Created
March 17, 2018 18:09
-
-
Save diericx/0d905b0eb674982a8e44033a031dac98 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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