Cockatrice/cockatrice/src/interface/widgets/tabs/tab_game.cpp
BruebachL a9284596a8
Update cockatrice/src/interface/widgets/tabs/tab_game.cpp
Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2026-01-24 12:51:20 +01:00

1553 lines
58 KiB
C++

#include "tab_game.h"
#include "../../../client/settings/cache_settings.h"
#include "../game/board/arrow_item.h"
#include "../game/board/card_item.h"
#include "../game/deckview/deck_view_container.h"
#include "../game/deckview/tabbed_deck_view_container.h"
#include "../game/game.h"
#include "../game/game_scene.h"
#include "../game/game_view.h"
#include "../game/log/message_log_widget.h"
#include "../game/phases_toolbar.h"
#include "../game/player/player.h"
#include "../game/player/player_list_widget.h"
#include "../game/replay.h"
#include "../interface/card_picture_loader/card_picture_loader.h"
#include "../interface/widgets/cards/card_info_frame_widget.h"
#include "../interface/widgets/dialogs/dlg_create_game.h"
#include "../interface/widgets/server/user/user_list_manager.h"
#include "../interface/widgets/utility/line_edit_completer.h"
#include "../interface/window_main.h"
#include "../main.h"
#include "../utility/visibility_change_listener.h"
#include "libcockatrice/utility/qt_utils.h"
#include "tab_supervisor.h"
#include <QAction>
#include <QCompleter>
#include <QDebug>
#include <QDockWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QStackedWidget>
#include <QTimer>
#include <QWidget>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/event_game_joined.pb.h>
#include <libcockatrice/protocol/pb/game_replay.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_player.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/utility/trice_limits.h>
TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
: Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr)
{
// THIS CTOR IS USED ON REPLAY
game = new Replay(this, _replay);
createCardInfoDock(true);
createPlayerListDock(true);
createMessageDock(true);
createPlayAreaWidget(true);
createDeckViewContainerWidget(true);
createReplayDock(_replay);
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
addDockWidget(Qt::BottomDockWidgetArea, replayDock);
mainWidget = new QStackedWidget(this);
mainWidget->addWidget(deckViewContainerWidget);
mainWidget->addWidget(gamePlayAreaWidget);
setCentralWidget(mainWidget);
createReplayMenuItems();
createViewMenuItems();
connectToGameState();
connectToPlayerManager();
connectToGameEventHandler();
connectPlayerListToGameEventHandler();
connectMessageLogToGameEventHandler();
retranslateUi();
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&TabGame::refreshShortcuts);
refreshShortcuts();
messageLog->logReplayStarted(game->getGameMetaInfo()->gameId());
QTimer::singleShot(0, this, &TabGame::loadLayout);
}
TabGame::TabGame(TabSupervisor *_tabSupervisor,
QList<AbstractClient *> &_clients,
const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes)
: Tab(_tabSupervisor), userListProxy(_tabSupervisor->getUserListManager())
{
// THIS CTOR IS USED ON GAMES
game = new Game(this, _clients, event, _roomGameTypes);
createCardInfoDock();
createPlayerListDock();
createMessageDock();
createPlayAreaWidget();
createDeckViewContainerWidget();
replayDock = nullptr;
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
mainWidget = new QStackedWidget(this);
mainWidget->addWidget(deckViewContainerWidget);
mainWidget->addWidget(gamePlayAreaWidget);
mainWidget->setContentsMargins(0, 0, 0, 0);
setCentralWidget(mainWidget);
createMenuItems();
createViewMenuItems();
connectToGameState();
connectToPlayerManager();
connectToGameEventHandler();
connectPlayerListToGameEventHandler();
connectMessageLogToGameEventHandler();
retranslateUi();
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&TabGame::refreshShortcuts);
refreshShortcuts();
// append game to rooms game list for others to see
for (int i = game->getGameMetaInfo()->gameTypesSize() - 1; i >= 0; i--)
gameTypes.append(game->getGameMetaInfo()->findRoomGameType(i));
QTimer::singleShot(0, this, &TabGame::loadLayout);
auto mainWindow = QtUtils::findParentOfType<QMainWindow>(this);
if (mainWindow) {
tutorialController = new TutorialController(mainWindow);
} else {
tutorialController = new TutorialController(this);
}
TutorialSequence lobbySequence;
TutorialStep introStep(deckViewContainerWidget, tr("Let's try this out."));
lobbySequence.addStep(introStep);
tutorialController->addSequence(lobbySequence);
}
void TabGame::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
if (!tutorialStarted) {
tutorialStarted = true;
// Start on next event loop iteration so everything is fully painted
QTimer::singleShot(3, tutorialController, [this] { tutorialController->start(); });
}
}
void TabGame::finishTutorialInitialization()
{
if (tutorialInitialized) {
return;
} else {
tutorialInitialized = true;
}
auto deckViewSequence = deckViewContainers.first()->generateTutorialSequence();
tutorialController->addSequence(deckViewSequence);
TutorialSequence deckSelectSequence;
deckSelectSequence.name = tr("Deck selection and readying up");
TutorialStep loadDeckStep;
loadDeckStep.targetWidget = deckViewContainers.first();
loadDeckStep.text = tr("Let's load a deck now.");
loadDeckStep.allowClickThrough = true;
loadDeckStep.requiresInteraction = true;
loadDeckStep.autoAdvanceOnValid = true;
loadDeckStep.validationTiming = ValidationTiming::OnSignal;
loadDeckStep.signalSource = game->getGameEventHandler();
loadDeckStep.signalName = SIGNAL(logDeckSelect(Player *, QString, int));
loadDeckStep.validator = [] { return true; };
deckSelectSequence.addStep(loadDeckStep);
TutorialStep readyUpStep;
readyUpStep.targetWidget = deckViewContainers.first();
readyUpStep.text = tr("Let's ready up now.");
readyUpStep.allowClickThrough = true;
readyUpStep.requiresInteraction = true;
readyUpStep.autoAdvanceOnValid = true;
readyUpStep.validationTiming = ValidationTiming::OnSignal;
readyUpStep.signalSource = this;
readyUpStep.signalName = SIGNAL(localPlayerReadyStateChanged(bool));
readyUpStep.validator = [] { return true; };
deckSelectSequence.addStep(readyUpStep);
tutorialController->addSequence(deckSelectSequence);
TutorialSequence gamePlaySequence;
gamePlaySequence.name = tr("Gameplay");
gamePlaySequence.addStep(
{gamePlayAreaWidget,
tr("Welcome to your first game! It's just a singleplayer game for now to teach you the controls.")});
gamePlaySequence.addStep(
{gamePlayAreaWidget,
tr("Welcome to your first game! It's just a singleplayer game for now to teach you the controls.")});
gamePlaySequence.addStep(
{gamePlayAreaWidget,
tr("Unfortunately, due to the way the game tab works, we can't highlight any specific gameplay elements but "
"we're confident you'll be able to spot all the relevant elements on-screen.")});
gamePlaySequence.addStep(
{gamePlayAreaWidget,
tr("Let's go over them quickly, left-to-right.\n\nThe phase toolbar\nThe player area\nThe battlefield")});
gamePlaySequence.addStep(
{gamePlayAreaWidget,
tr("First up, is the phase toolbar. This toolbar shows the current phase of the turn. You can advance it by "
"pressing\n\n"
"- Tab (simply advances the phase)\n"
"- Ctrl+Space (advances the phase and performes any associated actions)\n"
"- Clicking directly on the phase you want to change to.\n\n"
"You can also pass the turn here, although, you should note that most players prefer you simply leave your "
"turn on the end step and allow them to 'take' the turn from you by pressing 'Next turn' themselves.")});
gamePlaySequence.addStep({gamePlayAreaWidget, tr("Next up, is your player area.\n\nHere you can find:\n\n- Your "
"avatar\n- Your life-counter\n- Various counters you can use to "
"track temporary resources (i.e. mana)\n- Your "
"library,\n- Your hand\n- Your graveyard\n- Your exile")});
gamePlaySequence.addStep(
{gamePlayAreaWidget,
tr("To the right of your player area, and taking up most of the screen, is your battlefield.\nThe relevant "
"zones here are, left-to-right, top-to-bottom:\n- The Stack\n- The Battlefield (Currently highlighted "
"because it is your turn)\n- Your Hand")});
gamePlaySequence.addStep(
{gamePlayAreaWidget,
tr("Before we dive any deeper into the actual controls, remember this:\n\nYou can perform almost "
"EVERY action by right-clicking the relevant object or zone!")});
gamePlaySequence.addStep(
{gamePlayAreaWidget, tr("However, there are shortcuts and conveniences to speed up your games and make your "
"life easier.\n\nLet's run through a typical game start now to get you up to speed.")});
TutorialStep lifeCounterStep;
lifeCounterStep.targetWidget = gamePlayAreaWidget;
lifeCounterStep.text =
tr("To control your life total, you can:\n\nSet it directly using Ctrl+L\nLeft-click the "
"number on your avatar to increment it.\nRight-click the number on your avatar to decrement "
"it.\nMiddle-click the number on your avatar to open up an interval menu up to +-10.");
lifeCounterStep.requiresInteraction = true;
lifeCounterStep.allowClickThrough = true;
lifeCounterStep.autoAdvanceOnValid = true;
lifeCounterStep.validationTiming = ValidationTiming::OnSignal;
lifeCounterStep.signalSource = game->getPlayerManager()
->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())
->getPlayerEventHandler();
lifeCounterStep.signalName = SIGNAL(logSetCounter(Player *, QString, int, int));
lifeCounterStep.validator = [this] {
auto counters =
game->getPlayerManager()->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())->getCounters();
for (auto counter : counters) {
if (counter->getName() == "life") {
return counter->getValue() == 10;
}
}
return false;
};
lifeCounterStep.validationHint = tr("Set your life total to 10 using any of these methods.");
gamePlaySequence.addStep(lifeCounterStep);
TutorialStep diceRollStep;
diceRollStep.targetWidget = gamePlayAreaWidget;
diceRollStep.text = tr("Fantastic! Let's roll a dice now. Many players use this to determine the initial turn "
"order.\nYou can right-click the battlefield and choose the menu "
"option or use the shortcut (Default Ctrl+I).");
diceRollStep.requiresInteraction = true;
diceRollStep.allowClickThrough = true;
diceRollStep.autoAdvanceOnValid = true;
diceRollStep.validationTiming = ValidationTiming::OnSignal;
diceRollStep.signalSource = game->getPlayerManager()
->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())
->getPlayerEventHandler();
diceRollStep.signalName = SIGNAL(logRollDie(Player *, int, const QList<uint> &));
diceRollStep.validator = [this] { return true; };
diceRollStep.validationHint = tr("Roll a dice using any of these methods.");
gamePlaySequence.addStep(diceRollStep);
TutorialStep mulliganStep;
mulliganStep.targetWidget = gamePlayAreaWidget;
mulliganStep.text =
tr("Alright, with that out of the way, we can get down to business:\n\nDrawing cards!\n\nTo draw your initial "
"hand:\n\n- Right-click your hand in the player area and select 'Take mulligan'\n-n Right-click your hand "
"zone on the battlefield and select 'Take mulligan'\n- Use the default shortcut (Ctrl+M)");
mulliganStep.requiresInteraction = true;
mulliganStep.allowClickThrough = true;
mulliganStep.autoAdvanceOnValid = true;
mulliganStep.validationTiming = ValidationTiming::OnSignal;
mulliganStep.signalSource = game->getPlayerManager()
->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())
->getPlayerEventHandler();
mulliganStep.signalName = SIGNAL(logDrawCards(Player *, int, bool));
mulliganStep.validator = [this] {
return game->getPlayerManager()
->getActiveLocalPlayer(game->getPlayerManager()->getLocalPlayerId())
->getHandZone()
->getCards()
.size() == 7;
};
mulliganStep.validationHint = tr("Mulligan to 7 cards using any of these methods.");
gamePlaySequence.addStep(mulliganStep);
gamePlaySequence.addStep({gamePlayAreaWidget, tr("")});
gamePlaySequence.addStep({gamePlayAreaWidget, tr("")});
gamePlaySequence.addStep({gamePlayAreaWidget, tr("")});
tutorialController->addSequence(gamePlaySequence);
}
void TabGame::connectToGameState()
{
connect(game->getGameState(), &GameState::gameStarted, this, &TabGame::startGame);
connect(game->getGameState(), &GameState::gameStopped, this, &TabGame::stopGame);
connect(game->getGameState(), &GameState::activePhaseChanged, this, &TabGame::setActivePhase);
connect(game->getGameState(), &GameState::activePlayerChanged, this, &TabGame::setActivePlayer);
}
void TabGame::connectToPlayerManager()
{
connect(game->getPlayerManager(), &PlayerManager::playerAdded, this, &TabGame::addPlayer);
connect(game->getPlayerManager(), &PlayerManager::playerRemoved, this, &TabGame::processPlayerLeave);
// update menu text when player concedes so that "concede" gets updated to "unconcede"
connect(game->getPlayerManager(), &PlayerManager::playerConceded, this, &TabGame::retranslateUi);
connect(game->getPlayerManager(), &PlayerManager::playerUnconceded, this, &TabGame::retranslateUi);
}
void TabGame::connectToGameEventHandler()
{
connect(this, &TabGame::gameLeft, game->getGameEventHandler(), &GameEventHandler::handleGameLeft);
connect(game->getGameEventHandler(), &GameEventHandler::emitUserEvent, this, &TabGame::emitUserEvent);
connect(game->getGameEventHandler(), &GameEventHandler::gameStopped, this, &TabGame::stopGame);
connect(game->getGameEventHandler(), &GameEventHandler::gameClosed, this, &TabGame::closeGame);
connect(game->getGameEventHandler(), &GameEventHandler::localPlayerReadyStateChanged, this,
&TabGame::processLocalPlayerReadyStateChanged);
connect(game->getGameEventHandler(), &GameEventHandler::localPlayerSideboardLocked, this,
&TabGame::processLocalPlayerSideboardLocked);
connect(game->getGameEventHandler(), &GameEventHandler::localPlayerDeckSelected, this,
&TabGame::processLocalPlayerDeckSelect);
connect(game->getGameEventHandler(), &GameEventHandler::remotePlayerDeckSelected, this,
&TabGame::processRemotePlayerDeckSelect);
connect(game->getGameEventHandler(), &GameEventHandler::remotePlayersDecksSelected, this,
&TabGame::processMultipleRemotePlayerDeckSelect);
}
void TabGame::connectMessageLogToGameEventHandler()
{
connect(game->getGameEventHandler(), &GameEventHandler::gameFlooded, messageLog, &MessageLogWidget::logGameFlooded);
connect(game->getGameEventHandler(), &GameEventHandler::containerProcessingStarted, messageLog,
&MessageLogWidget::containerProcessingStarted);
connect(game->getGameEventHandler(), &GameEventHandler::containerProcessingDone, messageLog,
&MessageLogWidget::containerProcessingDone);
connect(game->getGameEventHandler(), &GameEventHandler::setContextJudgeName, messageLog,
&MessageLogWidget::setContextJudgeName);
connect(game->getGameEventHandler(), &GameEventHandler::logSpectatorSay, messageLog,
&MessageLogWidget::logSpectatorSay);
connect(game->getGameEventHandler(), &GameEventHandler::logJoinPlayer, messageLog, &MessageLogWidget::logJoin);
connect(game->getGameEventHandler(), &GameEventHandler::logJoinSpectator, messageLog,
&MessageLogWidget::logJoinSpectator);
connect(game->getGameEventHandler(), &GameEventHandler::logLeave, messageLog, &MessageLogWidget::logLeave);
connect(game->getGameEventHandler(), &GameEventHandler::logKicked, messageLog, &MessageLogWidget::logKicked);
connect(game->getGameEventHandler(), &GameEventHandler::logConnectionStateChanged, messageLog,
&MessageLogWidget::logConnectionStateChanged);
connect(game->getGameEventHandler(), &GameEventHandler::logDeckSelect, messageLog,
&MessageLogWidget::logDeckSelect);
connect(game->getGameEventHandler(), &GameEventHandler::logSideboardLockSet, messageLog,
&MessageLogWidget::logSetSideboardLock);
connect(game->getGameEventHandler(), &GameEventHandler::logReadyStart, messageLog,
&MessageLogWidget::logReadyStart);
connect(game->getGameEventHandler(), &GameEventHandler::logNotReadyStart, messageLog,
&MessageLogWidget::logNotReadyStart);
connect(game->getGameEventHandler(), &GameEventHandler::logGameStart, messageLog, &MessageLogWidget::logGameStart);
connect(game->getGameEventHandler(), &GameEventHandler::logActivePlayer, messageLog,
&MessageLogWidget::logSetActivePlayer);
connect(game->getGameEventHandler(), &GameEventHandler::logActivePhaseChanged, messageLog,
&MessageLogWidget::logSetActivePhase);
connect(game->getGameEventHandler(), &GameEventHandler::logTurnReversed, messageLog,
&MessageLogWidget::logReverseTurn);
connect(game->getGameEventHandler(), &GameEventHandler::logConcede, messageLog, &MessageLogWidget::logConcede);
connect(game->getGameEventHandler(), &GameEventHandler::logUnconcede, messageLog, &MessageLogWidget::logUnconcede);
connect(game->getGameEventHandler(), &GameEventHandler::logGameClosed, messageLog,
&MessageLogWidget::logGameClosed);
}
void TabGame::connectPlayerListToGameEventHandler()
{
connect(game->getGameEventHandler(), &GameEventHandler::playerJoined, playerListWidget,
&PlayerListWidget::addPlayer);
connect(game->getGameEventHandler(), &GameEventHandler::playerLeft, playerListWidget,
&PlayerListWidget::removePlayer);
connect(game->getGameEventHandler(), &GameEventHandler::spectatorJoined, playerListWidget,
&PlayerListWidget::addPlayer);
connect(game->getGameEventHandler(), &GameEventHandler::spectatorLeft, playerListWidget,
&PlayerListWidget::removePlayer);
connect(game->getGameEventHandler(), &GameEventHandler::playerPropertiesChanged, playerListWidget,
&PlayerListWidget::updatePlayerProperties);
}
void TabGame::addMentionTag(const QString &value)
{
sayEdit->insert(value + " ");
sayEdit->setFocus();
}
void TabGame::linkCardToChat(const QString &cardName)
{
sayEdit->insert("[[" + cardName + "]] ");
sayEdit->setFocus();
}
void TabGame::resetChatAndPhase()
{
// reset chat log
messageLog->clearChat();
// reset phase markers
game->getGameState()->setCurrentPhase(-1);
}
void TabGame::emitUserEvent()
{
bool globalEvent =
!game->getPlayerManager()->isSpectator() || SettingsCache::instance().getSpectatorNotificationsEnabled();
emit userEvent(globalEvent);
updatePlayerListDockTitle();
}
TabGame::~TabGame()
{
if (replayManager) {
delete replayManager->replay;
}
}
void TabGame::updatePlayerListDockTitle()
{
QString type = replayDock ? tr("Replay") : tr("Game");
QString tabText = " | " + type + " #" + QString::number(game->getGameMetaInfo()->gameId());
QString userCountInfo =
QString(" %1/%2").arg(game->getPlayerManager()->getPlayerCount()).arg(game->getGameMetaInfo()->maxPlayers());
playerListDock->setWindowTitle(tr("Player List") + userCountInfo +
(playerListDock->isWindow() ? tabText : QString()));
}
void TabGame::retranslateUi()
{
QString type = replayDock ? tr("Replay") : tr("Game");
QString tabText = " | " + type + " #" + QString::number(game->getGameMetaInfo()->gameId());
updatePlayerListDockTitle();
cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString()));
messageLayoutDock->setWindowTitle(tr("Messages") + (messageLayoutDock->isWindow() ? tabText : QString()));
if (replayDock)
replayDock->setWindowTitle(tr("Replay Timeline") + (replayDock->isWindow() ? tabText : QString()));
if (phasesMenu) {
for (int i = 0; i < phaseActions.size(); ++i)
phaseActions[i]->setText(phasesToolbar->getLongPhaseName(i));
phasesMenu->setTitle(tr("&Phases"));
}
gameMenu->setTitle(tr("&Game"));
if (aNextPhase) {
aNextPhase->setText(tr("Next &phase"));
}
if (aNextPhaseAction) {
aNextPhaseAction->setText(tr("Next phase with &action"));
}
if (aNextTurn) {
aNextTurn->setText(tr("Next &turn"));
}
if (aReverseTurn) {
aReverseTurn->setText(tr("Reverse turn order"));
}
if (aRemoveLocalArrows) {
aRemoveLocalArrows->setText(tr("&Remove all local arrows"));
}
if (aRotateViewCW) {
aRotateViewCW->setText(tr("Rotate View Cl&ockwise"));
}
if (aRotateViewCCW) {
aRotateViewCCW->setText(tr("Rotate View Co&unterclockwise"));
}
if (aGameInfo)
aGameInfo->setText(tr("Game &information"));
if (aConcede) {
if (game->getPlayerManager()->isMainPlayerConceded()) {
aConcede->setText(tr("Un&concede"));
} else {
aConcede->setText(tr("&Concede"));
}
}
if (aLeaveGame) {
if (replayDock) {
aLeaveGame->setText(tr("C&lose replay"));
} else {
aLeaveGame->setText(tr("&Leave game"));
}
}
if (aFocusChat) {
aFocusChat->setText(tr("&Focus Chat"));
}
if (sayLabel) {
sayLabel->setText(tr("&Say:"));
}
if (aCardMenu) {
aCardMenu->setText(tr("Selected cards"));
}
viewMenu->setTitle(tr("&View"));
dockToActions[cardInfoDock].menu->setTitle(tr("Card Info"));
dockToActions[messageLayoutDock].menu->setTitle(tr("Messages"));
dockToActions[playerListDock].menu->setTitle(tr("Player List"));
if (replayDock) {
dockToActions[replayDock].menu->setTitle(tr("Replay Timeline"));
}
for (auto &actions : dockToActions.values()) {
actions.aVisible->setText(tr("Visible"));
actions.aFloating->setText(tr("Floating"));
}
aResetLayout->setText(tr("Reset layout"));
cardInfoFrameWidget->retranslateUi();
QMapIterator<int, Player *> i(game->getPlayerManager()->getPlayers());
while (i.hasNext())
i.next().value()->getGraphicsItem()->retranslateUi();
QMapIterator<int, TabbedDeckViewContainer *> j(deckViewContainers);
while (j.hasNext())
j.next().value()->playerDeckView->retranslateUi();
scene->retranslateUi();
}
void TabGame::refreshShortcuts()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
for (int i = 0; i < phaseActions.size(); ++i) {
QAction *temp = phaseActions.at(i);
switch (i) {
case 0:
temp->setShortcuts(shortcuts.getShortcut("Player/phase0"));
break;
case 1:
temp->setShortcuts(shortcuts.getShortcut("Player/phase1"));
break;
case 2:
temp->setShortcuts(shortcuts.getShortcut("Player/phase2"));
break;
case 3:
temp->setShortcuts(shortcuts.getShortcut("Player/phase3"));
break;
case 4:
temp->setShortcuts(shortcuts.getShortcut("Player/phase4"));
break;
case 5:
temp->setShortcuts(shortcuts.getShortcut("Player/phase5"));
break;
case 6:
temp->setShortcuts(shortcuts.getShortcut("Player/phase6"));
break;
case 7:
temp->setShortcuts(shortcuts.getShortcut("Player/phase7"));
break;
case 8:
temp->setShortcuts(shortcuts.getShortcut("Player/phase8"));
break;
case 9:
temp->setShortcuts(shortcuts.getShortcut("Player/phase9"));
break;
case 10:
temp->setShortcuts(shortcuts.getShortcut("Player/phase10"));
break;
default:;
}
}
if (aNextPhase) {
aNextPhase->setShortcuts(shortcuts.getShortcut("Player/aNextPhase"));
}
if (aNextPhaseAction) {
aNextPhaseAction->setShortcuts(shortcuts.getShortcut("Player/aNextPhaseAction"));
}
if (aNextTurn) {
aNextTurn->setShortcuts(shortcuts.getShortcut("Player/aNextTurn"));
}
if (aReverseTurn) {
aReverseTurn->setShortcuts(shortcuts.getShortcut("Player/aReverseTurn"));
}
if (aRemoveLocalArrows) {
aRemoveLocalArrows->setShortcuts(shortcuts.getShortcut("Player/aRemoveLocalArrows"));
}
if (aRotateViewCW) {
aRotateViewCW->setShortcuts(shortcuts.getShortcut("Player/aRotateViewCW"));
}
if (aRotateViewCCW) {
aRotateViewCCW->setShortcuts(shortcuts.getShortcut("Player/aRotateViewCCW"));
}
if (aConcede) {
aConcede->setShortcuts(shortcuts.getShortcut("Player/aConcede"));
}
if (aLeaveGame) {
aLeaveGame->setShortcuts(shortcuts.getShortcut("Player/aLeaveGame"));
}
if (aResetLayout) {
aResetLayout->setShortcuts(shortcuts.getShortcut("Player/aResetLayout"));
}
if (aFocusChat) {
aFocusChat->setShortcuts(shortcuts.getShortcut("Player/aFocusChat"));
}
}
bool TabGame::closeRequest()
{
if (!leaveGame()) {
return false;
}
return close();
}
void TabGame::closeEvent(QCloseEvent *event)
{
emit gameClosing(this);
event->accept();
}
void TabGame::updateTimeElapsedLabel(const QString newTime)
{
timeElapsedLabel->setText(newTime);
}
void TabGame::adminLockChanged(bool lock)
{
bool v = !(game->getPlayerManager()->isSpectator() && !game->getGameMetaInfo()->spectatorsCanChat() && lock);
sayLabel->setVisible(v);
sayEdit->setVisible(v);
}
void TabGame::actGameInfo()
{
DlgCreateGame dlg(game->getGameMetaInfo()->proto(), game->getGameMetaInfo()->getRoomGameTypes(), this);
dlg.exec();
}
void TabGame::actConcede()
{
Player *player = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
if (player == nullptr)
return;
if (!player->getConceded()) {
if (QMessageBox::question(this, tr("Concede"), tr("Are you sure you want to concede this game?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes)
return;
emit game->getPlayerManager()->activeLocalPlayerConceded();
player->setConceded(true);
} 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;
emit game->getPlayerManager()->activeLocalPlayerUnconceded();
player->setConceded(false);
}
}
/**
* Confirms the leave game and sends the leave game command, if applicable.
*
* @return True if the leave game is confirmed
*/
bool TabGame::leaveGame()
{
if (!game->getGameState()->isGameClosed()) {
if (!game->getPlayerManager()->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)
return false;
}
if (!replayDock)
emit gameLeft();
}
return true;
}
void TabGame::actSay()
{
if (completer->popup()->isVisible())
return;
if (sayEdit->text().startsWith("/card ")) {
cardInfoFrameWidget->setCard(sayEdit->text().mid(6));
sayEdit->clear();
return;
}
if (!sayEdit->text().isEmpty()) {
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 = "@" + game->getPlayerManager()->getSpectatorName(spectatorId);
removePlayerFromAutoCompleteList(playerName);
}
void TabGame::actPhaseAction()
{
int phase = phaseActions.indexOf(static_cast<QAction *>(sender()));
emit phaseChanged(phase);
}
void TabGame::actNextPhase()
{
int phase = game->getGameState()->getCurrentPhase();
if (++phase >= phasesToolbar->phaseCount())
phase = 0;
emit phaseChanged(phase);
}
void TabGame::actNextPhaseAction()
{
int phase = game->getGameState()->getCurrentPhase() + 1;
if (phase >= phasesToolbar->phaseCount()) {
phase = 0;
}
if (phase == 0) {
emit turnAdvanced();
} else {
emit phaseChanged(phase);
}
phasesToolbar->triggerPhaseAction(phase);
}
void TabGame::actRemoveLocalArrows()
{
QMapIterator<int, Player *> playerIterator(game->getPlayerManager()->getPlayers());
while (playerIterator.hasNext()) {
Player *player = playerIterator.next().value();
if (!player->getPlayerInfo()->getLocal())
continue;
QMapIterator<int, ArrowItem *> arrowIterator(player->getArrows());
while (arrowIterator.hasNext()) {
ArrowItem *a = arrowIterator.next().value();
emit arrowDeletionRequested(a->getId());
}
}
}
void TabGame::actRotateViewCW()
{
scene->adjustPlayerRotation(-1);
}
void TabGame::actRotateViewCCW()
{
scene->adjustPlayerRotation(1);
}
void TabGame::actCompleterChanged()
{
SettingsCache::instance().getChatMentionCompleter() ? completer->setCompletionRole(2)
: completer->setCompletionRole(1);
}
void TabGame::notifyPlayerJoin(QString playerName)
{
if (trayIcon) {
QString gameId(QString::number(game->getGameMetaInfo()->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();
}
Player *TabGame::addPlayer(Player *newPlayer)
{
QString newPlayerName = "@" + newPlayer->getPlayerInfo()->getName();
addPlayerToAutoCompleteList(newPlayerName);
scene->addPlayer(newPlayer);
connect(newPlayer, &Player::newCardAdded, this, &TabGame::newCardAdded);
connect(newPlayer->getPlayerMenu(), &PlayerMenu::cardMenuUpdated, this, &TabGame::setCardMenu);
messageLog->connectToPlayerEventHandler(newPlayer->getPlayerEventHandler());
if (game->getGameState()->getIsLocalGame() ||
(game->getPlayerManager()->isLocalPlayer(newPlayer->getPlayerInfo()->getId()) &&
!game->getPlayerManager()->isSpectator())) {
if (game->getGameState()->getIsLocalGame()) {
newPlayer->getPlayerInfo()->setLocal(true);
}
addLocalPlayer(newPlayer, newPlayer->getPlayerInfo()->getId());
}
gameMenu->insertMenu(playersSeparator, newPlayer->getPlayerMenu()->getPlayerMenu());
createZoneForPlayer(newPlayer, newPlayer->getPlayerInfo()->getId());
return newPlayer;
}
void TabGame::addLocalPlayer(Player *newPlayer, int playerId)
{
if (game->getGameState()->getClients().size() == 1) {
newPlayer->getPlayerMenu()->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->getPlayerInfo()->getName());
if (!deckPath.isEmpty()) {
QTimer::singleShot(0, this, [deckView, deckPath] {
deckView->playerDeckView->loadDeckFromFile(deckPath);
deckView->playerDeckView->readyAndUpdate();
});
}
finishTutorialInitialization();
}
void TabGame::processPlayerLeave(Player *leavingPlayer)
{
QString playerName = "@" + leavingPlayer->getPlayerInfo()->getName();
removePlayerFromAutoCompleteList(playerName);
scene->removePlayer(leavingPlayer);
// When we inserted the playerMenu into the gameMenu earlier, Qt wrapped the playerMenu into a QAction*, which lives
// independently and does not get cleaned up when the source menu gets destroyed. We have to manually clean here.
if (leavingPlayer->getPlayerMenu()) {
QMenu *menu = leavingPlayer->getPlayerMenu()->getPlayerMenu();
if (menu) {
// Find and remove the QAction pointing to this menu
QList<QAction *> actions = gameMenu->actions();
for (QAction *act : actions) {
if (act->menu() == menu) {
gameMenu->removeAction(act);
delete act; // deletes the QAction wrapper around the submenu
break;
}
}
}
}
}
void TabGame::processRemotePlayerDeckSelect(QString deckList, int playerId, QString playerName)
{
DeckList loader;
loader.loadFromString_Native(deckList);
QMapIterator<int, TabbedDeckViewContainer *> i(deckViewContainers);
while (i.hasNext()) {
i.next();
i.value()->addOpponentDeckView(loader, playerId, playerName);
}
}
void TabGame::processMultipleRemotePlayerDeckSelect(QVector<QPair<int, QPair<QString, QString>>> 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()) {
DeckList deckList = DeckList(QString::fromStdString(playerInfo.deck_list()));
CardPictureLoader::cacheCardPixmaps(CardDatabaseManager::query()->getCards(deckList.getCardRefList()));
deckViewContainer->playerDeckView->setDeck(deckList);
localPlayer->setDeck(deckList);
emit localPlayerDeckSelected();
}
}
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);
emit localPlayerReadyStateChanged(ready);
}
void TabGame::createZoneForPlayer(Player *newPlayer, int playerId)
{
if (!game->getPlayerManager()->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 <= game->getPlayerManager()->getPlayerCount(); ++i) {
bool aPlayerHasThisZone = false;
for (auto &player : game->getPlayerManager()->getPlayers()) {
if (player->getZoneId() == i) {
aPlayerHasThisZone = true;
break;
}
}
if (!aPlayerHasThisZone) {
newPlayer->setZoneId(i);
break;
}
}
}
}
void TabGame::startGame(bool _resuming)
{
game->getGameState()->setCurrentPhase(-1);
QMapIterator<int, TabbedDeckViewContainer *> i(deckViewContainers);
while (i.hasNext()) {
i.next();
i.value()->playerDeckView->setReadyStart(false);
i.value()->playerDeckView->setVisualDeckStorageExists(false);
i.value()->hide();
}
mainWidget->setCurrentWidget(gamePlayAreaWidget);
if (!_resuming) {
QMapIterator<int, Player *> playerIterator(game->getPlayerManager()->getPlayers());
while (playerIterator.hasNext())
playerIterator.next().value()->setGameStarted();
}
playerListWidget->setGameStarted(true, game->getGameState()->isResuming());
game->getGameMetaInfo()->setStarted(true);
static_cast<GameScene *>(gameView->scene())->rearrange();
if (aConcede != nullptr) {
aConcede->setText(tr("&Concede"));
aConcede->setEnabled(true);
}
}
void TabGame::stopGame()
{
QMapIterator<int, TabbedDeckViewContainer *> i(deckViewContainers);
while (i.hasNext()) {
i.next();
i.value()->show();
}
mainWidget->setCurrentWidget(deckViewContainerWidget);
playerListWidget->setActivePlayer(-1);
playerListWidget->setGameStarted(false, false);
scene->clearViews();
if (aConcede != nullptr) {
aConcede->setText(tr("&Concede"));
aConcede->setEnabled(false);
}
}
void TabGame::closeGame()
{
gameMenu->clear();
gameMenu->addAction(aLeaveGame);
}
Player *TabGame::setActivePlayer(int id)
{
Player *player = game->getPlayerManager()->getPlayer(id);
if (!player)
return nullptr;
playerListWidget->setActivePlayer(id);
QMapIterator<int, Player *> i(game->getPlayerManager()->getPlayers());
while (i.hasNext()) {
i.next();
if (i.value() == player) {
i.value()->setActive(true);
if (game->getGameState()->getClients().size() > 1) {
i.value()->getPlayerMenu()->setShortcutsActive();
}
} else {
i.value()->setActive(false);
if (game->getGameState()->getClients().size() > 1) {
i.value()->getPlayerMenu()->setShortcutsInactive();
}
}
}
game->getGameState()->setCurrentPhase(-1);
emitUserEvent();
return player;
}
void TabGame::setActivePhase(int phase)
{
phasesToolbar->setActivePhase(phase);
}
void TabGame::newCardAdded(AbstractCardItem *card)
{
connect(card, &AbstractCardItem::hovered, cardInfoFrameWidget,
qOverload<AbstractCardItem *>(&CardInfoFrameWidget::setCard));
connect(card, &AbstractCardItem::showCardInfoPopup, this, &TabGame::showCardInfoPopup);
connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));
connect(card, &AbstractCardItem::cardShiftClicked, this, &TabGame::linkCardToChat);
}
QString TabGame::getTabText() const
{
QString gameTypeInfo;
if (!gameTypes.empty()) {
gameTypeInfo = gameTypes.at(0);
if (gameTypes.size() > 1)
gameTypeInfo.append("...");
}
QString gameDesc(game->getGameMetaInfo()->description());
QString gameId(QString::number(game->getGameMetaInfo()->gameId()));
QString tabText;
if (replayDock)
tabText.append(tr("Replay") + " ");
if (!gameTypeInfo.isEmpty())
tabText.append(gameTypeInfo + " ");
if (!gameDesc.isEmpty()) {
if (gameDesc.length() >= 15)
tabText.append("| " + gameDesc.left(15) + "... ");
else
tabText.append("| " + gameDesc + " ");
}
if (!tabText.isEmpty())
tabText.append("| ");
tabText.append("#" + gameId);
return tabText;
}
/**
* @param menu The menu to set. Pass in nullptr to set the menu to empty.
*/
void TabGame::setCardMenu(QMenu *menu)
{
if (!aCardMenu) {
return;
}
if (menu) {
aCardMenu->setMenu(menu);
} else {
aCardMenu->setMenu(new QMenu);
}
}
void TabGame::createMenuItems()
{
aNextPhase = new QAction(this);
connect(aNextPhase, &QAction::triggered, this, &TabGame::actNextPhase);
connect(this, &TabGame::phaseChanged, game->getGameEventHandler(), &GameEventHandler::handleActivePhaseChanged);
aNextPhaseAction = new QAction(this);
connect(aNextPhaseAction, &QAction::triggered, this, &TabGame::actNextPhaseAction);
connect(this, &TabGame::turnAdvanced, game->getGameEventHandler(), &GameEventHandler::handleNextTurn);
aNextTurn = new QAction(this);
connect(aNextTurn, &QAction::triggered, game->getGameEventHandler(), &GameEventHandler::handleNextTurn);
aReverseTurn = new QAction(this);
connect(aReverseTurn, &QAction::triggered, game->getGameEventHandler(), &GameEventHandler::handleReverseTurn);
aRemoveLocalArrows = new QAction(this);
connect(aRemoveLocalArrows, &QAction::triggered, this, &TabGame::actRemoveLocalArrows);
connect(this, &TabGame::arrowDeletionRequested, game->getGameEventHandler(),
&GameEventHandler::handleArrowDeletion);
aRotateViewCW = new QAction(this);
connect(aRotateViewCW, &QAction::triggered, this, &TabGame::actRotateViewCW);
aRotateViewCCW = new QAction(this);
connect(aRotateViewCCW, &QAction::triggered, this, &TabGame::actRotateViewCCW);
aGameInfo = new QAction(this);
connect(aGameInfo, &QAction::triggered, this, &TabGame::actGameInfo);
aConcede = new QAction(this);
connect(aConcede, &QAction::triggered, this, &TabGame::actConcede);
if (!game->getGameMetaInfo()->started()) {
aConcede->setEnabled(false);
}
connect(game->getPlayerManager(), &PlayerManager::activeLocalPlayerConceded, game->getGameEventHandler(),
&GameEventHandler::handleActiveLocalPlayerConceded);
connect(game->getPlayerManager(), &PlayerManager::activeLocalPlayerUnconceded, game->getGameEventHandler(),
&GameEventHandler::handleActiveLocalPlayerUnconceded);
aLeaveGame = new QAction(this);
connect(aLeaveGame, &QAction::triggered, this, &TabGame::closeRequest);
aFocusChat = new QAction(this);
connect(aFocusChat, &QAction::triggered, sayEdit, qOverload<>(&LineEditCompleter::setFocus));
phasesMenu = new TearOffMenu(this);
for (int i = 0; i < phasesToolbar->phaseCount(); ++i) {
auto *temp = new QAction(QString(), this);
connect(temp, &QAction::triggered, this, &TabGame::actPhaseAction);
phasesMenu->addAction(temp);
phaseActions.append(temp);
}
phasesMenu->addSeparator();
phasesMenu->addAction(aNextPhase);
phasesMenu->addAction(aNextPhaseAction);
gameMenu = new QMenu(this);
playersSeparator = gameMenu->addSeparator();
gameMenu->addMenu(phasesMenu);
gameMenu->addAction(aNextTurn);
gameMenu->addAction(aReverseTurn);
gameMenu->addSeparator();
gameMenu->addAction(aRemoveLocalArrows);
gameMenu->addAction(aRotateViewCW);
gameMenu->addAction(aRotateViewCCW);
gameMenu->addSeparator();
gameMenu->addAction(aGameInfo);
gameMenu->addAction(aConcede);
gameMenu->addAction(aFocusChat);
gameMenu->addAction(aLeaveGame);
gameMenu->addSeparator();
aCardMenu = gameMenu->addMenu(new QMenu(this));
addTabMenu(gameMenu);
}
void TabGame::createReplayMenuItems()
{
aNextPhase = nullptr;
aNextPhaseAction = nullptr;
aNextTurn = nullptr;
aReverseTurn = nullptr;
aRemoveLocalArrows = nullptr;
aRotateViewCW = nullptr;
aRotateViewCCW = nullptr;
aResetLayout = nullptr;
aGameInfo = nullptr;
aConcede = nullptr;
aFocusChat = nullptr;
aLeaveGame = new QAction(this);
connect(aLeaveGame, &QAction::triggered, this, &TabGame::closeRequest);
phasesMenu = nullptr;
gameMenu = new QMenu(this);
gameMenu->addAction(aLeaveGame);
aCardMenu = nullptr;
addTabMenu(gameMenu);
}
void TabGame::createViewMenuItems()
{
viewMenu = new QMenu(this);
registerDockWidget(viewMenu, cardInfoDock);
registerDockWidget(viewMenu, messageLayoutDock);
registerDockWidget(viewMenu, playerListDock);
if (replayDock) {
registerDockWidget(viewMenu, replayDock);
}
viewMenu->addSeparator();
aResetLayout = viewMenu->addAction(QString());
connect(aResetLayout, &QAction::triggered, this, &TabGame::actResetLayout);
viewMenu->addAction(aResetLayout);
addTabMenu(viewMenu);
}
void TabGame::registerDockWidget(QMenu *_viewMenu, QDockWidget *widget)
{
QMenu *menu = _viewMenu->addMenu(QString());
QAction *aVisible = menu->addAction(QString());
aVisible->setCheckable(true);
QAction *aFloating = menu->addAction(QString());
aFloating->setCheckable(true);
aFloating->setEnabled(false);
// user interaction
connect(aVisible, &QAction::triggered, widget, [widget](bool checked) { widget->setVisible(checked); });
connect(aFloating, &QAction::triggered, this, [widget](bool checked) { widget->setFloating(checked); });
// sync aFloating's enabled state with aVisible's checked state
connect(aVisible, &QAction::toggled, aFloating, [aFloating](bool checked) { aFloating->setEnabled(checked); });
// sync aFloating with dockWidget's floating state
connect(widget, &QDockWidget::topLevelChanged, aFloating,
[aFloating](bool topLevel) { aFloating->setChecked(topLevel); });
// sync aVisible with dockWidget's visible state
auto filter = new VisibilityChangeListener(widget);
connect(filter, &VisibilityChangeListener::visibilityChanged, aVisible,
[aVisible](bool visible) { aVisible->setChecked(visible); });
dockToActions.insert(widget, {menu, aVisible, aFloating});
}
void TabGame::loadLayout()
{
LayoutsSettings &layouts = SettingsCache::instance().layouts();
if (replayDock) {
restoreGeometry(layouts.getReplayPlayAreaGeometry());
restoreState(layouts.getReplayPlayAreaLayoutState());
cardInfoDock->setMinimumSize(layouts.getReplayCardInfoSize());
cardInfoDock->setMaximumSize(layouts.getReplayCardInfoSize());
messageLayoutDock->setMinimumSize(layouts.getReplayMessageLayoutSize());
messageLayoutDock->setMaximumSize(layouts.getReplayMessageLayoutSize());
playerListDock->setMinimumSize(layouts.getReplayPlayerListSize());
playerListDock->setMaximumSize(layouts.getReplayPlayerListSize());
replayDock->setMinimumSize(layouts.getReplayReplaySize());
replayDock->setMaximumSize(layouts.getReplayReplaySize());
} else {
restoreGeometry(layouts.getGamePlayAreaGeometry());
restoreState(layouts.getGamePlayAreaLayoutState());
cardInfoDock->setMinimumSize(layouts.getGameCardInfoSize());
cardInfoDock->setMaximumSize(layouts.getGameCardInfoSize());
messageLayoutDock->setMinimumSize(layouts.getGameMessageLayoutSize());
messageLayoutDock->setMaximumSize(layouts.getGameMessageLayoutSize());
playerListDock->setMinimumSize(layouts.getGamePlayerListSize());
playerListDock->setMaximumSize(layouts.getGamePlayerListSize());
}
QTimer::singleShot(100, this, &TabGame::freeDocksSize);
}
void TabGame::freeDocksSize()
{
cardInfoDock->setMinimumSize(100, 100);
cardInfoDock->setMaximumSize(5000, 5000);
messageLayoutDock->setMinimumSize(100, 100);
messageLayoutDock->setMaximumSize(5000, 5000);
playerListDock->setMinimumSize(100, 100);
playerListDock->setMaximumSize(5000, 5000);
if (replayDock) {
replayDock->setMinimumSize(100, 100);
replayDock->setMaximumSize(5000, 5000);
}
}
void TabGame::actResetLayout()
{
cardInfoDock->setVisible(true);
playerListDock->setVisible(true);
messageLayoutDock->setVisible(true);
cardInfoDock->setFloating(false);
playerListDock->setFloating(false);
messageLayoutDock->setFloating(false);
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
if (replayDock) {
replayDock->setVisible(true);
replayDock->setFloating(false);
addDockWidget(Qt::BottomDockWidgetArea, replayDock);
cardInfoDock->setMinimumSize(250, 360);
cardInfoDock->setMaximumSize(250, 360);
messageLayoutDock->setMinimumSize(250, 200);
messageLayoutDock->setMaximumSize(250, 200);
playerListDock->setMinimumSize(250, 50);
playerListDock->setMaximumSize(250, 50);
replayDock->setMinimumSize(900, 100);
replayDock->setMaximumSize(900, 100);
} else {
cardInfoDock->setMinimumSize(250, 360);
cardInfoDock->setMaximumSize(250, 360);
messageLayoutDock->setMinimumSize(250, 250);
messageLayoutDock->setMaximumSize(250, 250);
playerListDock->setMinimumSize(250, 50);
playerListDock->setMaximumSize(250, 50);
}
QTimer::singleShot(100, this, &TabGame::freeDocksSize);
}
void TabGame::createPlayAreaWidget(bool bReplay)
{
phasesToolbar = new PhasesToolbar;
if (!bReplay)
connect(phasesToolbar, &PhasesToolbar::sendGameCommand, game->getGameEventHandler(),
qOverload<const ::google::protobuf::Message &, int>(&GameEventHandler::sendGameCommand));
scene = new GameScene(phasesToolbar, this);
connect(game->getPlayerManager(), &PlayerManager::playerConceded, scene, &GameScene::rearrange);
connect(game->getPlayerManager(), &PlayerManager::playerCountChanged, scene, &GameScene::rearrange);
gameView = new GameView(scene);
auto gamePlayAreaVBox = new QVBoxLayout;
gamePlayAreaVBox->setContentsMargins(0, 0, 0, 0);
gamePlayAreaVBox->addWidget(gameView);
gamePlayAreaWidget = new QWidget;
gamePlayAreaWidget->setObjectName("gamePlayAreaWidget");
gamePlayAreaWidget->setLayout(gamePlayAreaVBox);
}
void TabGame::createReplayDock(GameReplay *replay)
{
replayManager = new ReplayManager(this, replay);
replayDock = new QDockWidget(this);
replayDock->setObjectName("replayDock");
replayDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetMovable);
replayDock->setWidget(replayManager);
replayDock->setFloating(false);
}
void TabGame::createDeckViewContainerWidget(bool bReplay)
{
Q_UNUSED(bReplay);
deckViewContainerWidget = new QWidget();
deckViewContainerWidget->setObjectName("deckViewContainerWidget");
deckViewContainerLayout = new QVBoxLayout;
deckViewContainerLayout->setContentsMargins(0, 0, 0, 0);
deckViewContainerWidget->setLayout(deckViewContainerLayout);
}
void TabGame::viewCardInfo(const CardRef &cardRef) const
{
cardInfoFrameWidget->setCard(cardRef);
}
void TabGame::createCardInfoDock(bool bReplay)
{
Q_UNUSED(bReplay);
cardInfoFrameWidget = new CardInfoFrameWidget();
auto cardHInfoLayout = new QHBoxLayout;
auto cardVInfoLayout = new QVBoxLayout;
cardVInfoLayout->setContentsMargins(0, 0, 0, 0);
cardVInfoLayout->addWidget(cardInfoFrameWidget);
cardVInfoLayout->addLayout(cardHInfoLayout);
auto cardBoxLayoutWidget = new QWidget;
cardBoxLayoutWidget->setLayout(cardVInfoLayout);
cardInfoDock = new QDockWidget(this);
cardInfoDock->setObjectName("cardInfoDock");
cardInfoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetMovable);
cardInfoDock->setWidget(cardBoxLayoutWidget);
cardInfoDock->setFloating(false);
}
void TabGame::createPlayerListDock(bool bReplay)
{
if (bReplay) {
playerListWidget = new PlayerListWidget(nullptr, nullptr, game);
} else {
playerListWidget = new PlayerListWidget(tabSupervisor, game->getGameState()->getClients().first(), game);
connect(playerListWidget, SIGNAL(openMessageDialog(QString, bool)), this,
SIGNAL(openMessageDialog(QString, bool)));
}
playerListWidget->setFocusPolicy(Qt::NoFocus);
playerListDock = new QDockWidget(this);
playerListDock->setObjectName("playerListDock");
playerListDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetMovable);
playerListDock->setWidget(playerListWidget);
playerListDock->setFloating(false);
}
void TabGame::createMessageDock(bool bReplay)
{
auto messageLogLayout = new QVBoxLayout;
messageLogLayout->setContentsMargins(0, 0, 0, 0);
// clock
if (!bReplay) {
timeElapsedLabel = new QLabel;
timeElapsedLabel->setAlignment(Qt::AlignCenter);
connect(game->getGameState(), &GameState::updateTimeElapsedLabel, this, &TabGame::updateTimeElapsedLabel);
messageLogLayout->addWidget(timeElapsedLabel);
}
// message log
messageLog = new MessageLogWidget(tabSupervisor, game);
connect(messageLog, &MessageLogWidget::cardNameHovered, cardInfoFrameWidget,
qOverload<const QString &>(&CardInfoFrameWidget::setCard));
connect(messageLog, &MessageLogWidget::showCardInfoPopup, this, &TabGame::showCardInfoPopup);
connect(messageLog, &MessageLogWidget::deleteCardInfoPopup, this, &TabGame::deleteCardInfoPopup);
if (!bReplay) {
connect(messageLog, &MessageLogWidget::openMessageDialog, this, &TabGame::openMessageDialog);
connect(messageLog, &MessageLogWidget::addMentionTag, this, &TabGame::addMentionTag);
connect(&SettingsCache::instance(), &SettingsCache::chatMentionCompleterChanged, this,
&TabGame::actCompleterChanged);
}
messageLogLayout->addWidget(messageLog);
// chat entry
if (!bReplay) {
sayLabel = new QLabel;
sayEdit = new LineEditCompleter;
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
sayLabel->setBuddy(sayEdit);
connect(this, &TabGame::chatMessageSent, game->getGameEventHandler(), &GameEventHandler::handleChatMessageSent);
completer = new QCompleter(autocompleteUserList, sayEdit);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setMaxVisibleItems(5);
completer->setFilterMode(Qt::MatchStartsWith);
sayEdit->setCompleter(completer);
actCompleterChanged();
if (game->getPlayerManager()->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() || game->getPlayerManager()->isJudge();
if (!isModOrJudge && !game->getGameMetaInfo()->spectatorsCanChat()) {
sayLabel->hide();
sayEdit->hide();
}
}
connect(tabSupervisor, &TabSupervisor::adminLockChanged, this, &TabGame::adminLockChanged);
connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabGame::actSay);
auto sayHLayout = new QHBoxLayout;
sayHLayout->addWidget(sayLabel);
sayHLayout->addWidget(sayEdit);
messageLogLayout->addLayout(sayHLayout);
}
// dock
auto messageLogLayoutWidget = new QWidget;
messageLogLayoutWidget->setLayout(messageLogLayout);
messageLayoutDock = new QDockWidget(this);
messageLayoutDock->setObjectName("messageLayoutDock");
messageLayoutDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetMovable);
messageLayoutDock->setWidget(messageLogLayoutWidget);
messageLayoutDock->setFloating(false);
}
void TabGame::hideEvent(QHideEvent *event)
{
LayoutsSettings &layouts = SettingsCache::instance().layouts();
if (replayDock) {
layouts.setReplayPlayAreaState(saveState());
layouts.setReplayPlayAreaGeometry(saveGeometry());
layouts.setReplayCardInfoSize(cardInfoDock->size());
layouts.setReplayMessageLayoutSize(messageLayoutDock->size());
layouts.setReplayPlayerListSize(playerListDock->size());
layouts.setReplayReplaySize(replayDock->size());
} else {
layouts.setGamePlayAreaState(saveState());
layouts.setGamePlayAreaGeometry(saveGeometry());
layouts.setGameCardInfoSize(cardInfoDock->size());
layouts.setGameMessageLayoutSize(messageLayoutDock->size());
layouts.setGamePlayerListSize(playerListDock->size());
}
Tab::hideEvent(event);
}