Skip to content

Instantly share code, notes, and snippets.

@diericx
Created March 17, 2018 18:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save diericx/0d905b0eb674982a8e44033a031dac98 to your computer and use it in GitHub Desktop.
Save diericx/0d905b0eb674982a8e44033a031dac98 to your computer and use it in GitHub Desktop.
import * as msgpack from "notepack.io";
import * as fossilDelta from "fossil-delta";
import * as shortid from "shortid";
import { Client, EntityMap } from "./index";
import { Protocol, send } from "./Protocol";
import { logError, spliceOne } from "./Utils";
import { debugPatch, debugPatchData } from "./Debug";
import { Room } from "./Room";
import * as jsonPatch from "fast-json-patch"; // this is only used for debugging patches
export type Map<T> = {[ id:string ]: T};
export class RegionState {
players: EntityMap<Actor>;
}
export class Region<GRegionState extends RegionState> {
public _previousState: any;
public _previousStateEncoded: any;
public width: number = 5;
public height: number = 5;
public state: GRegionState;
constructor(width, height) {
this.width = width;
this.height = height;
this._previousState = this.state;
this._previousStateEncoded = msgpack.encode( this._previousState );
}
}
export class RoomState<GRegionState extends RegionState, GRegion extends Region<GRegionState>> {
regions: Map<GRegion>;
regionWidth: 5;
regionHeight: 5;
// keep track of all actors
public actors = {};
}
export class Actor {
public id: string;
public x: number;
public y: number;
rotation: number;
public regionX: number;
public regionY: number;
constructor(id, x, y) {
if (id == "") {
id = shortid.generate();
}
this.id = id;
this.x = x;
this.y = y;
this.rotation = 0;
}
UpdatePosition(movX, movY) {
let rotation = this.ToRadians(this.rotation);
if (movX != 0) {
this.x += movX * Math.cos(rotation);
this.y += movX * Math.sin(rotation);
}
if (movY != 0) {
this.x += movY * Math.cos(rotation + Math.PI/2);
this.y += movY * Math.sin(rotation + Math.PI/2);
}
}
ToRadians (angle) {
return angle * (Math.PI / 180);
}
}
export abstract class RoomAOI<GRegionState extends RegionState, GRegion extends Region<GRegionState>, GRoomState extends RoomState<GRegionState,GRegion>> extends Room {
// keep track of all clients
public clients = {};
public state: GRoomState;
// when a new user connects, it receives the '_previousState', which holds
// the last binary snapshot other users already have, therefore the patches
// that follow will be the same for all clients.
protected _previousState: any;
protected _previousStateEncoded: any;
constructor () {
super();
if (arguments.length > 0) {
console.warn("DEPRECATION WARNING: use 'onInit(options)' instead of 'constructor(options)' to initialize the room.");
}
this.setPatchRate(this.patchRate);
}
public getNumClients(clients) {
return Object.keys(clients).length
}
public mapClients(clients, fun) {
let clientIds = Object.keys(this.clients);
for (let i = 0; i < clientIds.length; i++) {
fun(clients[clientIds[i]]);
}
}
protected addClient(client: Client) {
this.clients[client.sessionId] = client;
}
protected removeClient(client: Client): boolean {
return delete this.clients[client.sessionId];
}
public setState (newState) {
this.clock.start();
this._previousState = newState;
// ensure state is populated for `sendState()` method.
this._previousStateEncoded = msgpack.encode( this._previousState );
this.state = newState;
if ( this.timeline ) {
this.timeline.takeSnapshot( this.state );
}
}
protected sendState (client: Client): void {
send(client, [
Protocol.ROOM_STATE,
this.roomId,
this.state["regions"][0]._previousStateEncoded,
this.clock.currentTime,
this.clock.elapsedTime,
]);
}
protected broadcast (regionId: number, data: any): boolean {
// no data given, try to broadcast patched state
if (!data) {
throw new Error("Room#broadcast: 'data' is required to broadcast.");
}
// encode all messages with msgpack
if (!(data instanceof Buffer)) {
data = msgpack.encode([Protocol.ROOM_DATA, this.roomId, data]);
}
let region = this.state["regions"][regionId];
let players = region.state.players;
let playerIds = Object.keys(region.state.players);
for (let i = 0; i < playerIds.length; i++) {
this.clients[playerIds[i]].send(data, { binary: true }, logError.bind(this) );
}
return true;
}
protected broadcastPatch (): boolean {
if ( !this._previousState ) {
debugPatch('trying to broadcast null state. you should call #setState on constructor or during user connection.');
return false;
}
for (let i = 0; i < Object.keys(this.state.regions).length; i++) {
let region = this.state["regions"][i];
let regionState = region.state;
let regionStateEncoded = msgpack.encode( regionState );
if ( regionStateEncoded.equals( region._previousStateEncoded ) ) {
return false;
}
let patches = fossilDelta.create( region._previousStateEncoded, regionStateEncoded );
if (this.timeline) {
this.timeline.takeSnapshot( this.state, this.clock.elapsedTime );
}
// update the regions state status
region._previousState = regionState;
region._previousStateEncoded = regionStateEncoded;
// broadcast to all players in this region
this.broadcast( i, msgpack.encode([ Protocol.ROOM_STATE_PATCH, this.roomId, patches ]) );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment