enable replays
This commit is contained in:
parent
eabe338971
commit
7a7d6580fc
7 changed files with 129 additions and 36 deletions
|
|
@ -170,8 +170,11 @@ function getPlayerIdByIndex(gameId: string, playerIndex: number): string|null {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlayer(gameId: string, playerId: string): Player {
|
function getPlayer(gameId: string, playerId: string): Player|null {
|
||||||
const idx = getPlayerIndexById(gameId, playerId)
|
const idx = getPlayerIndexById(gameId, playerId)
|
||||||
|
if (idx === -1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return Util.decodePlayer(GAMES[gameId].players[idx])
|
return Util.decodePlayer(GAMES[gameId].players[idx])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,6 +302,10 @@ function changePlayer(
|
||||||
change: any
|
change: any
|
||||||
): void {
|
): void {
|
||||||
const player = getPlayer(gameId, playerId)
|
const player = getPlayer(gameId, playerId)
|
||||||
|
if (player === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for (let k of Object.keys(change)) {
|
for (let k of Object.keys(change)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
player[k] = change[k]
|
player[k] = change[k]
|
||||||
|
|
@ -647,9 +654,13 @@ function handleInput(
|
||||||
}
|
}
|
||||||
|
|
||||||
const _playerChange = (): void => {
|
const _playerChange = (): void => {
|
||||||
|
const player = getPlayer(gameId, playerId)
|
||||||
|
if (!player) {
|
||||||
|
return
|
||||||
|
}
|
||||||
changes.push([
|
changes.push([
|
||||||
Protocol.CHANGE_PLAYER,
|
Protocol.CHANGE_PLAYER,
|
||||||
Util.encodePlayer(getPlayer(gameId, playerId)),
|
Util.encodePlayer(player),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,10 @@ EV_SERVER_INIT: event sent to one client after that client
|
||||||
*/
|
*/
|
||||||
const EV_SERVER_EVENT = 1
|
const EV_SERVER_EVENT = 1
|
||||||
const EV_SERVER_INIT = 4
|
const EV_SERVER_INIT = 4
|
||||||
const EV_SERVER_INIT_REPLAY = 5
|
const EV_SERVER_REPLAY_DATA = 5
|
||||||
const EV_CLIENT_EVENT = 2
|
const EV_CLIENT_EVENT = 2
|
||||||
const EV_CLIENT_INIT = 3
|
const EV_CLIENT_INIT = 3
|
||||||
const EV_CLIENT_INIT_REPLAY = 6
|
const EV_CLIENT_REPLAY_DATA = 6
|
||||||
|
|
||||||
const LOG_HEADER = 1
|
const LOG_HEADER = 1
|
||||||
const LOG_ADD_PLAYER = 2
|
const LOG_ADD_PLAYER = 2
|
||||||
|
|
@ -68,10 +68,10 @@ const CHANGE_PLAYER = 3
|
||||||
export default {
|
export default {
|
||||||
EV_SERVER_EVENT,
|
EV_SERVER_EVENT,
|
||||||
EV_SERVER_INIT,
|
EV_SERVER_INIT,
|
||||||
EV_SERVER_INIT_REPLAY,
|
EV_SERVER_REPLAY_DATA,
|
||||||
EV_CLIENT_EVENT,
|
EV_CLIENT_EVENT,
|
||||||
EV_CLIENT_INIT,
|
EV_CLIENT_INIT,
|
||||||
EV_CLIENT_INIT_REPLAY,
|
EV_CLIENT_REPLAY_DATA,
|
||||||
|
|
||||||
LOG_HEADER,
|
LOG_HEADER,
|
||||||
LOG_ADD_PLAYER,
|
LOG_ADD_PLAYER,
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,13 @@ function connect(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestReplayData(
|
||||||
|
offset: number,
|
||||||
|
size: number
|
||||||
|
): void {
|
||||||
|
send([Protocol.EV_CLIENT_REPLAY_DATA, offset, size])
|
||||||
|
}
|
||||||
|
|
||||||
// TOOD: change replay stuff
|
// TOOD: change replay stuff
|
||||||
function connectReplay(
|
function connectReplay(
|
||||||
address: string,
|
address: string,
|
||||||
|
|
@ -109,16 +116,25 @@ function connectReplay(
|
||||||
ws = new WebSocket(address, clientId + '|' + gameId)
|
ws = new WebSocket(address, clientId + '|' + gameId)
|
||||||
ws.onopen = (e) => {
|
ws.onopen = (e) => {
|
||||||
setConnectionState(CONN_STATE_CONNECTED)
|
setConnectionState(CONN_STATE_CONNECTED)
|
||||||
send([Protocol.EV_CLIENT_INIT_REPLAY])
|
requestReplayData(0, 10000)
|
||||||
}
|
}
|
||||||
ws.onmessage = (e) => {
|
ws.onmessage = (e) => {
|
||||||
const msg = JSON.parse(e.data)
|
const msg = JSON.parse(e.data)
|
||||||
const msgType = msg[0]
|
const msgType = msg[0]
|
||||||
if (msgType === Protocol.EV_SERVER_INIT_REPLAY) {
|
if (msgType === Protocol.EV_SERVER_REPLAY_DATA) {
|
||||||
const game = msg[1]
|
const log: any[] = msg[1]
|
||||||
const log = msg[2]
|
const game = msg[2] // can be null or encoded game
|
||||||
const replay: { game: any, log: Array<any> } = { game, log }
|
if (game !== null) {
|
||||||
|
// this is the first/initial message
|
||||||
|
const replay: {
|
||||||
|
game: any,
|
||||||
|
log: any[]
|
||||||
|
} = { game, log }
|
||||||
resolve(replay)
|
resolve(replay)
|
||||||
|
} else {
|
||||||
|
// this is just the next batch of log entries
|
||||||
|
changesCallback(msg)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw `[ 2021-05-09 invalid connectReplay msgType ${msgType} ]`
|
throw `[ 2021-05-09 invalid connectReplay msgType ${msgType} ]`
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +174,7 @@ function sendClientEvent(evt: any): void {
|
||||||
export default {
|
export default {
|
||||||
connect,
|
connect,
|
||||||
connectReplay,
|
connectReplay,
|
||||||
|
requestReplayData,
|
||||||
disconnect,
|
disconnect,
|
||||||
sendClientEvent,
|
sendClientEvent,
|
||||||
onServerChange,
|
onServerChange,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
{{time(game.started, game.finished)}}<br />
|
{{time(game.started, game.finished)}}<br />
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link v-if="false && game.hasReplay" class="game-replay" :to="{ name: 'replay', params: { id: game.id } }">
|
<router-link v-if="game.hasReplay" class="game-replay" :to="{ name: 'replay', params: { id: game.id } }">
|
||||||
↪️ Watch replay
|
↪️ Watch replay
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,10 @@ interface Hud {
|
||||||
setReplayPaused?: (v: boolean) => void
|
setReplayPaused?: (v: boolean) => void
|
||||||
}
|
}
|
||||||
interface Replay {
|
interface Replay {
|
||||||
|
final: boolean
|
||||||
|
requesting: boolean
|
||||||
log: Array<any>
|
log: Array<any>
|
||||||
|
logPointer: number,
|
||||||
logIdx: number
|
logIdx: number
|
||||||
speeds: Array<number>
|
speeds: Array<number>
|
||||||
speedIdx: number
|
speedIdx: number
|
||||||
|
|
@ -260,7 +263,10 @@ export async function main(
|
||||||
// stuff only available in replay mode...
|
// stuff only available in replay mode...
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
const REPLAY: Replay = {
|
const REPLAY: Replay = {
|
||||||
|
final: false,
|
||||||
|
requesting: true,
|
||||||
log: [],
|
log: [],
|
||||||
|
logPointer: 0,
|
||||||
logIdx: 0,
|
logIdx: 0,
|
||||||
speeds: [0.5, 1, 2, 5, 10, 20, 50],
|
speeds: [0.5, 1, 2, 5, 10, 20, 50],
|
||||||
speedIdx: 1,
|
speedIdx: 1,
|
||||||
|
|
@ -283,12 +289,26 @@ export async function main(
|
||||||
TIME = () => Time.timestamp()
|
TIME = () => Time.timestamp()
|
||||||
} else if (MODE === MODE_REPLAY) {
|
} else if (MODE === MODE_REPLAY) {
|
||||||
// TODO: change how replay connect is done...
|
// TODO: change how replay connect is done...
|
||||||
|
Communication.onServerChange((msg) => {
|
||||||
|
const log = msg[1]
|
||||||
|
|
||||||
|
// cut log that was already handled
|
||||||
|
REPLAY.log = REPLAY.log.slice(REPLAY.logPointer)
|
||||||
|
REPLAY.logPointer = 0
|
||||||
|
|
||||||
|
REPLAY.log.push(...msg[1])
|
||||||
|
if (log.length < 10000) {
|
||||||
|
REPLAY.final = true
|
||||||
|
}
|
||||||
|
REPLAY.requesting = false
|
||||||
|
})
|
||||||
const replay: {game: any, log: Array<any>} = await Communication.connectReplay(wsAddress, gameId, clientId)
|
const replay: {game: any, log: Array<any>} = await Communication.connectReplay(wsAddress, gameId, clientId)
|
||||||
const gameObject = Util.decodeGame(replay.game)
|
const gameObject = Util.decodeGame(replay.game)
|
||||||
Game.setGame(gameObject.id, gameObject)
|
Game.setGame(gameObject.id, gameObject)
|
||||||
|
REPLAY.requesting = false
|
||||||
REPLAY.log = replay.log
|
REPLAY.log = replay.log
|
||||||
REPLAY.lastRealTs = Time.timestamp()
|
REPLAY.lastRealTs = Time.timestamp()
|
||||||
REPLAY.gameStartTs = parseInt(REPLAY.log[0][REPLAY.log[0].length - 2], 10)
|
REPLAY.gameStartTs = parseInt(REPLAY.log[0][4], 10)
|
||||||
REPLAY.lastGameTs = REPLAY.gameStartTs
|
REPLAY.lastGameTs = REPLAY.gameStartTs
|
||||||
TIME = () => REPLAY.lastGameTs
|
TIME = () => REPLAY.lastGameTs
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -426,6 +446,7 @@ export async function main(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MODE === MODE_PLAY) {
|
if (MODE === MODE_PLAY) {
|
||||||
|
// TODO: register onServerChange function before connecting to server
|
||||||
Communication.onServerChange((msg) => {
|
Communication.onServerChange((msg) => {
|
||||||
const msgType = msg[0]
|
const msgType = msg[0]
|
||||||
const evClientId = msg[1]
|
const evClientId = msg[1]
|
||||||
|
|
@ -458,6 +479,18 @@ export async function main(
|
||||||
// only the REPLAY.log is relevant
|
// only the REPLAY.log is relevant
|
||||||
let inter = setInterval(() => {
|
let inter = setInterval(() => {
|
||||||
const realTs = Time.timestamp()
|
const realTs = Time.timestamp()
|
||||||
|
if (REPLAY.requesting) {
|
||||||
|
REPLAY.lastRealTs = realTs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (REPLAY.logPointer + 1 >= REPLAY.log.length) {
|
||||||
|
REPLAY.lastRealTs = realTs
|
||||||
|
REPLAY.requesting = true
|
||||||
|
Communication.requestReplayData(REPLAY.logIdx, 10000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (REPLAY.paused) {
|
if (REPLAY.paused) {
|
||||||
REPLAY.lastRealTs = realTs
|
REPLAY.lastRealTs = realTs
|
||||||
return
|
return
|
||||||
|
|
@ -469,9 +502,11 @@ export async function main(
|
||||||
if (REPLAY.paused) {
|
if (REPLAY.paused) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
const nextIdx = REPLAY.logIdx + 1
|
const nextIdx = REPLAY.logPointer + 1
|
||||||
if (nextIdx >= REPLAY.log.length) {
|
if (nextIdx >= REPLAY.log.length) {
|
||||||
|
if (REPLAY.final) {
|
||||||
clearInterval(inter)
|
clearInterval(inter)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -502,7 +537,8 @@ export async function main(
|
||||||
Game.handleInput(gameId, playerId, input, nextTs)
|
Game.handleInput(gameId, playerId, input, nextTs)
|
||||||
RERENDER = true
|
RERENDER = true
|
||||||
}
|
}
|
||||||
REPLAY.logIdx = nextIdx
|
REPLAY.logPointer = nextIdx
|
||||||
|
REPLAY.logIdx++
|
||||||
} while (true)
|
} while (true)
|
||||||
REPLAY.lastRealTs = realTs
|
REPLAY.lastRealTs = realTs
|
||||||
REPLAY.lastGameTs = maxGameTs
|
REPLAY.lastGameTs = maxGameTs
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import readline from 'readline'
|
||||||
|
import stream from 'stream'
|
||||||
import { logger } from './../common/Util'
|
import { logger } from './../common/Util'
|
||||||
import { DATA_DIR } from './../server/Dirs'
|
import { DATA_DIR } from './../server/Dirs'
|
||||||
|
|
||||||
|
|
@ -27,19 +29,39 @@ const _log = (gameId: string, ...args: Array<any>) => {
|
||||||
fs.appendFileSync(file, str + "\n")
|
fs.appendFileSync(file, str + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
const get = (gameId: string) => {
|
const get = async (
|
||||||
|
gameId: string,
|
||||||
|
offset: number = 0,
|
||||||
|
size: number = 10000
|
||||||
|
): Promise<any[]> => {
|
||||||
const file = filename(gameId)
|
const file = filename(gameId)
|
||||||
if (!fs.existsSync(file)) {
|
if (!fs.existsSync(file)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const lines = fs.readFileSync(file, 'utf-8').split("\n")
|
return new Promise((resolve) => {
|
||||||
return lines.filter((line: string) => !!line).map((line: string) => {
|
const instream = fs.createReadStream(file)
|
||||||
try {
|
const outstream = new stream.Writable()
|
||||||
return JSON.parse(line)
|
const rl = readline.createInterface(instream, outstream)
|
||||||
} catch (e) {
|
const lines: any[] = []
|
||||||
log.log(line)
|
let i = -1
|
||||||
log.log(e)
|
rl.on('line', (line) => {
|
||||||
|
if (!line) {
|
||||||
|
// skip empty
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
i++
|
||||||
|
if (offset > i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (offset + size <= i) {
|
||||||
|
rl.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lines.push(JSON.parse(line))
|
||||||
|
})
|
||||||
|
rl.on('close', () => {
|
||||||
|
resolve(lines)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,20 +203,27 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
|
||||||
const msg = JSON.parse(data)
|
const msg = JSON.parse(data)
|
||||||
const msgType = msg[0]
|
const msgType = msg[0]
|
||||||
switch (msgType) {
|
switch (msgType) {
|
||||||
case Protocol.EV_CLIENT_INIT_REPLAY: {
|
case Protocol.EV_CLIENT_REPLAY_DATA: {
|
||||||
if (!GameLog.exists(gameId)) {
|
if (!GameLog.exists(gameId)) {
|
||||||
throw `[gamelog ${gameId} does not exist... ]`
|
throw `[gamelog ${gameId} does not exist... ]`
|
||||||
}
|
}
|
||||||
const log = GameLog.get(gameId)
|
const offset = msg[1]
|
||||||
const game = await Game.createGameObject(
|
const size = msg[2]
|
||||||
|
|
||||||
|
const log = await GameLog.get(gameId, offset, size)
|
||||||
|
let game = null
|
||||||
|
if (offset === 0) {
|
||||||
|
// also need the game
|
||||||
|
game = await Game.createGameObject(
|
||||||
gameId,
|
gameId,
|
||||||
log[0][2],
|
log[0][2],
|
||||||
log[0][3],
|
log[0][3],
|
||||||
log[0][4],
|
log[0][4],
|
||||||
log[0][5] || ScoreMode.FINAL
|
log[0][5] || ScoreMode.FINAL
|
||||||
)
|
)
|
||||||
|
}
|
||||||
notify(
|
notify(
|
||||||
[Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log],
|
[Protocol.EV_SERVER_REPLAY_DATA, log, game ? Util.encodeGame(game) : null],
|
||||||
[socket]
|
[socket]
|
||||||
)
|
)
|
||||||
} break
|
} break
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue