// Node. import {performance} from 'perf_hooks'; // 3rd party. import immutablediff from 'immutablediff'; // 2nd party. import { create as createEntity, EntityList, registerTrait, } from '@avocado/entity'; import {StateSynchronizer} from '@avocado/state'; // Traits. import {Controllable} from './traits/controllable'; registerTrait(Controllable); import {Informed} from './traits/informed'; registerTrait(Informed); // Create game. export default function(avocadoServer) { avocadoServer.on('connect', createConnectionListener(avocadoServer)); setInterval(createMainLoop(avocadoServer), 1000 / 80); } // Entity tracking. const entityList = new EntityList(); const stateSynchronizer = new StateSynchronizer({ entityList, }); // Connection listener. function createConnectionListener(avocadoServer) { return (socket) => { // Create and track a new entity for the connection. const entity = createEntityForConnection(); entityList.addEntity(entity); entity.addTrait('informed', { params: { socket: socket, }, }); socket.entity = entity; // Send current state. const currentState = stateSynchronizer.state().toJS(); const reduced = entity.reduceStateDiff(currentState); entity.inform(reduced); // Listen for events. socket.on('message', createMessageListener(avocadoServer, socket)); socket.on('disconnect', createDisconnectionListener(avocadoServer, socket)); } } // Handle incoming messages. function createMessageListener(avocadoServer, socket) { const {entity} = socket; return ({type, payload}) => { switch (type) { case 'input': entity.inputState = payload; break; } }; } // Handle disconnection. function createDisconnectionListener(avocadoServer, socket) { const {entity} = socket; return () => { const nearbyEntities = entity.entitiesToInform(); entityList.removeEntity(entity); nearbyEntities.forEach((nearbyEntity) => { if (!('inform' in nearbyEntity)) { return; } nearbyEntity.inform({ entityList: { [entity.instanceUuid]: false, } }); }); }; } // Create an entity for a new connection. function createEntityForConnection() { const entity = createEntity(); return entity.fromJSON({ traits: { animated: { params: { animations: { idle: { offset: [0, -48], uri: '/idle.animation.json', }, moving: { offset: [0, -48], uri: '/moving.animation.json', }, } }, }, controllable: {}, directional: { params: { directionCount: 4, }, }, existent: {}, graphical: {}, mobile: { state: { speed: 400, }, }, positioned: { state: { x: 100, y: 100, }, }, }, }); } // Main loop. let lastTime = performance.now(); function createMainLoop(avocadoServer) { return () => { const now = performance.now(); const elapsed = (now - lastTime) / 1000; lastTime = now; entityList.tick(elapsed); const diff = stateSynchronizer.diff(); if (StateSynchronizer.noChange === diff) { return; } // All informed entities get their own slice. for (const entity of entityList) { if (!('inform' in entity)) { continue; } const reduced = entity.reduceStateDiff(diff); entity.inform(reduced); } } }