Compare commits
24 Commits
dd456743f8
...
6957365723
Author | SHA1 | Date | |
---|---|---|---|
|
6957365723 | ||
|
f68f8d359e | ||
|
bfdd55e44a | ||
|
49b3fc3c46 | ||
|
9833e2ba16 | ||
|
bbdfe3b813 | ||
|
d89f744003 | ||
|
05e1bb5f92 | ||
|
bd6dade614 | ||
|
a670e201d6 | ||
|
349a93ab4b | ||
|
767f014107 | ||
|
13b457210e | ||
|
da13852216 | ||
|
2c2bfcbf0c | ||
|
82fd31802b | ||
|
0d8cdff6d7 | ||
|
578e796090 | ||
|
16871b0919 | ||
|
7f8bb9755f | ||
|
4529d2e8d3 | ||
|
94685e4654 | ||
|
908a6fd986 | ||
|
5a4666ae49 |
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,8 +1,8 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
/app/data
|
|
||||||
/.cache
|
/.cache
|
||||||
/build
|
/build
|
||||||
/coverage
|
/coverage
|
||||||
|
/data
|
||||||
/dev
|
/dev
|
||||||
.env
|
.env
|
||||||
|
|
|
@ -37,15 +37,19 @@ export default function traverse(node, visitor) {
|
||||||
throw new Error(`node type ${node.type} not traversable. (${Object.keys(node).join(', ')})`);
|
throw new Error(`node type ${node.type} not traversable. (${Object.keys(node).join(', ')})`);
|
||||||
}
|
}
|
||||||
visitor(node, 'enter');
|
visitor(node, 'enter');
|
||||||
const path = TRAVERSAL_PATH[node.type];
|
for (const key of TRAVERSAL_PATH[node.type]) {
|
||||||
const children = [];
|
if (Array.isArray(node[key])) {
|
||||||
for (const key of path) {
|
for (const child of node[key]) {
|
||||||
children.push(...(Array.isArray(node[key]) ? node[key] : [node[key]]));
|
|
||||||
}
|
|
||||||
for (const child of children) {
|
|
||||||
if (child) {
|
if (child) {
|
||||||
traverse(child, visitor);
|
traverse(child, visitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (node[key]) {
|
||||||
|
traverse(node[key], visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
visitor(node, 'exit');
|
visitor(node, 'exit');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Components from '@/ecs-components/index.js';
|
import Components from '@/ecs/components/index.js';
|
||||||
import Systems from '@/ecs-systems/index.js';
|
import Systems from '@/ecs/systems/index.js';
|
||||||
|
|
||||||
export default function createEcs(Ecs) {
|
export default function createEcs(Ecs) {
|
||||||
const ecs = new Ecs({Components, Systems});
|
const ecs = new Ecs({Components, Systems});
|
|
@ -1,4 +1,4 @@
|
||||||
import createEcs from './create-ecs.js';
|
import createEcs from './ecs.js';
|
||||||
|
|
||||||
export default async function createHouse(Ecs, id) {
|
export default async function createHouse(Ecs, id) {
|
||||||
const ecs = createEcs(Ecs);
|
const ecs = createEcs(Ecs);
|
|
@ -41,6 +41,7 @@ export default async function createPlayer(id) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Health: {health: 100},
|
Health: {health: 100},
|
||||||
|
Light: {},
|
||||||
Magnet: {strength: 24},
|
Magnet: {strength: 24},
|
||||||
Player: {},
|
Player: {},
|
||||||
Position: {x: 128, y: 128},
|
Position: {x: 128, y: 128},
|
|
@ -1,56 +0,0 @@
|
||||||
import mdx from 'remark-mdx';
|
|
||||||
import parse from 'remark-parse';
|
|
||||||
import {unified} from 'unified';
|
|
||||||
import {visitParents as visit} from 'unist-util-visit-parents';
|
|
||||||
|
|
||||||
const parser = unified().use(parse).use(mdx);
|
|
||||||
|
|
||||||
function computeParams(ancestors) {
|
|
||||||
const params = {};
|
|
||||||
for (let i = ancestors.length - 1; i >= 0; --i) {
|
|
||||||
const {dialogue} = ancestors[i];
|
|
||||||
if (dialogue) {
|
|
||||||
if (!(dialogue.name in params)) {
|
|
||||||
params[dialogue.name] = dialogue.params;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseLetters(source) {
|
|
||||||
const tree = parser.parse(source);
|
|
||||||
tree.dialogue = {
|
|
||||||
name: 'rate',
|
|
||||||
params: {frequency: 0.05, length: 0},
|
|
||||||
};
|
|
||||||
const letters = [];
|
|
||||||
visit(tree, (node, ancestors) => {
|
|
||||||
switch (node.type) {
|
|
||||||
case 'mdxJsxFlowElement':
|
|
||||||
case 'mdxJsxTextElement': {
|
|
||||||
node.dialogue = {name: node.name, params: {length: 0}};
|
|
||||||
for (const {name, value: {value}} of node.attributes) {
|
|
||||||
node.dialogue.params[name] = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'text': {
|
|
||||||
const params = computeParams(ancestors);
|
|
||||||
const split = node.value.split('');
|
|
||||||
for (let i = 0; i < split.length; ++i) {
|
|
||||||
const indices = {};
|
|
||||||
for (const name in params) {
|
|
||||||
indices[name] = i + params[name].length;
|
|
||||||
}
|
|
||||||
letters.push({character: split[i], indices, params});
|
|
||||||
}
|
|
||||||
for (const name in params) {
|
|
||||||
params[name].length += split.length;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return letters;
|
|
||||||
}
|
|
7
app/ecs/components/light.js
Normal file
7
app/ecs/components/light.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import Component from '@/ecs/component.js';
|
||||||
|
|
||||||
|
export default class Light extends Component {
|
||||||
|
static properties = {
|
||||||
|
radius: {type: 'uint8'},
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import {CHUNK_SIZE} from '@/constants.js';
|
|
||||||
import Component from '@/ecs/component.js';
|
import Component from '@/ecs/component.js';
|
||||||
|
import {CHUNK_SIZE} from '@/util/constants.js';
|
||||||
import {floodwalk2D, ortho, removeCollinear} from '@/util/math.js';
|
import {floodwalk2D, ortho, removeCollinear} from '@/util/math.js';
|
||||||
|
|
||||||
import vector2d from './helpers/vector-2d';
|
import vector2d from './helpers/vector-2d';
|
||||||
|
@ -99,7 +99,9 @@ class LayerProxy {
|
||||||
return this.instance.layers[this.index];
|
return this.instance.layers[this.index];
|
||||||
}
|
}
|
||||||
async load() {
|
async load() {
|
||||||
this.$$sourceJson = await this.Component.ecs.readJson(this.layer.source);
|
this.$$sourceJson = this.layer.source
|
||||||
|
? await this.Component.ecs.readJson(this.layer.source)
|
||||||
|
: {};
|
||||||
}
|
}
|
||||||
get source() {
|
get source() {
|
||||||
return this.layer.source;
|
return this.layer.source;
|
|
@ -1,5 +1,5 @@
|
||||||
import {IRL_MINUTES_PER_GAME_DAY} from '@/constants';
|
|
||||||
import Component from '@/ecs/component.js';
|
import Component from '@/ecs/component.js';
|
||||||
|
import {IRL_MINUTES_PER_GAME_DAY} from '@/util/constants';
|
||||||
|
|
||||||
const realSecondsPerGameDay = 60 * IRL_MINUTES_PER_GAME_DAY;
|
const realSecondsPerGameDay = 60 * IRL_MINUTES_PER_GAME_DAY;
|
||||||
const realSecondsPerGameHour = realSecondsPerGameDay / 24;
|
const realSecondsPerGameHour = realSecondsPerGameDay / 24;
|
|
@ -1,5 +1,5 @@
|
||||||
// import {RESOLUTION} from '@/constants.js'
|
|
||||||
import {System} from '@/ecs/index.js';
|
import {System} from '@/ecs/index.js';
|
||||||
|
// import {RESOLUTION} from '@/util/constants.js'
|
||||||
|
|
||||||
// const [hx, hy] = [RESOLUTION.x / 2, RESOLUTION.y / 2];
|
// const [hx, hy] = [RESOLUTION.x / 2, RESOLUTION.y / 2];
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
|
import Ecs from '@/ecs/ecs.js';
|
||||||
|
import {decode, encode} from '@/packets/index.js';
|
||||||
import {
|
import {
|
||||||
CHUNK_SIZE,
|
CHUNK_SIZE,
|
||||||
RESOLUTION,
|
RESOLUTION,
|
||||||
TPS,
|
TPS,
|
||||||
} from '@/constants.js';
|
} from '@/util/constants.js';
|
||||||
import Ecs from '@/ecs/ecs.js';
|
|
||||||
import {decode, encode} from '@/packets/index.js';
|
|
||||||
import Script from '@/util/script.js';
|
import Script from '@/util/script.js';
|
||||||
|
|
||||||
import createEcs from './create-ecs.js';
|
import createEcs from './create/ecs.js';
|
||||||
import createForest from './create-forest.js';
|
import createForest from './create/forest.js';
|
||||||
import createHomestead from './create-homestead.js';
|
import createHomestead from './create/homestead.js';
|
||||||
import createHouse from './create-house.js';
|
import createHouse from './create/house.js';
|
||||||
import createPlayer from './create-player.js';
|
import createPlayer from './create/player.js';
|
||||||
|
|
||||||
import {LRUCache} from 'lru-cache';
|
import {LRUCache} from 'lru-cache';
|
||||||
|
|
||||||
|
@ -252,6 +252,10 @@ export default class Engine {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createEcs() {
|
||||||
|
return createEcs(this.Ecs);
|
||||||
|
}
|
||||||
|
|
||||||
async disconnectPlayer(connection) {
|
async disconnectPlayer(connection) {
|
||||||
const connectedPlayer = this.connectedPlayers.get(connection);
|
const connectedPlayer = this.connectedPlayers.get(connection);
|
||||||
if (!connectedPlayer) {
|
if (!connectedPlayer) {
|
||||||
|
@ -270,7 +274,7 @@ export default class Engine {
|
||||||
|
|
||||||
async loadEcs(path) {
|
async loadEcs(path) {
|
||||||
this.ecses[path] = await this.Ecs.deserialize(
|
this.ecses[path] = await this.Ecs.deserialize(
|
||||||
createEcs(this.Ecs),
|
this.createEcs(),
|
||||||
await this.server.readData(path),
|
await this.server.readData(path),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -284,7 +288,7 @@ export default class Engine {
|
||||||
if ('ENOENT' !== error.code) {
|
if ('ENOENT' !== error.code) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const homestead = createEcs(this.Ecs);
|
const homestead = this.createEcs();
|
||||||
for (const entity of await createHomestead(id)) {
|
for (const entity of await createHomestead(id)) {
|
||||||
await homestead.create(entity);
|
await homestead.create(entity);
|
||||||
}
|
}
|
||||||
|
@ -297,7 +301,7 @@ export default class Engine {
|
||||||
['houses', `${id}`].join('/'),
|
['houses', `${id}`].join('/'),
|
||||||
house,
|
house,
|
||||||
);
|
);
|
||||||
const forest = createEcs(this.Ecs);
|
const forest = this.createEcs();
|
||||||
for (const entity of await createForest()) {
|
for (const entity of await createForest()) {
|
||||||
await forest.create(entity);
|
await forest.create(entity);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
import {expect, test} from 'vitest';
|
|
||||||
|
|
||||||
import {RESOLUTION} from '@/constants.js'
|
|
||||||
import Server from '@/net/server/server.js';
|
|
||||||
|
|
||||||
import Engine from './engine.js';
|
|
||||||
|
|
||||||
class TestServer extends Server {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.data = {};
|
|
||||||
}
|
|
||||||
async readAsset() {
|
|
||||||
return new ArrayBuffer(0);
|
|
||||||
}
|
|
||||||
async readData(path) {
|
|
||||||
if (path in this.data) {
|
|
||||||
return this.data[path];
|
|
||||||
}
|
|
||||||
const error = new Error();
|
|
||||||
error.code = 'ENOENT';
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
async writeData(path, view) {
|
|
||||||
this.data[path] = view;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test('visibility-based updates', async () => {
|
|
||||||
// const engine = new Engine(TestServer);
|
|
||||||
// // Connect an entity.
|
|
||||||
// await engine.connectPlayer(0, 0);
|
|
||||||
// const ecs = engine.ecses['homesteads/0'];
|
|
||||||
// // Create an entity.
|
|
||||||
// const entity = ecs.get(await ecs.create({
|
|
||||||
// Forces: {forceX: 1},
|
|
||||||
// Position: {x: (RESOLUTION.x * 1.5) + 32 - 3, y: 20},
|
|
||||||
// Sprite: {
|
|
||||||
// anchor: {x: 0.5, y: 0.8},
|
|
||||||
// animation: 'moving:down',
|
|
||||||
// frame: 0,
|
|
||||||
// frames: 8,
|
|
||||||
// source: '/assets/dude/dude.json',
|
|
||||||
// speed: 0.115,
|
|
||||||
// },
|
|
||||||
// VisibleAabb: {},
|
|
||||||
// }));
|
|
||||||
// const {entity: mainEntity} = engine.connectedPlayers.get(0);
|
|
||||||
// // Tick and get update. Should be a full update.
|
|
||||||
// engine.tick(1);
|
|
||||||
// expect(engine.updateFor(0))
|
|
||||||
// .to.deep.include({[mainEntity.id]: {MainEntity: {}, ...ecs.get(mainEntity.id).toJSON()}, [entity.id]: ecs.get(entity.id).toJSON()});
|
|
||||||
// engine.setClean();
|
|
||||||
// // Tick and get update. Should be a partial update.
|
|
||||||
// engine.tick(1);
|
|
||||||
// expect(engine.updateFor(0))
|
|
||||||
// .to.deep.include({
|
|
||||||
// [entity.id]: {
|
|
||||||
// Position: {x: (RESOLUTION.x * 1.5) + 32 - 1},
|
|
||||||
// VisibleAabb: {
|
|
||||||
// x0: 1199,
|
|
||||||
// x1: 1263,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// engine.setClean();
|
|
||||||
// // Tick and get update. Should remove the entity.
|
|
||||||
// engine.tick(1);
|
|
||||||
// expect(engine.updateFor(0))
|
|
||||||
// .to.deep.include({[entity.id]: false});
|
|
||||||
// // Aim back toward visible area and tick. Should be a full update for that entity.
|
|
||||||
// engine.setClean();
|
|
||||||
// entity.Forces.forceX = -1;
|
|
||||||
// engine.tick(1);
|
|
||||||
// expect(engine.updateFor(0))
|
|
||||||
// .to.deep.include({[entity.id]: ecs.get(entity.id).toJSON()});
|
|
||||||
});
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {CLIENT_LATENCY} from '@/constants.js';
|
import {CLIENT_LATENCY} from '@/util/constants.js';
|
||||||
|
|
||||||
export default class Client {
|
export default class Client {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {CLIENT_PREDICTION} from '@/constants.js';
|
|
||||||
import {encode} from '@/packets/index.js';
|
import {encode} from '@/packets/index.js';
|
||||||
|
import {CLIENT_PREDICTION} from '@/util/constants.js';
|
||||||
|
|
||||||
import Client from './client.js';
|
import Client from './client.js';
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {SERVER_LATENCY} from '@/constants.js';
|
import {SERVER_LATENCY} from '@/util/constants.js';
|
||||||
|
|
||||||
export default class Server {
|
export default class Server {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -2,10 +2,10 @@ import {del, get, set} from 'idb-keyval';
|
||||||
|
|
||||||
import {encode} from '@/packets/index.js';
|
import {encode} from '@/packets/index.js';
|
||||||
|
|
||||||
import createEcs from '../../create-ecs.js';
|
import createEcs from '../../create/ecs.js';
|
||||||
import '../../create-forest.js';
|
import '../../create/forest.js';
|
||||||
import '../../create-homestead.js';
|
import '../../create/homestead.js';
|
||||||
import '../../create-player.js';
|
import '../../create/player.js';
|
||||||
|
|
||||||
import Engine from '../../engine.js';
|
import Engine from '../../engine.js';
|
||||||
import Server from './server.js';
|
import Server from './server.js';
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
import {useEffect, useState} from 'react';
|
|
||||||
|
|
||||||
import {useClient} from '@/context/client.js';
|
|
||||||
|
|
||||||
import styles from './chat.module.css';
|
|
||||||
|
|
||||||
export default function Chat({
|
|
||||||
chatHistory,
|
|
||||||
onClose,
|
|
||||||
message,
|
|
||||||
setChatHistory,
|
|
||||||
setMessage,
|
|
||||||
}) {
|
|
||||||
const client = useClient();
|
|
||||||
const [disabled, setDisabled] = useState(true);
|
|
||||||
const [historyCaret, setHistoryCaret] = useState(0);
|
|
||||||
useEffect(() => {
|
|
||||||
setDisabled(false);
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<div className={styles.chat}>
|
|
||||||
<form
|
|
||||||
onSubmit={(event) => {
|
|
||||||
if (message) {
|
|
||||||
client.send({
|
|
||||||
type: 'Action',
|
|
||||||
payload: {type: 'chat', value: message},
|
|
||||||
});
|
|
||||||
setChatHistory([message, ...chatHistory]);
|
|
||||||
setMessage('');
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={(event) => {
|
|
||||||
setMessage(event.target.value);
|
|
||||||
}}
|
|
||||||
onKeyDown={(event) => {
|
|
||||||
switch (event.key) {
|
|
||||||
case 'ArrowDown': {
|
|
||||||
if (0 === historyCaret) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let localHistoryCaret = historyCaret - 1;
|
|
||||||
setMessage(chatHistory[localHistoryCaret])
|
|
||||||
setHistoryCaret(localHistoryCaret);
|
|
||||||
if (0 === localHistoryCaret) {
|
|
||||||
setChatHistory(chatHistory.slice(1));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ArrowUp': {
|
|
||||||
if (historyCaret === chatHistory.length - 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let localHistoryCaret = historyCaret;
|
|
||||||
let localChatHistory = chatHistory;
|
|
||||||
if (0 === historyCaret) {
|
|
||||||
localChatHistory = [message, ...localChatHistory];
|
|
||||||
setChatHistory(localChatHistory);
|
|
||||||
}
|
|
||||||
localHistoryCaret += 1;
|
|
||||||
setMessage(localChatHistory[localHistoryCaret])
|
|
||||||
setHistoryCaret(localHistoryCaret);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Escape': {
|
|
||||||
onClose();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onMouseDown={(event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
}}
|
|
||||||
maxLength="255"
|
|
||||||
ref={(element) => {
|
|
||||||
if (element) {
|
|
||||||
element.focus();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
type="text"
|
|
||||||
value={message}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
import {Sprite as PixiSprite} from '@pixi/react';
|
|
||||||
|
|
||||||
import {useAsset} from '@/context/assets.js';
|
|
||||||
|
|
||||||
export default function Sprite({entity, ...rest}) {
|
|
||||||
const asset = useAsset(entity.Sprite.source);
|
|
||||||
if (!asset) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let texture;
|
|
||||||
if (asset.data.animations) {
|
|
||||||
texture = asset.animations[entity.Sprite.animation][entity.Sprite.frame];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
texture = asset.textures[''];
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<PixiSprite
|
|
||||||
anchor={entity.Sprite.anchor}
|
|
||||||
scale={entity.Sprite.scale}
|
|
||||||
texture={texture}
|
|
||||||
x={Math.round(entity.Position.x)}
|
|
||||||
y={Math.round(entity.Position.y)}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -2,8 +2,8 @@ import {useState} from 'react';
|
||||||
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
||||||
import 'react-tabs/style/react-tabs.css';
|
import 'react-tabs/style/react-tabs.css';
|
||||||
|
|
||||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||||
import {useMainEntity} from '@/context/main-entity.js';
|
import {useMainEntity} from '@/react/context/main-entity.js';
|
||||||
|
|
||||||
import styles from './devtools.module.css';
|
import styles from './devtools.module.css';
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {useEffect, useRef, useState} from 'react';
|
import {useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
import {useClient} from '@/context/client.js';
|
import {useClient} from '@/react/context/client.js';
|
||||||
import {useEcs} from '@/context/ecs.js';
|
import {useEcs} from '@/react/context/ecs.js';
|
||||||
import useRect from '@/util/react-hooks/use-rect.js';
|
import useRect from '@/react/hooks/use-rect.js';
|
||||||
|
|
||||||
import styles from './tiles.module.css';
|
import styles from './tiles.module.css';
|
||||||
|
|
47
app/react/components/dom/chat/chat.jsx
Normal file
47
app/react/components/dom/chat/chat.jsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import styles from './chat.module.css';
|
||||||
|
|
||||||
|
import Input from './input.jsx';
|
||||||
|
import Messages from './messages.jsx';
|
||||||
|
|
||||||
|
export default function Chat({
|
||||||
|
chatHistory,
|
||||||
|
chatHistoryCaret,
|
||||||
|
chatInputRef,
|
||||||
|
chatMessages,
|
||||||
|
onClose,
|
||||||
|
message,
|
||||||
|
pendingMessage,
|
||||||
|
setChatHistoryCaret,
|
||||||
|
setChatHistory,
|
||||||
|
setMessage,
|
||||||
|
setPendingMessage,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||||
|
<div
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
onMouseUp={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={styles.chat}
|
||||||
|
>
|
||||||
|
<Messages
|
||||||
|
chatMessages={chatMessages}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
chatHistory={chatHistory}
|
||||||
|
chatHistoryCaret={chatHistoryCaret}
|
||||||
|
onClose={onClose}
|
||||||
|
message={message}
|
||||||
|
chatInputRef={chatInputRef}
|
||||||
|
pendingMessage={pendingMessage}
|
||||||
|
setChatHistory={setChatHistory}
|
||||||
|
setChatHistoryCaret={setChatHistoryCaret}
|
||||||
|
setMessage={setMessage}
|
||||||
|
setPendingMessage={setPendingMessage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
16
app/react/components/dom/chat/chat.module.css
Normal file
16
app/react/components/dom/chat/chat.module.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.chat {
|
||||||
|
background-color: #00000044;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
||||||
|
max-height: 25%;
|
||||||
|
position: absolute;
|
||||||
|
user-select: text;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Cookbook";
|
||||||
|
src: url("/assets/fonts/Cookbook.woff") format("woff");
|
||||||
|
}
|
89
app/react/components/dom/chat/input.jsx
Normal file
89
app/react/components/dom/chat/input.jsx
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
|
import {useClient} from '@/react/context/client.js';
|
||||||
|
|
||||||
|
import styles from './input.module.css';
|
||||||
|
|
||||||
|
export default function ChatInput({
|
||||||
|
setChatHistoryCaret,
|
||||||
|
chatHistory,
|
||||||
|
chatHistoryCaret,
|
||||||
|
chatInputRef,
|
||||||
|
message,
|
||||||
|
pendingMessage,
|
||||||
|
setChatHistory,
|
||||||
|
setMessage,
|
||||||
|
setPendingMessage,
|
||||||
|
}) {
|
||||||
|
const client = useClient();
|
||||||
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
setDisabled(false);
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className={styles.input}
|
||||||
|
onSubmit={(event) => {
|
||||||
|
if (message) {
|
||||||
|
client.send({
|
||||||
|
type: 'Action',
|
||||||
|
payload: {type: 'chat', value: message},
|
||||||
|
});
|
||||||
|
setChatHistory([message, ...chatHistory]);
|
||||||
|
setChatHistoryCaret(-1);
|
||||||
|
setPendingMessage('');
|
||||||
|
setMessage('');
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(event) => {
|
||||||
|
setMessage(event.target.value);
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowDown': {
|
||||||
|
if (-1 === chatHistoryCaret) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (0 === chatHistoryCaret) {
|
||||||
|
setMessage(pendingMessage);
|
||||||
|
setPendingMessage('');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setMessage(chatHistory[chatHistoryCaret - 1])
|
||||||
|
}
|
||||||
|
setChatHistoryCaret(chatHistoryCaret - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ArrowUp': {
|
||||||
|
if (chatHistory.length - 1 === chatHistoryCaret) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (-1 === chatHistoryCaret) {
|
||||||
|
setPendingMessage(message);
|
||||||
|
}
|
||||||
|
setMessage(chatHistory[chatHistoryCaret + 1])
|
||||||
|
setChatHistoryCaret(chatHistoryCaret + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
maxLength="255"
|
||||||
|
ref={(element) => {
|
||||||
|
chatInputRef.current = element;
|
||||||
|
if (element) {
|
||||||
|
element.focus();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
value={message}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,17 +1,15 @@
|
||||||
.chat {
|
|
||||||
background-color: #00000044;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat form {
|
.input {
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
background-color: #00000044;
|
background-color: #00000044;
|
||||||
border: 1px solid #333333;
|
border: 1px solid #333333;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
font-family: "Cookbook";
|
||||||
|
font-size: 16px;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 4px;
|
||||||
width: calc(100% - 8px);
|
width: calc(100% - 8px);
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
border: 1px solid #999999;
|
border: 1px solid #999999;
|
32
app/react/components/dom/chat/message.jsx
Normal file
32
app/react/components/dom/chat/message.jsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import {memo, useEffect, useState} from 'react';
|
||||||
|
|
||||||
|
import {useRadians} from '@/react/context/radians.js';
|
||||||
|
import {render} from '@/util/dialogue.js';
|
||||||
|
|
||||||
|
import styles from './message.module.css';
|
||||||
|
|
||||||
|
function Message({letters}) {
|
||||||
|
const radians = useRadians();
|
||||||
|
const [localRadians, setLocalRadians] = useState(radians);
|
||||||
|
const [isAnimating, setIsAnimating] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
const handle = setTimeout(() => {
|
||||||
|
setIsAnimating(false);
|
||||||
|
}, 2_000);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handle);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAnimating) {
|
||||||
|
setLocalRadians(radians)
|
||||||
|
}
|
||||||
|
}, [isAnimating, radians]);
|
||||||
|
return (
|
||||||
|
<div className={styles.message}>
|
||||||
|
{render(letters, '')(letters.length - 1, localRadians)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(Message);
|
4
app/react/components/dom/chat/message.module.css
Normal file
4
app/react/components/dom/chat/message.module.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.message {
|
||||||
|
color: #ffffff;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
20
app/react/components/dom/chat/messages.jsx
Normal file
20
app/react/components/dom/chat/messages.jsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import Message from './message.jsx';
|
||||||
|
|
||||||
|
import styles from './messages.module.css';
|
||||||
|
|
||||||
|
export default function Messages({chatMessages}) {
|
||||||
|
const messages = [];
|
||||||
|
for (const key in chatMessages) {
|
||||||
|
messages.push(
|
||||||
|
<Message
|
||||||
|
key={key}
|
||||||
|
letters={chatMessages[key]}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={styles.messages}>
|
||||||
|
{messages}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
5
app/react/components/dom/chat/messages.module.css
Normal file
5
app/react/components/dom/chat/messages.module.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.messages {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import {useEffect, useRef, useState} from 'react';
|
import {useEffect, useMemo, useRef, useState} from 'react';
|
||||||
|
|
||||||
import {RESOLUTION} from '@/constants.js';
|
import {useDomScale} from '@/react/context/dom-scale.js';
|
||||||
import {useDomScale} from '@/context/dom-scale.js';
|
import {useRadians} from '@/react/context/radians.js';
|
||||||
import {TAU} from '@/util/math.js';
|
import {RESOLUTION} from '@/util/constants.js';
|
||||||
|
import {render} from '@/util/dialogue.js';
|
||||||
|
|
||||||
import styles from './dialogue.module.css';
|
import styles from './dialogue.module.css';
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ export default function Dialogue({
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
const [dimensions, setDimensions] = useState({h: 0, w: 0});
|
const [dimensions, setDimensions] = useState({h: 0, w: 0});
|
||||||
const [caret, setCaret] = useState(0);
|
const [caret, setCaret] = useState(0);
|
||||||
const [radians, setRadians] = useState(0);
|
const radians = useRadians();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return dialogue.addSkipListener(() => {
|
return dialogue.addSkipListener(() => {
|
||||||
if (caret >= dialogue.letters.length - 1) {
|
if (caret >= dialogue.letters.length - 1) {
|
||||||
|
@ -28,24 +29,6 @@ export default function Dialogue({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [caret, dialogue]);
|
}, [caret, dialogue]);
|
||||||
useEffect(() => {
|
|
||||||
setRadians(0);
|
|
||||||
let handle;
|
|
||||||
let last;
|
|
||||||
const spin = (ts) => {
|
|
||||||
if ('undefined' === typeof last) {
|
|
||||||
last = ts;
|
|
||||||
}
|
|
||||||
const elapsed = (ts - last) / 1000;
|
|
||||||
last = ts;
|
|
||||||
setRadians((radians) => radians + (elapsed * TAU));
|
|
||||||
handle = requestAnimationFrame(spin);
|
|
||||||
};
|
|
||||||
handle = requestAnimationFrame(spin);
|
|
||||||
return () => {
|
|
||||||
cancelAnimationFrame(handle);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const {params} = dialogue.letters[caret];
|
const {params} = dialogue.letters[caret];
|
||||||
let handle;
|
let handle;
|
||||||
|
@ -88,6 +71,10 @@ export default function Dialogue({
|
||||||
cancelAnimationFrame(handle);
|
cancelAnimationFrame(handle);
|
||||||
};
|
};
|
||||||
}, [dialogue, domScale, ref]);
|
}, [dialogue, domScale, ref]);
|
||||||
|
const localRender = useMemo(
|
||||||
|
() => render(dialogue.letters, styles.letter),
|
||||||
|
[dialogue.letters],
|
||||||
|
);
|
||||||
const origin = 'function' === typeof dialogue.origin
|
const origin = 'function' === typeof dialogue.origin
|
||||||
? dialogue.origin()
|
? dialogue.origin()
|
||||||
: dialogue.origin || {x: 0, y: 0};
|
: dialogue.origin || {x: 0, y: 0};
|
||||||
|
@ -175,62 +162,7 @@ export default function Dialogue({
|
||||||
<polygon points="0 0, 24 0, 12 24" />
|
<polygon points="0 0, 24 0, 12 24" />
|
||||||
</svg>
|
</svg>
|
||||||
<p className={styles.letters}>
|
<p className={styles.letters}>
|
||||||
{
|
{localRender(caret, radians)}
|
||||||
dialogue.letters
|
|
||||||
.map(({character, indices, params}, i) => {
|
|
||||||
let color = 'inherit';
|
|
||||||
let fade = 0;
|
|
||||||
let fontStyle = 'normal';
|
|
||||||
let fontWeight = 'normal';
|
|
||||||
let left = 0;
|
|
||||||
let opacity = 1;
|
|
||||||
let top = 0;
|
|
||||||
if (params.blink) {
|
|
||||||
const {frequency = 1} = params.blink;
|
|
||||||
opacity = (radians * (2 / frequency) % TAU) > (TAU / 2) ? opacity : 0;
|
|
||||||
}
|
|
||||||
if (params.fade) {
|
|
||||||
const {frequency = 1} = params.fade;
|
|
||||||
fade = frequency;
|
|
||||||
}
|
|
||||||
if (params.wave) {
|
|
||||||
const {frequency = 1, magnitude = 3} = params.wave;
|
|
||||||
top += magnitude * Math.cos((radians * (1 / frequency)) + TAU * indices.wave / params.wave.length);
|
|
||||||
}
|
|
||||||
if (params.rainbow) {
|
|
||||||
const {frequency = 1} = params.rainbow;
|
|
||||||
color = `hsl(${(radians * (1 / frequency)) + TAU * indices.rainbow / params.rainbow.length}rad 100 50)`;
|
|
||||||
}
|
|
||||||
if (params.shake) {
|
|
||||||
const {magnitude = 1} = params.shake;
|
|
||||||
left += (Math.random() * magnitude * 2) - magnitude;
|
|
||||||
top += (Math.random() * magnitude * 2) - magnitude;
|
|
||||||
}
|
|
||||||
if (params.em) {
|
|
||||||
fontStyle = 'italic';
|
|
||||||
}
|
|
||||||
if (params.strong) {
|
|
||||||
fontWeight = 'bold';
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className={styles.letter}
|
|
||||||
key={i}
|
|
||||||
style={{
|
|
||||||
color,
|
|
||||||
fontStyle,
|
|
||||||
fontWeight,
|
|
||||||
left: `${left}px`,
|
|
||||||
opacity: i <= caret ? opacity : 0,
|
|
||||||
top: `${top}px`,
|
|
||||||
transition: `opacity ${fade}s`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{character}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -11,6 +11,7 @@
|
||||||
border: solid 1px white;
|
border: solid 1px white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: white;
|
color: white;
|
||||||
|
overflow-wrap: break-word;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -26,5 +27,4 @@
|
||||||
|
|
||||||
.letter {
|
.letter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import {useEffect, useRef, useState} from 'react';
|
import {useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
import {RESOLUTION} from '@/constants.js';
|
import DomContext from '@/react/context/dom-scale.js';
|
||||||
import DomContext from '@/context/dom-scale.js';
|
import {RESOLUTION} from '@/util/constants.js';
|
||||||
|
|
||||||
import styles from './dom.module.css';
|
import styles from './dom.module.css';
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import {useState} from 'react';
|
import {useState} from 'react';
|
||||||
|
|
||||||
import {usePacket} from '@/context/client.js';
|
import {usePacket} from '@/react/context/client.js';
|
||||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||||
import {parseLetters} from '@/dialogue.js';
|
import {parseLetters} from '@/util/dialogue.js';
|
||||||
|
|
||||||
import Entity from './entity.jsx';
|
import Entity from './entity.jsx';
|
||||||
|
|
||||||
export default function Entities({camera, scale, setMonopolizers}) {
|
export default function Entities({
|
||||||
|
camera,
|
||||||
|
scale,
|
||||||
|
setChatMessages,
|
||||||
|
setMonopolizers,
|
||||||
|
}) {
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [entities, setEntities] = useState({});
|
const [entities, setEntities] = useState({});
|
||||||
usePacket('EcsChange', async () => {
|
usePacket('EcsChange', async () => {
|
||||||
|
@ -34,6 +39,10 @@ export default function Entities({camera, scale, setMonopolizers}) {
|
||||||
for (const key in dialogue) {
|
for (const key in dialogue) {
|
||||||
dialogues[key] = dialogue[key];
|
dialogues[key] = dialogue[key];
|
||||||
dialogues[key].letters = parseLetters(dialogues[key].body);
|
dialogues[key].letters = parseLetters(dialogues[key].body);
|
||||||
|
setChatMessages((chatMessages) => ({
|
||||||
|
[[id, key].join('-')]: dialogues[key].letters,
|
||||||
|
...chatMessages,
|
||||||
|
}));
|
||||||
const skipListeners = new Set();
|
const skipListeners = new Set();
|
||||||
dialogues[key].addSkipListener = (listener) => {
|
dialogues[key].addSkipListener = (listener) => {
|
||||||
skipListeners.add(listener);
|
skipListeners.add(listener);
|
|
@ -1,8 +1,10 @@
|
||||||
import {Container} from '@pixi/react';
|
import {Container} from '@pixi/react';
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||||
import {useMainEntity} from '@/context/main-entity.js';
|
import {useMainEntity} from '@/react/context/main-entity.js';
|
||||||
|
// import {useRadians} from '@/react/context/radians.js';
|
||||||
|
// import {TAU} from '@/util/math.js';
|
||||||
|
|
||||||
import Entities from './entities.jsx';
|
import Entities from './entities.jsx';
|
||||||
import TargetingGhost from './targeting-ghost.jsx';
|
import TargetingGhost from './targeting-ghost.jsx';
|
||||||
|
@ -36,6 +38,26 @@ export default function Ecs({applyFilters, camera, monopolizers, scale}) {
|
||||||
const [projected, setProjected] = useState([]);
|
const [projected, setProjected] = useState([]);
|
||||||
const [position, setPosition] = useState({x: 0, y: 0});
|
const [position, setPosition] = useState({x: 0, y: 0});
|
||||||
const [water, setWater] = useState();
|
const [water, setWater] = useState();
|
||||||
|
// const radians = useRadians();
|
||||||
|
// const [sine, setSine] = useState();
|
||||||
|
// useEffect(() => {
|
||||||
|
// async function buildSineFilter() {
|
||||||
|
// const {default: SineFilter} = await import('./filters/horizontal-sine.js');
|
||||||
|
// const sine = new SineFilter();
|
||||||
|
// sine.frequency = 1;
|
||||||
|
// sine.magnitude = 3;
|
||||||
|
// setSine(sine);
|
||||||
|
// }
|
||||||
|
// buildSineFilter();
|
||||||
|
// }, []);
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (!sine) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const r = (radians / 8) % TAU;
|
||||||
|
// sine.offset = 6 * (camera.y + r);
|
||||||
|
// sine.magnitude = 2 * (r > Math.PI ? TAU - r : r);
|
||||||
|
// }, [camera, radians, scale, sine]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function buildNightFilter() {
|
async function buildNightFilter() {
|
||||||
const {ColorMatrixFilter} = await import('@pixi/filter-color-matrix');
|
const {ColorMatrixFilter} = await import('@pixi/filter-color-matrix');
|
||||||
|
@ -89,15 +111,16 @@ export default function Ecs({applyFilters, camera, monopolizers, scale}) {
|
||||||
setProjected(Wielder.activeItem()?.project(Position.tile, Direction.direction));
|
setProjected(Wielder.activeItem()?.project(Position.tile, Direction.direction));
|
||||||
}
|
}
|
||||||
}, [ecs, mainEntity, scale]);
|
}, [ecs, mainEntity, scale]);
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// setFilters(
|
setFilters(
|
||||||
// applyFilters
|
applyFilters
|
||||||
// ? [
|
? [
|
||||||
// ...(night ? [night] : [])
|
...(false && night ? [night] : []),
|
||||||
// ]
|
// ...(sine ? [sine] : []),
|
||||||
// : [],
|
]
|
||||||
// );
|
: [],
|
||||||
// }, [applyFilters, night])
|
);
|
||||||
|
}, [applyFilters, night])
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
scale={scale}
|
scale={scale}
|
|
@ -1,11 +1,12 @@
|
||||||
import {AdjustmentFilter} from '@pixi/filter-adjustment';
|
import {AdjustmentFilter} from '@pixi/filter-adjustment';
|
||||||
import {GlowFilter} from '@pixi/filter-glow';
|
import {GlowFilter} from '@pixi/filter-glow';
|
||||||
import {Container} from '@pixi/react';
|
import {Container} from '@pixi/react';
|
||||||
import {useEffect, useState} from 'react';
|
import {useState} from 'react';
|
||||||
|
|
||||||
import {usePacket} from '@/context/client.js';
|
import {usePacket} from '@/react/context/client.js';
|
||||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/react/context/ecs.js';
|
||||||
import {useMainEntity} from '@/context/main-entity.js';
|
import {useMainEntity} from '@/react/context/main-entity.js';
|
||||||
|
import {useRadians} from '@/react/context/radians.js';
|
||||||
|
|
||||||
import Entity from './entity.jsx';
|
import Entity from './entity.jsx';
|
||||||
|
|
||||||
|
@ -13,10 +14,13 @@ export default function Entities({filters, monopolizers}) {
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [entities, setEntities] = useState({});
|
const [entities, setEntities] = useState({});
|
||||||
const [mainEntity] = useMainEntity();
|
const [mainEntity] = useMainEntity();
|
||||||
const [radians, setRadians] = useState(0);
|
const radians = useRadians();
|
||||||
const [willInteractWith, setWillInteractWith] = useState(0);
|
const [willInteractWith, setWillInteractWith] = useState(0);
|
||||||
const [interactionFilters] = useState([new AdjustmentFilter(), new GlowFilter({color: 0x0})]);
|
const [interactionFilters] = useState([
|
||||||
const pulse = (Math.cos(radians) + 1) * 0.5;
|
new AdjustmentFilter(),
|
||||||
|
new GlowFilter({color: 0x0}),
|
||||||
|
]);
|
||||||
|
const pulse = (Math.cos(radians / 4) + 1) * 0.5;
|
||||||
interactionFilters[0].brightness = (pulse * 0.75) + 1;
|
interactionFilters[0].brightness = (pulse * 0.75) + 1;
|
||||||
interactionFilters[1].outerStrength = pulse * 0.5;
|
interactionFilters[1].outerStrength = pulse * 0.5;
|
||||||
usePacket('EcsChange', async () => {
|
usePacket('EcsChange', async () => {
|
||||||
|
@ -64,15 +68,6 @@ export default function Entities({filters, monopolizers}) {
|
||||||
setWillInteractWith(main.Interacts.willInteractWith);
|
setWillInteractWith(main.Interacts.willInteractWith);
|
||||||
}
|
}
|
||||||
}, [ecs, mainEntity]);
|
}, [ecs, mainEntity]);
|
||||||
useEffect(() => {
|
|
||||||
setRadians(0);
|
|
||||||
const handle = setInterval(() => {
|
|
||||||
setRadians((radians) => (radians + 0.1) % (Math.PI * 2))
|
|
||||||
}, 50);
|
|
||||||
return () => {
|
|
||||||
clearInterval(handle);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
const renderables = [];
|
const renderables = [];
|
||||||
for (const id in entities) {
|
for (const id in entities) {
|
||||||
const isHighlightedInteraction = 0 === monopolizers.length && id == willInteractWith;
|
const isHighlightedInteraction = 0 === monopolizers.length && id == willInteractWith;
|
|
@ -1,10 +1,11 @@
|
||||||
import {Container, Graphics} from '@pixi/react';
|
import {Container, Graphics} from '@pixi/react';
|
||||||
import {memo, useCallback} from 'react';
|
import {memo, useCallback} from 'react';
|
||||||
|
|
||||||
import {useDebug} from '@/context/debug.js';
|
import {useDebug} from '@/react/context/debug.js';
|
||||||
import {useMainEntity} from '@/context/main-entity.js';
|
import {useMainEntity} from '@/react/context/main-entity.js';
|
||||||
|
|
||||||
import Emitter from './emitter.jsx';
|
import Emitter from './emitter.jsx';
|
||||||
|
import Light from './light.jsx';
|
||||||
import Sprite from './sprite.jsx';
|
import Sprite from './sprite.jsx';
|
||||||
|
|
||||||
function Aabb({color, width = 0.5, x0, y0, x1, y1, ...rest}) {
|
function Aabb({color, width = 0.5, x0, y0, x1, y1, ...rest}) {
|
||||||
|
@ -66,6 +67,12 @@ function Entity({entity, ...rest}) {
|
||||||
entity={entity}
|
entity={entity}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{/* {entity.Light && (
|
||||||
|
<Light
|
||||||
|
x={entity.Position.x}
|
||||||
|
y={entity.Position.y}
|
||||||
|
/>
|
||||||
|
)} */}
|
||||||
{debug && entity.Position && (
|
{debug && entity.Position && (
|
||||||
<Crosshair x={entity.Position.x} y={entity.Position.y} />
|
<Crosshair x={entity.Position.x} y={entity.Position.y} />
|
||||||
)}
|
)}
|
32
app/react/components/pixi/extensions.js
Normal file
32
app/react/components/pixi/extensions.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import {ExtensionType} from '@pixi/core';
|
||||||
|
import {Stage as LayerStage} from '@pixi/layers';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AmbientLight,
|
||||||
|
deferredLighting,
|
||||||
|
} from './lights.js';
|
||||||
|
|
||||||
|
export const ApplicationStageLayers = {
|
||||||
|
type: ExtensionType.Application,
|
||||||
|
priority: 100,
|
||||||
|
ref: {
|
||||||
|
destroy: function() {},
|
||||||
|
init: function() {
|
||||||
|
this.stage = new LayerStage();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApplicationStageLights = {
|
||||||
|
type: ExtensionType.Application,
|
||||||
|
ref: {
|
||||||
|
destroy: function() {},
|
||||||
|
init: function() {
|
||||||
|
const {stage} = this;
|
||||||
|
deferredLighting.addToStage(stage);
|
||||||
|
// const ambientLight = new AmbientLight(0x2244cc, 0.25);
|
||||||
|
const ambientLight = new AmbientLight(0xffffff, 1);
|
||||||
|
stage.addChild(ambientLight);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
41
app/react/components/pixi/filters/horizontal-sine.frag
Normal file
41
app/react/components/pixi/filters/horizontal-sine.frag
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#define PI_D3 1.047197551
|
||||||
|
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
uniform vec4 filterArea;
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
|
||||||
|
uniform float frequency;
|
||||||
|
uniform bool isPixelated;
|
||||||
|
uniform float magnitude;
|
||||||
|
uniform float offset;
|
||||||
|
uniform float scale;
|
||||||
|
|
||||||
|
vec2 denormalizeCoordinates(vec2 coord) {
|
||||||
|
return coord * filterArea.xy + filterArea.zw;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 normalizeCoordinates( vec2 coord ) {
|
||||||
|
return (coord - filterArea.zw) / filterArea.xy;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
// Denormalize coordinates.
|
||||||
|
vec2 coord = denormalizeCoordinates(vTextureCoord);
|
||||||
|
// Calculate rotation radians.
|
||||||
|
float rads = mod(
|
||||||
|
frequency * ((((isPixelated ? floor(offset) : offset) * scale) + coord.y) / scale),
|
||||||
|
6.
|
||||||
|
);
|
||||||
|
if (isPixelated) {
|
||||||
|
rads = floor(rads / frequency) * frequency;
|
||||||
|
}
|
||||||
|
// Apply horizontal shift.
|
||||||
|
float xOff = scale * magnitude * cos(PI_D3 * rads);
|
||||||
|
if (isPixelated) {
|
||||||
|
xOff = floor(xOff / scale) * scale;
|
||||||
|
}
|
||||||
|
coord.x += xOff;
|
||||||
|
// Normalize and apply coordinates.
|
||||||
|
coord = normalizeCoordinates(coord);
|
||||||
|
gl_FragColor = texture2D(uSampler, coord);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user