| import { ChanceCard as ChanceCardFromDB, GameInfo, GameInitInfo, GameMap, MapItem } from "@/interfaces/game"; |
| import { Player } from "./class/Player"; |
| import { Property } from "./class/Property"; |
| import { User, UserInRoomInfo, GameSetting, SocketMessage, GameLog } from "@/interfaces/bace"; |
| import { compileTsToJs, getRandomInteger, randomString } from "@/utils"; |
| import { ChanceCardType, GameLinkItem, GameOverRule, OperateType } from "@/enums/game"; |
| import { ChanceCard } from "./class/ChanceCard"; |
| import { PlayerEvents } from "./enums/game"; |
| import { SocketMsgType } from "@/enums/bace"; |
| import Dice from "./class/Dice"; |
| import { OperateListener } from "./class/OperateListener"; |
| import { WorkerCommMsg } from "@/interfaces/worker"; |
| import { WorkerCommType } from "@/enums/worker"; |
| import { RoundTimeTimer } from "./class/RoundTimeTimer"; |
|
|
| import ChanceCardNeedTypes from "./base-interface.d.ts?raw"; |
| import Utils from "./class/Utils?raw"; |
|
|
| const effectFunctionTyps = [ChanceCardNeedTypes, Utils].join("\n"); |
| const operateListener = new OperateListener(); |
| let gameProcess: GameProcess | null = null; |
|
|
| self.postMessage(<WorkerCommMsg>{ |
| type: WorkerCommType.WorkerReady, |
| }); |
|
|
| self.addEventListener("message", (ev) => { |
| const data: WorkerCommMsg = ev.data; |
| switch (data.type) { |
| case WorkerCommType.LoadGameInfo: |
| { |
| const { mapInfo, setting, userList, roomOwnerId } = data.data; |
| gameProcess = new GameProcess(mapInfo, setting, userList, roomOwnerId); |
| gameProcess.start(); |
| } |
| break; |
| case WorkerCommType.EmitOperation: |
| { |
| const { userId, operateType, data: _data } = data.data; |
| operateListener.emit(userId, operateType, _data); |
| } |
| break; |
| case WorkerCommType.UserOffLine: |
| { |
| const { userId } = data.data; |
| gameProcess && gameProcess.handlePlayerOffline(userId); |
| } |
| break; |
| case WorkerCommType.UserReconnect: |
| { |
| const { userId } = data.data; |
| gameProcess && gameProcess.handlePlayerReconnect(userId); |
| } |
| break; |
| } |
| }); |
|
|
| function sendToUsers(userIdList: string[], msg: SocketMessage) { |
| self.postMessage(<WorkerCommMsg>{ |
| type: WorkerCommType.SendToUsers, |
| data: { |
| userIdList, |
| data: msg, |
| }, |
| }); |
| } |
|
|
| (async () => {})(); |
|
|
| export class GameProcess { |
| private mapInfo: GameMap; |
| private gameSetting: GameSetting; |
| private playerList: Player[] = []; |
| private propertyList: Map<string, Property> = new Map(); |
| private chanceCardInfoList: ChanceCardFromDB[] = []; |
| private mapItemList: Map<string, MapItem> = new Map(); |
| private startTime: number = Date.now(); |
|
|
| |
| private isGameOver: boolean = false; |
| private currentPlayerInRound: Player | null = null; |
| private currentRound: number = 0; |
| private currentMultiplier: number = 1; |
| private timeoutList: any[] = []; |
| private intervalTimerList: any[] = []; |
| private roundTimeTimer: RoundTimeTimer; |
| private eventMsg: string = ""; |
|
|
| private gameLogList: GameLog[] = []; |
|
|
| |
| private animationStepDuration_ms: number = 600; |
|
|
| |
| private dice: Dice; |
|
|
| constructor(mapInfo: GameMap, gameSetting: GameSetting, users: UserInRoomInfo[], roomOwnerId: string) { |
| this.mapInfo = mapInfo; |
| this.gameSetting = gameSetting; |
| this.dice = new Dice(gameSetting.diceNum); |
| this.roundTimeTimer = new RoundTimeTimer(gameSetting.roundTime, 1000); |
| if (gameSetting.slackOffMode) { |
| operateListener.on(roomOwnerId, OperateType.PauseGame, () => { |
| console.log("PauseGame"); |
| this.roundTimeTimer.pause(); |
| this.gameBroadcast(<SocketMessage>{ |
| type: SocketMsgType.PauseGame, |
| msg: { |
| type: "info", |
| content: "房主摸鱼被发现了,游戏暂停", |
| }, |
| }); |
| }); |
| operateListener.on(roomOwnerId, OperateType.ResumeGame, () => { |
| console.log("ResumeGame"); |
| this.roundTimeTimer.resume(); |
| this.gameBroadcast(<SocketMessage>{ |
| type: SocketMsgType.ResumeGame, |
| msg: { |
| type: "info", |
| content: "房主回来了,游戏继续", |
| }, |
| }); |
| }); |
| } |
|
|
| this.loadGameMap(mapInfo); |
| this.initPlayer(users); |
| } |
|
|
| public async start() { |
| this.gameInfoBroadcast(); |
| this.gameInitBroadcast(); |
|
|
| await this.waitInitFinished(); |
| await this.gameLoop(); |
| console.log("游戏结束"); |
| } |
|
|
| private loadGameMap(mapInfo: GameMap) { |
| const { mapItems, properties, chanceCards } = mapInfo; |
|
|
| mapItems.forEach((item) => { |
| if (item.arrivedEvent) |
| item.arrivedEvent.effectCode = compileTsToJs(item.arrivedEvent.effectCode, effectFunctionTyps); |
| this.mapItemList.set(item.id, item); |
| }); |
|
|
| properties.forEach((property) => { |
| this.propertyList.set(property.id, new Property(property)); |
| }); |
|
|
| chanceCards.map((chanceCard) => { |
| chanceCard.effectCode = compileTsToJs(chanceCard.effectCode, effectFunctionTyps); |
| return chanceCard; |
| }); |
|
|
| this.chanceCardInfoList = chanceCards; |
| } |
|
|
| private initPlayer(users: UserInRoomInfo[]) { |
| this.playerList = users.map((user) => { |
| const player = new Player( |
| user, |
| this.gameSetting.initMoney, |
| getRandomInteger(0, this.mapInfo.indexList.length - 1) |
| ); |
|
|
| player.setCardsList(this.getRandomChanceCard(4)); |
|
|
| |
| player.addEventListener(PlayerEvents.AfterCost, (money, target) => { |
| const msg: SocketMessage = { |
| type: SocketMsgType.CostMoney, |
| source: "server", |
| data: { |
| player: player.getPlayerInfo(), |
| money: parseInt(money + ""), |
| target: target ? target.getPlayerInfo() : undefined, |
| }, |
| }; |
| this.gameBroadcast(msg); |
| this.gameOverCheck(); |
| return; |
| }); |
|
|
| |
| player.addEventListener(PlayerEvents.AfterGain, (money, source) => { |
| const msg: SocketMessage = { |
| type: SocketMsgType.GainMoney, |
| source: "server", |
| data: { |
| player: player.getPlayerInfo(), |
| money: parseInt(money + ""), |
| source: source ? source.getPlayerInfo() : undefined, |
| }, |
| }; |
| this.gameBroadcast(msg); |
| this.gameOverCheck(); |
| return; |
| }); |
|
|
| player.addEventListener(PlayerEvents.AfterSetMoney, () => { |
| this.gameOverCheck(); |
| }); |
|
|
| player.addEventListener(PlayerEvents.AfterCost, () => { |
| this.gameOverCheck(); |
| }); |
|
|
| player.addEventListener(PlayerEvents.Walk, async (step: number) => { |
| const walkId = randomString(16); |
| const msg: SocketMessage = { |
| type: SocketMsgType.PlayerWalk, |
| source: "server", |
| data: { playerId: player.getId(), step, walkId }, |
| }; |
| const sourceIndex = player.getPositionIndex(); |
| const total = this.mapInfo.indexList.length; |
| const newIndex = (((sourceIndex + step) % total) + total) % total; |
| player.setPositionIndex(newIndex); |
| this.gameInfoBroadcast(); |
| this.gameBroadcast(msg); |
|
|
| |
| const animationDuration = this.animationStepDuration_ms * (Math.abs(step) + 3); |
| let animationTimer = setTimeout(() => { |
| operateListener.emit(player.getId(), OperateType.Animation + walkId); |
| }, animationDuration); |
| await operateListener.onceAsync(player.getId(), OperateType.Animation + walkId, () => { |
| clearTimeout(animationTimer); |
| }); |
| player.emit(PlayerEvents.AnimationFinished); |
| return step; |
| }); |
|
|
| player.addEventListener(PlayerEvents.Tp, async (positionIndex: number) => { |
| const walkId = randomString(16); |
| const msg: SocketMessage = { |
| type: SocketMsgType.PlayerTp, |
| source: "server", |
| data: { playerId: player.getId(), positionIndex, walkId }, |
| }; |
| player.setPositionIndex(positionIndex); |
| this.gameInfoBroadcast(); |
| this.gameBroadcast(msg); |
|
|
| |
| const animationDuration = 2000; |
| let animationTimer = setTimeout(() => { |
| operateListener.emit(player.getId(), OperateType.Animation + walkId); |
| }, animationDuration); |
| await operateListener.onceAsync(player.getId(), OperateType.Animation + walkId, () => { |
| clearTimeout(animationTimer); |
| }); |
| player.emit(PlayerEvents.AnimationFinished); |
| return positionIndex; |
| }); |
|
|
| |
| |
| |
| |
| |
|
|
| player.addEventListener(PlayerEvents.AfterSetBankrupted, (isBankrupted: boolean) => { |
| console.log("🚀 ~ GameProcess ~ player.addEventListener ~ isBankrupted:", isBankrupted); |
| if (isBankrupted) { |
| |
| Array.from(this.propertyList.values()).map((property) => { |
| const owner = property.getOwner(); |
| if (owner && owner.getId() === player.getId()) { |
| property.setOwner(undefined); |
| } |
| }); |
| player.setCardsList([]); |
| this.gameOverCheck(); |
| if (this.currentPlayerInRound === player) { |
| operateListener.removeAll(player.getId()); |
| player.removeAllListeners(); |
| } |
| this.gameBroadcast(<SocketMessage>{ |
| type: SocketMsgType.MsgNotify, |
| msg: { type: "info", content: `${player.getName()} 破产了` }, |
| }); |
|
|
| this.gameLogBroadcast(`${this.createGameLinkItem(GameLinkItem.Player, player.getId())} 破产了`); |
| } |
| }); |
| return player; |
| }); |
|
|
| this.currentPlayerInRound = this.playerList[0]; |
| } |
|
|
| private getRandomChanceCard(num: number): ChanceCard[] { |
| let tempChanceCardList: ChanceCard[] = []; |
| for (let i = 0; i < num; i++) { |
| const getIndex = Math.floor(Math.random() * this.chanceCardInfoList.length); |
| const card = this.chanceCardInfoList[getIndex]; |
| if (card) tempChanceCardList.push(new ChanceCard(card)); |
| } |
| return tempChanceCardList; |
| } |
|
|
| private getNewChanceCard(id: string): ChanceCard { |
| const tempChanceCard = this.chanceCardInfoList.find((c) => c.id === id); |
| if (!tempChanceCard) throw new Error("错误的机会卡ID"); |
| return new ChanceCard(tempChanceCard); |
| } |
|
|
| private gameOverCheck() { |
| const gameOverRule = this.gameSetting.gameOverRule; |
| switch (gameOverRule) { |
| case GameOverRule.Earn100000: |
| if ( |
| this.playerList.some((player) => player.getMoney() >= this.gameSetting.overMoney) || |
| (this.playerList.length === 1 && this.playerList.every((p) => p.getIsBankrupted())) || |
| (this.playerList.length > 1 && this.playerList.filter((player) => !player.getIsBankrupted()).length <= 1) |
| ) |
| this.gameOver(); |
| break; |
| } |
| } |
|
|
| public gameInitBroadcast() { |
| const { |
| id: mapId, |
| name: mapName, |
| background: mapBackground, |
| indexList: mapIndexList, |
| itemTypes: itemTypesList, |
| streets: streetsList, |
| houseModel_lv0: lv0, |
| houseModel_lv1: lv1, |
| houseModel_lv2: lv2, |
| } = this.mapInfo; |
| const gameInitInfo: GameInitInfo = { |
| mapId: mapId, |
| mapName: mapName, |
| mapBackground: mapBackground, |
| mapItemsList: Array.from(this.mapItemList.values()), |
| mapIndexList: mapIndexList, |
| itemTypesList: itemTypesList, |
| streetsList: streetsList, |
| playerList: this.playerList.map((player) => player.getPlayerInfo()), |
| properties: Array.from(this.propertyList.values()).map((property) => property.getPropertyInfo()), |
| chanceCards: this.chanceCardInfoList, |
| currentPlayerInRound: this.currentPlayerInRound ? this.currentPlayerInRound.getId() : "", |
| currentRound: this.currentRound, |
| currentMultiplier: this.currentMultiplier, |
| houseModels: { lv0, lv1, lv2 }, |
| }; |
|
|
| this.gameBroadcast({ |
| type: SocketMsgType.GameInit, |
| source: "server", |
| data: gameInitInfo, |
| }); |
| } |
|
|
| public gameInfoBroadcast() { |
| const gameInfo: GameInfo = { |
| currentPlayerInRound: this.currentPlayerInRound ? this.currentPlayerInRound.getId() : "", |
| currentRound: this.currentRound, |
| currentMultiplier: this.currentMultiplier, |
| playerList: this.playerList.map((player) => player.getPlayerInfo()), |
| properties: Array.from(this.propertyList.values()).map((property) => property.getPropertyInfo()), |
| }; |
| this.gameBroadcast({ |
| type: SocketMsgType.GameInfo, |
| source: "server", |
| data: gameInfo, |
| }); |
| } |
|
|
| |
| private async waitInitFinished() { |
| const promiseArr: Promise<any>[] = []; |
| this.playerList.forEach((player) => { |
| promiseArr.push(operateListener.onceAsync(player.getId(), OperateType.GameInitFinished, () => {})); |
| }); |
| await Promise.all(promiseArr); |
|
|
| this.gameBroadcast({ type: SocketMsgType.GameInitFinished, data: "", source: "server" }); |
| } |
|
|
| private async gameLoop() { |
| this.roundTimeTimer.setIntervalFunction(this.roundRemainingTimeBroadcast); |
| while (!this.isGameOver) { |
| let currentPlayerIndex = 0; |
| while (currentPlayerIndex < this.playerList.length) { |
| this.gameInfoBroadcast(); |
| const currentPlayer = this.playerList[currentPlayerIndex]; |
| if (currentPlayer.getIsBankrupted()) { |
| currentPlayerIndex++; |
| continue; |
| } |
|
|
| if (currentPlayer.getStop() > 0) { |
| this.gameMsgNotifyBroadcast("info", `${currentPlayer.getName()}睡着了,跳过回合`); |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem(GameLinkItem.Player, currentPlayer.getId())} 睡着了,跳过回合` |
| ); |
| await currentPlayer.setStop(currentPlayer.getStop() - 1); |
| currentPlayerIndex++; |
| continue; |
| } |
| this.currentPlayerInRound = this.playerList[currentPlayerIndex]; |
| this.roundTurnNotify(this.currentPlayerInRound); |
| this.gameInfoBroadcast(); |
|
|
| await this.gameRound(this.currentPlayerInRound); |
| currentPlayerIndex++; |
| } |
| this.nextRound(); |
| } |
| this.roundTimeTimer.clearInterval(); |
| } |
|
|
| private async gameRound(currentPlayer: Player) { |
| await currentPlayer.emit(PlayerEvents.BeforeRound, currentPlayer); |
| this.gameInfoBroadcast(); |
| this.roundTimeTimer.setTimeOutFunction(null); |
| this.useChanceCardListener(currentPlayer); |
| await this.waitRollDice(currentPlayer); |
| await this.handleArriveEvent(currentPlayer); |
| await currentPlayer.emit(PlayerEvents.AfterRound, currentPlayer); |
| } |
|
|
| private async useChanceCardListener(sourcePlayer: Player) { |
| const userId = sourcePlayer.getId(); |
| const roundTime = this.gameSetting.roundTime; |
|
|
| await new Promise(async (resolve, reject) => { |
| let isRoundEnd = false; |
|
|
| const handleRollDice = () => { |
| isRoundEnd = true; |
| operateListener.removeAll(userId, OperateType.UseChanceCard); |
| this.roundTimeTimer.stop(); |
| resolve("RollDice"); |
| }; |
|
|
| const handleUseChanceCardTimeOut = () => { |
| isRoundEnd = true; |
| operateListener.remove(userId, OperateType.RollDice, handleRollDice); |
| operateListener.removeAll(userId, OperateType.UseChanceCard); |
| operateListener.emit(userId, OperateType.RollDice); |
| }; |
|
|
| |
|
|
| |
| operateListener.once(userId, OperateType.RollDice, handleRollDice); |
|
|
| while (!isRoundEnd) { |
| |
| this.eventMsg = `等待 ${sourcePlayer.getName()} 执行回合`; |
| this.roundTimeTimer.setTimeOutFunction(handleUseChanceCardTimeOut); |
| await operateListener.onceAsync( |
| userId, |
| OperateType.UseChanceCard, |
| async (chanceCardId: string, targetIdList: string[] = []) => { |
| this.roundTimeTimer.stop(); |
| const chanceCard = sourcePlayer.getCardById(chanceCardId); |
| if (chanceCard) { |
| let error = ""; |
| try { |
| switch ( |
| chanceCard.getType() |
| ) { |
| case ChanceCardType.ToSelf: |
| await chanceCard.use(sourcePlayer, sourcePlayer, this); |
| this.gameMsgNotifyBroadcast( |
| "info", |
| `${sourcePlayer.getName()} 对自己使用了机会卡: "${chanceCard.getName()}"` |
| ); |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem( |
| GameLinkItem.Player, |
| sourcePlayer.getId() |
| )} 对自己使用了机会卡: ${this.createGameLinkItem( |
| GameLinkItem.ChanceCard, |
| chanceCard.getSourceId() |
| )}` |
| ); |
| break; |
| case ChanceCardType.ToOtherPlayer: |
| case ChanceCardType.ToPlayer: |
| const _targetPlayer = this.playerList.find((player) => player.getId() === targetIdList[0]); |
| if (!_targetPlayer) { |
| error = "目标玩家不存在"; |
| break; |
| } |
| await chanceCard.use(sourcePlayer, _targetPlayer, this); |
| this.gameMsgNotifyBroadcast( |
| "info", |
| `${sourcePlayer.getName()} 对玩家 ${_targetPlayer.getName()} 使用了机会卡: "${chanceCard.getName()}"` |
| ); |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem( |
| GameLinkItem.Player, |
| sourcePlayer.getId() |
| )} 对玩家 ${this.createGameLinkItem( |
| GameLinkItem.Player, |
| _targetPlayer.getId() |
| )} 使用了机会卡: ${this.createGameLinkItem(GameLinkItem.ChanceCard, chanceCard.getSourceId())}` |
| ); |
| break; |
| case ChanceCardType.ToProperty: |
| const _targetProperty = this.propertyList.get(targetIdList[0]); |
| if (!_targetProperty) { |
| error = "目标建筑/地皮不存在"; |
| break; |
| } |
| await chanceCard.use(sourcePlayer, _targetProperty, this); |
| this.gameMsgNotifyBroadcast( |
| "info", |
| `${sourcePlayer.getName()} 对地皮 ${_targetProperty.getName()} 使用了机会卡: "${chanceCard.getName()}"` |
| ); |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem( |
| GameLinkItem.Player, |
| sourcePlayer.getId() |
| )} 对地皮 ${this.createGameLinkItem( |
| GameLinkItem.Property, |
| _targetProperty.getId() |
| )} 使用了机会卡: ${this.createGameLinkItem(GameLinkItem.ChanceCard, chanceCard.getSourceId())}` |
| ); |
| break; |
| case ChanceCardType.ToMapItem: |
| const _targetIdList = targetIdList as string[]; |
| const _targetPlayerList: Player[] = []; |
| _targetIdList.forEach((id) => { |
| |
| const _tempPlayer = this.playerList.find((player) => player.getId() === id); |
| if (_tempPlayer) { |
| _targetPlayerList.push(_tempPlayer); |
| } |
| }); |
| if (_targetPlayerList.length === 0) { |
| error = "选中的玩家不存在"; |
| break; |
| } |
| await chanceCard.use(sourcePlayer, _targetPlayerList, this); |
| break; |
| } |
| } catch (e: any) { |
| error = e.message; |
| } |
| if (error) { |
| const errorMsg: SocketMessage = { |
| type: SocketMsgType.MsgNotify, |
| data: "", |
| source: "server", |
| msg: { |
| type: "error", |
| content: error, |
| }, |
| }; |
| sendToUsers([sourcePlayer.getId()], errorMsg); |
| const callBackMsg: SocketMessage = { |
| type: SocketMsgType.UseChanceCard, |
| data: "error", |
| source: "server", |
| }; |
| sendToUsers([sourcePlayer.getId()], callBackMsg); |
| } else { |
| await sourcePlayer.loseCard(chanceCardId); |
| const successMsg: SocketMessage = { |
| type: SocketMsgType.MsgNotify, |
| data: "", |
| source: "server", |
| msg: { |
| type: "success", |
| content: `机会卡 ${chanceCard.getName()} 使用成功!`, |
| }, |
| }; |
| this.gameInfoBroadcast(); |
| isRoundEnd = true; |
|
|
| this.eventMsg = `等待 ${sourcePlayer.getName()} 掷骰子`; |
| this.roundTimeTimer.setTimeOutFunction(handleUseChanceCardTimeOut); |
| sendToUsers([sourcePlayer.getId()], successMsg); |
| const callBackMsg: SocketMessage = { |
| type: SocketMsgType.UseChanceCard, |
| data: "", |
| source: "server", |
| }; |
| sendToUsers([sourcePlayer.getId()], callBackMsg); |
| } |
|
|
| this.gameInfoBroadcast(); |
| } else { |
| const errorMsg: SocketMessage = { |
| type: SocketMsgType.MsgNotify, |
| data: "", |
| source: "server", |
| msg: { |
| type: "error", |
| content: "机会卡使用失败: 未知的机会卡ID", |
| }, |
| }; |
| sendToUsers([sourcePlayer.getId()], errorMsg); |
| } |
| } |
| ); |
| } |
| }); |
| } |
|
|
| private async waitRollDice(player: Player) { |
| const userId = player.getId(); |
| |
| await new Promise((resolve, reject) => { |
| |
| operateListener.onceAsync(userId, OperateType.RollDice, resolve); |
|
|
| |
| player.addEventListener(PlayerEvents.AfterSetBankrupted, (isBankrupted) => { |
| if (isBankrupted) { |
| reject("bankrupted"); |
| } |
| }); |
| }) |
| .then(async () => { |
| this.gameBroadcast({ |
| type: SocketMsgType.RollDiceStart, |
| source: "server", |
| data: "", |
| }); |
| |
| this.dice.roll(); |
| |
| await this.sleep(1500); |
| |
| const msgToRollDice: SocketMessage = { |
| type: SocketMsgType.RollDiceResult, |
| source: "server", |
| data: { |
| rollDiceResult: this.dice.getResultArray(), |
| rollDiceCount: this.dice.getResultNumber(), |
| rollDicePlayerId: player.getId(), |
| }, |
| msg: { |
| type: "info", |
| content: `${player.getName()} 摇到的点数是: ${this.dice.getResultArray().join("-")}`, |
| }, |
| }; |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem(GameLinkItem.Player, player.getId())} 摇到的点数是: ${this.dice |
| .getResultArray() |
| .join("-")}` |
| ); |
| |
| this.gameBroadcast(msgToRollDice); |
| |
| await player.walk(this.dice.getResultNumber()); |
| }) |
| .catch(() => {}) |
| .finally(() => { |
| |
| this.gameInfoBroadcast(); |
| }); |
| } |
|
|
| private async handleArriveEvent(arrivedPlayer: Player) { |
| if (arrivedPlayer.getIsBankrupted()) return; |
| const playerPositionIndex = arrivedPlayer.getPositionIndex(); |
| const arriveItemId = this.mapInfo.indexList[playerPositionIndex]; |
| const arriveItem = this.mapItemList.get(arriveItemId); |
|
|
| if (!arriveItem) return; |
| if (arriveItem.linkto) { |
| const linkMapItem = arriveItem.linkto; |
| if (!linkMapItem.property) return; |
| const property = this.propertyList.get(linkMapItem.property.id); |
| if (!property) return; |
| const arrivePropertyMsg: SocketMessage = { |
| type: SocketMsgType.BuyProperty, |
| source: "server", |
| data: property.getPropertyInfo(), |
| msg: { |
| type: "", |
| content: "", |
| }, |
| }; |
|
|
| |
| const owner = property.getOwner(); |
| if (owner) { |
| |
| if (owner.getId() === arrivedPlayer.getId()) { |
| |
| if (property.getBuildingLevel() < 2) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| this.eventMsg = `等待 ${arrivedPlayer.getName()} 升级房子`; |
| this.roundTimeTimer.setTimeOutFunction(() => { |
| operateListener.emit(arrivedPlayer.getId(), OperateType.BuildHouse, false); |
| }); |
|
|
| |
| arrivePropertyMsg.type = SocketMsgType.BuildHouse; |
| arrivePropertyMsg.msg = { |
| type: "success", |
| content: `你到达了你的${property.getName()},可以升级房子`, |
| }; |
| sendToUsers([arrivedPlayer.getId()], arrivePropertyMsg); |
| const playerRes = await operateListener.onceAsync( |
| arrivedPlayer.getId(), |
| OperateType.BuildHouse, |
| (choice) => choice |
| ); |
| this.roundRemainingTimeBroadcast(0); |
| if (playerRes) { |
| await this.handlePlayerBuildUp(arrivedPlayer, property); |
| } |
| } |
| } else { |
| |
| const ownerPlayer = this.getPlayerById(owner.getId()); |
| if (!ownerPlayer) return; |
| const passCost = property.getPassCost() * this.currentMultiplier; |
| this.handlePayToSomeOne(arrivedPlayer, ownerPlayer, passCost); |
| arrivePropertyMsg.type = SocketMsgType.MsgNotify; |
| arrivePropertyMsg.msg = { |
| type: "error", |
| content: `你到达了${owner.getName()}的地皮: ${property.getName()},支付了${passCost}¥过路费`, |
| }; |
| sendToUsers([arrivedPlayer.getId()], arrivePropertyMsg); |
| arrivePropertyMsg.msg = { |
| type: "success", |
| content: `${arrivedPlayer.getName()}到达了你的地皮: ${property.getName()},支付了${passCost}¥过路费`, |
| }; |
| sendToUsers([ownerPlayer.getId()], arrivePropertyMsg); |
| arrivePropertyMsg.msg = { |
| type: "info", |
| content: `${arrivedPlayer.getName()}到达了${owner.getName()}的地皮: ${property.getName()},支付了${passCost}¥过路费`, |
| }; |
| sendToUsers( |
| this.playerList |
| .filter((p) => p.getId() !== arrivedPlayer.getId() && p.getId() !== owner.getId()) |
| .map((p) => p.getId()), |
| arrivePropertyMsg |
| ); |
| this.gameInfoBroadcast(); |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem(GameLinkItem.Player, arrivedPlayer.getId())} 到达了 ${this.createGameLinkItem( |
| GameLinkItem.Player, |
| owner.getId() |
| )} 的地皮: ${this.createGameLinkItem(GameLinkItem.Property, property.getId())},支付了 ${passCost}¥ 过路费` |
| ); |
| } |
| } else { |
| this.eventMsg = `等待 ${arrivedPlayer.getName()} 购买地皮`; |
| this.roundTimeTimer.setTimeOutFunction(() => { |
| operateListener.emit(arrivedPlayer.getId(), OperateType.BuyProperty, false); |
| }); |
|
|
| |
| arrivePropertyMsg.type = SocketMsgType.BuyProperty; |
| arrivePropertyMsg.msg = { |
| type: "success", |
| content: `你到达了${property.getName()},可以买下这块地皮`, |
| }; |
| |
| sendToUsers([arrivedPlayer.getId()], arrivePropertyMsg); |
| |
| const playerRes = await operateListener.onceAsync( |
| arrivedPlayer.getId(), |
| OperateType.BuyProperty, |
| (choice) => choice |
| ); |
| this.roundRemainingTimeBroadcast(0); |
| if (playerRes) { |
| await this.handlePlayerBuyProperty(arrivedPlayer, property); |
| } |
| } |
| } else if (arriveItem.arrivedEvent) { |
| const effectCode = arriveItem.arrivedEvent.effectCode; |
| if (effectCode) { |
| const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; |
| const arrivedFunction = new AsyncFunction("arrivedPlayer", "gameProcess", effectCode); |
| await arrivedFunction(arrivedPlayer, this); |
| this.gameMsgNotifyBroadcast( |
| "info", |
| `${arrivedPlayer.getName()} 踩到了特殊地块: ${arriveItem.arrivedEvent.name}` |
| ); |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem( |
| GameLinkItem.Player, |
| arrivedPlayer.getId() |
| )} 踩到了特殊地块: ${this.createGameLinkItem(GameLinkItem.ArrivedEvent, arriveItem.arrivedEvent.id)}` |
| ); |
| } |
| } |
| this.gameInfoBroadcast(); |
| } |
|
|
| private getPlayerById(id: string) { |
| return this.playerList.find((player) => player.getId() === id); |
| } |
|
|
| private async handlePayToSomeOne(source: Player, target: Player, money: number) { |
| await target.gain(money, source); |
| return await source.cost(money, target); |
| } |
|
|
| private nextRound() { |
| this.currentRound++; |
| this.gameOverCheck(); |
| if (this.currentRound % this.gameSetting.multiplierIncreaseRounds === 0) { |
| this.currentMultiplier += this.gameSetting.multiplier; |
| this.playerList.forEach((p) => { |
| p.gainCard(this.getRandomChanceCard(1)[0]); |
| }); |
| this.gameMsgNotifyBroadcast("info", `过路费倍率上涨为 ${this.currentMultiplier} 倍, 每人获得一张随机的机会卡`); |
| this.gameLogBroadcast(`---过路费倍率上涨为 ${this.currentMultiplier} 倍, 每人获得一张随机的机会卡---`); |
| } |
| } |
|
|
| private async handlePlayerBuyProperty(player: Player, property: Property) { |
| const msgToSend: SocketMessage = { |
| type: SocketMsgType.MsgNotify, |
| source: "server", |
| data: "", |
| msg: { |
| type: "", |
| content: "", |
| }, |
| }; |
| if (player.getMoney() > property.getSellCost()) { |
| await property.setOwner(player); |
| this.gameInfoBroadcast(); |
| this.gameMsgNotifyBroadcast("info", `${player.getName()} 买下了地皮 ${property.getName()}`); |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem(GameLinkItem.Player, player.getId())} 买下了地皮 ${this.createGameLinkItem( |
| GameLinkItem.Property, |
| property.getId() |
| )}` |
| ); |
| await player.cost(property.getSellCost()); |
| } else { |
| msgToSend.msg = { type: "error", content: "不够钱啊穷鬼" }; |
| sendToUsers([player.getId()], msgToSend); |
| } |
|
|
| return; |
| } |
|
|
| private async handlePlayerBuildUp(player: Player, property: Property) { |
| const msgToSend: SocketMessage = { |
| type: SocketMsgType.MsgNotify, |
| source: "server", |
| data: "", |
| msg: { |
| type: "", |
| content: "", |
| }, |
| }; |
| if (player.getMoney() > property.getSellCost()) { |
| property.buildUp(); |
| this.gameInfoBroadcast(); |
| this.gameMsgNotifyBroadcast( |
| "info", |
| `${player.getName()}把地皮${property.getName()}升到了${property.getBuildingLevel()}级` |
| ); |
| this.gameLogBroadcast( |
| `${this.createGameLinkItem(GameLinkItem.Player, player.getId())} 把地皮 ${this.createGameLinkItem( |
| GameLinkItem.Property, |
| property.getId() |
| )} 升到了 ${property.getBuildingLevel()} 级` |
| ); |
| await player.cost(property.getSellCost()); |
| } else { |
| msgToSend.msg = { type: "error", content: "不够钱啊穷鬼" }; |
| sendToUsers([player.getId()], msgToSend); |
| } |
| return; |
| } |
|
|
| private roundTurnNotify(player: Player) { |
| const msgToSend: SocketMessage = { |
| type: SocketMsgType.RoundTurn, |
| source: "server", |
| data: "", |
| msg: { |
| type: "info", |
| content: "现在是你的回合啦!", |
| }, |
| }; |
| sendToUsers([player.getId()], msgToSend); |
| this.gameLogBroadcast(`---接下来是 ${this.createGameLinkItem(GameLinkItem.Player, player.getId())} 的回合---`); |
| } |
|
|
| public roundRemainingTimeBroadcast = (remainingTime: number) => { |
| const eventMsg = this.eventMsg; |
| const msg: SocketMessage = { |
| type: SocketMsgType.RemainingTime, |
| source: "server", |
| data: { eventMsg, remainingTime }, |
| }; |
| this.gameBroadcast(msg); |
| }; |
|
|
| public handlePlayerOffline(userId: string) { |
| const player = this.getPlayerById(userId); |
| if (player) { |
| player.setIsOffline(true); |
| this.gameInfoBroadcast(); |
| } |
| } |
|
|
| public handlePlayerReconnect(userId: string) { |
| const player = this.playerList.find((player) => { |
| return player.getUser().userId === userId; |
| }); |
| if (player) { |
| player.setIsOffline(false); |
| sendToUsers([userId], <SocketMessage>{ |
| type: SocketMsgType.GameStart, |
| source: "server", |
| data: "", |
| }); |
| const { |
| id: mapId, |
| name: mapName, |
| background: mapBackground, |
| indexList: mapIndexList, |
| itemTypes: itemTypesList, |
| streets: streetsList, |
| houseModel_lv0: lv0, |
| houseModel_lv1: lv1, |
| houseModel_lv2: lv2, |
| } = this.mapInfo; |
| const gameInitInfo: GameInitInfo = { |
| mapId: mapId, |
| mapName: mapName, |
| mapBackground: mapBackground, |
| mapItemsList: Array.from(this.mapItemList.values()), |
| mapIndexList: mapIndexList, |
| itemTypesList: itemTypesList, |
| streetsList: streetsList, |
| playerList: this.playerList.map((player) => player.getPlayerInfo()), |
| properties: Array.from(this.propertyList.values()).map((property) => property.getPropertyInfo()), |
| chanceCards: this.chanceCardInfoList, |
| currentPlayerInRound: this.currentPlayerInRound ? this.currentPlayerInRound.getId() : "", |
| currentRound: this.currentRound, |
| currentMultiplier: this.currentMultiplier, |
| houseModels: { lv0, lv1, lv2 }, |
| }; |
| sendToUsers([userId], <SocketMessage>{ |
| type: SocketMsgType.GameInit, |
| source: "server", |
| data: gameInitInfo, |
| }); |
| operateListener.once(userId, OperateType.GameInitFinished, () => { |
| sendToUsers([userId], <SocketMessage>{ |
| type: SocketMsgType.GameInitFinished, |
| data: "", |
| source: "server", |
| }); |
| }); |
| this.gameInfoBroadcast(); |
| } else { |
| console.log("奇怪的玩家 in game"); |
| } |
| } |
|
|
| private sleep(ms: number) { |
| return new Promise((resolve) => setTimeout(resolve, ms)); |
| } |
|
|
| private gameOver() { |
| this.gameInfoBroadcast(); |
| this.gameBroadcast({ |
| type: SocketMsgType.GameOver, |
| source: "server", |
| data: "游戏结束", |
| msg: { content: "游戏结束", type: "info" }, |
| }); |
| self.postMessage(<WorkerCommMsg>{ |
| type: WorkerCommType.GameOver, |
| }); |
| this.isGameOver = true; |
| this.destroy(); |
| } |
|
|
| public gameMsgNotifyBroadcast(type: "success" | "warning" | "error" | "info", msg: string) { |
| this.gameBroadcast({ |
| type: SocketMsgType.MsgNotify, |
| data: "", |
| msg: { type, content: msg }, |
| source: "server", |
| }); |
| } |
|
|
| public gameLogBroadcast(log: string) { |
| const gameLog: GameLog = { id: randomString(8), time: Date.now() - this.startTime, content: log }; |
| this.gameLogList.push(gameLog); |
| this.gameBroadcast({ |
| type: SocketMsgType.GameLog, |
| data: gameLog, |
| source: "server", |
| }); |
| } |
|
|
| public getGameLog() { |
| return this.gameLogList; |
| } |
|
|
| public gameBroadcast(msg: SocketMessage) { |
| sendToUsers( |
| this.playerList.map((u) => u.getId()), |
| msg |
| ); |
| } |
|
|
| public createGameLinkItem(type: GameLinkItem, id: string) { |
| return `@-#${type}#-#${id}#`; |
| } |
|
|
| public destroy() { |
| this.isGameOver = true; |
| this.playerList.forEach((p) => { |
| operateListener.removeAll(p.getId()); |
| }); |
| this.intervalTimerList.forEach((id) => { |
| clearInterval(id); |
| }); |
| this.timeoutList.forEach((id) => { |
| clearTimeout(id); |
| }); |
| } |
| } |
|
|