// Node. import msgpack from 'msgpack-lite'; import {performance} from 'perf_hooks'; // 3rd party. // 2nd party. import {InputPacket} from '@avocado/input'; import {World} from '@avocado/physics/matter/world'; import {Ticker} from '@avocado/timing'; import {Room} from '@avocado/topdown'; // 1st party. import {actionIds} from '../common/action-ids'; import {SelfEntityPacket} from '../common/packets/self-entity.packet'; import {WorldTime} from '../common/world-time.synchronized'; import {createEntityForConnection} from './create-entity-for-connection'; // Create game. export default class Game { constructor() { const config = this.readConfig(); // Room. this.room = undefined; Room.load('/kitty-fire.room.json').then((room) => { room.world = new World(); room.world.stepTime = config.simulationInterval; this.room = room; }); // World time. this.worldTime = new WorldTime(); this.worldTime.hour = 10; // Entity tracking. this.informables = []; // State synchronization. this.informTicker = new Ticker(config.informInterval); this.informTicker.on('tick', this.inform, this); InputPacket.setActionIds(actionIds()); // Simulation. this.mainLoopHandle = setInterval( this.createMainLoop(), 1000 * config.simulationInterval ); } destroy(fn) { clearInterval(this.mainLoopHandle); if (this.room) { this.room.destroy(); } fn(); } acceptConnection(socket) { // Create and track a new entity for the connection. const entity = createEntityForConnection(socket); // Track informables. this.informables.push(entity); entity.on('destroyed', () => { const index = this.informables.indexOf(entity); if (-1 !== index) { this.informables.splice(index, 1); } }); Promise.resolve(entity.hydrate()).then(() => { // Flush AABB for visibility. entity.tick(0); // Sync world time. entity.addSynchronized(this.worldTime); // Add entity to room. if (this.room) { this.room.addEntityToLayer(entity, 0); entity.addSynchronized(this.room); } entity.queuePacket(new SelfEntityPacket(entity.numericUid)); entity.inform(); }); // Listen for events. socket.on('packet', this.createPacketListener(socket)); socket.on('disconnect', this.createDisconnectionListener(socket)); } createDisconnectionListener(socket) { const {entity} = socket; return () => { if (entity.is('existent')) { entity.destroy(); } }; } createMainLoop() { let lastTime = performance.now(); return () => { const now = performance.now(); const elapsed = (now - lastTime) / 1000; lastTime = now; // Tick synchronized. if (this.room) { this.room.tick(elapsed); } this.worldTime.tick(elapsed); // Tick informer. this.informTicker.tick(elapsed); } } createPacketListener(socket) { const {entity} = socket; return (packet) => { if (packet instanceof InputPacket) { entity.inputStream = packet.data; } }; } inform() { // Inform entities of the new state. for (let i = 0; i < this.informables.length; ++i) { this.informables[i].inform(); } // Clean packets. if (this.room) { this.room.cleanPackets(); } this.worldTime.cleanPackets(); } readConfig() { return { informInterval: 1 / 60, simulationInterval: 1 / 60, }; } }