157 lines
3.8 KiB
JavaScript
157 lines
3.8 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';
|
|
// Create game.
|
|
export default function(avocadoServer) {
|
|
avocadoServer.on('connect', createConnectionListener(avocadoServer));
|
|
setInterval(createMainLoop(avocadoServer), 1000 / 80);
|
|
}
|
|
// Entity tracking.
|
|
const entityList = new EntityList();
|
|
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,
|
|
},
|
|
},
|
|
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],
|
|
uri: '/flower-barrel.png',
|
|
},
|
|
}
|
|
},
|
|
},
|
|
existent: {},
|
|
graphical: {},
|
|
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);
|
|
}
|
|
}
|
|
}
|