134 lines
3.5 KiB
JavaScript
134 lines
3.5 KiB
JavaScript
// 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. Start at 10 am for testing.
|
|
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,
|
|
};
|
|
}
|
|
|
|
}
|