servatrice/cockatrice/src/tab_game.cpp
2015-12-26 16:40:17 +01:00

1337 lines
46 KiB
C++

#include <QLabel>
#include <QHBoxLayout>
#include <QSplitter>
#include <QMenu>
#include <QAction>
#include <QMessageBox>
#include <QFileDialog>
#include <QTimer>
#include <QToolButton>
#include <QDebug>
#include <QCompleter>
#include <QWidget>
#include "dlg_creategame.h"
#include "tab_game.h"
#include "tab_supervisor.h"
#include "cardframe.h"
#include "playerlistwidget.h"
#include "messagelogwidget.h"
#include "phasestoolbar.h"
#include "gameview.h"
#include "gamescene.h"
#include "player.h"
#include "zoneviewzone.h"
#include "zoneviewwidget.h"
#include "deckview.h"
#include "deck_loader.h"
#include "dlg_load_remote_deck.h"
#include "abstractclient.h"
#include "carditem.h"
#include "arrowitem.h"
#include "main.h"
#include "settingscache.h"
#include "carddatabase.h"
#include "pictureloader.h"
#include "replay_timeline_widget.h"
#include "lineeditcompleter.h"
#include <google/protobuf/descriptor.h>
#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"
#include "pb/command_set_sideboard_plan.pb.h"
#include "pb/command_set_sideboard_lock.pb.h"
#include "pb/command_leave_game.pb.h"
#include "pb/command_game_say.pb.h"
#include "pb/command_set_active_phase.pb.h"
#include "pb/command_next_turn.pb.h"
#include "pb/command_delete_arrow.pb.h"
#include "pb/response_deck_download.pb.h"
#include "pb/game_event_container.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_player_properties_changed.pb.h"
#include "pb/event_join.pb.h"
#include "pb/event_leave.pb.h"
#include "pb/event_kicked.pb.h"
#include "pb/event_game_host_changed.pb.h"
#include "pb/event_game_closed.pb.h"
#include "pb/event_set_active_player.pb.h"
#include "pb/event_set_active_phase.pb.h"
#include "pb/context_deck_select.pb.h"
#include "pb/context_connection_state_changed.pb.h"
#include "pb/context_ping_changed.pb.h"
#include "get_pb_extension.h"
ToggleButton::ToggleButton(QWidget *parent)
: QPushButton(parent), state(false)
{
}
void ToggleButton::paintEvent(QPaintEvent *event)
{
QPushButton::paintEvent(event);
QPainter painter(this);
QPen pen;
pen.setWidth(3);
pen.setJoinStyle(Qt::MiterJoin);
pen.setColor(state ? Qt::green : Qt::red);
painter.setPen(pen);
painter.drawRect(QRect(1, 1, width() - 3, height() - 3));
}
void ToggleButton::setState(bool _state)
{
state = _state;
emit stateChanged();
update();
}
DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
: QWidget(parent), playerId(_playerId)
{
loadLocalButton = new QPushButton;
loadRemoteButton = new QPushButton;
readyStartButton = new ToggleButton;
readyStartButton->setEnabled(false);
sideboardLockButton = new ToggleButton;
sideboardLockButton->setEnabled(false);
connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck()));
connect(loadRemoteButton, SIGNAL(clicked()), this, SLOT(loadRemoteDeck()));
connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart()));
connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked()));
connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText()));
QHBoxLayout *buttonHBox = new QHBoxLayout;
buttonHBox->addWidget(loadLocalButton);
buttonHBox->addWidget(loadRemoteButton);
buttonHBox->addWidget(readyStartButton);
buttonHBox->addWidget(sideboardLockButton);
buttonHBox->addStretch();
deckView = new DeckView;
connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *)));
connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged()));
QVBoxLayout *deckViewLayout = new QVBoxLayout;
deckViewLayout->addLayout(buttonHBox);
deckViewLayout->addWidget(deckView);
setLayout(deckViewLayout);
retranslateUi();
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()),this,SLOT(refreshShortcuts()));
refreshShortcuts();
}
void DeckViewContainer::retranslateUi()
{
loadLocalButton->setText(tr("Load local deck"));
loadRemoteButton->setText(tr("Load deck from server"));
readyStartButton->setText(tr("Ready to s&tart"));
updateSideboardLockButtonText();
}
void DeckViewContainer::setButtonsVisible(bool _visible)
{
loadLocalButton->setVisible(_visible);
loadRemoteButton->setVisible(_visible);
readyStartButton->setVisible(_visible);
sideboardLockButton->setVisible(_visible);
}
void DeckViewContainer::updateSideboardLockButtonText()
{
if (sideboardLockButton->getState())
sideboardLockButton->setText(tr("S&ideboard unlocked"));
else
sideboardLockButton->setText(tr("S&ideboard locked"));
}
void DeckViewContainer::refreshShortcuts()
{
loadLocalButton->setShortcut(settingsCache->shortcuts().getSingleShortcut("DeckViewContainer/loadLocalButton"));
loadRemoteButton->setShortcut(settingsCache->shortcuts().getSingleShortcut("DeckViewContainer/loadRemoteButton"));
}
void TabGame::refreshShortcuts()
{
for (int i = 0; i < phaseActions.size(); ++i) {
QAction *temp = phaseActions.at(i);
switch (i) {
case 0: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase0")); break;
case 1: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase1")); break;
case 2: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase2")); break;
case 3: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase3")); break;
case 4: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase4")); break;
case 5: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase5")); break;
case 6: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase6")); break;
case 7: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase7")); break;
case 8: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase8")); break;
case 9: temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase9")); break;
case 10:temp->setShortcuts(settingsCache->shortcuts().getShortcut("Player/phase10")); break;
default: ;
}
}
if (aNextPhase) {
aNextPhase->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextPhase"));
}
if (aNextTurn) {
aNextTurn->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aNextTurn"));
}
if (aRemoveLocalArrows) {
aRemoveLocalArrows->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aRemoveLocalArrows"));
}
if (aRotateViewCW) {
aRotateViewCW->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aRotateViewCW"));
}
if (aRotateViewCCW) {
aRotateViewCCW->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aRotateViewCCW"));
}
if (aConcede) {
aConcede->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aConcede"));
}
if (aLeaveGame) {
aLeaveGame->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aLeaveGame"));
}
if (aCloseReplay) {
aCloseReplay->setShortcuts(settingsCache->shortcuts().getShortcut("Player/aCloseReplay"));
}
}
void DeckViewContainer::loadLocalDeck()
{
QFileDialog dialog(this, tr("Load deck"));
dialog.setDirectory(settingsCache->getDeckPath());
dialog.setNameFilters(DeckLoader::fileNameFilters);
if (!dialog.exec())
return;
QString fileName = dialog.selectedFiles().at(0);
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
DeckLoader deck;
if (!deck.loadFromFile(fileName, fmt)) {
QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded."));
return;
}
Command_DeckSelect cmd;
cmd.set_deck(deck.writeToString_Native().toStdString());
PendingCommand *pend = static_cast<TabGame *>(parent())->prepareGameCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deckSelectFinished(const Response &)));
static_cast<TabGame *>(parent())->sendGameCommand(pend, playerId);
}
void DeckViewContainer::loadRemoteDeck()
{
DlgLoadRemoteDeck dlg(static_cast<TabGame *>(parent())->getClientForPlayer(playerId));
if (dlg.exec()) {
Command_DeckSelect cmd;
cmd.set_deck_id(dlg.getDeckId());
PendingCommand *pend = static_cast<TabGame *>(parent())->prepareGameCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deckSelectFinished(const Response &)));
static_cast<TabGame *>(parent())->sendGameCommand(pend, playerId);
}
}
void DeckViewContainer::deckSelectFinished(const Response &r)
{
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
DeckLoader newDeck(QString::fromStdString(resp.deck()));
PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
setDeck(newDeck);
}
void DeckViewContainer::readyStart()
{
Command_ReadyStart cmd;
cmd.set_ready(!readyStartButton->getState());
static_cast<TabGame *>(parent())->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::sideboardLockButtonClicked()
{
Command_SetSideboardLock cmd;
cmd.set_locked(sideboardLockButton->getState());
static_cast<TabGame *>(parent())->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::sideboardPlanChanged()
{
Command_SetSideboardPlan cmd;
const QList<MoveCard_ToZone> &newPlan = deckView->getSideboardPlan();
for (int i = 0; i < newPlan.size(); ++i)
cmd.add_move_list()->CopyFrom(newPlan[i]);
static_cast<TabGame *>(parent())->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::setReadyStart(bool ready)
{
readyStartButton->setState(ready);
deckView->setLocked(ready || !sideboardLockButton->getState());
sideboardLockButton->setEnabled(!readyStartButton->getState());
}
void DeckViewContainer::setSideboardLocked(bool locked)
{
sideboardLockButton->setState(!locked);
deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState());
if (locked)
deckView->resetSideboardPlan();
}
void DeckViewContainer::setDeck(const DeckLoader &deck)
{
deckView->setDeck(deck);
readyStartButton->setEnabled(true);
sideboardLockButton->setState(false);
sideboardLockButton->setEnabled(true);
}
TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
: Tab(_tabSupervisor),
secondsElapsed(0),
hostId(-1),
localPlayerId(-1),
spectator(true),
gameStateKnown(false),
resuming(false),
currentPhase(-1),
activeCard(0),
gameClosed(false),
replay(_replay),
currentReplayStep(0),
sayLabel(0),
sayEdit(0)
{
setAttribute(Qt::WA_DeleteOnClose);
gameInfo.CopyFrom(replay->game_info());
gameInfo.set_spectators_omniscient(true);
// Create list: event number -> time [ms]
// Distribute simultaneous events evenly across 1 second.
unsigned int lastEventTimestamp = 0;
const int eventCount = replay->event_list_size();
for (int i = 0; i < eventCount; ++i) {
int j = i + 1;
while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp))
++j;
const int numberEventsThisSecond = j - i;
for (int k = 0; k < numberEventsThisSecond; ++k)
replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + (int) ((qreal) k / (qreal) numberEventsThisSecond * 1000));
if (j < eventCount)
lastEventTimestamp = replay->event_list(j).seconds_elapsed();
i += numberEventsThisSecond - 1;
}
phasesToolbar = new PhasesToolbar;
scene = new GameScene(phasesToolbar, this);
gameView = new GameView(scene);
gameView->hide();
cardInfo = new CardFrame();
playerListWidget = new PlayerListWidget(0, 0, this);
playerListWidget->setFocusPolicy(Qt::NoFocus);
messageLog = new MessageLogWidget(tabSupervisor, this);
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)));
deckViewContainerLayout = new QVBoxLayout;
QVBoxLayout *messageLogLayout = new QVBoxLayout;
messageLogLayout->setContentsMargins(0, 0, 0, 0);
messageLogLayout->addWidget(messageLog);
QWidget *messageLogLayoutWidget = new QWidget;
messageLogLayoutWidget->setLayout(messageLogLayout);
timelineWidget = new ReplayTimelineWidget;
timelineWidget->setTimeline(replayTimeline);
connect(timelineWidget, SIGNAL(processNextEvent()), this, SLOT(replayNextEvent()));
connect(timelineWidget, SIGNAL(replayFinished()), this, SLOT(replayFinished()));
replayStartButton = new QToolButton;
replayStartButton->setIconSize(QSize(32, 32));
replayStartButton->setIcon(QPixmap("theme:replay/start"));
connect(replayStartButton, SIGNAL(clicked()), this, SLOT(replayStartButtonClicked()));
replayPauseButton = new QToolButton;
replayPauseButton->setIconSize(QSize(32, 32));
replayPauseButton->setEnabled(false);
replayPauseButton->setIcon(QPixmap("theme:replay/pause"));
connect(replayPauseButton, SIGNAL(clicked()), this, SLOT(replayPauseButtonClicked()));
replayFastForwardButton = new QToolButton;
replayFastForwardButton->setIconSize(QSize(32, 32));
replayFastForwardButton->setEnabled(false);
replayFastForwardButton->setIcon(QPixmap("theme:replay/fastforward"));
replayFastForwardButton->setCheckable(true);
connect(replayFastForwardButton, SIGNAL(toggled(bool)), this, SLOT(replayFastForwardButtonToggled(bool)));
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);
QHBoxLayout *replayControlLayout = new QHBoxLayout;
replayControlLayout->addWidget(timelineWidget, 10);
replayControlLayout->addWidget(replayStartButton);
replayControlLayout->addWidget(replayPauseButton);
replayControlLayout->addWidget(replayFastForwardButton);
QVBoxLayout *superMainLayout = new QVBoxLayout;
superMainLayout->addLayout(mainLayout);
superMainLayout->addLayout(replayControlLayout);
aNextPhase = 0;
aNextTurn = 0;
aRemoveLocalArrows = 0;
aRotateViewCW = 0;
aRotateViewCCW = 0;
aGameInfo = 0;
aConcede = 0;
aLeaveGame = 0;
aCloseReplay = new QAction(this);
connect(aCloseReplay, SIGNAL(triggered()), this, SLOT(actLeaveGame()));
phasesMenu = 0;
gameMenu = new QMenu(this);
gameMenu->addAction(aCloseReplay);
addTabMenu(gameMenu);
retranslateUi();
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()),this,SLOT(refreshShortcuts()));
refreshShortcuts();
setLayout(superMainLayout);
splitter->restoreState(settingsCache->getTabGameSplitterSizes());
splitter->setChildrenCollapsible(false);
messageLog->logReplayStarted(gameInfo.game_id());
}
TabGame::TabGame(TabSupervisor *_tabSupervisor, QList<AbstractClient *> &_clients, const Event_GameJoined &event, const QMap<int, QString> &_roomGameTypes)
: Tab(_tabSupervisor),
clients(_clients),
gameInfo(event.game_info()),
roomGameTypes(_roomGameTypes),
hostId(event.host_id()),
localPlayerId(event.player_id()),
spectator(event.spectator()),
gameStateKnown(false),
resuming(event.resuming()),
currentPhase(-1),
activeCard(0),
gameClosed(false),
replay(0)
{
gameInfo.set_started(false);
gameTimer = new QTimer(this);
gameTimer->setInterval(1000);
connect(gameTimer, SIGNAL(timeout()), this, SLOT(incrementGameTime()));
gameTimer->start();
phasesToolbar = new PhasesToolbar;
connect(phasesToolbar, SIGNAL(sendGameCommand(const ::google::protobuf::Message &, int)), this, SLOT(sendGameCommand(const ::google::protobuf::Message &, int)));
scene = new GameScene(phasesToolbar, this);
gameView = new GameView(scene);
gameView->hide();
cardInfo = new CardFrame();
playerListWidget = new PlayerListWidget(tabSupervisor, clients.first(), this);
playerListWidget->setFocusPolicy(Qt::NoFocus);
connect(playerListWidget, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool)));
timeElapsedLabel = new QLabel;
timeElapsedLabel->setAlignment(Qt::AlignCenter);
messageLog = new MessageLogWidget(tabSupervisor, this);
connect(messageLog, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool)));
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)));
connect(messageLog, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString)));
connect(settingsCache, SIGNAL(chatMentionCompleterChanged()), this, SLOT(actCompleterChanged()));
sayLabel = new QLabel;
sayEdit = new LineEditCompleter;
sayLabel->setBuddy(sayEdit);
QHBoxLayout *hLayout = new QHBoxLayout;
hLayout->addWidget(sayLabel);
hLayout->addWidget(sayEdit);
deckViewContainerLayout = new QVBoxLayout;
QVBoxLayout *messageLogLayout = new QVBoxLayout;
messageLogLayout->setContentsMargins(0, 0, 0, 0);
messageLogLayout->addWidget(timeElapsedLabel);
messageLogLayout->addWidget(messageLog);
messageLogLayout->addLayout(hLayout);
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);
if (spectator && !gameInfo.spectators_can_chat() && tabSupervisor->getAdminLocked()) {
sayLabel->hide();
sayEdit->hide();
}
connect(tabSupervisor, SIGNAL(adminLockChanged(bool)), this, SLOT(adminLockChanged(bool)));
connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(actSay()));
// Menu actions
aNextPhase = new QAction(this);
connect(aNextPhase, SIGNAL(triggered()), this, SLOT(actNextPhase()));
aNextTurn = new QAction(this);
connect(aNextTurn, SIGNAL(triggered()), this, SLOT(actNextTurn()));
aRemoveLocalArrows = new QAction(this);
connect(aRemoveLocalArrows, SIGNAL(triggered()), this, SLOT(actRemoveLocalArrows()));
aRotateViewCW = new QAction(this);
connect(aRotateViewCW, SIGNAL(triggered()), this, SLOT(actRotateViewCW()));
aRotateViewCCW = new QAction(this);
connect(aRotateViewCCW, SIGNAL(triggered()), this, SLOT(actRotateViewCCW()));
aGameInfo = new QAction(this);
connect(aGameInfo, SIGNAL(triggered()), this, SLOT(actGameInfo()));
aConcede = new QAction(this);
connect(aConcede, SIGNAL(triggered()), this, SLOT(actConcede()));
aLeaveGame = new QAction(this);
connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame()));
aCloseReplay = 0;
phasesMenu = new QMenu(this);
for (int i = 0; i < phasesToolbar->phaseCount(); ++i) {
QAction *temp = new QAction(QString(), this);
connect(temp, SIGNAL(triggered()), this, SLOT(actPhaseAction()));
phasesMenu->addAction(temp);
phaseActions.append(temp);
}
phasesMenu->addSeparator();
phasesMenu->addAction(aNextPhase);
gameMenu = new QMenu(this);
playersSeparator = gameMenu->addSeparator();
gameMenu->addMenu(phasesMenu);
gameMenu->addAction(aNextTurn);
gameMenu->addSeparator();
gameMenu->addAction(aRemoveLocalArrows);
gameMenu->addAction(aRotateViewCW);
gameMenu->addAction(aRotateViewCCW);
gameMenu->addSeparator();
gameMenu->addAction(aGameInfo);
gameMenu->addAction(aConcede);
gameMenu->addAction(aLeaveGame);
addTabMenu(gameMenu);
retranslateUi();
connect(&settingsCache->shortcuts(), SIGNAL(shortCutchanged()),this,SLOT(refreshShortcuts()));
refreshShortcuts();
setLayout(mainLayout);
splitter->restoreState(settingsCache->getTabGameSplitterSizes());
splitter->setChildrenCollapsible(false);
messageLog->logGameJoined(gameInfo.game_id());
for (int i = gameInfo.game_types_size() - 1; i >= 0; i--)
gameTypes.append(roomGameTypes.find(gameInfo.game_types(i)).value());
completer = new QCompleter(autocompleteUserList, sayEdit);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setMaxVisibleItems(5);
#if QT_VERSION >= 0x050000
completer->setFilterMode(Qt::MatchStartsWith);
#endif
sayEdit->setCompleter(completer);
actCompleterChanged();
}
void TabGame::addMentionTag(QString value) {
sayEdit->insert(value + " ");
sayEdit->setFocus();
}
void TabGame::emitUserEvent() {
bool globalEvent = !spectator || settingsCache->getSpectatorNotificationsEnabled();
emit userEvent(globalEvent);
}
TabGame::~TabGame()
{
delete replay;
settingsCache->setTabGameSplitterSizes(splitter->saveState());
QMapIterator<int, Player *> i(players);
while (i.hasNext())
delete i.next().value();
players.clear();
delete deckViewContainerLayout;
emit gameClosing(this);
}
void TabGame::retranslateUi()
{
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 (aNextTurn) {
aNextTurn->setText(tr("Next &turn"));
}
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) {
aConcede->setText(tr("&Concede"));
}
if (aLeaveGame) {
aLeaveGame->setText(tr("&Leave game"));
}
if (aCloseReplay) {
aCloseReplay->setText(tr("C&lose replay"));
}
if (sayLabel)
sayLabel->setText(tr("&Say:"));
cardInfo->retranslateUi();
QMapIterator<int, Player *> i(players);
while (i.hasNext())
i.next().value()->retranslateUi();
QMapIterator<int, DeckViewContainer *> j(deckViewContainers);
while (j.hasNext())
j.next().value()->retranslateUi();
scene->retranslateUi();
}
void TabGame::closeRequest()
{
actLeaveGame();
}
void TabGame::replayNextEvent()
{
processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), 0);
}
void TabGame::replayFinished()
{
replayStartButton->setEnabled(true);
replayPauseButton->setEnabled(false);
replayFastForwardButton->setEnabled(false);
}
void TabGame::replayStartButtonClicked()
{
replayStartButton->setEnabled(false);
replayPauseButton->setEnabled(true);
replayFastForwardButton->setEnabled(true);
timelineWidget->startReplay();
}
void TabGame::replayPauseButtonClicked()
{
replayStartButton->setEnabled(true);
replayPauseButton->setEnabled(false);
replayFastForwardButton->setEnabled(false);
timelineWidget->stopReplay();
}
void TabGame::replayFastForwardButtonToggled(bool checked)
{
timelineWidget->setTimeScaleFactor(checked ? 10.0 : 1.0);
}
void TabGame::incrementGameTime()
{
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'));
}
void TabGame::adminLockChanged(bool lock)
{
bool v = !(spectator && !gameInfo.spectators_can_chat() && lock);
sayLabel->setVisible(v);
sayEdit->setVisible(v);
}
void TabGame::actGameInfo()
{
DlgCreateGame dlg(gameInfo, roomGameTypes);
dlg.exec();
}
void TabGame::actConcede()
{
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;
sendGameCommand(Command_Concede());
}
void TabGame::actLeaveGame()
{
if (!gameClosed) {
if (!spectator)
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;
if (!replay)
sendGameCommand(Command_LeaveGame());
}
scene->clearViews();
deleteLater();
}
void TabGame::actSay()
{
if (completer->popup()->isVisible())
return;
if (!sayEdit->text().isEmpty()) {
Command_GameSay cmd;
cmd.set_message(sayEdit->text().toStdString());
sendGameCommand(cmd);
sayEdit->clear();
}
}
void TabGame::actPhaseAction()
{
int phase = phaseActions.indexOf(static_cast<QAction *>(sender()));
Command_SetActivePhase cmd;
cmd.set_phase(phase);
sendGameCommand(cmd);
}
void TabGame::actNextPhase()
{
int phase = currentPhase;
if (++phase >= phasesToolbar->phaseCount())
phase = 0;
Command_SetActivePhase cmd;
cmd.set_phase(phase);
sendGameCommand(cmd);
}
void TabGame::actNextTurn()
{
sendGameCommand(Command_NextTurn());
}
void TabGame::actRemoveLocalArrows()
{
QMapIterator<int, Player *> playerIterator(players);
while (playerIterator.hasNext()) {
Player *player = playerIterator.next().value();
if (!player->getLocal())
continue;
QMapIterator<int, ArrowItem *> arrowIterator(player->getArrows());
while (arrowIterator.hasNext()) {
ArrowItem *a = arrowIterator.next().value();
Command_DeleteArrow cmd;
cmd.set_arrow_id(a->getId());
sendGameCommand(cmd);
}
}
}
void TabGame::actRotateViewCW()
{
scene->adjustPlayerRotation(-1);
}
void TabGame::actRotateViewCCW()
{
scene->adjustPlayerRotation(1);
}
void TabGame::actCompleterChanged()
{
settingsCache->getChatMentionCompleter() ? completer->setCompletionRole(2) : completer->setCompletionRole(1);
}
Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info)
{
bool local = ((clients.size() > 1) || (playerId == localPlayerId));
Player *newPlayer = new Player(info, playerId, local, 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);
}
scene->addPlayer(newPlayer);
connect(newPlayer, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *)));
messageLog->connectToPlayer(newPlayer);
if (local && !spectator) {
if (clients.size() == 1)
newPlayer->setShortcutsActive();
DeckViewContainer *deckView = new DeckViewContainer(playerId, this);
connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *)));
deckViewContainers.insert(playerId, deckView);
deckViewContainerLayout->addWidget(deckView);
}
gameMenu->insertMenu(playersSeparator, newPlayer->getPlayerMenu());
players.insert(playerId, newPlayer);
emit playerAdded(newPlayer);
return newPlayer;
}
void TabGame::processGameEventContainer(const GameEventContainer &cont, AbstractClient *client)
{
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 GameEvent::GameEventType eventType = static_cast<GameEvent::GameEventType>(getPbExtension(event));
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: {
qDebug() << "unhandled spectator game event";
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;
default: {
Player *player = players.value(playerId, 0);
if (!player) {
qDebug() << "unhandled game event: invalid player id";
break;
}
player->processGameEvent(eventType, event, context);
emitUserEvent();
}
}
}
}
messageLog->containerProcessingDone();
}
AbstractClient *TabGame::getClientForPlayer(int playerId) const
{
if (clients.size() > 1) {
if (playerId == -1)
playerId = getActiveLocalPlayer()->getId();
return clients.at(playerId);
} else if (clients.isEmpty())
return 0;
else
return clients.first();
}
void TabGame::sendGameCommand(PendingCommand *pend, int playerId)
{
AbstractClient *client = getClientForPlayer(playerId);
if (!client)
return;
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(commandFinished(const Response &)));
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, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(commandFinished(const Response &)));
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(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< const ::google::protobuf::Message * > &cmdList)
{
CommandContainer cont;
cont.set_game_id(gameInfo.game_id());
for (int i = 0; i < cmdList.size(); ++i) {
GameCommand *c = cont.add_game_command();
c->GetReflection()->MutableMessage(c, cmdList[i]->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*cmdList[i]);
delete cmdList[i];
}
return new PendingCommand(cont);
}
void TabGame::startGame(bool resuming)
{
currentPhase = -1;
QMapIterator<int, DeckViewContainer *> i(deckViewContainers);
while (i.hasNext()) {
i.next();
i.value()->setReadyStart(false);
i.value()->hide();
}
mainLayout->removeItem(deckViewContainerLayout);
if (!resuming) {
QMapIterator<int, Player *> playerIterator(players);
while (playerIterator.hasNext())
playerIterator.next().value()->setGameStarted();
}
playerListWidget->setGameStarted(true, resuming);
gameInfo.set_started(true);
static_cast<GameScene *>(gameView->scene())->rearrange();
gameView->show();
if(sayEdit && players.size() > 1)
sayEdit->setFocus();
}
void TabGame::stopGame()
{
currentPhase = -1;
activePlayer = -1;
QMapIterator<int, DeckViewContainer *> i(deckViewContainers);
while (i.hasNext()) {
i.next();
i.value()->show();
}
mainLayout->insertLayout(1, deckViewContainerLayout, 10);
playerListWidget->setActivePlayer(-1);
playerListWidget->setGameStarted(false, false);
gameInfo.set_started(false);
gameView->hide();
}
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(QString::fromStdString(userInfo.name()), UserLevelFlags(userInfo.user_level()), 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()));
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();
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()) {
DeckViewContainer *deckViewContainer = deckViewContainers.value(playerId);
if (playerInfo.has_deck_list()) {
DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list()));
PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
deckViewContainer->setDeck(newDeck);
player->setDeck(newDeck);
}
deckViewContainer->setReadyStart(prop.ready_start());
deckViewContainer->setSideboardLocked(prop.sideboard_locked());
}
}
}
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);
}
}
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 GameEventContext::ContextType contextType = static_cast<const GameEventContext::ContextType>(getPbExtension(context));
switch (contextType) {
case GameEventContext::READY_START: {
bool ready = prop.ready_start();
if (player->getLocal())
deckViewContainers.value(player->getId())->setReadyStart(ready);
if (ready)
messageLog->logReadyStart(player);
else
messageLog->logNotReadyStart(player);
break;
}
case GameEventContext::CONCEDE: {
messageLog->logConcede(player);
player->setConceded(true);
QMapIterator<int, Player *> 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());
break;
}
case GameEventContext::SET_SIDEBOARD_LOCK: {
if (player->getLocal())
deckViewContainers.value(player->getId())->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);
}
playerListWidget->addPlayer(playerInfo);
emitUserEvent();
}
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);
playerListWidget->removePlayer(eventPlayerId);
players.remove(eventPlayerId);
emit playerRemoved(player);
player->clear();
player->deleteLater();
// Rearrange all remaining zones so that attachment relationship updates take place
QMapIterator<int, Player *> 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::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);
if (!player)
return 0;
activePlayer = id;
playerListWidget->setActivePlayer(id);
QMapIterator<int, Player *> i(players);
while (i.hasNext()) {
i.next();
if (i.value() == player) {
i.value()->setActive(true);
if (clients.size() > 1)
i.value()->setShortcutsActive();
} else {
i.value()->setActive(false);
if (clients.size() > 1)
i.value()->setShortcutsInactive();
}
}
currentPhase = -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();
}
void TabGame::newCardAdded(AbstractCardItem *card)
{
connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfo, SLOT(setCard(AbstractCardItem *)));
connect(card, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString)));
connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));
connect(card, SIGNAL(updateCardMenu(AbstractCardItem *)), this, SLOT(updateCardMenu(AbstractCardItem *)));
}
CardItem *TabGame::getCard(int playerId, const QString &zoneName, int cardId) const
{
Player *player = players.value(playerId, 0);
if (!player)
return 0;
CardZone *zone = player->getZones().value(zoneName, 0);
if (!zone)
return 0;
return zone->getCard(cardId, QString());
}
QString TabGame::getTabText() const
{
QString gameTypeInfo;
if (gameTypes.size() != 0) {
gameTypeInfo = gameTypes.at(0);
if (gameTypes.size() > 1)
gameTypeInfo.append("...");
}
QString gameDesc(gameInfo.description().c_str());
QString gameId(QString::number(gameInfo.game_id()));
QString tabText;
if (replay)
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;
}
Player *TabGame::getActiveLocalPlayer() const
{
Player *active = players.value(activePlayer, 0);
if (active)
if (active->getLocal())
return active;
QMapIterator<int, Player *> playerIterator(players);
while (playerIterator.hasNext()) {
Player *temp = playerIterator.next().value();
if (temp->getLocal())
return temp;
}
return 0;
}
void TabGame::updateCardMenu(AbstractCardItem *card)
{
Player *p;
if ((clients.size() > 1) || !players.contains(localPlayerId))
p = card->getOwner();
else
p = players.value(localPlayerId);
p->updateCardMenu(static_cast<CardItem *>(card));
}