Compare commits
18 Commits
3d4d29625d
...
92a77d8046
Author | SHA1 | Date | |
---|---|---|---|
|
92a77d8046 | ||
|
78060ded37 | ||
|
7141d20a94 | ||
|
bb0c5ab1e9 | ||
|
d086f802f8 | ||
|
375b83f366 | ||
|
34cd695a80 | ||
|
71c4f1f959 | ||
|
80a2833842 | ||
|
dfc7665856 | ||
|
ece20dfcd7 | ||
|
dcf6fff66d | ||
|
a8d345eebe | ||
|
ef744591d2 | ||
|
e2c62a6522 | ||
|
a208863823 | ||
|
354b013e70 | ||
|
14c15bbf4a |
|
@ -41,10 +41,22 @@ export default class ClientEcs extends Ecs {
|
||||||
}
|
}
|
||||||
return cache.get(key);
|
return cache.get(key);
|
||||||
}
|
}
|
||||||
async readScript(uri, context = {}) {
|
async readScript(uriOrCode, context = {}) {
|
||||||
const code = await this.readAsset(uri);
|
if (!uriOrCode) {
|
||||||
if (code.byteLength > 0) {
|
return undefined;
|
||||||
return Script.fromCode((new TextDecoder()).decode(code), context);
|
}
|
||||||
|
let code = '';
|
||||||
|
if (!uriOrCode.startsWith('/')) {
|
||||||
|
code = uriOrCode;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const buffer = await this.readAsset(uriOrCode);
|
||||||
|
if (buffer.byteLength > 0) {
|
||||||
|
code = (new TextDecoder()).decode(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (code) {
|
||||||
|
return Script.fromCode(code, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
app/context/dom-scale.js
Normal file
9
app/context/dom-scale.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import {createContext, useContext} from 'react';
|
||||||
|
|
||||||
|
const context = createContext();
|
||||||
|
|
||||||
|
export default context;
|
||||||
|
|
||||||
|
export function useDomScale() {
|
||||||
|
return useContext(context);
|
||||||
|
}
|
|
@ -3,8 +3,6 @@ import {createNoise2D} from 'simplex-noise';
|
||||||
|
|
||||||
import {createRandom, Generator} from '@/util/math.js';
|
import {createRandom, Generator} from '@/util/math.js';
|
||||||
|
|
||||||
import createEcs from './create-ecs.js';
|
|
||||||
|
|
||||||
const seed = 42069;
|
const seed = 42069;
|
||||||
const prng = alea(seed);
|
const prng = alea(seed);
|
||||||
const rawNoise = createNoise2D(prng);
|
const rawNoise = createNoise2D(prng);
|
||||||
|
@ -54,10 +52,10 @@ const Forest = new Generator({
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default async function createForest(Ecs) {
|
export default async function createForest() {
|
||||||
const ecs = createEcs(Ecs);
|
|
||||||
const area = {x: w, y: h};
|
const area = {x: w, y: h};
|
||||||
const master = ecs.get(await ecs.create({
|
const entities = [];
|
||||||
|
entities.push({
|
||||||
AreaSize: {x: area.x * 16, y: area.y * 16},
|
AreaSize: {x: area.x * 16, y: area.y * 16},
|
||||||
Ticking: {},
|
Ticking: {},
|
||||||
TileLayers: {
|
TileLayers: {
|
||||||
|
@ -77,8 +75,10 @@ export default async function createForest(Ecs) {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Time: {},
|
Time: {},
|
||||||
}));
|
Water: {},
|
||||||
|
});
|
||||||
Forest.generate();
|
Forest.generate();
|
||||||
|
const master = entities[0];
|
||||||
const layer0 = master.TileLayers.layers[0];
|
const layer0 = master.TileLayers.layers[0];
|
||||||
layer0.data = Forest.matrix;
|
layer0.data = Forest.matrix;
|
||||||
for (let y = 0; y < h; ++y) {
|
for (let y = 0; y < h; ++y) {
|
||||||
|
@ -109,7 +109,7 @@ export default async function createForest(Ecs) {
|
||||||
if (v > 0.2) {
|
if (v > 0.2) {
|
||||||
v = noise(x * (1 / 7), y * (1 / 7));
|
v = noise(x * (1 / 7), y * (1 / 7));
|
||||||
if (v < 0.15) {
|
if (v < 0.15) {
|
||||||
await ecs.create({
|
entities.push({
|
||||||
Position: entityPosition(x, y),
|
Position: entityPosition(x, y),
|
||||||
Sprite: {
|
Sprite: {
|
||||||
anchorY: 0.7,
|
anchorY: 0.7,
|
||||||
|
@ -119,7 +119,7 @@ export default async function createForest(Ecs) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (v < 0.17) {
|
else if (v < 0.17) {
|
||||||
await ecs.create({
|
entities.push({
|
||||||
Position: entityPosition(x, y),
|
Position: entityPosition(x, y),
|
||||||
Sprite: {
|
Sprite: {
|
||||||
anchorY: 0.875,
|
anchorY: 0.875,
|
||||||
|
@ -130,7 +130,7 @@ export default async function createForest(Ecs) {
|
||||||
}
|
}
|
||||||
v = noise(x * (1 / 12), y * (1 / 12));
|
v = noise(x * (1 / 12), y * (1 / 12));
|
||||||
if (v < 0.08) {
|
if (v < 0.08) {
|
||||||
await ecs.create({
|
entities.push({
|
||||||
Position: entityPosition(x, y),
|
Position: entityPosition(x, y),
|
||||||
Sprite: {
|
Sprite: {
|
||||||
anchorY: 0.7,
|
anchorY: 0.7,
|
||||||
|
@ -142,6 +142,6 @@ export default async function createForest(Ecs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ecs;
|
return entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import createEcs from './create-ecs.js';
|
export default async function createHomestead(id) {
|
||||||
|
|
||||||
export default async function createHomestead(Ecs, id) {
|
|
||||||
const ecs = createEcs(Ecs);
|
|
||||||
const area = {x: 100, y: 60};
|
const area = {x: 100, y: 60};
|
||||||
await ecs.create({
|
const entities = [];
|
||||||
|
entities.push({
|
||||||
AreaSize: {x: area.x * 16, y: area.y * 16},
|
AreaSize: {x: area.x * 16, y: area.y * 16},
|
||||||
Ticking: {},
|
Ticking: {},
|
||||||
TileLayers: {
|
TileLayers: {
|
||||||
|
@ -25,7 +23,7 @@ export default async function createHomestead(Ecs, id) {
|
||||||
Time: {},
|
Time: {},
|
||||||
Water: {water: {}},
|
Water: {water: {}},
|
||||||
});
|
});
|
||||||
await ecs.create({
|
entities.push({
|
||||||
Collider: {
|
Collider: {
|
||||||
bodies: [
|
bodies: [
|
||||||
{
|
{
|
||||||
|
@ -61,5 +59,39 @@ export default async function createHomestead(Ecs, id) {
|
||||||
Ticking: {},
|
Ticking: {},
|
||||||
VisibleAabb: {},
|
VisibleAabb: {},
|
||||||
});
|
});
|
||||||
return ecs;
|
entities.push({
|
||||||
|
Collider: {
|
||||||
|
bodies: [
|
||||||
|
{
|
||||||
|
impassable: 1,
|
||||||
|
points: [
|
||||||
|
{x: -11, y: -7},
|
||||||
|
{x: 10, y: -7},
|
||||||
|
{x: 10, y: 5},
|
||||||
|
{x: -11, y: 5},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Interactive: {
|
||||||
|
interacting: 1,
|
||||||
|
interactScript: `
|
||||||
|
subject.Interlocutor.dialogue({
|
||||||
|
body: 'Hey what is up :)',
|
||||||
|
origin: subject.Position.toJSON(),
|
||||||
|
position: {x: subject.Position.x, y: subject.Position.y - 32},
|
||||||
|
})
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
Interlocutor: {},
|
||||||
|
Position: {x: 200, y: 200},
|
||||||
|
Sprite: {
|
||||||
|
anchorX: 0.5,
|
||||||
|
anchorY: 0.7,
|
||||||
|
source: '/assets/chest.json',
|
||||||
|
},
|
||||||
|
Ticking: {},
|
||||||
|
VisibleAabb: {},
|
||||||
|
});
|
||||||
|
return entities;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default async function createPlayer(id) {
|
||||||
},
|
},
|
||||||
Controlled: {},
|
Controlled: {},
|
||||||
Direction: {direction: 2},
|
Direction: {direction: 2},
|
||||||
Ecs: {path: ['forests', `${id}`].join('/')},
|
Ecs: {path: ['homesteads', `${id}`].join('/')},
|
||||||
Emitter: {},
|
Emitter: {},
|
||||||
Forces: {},
|
Forces: {},
|
||||||
Interacts: {},
|
Interacts: {},
|
||||||
|
|
|
@ -13,9 +13,8 @@ export default class Emitter extends Component {
|
||||||
}
|
}
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const Component = this;
|
const Component = this;
|
||||||
const Instance = super.instanceFromSchema();
|
return class EmitterInstance extends super.instanceFromSchema() {
|
||||||
return class EmitterInstance extends Instance {
|
emitting = {};
|
||||||
emitting = [];
|
|
||||||
id = 0;
|
id = 0;
|
||||||
emit(specification) {
|
emit(specification) {
|
||||||
Component.markChange(this.entity, 'emit', {[this.id++]: specification});
|
Component.markChange(this.entity, 'emit', {[this.id++]: specification});
|
||||||
|
|
24
app/ecs-components/interlocutor.js
Normal file
24
app/ecs-components/interlocutor.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import Component from '@/ecs/component.js';
|
||||||
|
|
||||||
|
export default class Interlocutor extends Component {
|
||||||
|
mergeDiff(original, update) {
|
||||||
|
const merged = {};
|
||||||
|
if (update.dialogue) {
|
||||||
|
merged.dialogue = {
|
||||||
|
...original.dialogue,
|
||||||
|
...update.dialogue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
instanceFromSchema() {
|
||||||
|
const Component = this;
|
||||||
|
return class InterlocutorInstance extends super.instanceFromSchema() {
|
||||||
|
dialogues = {};
|
||||||
|
id = 0;
|
||||||
|
dialogue(specification) {
|
||||||
|
Component.markChange(this.entity, 'dialogue', {[this.id++]: specification});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -82,7 +82,7 @@ class ItemProxy {
|
||||||
if (this.scripts.projectionCheckInstance) {
|
if (this.scripts.projectionCheckInstance) {
|
||||||
this.scripts.projectionCheckInstance.context.ecs = this.Component.ecs;
|
this.scripts.projectionCheckInstance.context.ecs = this.Component.ecs;
|
||||||
this.scripts.projectionCheckInstance.context.projected = projected;
|
this.scripts.projectionCheckInstance.context.projected = projected;
|
||||||
return this.scripts.projectionCheckInstance.evaluateSync();
|
return this.scripts.projectionCheckInstance.evaluate();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return projected;
|
return projected;
|
||||||
|
|
|
@ -7,7 +7,7 @@ export default class Plant extends Component {
|
||||||
const Instance = super.instanceFromSchema();
|
const Instance = super.instanceFromSchema();
|
||||||
return class PlantInstance extends Instance {
|
return class PlantInstance extends Instance {
|
||||||
mayGrow() {
|
mayGrow() {
|
||||||
return this.mayGrowScriptInstance.evaluateSync();
|
return this.mayGrowScriptInstance.evaluate();
|
||||||
}
|
}
|
||||||
grow() {
|
grow() {
|
||||||
const {Ticking} = ecs.get(this.entity);
|
const {Ticking} = ecs.get(this.entity);
|
||||||
|
|
|
@ -76,13 +76,22 @@ export default class Engine {
|
||||||
}
|
}
|
||||||
return cache.get(key);
|
return cache.get(key);
|
||||||
}
|
}
|
||||||
async readScript(uri, context) {
|
async readScript(uriOrCode, context) {
|
||||||
if (!uri) {
|
if (!uriOrCode) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const code = await this.readAsset(uri);
|
let code = '';
|
||||||
if (code.byteLength > 0) {
|
if (!uriOrCode.startsWith('/')) {
|
||||||
return Script.fromCode((new TextDecoder()).decode(code), context);
|
code = uriOrCode;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const buffer = await this.readAsset(uriOrCode);
|
||||||
|
if (buffer.byteLength > 0) {
|
||||||
|
code = (new TextDecoder()).decode(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (code) {
|
||||||
|
return Script.fromCode(code, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async switchEcs(entity, path, updates) {
|
async switchEcs(entity, path, updates) {
|
||||||
|
@ -258,7 +267,10 @@ export default class Engine {
|
||||||
if ('ENOENT' !== error.code) {
|
if ('ENOENT' !== error.code) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const homestead = await createHomestead(this.Ecs, id);
|
const homestead = createEcs(this.Ecs);
|
||||||
|
for (const entity of await createHomestead(id)) {
|
||||||
|
await homestead.create(entity);
|
||||||
|
}
|
||||||
await this.saveEcs(
|
await this.saveEcs(
|
||||||
['homesteads', `${id}`].join('/'),
|
['homesteads', `${id}`].join('/'),
|
||||||
homestead,
|
homestead,
|
||||||
|
@ -268,7 +280,10 @@ export default class Engine {
|
||||||
['houses', `${id}`].join('/'),
|
['houses', `${id}`].join('/'),
|
||||||
house,
|
house,
|
||||||
);
|
);
|
||||||
const forest = await createForest(this.Ecs, id);
|
const forest = createEcs(this.Ecs);
|
||||||
|
for (const entity of await createForest()) {
|
||||||
|
await forest.create(entity);
|
||||||
|
}
|
||||||
await this.saveEcs(
|
await this.saveEcs(
|
||||||
['forests', `${id}`].join('/'),
|
['forests', `${id}`].join('/'),
|
||||||
forest,
|
forest,
|
||||||
|
|
|
@ -2,6 +2,7 @@ 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 '../../create-forest.js';
|
import '../../create-forest.js';
|
||||||
import '../../create-homestead.js';
|
import '../../create-homestead.js';
|
||||||
import '../../create-player.js';
|
import '../../create-player.js';
|
||||||
|
@ -70,7 +71,7 @@ if (import.meta.hot) {
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
const beforeResolver = createResolver();
|
const beforeResolver = createResolver();
|
||||||
const resolvers = [];
|
const resolvers = [beforeResolver];
|
||||||
import.meta.hot.on('vite:beforeUpdate', async () => {
|
import.meta.hot.on('vite:beforeUpdate', async () => {
|
||||||
engine.stop();
|
engine.stop();
|
||||||
await engine.disconnectPlayer(0);
|
await engine.disconnectPlayer(0);
|
||||||
|
@ -82,15 +83,15 @@ if (import.meta.hot) {
|
||||||
const resolver = createResolver();
|
const resolver = createResolver();
|
||||||
resolvers.push(resolver);
|
resolvers.push(resolver);
|
||||||
await beforeResolver;
|
await beforeResolver;
|
||||||
// const oldBuffer = await engine.server.readData('players/0');
|
const oldBuffer = await engine.server.readData('players/0');
|
||||||
// const oldPlayer = JSON.parse((new TextDecoder()).decode(oldBuffer));
|
const oldPlayer = JSON.parse((new TextDecoder()).decode(oldBuffer));
|
||||||
// const buffer = await createPlayer(0);
|
const buffer = await createPlayer(0);
|
||||||
// const player = JSON.parse((new TextDecoder()).decode(buffer));
|
const player = JSON.parse((new TextDecoder()).decode(buffer));
|
||||||
// // Less jarring
|
// Less jarring
|
||||||
// player.Ecs = oldPlayer.Ecs;
|
player.Ecs = oldPlayer.Ecs;
|
||||||
// player.Direction = oldPlayer.Direction;
|
player.Direction = oldPlayer.Direction;
|
||||||
// player.Position = oldPlayer.Position;
|
player.Position = oldPlayer.Position;
|
||||||
// await engine.server.writeData('players/0', (new TextEncoder()).encode(JSON.stringify(player)));
|
await engine.server.writeData('players/0', (new TextEncoder()).encode(JSON.stringify(player)));
|
||||||
resolver.resolve();
|
resolver.resolve();
|
||||||
});
|
});
|
||||||
import.meta.hot.accept('../../create-forest.js', async ({default: createForest}) => {
|
import.meta.hot.accept('../../create-forest.js', async ({default: createForest}) => {
|
||||||
|
@ -99,9 +100,10 @@ if (import.meta.hot) {
|
||||||
await beforeResolver;
|
await beforeResolver;
|
||||||
delete engine.ecses['forests/0'];
|
delete engine.ecses['forests/0'];
|
||||||
await engine.server.removeData('forests/0');
|
await engine.server.removeData('forests/0');
|
||||||
const last = performance.now();
|
const forest = createEcs(engine.Ecs);
|
||||||
const forest = await createForest(engine.Ecs, '0');
|
for (const entity of await createForest()) {
|
||||||
console.log((performance.now() - last) / 1000);
|
await forest.create(entity);
|
||||||
|
}
|
||||||
await engine.saveEcs('forests/0', forest);
|
await engine.saveEcs('forests/0', forest);
|
||||||
resolver.resolve();
|
resolver.resolve();
|
||||||
});
|
});
|
||||||
|
@ -109,10 +111,13 @@ if (import.meta.hot) {
|
||||||
const resolver = createResolver();
|
const resolver = createResolver();
|
||||||
resolvers.push(resolver);
|
resolvers.push(resolver);
|
||||||
await beforeResolver;
|
await beforeResolver;
|
||||||
// delete engine.ecses['homesteads/0'];
|
delete engine.ecses['homesteads/0'];
|
||||||
// await engine.server.removeData('homesteads/0');
|
await engine.server.removeData('homesteads/0');
|
||||||
// const homestead = await createHomestead(engine.Ecs, '0');
|
const homestead = createEcs(engine.Ecs);
|
||||||
// await engine.saveEcs('homesteads/0', homestead);
|
for (const entity of await createHomestead('0')) {
|
||||||
|
await homestead.create(entity);
|
||||||
|
}
|
||||||
|
await engine.saveEcs('homesteads/0', homestead);
|
||||||
resolver.resolve();
|
resolver.resolve();
|
||||||
});
|
});
|
||||||
import.meta.hot.on('vite:afterUpdate', async () => {
|
import.meta.hot.on('vite:afterUpdate', async () => {
|
||||||
|
|
|
@ -46,4 +46,7 @@
|
||||||
|
|
||||||
.dashboard {
|
.dashboard {
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
|
pre {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectionWrapper img {
|
.selectionWrapper img {
|
||||||
user-select: none;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +35,7 @@
|
||||||
gap: 32px;
|
gap: 32px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectionStatus {
|
.selectionStatus {
|
||||||
|
|
140
app/react-components/dom/dialogue.jsx
Normal file
140
app/react-components/dom/dialogue.jsx
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import {useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
|
import {RESOLUTION} from '@/constants.js';
|
||||||
|
import {useDomScale} from '@/context/dom-scale.js';
|
||||||
|
|
||||||
|
import styles from './dialogue.module.css';
|
||||||
|
|
||||||
|
const CARET_SIZE = 12;
|
||||||
|
|
||||||
|
export default function Dialogue({
|
||||||
|
camera,
|
||||||
|
dialogue,
|
||||||
|
onClose,
|
||||||
|
scale,
|
||||||
|
}) {
|
||||||
|
const domScale = useDomScale();
|
||||||
|
const ref = useRef();
|
||||||
|
const [dimensions, setDimensions] = useState({h: 0, w: 0});
|
||||||
|
useEffect(() => {
|
||||||
|
const {ttl = 5} = dialogue;
|
||||||
|
if (!ttl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
onClose();
|
||||||
|
}, ttl * 1000);
|
||||||
|
}, [dialogue, onClose]);
|
||||||
|
useEffect(() => {
|
||||||
|
let handle;
|
||||||
|
function track() {
|
||||||
|
if (ref.current) {
|
||||||
|
const {height, width} = ref.current.getBoundingClientRect();
|
||||||
|
setDimensions({h: height / domScale, w: width / domScale});
|
||||||
|
}
|
||||||
|
handle = requestAnimationFrame(track);
|
||||||
|
}
|
||||||
|
handle = requestAnimationFrame(track);
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(handle);
|
||||||
|
};
|
||||||
|
}, [dialogue, domScale, ref]);
|
||||||
|
const origin = 'function' === typeof dialogue.origin
|
||||||
|
? dialogue.origin()
|
||||||
|
: dialogue.origin || {x: 0, y: 0};
|
||||||
|
const bounds = {
|
||||||
|
x: dimensions.w / (2 * scale),
|
||||||
|
y: dimensions.h / (2 * scale),
|
||||||
|
};
|
||||||
|
const left = Math.max(
|
||||||
|
Math.min(
|
||||||
|
dialogue.position.x * scale - camera.x,
|
||||||
|
RESOLUTION.x - bounds.x * scale - 16,
|
||||||
|
),
|
||||||
|
bounds.x * scale + 16,
|
||||||
|
);
|
||||||
|
const top = Math.max(
|
||||||
|
Math.min(
|
||||||
|
dialogue.position.y * scale - camera.y,
|
||||||
|
RESOLUTION.y - bounds.y * scale - 16,
|
||||||
|
),
|
||||||
|
bounds.y * scale + 88,
|
||||||
|
);
|
||||||
|
const offsetPosition = {
|
||||||
|
x: ((dialogue.position.x * scale - camera.x) - left) / scale,
|
||||||
|
y: ((dialogue.position.y * scale - camera.y) - top) / scale,
|
||||||
|
};
|
||||||
|
const difference = {
|
||||||
|
x: origin.x - dialogue.position.x + offsetPosition.x,
|
||||||
|
y: origin.y - dialogue.position.y + offsetPosition.y,
|
||||||
|
};
|
||||||
|
const within = {
|
||||||
|
x: Math.abs(difference.x) < bounds.x,
|
||||||
|
y: Math.abs(difference.y) < bounds.y,
|
||||||
|
};
|
||||||
|
const caretPosition = {
|
||||||
|
x: Math.max(-bounds.x, Math.min(origin.x - dialogue.position.x + offsetPosition.x, bounds.x)),
|
||||||
|
y: Math.max(-bounds.y, Math.min(origin.y - dialogue.position.y + offsetPosition.y, bounds.y)),
|
||||||
|
};
|
||||||
|
let caretRotation = Math.atan2(
|
||||||
|
difference.y - caretPosition.y,
|
||||||
|
difference.x - caretPosition.x,
|
||||||
|
);
|
||||||
|
caretRotation += Math.PI * 1.5;
|
||||||
|
if (within.x) {
|
||||||
|
caretPosition.y = bounds.y * Math.sign(difference.y);
|
||||||
|
if (within.y) {
|
||||||
|
if (Math.sign(difference.y) > 0) {
|
||||||
|
caretRotation = Math.PI;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
caretRotation = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (within.y) {
|
||||||
|
caretPosition.x = bounds.x * Math.sign(difference.x);
|
||||||
|
}
|
||||||
|
caretPosition.x *= scale;
|
||||||
|
caretPosition.y *= scale;
|
||||||
|
caretPosition.x += -Math.sin(caretRotation) * (CARET_SIZE / 2);
|
||||||
|
caretPosition.y += Math.cos(caretRotation) * (CARET_SIZE / 2);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#00000044',
|
||||||
|
border: 'solid 1px white',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: 'white',
|
||||||
|
padding: '1em',
|
||||||
|
position: 'absolute',
|
||||||
|
left: `${left}px`,
|
||||||
|
margin: '0',
|
||||||
|
top: `${top}px`,
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
userSelect: 'none',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className={styles.caret}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={CARET_SIZE}
|
||||||
|
height={CARET_SIZE}
|
||||||
|
style={{
|
||||||
|
transform: `
|
||||||
|
translate(
|
||||||
|
calc(-50% + ${caretPosition.x}px),
|
||||||
|
calc(-50% + ${caretPosition.y}px)
|
||||||
|
)
|
||||||
|
rotate(${caretRotation}rad)
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<polygon points="0 0, 24 0, 12 24" />
|
||||||
|
</svg>
|
||||||
|
{dialogue.body}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
7
app/react-components/dom/dialogue.module.css
Normal file
7
app/react-components/dom/dialogue.module.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.caret {
|
||||||
|
position: absolute;
|
||||||
|
fill: #00000044;
|
||||||
|
stroke: white;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
}
|
22
app/react-components/dom/dialogues.jsx
Normal file
22
app/react-components/dom/dialogues.jsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import styles from './dialogues.module.css';
|
||||||
|
|
||||||
|
import Dialogue from './dialogue.jsx';
|
||||||
|
|
||||||
|
export default function Dialogues({camera, dialogues, scale}) {
|
||||||
|
const elements = [];
|
||||||
|
for (const key in dialogues) {
|
||||||
|
elements.push(
|
||||||
|
<Dialogue
|
||||||
|
camera={camera}
|
||||||
|
dialogue={dialogues[key]}
|
||||||
|
key={key}
|
||||||
|
onClose={() => {
|
||||||
|
delete dialogues[key];
|
||||||
|
}}
|
||||||
|
scale={scale}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div className={styles.dialogues}>{elements}</div>;
|
||||||
|
}
|
||||||
|
|
4
app/react-components/dom/dialogues.module.css
Normal file
4
app/react-components/dom/dialogues.module.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.dialogues {
|
||||||
|
font-family: 'Times New Roman', Times, serif;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import {useEffect, useRef, useState} from 'react';
|
import {useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
import {RESOLUTION} from '@/constants.js';
|
import {RESOLUTION} from '@/constants.js';
|
||||||
|
import DomContext from '@/context/dom-scale.js';
|
||||||
|
|
||||||
import styles from './dom.module.css';
|
import styles from './dom.module.css';
|
||||||
|
|
||||||
|
@ -35,7 +36,9 @@ export default function Dom({children}) {
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
)}
|
)}
|
||||||
{children}
|
<DomContext.Provider value={scale}>
|
||||||
|
{children}
|
||||||
|
</DomContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: calc(100% / var(--scale));
|
height: calc(100% / var(--scale));
|
||||||
left: 0;
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
transform: scale(var(--scale));
|
transform: scale(var(--scale));
|
49
app/react-components/dom/entities.jsx
Normal file
49
app/react-components/dom/entities.jsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import {useState} from 'react';
|
||||||
|
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
||||||
|
|
||||||
|
import Entity from './entity.jsx';
|
||||||
|
|
||||||
|
export default function Entities({camera, scale}) {
|
||||||
|
const [ecs] = useEcs();
|
||||||
|
const [entities, setEntities] = useState({});
|
||||||
|
useEcsTick((payload) => {
|
||||||
|
if (!ecs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const updatedEntities = {...entities};
|
||||||
|
for (const id in payload) {
|
||||||
|
if ('1' === id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const update = payload[id];
|
||||||
|
if (false === update) {
|
||||||
|
delete updatedEntities[id];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
updatedEntities[id] = ecs.get(id);
|
||||||
|
const {dialogue} = update.Interlocutor || {};
|
||||||
|
if (dialogue) {
|
||||||
|
for (const key in dialogue) {
|
||||||
|
updatedEntities[id].Interlocutor.dialogues[key] = dialogue[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEntities(updatedEntities);
|
||||||
|
}, [ecs, entities]);
|
||||||
|
const renderables = [];
|
||||||
|
for (const id in entities) {
|
||||||
|
renderables.push(
|
||||||
|
<Entity
|
||||||
|
camera={camera}
|
||||||
|
entity={entities[id]}
|
||||||
|
key={id}
|
||||||
|
scale={scale}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderables}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
15
app/react-components/dom/entity.jsx
Normal file
15
app/react-components/dom/entity.jsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import Dialogues from './dialogues.jsx';
|
||||||
|
|
||||||
|
export default function Entity({camera, entity, scale}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{entity.Interlocutor && (
|
||||||
|
<Dialogues
|
||||||
|
camera={camera}
|
||||||
|
dialogues={entity.Interlocutor.dialogues}
|
||||||
|
scale={scale}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -10,7 +10,6 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: var(--size);
|
height: var(--size);
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
user-select: none;
|
|
||||||
width: var(--size);
|
width: var(--size);
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
|
@ -1,8 +1,6 @@
|
||||||
import {Container} from '@pixi/react';
|
import {Container} from '@pixi/react';
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
import {RESOLUTION} from '@/constants.js';
|
|
||||||
import {usePacket} from '@/context/client.js';
|
|
||||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
||||||
import {useMainEntity} from '@/context/main-entity.js';
|
import {useMainEntity} from '@/context/main-entity.js';
|
||||||
|
|
||||||
|
@ -28,13 +26,16 @@ function calculateDarkness(hour) {
|
||||||
return Math.floor(darkness * 1000) / 1000;
|
return Math.floor(darkness * 1000) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Ecs({applyFilters, scale}) {
|
export default function Ecs({applyFilters, camera, scale}) {
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [entities, setEntities] = useState({});
|
|
||||||
const [filters, setFilters] = useState([]);
|
const [filters, setFilters] = useState([]);
|
||||||
const [mainEntity] = useMainEntity();
|
const [mainEntity] = useMainEntity();
|
||||||
|
const [layers, setLayers] = useState([]);
|
||||||
const [hour, setHour] = useState(10);
|
const [hour, setHour] = useState(10);
|
||||||
const [night, setNight] = useState();
|
const [night, setNight] = useState();
|
||||||
|
const [projected, setProjected] = useState([]);
|
||||||
|
const [position, setPosition] = useState({x: 0, y: 0});
|
||||||
|
const [water, setWater] = useState();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function buildNightFilter() {
|
async function buildNightFilter() {
|
||||||
const {ColorMatrixFilter} = await import('@pixi/filter-color-matrix');
|
const {ColorMatrixFilter} = await import('@pixi/filter-color-matrix');
|
||||||
|
@ -62,101 +63,78 @@ export default function Ecs({applyFilters, scale}) {
|
||||||
night.setIntensity(calculateDarkness(hour));
|
night.setIntensity(calculateDarkness(hour));
|
||||||
}
|
}
|
||||||
}, [hour, night]);
|
}, [hour, night]);
|
||||||
usePacket('EcsChange', async () => {
|
|
||||||
setEntities({});
|
|
||||||
}, [setEntities]);
|
|
||||||
useEcsTick((payload) => {
|
useEcsTick((payload) => {
|
||||||
if (!ecs) {
|
const entity = ecs.get(mainEntity);
|
||||||
return;
|
|
||||||
}
|
|
||||||
const updatedEntities = {...entities};
|
|
||||||
for (const id in payload) {
|
for (const id in payload) {
|
||||||
const update = payload[id];
|
const update = payload[id];
|
||||||
if (false === update) {
|
switch (id) {
|
||||||
delete updatedEntities[id];
|
case '1': {
|
||||||
}
|
const master = ecs.get(1);
|
||||||
else {
|
if (update.TileLayers) {
|
||||||
if ('1' === id) {
|
setLayers(Object.values(master.TileLayers.$$layersProxies));
|
||||||
|
}
|
||||||
if (update.Time) {
|
if (update.Time) {
|
||||||
setHour(Math.round(ecs.get(1).Time.hour * 60) / 60);
|
setHour(Math.round(ecs.get(1).Time.hour * 60) / 60);
|
||||||
}
|
}
|
||||||
}
|
if (update.Water) {
|
||||||
updatedEntities[id] = ecs.get(id);
|
setWater(master.Water.water);
|
||||||
if (update.Emitter?.emit) {
|
}
|
||||||
updatedEntities[id].Emitter.emitting = {
|
break;
|
||||||
...updatedEntities[id].Emitter.emitting,
|
|
||||||
...update.Emitter.emit,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setEntities(updatedEntities);
|
if (entity) {
|
||||||
}, [ecs, entities, mainEntity]);
|
const {Direction, Position, Wielder} = entity;
|
||||||
|
setPosition(Position.toJSON());
|
||||||
|
setProjected(Wielder.activeItem()?.project(Position.tile, Direction.direction));
|
||||||
|
}
|
||||||
|
}, [ecs, mainEntity, scale]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilters(
|
setFilters(
|
||||||
applyFilters
|
applyFilters
|
||||||
? [night]
|
? [
|
||||||
|
...(night ? [night] : [])
|
||||||
|
]
|
||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
}, [applyFilters, night])
|
}, [applyFilters, night])
|
||||||
if (!ecs || !mainEntity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const entity = ecs.get(mainEntity);
|
|
||||||
if (!entity) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const {Direction, Position, Wielder} = entity;
|
|
||||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.direction)
|
|
||||||
const {Camera} = entity;
|
|
||||||
const {TileLayers, Water: WaterEcs} = ecs.get(1);
|
|
||||||
const layer0 = TileLayers.layer(0);
|
|
||||||
const layer1 = TileLayers.layer(1);
|
|
||||||
const [cx, cy] = [
|
|
||||||
Math.round((Camera.x * scale) - RESOLUTION.x / 2),
|
|
||||||
Math.round((Camera.y * scale) - RESOLUTION.y / 2),
|
|
||||||
];
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
scale={scale}
|
scale={scale}
|
||||||
x={-cx}
|
x={-camera.x}
|
||||||
y={-cy}
|
y={-camera.y}
|
||||||
>
|
>
|
||||||
<Container
|
<Container
|
||||||
filters={filters}
|
filters={filters}
|
||||||
>
|
>
|
||||||
<TileLayer
|
{layers.map((layer, i) => (
|
||||||
filters={filters}
|
|
||||||
tileLayer={layer0}
|
|
||||||
/>
|
|
||||||
{layer1 && (
|
|
||||||
<TileLayer
|
<TileLayer
|
||||||
filters={filters}
|
filters={filters}
|
||||||
tileLayer={layer1}
|
key={i}
|
||||||
|
tileLayer={layer}
|
||||||
/>
|
/>
|
||||||
)}
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
{WaterEcs && (
|
{water && layers[0] && (
|
||||||
<Water
|
<Water
|
||||||
tileLayer={layer0}
|
tileLayer={layers[0]}
|
||||||
water={WaterEcs.water}
|
water={water}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{projected && (
|
{projected && layers[0] && (
|
||||||
<TargetingGrid
|
<TargetingGrid
|
||||||
tileLayer={layer0}
|
tileLayer={layers[0]}
|
||||||
x={Position.x}
|
x={position.x}
|
||||||
y={Position.y}
|
y={position.y}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Entities
|
<Entities
|
||||||
entities={entities}
|
|
||||||
filters={filters}
|
filters={filters}
|
||||||
/>
|
/>
|
||||||
{projected?.length > 0 && (
|
{projected?.length > 0 && layers[0] && (
|
||||||
<TargetingGhost
|
<TargetingGhost
|
||||||
projected={projected}
|
projected={projected}
|
||||||
tileLayer={layer0}
|
tileLayer={layers[0]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
|
@ -3,13 +3,15 @@ import {GlowFilter} from '@pixi/filter-glow';
|
||||||
import {Container} from '@pixi/react';
|
import {Container} from '@pixi/react';
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
import {useEcs} from '@/context/ecs.js';
|
import {usePacket} from '@/context/client.js';
|
||||||
|
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
||||||
import {useMainEntity} from '@/context/main-entity.js';
|
import {useMainEntity} from '@/context/main-entity.js';
|
||||||
|
|
||||||
import Entity from './entity.jsx';
|
import Entity from './entity.jsx';
|
||||||
|
|
||||||
export default function Entities({entities, filters}) {
|
export default function Entities({filters}) {
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
|
const [entities, setEntities] = useState({});
|
||||||
const [mainEntity] = useMainEntity();
|
const [mainEntity] = useMainEntity();
|
||||||
const [radians, setRadians] = useState(0);
|
const [radians, setRadians] = useState(0);
|
||||||
const [willInteractWith, setWillInteractWith] = useState(0);
|
const [willInteractWith, setWillInteractWith] = useState(0);
|
||||||
|
@ -17,6 +19,38 @@ export default function Entities({entities, filters}) {
|
||||||
const pulse = (Math.cos(radians) + 1) * 0.5;
|
const pulse = (Math.cos(radians) + 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 () => {
|
||||||
|
setEntities({});
|
||||||
|
}, [setEntities]);
|
||||||
|
useEcsTick((payload) => {
|
||||||
|
if (!ecs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const updatedEntities = {...entities};
|
||||||
|
for (const id in payload) {
|
||||||
|
if ('1' === id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const update = payload[id];
|
||||||
|
if (false === update) {
|
||||||
|
delete updatedEntities[id];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updatedEntities[id] = ecs.get(id);
|
||||||
|
if (update.Emitter?.emit) {
|
||||||
|
updatedEntities[id].Emitter.emitting = {
|
||||||
|
...updatedEntities[id].Emitter.emitting,
|
||||||
|
...update.Emitter.emit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEntities(updatedEntities);
|
||||||
|
const main = ecs.get(mainEntity);
|
||||||
|
if (main) {
|
||||||
|
setWillInteractWith(main.Interacts.willInteractWith);
|
||||||
|
}
|
||||||
|
}, [ecs, entities, mainEntity]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRadians(0);
|
setRadians(0);
|
||||||
const handle = setInterval(() => {
|
const handle = setInterval(() => {
|
||||||
|
@ -26,17 +60,8 @@ export default function Entities({entities, filters}) {
|
||||||
clearInterval(handle);
|
clearInterval(handle);
|
||||||
};
|
};
|
||||||
}, [willInteractWith]);
|
}, [willInteractWith]);
|
||||||
useEffect(() => {
|
|
||||||
if (!mainEntity) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setWillInteractWith(ecs.get(mainEntity).Interacts.willInteractWith);
|
|
||||||
}, [entities, ecs, mainEntity]);
|
|
||||||
const renderables = [];
|
const renderables = [];
|
||||||
for (const id in entities) {
|
for (const id in entities) {
|
||||||
if ('1' === id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const isHighlightedInteraction = id == willInteractWith;
|
const isHighlightedInteraction = id == willInteractWith;
|
||||||
renderables.push(
|
renderables.push(
|
||||||
<Entity
|
<Entity
|
|
@ -45,7 +45,7 @@ export const Stage = ({children, ...props}) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Pixi({applyFilters, scale}) {
|
export default function Pixi({applyFilters, camera, scale}) {
|
||||||
return (
|
return (
|
||||||
<Stage
|
<Stage
|
||||||
className={styles.stage}
|
className={styles.stage}
|
||||||
|
@ -55,7 +55,11 @@ export default function Pixi({applyFilters, scale}) {
|
||||||
background: 0x1099bb,
|
background: 0x1099bb,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Ecs applyFilters={applyFilters} scale={scale} />
|
<Ecs
|
||||||
|
applyFilters={applyFilters}
|
||||||
|
camera={camera}
|
||||||
|
scale={scale}
|
||||||
|
/>
|
||||||
</Stage>
|
</Stage>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -15,7 +15,7 @@ const WaterTile = forwardRef(function WaterTile({height, width}, ref) {
|
||||||
return <Graphics alpha={0} draw={draw} ref={ref} />
|
return <Graphics alpha={0} draw={draw} ref={ref} />
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Water({mask, tileLayer, water}) {
|
export default function Water({tileLayer, water}) {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
|
@ -39,7 +39,7 @@ export default function Water({mask, tileLayer, water}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Container mask={mask}>
|
<Container>
|
||||||
<WaterTile
|
<WaterTile
|
||||||
height={tileLayer.tileSize.y}
|
height={tileLayer.tileSize.y}
|
||||||
ref={waterTile}
|
ref={waterTile}
|
|
@ -8,11 +8,12 @@ import {useDebug} from '@/context/debug.js';
|
||||||
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
import {useEcs, useEcsTick} from '@/context/ecs.js';
|
||||||
import {useMainEntity} from '@/context/main-entity.js';
|
import {useMainEntity} from '@/context/main-entity.js';
|
||||||
|
|
||||||
|
import Disconnected from './dom/disconnected.jsx';
|
||||||
|
import Dom from './dom/dom.jsx';
|
||||||
|
import Entities from './dom/entities.jsx';
|
||||||
|
import HotBar from './dom/hotbar.jsx';
|
||||||
|
import Pixi from './pixi/pixi.jsx';
|
||||||
import Devtools from './devtools.jsx';
|
import Devtools from './devtools.jsx';
|
||||||
import Disconnected from './disconnected.jsx';
|
|
||||||
import Dom from './dom.jsx';
|
|
||||||
import HotBar from './hotbar.jsx';
|
|
||||||
import Pixi from './pixi.jsx';
|
|
||||||
import styles from './ui.module.css';
|
import styles from './ui.module.css';
|
||||||
|
|
||||||
function emptySlots() {
|
function emptySlots() {
|
||||||
|
@ -56,6 +57,7 @@ export default function Ui({disconnected}) {
|
||||||
const [bufferSlot, setBufferSlot] = useState();
|
const [bufferSlot, setBufferSlot] = useState();
|
||||||
const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
|
const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
|
||||||
const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
|
const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
|
||||||
|
const [camera, setCamera] = useState({x: 0, y: 0});
|
||||||
const [hotbarSlots, setHotbarSlots] = useState(emptySlots());
|
const [hotbarSlots, setHotbarSlots] = useState(emptySlots());
|
||||||
const [activeSlot, setActiveSlot] = useState(0);
|
const [activeSlot, setActiveSlot] = useState(0);
|
||||||
const [scale, setScale] = useState(2);
|
const [scale, setScale] = useState(2);
|
||||||
|
@ -236,12 +238,15 @@ export default function Ui({disconnected}) {
|
||||||
for (const id in payload) {
|
for (const id in payload) {
|
||||||
const entity = ecs.get(id);
|
const entity = ecs.get(id);
|
||||||
const update = payload[id];
|
const update = payload[id];
|
||||||
|
if (!update) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (update.Sound?.play) {
|
if (update.Sound?.play) {
|
||||||
for (const sound of update.Sound.play) {
|
for (const sound of update.Sound.play) {
|
||||||
(new Audio(sound)).play();
|
(new Audio(sound)).play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (update?.MainEntity) {
|
if (update.MainEntity) {
|
||||||
setMainEntity(localMainEntity = id);
|
setMainEntity(localMainEntity = id);
|
||||||
}
|
}
|
||||||
if (localMainEntity === id) {
|
if (localMainEntity === id) {
|
||||||
|
@ -258,7 +263,14 @@ export default function Ui({disconnected}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ecs, mainEntity]);
|
if (localMainEntity) {
|
||||||
|
const mainEntityEntity = ecs.get(localMainEntity);
|
||||||
|
setCamera({
|
||||||
|
x: Math.round((mainEntityEntity.Camera.x * scale) - RESOLUTION.x / 2),
|
||||||
|
y: Math.round((mainEntityEntity.Camera.y * scale) - RESOLUTION.y / 2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [ecs, mainEntity, scale]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function onContextMenu(event) {
|
function onContextMenu(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -267,7 +279,7 @@ export default function Ui({disconnected}) {
|
||||||
return () => {
|
return () => {
|
||||||
document.body.removeEventListener('contextmenu', onContextMenu);
|
document.body.removeEventListener('contextmenu', onContextMenu);
|
||||||
};
|
};
|
||||||
}, [])
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.ui}
|
className={styles.ui}
|
||||||
|
@ -367,24 +379,30 @@ export default function Ui({disconnected}) {
|
||||||
}}
|
}}
|
||||||
ref={gameRef}
|
ref={gameRef}
|
||||||
>
|
>
|
||||||
<Pixi applyFilters={applyFilters} scale={scale} />
|
<Pixi
|
||||||
{mainEntity && (
|
applyFilters={applyFilters}
|
||||||
<Dom devtoolsIsOpen={devtoolsIsOpen}>
|
camera={camera}
|
||||||
<HotBar
|
scale={scale}
|
||||||
active={activeSlot}
|
/>
|
||||||
onActivate={(i) => {
|
<Dom>
|
||||||
client.send({
|
<HotBar
|
||||||
type: 'Action',
|
active={activeSlot}
|
||||||
payload: {type: 'swapSlots', value: [0, i + 1]},
|
onActivate={(i) => {
|
||||||
});
|
client.send({
|
||||||
}}
|
type: 'Action',
|
||||||
slots={hotbarSlots}
|
payload: {type: 'swapSlots', value: [0, i + 1]},
|
||||||
/>
|
});
|
||||||
{showDisconnected && (
|
}}
|
||||||
<Disconnected />
|
slots={hotbarSlots}
|
||||||
)}
|
/>
|
||||||
</Dom>
|
<Entities
|
||||||
)}
|
camera={camera}
|
||||||
|
scale={scale}
|
||||||
|
/>
|
||||||
|
{showDisconnected && (
|
||||||
|
<Disconnected />
|
||||||
|
)}
|
||||||
|
</Dom>
|
||||||
</div>
|
</div>
|
||||||
<div className={[styles.devtools, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}>
|
<div className={[styles.devtools, devtoolsIsOpen && styles.devtoolsIsOpen].filter(Boolean).join(' ')}>
|
||||||
<Devtools
|
<Devtools
|
||||||
|
|
|
@ -20,4 +20,5 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,28 +65,7 @@ export default class Script {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// async evaluate(callback) {
|
evaluate() {
|
||||||
// this.sandbox.reset();
|
|
||||||
// let {done, value} = this.sandbox.next();
|
|
||||||
// if (value instanceof Promise) {
|
|
||||||
// await value;
|
|
||||||
// }
|
|
||||||
// while (!done) {
|
|
||||||
// ({done, value} = this.sandbox.next());
|
|
||||||
// if (value instanceof Promise) {
|
|
||||||
// // eslint-disable-next-line no-await-in-loop
|
|
||||||
// await value;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (value instanceof Promise) {
|
|
||||||
// value.then(callback);
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// callback(value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
evaluateSync() {
|
|
||||||
this.sandbox.reset();
|
this.sandbox.reset();
|
||||||
const {value} = this.sandbox.run();
|
const {value} = this.sandbox.run();
|
||||||
return value;
|
return value;
|
||||||
|
|
1
public/assets/chest.json
Normal file
1
public/assets/chest.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"frames":{"":{"frame":{"x":0,"y":0,"w":22,"h":22},"spriteSourceSize":{"x":0,"y":0,"w":22,"h":22},"sourceSize":{"w":22,"h":22}}},"meta":{"format":"RGBA8888","image":"./chest.png","scale":1,"size":{"w":22,"h":22}}}
|
BIN
public/assets/chest.png
Normal file
BIN
public/assets/chest.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -6,8 +6,8 @@ import {basename, dirname, extname, join} from 'node:path';
|
||||||
import imageSize from 'image-size';
|
import imageSize from 'image-size';
|
||||||
|
|
||||||
const tileset = process.argv[2];
|
const tileset = process.argv[2];
|
||||||
let w = parseInt(process.argv[3]);
|
let w = parseInt(process.argv[3] || '0');
|
||||||
let h = parseInt(process.argv[4]);
|
let h = parseInt(process.argv[4] || '0');
|
||||||
|
|
||||||
const {width, height} = imageSize(tileset);
|
const {width, height} = imageSize(tileset);
|
||||||
|
|
|
@ -30,13 +30,9 @@ if (projected?.length > 0) {
|
||||||
stages: Array(5).fill(0.5),
|
stages: Array(5).fill(0.5),
|
||||||
},
|
},
|
||||||
Sprite: {
|
Sprite: {
|
||||||
anchorX: 0.5,
|
|
||||||
anchorY: 0.75,
|
anchorY: 0.75,
|
||||||
animation: 'stage/0',
|
animation: 'stage/0',
|
||||||
frame: 0,
|
|
||||||
frames: 1,
|
|
||||||
source: '/assets/tomato-plant/tomato-plant.json',
|
source: '/assets/tomato-plant/tomato-plant.json',
|
||||||
speed: 0,
|
|
||||||
},
|
},
|
||||||
Ticking: {},
|
Ticking: {},
|
||||||
VisibleAabb: {},
|
VisibleAabb: {},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user