diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index b70d29b1..30e15053 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -175,7 +175,8 @@ if (NOT QT_QTMULTIMEDIA_FOUND) endif (NOT QT_QTMULTIMEDIA_FOUND) FIND_PACKAGE(Protobuf REQUIRED) -set(CMAKE_BUILD_TYPE Release) +#set(CMAKE_BUILD_TYPE Release) +set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0") set(CMAKE_CXX_FLAGS_RELEASE "-s -O2") QT4_WRAP_CPP(cockatrice_HEADERS_MOC ${cockatrice_HEADERS}) diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 43a643f1..f69cd9a9 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -56,6 +56,12 @@ GeneralSettingsPage::GeneralSettingsPage() QPushButton *deckPathButton = new QPushButton("..."); connect(deckPathButton, SIGNAL(clicked()), this, SLOT(deckPathButtonClicked())); + replaysPathLabel = new QLabel; + replaysPathEdit = new QLineEdit(settingsCache->getReplaysPath()); + replaysPathEdit->setReadOnly(true); + QPushButton *replaysPathButton = new QPushButton("..."); + connect(replaysPathButton, SIGNAL(clicked()), this, SLOT(replaysPathButtonClicked())); + picsPathLabel = new QLabel; picsPathEdit = new QLineEdit(settingsCache->getPicsPath()); picsPathEdit->setReadOnly(true); @@ -72,12 +78,15 @@ GeneralSettingsPage::GeneralSettingsPage() pathsGrid->addWidget(deckPathLabel, 0, 0); pathsGrid->addWidget(deckPathEdit, 0, 1); pathsGrid->addWidget(deckPathButton, 0, 2); - pathsGrid->addWidget(picsPathLabel, 1, 0); - pathsGrid->addWidget(picsPathEdit, 1, 1); - pathsGrid->addWidget(picsPathButton, 1, 2); - pathsGrid->addWidget(cardDatabasePathLabel, 2, 0); - pathsGrid->addWidget(cardDatabasePathEdit, 2, 1); - pathsGrid->addWidget(cardDatabasePathButton, 2, 2); + pathsGrid->addWidget(replaysPathLabel, 1, 0); + pathsGrid->addWidget(replaysPathEdit, 1, 1); + pathsGrid->addWidget(replaysPathButton, 1, 2); + pathsGrid->addWidget(picsPathLabel, 2, 0); + pathsGrid->addWidget(picsPathEdit, 2, 1); + pathsGrid->addWidget(picsPathButton, 2, 2); + pathsGrid->addWidget(cardDatabasePathLabel, 3, 0); + pathsGrid->addWidget(cardDatabasePathEdit, 3, 1); + pathsGrid->addWidget(cardDatabasePathButton, 3, 2); pathsGroupBox = new QGroupBox; pathsGroupBox->setLayout(pathsGrid); @@ -114,6 +123,16 @@ void GeneralSettingsPage::deckPathButtonClicked() settingsCache->setDeckPath(path); } +void GeneralSettingsPage::replaysPathButtonClicked() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); + if (path.isEmpty()) + return; + + replaysPathEdit->setText(path); + settingsCache->setReplaysPath(path); +} + void GeneralSettingsPage::picsPathButtonClicked() { QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); @@ -146,6 +165,7 @@ void GeneralSettingsPage::retranslateUi() picDownloadCheckBox->setText(tr("Download card pictures on the fly")); pathsGroupBox->setTitle(tr("Paths")); deckPathLabel->setText(tr("Decks directory:")); + replaysPathLabel->setText(tr("Replays directory:")); picsPathLabel->setText(tr("Pictures directory:")); cardDatabasePathLabel->setText(tr("Path to card database:")); } diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index f7bd1a52..0ab474c0 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -28,22 +28,24 @@ public: void retranslateUi(); private slots: void deckPathButtonClicked(); + void replaysPathButtonClicked(); void picsPathButtonClicked(); void cardDatabasePathButtonClicked(); void languageBoxChanged(int index); signals: - void picsPathChanged(const QString &path); +/* void picsPathChanged(const QString &path); + void replaysPathChanged(const QString &path); void cardDatabasePathChanged(const QString &path); void changeLanguage(const QString &qmFile); void picDownloadChanged(int state); -private: +*/private: QStringList findQmFiles(); QString languageName(const QString &qmFile); - QLineEdit *deckPathEdit, *picsPathEdit, *cardDatabasePathEdit; + QLineEdit *deckPathEdit, *replaysPathEdit, *picsPathEdit, *cardDatabasePathEdit; QGroupBox *personalGroupBox, *pathsGroupBox; QComboBox *languageBox; QCheckBox *picDownloadCheckBox; - QLabel *languageLabel, *deckPathLabel, *picsPathLabel, *cardDatabasePathLabel; + QLabel *languageLabel, *deckPathLabel, *replaysPathLabel, *picsPathLabel, *cardDatabasePathLabel; }; class AppearanceSettingsPage : public AbstractSettingsPage { diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index d4abc371..e3ff900a 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -108,6 +108,10 @@ int main(int argc, char *argv[]) QDir().mkpath(dataDir + "/decks"); settingsCache->setDeckPath(dataDir + "/decks"); } + if (!QDir(settingsCache->getReplaysPath()).exists() || settingsCache->getReplaysPath().isEmpty()) { + QDir().mkpath(dataDir + "/replays"); + settingsCache->setReplaysPath(dataDir + "/replays"); + } if (!QDir(settingsCache->getPicsPath()).exists() || settingsCache->getPicsPath().isEmpty()) { QDir().mkpath(dataDir + "/pics"); settingsCache->setPicsPath(dataDir + "/pics"); diff --git a/cockatrice/src/messagelogwidget.cpp b/cockatrice/src/messagelogwidget.cpp index bcb6d853..41f0e2f1 100644 --- a/cockatrice/src/messagelogwidget.cpp +++ b/cockatrice/src/messagelogwidget.cpp @@ -29,6 +29,14 @@ void MessageLogWidget::logGameJoined(int gameId) appendHtml(tr("You have joined game #%1.", "male").arg(gameId)); } +void MessageLogWidget::logReplayStarted(int gameId) +{ + if (female) + appendHtml(tr("You are watching a replay of game #%1.", "female").arg(gameId)); + else + appendHtml(tr("You are watching a replay of game #%1.", "male").arg(gameId)); +} + void MessageLogWidget::logJoin(Player *player) { soundEngine->cuckoo(); diff --git a/cockatrice/src/messagelogwidget.h b/cockatrice/src/messagelogwidget.h index 0d5ac7ef..abbe9eb9 100644 --- a/cockatrice/src/messagelogwidget.h +++ b/cockatrice/src/messagelogwidget.h @@ -40,6 +40,7 @@ private: int mulliganNumber; public slots: void logGameJoined(int gameId); + void logReplayStarted(int gameId); void logJoin(Player *player); void logLeave(Player *player); void logGameClosed(); diff --git a/cockatrice/src/playerlistwidget.cpp b/cockatrice/src/playerlistwidget.cpp index d6e9be1d..f5a09a63 100644 --- a/cockatrice/src/playerlistwidget.cpp +++ b/cockatrice/src/playerlistwidget.cpp @@ -55,9 +55,11 @@ PlayerListWidget::PlayerListWidget(TabSupervisor *_tabSupervisor, AbstractClient concededIcon = QIcon(":/resources/icon_conceded.svg"); playerIcon = QIcon(":/resources/icon_player.svg"); spectatorIcon = QIcon(":/resources/icon_spectator.svg"); - - itemDelegate = new PlayerListItemDelegate(this); - setItemDelegate(itemDelegate); + + if (tabSupervisor) { + itemDelegate = new PlayerListItemDelegate(this); + setItemDelegate(itemDelegate); + } setMinimumHeight(60); setIconSize(QSize(20, 15)); diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 8c3cee47..191035b0 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -8,6 +8,7 @@ SettingsCache::SettingsCache() lang = settings->value("personal/lang").toString(); deckPath = settings->value("paths/decks").toString(); + replaysPath = settings->value("paths/replays").toString(); picsPath = settings->value("paths/pics").toString(); cardDatabasePath = settings->value("paths/carddatabase").toString(); @@ -49,6 +50,12 @@ void SettingsCache::setDeckPath(const QString &_deckPath) settings->setValue("paths/decks", deckPath); } +void SettingsCache::setReplaysPath(const QString &_replaysPath) +{ + replaysPath = _replaysPath; + settings->setValue("paths/replays", replaysPath); +} + void SettingsCache::setPicsPath(const QString &_picsPath) { picsPath = _picsPath; diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index b3f1ae6c..ac08f053 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -26,7 +26,7 @@ private: QSettings *settings; QString lang; - QString deckPath, picsPath, cardDatabasePath; + QString deckPath, replaysPath, picsPath, cardDatabasePath; QString handBgPath, stackBgPath, tableBgPath, playerBgPath, cardBackPicturePath; bool picDownload; bool doubleClickToPlay; @@ -45,6 +45,7 @@ public: SettingsCache(); QString getLang() const { return lang; } QString getDeckPath() const { return deckPath; } + QString getReplaysPath() const { return replaysPath; } QString getPicsPath() const { return picsPath; } QString getCardDatabasePath() const { return cardDatabasePath; } QString getHandBgPath() const { return handBgPath; } @@ -69,6 +70,7 @@ public: public slots: void setLang(const QString &_lang); void setDeckPath(const QString &_deckPath); + void setReplaysPath(const QString &_replaysPath); void setPicsPath(const QString &_picsPath); void setCardDatabasePath(const QString &_cardDatabasePath); void setHandBgPath(const QString &_handBgPath); diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index 9e2a7c2a..5e9ae917 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -29,6 +29,7 @@ #include #include "pending_command.h" +#include "pb/game_replay.pb.h" #include "pb/command_concede.pb.h" #include "pb/command_deck_select.pb.h" #include "pb/command_ready_start.pb.h" @@ -195,6 +196,85 @@ void DeckViewContainer::setDeck(DeckList *deck) readyStartButton->setEnabled(true); } +TabGame::TabGame(GameReplay *_replay) + : Tab(0), + hostId(-1), + localPlayerId(-1), + spectator(true), + spectatorsCanTalk(false), + spectatorsSeeEverything(true), + gameStateKnown(false), + started(false), + resuming(false), + currentPhase(-1), + replay(_replay), + currentReplayStep(0) +{ + gameId = replay->game_info().game_id(); + gameDescription = QString::fromStdString(replay->game_info().description()); + + phasesToolbar = new PhasesToolbar; + phasesToolbar->hide(); + + scene = new GameScene(phasesToolbar, this); + gameView = new GameView(scene); + gameView->hide(); + + cardInfo = new CardInfoWidget(CardInfoWidget::ModeGameTab); + playerListWidget = new PlayerListWidget(0, 0, this); + playerListWidget->setFocusPolicy(Qt::NoFocus); + + timeElapsedLabel = new QLabel; + timeElapsedLabel->setAlignment(Qt::AlignCenter); + messageLog = new MessageLogWidget(QString(), false); + connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfo, SLOT(setCard(QString))); + connect(messageLog, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(messageLog, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); + sayLabel = 0; + + deckViewContainerLayout = new QVBoxLayout; + + QVBoxLayout *messageLogLayout = new QVBoxLayout; + messageLogLayout->addWidget(timeElapsedLabel); + messageLogLayout->addWidget(messageLog); + + QWidget *messageLogLayoutWidget = new QWidget; + messageLogLayoutWidget->setLayout(messageLogLayout); + + splitter = new QSplitter(Qt::Vertical); + splitter->addWidget(cardInfo); + splitter->addWidget(playerListWidget); + splitter->addWidget(messageLogLayoutWidget); + + mainLayout = new QHBoxLayout; + mainLayout->addWidget(gameView, 10); + mainLayout->addLayout(deckViewContainerLayout, 10); + mainLayout->addWidget(splitter); + + aNextPhase = 0; + aNextTurn = 0; + aRemoveLocalArrows = 0; + aConcede = 0; + aLeaveGame = new QAction(this); + connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame())); + + phasesMenu = 0; + tabMenu = new QMenu(this); + tabMenu->addAction(aLeaveGame); + + retranslateUi(); + setLayout(mainLayout); + + splitter->restoreState(settingsCache->getTabGameSplitterSizes()); + + messageLog->logReplayStarted(gameId); + + gameTimer = new QTimer(this); + gameTimer->setInterval(1000); + connect(gameTimer, SIGNAL(timeout()), this, SLOT(nextReplayStep())); + gameTimer->start(); +} + TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_clients, const Event_GameJoined &event) : Tab(_tabSupervisor), clients(_clients), @@ -205,10 +285,11 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_client spectator(event.spectator()), spectatorsCanTalk(event.spectators_can_talk()), spectatorsSeeEverything(event.spectators_see_everything()), - gameStateKnown(false), + gameStateKnown(true), started(false), resuming(event.resuming()), - currentPhase(-1) + currentPhase(-1), + replay(0) { gameTimer = new QTimer(this); gameTimer->setInterval(1000); @@ -261,7 +342,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_client mainLayout->addWidget(gameView, 10); mainLayout->addLayout(deckViewContainerLayout, 10); mainLayout->addWidget(splitter); - + if (spectator && !spectatorsCanTalk && tabSupervisor->getAdminLocked()) { sayLabel->hide(); sayEdit->hide(); @@ -320,6 +401,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_client TabGame::~TabGame() { + delete replay; settingsCache->setTabGameSplitterSizes(splitter->saveState()); QMapIterator i(players); @@ -334,23 +416,34 @@ TabGame::~TabGame() void TabGame::retranslateUi() { - for (int i = 0; i < phaseActions.size(); ++i) - phaseActions[i]->setText(phasesToolbar->getLongPhaseName(i)); - phasesMenu->setTitle(tr("&Phases")); + if (phasesMenu) { + for (int i = 0; i < phaseActions.size(); ++i) + phaseActions[i]->setText(phasesToolbar->getLongPhaseName(i)); + phasesMenu->setTitle(tr("&Phases")); + } tabMenu->setTitle(tr("&Game")); - aNextPhase->setText(tr("Next &phase")); - aNextPhase->setShortcut(tr("Ctrl+Space")); - aNextTurn->setText(tr("Next &turn")); - aNextTurn->setShortcuts(QList() << QKeySequence(tr("Ctrl+Return")) << QKeySequence(tr("Ctrl+Enter"))); - aRemoveLocalArrows->setText(tr("&Remove all local arrows")); - aRemoveLocalArrows->setShortcut(tr("Ctrl+R")); - aConcede->setText(tr("&Concede")); - aConcede->setShortcut(tr("F2")); + if (aNextPhase) { + aNextPhase->setText(tr("Next &phase")); + aNextPhase->setShortcut(tr("Ctrl+Space")); + } + if (aNextTurn) { + aNextTurn->setText(tr("Next &turn")); + aNextTurn->setShortcuts(QList() << QKeySequence(tr("Ctrl+Return")) << QKeySequence(tr("Ctrl+Enter"))); + } + if (aRemoveLocalArrows) { + aRemoveLocalArrows->setText(tr("&Remove all local arrows")); + aRemoveLocalArrows->setShortcut(tr("Ctrl+R")); + } + if (aConcede) { + aConcede->setText(tr("&Concede")); + aConcede->setShortcut(tr("F2")); + } aLeaveGame->setText(tr("&Leave game")); aLeaveGame->setShortcut(tr("Ctrl+Q")); - sayLabel->setText(tr("&Say:")); + if (sayLabel) + sayLabel->setText(tr("&Say:")); cardInfo->retranslateUi(); QMapIterator i(players); @@ -368,6 +461,17 @@ void TabGame::closeRequest() actLeaveGame(); } +void TabGame::nextReplayStep() +{ + if (replay->event_list_size() <= currentReplayStep) { + QMessageBox::information(this, "", "done"); + gameTimer->stop(); + return; + } + processGameEventContainer(replay->event_list(currentReplayStep), 0); + ++currentReplayStep; +} + void TabGame::incrementGameTime() { int seconds = ++secondsElapsed; diff --git a/cockatrice/src/tab_game.h b/cockatrice/src/tab_game.h index 0efc922e..26c5c910 100644 --- a/cockatrice/src/tab_game.h +++ b/cockatrice/src/tab_game.h @@ -49,6 +49,7 @@ class TabGame; class DeckList; class QVBoxLayout; class QHBoxLayout; +class GameReplay; class ServerInfo_User; class PendingCommand; @@ -107,7 +108,9 @@ private: QStringList phasesList; int currentPhase; int activePlayer; - + GameReplay *replay; + int currentReplayStep; + QSplitter *splitter; CardInfoWidget *cardInfo; PlayerListWidget *playerListWidget; @@ -155,6 +158,7 @@ signals: void containerProcessingDone(); void openMessageDialog(const QString &userName, bool focus); private slots: + void nextReplayStep(); void incrementGameTime(); void adminLockChanged(bool lock); void newCardAdded(AbstractCardItem *card); @@ -168,6 +172,7 @@ private slots: void actNextTurn(); public: TabGame(TabSupervisor *_tabSupervisor, QList &_clients, const Event_GameJoined &event); + TabGame(GameReplay *replay); ~TabGame(); void retranslateUi(); void closeRequest(); diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index b31bd000..573653d2 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "main.h" #include "window_main.h" @@ -35,7 +37,10 @@ #include "localserver.h" #include "localserverinterface.h" #include "localclient.h" +#include "settingscache.h" +#include "tab_game.h" +#include "pb/game_replay.pb.h" #include "pb/room_commands.pb.h" #include "pb/event_connection_closed.pb.h" #include "pb/event_server_shutdown.pb.h" @@ -145,6 +150,28 @@ void MainWindow::actSinglePlayer() mainClient->sendCommand(mainClient->prepareRoomCommand(createCommand, 0)); } +void MainWindow::actWatchReplay() +{ + QFileDialog dlg(this, tr("Load replay")); + dlg.setDirectory(settingsCache->getReplaysPath()); + dlg.setNameFilters(QStringList() << QObject::tr("Cockatrice replays (*.cor)")); + if (!dlg.exec()) + return; + + QString fileName = dlg.selectedFiles().at(0); + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return; + QByteArray buf = file.readAll(); + file.close(); + + GameReplay *replay = new GameReplay; + replay->ParseFromArray(buf.data(), buf.size()); + + TabGame *replayWatcher = new TabGame(replay); + replayWatcher->show(); +} + void MainWindow::localGameEnded() { delete localServer; @@ -243,6 +270,7 @@ void MainWindow::retranslateUi() aConnect->setText(tr("&Connect...")); aDisconnect->setText(tr("&Disconnect")); aSinglePlayer->setText(tr("Start &local game...")); + aWatchReplay->setText(tr("&Watch replay...")); aDeckEditor->setText(tr("&Deck editor")); aFullScreen->setText(tr("&Full screen")); aFullScreen->setShortcut(tr("Ctrl+F")); @@ -266,6 +294,8 @@ void MainWindow::createActions() connect(aDisconnect, SIGNAL(triggered()), this, SLOT(actDisconnect())); aSinglePlayer = new QAction(this); connect(aSinglePlayer, SIGNAL(triggered()), this, SLOT(actSinglePlayer())); + aWatchReplay = new QAction(this); + connect(aWatchReplay, SIGNAL(triggered()), this, SLOT(actWatchReplay())); aDeckEditor = new QAction(this); connect(aDeckEditor, SIGNAL(triggered()), this, SLOT(actDeckEditor())); aFullScreen = new QAction(this); @@ -286,6 +316,7 @@ void MainWindow::createMenus() cockatriceMenu->addAction(aConnect); cockatriceMenu->addAction(aDisconnect); cockatriceMenu->addAction(aSinglePlayer); + cockatriceMenu->addAction(aWatchReplay); cockatriceMenu->addSeparator(); cockatriceMenu->addAction(aDeckEditor); cockatriceMenu->addSeparator(); diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 83307d37..c49550fe 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -47,6 +47,7 @@ private slots: void actConnect(); void actDisconnect(); void actSinglePlayer(); + void actWatchReplay(); void actDeckEditor(); void actFullScreen(bool checked); void actSettings(); @@ -60,7 +61,7 @@ private: void createActions(); void createMenus(); QMenu *cockatriceMenu, *tabMenu, *helpMenu; - QAction *aConnect, *aDisconnect, *aSinglePlayer, *aDeckEditor, *aFullScreen, *aSettings, *aExit, + QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit, *aAbout; TabSupervisor *tabSupervisor;