card_svr/src/rooms/GeneralRoom.ts
2021-03-05 18:18:16 +08:00

416 lines
13 KiB
TypeScript

import { Client, Room } from 'colyseus'
import { CardGameState } from './schema/CardGameState'
import { OnJoinCommand } from './commands/OnJoinCommand'
import { PlayReadyCommand } from './commands/PlayReadyCommand'
import { Dispatcher } from '@colyseus/command'
import { DiscardCommand } from './commands/DiscardCommand'
import { SelectPetCommand } from './commands/SelectPetCommand'
import { ChangeCardCommand } from './commands/ChangeCardCommand'
import { SelectHeroCommand } from './commands/SelectHeroCommand'
import { EatCardCommand } from './commands/EatCardCommand'
import { GiveUpCommand } from './commands/GiveUpCommand'
import { BattleHandler } from './logic/Handler/BattleHandler'
import { debugRoom, error, msgLog } from '../common/Debug'
import { Delayed } from '@gamestdio/timer/lib/Delayed'
import { IncomingMessage } from 'http'
import { Player } from './schema/Player'
import { GMCommand } from './commands/GMCommand'
import { GameStateConst } from '../constants/GameStateConst'
import { GameRestartCommand } from './commands/GameRestartCommand'
import { RobotClient } from '../robot/RobotClient'
import { ChangePetCommand } from './commands/ChangePetCommand'
import { createRobot } from '../common/WebApi'
import { Service } from '../service/Service'
import { ManualTurnEndCommand } from './commands/ManualTurnEndCommand'
import { RoomOptions } from '../cfg/RoomOptions'
export class GeneralRoom extends Room {
dispatcher = new Dispatcher(this)
maxClients = 4
score = 0
battleMan = new BattleHandler()
// 用于游戏过程中各种计时器, 使用该计时器的前提是, 只针对当前操作玩家
gameClock: Map<string, Delayed> = new Map()
assistMap: Map<String, RobotClient> = new Map()
match = ''
robotCount = 0
async onAuth(client: Client, options: any, request: IncomingMessage) {
debugRoom(options)
// TODO: 验证用户信息
// client.auth.accountId = options.accountId;
// client.auth.sessionId = options.sessionId;
return true
}
onCreate(options: any) {
let cs = new CardGameState()
if (options.clients) {
this.maxClients = options.clients
}
this.setState(cs)
if (options.mode) {
this.state.mode = +options.mode
}
this.setPrivate(true)
if (options.count) {
this.robotCount = Math.min(Math.max(0, options.count), this.maxClients - 1)
}
this.match = options.match
if (options.score) {
this.score = options.score
}
let val = 0
if (options.debug_cv) {
val = +options.debug_cv
}
new RoomOptions().initGameRule(this.state, val)
this.battleMan.init(cs, this)
this.clock.start()
this.state.gameState = GameStateConst.STATE_WAIT_JOIN
this.onMessage('play_ready_c2s', (client, message) => {
msgLog('play_ready from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new PlayReadyCommand(), { client })
})
this.onMessage('change_card_c2s', (client, message) => {
msgLog('change_card from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new ChangeCardCommand(), {
client,
cards: message.cards
})
})
this.onMessage('discard_card_c2s', (client, message) => {
msgLog('discard_card from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new DiscardCommand(), {
client,
cards: message.cards,
target: message.target,
dtype: 0,
nums: message.nums
})
})
this.onMessage('eat_card_c2s', (client, message) => {
msgLog('eat_card from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new EatCardCommand(), {
client,
cards: message.cards,
target: message.target
})
})
this.onMessage('give_up_eat_c2s', (client, message) => {
msgLog('give_up_take from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new GiveUpCommand(), { client })
})
this.onMessage('select_pet_c2s', (client, message) => {
msgLog('select_pet from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new SelectPetCommand(), {
client,
cardId: message.card,
playerId: message.player,
pos: message.pos,
effCards: message.effCards,
oldpos: message.oldPos
})
})
this.onMessage('select_hero_c2s', (client, message) => {
msgLog('select_hero from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new SelectHeroCommand(), {
client,
heroId: message.heroId,
cardGroup: message.cardgroup
})
})
this.onMessage('gm', (client, message) => {
msgLog('gm command from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new GMCommand(), { client, message })
})
this.onMessage('restart_c2s', (client, message) => {
msgLog('restart game from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new GameRestartCommand(), { client })
})
this.onMessage('change_pet_c2s', (client, message) => {
msgLog('change pet from ', client.sessionId, JSON.stringify(message))
this.dispatcher.dispatch(new ChangePetCommand(), {
client,
pos: message.pos,
petId: message.petId
})
})
/**
* broadcast_c2s的消息, 原样广播出去
*/
this.onMessage('broadcast_c2s', (client, message) => {
msgLog('broadcast_c2s from ', client.sessionId, JSON.stringify(message))
this.broadcast('broadcast_s2c', message, { except: client })
})
this.onMessage('turn_end_c2s', (client) => {
msgLog('turn_end_c2s from ', client.sessionId)
this.dispatcher.dispatch(new ManualTurnEndCommand(), { client })
})
this.onMessage('*', (client, type, message) => {
//
// Triggers when any other type of message is sent,
// excluding "action", which has its own specific handler defined above.
//
msgLog(client.sessionId, 'sent', type, JSON.stringify(message))
})
}
onJoin(client: Client, options: any) {
let data = {
client: client,
accountId: options.accountid,
seat: options.seat,
score: options.score
}
this.dispatcher.dispatch(new OnJoinCommand(), data)
}
// 掉线逻辑
async onLeave(client: Client, consented: boolean) {
if (this.state.gameState === GameStateConst.STATE_GAME_OVER || this.state.gameState === GameStateConst.STATE_WAIT_JOIN) {
this.state.players.delete(client.sessionId)
if (this.assistMap.has(client.sessionId)) {
this.assistMap.get(client.sessionId).leave()
this.assistMap.delete(client.sessionId)
}
this.bUserLeft(client.sessionId)
} else {
this.state.players.get(client.sessionId).online = false
let assistClient = this.getAssistClient(client.sessionId)
assistClient.active = true
try {
if (consented) {
throw new Error('consented leave')
} else {
await this.allowReconnection(client, 10 * 60)
debugRoom(`${ client.sessionId } 重连`)
assistClient.active = false
this.state.players.get(client.sessionId).online = true
}
} catch (e) {
debugRoom(`player realy level :${ client.sessionId }, try add robot`)
// this.state.players.delete(client.sessionId);
}
}
}
onDispose() {
for (let [, clock] of this.gameClock) {
clock.clear()
}
this.gameClock.clear()
this.assistMap.clear()
this.dispatcher.stop()
}
/**
* 获取指定player的client实例, 如果玩家已掉线, 则获取该玩家的assist client
* @param {string | Player} player
* @return {Client}
*/
getClient(player: string | Player): Client {
let result: Client
let sessionId
if (typeof player == 'string') {
sessionId = player
result = this.clients.find(client => client.sessionId == player)
} else {
sessionId = player.id
result = this.clients.find(client => client.sessionId == player.id)
}
if (!result) {
debugRoom(`无法获取id为: ${ typeof player == 'string' ? player : player.id } 的客户端`)
result = this.getAssistClient(sessionId)
debugRoom(`启用辅助机器人, 当前机器人状态: ${ (result as RobotClient).active }`)
}
return result
}
/**
* 加入当前房间的client数量
* @return {number}
*/
clientCount(): number {
return this.clients.length
}
/**
* 添加一个计时器
* @param millisecond
* @param handler
* @param name
*/
beginSchedule(millisecond: number, handler: Function, name: string): void {
debugRoom(`begin schedule: `, name, millisecond / 1000)
if (this.gameClock.has(name) && this.gameClock.get(name)?.active) {
error(`当前已存在进行中的gameClock: ${ name }`)
this.gameClock.get(name).clear()
this.gameClock.delete(name)
}
let self = this
let timeOverFun = function () {
handler && handler()
}
this.gameClock.set(name, this.clock.setTimeout(timeOverFun, millisecond, name))
}
/**
* 取消某个计时器
*/
stopSchedule(name: string): number {
debugRoom(`manual stop schedule: ${ name }`)
if (!this.gameClock.has(name)) {
return -1
}
let clock = this.gameClock.get(name)
if (!clock.active) {
this.gameClock.delete(name)
return -1
}
let time = clock.elapsedTime
clock.clear()
this.gameClock.delete(name)
return time
}
/**
* 暂停某个计时器, 返回这个机器器的剩余时间
* @param {string} name
* @return {number}
*/
pauseSchedule(name: string) {
if (!this.gameClock.has(name)) {
return -1
}
let clock = this.gameClock.get(name)
if (!clock.active) {
return -1
}
clock.pause()
return clock.elapsedTime
}
/**
* 恢复某个计时器, 返回这个机器器的剩余时间
* @param {string} name
* @return {number}
*/
resumeSchedule(name: string) {
if (!this.gameClock.has(name)) {
return -1
}
let clock = this.gameClock.get(name)
if (!clock.active) {
return -1
}
clock.resume()
return clock.time - clock.elapsedTime
}
/**
* 给某个计时器增加n秒
* @param name
* @param millisecond
* @param reason
*/
addScheduleTime(millisecond: number, reason?: string, name?: string): void {
let current
let currentName = name
if (!name) {
for (let [id, clock] of this.gameClock) {
if (clock.active) {
current = clock
currentName = id
break
}
}
} else {
if (this.gameClock.has(name)) {
current = this.gameClock.get(name)
}
}
debugRoom(`add schedule for ${ currentName }, time: ${ millisecond / 1000 }, reason: ${ reason }`)
if (current) {
current.time += millisecond
debugRoom(`schedule for ${ name } remain: ${ (current.time - current.elapsedTime) / 1000 }`)
}
}
async addRobot(playerId?: string) {
const host = new Service().selfHost
let data = {
host,
room: this.roomId,
sessionId: playerId
}
await createRobot(data)
}
/**
* 跟指定玩家添加一个assist client, 用于玩家掉线后接管
* @param {string} sessionId
*/
addAssistClient(sessionId: string) {
if (!this.assistMap.has(sessionId)) {
let client = new RobotClient(sessionId, this.state, this['onMessageHandlers'])
this.assistMap.set(sessionId, client)
}
}
getAssistClient(sessionId: string): RobotClient {
return this.assistMap.get(sessionId)
}
/**
* 根据指定的座次号获取玩家信息
*/
getPlayerByIdx(idx: number) {
for (let [, player] of this.state.players) {
if (player.idx == idx) {
return player
}
}
}
/**
* 获取对位玩家
* @param srcPlayer
*/
getOppositePlayer(srcPlayer: string | Player): Player {
let playerId
if (typeof srcPlayer === 'string') {
playerId = srcPlayer as string
} else {
playerId = srcPlayer.id
}
if (!this.state.players.has(playerId)) {
return null
}
let idx = this.state.players.get(playerId).idx
let opposIdx = ((this.maxClients / 2 | 0) + idx) % this.maxClients
return this.getPlayerByIdx(opposIdx)
}
getOtherTeamPlayers(srcPlayer: string, exPlayer?: string): Player[] {
let team = this.state.players.get(srcPlayer).team
let results:Player[] = []
for (let [,player] of this.state.players) {
if (player.team !== team) {
if (exPlayer && player.id != exPlayer) {
results.push(player)
}
}
}
return results
}
}