import { MOVE_MAP, RESOLUTION, TPS, } from '@/constants.js'; import ControlMovement from '@/ecs-systems/control-movement.js'; import ApplyMomentum from '@/ecs-systems/apply-momentum.js'; import CalculateAabbs from '@/ecs-systems/calculate-aabbs.js'; import FollowCamera from '@/ecs-systems/follow-camera.js'; import UpdateSpatialHash from '@/ecs-systems/update-spatial-hash.js'; import Ecs from '@/engine/ecs.js'; import {decode, encode} from '@/packets/index.js'; const players = { 0: { Camera: {}, Controlled: {up: 0, right: 0, down: 0, left: 0}, Momentum: {}, Position: {x: 50, y: 50}, VisibleAabb: {}, World: {world: 1}, Sprite: {image: '/assets/bunny.png'}, }, }; export default class Engine { static Ecs = Ecs; constructor(Server) { const ecs = new this.constructor.Ecs(); ecs.create({ AreaSize: {x: RESOLUTION[0] * 4, y: RESOLUTION[1] * 4}, }); ecs.addSystem(ControlMovement); ecs.addSystem(ApplyMomentum); ecs.addSystem(FollowCamera); ecs.addSystem(CalculateAabbs); ecs.addSystem(UpdateSpatialHash); this.ecses = { 1: ecs, }; this.connections = []; this.connectedPlayers = new Map(); this.frame = 0; this.last = Date.now(); class SilphiusServer extends Server { accept(connection, packed) { super.accept(connection, decode(packed)); } transmit(connection, packet) { super.transmit(connection, encode(packet)); } } this.server = new SilphiusServer(); this.server.addPacketListener((connection, packet) => { this.accept(connection, packet); }); } accept(connection, {payload, type}) { switch (type) { case 'Action': { const {entity} = this.connectedPlayers.get(connection); if (payload.type in MOVE_MAP) { entity.Controlled[MOVE_MAP[payload.type]] = payload.value; } break; } default: } } async connectPlayer(connection) { this.connections.push(connection); const entityJson = await this.loadPlayer(connection); const ecs = this.ecses[entityJson.World.world]; const entity = ecs.create(entityJson); this.connectedPlayers.set( connection, { entity: ecs.get(entity), memory: new Set(), }, ); } disconnectPlayer(connection) { const {entity} = this.connectedPlayers.get(connection); const ecs = this.ecses[entity.World.world]; players[0] = JSON.parse(JSON.stringify(entity.toJSON())); ecs.destroy(entity.id); this.connectedPlayers.delete(connection); this.connections.splice(this.connections.indexOf(connection), 1); } async load() { } async loadPlayer() { return players[0]; } start() { return setInterval(() => { const elapsed = (Date.now() - this.last) / 1000; this.last = Date.now(); this.tick(elapsed); this.update(elapsed); this.frame += 1; }, 1000 / TPS); } tick(elapsed) { for (const i in this.ecses) { this.ecses[i].setClean(); 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); const mainEntityId = entity.id; const ecs = this.ecses[entity.World.world]; const {hash} = ecs.system(UpdateSpatialHash); const nearby = new Set(); for (const [cx, cy] of hash.data[entity.id]) { hash.chunks[cx][cy].forEach((id) => { nearby.add(ecs.get(id)); }); } 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(); if (mainEntityId === id) { update[id].MainEntity = {}; } } 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; } }