Compare commits
9 Commits
e3ebe46bf4
...
3d862d39d5
Author | SHA1 | Date | |
---|---|---|---|
|
3d862d39d5 | ||
|
5fb34d66ac | ||
|
30033fd8e4 | ||
|
86a3367efa | ||
|
7fdc7ba4e9 | ||
|
e2cad034c2 | ||
|
26b85f6520 | ||
|
b21ef309aa | ||
|
41d447a6a3 |
37
app/client-ecs.js
Normal file
37
app/client-ecs.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import Ecs from '@/ecs/ecs.js';
|
||||||
|
import Script from '@/util/script.js';
|
||||||
|
|
||||||
|
import {LRUCache} from 'lru-cache';
|
||||||
|
|
||||||
|
const cache = new LRUCache({
|
||||||
|
max: 128,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default class ClientEcs extends Ecs {
|
||||||
|
async readAsset(uri) {
|
||||||
|
if (!cache.has(uri)) {
|
||||||
|
let promise, resolve, reject;
|
||||||
|
promise = new Promise((res, rej) => {
|
||||||
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
|
cache.set(uri, promise);
|
||||||
|
fetch(new URL(uri, window.location.origin))
|
||||||
|
.then(async (response) => {
|
||||||
|
resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0));
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
return cache.get(uri);
|
||||||
|
}
|
||||||
|
async readJson(uri) {
|
||||||
|
const chars = await this.readAsset(uri);
|
||||||
|
return chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {};
|
||||||
|
}
|
||||||
|
async readScript(uri, context = {}) {
|
||||||
|
const code = await this.readAsset(uri);
|
||||||
|
if (code.byteLength > 0) {
|
||||||
|
return Script.fromCode((new TextDecoder()).decode(code), context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,6 @@ export function useEcs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEcsTick(fn, dependencies) {
|
export function useEcsTick(fn, dependencies) {
|
||||||
const ecs = useEcs();
|
const [ecs] = useEcs();
|
||||||
usePacket(':Ecs', fn, [ecs, ...dependencies]);
|
usePacket(':Ecs', fn, [ecs, ...dependencies]);
|
||||||
}
|
}
|
29
app/create-ecs.js
Normal file
29
app/create-ecs.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import Components from '@/ecs-components/index.js';
|
||||||
|
import Systems from '@/ecs-systems/index.js';
|
||||||
|
|
||||||
|
export default function createEcs(Ecs) {
|
||||||
|
const ecs = new Ecs({Components, Systems});
|
||||||
|
const defaultSystems = [
|
||||||
|
'ResetForces',
|
||||||
|
'ApplyControlMovement',
|
||||||
|
'IntegratePhysics',
|
||||||
|
'ClampPositions',
|
||||||
|
'PlantGrowth',
|
||||||
|
'FollowCamera',
|
||||||
|
'VisibleAabbs',
|
||||||
|
'Colliders',
|
||||||
|
'ControlDirection',
|
||||||
|
'SpriteDirection',
|
||||||
|
'RunAnimations',
|
||||||
|
'RunTickingPromises',
|
||||||
|
'Water',
|
||||||
|
'Interactions',
|
||||||
|
];
|
||||||
|
defaultSystems.forEach((defaultSystem) => {
|
||||||
|
const System = ecs.system(defaultSystem);
|
||||||
|
if (System) {
|
||||||
|
System.active = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ecs;
|
||||||
|
}
|
42
app/create-homestead.js
Normal file
42
app/create-homestead.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import createEcs from './create-ecs.js';
|
||||||
|
|
||||||
|
export default async function createHomestead(Ecs) {
|
||||||
|
const ecs = createEcs(Ecs);
|
||||||
|
const area = {x: 100, y: 60};
|
||||||
|
await ecs.create({
|
||||||
|
AreaSize: {x: area.x * 16, y: area.y * 16},
|
||||||
|
Ticking: {},
|
||||||
|
TileLayers: {
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
area,
|
||||||
|
data: Array(area.x * area.y).fill(0).map(() => 1 + Math.floor(Math.random() * 4)),
|
||||||
|
source: '/assets/tileset.json',
|
||||||
|
tileSize: {x: 16, y: 16},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Water: {water: {}},
|
||||||
|
});
|
||||||
|
await ecs.create({
|
||||||
|
Collider: {
|
||||||
|
bodies: [
|
||||||
|
[
|
||||||
|
{x: -36, y: -16},
|
||||||
|
{x: -21, y: -16},
|
||||||
|
{x: -36, y: -1},
|
||||||
|
{x: -21, y: -1},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
collisionStartScript: '/assets/shit-shack/collision-start.js',
|
||||||
|
},
|
||||||
|
Ecs: {},
|
||||||
|
Position: {x: 100, y: 100},
|
||||||
|
Sprite: {
|
||||||
|
anchor: {x: 0.5, y: 0.8},
|
||||||
|
source: '/assets/shit-shack/shit-shack.json',
|
||||||
|
},
|
||||||
|
VisibleAabb: {},
|
||||||
|
});
|
||||||
|
return ecs;
|
||||||
|
}
|
39
app/create-house.js
Normal file
39
app/create-house.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import createEcs from './create-ecs.js';
|
||||||
|
|
||||||
|
export default async function createHouse(Ecs) {
|
||||||
|
const ecs = createEcs(Ecs);
|
||||||
|
const area = {x: 20, y: 20};
|
||||||
|
await ecs.create({
|
||||||
|
AreaSize: {x: area.x * 16, y: area.y * 16},
|
||||||
|
Ticking: {},
|
||||||
|
TileLayers: {
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
area,
|
||||||
|
data: Array(area.x * area.y).fill(0).map(() => 5 + Math.floor(Math.random() * 2)),
|
||||||
|
source: '/assets/tileset.json',
|
||||||
|
tileSize: {x: 16, y: 16},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await ecs.create({
|
||||||
|
Collider: {
|
||||||
|
bodies: [
|
||||||
|
[
|
||||||
|
{x: -8, y: -8},
|
||||||
|
{x: 7, y: -8},
|
||||||
|
{x: 7, y: 7},
|
||||||
|
{x: -8, y: 7},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
collisionStartScript: '/assets/house/collision-start.js',
|
||||||
|
},
|
||||||
|
Ecs: {},
|
||||||
|
Position: {
|
||||||
|
x: 72,
|
||||||
|
y: 320,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return ecs;
|
||||||
|
}
|
60
app/create-player.js
Normal file
60
app/create-player.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
export default async function createPlayer(id) {
|
||||||
|
const player = {
|
||||||
|
Camera: {},
|
||||||
|
Collider: {
|
||||||
|
bodies: [
|
||||||
|
[
|
||||||
|
{x: -8, y: -8},
|
||||||
|
{x: 7, y: -8},
|
||||||
|
{x: -8, y: 7},
|
||||||
|
{x: 7, y: 7},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Controlled: {},
|
||||||
|
Direction: {direction: 2},
|
||||||
|
Ecs: {path: ['homesteads', `${id}`].join('/')},
|
||||||
|
Emitter: {},
|
||||||
|
Forces: {},
|
||||||
|
Interacts: {},
|
||||||
|
Inventory: {
|
||||||
|
slots: {
|
||||||
|
1: {
|
||||||
|
qty: 100,
|
||||||
|
source: '/assets/potion/potion.json',
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
qty: 1,
|
||||||
|
source: '/assets/watering-can/watering-can.json',
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
qty: 1,
|
||||||
|
source: '/assets/tomato-seeds/tomato-seeds.json',
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
qty: 1,
|
||||||
|
source: '/assets/hoe/hoe.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Health: {health: 100},
|
||||||
|
Position: {x: 128, y: 128},
|
||||||
|
Speed: {speed: 100},
|
||||||
|
Sound: {},
|
||||||
|
Sprite: {
|
||||||
|
anchor: {x: 0.5, y: 0.8},
|
||||||
|
animation: 'moving:down',
|
||||||
|
frame: 0,
|
||||||
|
frames: 8,
|
||||||
|
source: '/assets/dude/dude.json',
|
||||||
|
speed: 0.115,
|
||||||
|
},
|
||||||
|
Ticking: {},
|
||||||
|
VisibleAabb: {},
|
||||||
|
Wielder: {
|
||||||
|
activeSlot: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (new TextEncoder()).encode(JSON.stringify(player));
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ export default class Collider extends Component {
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const {ecs} = this;
|
const {ecs} = this;
|
||||||
return class ColliderInstance extends super.instanceFromSchema() {
|
return class ColliderInstance extends super.instanceFromSchema() {
|
||||||
|
collidingWith = {};
|
||||||
isCollidingWith(other) {
|
isCollidingWith(other) {
|
||||||
const {aabb, aabbs} = this;
|
const {aabb, aabbs} = this;
|
||||||
const {aabb: otherAabb, aabbs: otherAabbs} = other;
|
const {aabb: otherAabb, aabbs: otherAabbs} = other;
|
||||||
|
@ -63,6 +64,26 @@ export default class Collider extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async load(instance) {
|
||||||
|
// heavy handed...
|
||||||
|
if ('undefined' !== typeof window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
instance.collisionEndScriptInstance = await this.ecs.readScript(
|
||||||
|
instance.collisionEndScript,
|
||||||
|
{
|
||||||
|
ecs: this.ecs,
|
||||||
|
entity: this.ecs.get(instance.entity),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
instance.collisionStartScriptInstance = await this.ecs.readScript(
|
||||||
|
instance.collisionStartScript,
|
||||||
|
{
|
||||||
|
ecs: this.ecs,
|
||||||
|
entity: this.ecs.get(instance.entity),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
static properties = {
|
static properties = {
|
||||||
bodies: {
|
bodies: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
@ -71,5 +92,7 @@ export default class Collider extends Component {
|
||||||
subtype: vector2d('int16'),
|
subtype: vector2d('int16'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
collisionEndScript: {type: 'string'},
|
||||||
|
collisionStartScript: {type: 'string'},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
import Component from '@/ecs/component.js';
|
|
||||||
|
|
||||||
export default class Engine extends Component {}
|
|
|
@ -101,13 +101,13 @@ class ItemProxy {
|
||||||
set qty(qty) {
|
set qty(qty) {
|
||||||
const {instance} = this;
|
const {instance} = this;
|
||||||
if (qty <= 0) {
|
if (qty <= 0) {
|
||||||
Component.markChange(instance.entity, 'cleared', {[this.slot]: true});
|
this.Component.markChange(instance.entity, 'cleared', {[this.slot]: true});
|
||||||
delete instance.slots[this.slot];
|
delete instance.slots[this.slot];
|
||||||
delete instance.$$items[this.slot];
|
delete instance.$$items[this.slot];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
instance.slots[this.slot].qty = qty;
|
instance.slots[this.slot].qty = qty;
|
||||||
Component.markChange(instance.entity, 'qtyUpdated', {[this.slot]: qty});
|
this.Component.markChange(instance.entity, 'qtyUpdated', {[this.slot]: qty});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,17 @@ export default class Inventory extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.insertMany(entities);
|
await super.insertMany(entities);
|
||||||
|
for (const [id, {slots}] of entities) {
|
||||||
|
if (slots) {
|
||||||
|
const instance = this.get(id);
|
||||||
|
instance.$$items = {};
|
||||||
|
for (const slot in slots) {
|
||||||
|
instance.$$items[slot] = new ItemProxy(this, instance, slot);
|
||||||
|
await instance.$$items[slot].load(instance.slots[slot].source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const Instance = super.instanceFromSchema();
|
const Instance = super.instanceFromSchema();
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default class Colliders extends System {
|
||||||
}
|
}
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
|
const {Ticking} = this.ecs.get(1);
|
||||||
const seen = {};
|
const seen = {};
|
||||||
for (const entity of this.ecs.changed(['Position'])) {
|
for (const entity of this.ecs.changed(['Position'])) {
|
||||||
if (seen[entity.id]) {
|
if (seen[entity.id]) {
|
||||||
|
@ -48,6 +49,8 @@ export default class Colliders extends System {
|
||||||
if (!entity.Collider) {
|
if (!entity.Collider) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const {collidingWith: wasCollidingWith} = entity.Collider;
|
||||||
|
entity.Collider.collidingWith = {};
|
||||||
this.updateHash(entity);
|
this.updateHash(entity);
|
||||||
for (const other of this.within(entity.Collider.aabb)) {
|
for (const other of this.within(entity.Collider.aabb)) {
|
||||||
if (seen[other.id]) {
|
if (seen[other.id]) {
|
||||||
|
@ -57,8 +60,33 @@ export default class Colliders extends System {
|
||||||
if (!other.Collider) {
|
if (!other.Collider) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
delete other.Collider.collidingWith[entity.id];
|
||||||
if (entity.Collider.isCollidingWith(other.Collider)) {
|
if (entity.Collider.isCollidingWith(other.Collider)) {
|
||||||
console.log('collide', entity, other);
|
entity.Collider.collidingWith[other.id] = true;
|
||||||
|
other.Collider.collidingWith[entity.id] = true;
|
||||||
|
if (!wasCollidingWith[other.id]) {
|
||||||
|
if (entity.Collider.collisionStartScriptInstance) {
|
||||||
|
entity.Collider.collisionStartScriptInstance.context.other = other;
|
||||||
|
Ticking.addTickingPromise(entity.Collider.collisionStartScriptInstance.tickingPromise());
|
||||||
|
}
|
||||||
|
if (other.Collider.collisionStartScriptInstance) {
|
||||||
|
other.Collider.collisionStartScriptInstance.context.other = entity;
|
||||||
|
Ticking.addTickingPromise(other.Collider.collisionStartScriptInstance.tickingPromise());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const otherId in wasCollidingWith) {
|
||||||
|
if (!entity.Collider.collidingWith[otherId]) {
|
||||||
|
const other = this.ecs.get(otherId);
|
||||||
|
if (entity.Collider.collisionEndScriptInstance) {
|
||||||
|
entity.Collider.collisionEndScriptInstance.context.other = other;
|
||||||
|
Ticking.addTickingPromise(entity.Collider.collisionEndScriptInstance.tickingPromise());
|
||||||
|
}
|
||||||
|
if (other.Collider.collisionEndScriptInstance) {
|
||||||
|
other.Collider.collisionEndScriptInstance.context.other = entity;
|
||||||
|
Ticking.addTickingPromise(other.Collider.collisionEndScriptInstance.tickingPromise());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,15 @@ export default class Water extends System {
|
||||||
|
|
||||||
tick(elapsed) {
|
tick(elapsed) {
|
||||||
const {Water} = this.ecs.get(1);
|
const {Water} = this.ecs.get(1);
|
||||||
|
if (!Water) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (const tile in Water.water) {
|
for (const tile in Water.water) {
|
||||||
Water.water[tile] = Math.max(0, Water.water[tile] - elapsed);
|
Water.water[tile] = Math.max(0, Water.water[tile] - elapsed);
|
||||||
}
|
}
|
||||||
Water.water = {...Water.water};
|
if (Object.keys(Water.water).length > 0) {
|
||||||
|
Water.water = {...Water.water};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,10 +62,18 @@ export default class Ecs {
|
||||||
creating.push([entityId, componentsToUpdate]);
|
creating.push([entityId, componentsToUpdate]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.destroyMany(destroying);
|
if (destroying.length > 0) {
|
||||||
await this.insertMany(updating);
|
this.destroyMany(destroying);
|
||||||
this.removeMany(removing);
|
}
|
||||||
await this.createManySpecific(creating);
|
if (updating.length > 0) {
|
||||||
|
await this.insertMany(updating);
|
||||||
|
}
|
||||||
|
if (removing.length > 0) {
|
||||||
|
this.removeMany(removing);
|
||||||
|
}
|
||||||
|
if (creating.length > 0) {
|
||||||
|
await this.createManySpecific(creating);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changed(criteria) {
|
changed(criteria) {
|
||||||
|
@ -111,6 +119,9 @@ export default class Ecs {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createManySpecific(specificsList) {
|
async createManySpecific(specificsList) {
|
||||||
|
if (0 === specificsList.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const entityIds = [];
|
const entityIds = [];
|
||||||
const creating = {};
|
const creating = {};
|
||||||
for (let i = 0; i < specificsList.length; i++) {
|
for (let i = 0; i < specificsList.length; i++) {
|
||||||
|
|
275
app/engine.js
275
app/engine.js
|
@ -3,24 +3,21 @@ import {
|
||||||
TPS,
|
TPS,
|
||||||
} from '@/constants.js';
|
} from '@/constants.js';
|
||||||
import Ecs from '@/ecs/ecs.js';
|
import Ecs from '@/ecs/ecs.js';
|
||||||
import Components from '@/ecs-components/index.js';
|
|
||||||
import Systems from '@/ecs-systems/index.js';
|
|
||||||
import {decode, encode} from '@/packets/index.js';
|
import {decode, encode} from '@/packets/index.js';
|
||||||
import Script from '@/util/script.js';
|
import Script from '@/util/script.js';
|
||||||
|
|
||||||
function join(...parts) {
|
import createEcs from './create-ecs.js';
|
||||||
return parts.join('/');
|
import createHomestead from './create-homestead.js';
|
||||||
}
|
import createHouse from './create-house.js';
|
||||||
|
import createPlayer from './create-player.js';
|
||||||
|
|
||||||
export default class Engine {
|
export default class Engine {
|
||||||
|
|
||||||
connections = [];
|
|
||||||
connectedPlayers = new Map();
|
connectedPlayers = new Map();
|
||||||
connectingPlayers = [];
|
|
||||||
ecses = {};
|
ecses = {};
|
||||||
frame = 0;
|
frame = 0;
|
||||||
handle;
|
handle;
|
||||||
incomingActions = [];
|
incomingActions = new Map();
|
||||||
last = Date.now();
|
last = Date.now();
|
||||||
server;
|
server;
|
||||||
|
|
||||||
|
@ -34,6 +31,7 @@ export default class Engine {
|
||||||
super.transmit(connection, encode(packet));
|
super.transmit(connection, encode(packet));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const engine = this;
|
||||||
const server = this.server = new SilphiusServer();
|
const server = this.server = new SilphiusServer();
|
||||||
this.Ecs = class EngineEcs extends Ecs {
|
this.Ecs = class EngineEcs extends Ecs {
|
||||||
async readAsset(uri) {
|
async readAsset(uri) {
|
||||||
|
@ -44,68 +42,110 @@ export default class Engine {
|
||||||
return chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {};
|
return chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {};
|
||||||
}
|
}
|
||||||
async readScript(uri, context) {
|
async readScript(uri, context) {
|
||||||
|
if (!uri) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
const code = await this.readAsset(uri);
|
const code = await this.readAsset(uri);
|
||||||
if (code.byteLength > 0) {
|
if (code.byteLength > 0) {
|
||||||
return Script.fromCode((new TextDecoder()).decode(code), context);
|
return Script.fromCode((new TextDecoder()).decode(code), context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async switchEcs(entity, path, updates) {
|
||||||
|
for (const [connection, connectedPlayer] of engine.connectedPlayers) {
|
||||||
|
if (entity !== connectedPlayer.entity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// remove entity link to connection to start queueing actions and pause updates
|
||||||
|
delete connectedPlayer.entity;
|
||||||
|
// forget previous state
|
||||||
|
connectedPlayer.memory.clear();
|
||||||
|
// inform client of the upcoming change
|
||||||
|
server.send(
|
||||||
|
connection,
|
||||||
|
{
|
||||||
|
type: 'EcsChange',
|
||||||
|
payload: {},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// dump entity state with updates for the transition
|
||||||
|
const dumped = {
|
||||||
|
...entity.toJSON(),
|
||||||
|
Controlled: entity.Controlled,
|
||||||
|
Ecs: {path},
|
||||||
|
...updates,
|
||||||
|
};
|
||||||
|
// remove from old ECS
|
||||||
|
this.destroy(entity.id);
|
||||||
|
// load if necessary
|
||||||
|
if (!engine.ecses[path]) {
|
||||||
|
await engine.loadEcs(path);
|
||||||
|
}
|
||||||
|
// recreate the entity in the new ECS and again associate it with the connection
|
||||||
|
connectedPlayer.entity = engine.ecses[path].get(await engine.ecses[path].create(dumped));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.server.addPacketListener('Action', (connection, payload) => {
|
this.server.addPacketListener('Action', (connection, payload) => {
|
||||||
this.incomingActions.push([connection, payload]);
|
if (!this.incomingActions.has(connection)) {
|
||||||
|
this.incomingActions.set(connection, []);
|
||||||
|
}
|
||||||
|
this.incomingActions.get(connection).push(payload);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptActions() {
|
acceptActions() {
|
||||||
for (const [
|
for (const [connection, payloads] of this.incomingActions) {
|
||||||
connection,
|
|
||||||
payload,
|
|
||||||
] of this.incomingActions) {
|
|
||||||
if (!this.connectedPlayers.get(connection)) {
|
if (!this.connectedPlayers.get(connection)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const {entity} = this.connectedPlayers.get(connection);
|
const {entity} = this.connectedPlayers.get(connection);
|
||||||
|
if (!entity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const {Controlled, Ecs, Interacts, Inventory, Wielder} = entity;
|
const {Controlled, Ecs, Interacts, Inventory, Wielder} = entity;
|
||||||
switch (payload.type) {
|
for (const payload of payloads) {
|
||||||
case 'changeSlot': {
|
switch (payload.type) {
|
||||||
if (!Controlled.locked) {
|
case 'changeSlot': {
|
||||||
Wielder.activeSlot = payload.value - 1;
|
if (!Controlled.locked) {
|
||||||
|
Wielder.activeSlot = payload.value - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case 'moveUp':
|
||||||
}
|
case 'moveRight':
|
||||||
case 'moveUp':
|
case 'moveDown':
|
||||||
case 'moveRight':
|
case 'moveLeft': {
|
||||||
case 'moveDown':
|
Controlled[payload.type] = payload.value;
|
||||||
case 'moveLeft': {
|
break;
|
||||||
Controlled[payload.type] = payload.value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'swapSlots': {
|
|
||||||
if (!Controlled.locked) {
|
|
||||||
Inventory.swapSlots(...payload.value);
|
|
||||||
}
|
}
|
||||||
break;
|
case 'swapSlots': {
|
||||||
}
|
if (!Controlled.locked) {
|
||||||
case 'use': {
|
Inventory.swapSlots(...payload.value);
|
||||||
if (!Controlled.locked) {
|
}
|
||||||
Wielder.useActiveItem(payload.value);
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case 'use': {
|
||||||
}
|
if (!Controlled.locked) {
|
||||||
case 'interact': {
|
Wielder.useActiveItem(payload.value);
|
||||||
if (!Controlled.locked) {
|
}
|
||||||
if (payload.value) {
|
break;
|
||||||
if (Interacts.willInteractWith) {
|
}
|
||||||
const ecs = this.ecses[Ecs.path];
|
case 'interact': {
|
||||||
const subject = ecs.get(Interacts.willInteractWith);
|
if (!Controlled.locked) {
|
||||||
subject.Interactive.interact(entity);
|
if (payload.value) {
|
||||||
|
if (Interacts.willInteractWith) {
|
||||||
|
const ecs = this.ecses[Ecs.path];
|
||||||
|
const subject = ecs.get(Interacts.willInteractWith);
|
||||||
|
subject.Interactive.interact(entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.incomingActions.set(connection, []);
|
||||||
}
|
}
|
||||||
this.incomingActions = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectPlayer(connection, id) {
|
async connectPlayer(connection, id) {
|
||||||
|
@ -115,7 +155,6 @@ export default class Engine {
|
||||||
}
|
}
|
||||||
const ecs = this.ecses[entityJson.Ecs.path];
|
const ecs = this.ecses[entityJson.Ecs.path];
|
||||||
const entity = await ecs.create(entityJson);
|
const entity = await ecs.create(entityJson);
|
||||||
this.connections.push(connection);
|
|
||||||
this.connectedPlayers.set(
|
this.connectedPlayers.set(
|
||||||
connection,
|
connection,
|
||||||
{
|
{
|
||||||
|
@ -126,123 +165,17 @@ export default class Engine {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createEcs() {
|
|
||||||
return new this.Ecs({Components, Systems});
|
|
||||||
}
|
|
||||||
|
|
||||||
async createHomestead(id) {
|
|
||||||
const ecs = this.createEcs();
|
|
||||||
const area = {x: 100, y: 60};
|
|
||||||
await ecs.create({
|
|
||||||
AreaSize: {x: area.x * 16, y: area.y * 16},
|
|
||||||
Engine: {},
|
|
||||||
TileLayers: {
|
|
||||||
layers: [
|
|
||||||
{
|
|
||||||
area,
|
|
||||||
data: Array(area.x * area.y).fill(0).map(() => 1 + Math.floor(Math.random() * 4)),
|
|
||||||
source: '/assets/tileset.json',
|
|
||||||
tileSize: {x: 16, y: 16},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Water: {water: {}},
|
|
||||||
});
|
|
||||||
await ecs.create({
|
|
||||||
Position: {x: 100, y: 100},
|
|
||||||
Sprite: {
|
|
||||||
anchor: {x: 0.5, y: 0.8},
|
|
||||||
source: '/assets/shit-shack/shit-shack.json',
|
|
||||||
},
|
|
||||||
VisibleAabb: {},
|
|
||||||
});
|
|
||||||
const defaultSystems = [
|
|
||||||
'ResetForces',
|
|
||||||
'ApplyControlMovement',
|
|
||||||
'IntegratePhysics',
|
|
||||||
'ClampPositions',
|
|
||||||
'PlantGrowth',
|
|
||||||
'FollowCamera',
|
|
||||||
'VisibleAabbs',
|
|
||||||
'Collliders',
|
|
||||||
'ControlDirection',
|
|
||||||
'SpriteDirection',
|
|
||||||
'RunAnimations',
|
|
||||||
'RunTickingPromises',
|
|
||||||
'Water',
|
|
||||||
'Interactions',
|
|
||||||
];
|
|
||||||
defaultSystems.forEach((defaultSystem) => {
|
|
||||||
const System = ecs.system(defaultSystem);
|
|
||||||
if (System) {
|
|
||||||
System.active = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await this.saveEcs(join('homesteads', `${id}`), ecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createPlayer(id) {
|
|
||||||
const player = {
|
|
||||||
Camera: {},
|
|
||||||
Controlled: {},
|
|
||||||
Direction: {direction: 2},
|
|
||||||
Ecs: {path: join('homesteads', `${id}`)},
|
|
||||||
Emitter: {},
|
|
||||||
Forces: {},
|
|
||||||
Interacts: {},
|
|
||||||
Inventory: {
|
|
||||||
slots: {
|
|
||||||
// 1: {
|
|
||||||
// qty: 10,
|
|
||||||
// source: '/assets/potion/potion.json',
|
|
||||||
// },
|
|
||||||
2: {
|
|
||||||
qty: 1,
|
|
||||||
source: '/assets/watering-can/watering-can.json',
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
qty: 1,
|
|
||||||
source: '/assets/tomato-seeds/tomato-seeds.json',
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
qty: 1,
|
|
||||||
source: '/assets/hoe/hoe.json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Health: {health: 100},
|
|
||||||
Position: {x: 368, y: 368},
|
|
||||||
Speed: {speed: 100},
|
|
||||||
Sound: {},
|
|
||||||
Sprite: {
|
|
||||||
anchor: {x: 0.5, y: 0.8},
|
|
||||||
animation: 'moving:down',
|
|
||||||
frame: 0,
|
|
||||||
frames: 8,
|
|
||||||
source: '/assets/dude/dude.json',
|
|
||||||
speed: 0.115,
|
|
||||||
},
|
|
||||||
Ticking: {},
|
|
||||||
VisibleAabb: {},
|
|
||||||
Wielder: {
|
|
||||||
activeSlot: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const buffer = (new TextEncoder()).encode(JSON.stringify(player));
|
|
||||||
await this.server.writeData(
|
|
||||||
join('players', `${id}`),
|
|
||||||
buffer,
|
|
||||||
);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
async disconnectPlayer(connection) {
|
async disconnectPlayer(connection) {
|
||||||
const {entity, id} = this.connectedPlayers.get(connection);
|
const connectedPlayer = this.connectedPlayers.get(connection);
|
||||||
|
if (!connectedPlayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {entity, id} = connectedPlayer;
|
||||||
const ecs = this.ecses[entity.Ecs.path];
|
const ecs = this.ecses[entity.Ecs.path];
|
||||||
await this.savePlayer(id, entity);
|
await this.savePlayer(id, entity);
|
||||||
ecs.destroy(entity.id);
|
ecs.destroy(entity.id);
|
||||||
this.connectedPlayers.delete(connection);
|
this.connectedPlayers.delete(connection);
|
||||||
this.connections.splice(this.connections.indexOf(connection), 1);
|
this.incomingActions.delete(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
|
@ -250,7 +183,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(
|
||||||
this.createEcs(),
|
createEcs(this.Ecs),
|
||||||
await this.server.readData(path),
|
await this.server.readData(path),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -264,8 +197,23 @@ export default class Engine {
|
||||||
if ('ENOENT' !== error.code) {
|
if ('ENOENT' !== error.code) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
await this.createHomestead(id);
|
const homestead = await createHomestead(this.Ecs);
|
||||||
buffer = await this.createPlayer(id);
|
homestead.get(2).Ecs.path = ['houses', `${id}`].join('/');
|
||||||
|
await this.saveEcs(
|
||||||
|
['homesteads', `${id}`].join('/'),
|
||||||
|
homestead,
|
||||||
|
);
|
||||||
|
const house = await createHouse(this.Ecs);
|
||||||
|
house.get(2).Ecs.path = ['homesteads', `${id}`].join('/');
|
||||||
|
await this.saveEcs(
|
||||||
|
['houses', `${id}`].join('/'),
|
||||||
|
house,
|
||||||
|
);
|
||||||
|
buffer = await createPlayer(id);
|
||||||
|
await this.server.writeData(
|
||||||
|
['players', `${id}`].join('/'),
|
||||||
|
buffer,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return JSON.parse((new TextDecoder()).decode(buffer));
|
return JSON.parse((new TextDecoder()).decode(buffer));
|
||||||
}
|
}
|
||||||
|
@ -321,7 +269,10 @@ export default class Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
update(elapsed) {
|
update(elapsed) {
|
||||||
for (const connection of this.connections) {
|
for (const [connection, {entity}] of this.connectedPlayers) {
|
||||||
|
if (!entity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.server.send(
|
this.server.send(
|
||||||
connection,
|
connection,
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,9 @@ import {del, get, set} from 'idb-keyval';
|
||||||
|
|
||||||
import {encode} from '@/packets/index.js';
|
import {encode} from '@/packets/index.js';
|
||||||
|
|
||||||
|
import '../../create-homestead.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';
|
||||||
|
|
||||||
|
@ -57,15 +60,52 @@ onmessage = async (event) => {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (import.meta.hot) {
|
if (import.meta.hot) {
|
||||||
import.meta.hot.accept('../../engine.js', async ({default: Engine}) => {
|
const createResolver = () => {
|
||||||
|
let r;
|
||||||
|
const promise = new Promise((resolve) => {
|
||||||
|
r = resolve;
|
||||||
|
});
|
||||||
|
promise.resolve = r;
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
const beforeResolver = createResolver();
|
||||||
|
const resolvers = [];
|
||||||
|
import.meta.hot.on('vite:beforeUpdate', async () => {
|
||||||
|
engine.stop();
|
||||||
await engine.disconnectPlayer(0);
|
await engine.disconnectPlayer(0);
|
||||||
if (Engine.prototype.createHomestead.toString() !== engine.createHomestead.toString()) {
|
beforeResolver.resolve();
|
||||||
delete engine.ecses['homesteads/0'];
|
});
|
||||||
await engine.server.removeData('homesteads/0');
|
import.meta.hot.accept('../../engine.js');
|
||||||
const newEngine = new Engine(WorkerServer);
|
import.meta.hot.accept('../../create-player.js', async ({default: createPlayer}) => {
|
||||||
await newEngine.createHomestead(0);
|
const resolver = createResolver();
|
||||||
}
|
resolvers.push(resolver);
|
||||||
|
await beforeResolver;
|
||||||
|
const oldBuffer = await engine.server.readData('players/0');
|
||||||
|
const oldPlayer = JSON.parse((new TextDecoder()).decode(oldBuffer));
|
||||||
|
const buffer = await createPlayer(0);
|
||||||
|
const player = JSON.parse((new TextDecoder()).decode(buffer));
|
||||||
|
// Less jarring
|
||||||
|
player.Ecs = oldPlayer.Ecs;
|
||||||
|
player.Direction = oldPlayer.Direction;
|
||||||
|
player.Position = oldPlayer.Position;
|
||||||
|
await engine.server.writeData('players/0', (new TextEncoder()).encode(JSON.stringify(player)));
|
||||||
|
resolver.resolve();
|
||||||
|
});
|
||||||
|
import.meta.hot.accept('../../create-homestead.js', async ({default: createHomestead}) => {
|
||||||
|
const resolver = createResolver();
|
||||||
|
resolvers.push(resolver);
|
||||||
|
await beforeResolver;
|
||||||
|
delete engine.ecses['homesteads/0'];
|
||||||
|
await engine.server.removeData('homesteads/0');
|
||||||
|
const homestead = await createHomestead(engine.Ecs);
|
||||||
|
homestead.get(2).Ecs.path = 'houses/0';
|
||||||
|
await engine.saveEcs('homesteads/0', homestead);
|
||||||
|
resolver.resolve();
|
||||||
|
});
|
||||||
|
import.meta.hot.on('vite:afterUpdate', async () => {
|
||||||
|
await Promise.all(resolvers);
|
||||||
postMessage(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
postMessage(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
||||||
close();
|
close();
|
||||||
});
|
});
|
||||||
|
import.meta.hot.accept();
|
||||||
}
|
}
|
||||||
|
|
3
app/packets/ecs-change.js
Normal file
3
app/packets/ecs-change.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import Packet from '@/net/packet.js';
|
||||||
|
|
||||||
|
export default class EcsChange extends Packet {}
|
|
@ -2,6 +2,7 @@ import {Container} from '@pixi/react';
|
||||||
import {useState} from 'react';
|
import {useState} from 'react';
|
||||||
|
|
||||||
import {RESOLUTION} from '@/constants.js';
|
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';
|
||||||
|
|
||||||
|
@ -15,7 +16,13 @@ export default function Ecs({scale}) {
|
||||||
const [ecs] = useEcs();
|
const [ecs] = useEcs();
|
||||||
const [entities, setEntities] = useState({});
|
const [entities, setEntities] = useState({});
|
||||||
const [mainEntity] = useMainEntity();
|
const [mainEntity] = useMainEntity();
|
||||||
|
usePacket('EcsChange', async () => {
|
||||||
|
setEntities({});
|
||||||
|
}, [setEntities]);
|
||||||
useEcsTick((payload) => {
|
useEcsTick((payload) => {
|
||||||
|
if (!ecs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const updatedEntities = {...entities};
|
const updatedEntities = {...entities};
|
||||||
for (const id in payload) {
|
for (const id in payload) {
|
||||||
const update = payload[id];
|
const update = payload[id];
|
||||||
|
@ -34,14 +41,17 @@ export default function Ecs({scale}) {
|
||||||
}
|
}
|
||||||
setEntities(updatedEntities);
|
setEntities(updatedEntities);
|
||||||
}, [ecs, entities, mainEntity]);
|
}, [ecs, entities, mainEntity]);
|
||||||
if (!mainEntity) {
|
if (!ecs || !mainEntity) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const entity = ecs.get(mainEntity);
|
const entity = ecs.get(mainEntity);
|
||||||
|
if (!entity) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const {Direction, Position, Wielder} = entity;
|
const {Direction, Position, Wielder} = entity;
|
||||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.direction)
|
const projected = Wielder.activeItem()?.project(Position.tile, Direction.direction)
|
||||||
const {Camera} = entity;
|
const {Camera} = entity;
|
||||||
const {TileLayers: {layers: [layer]}, Water: {water}} = ecs.get(1);
|
const {TileLayers: {layers: [layer]}, Water: WaterEcs} = ecs.get(1);
|
||||||
const [cx, cy] = [
|
const [cx, cy] = [
|
||||||
Math.round((Camera.x * scale) - RESOLUTION.x / 2),
|
Math.round((Camera.x * scale) - RESOLUTION.x / 2),
|
||||||
Math.round((Camera.y * scale) - RESOLUTION.y / 2),
|
Math.round((Camera.y * scale) - RESOLUTION.y / 2),
|
||||||
|
@ -53,7 +63,9 @@ export default function Ecs({scale}) {
|
||||||
y={-cy}
|
y={-cy}
|
||||||
>
|
>
|
||||||
<TileLayer tileLayer={layer} />
|
<TileLayer tileLayer={layer} />
|
||||||
<Water tileLayer={layer} water={water} />
|
{WaterEcs && (
|
||||||
|
<Water tileLayer={layer} water={WaterEcs.water} />
|
||||||
|
)}
|
||||||
{projected && (
|
{projected && (
|
||||||
<TargetingGrid
|
<TargetingGrid
|
||||||
tileLayer={layer}
|
tileLayer={layer}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
|
|
||||||
import addKeyListener from '@/add-key-listener.js';
|
import addKeyListener from '@/add-key-listener.js';
|
||||||
|
import ClientEcs from '@/client-ecs';
|
||||||
import {RESOLUTION} from '@/constants.js';
|
import {RESOLUTION} from '@/constants.js';
|
||||||
import {useClient, usePacket} from '@/context/client.js';
|
import {useClient, usePacket} from '@/context/client.js';
|
||||||
import {useDebug} from '@/context/debug.js';
|
import {useDebug} from '@/context/debug.js';
|
||||||
|
@ -24,12 +25,26 @@ export default function Ui({disconnected}) {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const [mainEntity, setMainEntity] = useMainEntity();
|
const [mainEntity, setMainEntity] = useMainEntity();
|
||||||
const [debug, setDebug] = useDebug();
|
const [debug, setDebug] = useDebug();
|
||||||
const [ecs] = useEcs();
|
const [ecs, setEcs] = useEcs();
|
||||||
const [showDisconnected, setShowDisconnected] = useState(false);
|
const [showDisconnected, setShowDisconnected] = useState(false);
|
||||||
const [bufferSlot, setBufferSlot] = useState();
|
const [bufferSlot, setBufferSlot] = useState();
|
||||||
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);
|
||||||
|
const [Components, setComponents] = useState();
|
||||||
|
const [Systems, setSystems] = useState();
|
||||||
|
useEffect(() => {
|
||||||
|
async function setEcsStuff() {
|
||||||
|
const {default: Components} = await import('@/ecs-components/index.js');
|
||||||
|
const {default: Systems} = await import('@/ecs-systems/index.js');
|
||||||
|
setComponents(Components);
|
||||||
|
setSystems(Systems);
|
||||||
|
}
|
||||||
|
setEcsStuff();
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
setEcs(new ClientEcs({Components, Systems}));
|
||||||
|
}, [Components, setEcs, Systems]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let handle;
|
let handle;
|
||||||
if (disconnected) {
|
if (disconnected) {
|
||||||
|
@ -165,6 +180,10 @@ export default function Ui({disconnected}) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [client, debug, setDebug, setScale]);
|
}, [client, debug, setDebug, setScale]);
|
||||||
|
usePacket('EcsChange', async () => {
|
||||||
|
setMainEntity(undefined);
|
||||||
|
setEcs(new ClientEcs({Components, Systems}));
|
||||||
|
}, [Components, Systems, setEcs, setMainEntity]);
|
||||||
usePacket('Tick', async (payload, client) => {
|
usePacket('Tick', async (payload, client) => {
|
||||||
if (0 === Object.keys(payload.ecs).length) {
|
if (0 === Object.keys(payload.ecs).length) {
|
||||||
return;
|
return;
|
||||||
|
@ -173,7 +192,7 @@ export default function Ui({disconnected}) {
|
||||||
for (const listener of client.listeners[':Ecs'] ?? []) {
|
for (const listener of client.listeners[':Ecs'] ?? []) {
|
||||||
listener(payload.ecs);
|
listener(payload.ecs);
|
||||||
}
|
}
|
||||||
}, [hotbarSlots, mainEntity, setMainEntity]);
|
}, [ecs]);
|
||||||
useEcsTick((payload) => {
|
useEcsTick((payload) => {
|
||||||
let localMainEntity = mainEntity;
|
let localMainEntity = mainEntity;
|
||||||
for (const id in payload) {
|
for (const id in payload) {
|
||||||
|
@ -201,21 +220,67 @@ export default function Ui({disconnected}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [hotbarSlots, mainEntity, setMainEntity]);
|
}, [ecs, mainEntity]);
|
||||||
|
useEffect(() => {
|
||||||
|
function onContextMenu(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
document.body.addEventListener('contextmenu', onContextMenu);
|
||||||
|
return () => {
|
||||||
|
document.body.removeEventListener('contextmenu', onContextMenu);
|
||||||
|
};
|
||||||
|
}, [])
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||||
<div
|
<div
|
||||||
className={styles.ui}
|
className={styles.ui}
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
client.send({
|
switch (event.button) {
|
||||||
type: 'Action',
|
case 0:
|
||||||
payload: {type: 'use', value: 'keyDown'},
|
client.send({
|
||||||
});
|
type: 'Action',
|
||||||
|
payload: {type: 'use', value: 1},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
client.send({
|
||||||
|
type: 'Action',
|
||||||
|
payload: {type: 'interact', value: 1},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
}}
|
}}
|
||||||
onMouseUp={(event) => {
|
onMouseUp={(event) => {
|
||||||
client.send({
|
switch (event.button) {
|
||||||
type: 'Action',
|
case 0:
|
||||||
payload: {type: 'use', value: 'keyUp'},
|
client.send({
|
||||||
});
|
type: 'Action',
|
||||||
|
payload: {type: 'use', value: 0},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
client.send({
|
||||||
|
type: 'Action',
|
||||||
|
payload: {type: 'interact', value: 0},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
onWheel={(event) => {
|
||||||
|
if (event.deltaY > 0) {
|
||||||
|
client.send({
|
||||||
|
type: 'Action',
|
||||||
|
payload: {type: 'changeSlot', value: 1 + ((activeSlot + 1) % 10)},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
client.send({
|
||||||
|
type: 'Action',
|
||||||
|
payload: {type: 'changeSlot', value: 1 + ((activeSlot + 9) % 10)},
|
||||||
|
});
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -7,47 +7,8 @@ import ClientContext from '@/context/client.js';
|
||||||
import DebugContext from '@/context/debug.js';
|
import DebugContext from '@/context/debug.js';
|
||||||
import EcsContext from '@/context/ecs.js';
|
import EcsContext from '@/context/ecs.js';
|
||||||
import MainEntityContext from '@/context/main-entity.js';
|
import MainEntityContext from '@/context/main-entity.js';
|
||||||
import Ecs from '@/ecs/ecs.js';
|
|
||||||
import Components from '@/ecs-components/index.js';
|
|
||||||
import Systems from '@/ecs-systems/index.js';
|
|
||||||
import Ui from '@/react-components/ui.jsx';
|
import Ui from '@/react-components/ui.jsx';
|
||||||
import {juggleSession} from '@/session.server';
|
import {juggleSession} from '@/session.server';
|
||||||
import Script from '@/util/script.js';
|
|
||||||
|
|
||||||
import {LRUCache} from 'lru-cache';
|
|
||||||
|
|
||||||
export const cache = new LRUCache({
|
|
||||||
max: 128,
|
|
||||||
});
|
|
||||||
|
|
||||||
class ClientEcs extends Ecs {
|
|
||||||
async readAsset(uri) {
|
|
||||||
if (!cache.has(uri)) {
|
|
||||||
let promise, resolve, reject;
|
|
||||||
promise = new Promise((res, rej) => {
|
|
||||||
resolve = res;
|
|
||||||
reject = rej;
|
|
||||||
});
|
|
||||||
cache.set(uri, promise);
|
|
||||||
fetch(new URL(uri, window.location.origin))
|
|
||||||
.then(async (response) => {
|
|
||||||
resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0));
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
}
|
|
||||||
return cache.get(uri);
|
|
||||||
}
|
|
||||||
async readJson(uri) {
|
|
||||||
const chars = await this.readAsset(uri);
|
|
||||||
return chars.byteLength > 0 ? JSON.parse((new TextDecoder()).decode(chars)) : {};
|
|
||||||
}
|
|
||||||
async readScript(uri, context = {}) {
|
|
||||||
const code = await this.readAsset(uri);
|
|
||||||
if (code.byteLength > 0) {
|
|
||||||
return Script.fromCode((new TextDecoder()).decode(code), context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loader({request}) {
|
export async function loader({request}) {
|
||||||
await juggleSession(request);
|
await juggleSession(request);
|
||||||
|
@ -59,8 +20,9 @@ export default function PlaySpecific() {
|
||||||
const assetsTuple = useState({});
|
const assetsTuple = useState({});
|
||||||
const [client, setClient] = useState();
|
const [client, setClient] = useState();
|
||||||
const mainEntityTuple = useState();
|
const mainEntityTuple = useState();
|
||||||
|
const setMainEntity = mainEntityTuple[1];
|
||||||
const debugTuple = useState(false);
|
const debugTuple = useState(false);
|
||||||
const ecsTuple = useState(new ClientEcs({Components, Systems}));
|
const ecsTuple = useState();
|
||||||
const [disconnected, setDisconnected] = useState(false);
|
const [disconnected, setDisconnected] = useState(false);
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const [type, url] = params['*'].split('/');
|
const [type, url] = params['*'].split('/');
|
||||||
|
@ -117,9 +79,10 @@ export default function PlaySpecific() {
|
||||||
};
|
};
|
||||||
}, [client]);
|
}, [client]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!disconnected) {
|
if (!client || !disconnected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setMainEntity(undefined);
|
||||||
async function reconnect() {
|
async function reconnect() {
|
||||||
await client.connect(url);
|
await client.connect(url);
|
||||||
}
|
}
|
||||||
|
@ -128,7 +91,7 @@ export default function PlaySpecific() {
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(handle);
|
clearInterval(handle);
|
||||||
};
|
};
|
||||||
}, [client, disconnected, url]);
|
}, [client, disconnected, setMainEntity, url]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let source = true;
|
let source = true;
|
||||||
async function play() {
|
async function play() {
|
||||||
|
|
10
public/assets/house/collision-start.js
Normal file
10
public/assets/house/collision-start.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
ecs.switchEcs(
|
||||||
|
other,
|
||||||
|
entity.Ecs.path,
|
||||||
|
{
|
||||||
|
Position: {
|
||||||
|
x: 74,
|
||||||
|
y: 108,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
10
public/assets/shit-shack/collision-start.js
Normal file
10
public/assets/shit-shack/collision-start.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
ecs.switchEcs(
|
||||||
|
other,
|
||||||
|
entity.Ecs.path,
|
||||||
|
{
|
||||||
|
Position: {
|
||||||
|
x: 72,
|
||||||
|
y: 304,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user