diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index a4bc922dc..a263bd54b 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -207,8 +207,11 @@ set(cockatrice_SOURCES src/game/filters/filter_tree.cpp src/game/filters/filter_tree_model.cpp src/game/filters/syntax_help.cpp + src/game/game_event_handler.cpp + src/game/game_meta_info.cpp src/game/game_scene.cpp src/game/game_selector.cpp + src/game/game_state.cpp src/game/game_view.cpp src/game/games_model.cpp src/game/hand_counter.cpp diff --git a/cockatrice/src/client/replay_manager.cpp b/cockatrice/src/client/replay_manager.cpp index 251e4867e..f410f75fa 100644 --- a/cockatrice/src/client/replay_manager.cpp +++ b/cockatrice/src/client/replay_manager.cpp @@ -95,7 +95,8 @@ ReplayManager::ReplayManager(TabGame *parent, GameReplay *_replay) void ReplayManager::replayNextEvent(Player::EventProcessingOptions options) { - game->processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), nullptr, options); + game->getGameEventHandler()->processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), + nullptr, options); } void ReplayManager::replayFinished() diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index 770ba2383..9b8aa06c3 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -16,7 +16,6 @@ #include "../../main.h" #include "../../server/abstract_client.h" #include "../../server/message_log_widget.h" -#include "../../server/pending_command.h" #include "../../server/user/user_list_manager.h" #include "../../settings/cache_settings.h" #include "../network/replay_timeline_widget.h" @@ -24,31 +23,10 @@ #include "../ui/phases_toolbar.h" #include "../ui/picture_loader/picture_loader.h" #include "../ui/window_main.h" -#include "get_pb_extension.h" -#include "pb/command_concede.pb.h" -#include "pb/command_delete_arrow.pb.h" -#include "pb/command_game_say.pb.h" -#include "pb/command_leave_game.pb.h" -#include "pb/command_next_turn.pb.h" -#include "pb/command_reverse_turn.pb.h" -#include "pb/command_set_active_phase.pb.h" -#include "pb/context_connection_state_changed.pb.h" -#include "pb/context_deck_select.pb.h" -#include "pb/context_ping_changed.pb.h" -#include "pb/event_game_closed.pb.h" -#include "pb/event_game_host_changed.pb.h" #include "pb/event_game_joined.pb.h" -#include "pb/event_game_say.pb.h" -#include "pb/event_game_state_changed.pb.h" -#include "pb/event_join.pb.h" -#include "pb/event_kicked.pb.h" -#include "pb/event_leave.pb.h" -#include "pb/event_player_properties_changed.pb.h" -#include "pb/event_reverse_turn.pb.h" -#include "pb/event_set_active_phase.pb.h" -#include "pb/event_set_active_player.pb.h" -#include "pb/game_event_container.pb.h" #include "pb/game_replay.pb.h" +#include "pb/serverinfo_player.pb.h" +#include "pb/serverinfo_user.pb.h" #include "tab_supervisor.h" #include "trice_limits.h" @@ -68,15 +46,23 @@ #include TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) - : Tab(_tabSupervisor), secondsElapsed(0), hostId(-1), localPlayerId(-1), - isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(true), judge(false), gameStateKnown(false), - resuming(false), currentPhase(-1), activeCard(nullptr), gameClosed(false), sayLabel(nullptr), sayEdit(nullptr) + : Tab(_tabSupervisor), activeCard(nullptr), sayLabel(nullptr), sayEdit(nullptr) { // THIS CTOR IS USED ON REPLAY + gameMetaInfo = new GameMetaInfo(); + gameState = new GameState(0, -1, -1, _tabSupervisor->getIsLocalGame(), QList(), true, false, + false, false, -1, false); + connectToGameState(); + + gameEventHandler = new GameEventHandler(this); + connectToGameEventHandler(); + createCardInfoDock(true); createPlayerListDock(true); + connectPlayerListToGameEventHandler(); createMessageDock(true); + connectMessageLogToGameEventHandler(); createPlayAreaWidget(true); createDeckViewContainerWidget(true); createReplayDock(_replay); @@ -97,7 +83,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, &TabGame::refreshShortcuts); refreshShortcuts(); - messageLog->logReplayStarted(gameInfo.game_id()); + messageLog->logReplayStarted(gameMetaInfo->gameId()); QTimer::singleShot(0, this, &TabGame::loadLayout); } @@ -106,18 +92,29 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_clients, const Event_GameJoined &event, const QMap &_roomGameTypes) - : Tab(_tabSupervisor), userListProxy(_tabSupervisor->getUserListManager()), clients(_clients), - gameInfo(event.game_info()), roomGameTypes(_roomGameTypes), hostId(event.host_id()), - localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(event.spectator()), - judge(event.judge()), gameStateKnown(false), resuming(event.resuming()), currentPhase(-1), activeCard(nullptr), - gameClosed(false) + : Tab(_tabSupervisor), userListProxy(_tabSupervisor->getUserListManager()), activeCard(nullptr) { + + gameMetaInfo = new GameMetaInfo(); + gameMetaInfo->setFromProto(event.game_info()); + gameMetaInfo->setRoomGameTypes(_roomGameTypes); + gameState = new GameState(0, event.host_id(), event.player_id(), _tabSupervisor->getIsLocalGame(), _clients, + event.spectator(), event.judge(), false, event.resuming(), -1, false); + connectToGameState(); + // THIS CTOR IS USED ON GAMES - gameInfo.set_started(false); + gameMetaInfo->setStarted(false); + + connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged); + + gameEventHandler = new GameEventHandler(this); + connectToGameEventHandler(); createCardInfoDock(); createPlayerListDock(); + connectPlayerListToGameEventHandler(); createMessageDock(); + connectMessageLogToGameEventHandler(); createPlayAreaWidget(); createDeckViewContainerWidget(); createReplayDock(nullptr); @@ -141,16 +138,84 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, refreshShortcuts(); // append game to rooms game list for others to see - for (int i = gameInfo.game_types_size() - 1; i >= 0; i--) - gameTypes.append(roomGameTypes.find(gameInfo.game_types(i)).value()); + for (int i = gameMetaInfo->gameTypesSize() - 1; i >= 0; i--) + gameTypes.append(gameMetaInfo->findRoomGameType(i)); QTimer::singleShot(0, this, &TabGame::loadLayout); } +void TabGame::connectToGameState() +{ + connect(gameState, &GameState::playerAdded, this, &TabGame::addPlayer); + connect(gameState, &GameState::gameStarted, this, &TabGame::startGame); + connect(gameState, &GameState::activePhaseChanged, this, &TabGame::setActivePhase); + connect(gameState, &GameState::activePlayerChanged, this, &TabGame::setActivePlayer); +} + +void TabGame::connectToGameEventHandler() +{ + connect(this, &TabGame::gameLeft, gameEventHandler, &GameEventHandler::handleGameLeft); + connect(gameEventHandler, &GameEventHandler::gameStopped, this, &TabGame::stopGame); + connect(gameEventHandler, &GameEventHandler::gameClosed, this, &TabGame::closeGame); + connect(gameEventHandler, &GameEventHandler::localPlayerReadyStateChanged, this, + &TabGame::processLocalPlayerReadyStateChanged); + connect(gameEventHandler, &GameEventHandler::localPlayerSideboardLocked, this, + &TabGame::processLocalPlayerSideboardLocked); + connect(gameEventHandler, &GameEventHandler::localPlayerDeckSelected, this, &TabGame::processLocalPlayerDeckSelect); +} + +void TabGame::connectMessageLogToGameEventHandler() +{ + // connect(gameEventHandler, &GameEventHandler:: , messageLog, &MessageLogWidget::); + connect(gameEventHandler, &GameEventHandler::gameFlooded, messageLog, &MessageLogWidget::logGameFlooded); + connect(gameEventHandler, &GameEventHandler::containerProcessingStarted, messageLog, + &MessageLogWidget::containerProcessingStarted); + connect(gameEventHandler, &GameEventHandler::containerProcessingDone, messageLog, + &MessageLogWidget::containerProcessingDone); + connect(gameEventHandler, &GameEventHandler::setContextJudgeName, messageLog, + &MessageLogWidget::setContextJudgeName); + connect(gameEventHandler, &GameEventHandler::logSpectatorSay, messageLog, &MessageLogWidget::logSpectatorSay); + + connect(gameEventHandler, &GameEventHandler::logJoinPlayer, messageLog, &MessageLogWidget::logJoin); + connect(gameEventHandler, &GameEventHandler::logJoinSpectator, messageLog, &MessageLogWidget::logJoinSpectator); + connect(gameEventHandler, &GameEventHandler::logLeave, messageLog, &MessageLogWidget::logLeave); + connect(gameEventHandler, &GameEventHandler::logKicked, messageLog, &MessageLogWidget::logKicked); + connect(gameEventHandler, &GameEventHandler::logConnectionStateChanged, messageLog, + &MessageLogWidget::logConnectionStateChanged); + + connect(gameEventHandler, &GameEventHandler::logDeckSelect, messageLog, &MessageLogWidget::logDeckSelect); + connect(gameEventHandler, &GameEventHandler::logSideboardLockSet, messageLog, + &MessageLogWidget::logSetSideboardLock); + connect(gameEventHandler, &GameEventHandler::logReadyStart, messageLog, &MessageLogWidget::logReadyStart); + connect(gameEventHandler, &GameEventHandler::logNotReadyStart, messageLog, &MessageLogWidget::logNotReadyStart); + connect(gameEventHandler, &GameEventHandler::logGameStart, messageLog, &MessageLogWidget::logGameStart); + + connect(gameEventHandler, &GameEventHandler::playerConceded, messageLog, &MessageLogWidget::logConcede); + connect(gameEventHandler, &GameEventHandler::playerUnconceded, messageLog, &MessageLogWidget::logUnconcede); + + connect(gameEventHandler, &GameEventHandler::logActivePlayer, messageLog, &MessageLogWidget::logSetActivePlayer); + connect(gameEventHandler, &GameEventHandler::logActivePhaseChanged, messageLog, + &MessageLogWidget::logSetActivePhase); + + connect(gameEventHandler, &GameEventHandler::logTurnReversed, messageLog, &MessageLogWidget::logReverseTurn); + + connect(gameEventHandler, &GameEventHandler::logGameClosed, messageLog, &MessageLogWidget::logGameClosed); +} + +void TabGame::connectPlayerListToGameEventHandler() +{ + connect(gameEventHandler, &GameEventHandler::playerJoined, playerListWidget, &PlayerListWidget::addPlayer); + connect(gameEventHandler, &GameEventHandler::playerLeft, playerListWidget, &PlayerListWidget::removePlayer); + connect(gameEventHandler, &GameEventHandler::spectatorJoined, playerListWidget, &PlayerListWidget::addPlayer); + connect(gameEventHandler, &GameEventHandler::spectatorLeft, playerListWidget, &PlayerListWidget::removePlayer); + connect(gameEventHandler, &GameEventHandler::playerPropertiesChanged, playerListWidget, + &PlayerListWidget::updatePlayerProperties); +} + void TabGame::loadReplay(GameReplay *replay) { - gameInfo.CopyFrom(replay->game_info()); - gameInfo.set_spectators_omniscient(true); + gameMetaInfo->setFromProto(replay->game_info()); + gameMetaInfo->setSpectatorsOmniscient(true); } void TabGame::addMentionTag(const QString &value) @@ -171,12 +236,12 @@ void TabGame::resetChatAndPhase() messageLog->clearChat(); // reset phase markers - setActivePhase(-1); + gameState->setCurrentPhase(-1); } void TabGame::emitUserEvent() { - bool globalEvent = !spectator || SettingsCache::instance().getSpectatorNotificationsEnabled(); + bool globalEvent = !gameState->isSpectator() || SettingsCache::instance().getSpectatorNotificationsEnabled(); emit userEvent(globalEvent); updatePlayerListDockTitle(); } @@ -189,22 +254,16 @@ TabGame::~TabGame() void TabGame::updatePlayerListDockTitle() { QString tabText = - " | " + (replayManager->replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameInfo.game_id()); - QString userCountInfo = QString(" %1/%2").arg(players.size()).arg(gameInfo.max_players()); + " | " + (replayManager->replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameMetaInfo->gameId()); + QString userCountInfo = QString(" %1/%2").arg(gameState->getPlayerCount()).arg(gameMetaInfo->maxPlayers()); playerListDock->setWindowTitle(tr("Player List") + userCountInfo + (playerListDock->isWindow() ? tabText : QString())); } -bool TabGame::isMainPlayerConceded() const -{ - Player *player = players.value(localPlayerId, nullptr); - return player && player->getConceded(); -} - void TabGame::retranslateUi() { QString tabText = - " | " + (replayManager->replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameInfo.game_id()); + " | " + (replayManager->replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameMetaInfo->gameId()); updatePlayerListDockTitle(); cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString())); @@ -243,7 +302,7 @@ void TabGame::retranslateUi() if (aGameInfo) aGameInfo->setText(tr("Game &information")); if (aConcede) { - if (isMainPlayerConceded()) { + if (gameState->isMainPlayerConceded()) { aConcede->setText(tr("Un&concede")); } else { aConcede->setText(tr("&Concede")); @@ -290,7 +349,7 @@ void TabGame::retranslateUi() cardInfoFrameWidget->retranslateUi(); - QMapIterator i(players); + QMapIterator i(gameState->getPlayers()); while (i.hasNext()) i.next().value()->retranslateUi(); QMapIterator j(deckViewContainers); @@ -396,35 +455,27 @@ void TabGame::closeEvent(QCloseEvent *event) event->accept(); } -void TabGame::incrementGameTime() +void TabGame::updateTimeElapsedLabel(const QString newTime) { - int seconds = ++secondsElapsed; - int minutes = seconds / 60; - seconds -= minutes * 60; - int hours = minutes / 60; - minutes -= hours * 60; - - timeElapsedLabel->setText(QString::number(hours).rightJustified(2, '0') + ":" + - QString::number(minutes).rightJustified(2, '0') + ":" + - QString::number(seconds).rightJustified(2, '0')); + timeElapsedLabel->setText(newTime); } void TabGame::adminLockChanged(bool lock) { - bool v = !(spectator && !gameInfo.spectators_can_chat() && lock); + bool v = !(gameState->isSpectator() && !gameMetaInfo->spectatorsCanChat() && lock); sayLabel->setVisible(v); sayEdit->setVisible(v); } void TabGame::actGameInfo() { - DlgCreateGame dlg(gameInfo, roomGameTypes, this); + DlgCreateGame dlg(gameMetaInfo->proto(), gameMetaInfo->getRoomGameTypes(), this); dlg.exec(); } void TabGame::actConcede() { - Player *player = players.value(localPlayerId, nullptr); + Player *player = gameState->getActiveLocalPlayer(); if (player == nullptr) return; if (!player->getConceded()) { @@ -432,14 +483,14 @@ void TabGame::actConcede() QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return; - sendGameCommand(Command_Concede()); + emit playerConceded(); } else { if (QMessageBox::question(this, tr("Unconcede"), tr("You have already conceded. Do you want to return to this game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return; - sendGameCommand(Command_Unconcede()); + emit playerUnconceded(); } } @@ -450,8 +501,8 @@ void TabGame::actConcede() */ bool TabGame::leaveGame() { - if (!gameClosed) { - if (!spectator) { + if (!gameState->isGameClosed()) { + if (!gameState->isSpectator()) { tabSupervisor->setCurrentWidget(this); if (QMessageBox::question(this, tr("Leave game"), tr("Are you sure you want to leave this game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) @@ -459,7 +510,7 @@ bool TabGame::leaveGame() } if (!replayManager->replay) - sendGameCommand(Command_LeaveGame()); + emit gameLeft(); } return true; } @@ -476,63 +527,67 @@ void TabGame::actSay() } if (!sayEdit->text().isEmpty()) { - Command_GameSay cmd; - cmd.set_message(sayEdit->text().toStdString()); - sendGameCommand(cmd); + emit chatMessageSent(sayEdit->text()); sayEdit->clear(); } } +void TabGame::addPlayerToAutoCompleteList(QString playerName) +{ + if (sayEdit && !autocompleteUserList.contains(playerName)) { + autocompleteUserList << playerName; + sayEdit->setCompletionList(autocompleteUserList); + } +} + +void TabGame::removePlayerFromAutoCompleteList(QString playerName) +{ + if (sayEdit && autocompleteUserList.removeOne(playerName)) { + sayEdit->setCompletionList(autocompleteUserList); + } +} + +void TabGame::removeSpectator(int spectatorId, ServerInfo_User spectator) +{ + Q_UNUSED(spectator); + QString playerName = "@" + gameState->getSpectatorName(spectatorId); + removePlayerFromAutoCompleteList(playerName); +} + void TabGame::actPhaseAction() { int phase = phaseActions.indexOf(static_cast(sender())); - Command_SetActivePhase cmd; - cmd.set_phase(static_cast(phase)); - sendGameCommand(cmd); + emit phaseChanged(phase); } void TabGame::actNextPhase() { - int phase = currentPhase; + int phase = gameState->getCurrentPhase(); if (++phase >= phasesToolbar->phaseCount()) phase = 0; - Command_SetActivePhase cmd; - cmd.set_phase(static_cast(phase)); - sendGameCommand(cmd); + + emit phaseChanged(phase); } void TabGame::actNextPhaseAction() { - int phase = currentPhase + 1; + int phase = gameState->getCurrentPhase() + 1; if (phase >= phasesToolbar->phaseCount()) { phase = 0; } if (phase == 0) { - Command_NextTurn cmd; - sendGameCommand(cmd); + emit turnAdvanced(); } else { - Command_SetActivePhase cmd; - cmd.set_phase(static_cast(phase)); - sendGameCommand(cmd); + emit phaseChanged(phase); } phasesToolbar->triggerPhaseAction(phase); } -void TabGame::actNextTurn() -{ - sendGameCommand(Command_NextTurn()); -} - -void TabGame::actReverseTurn() -{ - sendGameCommand(Command_ReverseTurn()); -} - void TabGame::actRemoveLocalArrows() { - QMapIterator playerIterator(players); + QMapIterator playerIterator(gameState->getPlayers()); while (playerIterator.hasNext()) { Player *player = playerIterator.next().value(); if (!player->getLocal()) @@ -540,9 +595,7 @@ void TabGame::actRemoveLocalArrows() QMapIterator arrowIterator(player->getArrows()); while (arrowIterator.hasNext()) { ArrowItem *a = arrowIterator.next().value(); - Command_DeleteArrow cmd; - cmd.set_arrow_id(a->getId()); - sendGameCommand(cmd); + emit arrowDeletionRequested(a->getId()); } } } @@ -563,51 +616,144 @@ void TabGame::actCompleterChanged() : completer->setCompletionRole(1); } -Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) +void TabGame::notifyPlayerJoin(QString playerName) { - bool local = clients.size() > 1 || playerId == localPlayerId; - auto *newPlayer = new Player(info, playerId, local, judge, this); - connect(newPlayer, SIGNAL(openDeckEditor(const DeckLoader *)), this, SIGNAL(openDeckEditor(const DeckLoader *))); - QString newPlayerName = "@" + newPlayer->getName(); - if (sayEdit && !autocompleteUserList.contains(newPlayerName)) { - autocompleteUserList << newPlayerName; - sayEdit->setCompletionList(autocompleteUserList); + if (trayIcon) { + QString gameId(QString::number(gameMetaInfo->gameId())); + trayIcon->showMessage(tr("A player has joined game #%1").arg(gameId), + tr("%1 has joined the game").arg(playerName)); } +} + +void TabGame::notifyPlayerKicked() +{ + tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this)); + QMessageBox msgBox(this); + msgBox.setWindowTitle(getTabText()); + msgBox.setText(tr("You have been kicked out of the game.")); + msgBox.setIcon(QMessageBox::Information); + msgBox.exec(); +} + +void TabGame::processPlayerLeave(Player *leavingPlayer) +{ + QString playerName = "@" + leavingPlayer->getName(); + removePlayerFromAutoCompleteList(playerName); + + scene->removePlayer(leavingPlayer); +} + +Player *TabGame::addPlayer(Player *newPlayer) +{ + QString newPlayerName = "@" + newPlayer->getName(); + addPlayerToAutoCompleteList(newPlayerName); + scene->addPlayer(newPlayer); connect(newPlayer, &Player::newCardAdded, this, &TabGame::newCardAdded); - connect(newPlayer, &Player::cardMenuUpdated, this, &TabGame::setCardMenu); + // TODO + // connect(newPlayer, &Player::cardMenuUpdated, this, &TabGame::setCardMenu); messageLog->connectToPlayer(newPlayer); - if (local && !spectator) { - if (clients.size() == 1) - newPlayer->setShortcutsActive(); - - auto *deckView = new TabbedDeckViewContainer(playerId, this); - connect(deckView->playerDeckView, &DeckViewContainer::newCardAdded, this, &TabGame::newCardAdded); - deckViewContainers.insert(playerId, deckView); - deckViewContainerLayout->addWidget(deckView); - - // auto load deck for player if that debug setting is enabled - QString deckPath = SettingsCache::instance().debug().getDeckPathForPlayer(newPlayer->getName()); - if (!deckPath.isEmpty()) { - QTimer::singleShot(0, this, [deckView, deckPath] { - deckView->playerDeckView->loadDeckFromFile(deckPath); - deckView->playerDeckView->readyAndUpdate(); - }); - } + if (gameState->isLocalPlayer(newPlayer->getId()) && !gameState->isSpectator()) { + addLocalPlayer(newPlayer, newPlayer->getId()); } gameMenu->insertMenu(playersSeparator, newPlayer->getPlayerMenu()); - players.insert(playerId, newPlayer); + createZoneForPlayer(newPlayer, newPlayer->getId()); - if (!spectators.contains(playerId)) { + // update menu text when player concedes so that "concede" gets updated to "unconcede" + connect(newPlayer, &Player::playerCountChanged, this, &TabGame::retranslateUi); + + emit playerAdded(newPlayer); + return newPlayer; +} + +void TabGame::addLocalPlayer(Player *newPlayer, int playerId) +{ + if (gameState->getClients().size() == 1) { + newPlayer->setShortcutsActive(); + } + + auto *deckView = new TabbedDeckViewContainer(playerId, this); + connect(deckView->playerDeckView, &DeckViewContainer::newCardAdded, this, &TabGame::newCardAdded); + deckViewContainers.insert(playerId, deckView); + deckViewContainerLayout->addWidget(deckView); + + // auto load deck for player if that debug setting is enabled + QString deckPath = SettingsCache::instance().debug().getDeckPathForPlayer(newPlayer->getName()); + if (!deckPath.isEmpty()) { + QTimer::singleShot(0, this, [deckView, deckPath] { + deckView->playerDeckView->loadDeckFromFile(deckPath); + deckView->playerDeckView->readyAndUpdate(); + }); + } +} + +void TabGame::processRemotePlayerDeckSelect(QString deckList, int playerId, QString playerName) +{ + DeckList loader; + loader.loadFromString_Native(deckList); + QMapIterator i(deckViewContainers); + while (i.hasNext()) { + i.next(); + i.value()->addOpponentDeckView(loader, playerId, playerName); + } +} + +void TabGame::processMultipleRemotePlayerDeckSelect(QVector>> playerIdDeckMap) +{ + for (const auto &entry : playerIdDeckMap) { + int playerId = entry.first; + QString playerName = entry.second.first; + QString deckList = entry.second.second; + + processRemotePlayerDeckSelect(deckList, playerId, playerName); + } +} + +void TabGame::processLocalPlayerDeckSelect(Player *localPlayer, int playerId, ServerInfo_Player playerInfo) +{ + loadDeckForLocalPlayer(localPlayer, playerId, playerInfo); + processLocalPlayerReady(playerId, playerInfo); +} + +void TabGame::loadDeckForLocalPlayer(Player *localPlayer, int playerId, ServerInfo_Player playerInfo) +{ + TabbedDeckViewContainer *deckViewContainer = deckViewContainers.value(playerId); + if (playerInfo.has_deck_list()) { + DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list())); + PictureLoader::cacheCardPixmaps(CardDatabaseManager::getInstance()->getCards(newDeck.getCardRefList())); + deckViewContainer->playerDeckView->setDeck(newDeck); + localPlayer->setDeck(newDeck); + } +} + +void TabGame::processLocalPlayerReady(int playerId, ServerInfo_Player playerInfo) +{ + processLocalPlayerReadyStateChanged(playerId, playerInfo.properties().ready_start()); + processLocalPlayerSideboardLocked(playerId, playerInfo.properties().sideboard_locked()); +} + +void TabGame::processLocalPlayerSideboardLocked(int playerId, bool sideboardLocked) +{ + deckViewContainers.value(playerId)->playerDeckView->setSideboardLocked(sideboardLocked); +} + +void TabGame::processLocalPlayerReadyStateChanged(int playerId, bool ready) +{ + deckViewContainers.value(playerId)->playerDeckView->setReadyStart(ready); +} + +void TabGame::createZoneForPlayer(Player *newPlayer, int playerId) +{ + if (!gameState->getSpectators().contains(playerId)) { // Loop for each player, the idea is to have one assigned zone for each non-spectator player - for (int i = 1; i <= players.count(); ++i) { + for (int i = 1; i <= gameState->getPlayerCount(); ++i) { bool aPlayerHasThisZone = false; - for (auto &player : players) { + for (auto &player : gameState->getPlayers()) { if (player->getZoneId() == i) { aPlayerHasThisZone = true; break; @@ -619,164 +765,24 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) } } } - - // update menu text when player concedes so that "concede" gets updated to "unconcede" - connect(newPlayer, &Player::playerCountChanged, this, &TabGame::retranslateUi); - - emit playerAdded(newPlayer); - return newPlayer; -} - -void TabGame::processGameEventContainer(const GameEventContainer &cont, - AbstractClient *client, - Player::EventProcessingOptions options) -{ - const GameEventContext &context = cont.context(); - messageLog->containerProcessingStarted(context); - const int eventListSize = cont.event_list_size(); - for (int i = 0; i < eventListSize; ++i) { - const GameEvent &event = cont.event_list(i); - const int playerId = event.player_id(); - const auto eventType = static_cast(getPbExtension(event)); - - if (cont.has_forced_by_judge()) { - auto id = cont.forced_by_judge(); - Player *judgep = players.value(id, nullptr); - if (judgep) { - messageLog->setContextJudgeName(judgep->getName()); - } else if (spectators.contains(id)) { - messageLog->setContextJudgeName(QString::fromStdString(spectators.value(id).name())); - } - } - - if (spectators.contains(playerId)) { - switch (eventType) { - case GameEvent::GAME_SAY: - eventSpectatorSay(event.GetExtension(Event_GameSay::ext), playerId, context); - break; - case GameEvent::LEAVE: - eventSpectatorLeave(event.GetExtension(Event_Leave::ext), playerId, context); - break; - default: - break; - } - } else { - if ((clients.size() > 1) && (playerId != -1)) - if (clients.at(playerId) != client) - continue; - - switch (eventType) { - case GameEvent::GAME_STATE_CHANGED: - eventGameStateChanged(event.GetExtension(Event_GameStateChanged::ext), playerId, context); - break; - case GameEvent::PLAYER_PROPERTIES_CHANGED: - eventPlayerPropertiesChanged(event.GetExtension(Event_PlayerPropertiesChanged::ext), playerId, - context); - break; - case GameEvent::JOIN: - eventJoin(event.GetExtension(Event_Join::ext), playerId, context); - break; - case GameEvent::LEAVE: - eventLeave(event.GetExtension(Event_Leave::ext), playerId, context); - break; - case GameEvent::KICKED: - eventKicked(event.GetExtension(Event_Kicked::ext), playerId, context); - break; - case GameEvent::GAME_HOST_CHANGED: - eventGameHostChanged(event.GetExtension(Event_GameHostChanged::ext), playerId, context); - break; - case GameEvent::GAME_CLOSED: - eventGameClosed(event.GetExtension(Event_GameClosed::ext), playerId, context); - break; - case GameEvent::SET_ACTIVE_PLAYER: - eventSetActivePlayer(event.GetExtension(Event_SetActivePlayer::ext), playerId, context); - break; - case GameEvent::SET_ACTIVE_PHASE: - eventSetActivePhase(event.GetExtension(Event_SetActivePhase::ext), playerId, context); - break; - case GameEvent::REVERSE_TURN: - eventReverseTurn(event.GetExtension(Event_ReverseTurn::ext), playerId, context); - break; - - default: { - Player *player = players.value(playerId, 0); - if (!player) { - qCWarning(TabGameLog) << "unhandled game event: invalid player id"; - break; - } - player->processGameEvent(eventType, event, context, options); - emitUserEvent(); - } - } - } - } - messageLog->containerProcessingDone(); } AbstractClient *TabGame::getClientForPlayer(int playerId) const { - if (clients.size() > 1) { + if (gameState->getClients().size() > 1) { if (playerId == -1) - playerId = getActiveLocalPlayer()->getId(); + playerId = gameState->getActiveLocalPlayer()->getId(); - return clients.at(playerId); - } else if (clients.isEmpty()) + return gameState->getClients().at(playerId); + } else if (gameState->getClients().isEmpty()) return nullptr; else - return clients.first(); -} - -void TabGame::sendGameCommand(PendingCommand *pend, int playerId) -{ - AbstractClient *client = getClientForPlayer(playerId); - if (!client) - return; - - connect(pend, &PendingCommand::finished, this, &TabGame::commandFinished); - client->sendCommand(pend); -} - -void TabGame::sendGameCommand(const google::protobuf::Message &command, int playerId) -{ - AbstractClient *client = getClientForPlayer(playerId); - if (!client) - return; - - PendingCommand *pend = prepareGameCommand(command); - connect(pend, &PendingCommand::finished, this, &TabGame::commandFinished); - client->sendCommand(pend); -} - -void TabGame::commandFinished(const Response &response) -{ - if (response.response_code() == Response::RespChatFlood) - messageLog->appendMessage(tr("You are flooding the game. Please wait a couple of seconds.")); -} - -PendingCommand *TabGame::prepareGameCommand(const ::google::protobuf::Message &cmd) -{ - CommandContainer cont; - cont.set_game_id(static_cast(gameInfo.game_id())); - GameCommand *c = cont.add_game_command(); - c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); - return new PendingCommand(cont); -} - -PendingCommand *TabGame::prepareGameCommand(const QList &cmdList) -{ - CommandContainer cont; - cont.set_game_id(static_cast(gameInfo.game_id())); - for (auto i : cmdList) { - GameCommand *c = cont.add_game_command(); - c->GetReflection()->MutableMessage(c, i->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*i); - delete i; - } - return new PendingCommand(cont); + return gameState->getClients().first(); } void TabGame::startGame(bool _resuming) { - currentPhase = -1; + gameState->setCurrentPhase(-1); QMapIterator i(deckViewContainers); while (i.hasNext()) { @@ -789,21 +795,18 @@ void TabGame::startGame(bool _resuming) mainWidget->setCurrentWidget(gamePlayAreaWidget); if (!_resuming) { - QMapIterator playerIterator(players); + QMapIterator playerIterator(gameState->getPlayers()); while (playerIterator.hasNext()) playerIterator.next().value()->setGameStarted(); } - playerListWidget->setGameStarted(true, resuming); - gameInfo.set_started(true); + playerListWidget->setGameStarted(true, gameState->isResuming()); + gameMetaInfo->setStarted(true); static_cast(gameView->scene())->rearrange(); } void TabGame::stopGame() { - currentPhase = -1; - activePlayer = -1; - QMapIterator i(deckViewContainers); while (i.hasNext()) { i.next(); @@ -814,366 +817,44 @@ void TabGame::stopGame() playerListWidget->setActivePlayer(-1); playerListWidget->setGameStarted(false, false); - gameInfo.set_started(false); + + scene->clearViews(); } void TabGame::closeGame() { - gameInfo.set_started(false); - gameClosed = true; - gameMenu->clear(); gameMenu->addAction(aLeaveGame); } -void TabGame::eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext & /*context*/) -{ - const ServerInfo_User &userInfo = spectators.value(eventPlayerId); - messageLog->logSpectatorSay(userInfo, QString::fromStdString(event.message())); -} - -void TabGame::eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext & /*context*/) -{ - QString playerName = "@" + QString::fromStdString(spectators.value(eventPlayerId).name()); - if (sayEdit && autocompleteUserList.removeOne(playerName)) - sayEdit->setCompletionList(autocompleteUserList); - messageLog->logLeaveSpectator(QString::fromStdString(spectators.value(eventPlayerId).name()), - getLeaveReason(event.reason())); - playerListWidget->removePlayer(eventPlayerId); - spectators.remove(eventPlayerId); - - emitUserEvent(); -} - -void TabGame::eventGameStateChanged(const Event_GameStateChanged &event, - int /*eventPlayerId*/, - const GameEventContext & /*context*/) -{ - const int playerListSize = event.player_list_size(); - - QVector>> opponentDecksToDisplay; - - for (int i = 0; i < playerListSize; ++i) { - const ServerInfo_Player &playerInfo = event.player_list(i); - const ServerInfo_PlayerProperties &prop = playerInfo.properties(); - const int playerId = prop.player_id(); - QString playerName = "@" + QString::fromStdString(prop.user_info().name()); - if (sayEdit && !autocompleteUserList.contains(playerName)) { - autocompleteUserList << playerName; - sayEdit->setCompletionList(autocompleteUserList); - } - if (prop.spectator()) { - if (!spectators.contains(playerId)) { - spectators.insert(playerId, prop.user_info()); - playerListWidget->addPlayer(prop); - } - } else { - Player *player = players.value(playerId, 0); - if (!player) { - player = addPlayer(playerId, prop.user_info()); - playerListWidget->addPlayer(prop); - } - player->processPlayerInfo(playerInfo); - if (player->getLocal()) { - TabbedDeckViewContainer *deckViewContainer = deckViewContainers.value(playerId); - if (playerInfo.has_deck_list()) { - DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list())); - PictureLoader::cacheCardPixmaps( - CardDatabaseManager::getInstance()->getCards(newDeck.getCardRefList())); - deckViewContainer->playerDeckView->setDeck(newDeck); - player->setDeck(newDeck); - } - deckViewContainer->playerDeckView->setReadyStart(prop.ready_start()); - deckViewContainer->playerDeckView->setSideboardLocked(prop.sideboard_locked()); - } else { - if (!gameInfo.share_decklists_on_load()) { - continue; - } - - opponentDecksToDisplay.append( - qMakePair(playerId, qMakePair(playerName, QString::fromStdString(playerInfo.deck_list())))); - } - } - } - for (int i = 0; i < playerListSize; ++i) { - const ServerInfo_Player &playerInfo = event.player_list(i); - const ServerInfo_PlayerProperties &prop = playerInfo.properties(); - if (!prop.spectator()) { - Player *player = players.value(prop.player_id(), 0); - if (!player) - continue; - player->processCardAttachment(playerInfo); - } - } - - for (const auto &entry : opponentDecksToDisplay) { - int playerId = entry.first; - QString playerName = entry.second.first; - QString deckList = entry.second.second; - - DeckList loader; - loader.loadFromString_Native(deckList); - - QMapIterator it(deckViewContainers); - while (it.hasNext()) { - it.next(); - it.value()->addOpponentDeckView(loader, playerId, playerName); - } - } - - secondsElapsed = event.seconds_elapsed(); - - if (event.game_started() && !gameInfo.started()) { - startGame(!gameStateKnown); - if (gameStateKnown) - messageLog->logGameStart(); - setActivePlayer(event.active_player_id()); - setActivePhase(event.active_phase()); - } else if (!event.game_started() && gameInfo.started()) { - stopGame(); - scene->clearViews(); - } - gameStateKnown = true; - emitUserEvent(); -} - -void TabGame::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event, - int eventPlayerId, - const GameEventContext &context) -{ - Player *player = players.value(eventPlayerId, 0); - if (!player) - return; - const ServerInfo_PlayerProperties &prop = event.player_properties(); - playerListWidget->updatePlayerProperties(prop, eventPlayerId); - - const auto contextType = static_cast(getPbExtension(context)); - switch (contextType) { - case GameEventContext::READY_START: { - bool ready = prop.ready_start(); - if (player->getLocal()) - deckViewContainers.value(player->getId())->playerDeckView->setReadyStart(ready); - if (ready) - messageLog->logReadyStart(player); - else - messageLog->logNotReadyStart(player); - break; - } - case GameEventContext::CONCEDE: { - messageLog->logConcede(player); - player->setConceded(true); - - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) - playerIterator.next().value()->updateZones(); - - break; - } - case GameEventContext::UNCONCEDE: { - messageLog->logUnconcede(player); - player->setConceded(false); - - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) - playerIterator.next().value()->updateZones(); - - break; - } - case GameEventContext::DECK_SELECT: { - Context_DeckSelect deckSelect = context.GetExtension(Context_DeckSelect::ext); - messageLog->logDeckSelect(player, QString::fromStdString(deckSelect.deck_hash()), - deckSelect.sideboard_size()); - if (gameInfo.share_decklists_on_load() && deckSelect.has_deck_list() && eventPlayerId != localPlayerId) { - DeckList loader; - loader.loadFromString_Native(QString::fromStdString(deckSelect.deck_list())); - QMapIterator i(deckViewContainers); - while (i.hasNext()) { - i.next(); - i.value()->addOpponentDeckView(loader, eventPlayerId, player->getName()); - } - } - break; - } - case GameEventContext::SET_SIDEBOARD_LOCK: { - if (player->getLocal()) - deckViewContainers.value(player->getId())->playerDeckView->setSideboardLocked(prop.sideboard_locked()); - messageLog->logSetSideboardLock(player, prop.sideboard_locked()); - break; - } - case GameEventContext::CONNECTION_STATE_CHANGED: { - messageLog->logConnectionStateChanged(player, prop.ping_seconds() != -1); - break; - } - default:; - } -} - -void TabGame::eventJoin(const Event_Join &event, int /*eventPlayerId*/, const GameEventContext & /*context*/) -{ - const ServerInfo_PlayerProperties &playerInfo = event.player_properties(); - const int playerId = playerInfo.player_id(); - QString playerName = QString::fromStdString(playerInfo.user_info().name()); - if (sayEdit && !autocompleteUserList.contains("@" + playerName)) { - autocompleteUserList << "@" + playerName; - sayEdit->setCompletionList(autocompleteUserList); - } - - if (players.contains(playerId)) - return; - - if (playerInfo.spectator()) { - spectators.insert(playerId, playerInfo.user_info()); - messageLog->logJoinSpectator(playerName); - } else { - Player *newPlayer = addPlayer(playerId, playerInfo.user_info()); - messageLog->logJoin(newPlayer); - if (trayIcon) { - QString gameId(QString::number(gameInfo.game_id())); - trayIcon->showMessage(tr("A player has joined game #%1").arg(gameId), - tr("%1 has joined the game").arg(newPlayer->getName())); - } - } - playerListWidget->addPlayer(playerInfo); - emitUserEvent(); -} - -QString TabGame::getLeaveReason(Event_Leave::LeaveReason reason) -{ - switch (reason) { - case Event_Leave::USER_KICKED: - return tr("kicked by game host or moderator"); - break; - case Event_Leave::USER_LEFT: - return tr("player left the game"); - break; - case Event_Leave::USER_DISCONNECTED: - return tr("player disconnected from server"); - break; - case Event_Leave::OTHER: - default: - return tr("reason unknown"); - break; - } -} -void TabGame::eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext & /*context*/) -{ - Player *player = players.value(eventPlayerId, 0); - if (!player) - return; - - QString playerName = "@" + player->getName(); - if (sayEdit && autocompleteUserList.removeOne(playerName)) - sayEdit->setCompletionList(autocompleteUserList); - - messageLog->logLeave(player, getLeaveReason(event.reason())); - playerListWidget->removePlayer(eventPlayerId); - players.remove(eventPlayerId); - emit playerRemoved(player); - player->clear(); - scene->removePlayer(player); - player->deleteLater(); - - // Rearrange all remaining zones so that attachment relationship updates take place - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) - playerIterator.next().value()->updateZones(); - - emitUserEvent(); -} - -void TabGame::eventKicked(const Event_Kicked & /*event*/, int /*eventPlayerId*/, const GameEventContext & /*context*/) -{ - closeGame(); - tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this)); - messageLog->logKicked(); - - QMessageBox msgBox(this); - msgBox.setWindowTitle(getTabText()); - msgBox.setText(tr("You have been kicked out of the game.")); - msgBox.setIcon(QMessageBox::Information); - msgBox.exec(); - - emitUserEvent(); -} - -void TabGame::eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/) -{ - Player *player = players.value(eventPlayerId, 0); - if (!player) - return; - - messageLog->logReverseTurn(player, event.reversed()); -} - -void TabGame::eventGameHostChanged(const Event_GameHostChanged & /*event*/, - int eventPlayerId, - const GameEventContext & /*context*/) -{ - hostId = eventPlayerId; -} - -void TabGame::eventGameClosed(const Event_GameClosed & /*event*/, - int /*eventPlayerId*/, - const GameEventContext & /*context*/) -{ - closeGame(); - messageLog->logGameClosed(); - emitUserEvent(); -} - Player *TabGame::setActivePlayer(int id) { - Player *player = players.value(id, 0); + Player *player = gameState->getPlayer(id); if (!player) return nullptr; - activePlayer = id; + playerListWidget->setActivePlayer(id); - QMapIterator i(players); + QMapIterator i(gameState->getPlayers()); while (i.hasNext()) { i.next(); if (i.value() == player) { i.value()->setActive(true); - if (clients.size() > 1) + if (gameState->getClients().size() > 1) i.value()->setShortcutsActive(); } else { i.value()->setActive(false); - if (clients.size() > 1) + if (gameState->getClients().size() > 1) i.value()->setShortcutsInactive(); } } - currentPhase = -1; + gameState->setCurrentPhase(-1); emitUserEvent(); return player; } -void TabGame::eventSetActivePlayer(const Event_SetActivePlayer &event, - int /*eventPlayerId*/, - const GameEventContext & /*context*/) -{ - Player *player = setActivePlayer(event.active_player_id()); - if (!player) - return; - messageLog->logSetActivePlayer(player); - emitUserEvent(); -} - void TabGame::setActivePhase(int phase) { - if (currentPhase != phase) { - currentPhase = phase; - phasesToolbar->setActivePhase(phase); - } -} - -void TabGame::eventSetActivePhase(const Event_SetActivePhase &event, - int /*eventPlayerId*/, - const GameEventContext & /*context*/) -{ - const int phase = event.phase(); - if (currentPhase != phase) - messageLog->logSetActivePhase(phase); - setActivePhase(phase); - emitUserEvent(); + phasesToolbar->setActivePhase(phase); } void TabGame::newCardAdded(AbstractCardItem *card) @@ -1187,7 +868,7 @@ void TabGame::newCardAdded(AbstractCardItem *card) CardItem *TabGame::getCard(int playerId, const QString &zoneName, int cardId) const { - Player *player = players.value(playerId, 0); + Player *player = gameState->getPlayer(playerId); if (!player) return nullptr; @@ -1207,8 +888,8 @@ QString TabGame::getTabText() const gameTypeInfo.append("..."); } - QString gameDesc(gameInfo.description().c_str()); - QString gameId(QString::number(gameInfo.game_id())); + QString gameDesc(gameMetaInfo->description()); + QString gameId(QString::number(gameMetaInfo->gameId())); QString tabText; if (replayManager->replay) @@ -1228,23 +909,6 @@ QString TabGame::getTabText() const return tabText; } -Player *TabGame::getActiveLocalPlayer() const -{ - Player *active = players.value(activePlayer, 0); - if (active) - if (active->getLocal()) - return active; - - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Player *temp = playerIterator.next().value(); - if (temp->getLocal()) - return temp; - } - - return nullptr; -} - void TabGame::setActiveCard(CardItem *card) { activeCard = card; @@ -1270,14 +934,17 @@ void TabGame::createMenuItems() { aNextPhase = new QAction(this); connect(aNextPhase, &QAction::triggered, this, &TabGame::actNextPhase); + connect(this, &TabGame::phaseChanged, gameEventHandler, &GameEventHandler::handleActivePhaseChanged); aNextPhaseAction = new QAction(this); connect(aNextPhaseAction, &QAction::triggered, this, &TabGame::actNextPhaseAction); + connect(this, &TabGame::turnAdvanced, gameEventHandler, &GameEventHandler::handleNextTurn); aNextTurn = new QAction(this); - connect(aNextTurn, &QAction::triggered, this, &TabGame::actNextTurn); + connect(aNextTurn, &QAction::triggered, gameEventHandler, &GameEventHandler::handleNextTurn); aReverseTurn = new QAction(this); - connect(aReverseTurn, &QAction::triggered, this, &TabGame::actReverseTurn); + connect(aReverseTurn, &QAction::triggered, gameEventHandler, &GameEventHandler::handleReverseTurn); aRemoveLocalArrows = new QAction(this); connect(aRemoveLocalArrows, &QAction::triggered, this, &TabGame::actRemoveLocalArrows); + connect(this, &TabGame::arrowDeletionRequested, gameEventHandler, &GameEventHandler::handleArrowDeletion); aRotateViewCW = new QAction(this); connect(aRotateViewCW, &QAction::triggered, this, &TabGame::actRotateViewCW); aRotateViewCCW = new QAction(this); @@ -1286,6 +953,8 @@ void TabGame::createMenuItems() connect(aGameInfo, &QAction::triggered, this, &TabGame::actGameInfo); aConcede = new QAction(this); connect(aConcede, &QAction::triggered, this, &TabGame::actConcede); + connect(this, &TabGame::playerConceded, gameEventHandler, &GameEventHandler::handlePlayerConceded); + connect(this, &TabGame::playerUnconceded, gameEventHandler, &GameEventHandler::handlePlayerUnconceded); aLeaveGame = new QAction(this); connect(aLeaveGame, &QAction::triggered, this, &TabGame::closeRequest); aFocusChat = new QAction(this); @@ -1520,8 +1189,8 @@ void TabGame::createPlayAreaWidget(bool bReplay) { phasesToolbar = new PhasesToolbar; if (!bReplay) - connect(phasesToolbar, &PhasesToolbar::sendGameCommand, this, - qOverload(&TabGame::sendGameCommand)); + connect(phasesToolbar, &PhasesToolbar::sendGameCommand, gameEventHandler, + qOverload(&GameEventHandler::sendGameCommand)); scene = new GameScene(phasesToolbar, this); gameView = new GameView(scene); @@ -1595,7 +1264,7 @@ void TabGame::createPlayerListDock(bool bReplay) if (bReplay) { playerListWidget = new PlayerListWidget(nullptr, nullptr, this); } else { - playerListWidget = new PlayerListWidget(tabSupervisor, clients.first(), this); + playerListWidget = new PlayerListWidget(tabSupervisor, gameState->getClients().first(), this); connect(playerListWidget, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); } @@ -1621,10 +1290,8 @@ void TabGame::createMessageDock(bool bReplay) if (!bReplay) { timeElapsedLabel = new QLabel; timeElapsedLabel->setAlignment(Qt::AlignCenter); - gameTimer = new QTimer(this); - gameTimer->setInterval(1000); - connect(gameTimer, &QTimer::timeout, this, &TabGame::incrementGameTime); - gameTimer->start(); + connect(gameState, &GameState::updateTimeElapsedLabel, this, &TabGame::updateTimeElapsedLabel); + gameState->startGameTimer(); messageLogLayout->addWidget(timeElapsedLabel); } @@ -1651,6 +1318,7 @@ void TabGame::createMessageDock(bool bReplay) sayEdit = new LineEditCompleter; sayEdit->setMaxLength(MAX_TEXT_LENGTH); sayLabel->setBuddy(sayEdit); + connect(this, &TabGame::chatMessageSent, gameEventHandler, &GameEventHandler::handleChatMessageSent); completer = new QCompleter(autocompleteUserList, sayEdit); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setMaxVisibleItems(5); @@ -1659,14 +1327,14 @@ void TabGame::createMessageDock(bool bReplay) sayEdit->setCompleter(completer); actCompleterChanged(); - if (spectator) { + if (gameState->isSpectator()) { /* Spectators can only talk if: * (a) the game creator allows it * (b) the spectator is a moderator/administrator * (c) the spectator is a judge */ - bool isModOrJudge = !tabSupervisor->getAdminLocked() || judge; - if (!isModOrJudge && !gameInfo.spectators_can_chat()) { + bool isModOrJudge = !tabSupervisor->getAdminLocked() || gameState->isJudge(); + if (!isModOrJudge && !gameMetaInfo->spectatorsCanChat()) { sayLabel->hide(); sayEdit->hide(); } diff --git a/cockatrice/src/client/tabs/tab_game.h b/cockatrice/src/client/tabs/tab_game.h index e7f69485c..a336ebd4a 100644 --- a/cockatrice/src/client/tabs/tab_game.h +++ b/cockatrice/src/client/tabs/tab_game.h @@ -2,17 +2,20 @@ #define TAB_GAME_H #include "../../client/tearoff_menu.h" +#include "../../game/game_event_handler.h" +#include "../../game/game_meta_info.h" +#include "../../game/game_state.h" #include "../../game/player/player.h" #include "../replay_manager.h" #include "../ui/widgets/visual_deck_storage/visual_deck_storage_widget.h" #include "pb/event_leave.pb.h" -#include "pb/serverinfo_game.pb.h" #include "tab.h" #include #include #include +class ServerInfo_PlayerProperties; class TabbedDeckViewContainer; inline Q_LOGGING_CATEGORY(TabGameLog, "tab_game"); @@ -24,7 +27,6 @@ class GameView; class GameScene; class ReplayManager; class CardInfoFrameWidget; -class MessageLogWidget; class QTimer; class QSplitter; class QLabel; @@ -35,25 +37,6 @@ class ZoneViewWidget; class PhasesToolbar; class PlayerListWidget; class ReplayTimelineWidget; -class Response; -class GameEventContainer; -class GameEventContext; -class GameCommand; -class CommandContainer; -class Event_GameJoined; -class Event_GameStateChanged; -class Event_PlayerPropertiesChanged; -class Event_Join; -class Event_Leave; -class Event_GameHostChanged; -class Event_GameClosed; -class Event_GameStart; -class Event_SetActivePlayer; -class Event_SetActivePhase; -class Event_Ping; -class Event_GameSay; -class Event_Kicked; -class Event_ReverseTurn; class CardZone; class AbstractCardItem; class CardItem; @@ -61,8 +44,6 @@ class DeckLoader; class QVBoxLayout; class QHBoxLayout; class GameReplay; -class ServerInfo_User; -class PendingCommand; class LineEditCompleter; class QDockWidget; class QStackedWidget; @@ -71,26 +52,11 @@ class TabGame : public Tab { Q_OBJECT private: - QTimer *gameTimer; - int secondsElapsed; + GameMetaInfo *gameMetaInfo; + GameState *gameState; + GameEventHandler *gameEventHandler; const UserListProxy *userListProxy; - QList clients; - ServerInfo_Game gameInfo; - QMap roomGameTypes; - int hostId; - int localPlayerId; - const bool isLocalGame; - bool spectator; - bool judge; - QMap players; - QMap spectators; - bool gameStateKnown; - bool resuming; - QStringList phasesList; - int currentPhase; - int activePlayer; CardItem *activeCard; - bool gameClosed; ReplayManager *replayManager; QStringList gameTypes; QCompleter *completer; @@ -121,34 +87,22 @@ private: QList phaseActions; QAction *aCardMenu; - Player *addPlayer(int playerId, const ServerInfo_User &info); - - bool isMainPlayerConceded() const; + Player *addPlayer(Player *newPlayer); + void addLocalPlayer(Player *newPlayer, int playerId); + void processRemotePlayerDeckSelect(QString deckList, int playerId, QString playerName); + void processMultipleRemotePlayerDeckSelect(QVector>> playerIdDeckMap); + void processLocalPlayerDeckSelect(Player *localPlayer, int playerId, ServerInfo_Player playerInfo); + void loadDeckForLocalPlayer(Player *localPlayer, int playerId, ServerInfo_Player playerInfo); + void processLocalPlayerReady(int playerId, ServerInfo_Player playerInfo); + void createZoneForPlayer(Player *newPlayer, int playerId); void startGame(bool resuming); void stopGame(); void closeGame(); bool leaveGame(); - void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context); - void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); - - void eventGameStateChanged(const Event_GameStateChanged &event, int eventPlayerId, const GameEventContext &context); - void eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event, - int eventPlayerId, - const GameEventContext &context); - void eventJoin(const Event_Join &event, int eventPlayerId, const GameEventContext &context); - void eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); - void eventKicked(const Event_Kicked &event, int eventPlayerId, const GameEventContext &context); - void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context); - void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context); Player *setActivePlayer(int id); - void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context); void setActivePhase(int phase); - void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context); - void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context); - void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/); - void emitUserEvent(); void createMenuItems(); void createReplayMenuItems(); void createViewMenuItems(); @@ -158,7 +112,6 @@ private: void createPlayAreaWidget(bool bReplay = false); void createDeckViewContainerWidget(bool bReplay = false); void createReplayDock(GameReplay *replay); - QString getLeaveReason(Event_Leave::LeaveReason reason); signals: void gameClosing(TabGame *tab); void playerAdded(Player *player); @@ -169,8 +122,15 @@ signals: void openDeckEditor(const DeckLoader *deck); void notIdle(); + void playerConceded(); + void playerUnconceded(); + void phaseChanged(int phase); + void gameLeft(); + void chatMessageSent(QString chatMessage); + void turnAdvanced(); + void arrowDeletionRequested(int arrowId); + private slots: - void incrementGameTime(); void adminLockChanged(bool lock); void newCardAdded(AbstractCardItem *card); void setCardMenu(QMenu *menu); @@ -184,17 +144,17 @@ private slots: void actPhaseAction(); void actNextPhase(); void actNextPhaseAction(); - void actNextTurn(); - void actReverseTurn(); void addMentionTag(const QString &value); void linkCardToChat(const QString &cardName); - void commandFinished(const Response &response); void refreshShortcuts(); void loadLayout(); void actCompleterChanged(); + void notifyPlayerJoin(QString playerName); + void notifyPlayerKicked(); + void processPlayerLeave(Player *leavingPlayer); void actResetLayout(); void freeDocksSize(); @@ -212,39 +172,36 @@ public: QList &_clients, const Event_GameJoined &event, const QMap &_roomGameTypes); + void connectToGameState(); + void connectToGameEventHandler(); + void connectMessageLogToGameEventHandler(); + void connectPlayerListToGameEventHandler(); void loadReplay(GameReplay *replay); TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay); ~TabGame() override; void retranslateUi() override; void updatePlayerListDockTitle(); bool closeRequest() override; - const QMap &getPlayers() const + + GameMetaInfo *getGameMetaInfo() { - return players; + return gameMetaInfo; } + + GameState *getGameState() const + { + return gameState; + } + + GameEventHandler *getGameEventHandler() const + { + return gameEventHandler; + } + CardItem *getCard(int playerId, const QString &zoneName, int cardId) const; - bool isHost() const - { - return hostId == localPlayerId; - } - bool getIsLocalGame() const - { - return isLocalGame; - } - int getGameId() const - { - return gameInfo.game_id(); - } + QString getTabText() const override; - bool isSpectator() const - { - return spectator; - } - bool isSpectatorsOmniscient() const - { - return gameInfo.spectators_omniscient(); - } - Player *getActiveLocalPlayer() const; + AbstractClient *getClientForPlayer(int playerId) const; void setActiveCard(CardItem *card); @@ -253,16 +210,16 @@ public: return activeCard; } - void processGameEventContainer(const GameEventContainer &cont, - AbstractClient *client, - Player::EventProcessingOptions options); - PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd); - PendingCommand *prepareGameCommand(const QList &cmdList); public slots: - void sendGameCommand(PendingCommand *pend, int playerId = -1); - void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1); void viewCardInfo(const CardRef &cardRef = {}) const; void resetChatAndPhase(); + void updateTimeElapsedLabel(QString newTime); + void addPlayerToAutoCompleteList(QString playerName); + void removePlayerFromAutoCompleteList(QString playerName); + void removeSpectator(int spectatorId, ServerInfo_User spectator); + void processLocalPlayerSideboardLocked(int playerId, bool sideboardLocked); + void processLocalPlayerReadyStateChanged(int playerId, bool ready); + void emitUserEvent(); }; #endif diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp index 37bff7413..2b7215375 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.cpp +++ b/cockatrice/src/client/tabs/tab_supervisor.cpp @@ -707,7 +707,7 @@ void TabSupervisor::gameLeft(TabGame *tab) if (tab == currentWidget()) emit setMenu(); - gameTabs.remove(tab->getGameId()); + gameTabs.remove(tab->getGameMetaInfo()->gameId()); removeTab(indexOf(tab)); if (!localClients.isEmpty()) @@ -916,7 +916,7 @@ void TabSupervisor::processGameEventContainer(const GameEventContainer &cont) { TabGame *tab = gameTabs.value(cont.game_id()); if (tab) - tab->processGameEventContainer(cont, qobject_cast(sender()), {}); + tab->getGameEventHandler()->processGameEventContainer(cont, qobject_cast(sender()), {}); else qCInfo(TabSupervisorLog) << "gameEvent: invalid gameId" << cont.game_id(); } diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index 1fb77426e..2b40acd25 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -259,10 +259,10 @@ void CardItem::deleteDragItem() void CardItem::drawArrow(const QColor &arrowColor) { - if (static_cast(owner->parent())->isSpectator()) + if (static_cast(owner->parent())->getGameState()->isSpectator()) return; - Player *arrowOwner = static_cast(owner->parent())->getActiveLocalPlayer(); + Player *arrowOwner = static_cast(owner->parent())->getGameState()->getActiveLocalPlayer(); ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor); scene()->addItem(arrow); arrow->grabMouse(); @@ -282,7 +282,7 @@ void CardItem::drawArrow(const QColor &arrowColor) void CardItem::drawAttachArrow() { - if (static_cast(owner->parent())->isSpectator()) + if (static_cast(owner->parent())->getGameState()->isSpectator()) return; auto *arrow = new ArrowAttachItem(this); diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp index 87ea88b94..ccaeee596 100644 --- a/cockatrice/src/game/deckview/deck_view_container.cpp +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -157,7 +157,7 @@ void DeckViewContainer::switchToDeckSelectView() deckViewLayout->update(); setVisibility(loadLocalButton, true); - setVisibility(loadRemoteButton, !parentGame->getIsLocalGame()); + setVisibility(loadRemoteButton, !parentGame->getGameState()->getIsLocalGame()); setVisibility(loadFromClipboardButton, true); setVisibility(loadFromWebsiteButton, true); setVisibility(unloadDeckButton, false); @@ -190,7 +190,7 @@ void DeckViewContainer::switchToDeckLoadedView() setVisibility(readyStartButton, true); setVisibility(sideboardLockButton, true); - if (parentGame->isHost()) { + if (parentGame->getGameState()->isHost()) { setVisibility(forceStartGameButton, true); } } @@ -287,9 +287,9 @@ void DeckViewContainer::loadDeckFromDeckLoader(const DeckLoader *deck) Command_DeckSelect cmd; cmd.set_deck(deckString.toStdString()); - PendingCommand *pend = parentGame->prepareGameCommand(cmd); + PendingCommand *pend = parentGame->getGameEventHandler()->prepareGameCommand(cmd); connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished); - parentGame->sendGameCommand(pend, playerId); + parentGame->getGameEventHandler()->sendGameCommand(pend, playerId); } void DeckViewContainer::loadRemoteDeck() @@ -298,9 +298,9 @@ void DeckViewContainer::loadRemoteDeck() if (dlg.exec()) { Command_DeckSelect cmd; cmd.set_deck_id(dlg.getDeckId()); - PendingCommand *pend = parentGame->prepareGameCommand(cmd); + PendingCommand *pend = parentGame->getGameEventHandler()->prepareGameCommand(cmd); connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished); - parentGame->sendGameCommand(pend, playerId); + parentGame->getGameEventHandler()->sendGameCommand(pend, playerId); } } @@ -354,7 +354,7 @@ void DeckViewContainer::forceStart() Command_ReadyStart cmd; cmd.set_force_start(true); cmd.set_ready(true); - parentGame->sendGameCommand(cmd, playerId); + parentGame->getGameEventHandler()->sendGameCommand(cmd, playerId); } void DeckViewContainer::sideboardLockButtonClicked() @@ -362,7 +362,7 @@ void DeckViewContainer::sideboardLockButtonClicked() Command_SetSideboardLock cmd; cmd.set_locked(sideboardLockButton->getState()); - parentGame->sendGameCommand(cmd, playerId); + parentGame->getGameEventHandler()->sendGameCommand(cmd, playerId); } void DeckViewContainer::sideboardPlanChanged() @@ -371,7 +371,7 @@ void DeckViewContainer::sideboardPlanChanged() const QList &newPlan = deckView->getSideboardPlan(); for (const auto &i : newPlan) cmd.add_move_list()->CopyFrom(i); - parentGame->sendGameCommand(cmd, playerId); + parentGame->getGameEventHandler()->sendGameCommand(cmd, playerId); } /** @@ -381,7 +381,7 @@ void DeckViewContainer::sendReadyStartCommand(bool ready) { Command_ReadyStart cmd; cmd.set_ready(ready); - parentGame->sendGameCommand(cmd, playerId); + parentGame->getGameEventHandler()->sendGameCommand(cmd, playerId); } /** diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp new file mode 100644 index 000000000..ffa3f720e --- /dev/null +++ b/cockatrice/src/game/game_event_handler.cpp @@ -0,0 +1,517 @@ +#include "game_event_handler.h" + +#include "../client/tabs/tab_game.h" +#include "../server/abstract_client.h" +#include "../server/message_log_widget.h" +#include "../server/pending_command.h" +#include "get_pb_extension.h" +#include "pb/command_concede.pb.h" +#include "pb/command_delete_arrow.pb.h" +#include "pb/command_game_say.pb.h" +#include "pb/command_leave_game.pb.h" +#include "pb/command_next_turn.pb.h" +#include "pb/command_reverse_turn.pb.h" +#include "pb/command_set_active_phase.pb.h" +#include "pb/context_connection_state_changed.pb.h" +#include "pb/context_deck_select.pb.h" +#include "pb/context_ping_changed.pb.h" +#include "pb/event_game_closed.pb.h" +#include "pb/event_game_host_changed.pb.h" +#include "pb/event_game_say.pb.h" +#include "pb/event_game_state_changed.pb.h" +#include "pb/event_join.pb.h" +#include "pb/event_kicked.pb.h" +#include "pb/event_leave.pb.h" +#include "pb/event_player_properties_changed.pb.h" +#include "pb/event_reverse_turn.pb.h" +#include "pb/event_set_active_phase.pb.h" +#include "pb/event_set_active_player.pb.h" +#include "pb/game_event_container.pb.h" + +GameEventHandler::GameEventHandler(TabGame *_game) : game(_game), gameState(_game->getGameState()) +{ +} + +void GameEventHandler::sendGameCommand(PendingCommand *pend, int playerId) +{ + AbstractClient *client = game->getClientForPlayer(playerId); + if (!client) + return; + + connect(pend, &PendingCommand::finished, this, &GameEventHandler::commandFinished); + client->sendCommand(pend); +} + +void GameEventHandler::sendGameCommand(const google::protobuf::Message &command, int playerId) +{ + AbstractClient *client = game->getClientForPlayer(playerId); + if (!client) + return; + + PendingCommand *pend = prepareGameCommand(command); + connect(pend, &PendingCommand::finished, this, &GameEventHandler::commandFinished); + client->sendCommand(pend); +} + +void GameEventHandler::commandFinished(const Response &response) +{ + if (response.response_code() == Response::RespChatFlood) + emit gameFlooded(); +} + +PendingCommand *GameEventHandler::prepareGameCommand(const ::google::protobuf::Message &cmd) +{ + CommandContainer cont; + cont.set_game_id(static_cast(game->getGameMetaInfo()->gameId())); + GameCommand *c = cont.add_game_command(); + c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); + return new PendingCommand(cont); +} + +PendingCommand *GameEventHandler::prepareGameCommand(const QList &cmdList) +{ + CommandContainer cont; + cont.set_game_id(static_cast(game->getGameMetaInfo()->gameId())); + for (auto i : cmdList) { + GameCommand *c = cont.add_game_command(); + c->GetReflection()->MutableMessage(c, i->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*i); + delete i; + } + return new PendingCommand(cont); +} + +void GameEventHandler::processGameEventContainer(const GameEventContainer &cont, + AbstractClient *client, + Player::EventProcessingOptions options) +{ + const GameEventContext &context = cont.context(); + emit containerProcessingStarted(context); + + const int eventListSize = cont.event_list_size(); + for (int i = 0; i < eventListSize; ++i) { + const GameEvent &event = cont.event_list(i); + const int playerId = event.player_id(); + const auto eventType = static_cast(getPbExtension(event)); + + if (cont.has_forced_by_judge()) { + auto id = cont.forced_by_judge(); + Player *judgep = gameState->getPlayers().value(id, nullptr); + if (judgep) { + emit setContextJudgeName(judgep->getName()); + } else if (gameState->getSpectators().contains(id)) { + emit setContextJudgeName(QString::fromStdString(gameState->getSpectators().value(id).name())); + } + } + + if (gameState->getSpectators().contains(playerId)) { + switch (eventType) { + case GameEvent::GAME_SAY: + eventSpectatorSay(event.GetExtension(Event_GameSay::ext), playerId, context); + break; + case GameEvent::LEAVE: + eventSpectatorLeave(event.GetExtension(Event_Leave::ext), playerId, context); + break; + default: + break; + } + } else { + if ((gameState->getClients().size() > 1) && (playerId != -1)) + if (gameState->getClients().at(playerId) != client) + continue; + + switch (eventType) { + case GameEvent::GAME_STATE_CHANGED: + qInfo() << "Game state changed event"; + eventGameStateChanged(event.GetExtension(Event_GameStateChanged::ext), playerId, context); + break; + case GameEvent::PLAYER_PROPERTIES_CHANGED: + qInfo() << "player prop event"; + eventPlayerPropertiesChanged(event.GetExtension(Event_PlayerPropertiesChanged::ext), playerId, + context); + break; + case GameEvent::JOIN: + qInfo() << "join event"; + eventJoin(event.GetExtension(Event_Join::ext), playerId, context); + break; + case GameEvent::LEAVE: + qInfo() << "leave event"; + eventLeave(event.GetExtension(Event_Leave::ext), playerId, context); + break; + case GameEvent::KICKED: + qInfo() << "kicked event"; + eventKicked(event.GetExtension(Event_Kicked::ext), playerId, context); + break; + case GameEvent::GAME_HOST_CHANGED: + qInfo() << "host changed event"; + eventGameHostChanged(event.GetExtension(Event_GameHostChanged::ext), playerId, context); + break; + case GameEvent::GAME_CLOSED: + qInfo() << "game closed event"; + eventGameClosed(event.GetExtension(Event_GameClosed::ext), playerId, context); + break; + case GameEvent::SET_ACTIVE_PLAYER: + qInfo() << "set active player event"; + eventSetActivePlayer(event.GetExtension(Event_SetActivePlayer::ext), playerId, context); + break; + case GameEvent::SET_ACTIVE_PHASE: + qInfo() << "set active phase"; + eventSetActivePhase(event.GetExtension(Event_SetActivePhase::ext), playerId, context); + break; + case GameEvent::REVERSE_TURN: + qInfo() << "reverse turn event"; + eventReverseTurn(event.GetExtension(Event_ReverseTurn::ext), playerId, context); + break; + + default: { + Player *player = gameState->getPlayers().value(playerId, 0); + if (!player) { + // qCWarning(GameEventHandlerLog) << "unhandled game event: invalid player id"; + break; + } + player->processGameEvent(eventType, event, context, options); + game->emitUserEvent(); + } + } + } + } + emit containerProcessingDone(); +} + +void GameEventHandler::handleNextTurn() +{ + sendGameCommand(Command_NextTurn()); +} + +void GameEventHandler::handleReverseTurn() +{ + sendGameCommand(Command_ReverseTurn()); +} + +void GameEventHandler::handlePlayerConceded() +{ + sendGameCommand(Command_Concede()); +} + +void GameEventHandler::handlePlayerUnconceded() +{ + sendGameCommand(Command_Unconcede()); +} + +void GameEventHandler::handleActivePhaseChanged(int phase) +{ + Command_SetActivePhase cmd; + cmd.set_phase(static_cast(phase)); + sendGameCommand(cmd); +} + +void GameEventHandler::handleGameLeft() +{ + sendGameCommand(Command_LeaveGame()); +} + +void GameEventHandler::handleChatMessageSent(const QString &chatMessage) +{ + Command_GameSay cmd; + cmd.set_message(chatMessage.toStdString()); + sendGameCommand(cmd); +} + +void GameEventHandler::handleArrowDeletion(int arrowId) +{ + Command_DeleteArrow cmd; + cmd.set_arrow_id(arrowId); + sendGameCommand(cmd); +} + +void GameEventHandler::eventSpectatorSay(const Event_GameSay &event, + int eventPlayerId, + const GameEventContext & /*context*/) +{ + const ServerInfo_User &userInfo = gameState->getSpectators().value(eventPlayerId); + emit logSpectatorSay(userInfo, QString::fromStdString(event.message())); +} + +void GameEventHandler::eventSpectatorLeave(const Event_Leave &event, + int eventPlayerId, + const GameEventContext & /*context*/) +{ + emit logSpectatorLeave(gameState->getSpectatorName(eventPlayerId), getLeaveReason(event.reason())); + + emit spectatorLeft(eventPlayerId); + + gameState->removeSpectator(eventPlayerId); + + game->emitUserEvent(); +} + +void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event, + int /*eventPlayerId*/, + const GameEventContext & /*context*/) +{ + const int playerListSize = event.player_list_size(); + + QVector>> opponentDecksToDisplay; + + for (int i = 0; i < playerListSize; ++i) { + const ServerInfo_Player &playerInfo = event.player_list(i); + const ServerInfo_PlayerProperties &prop = playerInfo.properties(); + const int playerId = prop.player_id(); + QString playerName = "@" + QString::fromStdString(prop.user_info().name()); + game->addPlayerToAutoCompleteList(playerName); + if (prop.spectator()) { + gameState->addSpectator(playerId, prop); + } else { + Player *player = gameState->getPlayers().value(playerId, 0); + if (!player) { + player = gameState->addPlayer(playerId, prop.user_info(), game); + emit playerJoined(prop); + emit logJoinPlayer(player); + } + player->processPlayerInfo(playerInfo); + if (player->getLocal()) { + emit localPlayerDeckSelected(player, playerId, playerInfo); + } else { + if (!game->getGameMetaInfo()->proto().share_decklists_on_load()) { + continue; + } + + opponentDecksToDisplay.append( + qMakePair(playerId, qMakePair(playerName, QString::fromStdString(playerInfo.deck_list())))); + } + } + } + + processCardAttachmentsForPlayers(event); + + emit remotePlayersDecksSelected(opponentDecksToDisplay); + + gameState->setGameTime(event.seconds_elapsed()); + + if (event.game_started() && !game->getGameMetaInfo()->started()) { + gameState->setResuming(!gameState->isGameStateKnown()); + game->getGameMetaInfo()->setStarted(event.game_started()); + if (gameState->isGameStateKnown()) + emit logGameStart(); + gameState->setActivePlayer(event.active_player_id()); + gameState->setCurrentPhase(event.active_phase()); + } else if (!event.game_started() && game->getGameMetaInfo()->started()) { + gameState->setCurrentPhase(-1); + gameState->setActivePlayer(-1); + game->getGameMetaInfo()->setStarted(false); + emit gameStopped(); + } + gameState->setGameStateKnown(true); + game->emitUserEvent(); +} + +void GameEventHandler::processCardAttachmentsForPlayers(const Event_GameStateChanged &event) +{ + for (int i = 0; i < event.player_list_size(); ++i) { + const ServerInfo_Player &playerInfo = event.player_list(i); + const ServerInfo_PlayerProperties &prop = playerInfo.properties(); + if (!prop.spectator()) { + Player *player = gameState->getPlayers().value(prop.player_id(), 0); + if (!player) + continue; + player->processCardAttachment(playerInfo); + } + } +} + +void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event, + int eventPlayerId, + const GameEventContext &context) +{ + Player *player = gameState->getPlayers().value(eventPlayerId, 0); + if (!player) + return; + const ServerInfo_PlayerProperties &prop = event.player_properties(); + emit playerPropertiesChanged(prop, eventPlayerId); + + const auto contextType = static_cast(getPbExtension(context)); + switch (contextType) { + case GameEventContext::READY_START: { + bool ready = prop.ready_start(); + if (player->getLocal()) + emit localPlayerReadyStateChanged(player->getId(), ready); + if (ready) { + emit logReadyStart(player); + } else { + emit logNotReadyStart(player); + } + break; + } + case GameEventContext::CONCEDE: { + emit playerConceded(player); + player->setConceded(true); + + QMapIterator playerIterator(gameState->getPlayers()); + while (playerIterator.hasNext()) + playerIterator.next().value()->updateZones(); + + break; + } + case GameEventContext::UNCONCEDE: { + emit playerUnconceded(player); + player->setConceded(false); + + QMapIterator playerIterator(gameState->getPlayers()); + while (playerIterator.hasNext()) + playerIterator.next().value()->updateZones(); + + break; + } + case GameEventContext::DECK_SELECT: { + Context_DeckSelect deckSelect = context.GetExtension(Context_DeckSelect::ext); + emit logDeckSelect(player, QString::fromStdString(deckSelect.deck_hash()), deckSelect.sideboard_size()); + if (game->getGameMetaInfo()->proto().share_decklists_on_load() && deckSelect.has_deck_list() && + eventPlayerId != gameState->getLocalPlayerId()) { + emit remotePlayerDeckSelected(QString::fromStdString(deckSelect.deck_list()), eventPlayerId, + player->getName()); + } + break; + } + case GameEventContext::SET_SIDEBOARD_LOCK: { + if (player->getLocal()) { + emit localPlayerSideboardLocked(player->getId(), prop.sideboard_locked()); + } + emit logSideboardLockSet(player, prop.sideboard_locked()); + break; + } + case GameEventContext::CONNECTION_STATE_CHANGED: { + emit logConnectionStateChanged(player, prop.ping_seconds() != -1); + break; + } + default:; + } +} + +void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/, const GameEventContext & /*context*/) +{ + const ServerInfo_PlayerProperties &playerInfo = event.player_properties(); + const int playerId = playerInfo.player_id(); + QString playerName = QString::fromStdString(playerInfo.user_info().name()); + game->addPlayerToAutoCompleteList(playerName); + + if (gameState->getPlayers().contains(playerId)) + return; + + if (playerInfo.spectator()) { + gameState->addSpectator(playerId, playerInfo); + emit logJoinSpectator(playerName); + emit spectatorJoined(playerInfo); + } else { + Player *newPlayer = gameState->addPlayer(playerId, playerInfo.user_info(), game); + emit logJoinPlayer(newPlayer); + emit playerJoined(playerInfo); + } + + game->emitUserEvent(); +} + +QString GameEventHandler::getLeaveReason(Event_Leave::LeaveReason reason) +{ + switch (reason) { + case Event_Leave::USER_KICKED: + return tr("kicked by game host or moderator"); + break; + case Event_Leave::USER_LEFT: + return tr("player left the game"); + break; + case Event_Leave::USER_DISCONNECTED: + return tr("player disconnected from server"); + break; + case Event_Leave::OTHER: + default: + return tr("reason unknown"); + break; + } +} +void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext & /*context*/) +{ + Player *player = gameState->getPlayers().value(eventPlayerId, 0); + if (!player) + return; + + emit playerLeft(eventPlayerId); + + emit logLeave(player, getLeaveReason(event.reason())); + + gameState->removePlayer(eventPlayerId); + + player->clear(); + player->deleteLater(); + + // Rearrange all remaining zones so that attachment relationship updates take place + QMapIterator playerIterator(gameState->getPlayers()); + while (playerIterator.hasNext()) + playerIterator.next().value()->updateZones(); + + game->emitUserEvent(); +} + +void GameEventHandler::eventKicked(const Event_Kicked & /*event*/, + int /*eventPlayerId*/, + const GameEventContext & /*context*/) +{ + emit gameClosed(); + + emit logKicked(); + + emit playerKicked(); + + game->emitUserEvent(); +} + +void GameEventHandler::eventReverseTurn(const Event_ReverseTurn &event, + int eventPlayerId, + const GameEventContext & /*context*/) +{ + Player *player = gameState->getPlayers().value(eventPlayerId, 0); + if (!player) + return; + + emit logTurnReversed(player, event.reversed()); +} + +void GameEventHandler::eventGameHostChanged(const Event_GameHostChanged & /*event*/, + int eventPlayerId, + const GameEventContext & /*context*/) +{ + gameState->setHostId(eventPlayerId); +} + +void GameEventHandler::eventGameClosed(const Event_GameClosed & /*event*/, + int /*eventPlayerId*/, + const GameEventContext & /*context*/) +{ + game->getGameMetaInfo()->setStarted(false); + gameState->setGameClosed(true); + emit gameClosed(); + emit logGameClosed(); + game->emitUserEvent(); +} + +void GameEventHandler::eventSetActivePlayer(const Event_SetActivePlayer &event, + int /*eventPlayerId*/, + const GameEventContext & /*context*/) +{ + gameState->setActivePlayer(event.active_player_id()); + Player *player = gameState->getPlayer(event.active_player_id()); + if (!player) + return; + emit logActivePlayer(player); + game->emitUserEvent(); +} + +void GameEventHandler::eventSetActivePhase(const Event_SetActivePhase &event, + int /*eventPlayerId*/, + const GameEventContext & /*context*/) +{ + const int phase = event.phase(); + if (gameState->getCurrentPhase() != phase) { + emit logActivePhaseChanged(phase); + } + gameState->setCurrentPhase(phase); + game->emitUserEvent(); +} \ No newline at end of file diff --git a/cockatrice/src/game/game_event_handler.h b/cockatrice/src/game/game_event_handler.h new file mode 100644 index 000000000..3ce036e12 --- /dev/null +++ b/cockatrice/src/game/game_event_handler.h @@ -0,0 +1,125 @@ +#ifndef COCKATRICE_GAME_EVENT_HANDLER_H +#define COCKATRICE_GAME_EVENT_HANDLER_H + +#include "pb/event_leave.pb.h" +#include "pb/serverinfo_player.pb.h" +#include "player/player.h" + +#include + +class AbstractClient; +class TabGame; +class Response; +class GameEventContainer; +class GameEventContext; +class GameCommand; +class GameState; +class MessageLogWidget; +class CommandContainer; +class Event_GameJoined; +class Event_GameStateChanged; +class Event_PlayerPropertiesChanged; +class Event_Join; +class Event_Leave; +class Event_GameHostChanged; +class Event_GameClosed; +class Event_GameStart; +class Event_SetActivePlayer; +class Event_SetActivePhase; +class Event_Ping; +class Event_GameSay; +class Event_Kicked; +class Event_ReverseTurn; +class PendingCommand; + +class GameEventHandler : public QObject +{ + Q_OBJECT + +private: + TabGame *game; + GameState *gameState; + +public: + GameEventHandler(TabGame *game); + + void handleNextTurn(); + void handleReverseTurn(); + + void handlePlayerConceded(); + void handlePlayerUnconceded(); + void handleActivePhaseChanged(int phase); + void handleGameLeft(); + void handleChatMessageSent(const QString &chatMessage); + void handleArrowDeletion(int arrowId); + + void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context); + void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); + + void eventGameStateChanged(const Event_GameStateChanged &event, int eventPlayerId, const GameEventContext &context); + void processCardAttachmentsForPlayers(const Event_GameStateChanged &event); + void eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event, + int eventPlayerId, + const GameEventContext &context); + void eventJoin(const Event_Join &event, int eventPlayerId, const GameEventContext &context); + void eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); + QString getLeaveReason(Event_Leave::LeaveReason reason); + void eventKicked(const Event_Kicked &event, int eventPlayerId, const GameEventContext &context); + void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context); + void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context); + + void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context); + void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context); + void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context); + void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/); + + void commandFinished(const Response &response); + + void processGameEventContainer(const GameEventContainer &cont, + AbstractClient *client, + Player::EventProcessingOptions options); + PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd); + PendingCommand *prepareGameCommand(const QList &cmdList); +public slots: + void sendGameCommand(PendingCommand *pend, int playerId = -1); + void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1); + +signals: + void localPlayerDeckSelected(Player *localPlayer, int playerId, ServerInfo_Player playerInfo); + void remotePlayerDeckSelected(QString deckList, int playerId, QString playerName); + void remotePlayersDecksSelected(QVector>> opponentDecks); + void localPlayerSideboardLocked(int playerId, bool sideboardLocked); + void localPlayerReadyStateChanged(int playerId, bool ready); + void gameStopped(); + void gameClosed(); + void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId); + void playerJoined(const ServerInfo_PlayerProperties &playerInfo); + void playerLeft(int leavingPlayerId); + void playerKicked(); + void spectatorJoined(const ServerInfo_PlayerProperties &spectatorInfo); + void spectatorLeft(int leavingSpectatorId); + void gameFlooded(); + void containerProcessingStarted(GameEventContext context); + void setContextJudgeName(QString judgeName); + void containerProcessingDone(); + void logSpectatorSay(ServerInfo_User userInfo, QString message); + void logSpectatorLeave(QString name, QString reason); + void logGameStart(); + void logReadyStart(Player *player); + void logNotReadyStart(Player *player); + void playerConceded(Player *player); + void playerUnconceded(Player *player); + void logDeckSelect(Player *player, QString deckHash, int sideboardSize); + void logSideboardLockSet(Player *player, bool sideboardLocked); + void logConnectionStateChanged(Player *player, bool connected); + void logJoinSpectator(QString spectatorName); + void logJoinPlayer(Player *player); + void logLeave(Player *player, QString reason); + void logKicked(); + void logTurnReversed(Player *player, bool reversed); + void logGameClosed(); + void logActivePlayer(Player *activePlayer); + void logActivePhaseChanged(int activePhase); +}; + +#endif // COCKATRICE_GAME_EVENT_HANDLER_H diff --git a/cockatrice/src/game/game_meta_info.cpp b/cockatrice/src/game/game_meta_info.cpp new file mode 100644 index 000000000..90a6cbeab --- /dev/null +++ b/cockatrice/src/game/game_meta_info.cpp @@ -0,0 +1 @@ +#include "game_meta_info.h" diff --git a/cockatrice/src/game/game_meta_info.h b/cockatrice/src/game/game_meta_info.h new file mode 100644 index 000000000..08600423e --- /dev/null +++ b/cockatrice/src/game/game_meta_info.h @@ -0,0 +1,107 @@ +#ifndef GAME_META_INFO_H +#define GAME_META_INFO_H + +#include "pb/serverinfo_game.pb.h" + +#include +#include + +// Translation layer class to expose protobuf safely and hook it up to Qt Signals. +// This class de-couples the domain object (i.e. the GameMetaInfo) from the network object. +// If the network object changes, only this class needs to be adjusted. + +class GameMetaInfo : public QObject +{ + Q_OBJECT +public: + explicit GameMetaInfo(QObject *parent = nullptr) : QObject(parent) + { + } + + QMap roomGameTypes; + + // Populate from protobuf (e.g., after network message) + void setFromProto(const ServerInfo_Game &gi) + { + gameInfo_.CopyFrom(gi); + } + + const ServerInfo_Game &proto() const + { + return gameInfo_; + } + + // High-level getters that avoid exposing protobuf directly + int gameId() const + { + return gameInfo_.game_id(); + } + int maxPlayers() const + { + return gameInfo_.max_players(); + } + QString description() const + { + return QString::fromStdString(gameInfo_.description()); + } + bool started() const + { + return gameInfo_.started(); + } + bool spectatorsOmniscient() const + { + return gameInfo_.spectators_omniscient(); + } + bool spectatorsCanChat() const + { + return gameInfo_.spectators_can_chat(); + } + int gameTypesSize() const + { + return gameInfo_.game_types_size(); + } + int gameTypeIdAt(int index) const + { + return gameInfo_.game_types(index); + } + + QMap getRoomGameTypes() const + { + return roomGameTypes; + } + + void setRoomGameTypes(QMap _roomGameTypes) + { + roomGameTypes = _roomGameTypes; + } + + QString findRoomGameType(int index) + { + return roomGameTypes.find(gameInfo_.game_types(index)).value(); + } + +public slots: + void setStarted(bool s) + { + if (gameInfo_.started() == s) + return; + gameInfo_.set_started(s); + emit startedChanged(s); + } + void setSpectatorsOmniscient(bool v) + { + if (gameInfo_.spectators_omniscient() == v) + return; + gameInfo_.set_spectators_omniscient(v); + emit spectatorsOmniscienceChanged(v); + } + +signals: + void startedChanged(bool started); + void spectatorsOmniscienceChanged(bool omniscient); + +private: + ServerInfo_Game gameInfo_; +}; + +#endif // GAME_META_INFO_H diff --git a/cockatrice/src/game/game_state.cpp b/cockatrice/src/game/game_state.cpp new file mode 100644 index 000000000..91ed3e356 --- /dev/null +++ b/cockatrice/src/game/game_state.cpp @@ -0,0 +1,44 @@ +#include "game_state.h" + +GameState::GameState(int _secondsElapsed, + int _hostId, + int _localPlayerId, + bool _isLocalGame, + const QList _clients, + bool _spectator, + bool _judge, + bool _gameStateKnown, + bool _resuming, + int _currentPhase, + bool _gameClosed) + : secondsElapsed(_secondsElapsed), hostId(_hostId), localPlayerId(_localPlayerId), isLocalGame(_isLocalGame), + clients(_clients), spectator(_spectator), judge(_judge), gameStateKnown(_gameStateKnown), resuming(_resuming), + currentPhase(_currentPhase), gameClosed(_gameClosed) +{ +} + +void GameState::incrementGameTime() +{ + setGameTime(++secondsElapsed); +} + +void GameState::setGameTime(int _secondsElapsed) +{ + int seconds = _secondsElapsed; + int minutes = seconds / 60; + seconds -= minutes * 60; + int hours = minutes / 60; + minutes -= hours * 60; + + emit updateTimeElapsedLabel(QString::number(hours).rightJustified(2, '0') + ":" + + QString::number(minutes).rightJustified(2, '0') + ":" + + QString::number(seconds).rightJustified(2, '0')); +} + +void GameState::startGameTimer() +{ + gameTimer = new QTimer(this); + gameTimer->setInterval(1000); + connect(gameTimer, &QTimer::timeout, this, &GameState::incrementGameTime); + gameTimer->start(); +} \ No newline at end of file diff --git a/cockatrice/src/game/game_state.h b/cockatrice/src/game/game_state.h new file mode 100644 index 000000000..ed6fdbc49 --- /dev/null +++ b/cockatrice/src/game/game_state.h @@ -0,0 +1,255 @@ +#ifndef COCKATRICE_GAME_STATE_H +#define COCKATRICE_GAME_STATE_H + +#include "../client/tabs/tab_game.h" +#include "../server/abstract_client.h" +#include "pb/serverinfo_game.pb.h" +#include "pb/serverinfo_playerproperties.pb.h" + +#include + +class ServerInfo_PlayerProperties; +class ServerInfo_User; + +class GameState : public QObject +{ + Q_OBJECT + +public: + explicit GameState(int secondsElapsed, + int hostId, + int localPlayerId, + bool isLocalGame, + QList clients, + bool spectator, + bool judge, + bool gameStateKnown, + bool resuming, + int currentPhase, + bool gameClosed); + + const QMap &getPlayers() const + { + return players; + } + + int getPlayerCount() const + { + return players.size(); + } + + const QMap &getSpectators() const + { + return spectators; + } + + ServerInfo_User getSpectator(int playerId) const + { + return spectators.value(playerId); + } + + QString getSpectatorName(int spectatorId) const + { + return QString::fromStdString(spectators.value(spectatorId).name()); + } + + void addSpectator(int spectatorId, const ServerInfo_PlayerProperties &prop) + { + if (!spectators.contains(spectatorId)) { + spectators.insert(spectatorId, prop.user_info()); + emit spectatorAdded(prop); + } + } + + void removeSpectator(int spectatorId) + { + ServerInfo_User spectatorInfo = spectators.value(spectatorId); + spectators.remove(spectatorId); + emit spectatorRemoved(spectatorId, spectatorInfo); + } + + bool isHost() const + { + return hostId == localPlayerId; + } + + void setHostId(int _hostId) + { + hostId = _hostId; + } + + bool isJudge() const + { + return judge; + } + + int getLocalPlayerId() const + { + return localPlayerId; + } + + QList getClients() const + { + return clients; + } + + bool isLocalPlayer(int playerId) const + { + return clients.size() > 1 || playerId == getLocalPlayerId(); + } + + Player *addPlayer(int playerId, const ServerInfo_User &info, TabGame *game) + { + auto *newPlayer = new Player(info, playerId, isLocalPlayer(playerId), isJudge(), game); + // TODO + // connect(newPlayer, &Player::openDeckEditor, game, &TabGame::openDeckEditor); + players.insert(playerId, newPlayer); + emit playerAdded(newPlayer); + return newPlayer; + } + + void removePlayer(int playerId) + { + Player *player = getPlayer(playerId); + if (!player) { + return; + } + players.remove(playerId); + emit playerRemoved(player); + } + + Player *getPlayer(int playerId) + { + Player *player = players.value(playerId, 0); + if (!player) + return nullptr; + return player; + } + + Player *getActiveLocalPlayer() const + { + Player *active = players.value(activePlayer, 0); + if (active) + if (active->getLocal()) + return active; + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Player *temp = playerIterator.next().value(); + if (temp->getLocal()) + return temp; + } + + return nullptr; + } + + void setActivePlayer(int activePlayerId) + { + activePlayer = activePlayerId; + emit activePlayerChanged(activePlayer); + } + + bool getIsLocalGame() const + { + return isLocalGame; + } + + bool isSpectator() const + { + return spectator; + } + + bool isResuming() const + { + return resuming; + } + + void setResuming(bool _resuming) + { + resuming = _resuming; + } + + bool isGameStateKnown() + { + return gameStateKnown; + } + + int getCurrentPhase() const + { + return currentPhase; + } + + void setCurrentPhase(int phase) + { + currentPhase = phase; + emit activePhaseChanged(phase); + } + + bool isMainPlayerConceded() const + { + Player *player = players.value(localPlayerId, nullptr); + return player && player->getConceded(); + } + + void setGameClosed(bool closed) + { + gameClosed = closed; + } + + bool isGameClosed() const + { + return gameClosed; + } + + void onStartedChanged(bool _started) + { + if (_started) { + startGameTimer(); + emit gameStarted(_started); + } else { + emit gameStopped(); + } + } + + void startGameTimer(); + + void setGameStateKnown(bool known) + { + gameStateKnown = known; + } + +signals: + void updateTimeElapsedLabel(QString newTime); + void playerAdded(Player *player); + void playerRemoved(Player *player); + void spectatorAdded(ServerInfo_PlayerProperties spectator); + void spectatorRemoved(int spectatorId, ServerInfo_User spectator); + void gameStarted(bool resuming); + void gameStopped(); + void activePhaseChanged(int activePhase); + void activePlayerChanged(int playerId); + +public slots: + void incrementGameTime(); + void setGameTime(int _secondsElapsed); + +private: + QTimer *gameTimer; + int secondsElapsed; + int hostId; + int localPlayerId; + const bool isLocalGame; + QMap players; + QMap spectators; + QList clients; + bool spectator; + bool judge; + bool gameStateKnown; + bool resuming; + QStringList phasesList; + int currentPhase; + int activePlayer; + bool gameClosed; +}; + +#endif // COCKATRICE_GAME_STATE_H diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 24075c3c1..25181a624 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -150,16 +150,19 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T stack = addZone(new StackZone(this, (int)table->boundingRect().height(), this)); - hand = addZone(new HandZone(this, _local || _judge || (_parent->isSpectator() && _parent->isSpectatorsOmniscient()), - (int)table->boundingRect().height(), this)); + hand = addZone( + new HandZone(this, + _local || _judge || + (_parent->getGameState()->isSpectator() && _parent->getGameMetaInfo()->spectatorsOmniscient()), + (int)table->boundingRect().height(), this)); connect(hand, &HandZone::cardCountChanged, handCounter, &HandCounter::updateNumber); connect(handCounter, &HandCounter::showContextMenu, hand, &HandZone::showContextMenu); updateBoundingRect(); if (local || judge) { - connect(_parent, &TabGame::playerAdded, this, &Player::addPlayer); - connect(_parent, &TabGame::playerRemoved, this, &Player::removePlayer); + connect(_parent->getGameState(), &GameState::playerAdded, this, &Player::addPlayer); + connect(_parent->getGameState(), &GameState::playerRemoved, this, &Player::removePlayer); } if (local || judge) { @@ -558,7 +561,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T connect(tempSetCounter, &QAction::triggered, this, &Player::actCardCounterTrigger); } - const QList &players = game->getPlayers().values(); + const QList &players = game->getGameState()->getPlayers().values(); for (const auto player : players) { addPlayer(player); } @@ -1015,7 +1018,7 @@ void Player::setShortcutsActive() // Don't enable always-active shortcuts in local games, since it causes keyboard shortcuts to work inconsistently // when there are more than 1 player. - if (!game->getIsLocalGame()) { + if (!game->getGameState()->getIsLocalGame()) { // unattach action is only active in card menu if the active card is attached. // make unattach shortcut always active so that it consistently works when multiple cards are selected. game->addAction(aUnattach); @@ -2341,7 +2344,7 @@ void Player::eventDelCounter(const Event_DelCounter &event) void Player::eventDumpZone(const Event_DumpZone &event) { - Player *zoneOwner = game->getPlayers().value(event.zone_owner_id(), 0); + Player *zoneOwner = game->getGameState()->getPlayers().value(event.zone_owner_id(), 0); if (!zoneOwner) { return; } @@ -2354,13 +2357,13 @@ void Player::eventDumpZone(const Event_DumpZone &event) void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext &context) { - Player *startPlayer = game->getPlayers().value(event.start_player_id()); + Player *startPlayer = game->getGameState()->getPlayers().value(event.start_player_id()); if (!startPlayer) { return; } QString startZoneString = QString::fromStdString(event.start_zone()); CardZone *startZone = startPlayer->getZones().value(startZoneString, 0); - Player *targetPlayer = game->getPlayers().value(event.target_player_id()); + Player *targetPlayer = game->getGameState()->getPlayers().value(event.target_player_id()); if (!targetPlayer) { return; } @@ -2433,7 +2436,7 @@ void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext & // Look at all arrows from and to the card. // If the card was moved to another zone, delete the arrows, otherwise update them. - QMapIterator playerIterator(game->getPlayers()); + QMapIterator playerIterator(game->getGameState()->getPlayers()); while (playerIterator.hasNext()) { Player *p = playerIterator.next().value(); @@ -2507,7 +2510,7 @@ void Player::eventDestroyCard(const Event_DestroyCard &event) void Player::eventAttachCard(const Event_AttachCard &event) { - const QMap &playerList = game->getPlayers(); + const QMap &playerList = game->getGameState()->getPlayers(); Player *targetPlayer = nullptr; CardZone *targetZone = nullptr; CardItem *targetCard = nullptr; @@ -2586,7 +2589,7 @@ void Player::eventRevealCards(const Event_RevealCards &event, EventProcessingOpt } Player *otherPlayer = nullptr; if (event.has_other_player_id()) { - otherPlayer = game->getPlayers().value(event.other_player_id()); + otherPlayer = game->getGameState()->getPlayers().value(event.other_player_id()); if (!otherPlayer) { return; } @@ -2790,7 +2793,9 @@ void Player::processPlayerInfo(const ServerInfo_Player &info) switch (zoneInfo.type()) { case ServerInfo_Zone::PrivateZone: - contentsKnown = local || judge || (game->isSpectator() && game->isSpectatorsOmniscient()); + contentsKnown = + local || judge || + (game->getGameState()->isSpectator() && game->getGameMetaInfo()->spectatorsOmniscient()); break; case ServerInfo_Zone::PublicZone: @@ -3084,7 +3089,7 @@ void Player::incrementAllCardCounters() ArrowItem *Player::addArrow(const ServerInfo_Arrow &arrow) { - const QMap &playerList = game->getPlayers(); + const QMap &playerList = game->getGameState()->getPlayers(); Player *startPlayer = playerList.value(arrow.start_player_id(), 0); Player *targetPlayer = playerList.value(arrow.target_player_id(), 0); if (!startPlayer || !targetPlayer) { @@ -3178,9 +3183,9 @@ PendingCommand *Player::prepareGameCommand(const google::protobuf::Message &cmd) GameCommand *c = base.add_game_command(); base.set_target_id(id); c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); - return game->prepareGameCommand(base); + return game->getGameEventHandler()->prepareGameCommand(base); } else { - return game->prepareGameCommand(cmd); + return game->getGameEventHandler()->prepareGameCommand(cmd); } } @@ -3196,9 +3201,9 @@ PendingCommand *Player::prepareGameCommand(const QListCopyFrom(*cmdList[i]); delete cmdList[i]; } - return game->prepareGameCommand(base); + return game->getGameEventHandler()->prepareGameCommand(base); } else { - return game->prepareGameCommand(cmdList); + return game->getGameEventHandler()->prepareGameCommand(cmdList); } } @@ -3209,15 +3214,15 @@ void Player::sendGameCommand(const google::protobuf::Message &command) GameCommand *c = base.add_game_command(); base.set_target_id(id); c->GetReflection()->MutableMessage(c, command.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(command); - game->sendGameCommand(base, id); + game->getGameEventHandler()->sendGameCommand(base, id); } else { - game->sendGameCommand(command, id); + game->getGameEventHandler()->sendGameCommand(command, id); } } void Player::sendGameCommand(PendingCommand *pend) { - game->sendGameCommand(pend, id); + game->getGameEventHandler()->sendGameCommand(pend, id); } bool Player::clearCardsToDelete() @@ -3284,7 +3289,7 @@ void Player::actMoveCardXCardsFromTop() if (local) { sendGameCommand(prepareGameCommand(commandList)); } else { - game->sendGameCommand(prepareGameCommand(commandList)); + game->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList)); } } @@ -3470,7 +3475,7 @@ void Player::cardMenuAction() if (local) { sendGameCommand(prepareGameCommand(commandList)); } else { - game->sendGameCommand(prepareGameCommand(commandList)); + game->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList)); } } @@ -3505,7 +3510,7 @@ void Player::actIncPT(int deltaP, int deltaT) } } - game->sendGameCommand(prepareGameCommand(commandList), playerid); + game->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid); } void Player::actResetPT() @@ -3538,7 +3543,7 @@ void Player::actResetPT() } if (!commandList.empty()) { - game->sendGameCommand(prepareGameCommand(commandList), playerid); + game->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid); } } @@ -3632,7 +3637,7 @@ void Player::actSetPT() } } - game->sendGameCommand(prepareGameCommand(commandList), playerid); + game->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid); } void Player::actDrawArrow() @@ -4045,6 +4050,7 @@ QMenu *Player::createCardMenu(const CardItem *card) QMenu *revealMenu = cardMenu->addMenu(tr("Re&veal to...")); initContextualPlayersMenu(revealMenu); + connect(revealMenu, &QMenu::triggered, this, &Player::actReveal); cardMenu->addSeparator(); @@ -4243,7 +4249,7 @@ QMenu *Player::updateCardMenu(const CardItem *card) // If is spectator (as spectators don't need card menus), return // only update the menu if the card is actually selected - if ((game->isSpectator() && !judge) || game->getActiveCard() != card) { + if ((game->getGameState()->isSpectator() && !judge) || game->getActiveCard() != card) { return nullptr; } diff --git a/cockatrice/src/game/player/player_list_widget.h b/cockatrice/src/game/player/player_list_widget.h index a0714bc4d..4cef51998 100644 --- a/cockatrice/src/game/player/player_list_widget.h +++ b/cockatrice/src/game/player/player_list_widget.h @@ -1,6 +1,8 @@ #ifndef PLAYERLISTWIDGET_H #define PLAYERLISTWIDGET_H +#include "player.h" + #include #include #include @@ -47,12 +49,14 @@ signals: public: PlayerListWidget(TabSupervisor *_tabSupervisor, AbstractClient *_client, TabGame *_game, QWidget *parent = nullptr); void retranslateUi(); - void addPlayer(const ServerInfo_PlayerProperties &player); - void removePlayer(int playerId); void setActivePlayer(int playerId); - void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1); void setGameStarted(bool _gameStarted, bool resuming); void showContextMenu(const QPoint &pos, const QModelIndex &index); + +public slots: + void addPlayer(const ServerInfo_PlayerProperties &player); + void removePlayer(int playerId); + void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1); }; #endif diff --git a/cockatrice/src/server/message_log_widget.cpp b/cockatrice/src/server/message_log_widget.cpp index 31edd4f52..b29a68671 100644 --- a/cockatrice/src/server/message_log_widget.cpp +++ b/cockatrice/src/server/message_log_widget.cpp @@ -391,6 +391,11 @@ void MessageLogWidget::logGameStart() appendHtmlServerMessage(tr("The game has started.")); } +void MessageLogWidget::logGameFlooded() +{ + appendMessage(tr("You are flooding the game. Please wait a couple of seconds.")); +} + void MessageLogWidget::logJoin(Player *player) { soundEngine->playSound("player_join"); diff --git a/cockatrice/src/server/message_log_widget.h b/cockatrice/src/server/message_log_widget.h index 21b03dee9..12820abad 100644 --- a/cockatrice/src/server/message_log_widget.h +++ b/cockatrice/src/server/message_log_widget.h @@ -49,6 +49,7 @@ public slots: void logFlipCard(Player *player, QString cardName, bool faceDown); void logGameClosed(); void logGameStart(); + void logGameFlooded(); void logJoin(Player *player); void logJoinSpectator(QString name); void logKicked(); diff --git a/cockatrice/src/server/user/user_context_menu.cpp b/cockatrice/src/server/user/user_context_menu.cpp index 544590828..457cc3dd5 100644 --- a/cockatrice/src/server/user/user_context_menu.cpp +++ b/cockatrice/src/server/user/user_context_menu.cpp @@ -380,7 +380,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos, aRemoveMessages = new QAction(tr("Remove this user's messages"), this); menu->addAction(aRemoveMessages); } - if (game && (game->isHost() || !tabSupervisor->getAdminLocked())) { + if (game && (game->getGameState()->isHost() || !tabSupervisor->getAdminLocked())) { menu->addSeparator(); menu->addAction(aKick); } @@ -476,7 +476,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos, Command_KickFromGame cmd; cmd.set_player_id(playerId); - game->sendGameCommand(cmd); + game->getGameEventHandler()->sendGameCommand(cmd); } else if (actionClicked == aBan) { Command_GetUserInfo cmd; cmd.set_user_name(userName.toStdString());