// Node. import {performance} from 'perf_hooks'; // 3rd party. import immutablediff from 'immutablediff'; // 2nd party. import { create as createEntity, EntityList, registerTrait, } from '@avocado/entity'; import {World} from '@avocado/physics/dummy'; import {StateSynchronizer} from '@avocado/state'; // Create game. export default function(avocadoServer) { avocadoServer.on('connect', createConnectionListener(avocadoServer)); setInterval(createMainLoop(avocadoServer), 1000 / 80); } // Entity tracking. const entityList = new EntityList(); entityList.world = new World(); const informables = []; const stateSynchronizer = new StateSynchronizer({ entityList, }); for (let i = 0; i < 20; ++i) { const x = Math.floor(Math.random() * 1000) + 100; const y = Math.floor(Math.random() * 500) + 100; const flowerBarrel = createFlowerBarrelEntity([x, y]); entityList.addEntity(flowerBarrel); } // Connection listener. function createConnectionListener(avocadoServer) { return (socket) => { // Create and track a new entity for the connection. const entity = createEntityForConnection(socket); entityList.addEntity(entity); // 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 () => { entity.destroy(); }; } // Create an entity for a new connection. function createEntityForConnection(socket) { let entity = createEntity(); entity = 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: {}, informed: {}, mobile: { state: { speed: 400, }, }, physical: { params: { shape: { type: 'rectangle', position: [0, 0], size: [32, 32], } } }, positioned: { state: { x: 100, y: 100, }, }, }, }); // Track informables. informables.push(entity); entity.on('destroyed', () => { const index = informables.indexOf(entity); if (-1 !== index) { informables.splice(index, 1); } }); // Embed socket. entity.socket = socket; return entity; } // Create a flower barrel. function createFlowerBarrelEntity(position) { const entity = createEntity(); return entity.fromJSON({ traits: { pictured: { params: { images: { initial: { offset: [0, -32], size: [128, 128], // Derive? uri: '/flower-barrel.png', }, } }, }, existent: {}, graphical: {}, physical: { params: { shape: { type: 'rectangle', position: [0, 0], size: [32, 32], } } }, positioned: { state: { x: position[0], y: position[1], }, }, }, }); } // 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 informables) { const CAMERA_LOCATION = [640, 360]; const reduced = entity.reduceStateDiff(diff, CAMERA_LOCATION); entity.inform(reduced); } } }