silphius/app/engine.js

337 lines
8.1 KiB
JavaScript
Raw Normal View History

2024-06-10 22:42:30 -05:00
import {
TPS,
} from '@/constants.js';
2024-06-15 18:23:10 -05:00
import Ecs from '@/ecs/ecs.js';
2024-06-15 19:38:49 -05:00
import Components from '@/ecs-components/index.js';
2024-06-15 18:23:10 -05:00
import Systems from '@/ecs-systems/index.js';
2024-06-10 23:55:06 -05:00
import {decode, encode} from '@/packets/index.js';
import Script from '@/util/script.js';
2024-06-10 22:42:30 -05:00
2024-06-15 20:59:11 -05:00
function join(...parts) {
return parts.join('/');
}
2024-06-10 22:42:30 -05:00
export default class Engine {
2024-06-13 12:24:32 -05:00
connections = [];
connectedPlayers = new Map();
ecses = {};
frame = 0;
2024-06-21 18:16:41 -05:00
handle;
2024-06-26 11:40:14 -05:00
incomingActions = [];
2024-06-13 12:24:32 -05:00
last = Date.now();
server;
2024-06-10 22:42:30 -05:00
constructor(Server) {
2024-06-14 15:18:55 -05:00
this.ecses = {};
2024-06-10 22:42:30 -05:00
class SilphiusServer extends Server {
accept(connection, packed) {
super.accept(connection, decode(packed));
}
transmit(connection, packet) {
super.transmit(connection, encode(packet));
}
}
this.server = new SilphiusServer();
2024-06-13 01:26:01 -05:00
this.server.addPacketListener('Action', (connection, payload) => {
this.incomingActions.push([this.connectedPlayers.get(connection).entity, payload]);
2024-06-10 22:42:30 -05:00
});
}
2024-06-25 12:05:14 -05:00
acceptActions() {
for (const [
entity,
payload,
] of this.incomingActions) {
const {Controlled, Inventory, Ticking, Wielder} = entity;
switch (payload.type) {
case 'changeSlot': {
if (!Controlled.locked) {
Wielder.activeSlot = payload.value - 1;
}
break;
}
case 'moveUp':
case 'moveRight':
case 'moveDown':
case 'moveLeft': {
Controlled[payload.type] = payload.value;
break;
}
case 'swapSlots': {
if (!Controlled.locked) {
Inventory.swapSlots(...payload.value);
}
break;
}
case 'use': {
if (!Controlled.locked) {
Inventory.item(Wielder.activeSlot + 1).then(async (item) => {
if (item) {
const code = await(
this.server.readAsset([
item.source,
payload.value ? 'start.js' : 'stop.js',
].join('/'))
.then((script) => (script.ok ? script.text() : ''))
);
if (code) {
const context = {
ecs: this.ecses[entity.Ecs.path],
item,
wielder: entity,
};
Ticking.addTickingPromise(Script.tickingPromise(code, context));
}
}
});
}
break;
}
}
}
this.incomingActions = [];
}
2024-06-14 15:18:55 -05:00
async connectPlayer(connection, id) {
const entityJson = await this.loadPlayer(id);
if (!this.ecses[entityJson.Ecs.path]) {
await this.loadEcs(entityJson.Ecs.path);
}
const ecs = this.ecses[entityJson.Ecs.path];
2024-06-10 22:42:30 -05:00
const entity = ecs.create(entityJson);
2024-06-14 15:18:55 -05:00
this.connections.push(connection);
2024-06-10 22:42:30 -05:00
this.connectedPlayers.set(
connection,
{
entity: ecs.get(entity),
2024-06-14 15:18:55 -05:00
id,
2024-06-10 22:42:30 -05:00
memory: new Set(),
},
);
}
2024-06-14 15:18:55 -05:00
createEcs() {
2024-06-15 19:38:49 -05:00
return new Ecs({Components, Systems});
2024-06-15 18:23:10 -05:00
}
async createHomestead(id) {
const ecs = this.createEcs();
2024-06-14 12:05:02 -05:00
const area = {x: 100, y: 60};
2024-06-14 15:18:55 -05:00
ecs.create({
2024-06-14 12:05:02 -05:00
AreaSize: {x: area.x * 16, y: area.y * 16},
2024-06-25 05:46:03 -05:00
Engine: {},
2024-06-14 12:05:02 -05:00
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},
}
],
},
});
2024-06-14 15:18:55 -05:00
const defaultSystems = [
2024-06-25 10:44:37 -05:00
'ResetForces',
2024-06-19 02:02:14 -05:00
'ApplyControlMovement',
2024-06-25 10:44:37 -05:00
'ApplyForces',
2024-06-14 15:18:55 -05:00
'ClampPositions',
'FollowCamera',
'CalculateAabbs',
'UpdateSpatialHash',
'ControlDirection',
'SpriteDirection',
'RunAnimations',
'RunTickingPromises',
2024-06-14 15:18:55 -05:00
];
defaultSystems.forEach((defaultSystem) => {
2024-06-21 06:04:22 -05:00
const System = ecs.system(defaultSystem);
if (System) {
System.active = true;
}
2024-06-14 15:18:55 -05:00
});
2024-06-21 18:16:41 -05:00
this.saveEcs(join('homesteads', `${id}`), ecs);
2024-06-14 12:05:02 -05:00
}
2024-06-14 15:18:55 -05:00
async createPlayer(id) {
const player = {
Camera: {},
Controlled: {},
2024-06-14 15:18:55 -05:00
Direction: {direction: 2},
Ecs: {path: join('homesteads', `${id}`)},
2024-06-26 04:18:46 -05:00
Emitter: {},
2024-06-25 10:44:37 -05:00
Forces: {},
Inventory: {
2024-06-24 04:20:07 -05:00
slots: {
1: {
2024-06-24 04:43:11 -05:00
qty: 10,
source: '/assets/potion',
2024-06-24 04:20:07 -05:00
},
2024-06-25 01:46:07 -05:00
3: {
qty: 1,
source: '/assets/tomato-seeds',
},
2024-06-24 09:01:30 -05:00
4: {
qty: 1,
source: '/assets/hoe',
},
2024-06-24 04:20:07 -05:00
},
},
Health: {health: 100},
2024-06-14 15:18:55 -05:00
Position: {x: 368, y: 368},
VisibleAabb: {},
2024-06-19 02:02:14 -05:00
Speed: {speed: 100},
2024-06-25 12:29:09 -05:00
Sound: {},
2024-06-14 15:18:55 -05:00
Sprite: {
2024-06-22 12:12:13 -05:00
anchor: {x: 0.5, y: 0.8},
2024-06-14 15:18:55 -05:00
animation: 'moving:down',
frame: 0,
frames: 8,
2024-06-26 09:17:56 -05:00
source: '/assets/dude/dude.json',
2024-06-14 15:18:55 -05:00
speed: 0.115,
},
Ticking: {},
Wielder: {
activeSlot: 0,
},
2024-06-14 15:18:55 -05:00
};
const buffer = (new TextEncoder()).encode(JSON.stringify(player));
await this.server.writeData(
join('players', `${id}`),
buffer,
);
return buffer;
}
async disconnectPlayer(connection) {
const {entity, id} = this.connectedPlayers.get(connection);
const ecs = this.ecses[entity.Ecs.path];
await this.savePlayer(id, entity);
2024-06-10 22:42:30 -05:00
ecs.destroy(entity.id);
this.connectedPlayers.delete(connection);
this.connections.splice(this.connections.indexOf(connection), 1);
}
async load() {
}
2024-06-14 15:18:55 -05:00
async loadEcs(path) {
2024-06-15 18:23:10 -05:00
this.ecses[path] = Ecs.deserialize(
this.createEcs(),
await this.server.readData(path),
);
2024-06-25 05:46:03 -05:00
this.ecses[path].get(1).Engine.engine = this;
2024-06-14 15:18:55 -05:00
}
async loadPlayer(id) {
let buffer;
try {
buffer = await this.server.readData(['players', `${id}`].join('/'))
}
catch (error) {
if ('ENOENT' !== error.code) {
throw error;
}
await this.createHomestead(id);
buffer = await this.createPlayer(id);
}
return JSON.parse((new TextDecoder()).decode(buffer));
}
2024-06-21 18:16:41 -05:00
async saveEcs(path, ecs) {
const view = Ecs.serialize(ecs);
await this.server.writeData(path, view);
}
async saveEcses() {
const promises = []
for (const i in this.ecses) {
promises.push(this.saveEcs(i, this.ecses[i]));
}
await Promise.all(promises);
}
2024-06-14 15:18:55 -05:00
async savePlayer(id, entity) {
const encoder = new TextEncoder();
const buffer = encoder.encode(JSON.stringify(entity.toJSON()));
await this.server.writeData(['players', `${id}`].join('/'), buffer);
2024-06-10 22:42:30 -05:00
}
2024-06-25 12:05:14 -05:00
setClean() {
for (const i in this.ecses) {
this.ecses[i].setClean();
}
}
2024-06-10 22:42:30 -05:00
start() {
2024-06-21 18:16:41 -05:00
this.handle = setInterval(() => {
2024-06-10 22:42:30 -05:00
const elapsed = (Date.now() - this.last) / 1000;
this.last = Date.now();
2024-06-25 12:05:14 -05:00
this.acceptActions();
2024-06-10 22:42:30 -05:00
this.tick(elapsed);
this.update(elapsed);
2024-06-25 12:05:14 -05:00
this.setClean();
2024-06-10 22:42:30 -05:00
this.frame += 1;
}, 1000 / TPS);
}
2024-06-21 18:16:41 -05:00
stop() {
clearInterval(this.handle);
this.handle = undefined;
}
2024-06-10 22:42:30 -05:00
tick(elapsed) {
2024-06-12 19:35:51 -05:00
for (const i in this.ecses) {
2024-06-10 22:42:30 -05:00
this.ecses[i].tick(elapsed);
}
}
update(elapsed) {
for (const connection of this.connections) {
this.server.send(
connection,
{
type: 'Tick',
payload: {
ecs: this.updateFor(connection),
elapsed,
frame: this.frame,
},
},
);
}
}
updateFor(connection) {
const update = {};
const {entity, memory} = this.connectedPlayers.get(connection);
2024-06-11 01:41:19 -05:00
const mainEntityId = entity.id;
2024-06-14 15:18:55 -05:00
const ecs = this.ecses[entity.Ecs.path];
const nearby = ecs.system('UpdateSpatialHash').nearby(entity);
2024-06-12 13:19:16 -05:00
// Master entity.
nearby.add(ecs.get(1));
2024-06-10 22:42:30 -05:00
const lastMemory = new Set(memory.values());
for (const entity of nearby) {
const {id} = entity;
lastMemory.delete(id);
if (!memory.has(id)) {
update[id] = entity.toJSON();
2024-06-11 01:41:19 -05:00
if (mainEntityId === id) {
update[id].MainEntity = {};
}
2024-06-10 22:42:30 -05:00
}
else if (ecs.diff[id]) {
update[id] = ecs.diff[id];
}
memory.add(id);
}
for (const id of lastMemory) {
memory.delete(id);
update[id] = false;
}
return update;
}
}