humus-old/server/game.js
2019-03-20 18:58:19 -05:00

137 lines
3.4 KiB
JavaScript

// 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) => {
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);
}
}
}