diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index e469a84b3..2f59e0b05 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -20,6 +20,7 @@ on: - '.github/workflows/desktop-build.yml' - 'CMakeLists.txt' - 'vcpkg.json' + - 'vcpkg' tags: - '*' pull_request: @@ -34,6 +35,7 @@ on: - '.github/workflows/desktop-build.yml' - 'CMakeLists.txt' - 'vcpkg.json' + - 'vcpkg' # Cancel earlier, unfinished runs of this workflow on the same branch (unless on master) concurrency: diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml index b8c015c16..adc4a8860 100644 --- a/.github/workflows/desktop-lint.yml +++ b/.github/workflows/desktop-lint.yml @@ -1,18 +1,19 @@ name: Code Style (C++) on: + # push trigger not needed for linting, we do not allow direct pushes to master pull_request: paths: - '*/**' # matches all files not in root - '!**.md' - '!.ci/**' - '!.github/**' - - '!.husky' - - '!.tx' + - '!.husky/**' + - '!.tx/**' - '!doc/**' - '!webclient/**' - - '.github/workflows/desktop-lint.yml' - '.ci/lint_cpp.sh' + - '.github/workflows/desktop-lint.yml' - '.clang-format' - '.cmake-format.json' - 'format.sh' diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml index 54bc76b19..7834e06b0 100644 --- a/.github/workflows/translations-pull.yml +++ b/.github/workflows/translations-pull.yml @@ -7,6 +7,7 @@ on: - cron: '0 0 15 1,4,7,10 *' pull_request: paths: + - '.tx/**' - '.github/workflows/translations-pull.yml' jobs: diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml index 5e299bf1f..927b3d5a1 100644 --- a/.github/workflows/translations-push.yml +++ b/.github/workflows/translations-push.yml @@ -7,6 +7,7 @@ on: - cron: '0 0 1 1,4,7,10 *' pull_request: paths: + - '.ci/update_translation_source_strings.sh' - '.github/workflows/translations-push.yml' jobs: diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml index f328fac88..aba0dbf0b 100644 --- a/.github/workflows/web-build.yml +++ b/.github/workflows/web-build.yml @@ -5,14 +5,16 @@ on: branches: - master paths: - - '.github/workflows/web-*.yml' + - '.husky/**' - 'webclient/**' - '!**.md' + - '.github/workflows/web-build.yml' pull_request: paths: - - '.github/workflows/web-*.yml' + - '.husky/**' - 'webclient/**' - '!**.md' + - '.github/workflows/web-build.yml' jobs: build-web: diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml index 7a962ea55..cb712dd58 100644 --- a/.github/workflows/web-lint.yml +++ b/.github/workflows/web-lint.yml @@ -1,11 +1,12 @@ name: Code Style (TypeScript) on: + # push trigger not needed for linting, we do not allow direct pushes to master pull_request: paths: - - '.github/workflows/web-*.yml' - 'webclient/**' - '!**.md' + - '.github/workflows/web-lint.yml' jobs: ESLint: diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index 6948a8e8b..008735585 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -478,8 +478,8 @@ AppearanceSettingsPage::AppearanceSettingsPage() &SettingsCache::setAutoRotateSidewaysLayoutCards); overrideAllCardArtWithPersonalPreferenceCheckBox.setChecked(settings.getOverrideAllCardArtWithPersonalPreference()); - connect(&overrideAllCardArtWithPersonalPreferenceCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, - &SettingsCache::setOverrideAllCardArtWithPersonalPreference); + connect(&overrideAllCardArtWithPersonalPreferenceCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &AppearanceSettingsPage::overrideAllCardArtWithPersonalPreferenceToggled); bumpSetsWithCardsInDeckToTopCheckBox.setChecked(settings.getBumpSetsWithCardsInDeckToTop()); connect(&bumpSetsWithCardsInDeckToTopCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, @@ -652,6 +652,45 @@ void AppearanceSettingsPage::showShortcutsChanged(QT_STATE_CHANGED_T value) qApp->setAttribute(Qt::AA_DontShowShortcutsInContextMenus, value == 0); // 0 = unchecked } +void AppearanceSettingsPage::overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T value) +{ + bool enable = static_cast(value); + + QString message; + if (enable) { + message = tr("Enabling this feature will disable the use of the Printing Selector.\n\n" + "You will not be able to manage printing preferences on a per-deck basis, " + "or see printings other people have selected for their decks.\n\n" + "You will have to use the Set Manager, available through Card Database -> Manage Sets.\n\n" + "Are you sure you would like to enable this feature?"); + } else { + message = + tr("Disabling this feature will enable the Printing Selector.\n\n" + "You can now choose printings on a per-deck basis in the Deck Editor and configure which printing " + "gets added to a deck by default by pinning it in the Printing Selector.\n\n" + "You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector" + " (other sort orders like alphabetical or release date are available).\n\n" + "Are you sure you would like to disable this feature?"); + } + + QMessageBox::StandardButton result = + QMessageBox::question(this, tr("Confirm Change"), message, QMessageBox::Yes | QMessageBox::No); + + if (result == QMessageBox::Yes) { + SettingsCache::instance().setOverrideAllCardArtWithPersonalPreference(value); + // Caches are now invalid. + PictureLoader::clearPixmapCache(); + PictureLoader::clearNetworkCache(); + } else { + // If user cancels, revert the checkbox/state back + QTimer::singleShot(0, this, [this, enable]() { + overrideAllCardArtWithPersonalPreferenceCheckBox.blockSignals(true); + overrideAllCardArtWithPersonalPreferenceCheckBox.setChecked(!enable); + overrideAllCardArtWithPersonalPreferenceCheckBox.blockSignals(false); + }); + } +} + /** * Updates the settings for cardViewInitialRowsMax. * Forces expanded rows max to always be >= initial rows max @@ -694,8 +733,7 @@ void AppearanceSettingsPage::retranslateUi() displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture")); autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout")); overrideAllCardArtWithPersonalPreferenceCheckBox.setText( - tr("Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client " - "restart]")); + tr("Override all card art with personal set preference (Pre-ProviderID change behavior)")); bumpSetsWithCardsInDeckToTopCheckBox.setText( tr("Bump sets that the deck contains cards from to the top in the printing selector")); cardScalingCheckBox.setText(tr("Scale cards on mouse over")); diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index bf8dbd2a5..c88dce8d0 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -105,6 +105,7 @@ private slots: void themeBoxChanged(int index); void openThemeLocation(); void showShortcutsChanged(QT_STATE_CHANGED_T enabled); + void overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T enabled); void cardViewInitialRowsMaxChanged(int value); void cardViewExpandedRowsMaxChanged(int value); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp index b0a9f0814..c0bf03b9f 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp @@ -203,7 +203,10 @@ void DeckEditorDatabaseDisplayWidget::databaseCustomMenu(QPoint point) QAction *addToDeck, *addToSideboard, *selectPrinting, *edhRecCommander, *edhRecCard; addToDeck = menu.addAction(tr("Add to Deck")); addToSideboard = menu.addAction(tr("Add to Sideboard")); - selectPrinting = menu.addAction(tr("Select Printing")); + if (!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { + selectPrinting = menu.addAction(tr("Select Printing")); + connect(selectPrinting, &QAction::triggered, this, [this, card] { deckEditor->showPrintingSelector(); }); + } if (canBeCommander(card.getInfo())) { edhRecCommander = menu.addAction(tr("Show on EDHRec (Commander)")); connect(edhRecCommander, &QAction::triggered, this, @@ -213,7 +216,6 @@ void DeckEditorDatabaseDisplayWidget::databaseCustomMenu(QPoint point) connect(addToDeck, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck); connect(addToSideboard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard); - connect(selectPrinting, &QAction::triggered, this, [this, card] { deckEditor->showPrintingSelector(); }); connect(edhRecCard, &QAction::triggered, this, [this, card] { deckEditor->getTabSupervisor()->addEdhrecTab(card.getCardPtr()); }); diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index dc995ff17..90489b296 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -37,11 +37,11 @@ void DeckEditorDeckDockWidget::createDeckDock() deckView->sortByColumn(1, Qt::AscendingOrder); deckView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); deckView->installEventFilter(&deckViewKeySignals); - deckView->setContextMenuPolicy(Qt::CustomContextMenu); deckView->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DeckEditorDeckDockWidget::updateCard); connect(deckView, &QTreeView::doubleClicked, this, &DeckEditorDeckDockWidget::actSwapCard); + deckView->setContextMenuPolicy(Qt::CustomContextMenu); connect(deckView, &QTreeView::customContextMenuRequested, this, &DeckEditorDeckDockWidget::decklistCustomMenu); connect(&deckViewKeySignals, &KeySignals::onShiftS, this, &DeckEditorDeckDockWidget::actSwapCard); connect(&deckViewKeySignals, &KeySignals::onEnter, this, &DeckEditorDeckDockWidget::actIncrement); @@ -577,13 +577,14 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point) { - QMenu menu; + if (!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { + QMenu menu; - QAction *selectPrinting = menu.addAction(tr("Select Printing")); + QAction *selectPrinting = menu.addAction(tr("Select Printing")); + connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector); - connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector); - - menu.exec(deckView->mapToGlobal(point)); + menu.exec(deckView->mapToGlobal(point)); + } } void DeckEditorDeckDockWidget::refreshShortcuts() diff --git a/cockatrice/src/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/tabs/abstract_tab_deck_editor.cpp index 3bd29b577..8eb7d35f7 100644 --- a/cockatrice/src/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/tabs/abstract_tab_deck_editor.cpp @@ -49,6 +49,9 @@ AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Ta cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this); filterDockWidget = new DeckEditorFilterDockWidget(this); printingSelectorDockWidget = new DeckEditorPrintingSelectorDockWidget(this); + connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, [this] { + printingSelectorDockWidget->setHidden(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); + }); connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged); connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified); diff --git a/cockatrice/src/tabs/tab_deck_editor.cpp b/cockatrice/src/tabs/tab_deck_editor.cpp index 1f56f7b7b..6d4da740d 100644 --- a/cockatrice/src/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/tabs/tab_deck_editor.cpp @@ -93,6 +93,13 @@ void TabDeckEditor::createMenus() aPrintingSelectorDockFloating->setCheckable(true); connect(aPrintingSelectorDockFloating, &QAction::triggered, this, &TabDeckEditor::dockFloatingTriggered); + if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { + printingSelectorDockMenu->setEnabled(false); + } + + connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, + [this](bool enabled) { printingSelectorDockMenu->setEnabled(!enabled); }); + viewMenu->addSeparator(); aResetLayout = viewMenu->addAction(QString()); @@ -171,6 +178,13 @@ void TabDeckEditor::loadLayout() restoreGeometry(layouts.getDeckEditorGeometry()); } + if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { + if (!printingSelectorDockWidget->isHidden()) { + printingSelectorDockWidget->setHidden(true); + aPrintingSelectorDockVisible->setChecked(false); + } + } + aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden()); aFilterDockVisible->setChecked(!filterDockWidget->isHidden()); aDeckDockVisible->setChecked(!deckDockWidget->isHidden()); @@ -203,10 +217,11 @@ void TabDeckEditor::loadLayout() void TabDeckEditor::restartLayout() { + aCardInfoDockVisible->setChecked(true); aDeckDockVisible->setChecked(true); aFilterDockVisible->setChecked(true); - aPrintingSelectorDockVisible->setChecked(true); + aPrintingSelectorDockVisible->setChecked(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); aCardInfoDockFloating->setChecked(false); aDeckDockFloating->setChecked(false); @@ -227,7 +242,7 @@ void TabDeckEditor::restartLayout() deckDockWidget->setVisible(true); cardInfoDockWidget->setVisible(true); filterDockWidget->setVisible(true); - printingSelectorDockWidget->setVisible(true); + printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal); splitDockWidget(printingSelectorDockWidget, deckDockWidget, Qt::Horizontal); diff --git a/cockatrice/src/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index f3ae2e293..40b07e792 100644 --- a/cockatrice/src/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -126,6 +126,13 @@ void TabDeckEditorVisual::createMenus() aPrintingSelectorDockFloating->setCheckable(true); connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { + printingSelectorDockMenu->setEnabled(false); + } + + connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, + [this](bool enabled) { printingSelectorDockMenu->setEnabled(!enabled); }); + viewMenu->addSeparator(); aResetLayout = viewMenu->addAction(QString()); @@ -236,6 +243,13 @@ void TabDeckEditorVisual::loadLayout() restoreGeometry(layouts.getDeckEditorGeometry()); } + if (SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { + if (!printingSelectorDockWidget->isHidden()) { + printingSelectorDockWidget->setHidden(true); + aPrintingSelectorDockVisible->setChecked(false); + } + } + aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden()); aFilterDockVisible->setChecked(!filterDockWidget->isHidden()); aDeckDockVisible->setChecked(!deckDockWidget->isHidden()); @@ -271,7 +285,7 @@ void TabDeckEditorVisual::restartLayout() aCardInfoDockVisible->setChecked(true); aDeckDockVisible->setChecked(true); aFilterDockVisible->setChecked(false); - aPrintingSelectorDockVisible->setChecked(true); + aPrintingSelectorDockVisible->setChecked(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); aCardInfoDockFloating->setChecked(false); aDeckDockFloating->setChecked(false); @@ -279,22 +293,21 @@ void TabDeckEditorVisual::restartLayout() aPrintingSelectorDockFloating->setChecked(false); setCentralWidget(centralWidget); - addDockWidget(Qt::RightDockWidgetArea, deckDockWidget); addDockWidget(Qt::RightDockWidgetArea, cardInfoDockWidget); addDockWidget(Qt::RightDockWidgetArea, filterDockWidget); addDockWidget(Qt::RightDockWidgetArea, printingSelectorDockWidget); + deckDockWidget->setVisible(true); + cardInfoDockWidget->setVisible(true); + filterDockWidget->setVisible(false); + printingSelectorDockWidget->setVisible(!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()); + deckDockWidget->setFloating(false); cardInfoDockWidget->setFloating(false); filterDockWidget->setFloating(false); printingSelectorDockWidget->setFloating(false); - deckDockWidget->setVisible(true); - cardInfoDockWidget->setVisible(true); - filterDockWidget->setVisible(false); - printingSelectorDockWidget->setVisible(true); - splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Vertical); splitDockWidget(cardInfoDockWidget, deckDockWidget, Qt::Horizontal); splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Horizontal); diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index 09941a2bd..9fefa58df 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -58,8 +58,8 @@ Vuoi salvare i cambiamenti? - - + + Error Errore @@ -92,12 +92,12 @@ Controlla se la cartella è valida e prova ancora. Il mazzo non può essere salvato. - + There are no cards in your deck to be exported Non ci sono carte da esportare nel tuo mazzo - + No deck was selected to be exported. Nessun mazzo da esportare è stato selezionato. @@ -118,12 +118,12 @@ Controlla se la cartella è valida e prova ancora. AllZonesCardAmountWidget - + Mainboard Mazzo - + Sideboard Sideboard @@ -131,142 +131,180 @@ Controlla se la cartella è valida e prova ancora. AppearanceSettingsPage - + + seconds + secondi + + + Error Errore - + Could not create themes directory at '%1'. Impossibile creare la cartella dei temi in '%1'. - + Theme settings Impostazioni temi - + Current theme: Tema attuale: - + Open themes folder Apri cartella temi - + + Home tab background source: + Sorgente dello sfondo della Home: + + + + Home tab background shuffle frequency: + Frequenza di modifica dello sfondo della Home: + + + + Disabled + Disattivato + + + Menu settings Impostazioni menù - + Show keyboard shortcuts in right-click menus Mostra scorciatoie da tastiera nel menù del tasto destro del mouse - + Card rendering Visualizzazione delle carte - + Display card names on cards having a picture Visualizza nome delle carte sopra le immagini - + Auto-Rotate cards with sideways layout Ruota automaticamente carte con disposizione orizzontale - + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] Sovrascrivi tutte le immagini delle carte con set personale (Comportamento pre-modifica Provider ID) [richiede riavvio] - + Bump sets that the deck contains cards from to the top in the printing selector Nel selettore di stampa, mostra per primi i set delle carte che il mazzo contiene - + Scale cards on mouse over Ingrandisci la carta sotto il mouse - + Use rounded card corners Usa i bordi delle carte arrotondati - + Minimum overlap percentage of cards on the stack and in vertical hand Sovrapposizione % minima delle carte in pila e nella mano verticale: - + Maximum initial height for card view window: Altezza iniziale massima per la finestra di visualizzazione delle carte: - - + + rows file - + Maximum expanded height for card view window: Altezza massima per la finestra di visualizzazione delle carte: - + Card counters Segnalini della carta - + Counter %1 Segnalino %1 - + Hand layout Disposizione della mano - + Display hand horizontally (wastes space) Disponi la mano orizzontalmente (spreca spazio) - + Enable left justification Allinea a sinistra - + Table grid layout Disposizione delle aree di gioco - + Invert vertical coordinate Inverti disposizione verticale - + Minimum player count for multi-column layout: Numero di giocatori minimo per disposizione multicolonna: - + Maximum font size for information displayed on cards: Dimensione massima carattere per le informazioni mostrate sulle carte: + + BackgroundSources + + + Theme + Tema + + + + Art crop of random card + Illustrazione di una carta casuale + + + + Art crop of background.cod deck file + Illustrazione di un file background.cod + + BanDialog @@ -406,32 +444,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardDatabaseModel - + Name Nome - + Sets Set - + Mana cost Costo - + Card type Tipo - + P/T F/C - + Color(s) Colore @@ -439,96 +477,96 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardFilter - + AND Logical conjunction operator used in card filter AND - + OR Logical disjunction operator used in card filter OR - + AND NOT Negated logical conjunction operator used in card filter AND NOT - + OR NOT Negated logical disjunction operator used in card filter OR NOT - + Name Nome - + Type Tipo - + Color Colore - + Text Testo - + Set Set - + Mana Cost Costo di mana - + Mana Value Valore di mana - + Rarity Rarità - + Power Forza - + Toughness Costituzione - + Loyalty Fedeltà - + Format Formato - + Main Type Tipo - + Sub Type Sottotipo @@ -536,22 +574,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardInfoFrameWidget - + Image Immagine - + Description Descrizione - + Both Entrambi - + View transformation Visualizza trasformazione @@ -559,22 +597,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardInfoPictureWidget - + View related cards Guarda carte correlate - + Add card to deck Aggiungi la carta al &mazzo - + Mainboard Mazzo - + Sideboard Sideboard @@ -598,16 +636,128 @@ Questa è visibile solo ai moderatori e non alla persona bannata. - CardItem + CardMenu - - &Move to - &Metti in + + Re&veal to... + Ri&vela a... - - &Power / toughness - &Forza / Costituzione + + &All players + &Tutti i giocatori + + + + View related cards + Guarda carte correlate + + + + Token: + Pedina: + + + + All tokens + Tutte le pedine + + + + &Select All + &Seleziona tutto + + + + S&elect Row + S&eleziona fila + + + + S&elect Column + S&eleziona colonna + + + + &Play + &Gioca + + + + &Hide + &Nascondi + + + + Play &Face Down + Gioca a &faccia in giù + + + + &Tap / Untap + Turn sideways or back again + &TAPpa/STAPpa + + + + Toggle &normal untapping + Blocca/Sblocca &STAP normale + + + + T&urn Over + Turn face up/face down + Capovolgi + + + + &Peek at card face + &Sbircia la faccia della carta + + + + &Clone + &Copia + + + + Attac&h to card... + Asse&gna alla carta... + + + + Unattac&h + Tog&li + + + + &Draw arrow... + &Disegna una freccia... + + + + &Set annotation... + &Imposta note... + + + + Ca&rd counters + Segnalini delle ca&rte + + + + &Add counter (%1) + &Aggiungi segnalino (%1) + + + + &Remove counter (%1) + &Rimuovi segnalino (%1) + + + + &Set counters (%1)... + Imposta &segnalini (%1)... @@ -619,135 +769,135 @@ Questa è visibile solo ai moderatori e non alla persona bannata. - CardZone + CardZoneLogic - + their hand nominative la sua mano - + %1's hand nominative Mano di %1 - + their library look at zone il suo grimorio - + %1's library look at zone Grimorio di %1 - + of their library top cards of zone, del suo grimorio - + of %1's library top cards of zone del grimorio di %1 - + their library reveal zone il suo grimorio - + %1's library reveal zone Grimorio di %1 - + their library shuffle il suo grimorio - + %1's library shuffle Grimorio di %1 - + their library nominative il suo grimorio - + %1's library nominative Grimorio di %1 - + their graveyard nominative il suo cimitero - + %1's graveyard nominative Cimitero di %1 - + their exile nominative la sua zona di esilio - + %1's exile nominative Esilio di %1 - + their sideboard look at zone la sua sideboard - + %1's sideboard look at zone Sideboard di %1 - + their sideboard nominative la sua sideboard - + %1's sideboard nominative Sideboard di %1 - + their custom zone '%1' nominative la sua zona personalizzata '%1' - + %1's custom zone '%2' nominative la zona personalizzata '%2' di %1 @@ -756,7 +906,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CockatriceXml3Parser - + Parse error at line %1 col %2: Errore di lettura alla riga %1 posizione %2: @@ -764,11 +914,25 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CockatriceXml4Parser - + Parse error at line %1 col %2: Errore di lettura alla riga %1 posizione %2: + + CustomZoneMenu + + + C&ustom Zones + &Zone personalizzate + + + + + View custom zone '%1' + Visualizza la zona personalizzata '%1' + + DeckEditorCardInfoDockWidget @@ -785,42 +949,42 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Cerca per nome della carta (o espressioni di ricerca) - + Add to Deck Aggiungi al mazzo - + Add to Sideboard Aggiungi alla sideboard - + Select Printing Seleziona stampa - - Show on EDHREC (Commander) - Mostra su EDHREC (Commander) - - - - Show on EDHREC (Card) - Mostra su EDHREC (Carta) + + Show on EDHRec (Commander) + Mostra su EDHRec (Commander) + Show on EDHRec (Card) + Mostra su EDHRec (Carta) + + + Show Related cards Mostra carte correlate - + Add card to &maindeck Aggiungi carta al &mazzo - + Add card to &sideboard Aggiungi carta alla &sideboard @@ -848,67 +1012,67 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Colori - + Select Printing Seleziona stampa - + Deck Mazzo - + Deck &name: &Nome mazzo: - + Banner Card/Tags Visibility Settings Impostazioni carta copertina/etichette - + Show banner card selection menu Visualizza menu di selezione carta copertina - + Show tags selection menu Visualizza menu di selezione etichette - + &Comments: &Commenti: - + Group by: Raggruppa per: - + Hash: Hash: - + &Increment number &Aumenta il numero - + &Decrement number &Diminuisci il numero - + &Remove row &Rimuovi carta - + Swap card to/from sideboard Sposta carta in maindeck/sideboard @@ -934,109 +1098,114 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorMenu - + &Deck Editor &Editor dei mazzi - + &New deck &Nuovo mazzo - + &Load deck... &Carica mazzo... - + Load recent deck... Carica mazzo recente... - + Clear Svuota - + &Save deck &Salva mazzo - + Save deck &as... Salva mazzo &con nome... - + Load deck from cl&ipboard... Carica mazzo dagli app&unti... - + Edit deck in clipboard Modifica mazzo negli appunti - + Annotated Con annotazioni - - + + Not Annotated Senza annotazioni - + Save deck to clipboard Salva il mazzo negli appunti - + Annotated (No set info) Con annotazioni (senza info set) - + Not Annotated (No set info) Senza annotazioni (senza info set) - + &Print deck... Stam&pa mazzo... - + + Load deck from online service... + Carica mazzo dal servizio online... + + + &Send deck to online service &Invia mazzo al servizio online - + Create decklist (decklist.org) Crea una decklist (decklist.org) - + Create decklist (decklist.xyz) Crea una decklist (decklist.xyz) - + Analyze deck (deckstats.net) Analizza mazzo (deckstats.net) - + Analyze deck (tappedout.net) Analizza mazzo (tappedout.net) - + &Close &Chiudi @@ -1052,166 +1221,166 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckEditorSettingsPage - - + + Update Spoilers Aggiorna Spoiler - - + + Success Fatto - + Download URLs have been reset. Gli indirizzi di download sono stati resettati. - + Downloaded card pictures have been reset. Le immagini delle carte scaricate sono state eliminate. - + Error Errore - + One or more downloaded card pictures could not be cleared. Non è stato possibile eliminare alcune delle immagini delle carte scaricate. - + Add URL Aggiungi indirizzo - - + + URL: Indirizzo: - - + + Edit URL Modifica indirizzo: - + Network Cache Size: Dimensione cache di rete: - + Redirect Cache TTL: TTL cache dei reindirizzamenti: - + How long cached redirects for urls are valid for. Per quanto tempo sono validi i reindirizzamenti per gli URL memorizzati nella cache. - + Picture Cache Size: Dimensione cache immagini: - + Add New URL Aggiungi indirizzo URL - + Remove URL Elimina indirizzo URL - + Day(s) Giorno/i - + Updating... Aggiornando... - + Choose path Scegli il percorso - + URL Download Priority Ordine di priorità degli indirizzi - + Spoilers Spoiler - + Download Spoilers Automatically Scarica spoiler automaticamente - + Spoiler Location: Indirizzo spoiler: - + Last Change Ultima modifica - + Spoilers download automatically on launch Scarica spoiler automaticamente all'avvio - + Press the button to manually update without relaunching Premi il pulsante per aggiornare manualmente senza riavviare - + Do not close settings until manual update is complete Non chiudere le impostazioni fino a che l'aggiornamento manuale sia completato - + Download card pictures on the fly Scarica immagini delle carte in tempo reale - + How to add a custom URL Come aggiungere indirizzi personalizzati - + Delete Downloaded Images Elimina immagini scaricate - + Reset Download URLs Resetta indirizzi di download - + On-disk cache for downloaded pictures Cache su disco immagini scaricate - + In-memory cache for pictures not currently on screen Cache in memoria per immagini non attualmente su schermo @@ -1219,27 +1388,27 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckListModel - + Count Quantità - + Set Set - + Number Numero - + Provider ID ID Provider - + Card Carta @@ -1247,12 +1416,12 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckLoader - + Common deck formats (%1) Formati di mazzo comuni (%1) - + All files (*.*) Tutti i file (*.*) @@ -1354,89 +1523,89 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Carta copertina - + Open in deck editor Apri nell'editor dei mazzi - + Edit Tags Modifica etichette - + Rename Deck Rinomina mazzo - + Save Deck to Clipboard Salva il mazzo negli appunti - + Annotated Con annotazioni - + Annotated (No set info) Con annotazioni (senza info set) - + Not Annotated Senza annotazioni - + Not Annotated (No set info) Senza annotazioni (senza info set) - + Rename File Rinomina file - + Delete File Elimina file - + Set Banner Card Imposta carta copertina - - + + New name: Nuovo nome: - - + + Error Errore - + Rename failed Rinomina non riuscita - + Delete file Elimina file - + Are you sure you want to delete the selected file? Vuoi davvero eliminare il file selezionato? - + Delete failed Eliminazione non riuscita @@ -1444,13 +1613,13 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckStatsInterface - - + + Error Errore - + The reply from the server could not be parsed. La risposta del server non può essere analizzata. @@ -1458,71 +1627,76 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckViewContainer - + Load deck... Carica mazzo... - + Load remote deck... Carica mazzo remoto... - + Load from clipboard... Carica dagli appunti... - + + Load from website... + Carica dal sito... + + + Unload deck Unload deck... Deseleziona mazzo - + Ready to start Pronto ad iniziare - + Force start Forza avvio - + Sideboard unlocked Sideboard sbloccata - + Sideboard locked Sideboard bloccata - - + + Error Errore - + The selected file could not be loaded. I file selezionati non posso essere caricati. - + Deck is greater than maximum file size. Il mazzo è più grande della dimensione massima del file consentita. - + Are you sure you want to force start? This will kick all non-ready players from the game. Sicuro di voler forzare l'avvio? Ciò espellerà dalla partita tutti i giocatori che non sono pronti. - + Cockatrice Cockatrice @@ -1781,32 +1955,37 @@ Vuoi convertire il mazzo al formato .cod? Punti vita iniziali: - + + Open decklists in lobby + Apri mazzi nella lobby + + + Game setup options Configurazione partita - + &Clear Pulisci - + Create game Crea partita - + Game information Informazioni partita - + Error Errore - + Server error. Errore del server. @@ -2248,77 +2427,82 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza Nascondi partite non create dagli amici - + + Hide games with forced open decklists + Nascondi partite con liste di mazzo rivelate + + + &Newer than: &Più nuovo di: - + Game &description: &Descrizione partita: - + &Creator name: Nome &creatore: - + General Generale - + &Game types Tipi di &partita - + at &least: Minimo: - + at &most: Massimo: - + Maximum player count Numero di giocatori - + Restrictions Restrizioni - + Show games only if &spectators can watch Mostra le partite solo se &gli spettatori possono guardare - + Show spectator password p&rotected games Mostra partite protette da password - + Show only if spectators can ch&at Mostra le partite solo se gli spettatori possono guardare - + Show only if spectators can see &hands Mostra le partite solo se gli spettatori possono guardare le mani - + Spectators Spettatori - + Filter games Filtro partite @@ -2527,6 +2711,64 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza Lista del mazzo non valida. + + DlgLoadDeckFromWebsite + + + Paste a link to a decklist site here to import it. +(Archidekt, Deckstats, Moxfield, and TappedOut are supported.) + Incolla il link a una lista di mazzo online per importarla. +(Sono supportati Archidekt, Deckstats, Moxfield, e TappedOut). + + + + + + + + Load Deck from Website + Carica Mazzo dal Sito + + + + No parser available for this deck provider. + (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) + Impossibile acquisire lista di mazzo da questo sito. +(Sono supportati Archidekt, Deckstats, Moxfield, e TappedOut). + + + + Network error: %1 + Errore di rete: %1 + + + + Received empty deck data. + Ricevuta lista di mazzo vuota. + + + + Failed to parse deck data: %1 + Impossibile acquisire i dati del mazzo: %1 + + + + The provided URL is not recognized as a valid deck URL. +Valid deck URLs look like this: + +https://archidekt.com/decks/9999999 +https://deckstats.net/decks/99999/9999999-your-deck-name/en +https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz +https://tappedout.net/mtg-decks/your-deck-name/ + L'URL fornito non è riconosciuto come valido. +Un URL valido assomiglia a uno di questi: + +https://archidekt.com/decks/9999999 +https://deckstats.net/decks/99999/9999999-nome-del-tuo-mazzo/en +https://moxfield.com/decks/XYZxx-XYZ99Yyy-xyzXzzz +https://tappedout.net/mtg-decks/nome-del-tuo-mazzo/ + + DlgLoadRemoteDeck @@ -2715,12 +2957,12 @@ La tua email verrà utilizzata per verificare il tuo account. DlgSettings - + Unknown Error loading card database Errore sconosciuto durante il caricamento del database delle carte - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -2737,7 +2979,7 @@ Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database version is too old. This can cause problems loading card information or images @@ -2754,7 +2996,7 @@ Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -2766,7 +3008,7 @@ Per favore crea un ticket di assistenza su https://github.com/Cockatrice/Cockatr Desideri modificare l'impostazione della posizione del database? - + File Error loading your card database. Would you like to change your database location setting? @@ -2775,7 +3017,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -2784,7 +3026,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -2797,59 +3039,59 @@ https://github.com/Cockatrice/Cockatrice/issues Desideri modificare l'impostazione della posizione del database? - - - + + + Error Errore - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella del mazzo non è valido. Vuoi tornare in dietro e impostare il percorso corretto? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella delle immagini delle carte è invilido. Vuoi tornare indietro e impostare il percorso corretto? - + Settings Impostazioni - + General Generale - + Appearance Aspetto - + User Interface Interfaccia - + Card Sources Immagini carte - + Chat Chat - + Sound Suoni - + Shortcuts Scorciatoie @@ -3207,7 +3449,7 @@ Dovrai scaricare la nuova versione manualmente. FilterBuilder - + Type your filter here Scrivi il filtro qui @@ -3235,123 +3477,146 @@ Dovrai scaricare la nuova versione manualmente. Impossibile eliminare il filtro '%1'. + + GameEventHandler + + + kicked by game host or moderator + espulso da proprietario del gioco o moderatore + + + + player left the game + il giocatore ha lasciato la partita + + + + player disconnected from server + il giocatore si è disconnesso dal server + + + + reason unknown + ragione sconosciuta + + GameSelector - - - - - - - - - + + + + + + + + + Error Errore - + Please join the appropriate room first. Si prega di entrare prima in una stanza adeguata. - + Wrong password. Password errata. - + Spectators are not allowed in this game. Spettatori non ammessi in questa partita. - + The game is already full. La partita è piena. - + The game does not exist any more. Questa partita non esiste più. - + This game is only open to registered users. Questa partita è solo per utenti registrati. - + This game is only open to its creator's buddies. Questa stanza è aperta solo agli amici del suo creatore. - + You are being ignored by the creator of this game. Sei stato ingnorato dal creatore di questa partita. - + Join Game Entra nella partita - + Spectate Game Osserva partita - + Game Information Informazioni partita - + Join game Entra nella partita - + Password: Password: - + Please join the respective room first. Si prega di entrare prima nella rispettiva stanza. - + &Filter games &Filtra partite - + C&lear filter E&limina filtri - + C&reate Cr&ea - + &Join &Entra - + J&oin as spectator Entra c&ome spettatore - + Games shown: %1 / %2 Partite mostrate: %1 / %2 - + Games Partite @@ -3396,63 +3661,68 @@ Dovrai scaricare la nuova versione manualmente. solo utenti registrati - - + + open decklists + Apri impostazioni + + + + can chat può chattare - + see hands vede mani - + can see hands può vedere mani - + not allowed non ammessi - + Room Stanza - + Age Età - + Description Descrizione - + Creator Creatore - + Type Tipo - + Restrictions Restrizioni - + Players Giocatori - + Spectators Spettatori @@ -3460,147 +3730,474 @@ Dovrai scaricare la nuova versione manualmente. GeneralSettingsPage - + Reset all paths Reimposta tutti i percorsi - + All paths have been reset I percorsi sono stati resettati - - - - - - - + + + + + + + Choose path Seleziona il percorso - + Personal settings Impostazioni personali - + Language: Lingua: - + Paths (editing disabled in portable mode) Destinazioni (non si può personalizzare in modalità portatile) - + Paths Percorsi - + How to help with translations Come aiutare con le traduzioni - + Decks directory: Cartella mazzi: - + Filters directory: Cartella filtri: - + Replays directory: Cartella replay: - + Pictures directory: Cartella immagini: - + Card database: Database carte: - + Custom database directory: Cartella database personalizzata: - + Token database: Database pedine: - + Update channel Canale di aggiornamento: - + Check for client updates on startup Controlla gli aggiornamenti del client all'avvio - + Check for card database updates on startup Controlla gli aggiornamenti delle carte all'avvio - + Don't check Non controllare - + Prompt for update Chiedi se aggiornare - + Always update in the background Aggiorna sempre in background - + Check for card database updates every Controlla gli aggiornamenti delle carte ogni - + days giorni - + Notify if a feature supported by the server is missing in my client Avvisami se una funzionalità supportata dal server manca nel mio programma - + Automatically run Oracle when running a new version of Cockatrice Avvia automaticamente Oracle se Cockatrice è stato aggiornato - + Show tips on startup Mostra suggerimenti all'avvio - + Last update check on %1 (%2 days ago) Ultimo controllo %1 (%2 giorni fa) + + GraveyardMenu + + + &Graveyard + &Cimitero + + + + &View graveyard + &Guarda il cimitero + + + + &Move graveyard to... + &Muovi cimitero in... + + + + &Top of library + &Cima al grimorio + + + + &Bottom of library + &Fondo al grimorio + + + + &Hand + &Mano + + + + &Exile + &Esilio + + + + Reveal random card to... + Rivela carta casuale a... + + + + HandMenu + + + &Hand + &Mano + + + + &View hand + &Vedi mano + + + + &Sort hand + &Ordina mano + + + + Take &mulligan + Mu&lliga + + + + &Move hand to... + &Sposta mano in... + + + + &Top of library + &Cima al grimorio + + + + &Bottom of library + &Fondo al grimorio + + + + &Graveyard + &Cimitero + + + + &Exile + &Esilio + + + + &Reveal hand to... + &Rivela mano a... + + + + Reveal r&andom card to... + Rivela carta c&asuale a... + + + + HomeWidget + + + Create New Deck + Crea un nuovo mazzo + + + + Browse Decks + Esplora i mazzi + + + + Browse Card Database + Esplora il database delle carte + + + + Browse EDHRec + Esplora EDHRec + + + + View Replays + Guarda i replay + + + + Quit + Esci + + + + Connecting... + Connessione in corso... + + + + Connect + Connetti + + + + Play + Gioca + + + + LibraryMenu + + + &Library + &Grimorio + + + + &View library + &Guarda il grimorio + + + + View &top cards of library... + Guarda &le carte in cima al grimorio... + + + + View bottom cards of library... + Guarda le carte in fondo al grimorio... + + + + Reveal &library to... + Rive&la grimorio a... + + + + Lend library to... + Presta il grimorio a... + + + + Reveal &top cards to... + Rivela le &prime carte a... + + + + &Top of library... + &In cima al grimorio... + + + + &Bottom of library... + &In fondo al grimorio... + + + + &Always reveal top card + Rivela &sempre la prima carta + + + + &Always look at top card + &Guarda sempre la prima carta + + + + &Open deck in deck editor + &Apri il mazzo nell'editor dei mazzi + + + + &Draw card + &Pesca una carta + + + + D&raw cards... + P&esca carte... + + + + &Undo last draw + &Annulla l'ultima pescata + + + + Shuffle + Mescola + + + + &Play top card + &Gioca la prima carta + + + + Play top card &face down + Gioca la prima carta a &faccia in giù + + + + Put top card on &bottom + Metti la prima carta in fondo + + + + Move top card to grave&yard + Metti la prima carta nel c&imitero + + + + Move top card to e&xile + E&silia la prima carta + + + + Move top cards to &graveyard... + Metti le prime carte nel &cimitero... + + + + Move top cards to &exile... + &Esilia le prime carte... + + + + Put top cards on stack &until... + Metti le carte in cima alla pila fino a... + + + + Shuffle top cards... + Mescola le carte in cima... + + + + &Draw bottom card + &Pesca l'ultima carta dal fondo + + + + D&raw bottom cards... + P&esca carte dal fondo... + + + + &Play bottom card + &Gioca la carta in fondo + + + + Play bottom card &face down + Gioca la carta in fondo a &faccia in giù + + + + Move bottom card to grave&yard + Metti l'ultima carta nel c&imitero + + + + Move bottom card to e&xile + Metti l'ultima carta in esilio + + + + Move bottom cards to &graveyard... + Metti le ultime carte nel cimitero... + + + + Move bottom cards to &exile... + &Esilia le ultime carte... + + + + Put bottom card on &top + Metti l'ultima carta in cima + + + + Shuffle bottom cards... + Mescola le carte in fondo... + + MainWindow @@ -4463,586 +5060,591 @@ Il database delle carte verrà ricaricato. MessageLogWidget - + from play dal campo di battaglia - + from their graveyard dal suo cimitero - + from exile dall'esilio - + from their hand dalla sua mano - + the top card of %1's library la prima carta del grimorio di %1 - + the top card of their library la prima carta del suo grimorio - + from the top of %1's library dalla cima del grimorio di %1 - + from the top of their library dalla cima del suo grimorio - + the bottom card of %1's library l'ultima carta del grimorio di %1 - + the bottom card of their library l'ultima carta del suo grimorio - + from the bottom of %1's library dal fondo del grimorio di %1 - + from the bottom of their library dal fondo del suo grimorio - + from %1's library dal grimorio di %1 - + from their library dal suo grimorio - + from sideboard dalla sideboard - + from the stack dalla pila - + from custom zone '%1' dalla zona personalizzata '%1' - + %1 is now keeping the top card %2 revealed. %1 sta tenendo la prima carta %2 rivelata. - + %1 is not revealing the top card %2 any longer. %1 non sta più rivelando la prima carta %2. - + %1 can now look at top card %2 at any time. %1 può guardare la prima carta %2 della libreria in qualunque momento. - + %1 no longer can look at top card %2 at any time. %1 non può più guardare la prima carta %2 del mazzo in qualunque momento. - + %1 attaches %2 to %3's %4. %1 assegna %2 a %4 di %3. - + %1 has conceded the game. %1 ha concesso la partita. - + %1 has unconceded the game. %1 ha annullato la concessione della partita. - + %1 has restored connection to the game. %1 ha ripristinato il collegamento alla partita. - + %1 has lost connection to the game. %1 ha perso il collegamento alla partita. - + %1 points from their %2 to themselves. %1 disegna una freccia dal suo %2 a sé stesso. - + %1 points from their %2 to %3. %1 disegna una freccia dal suo %2 a %3. - + %1 points from %2's %3 to themselves. %1 disegna una freccia dal %3 di %2 a sé stesso. - + %1 points from %2's %3 to %4. %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from their %2 to their %3. %1 disegna una freccia dal suo %2 al suo %3. - + %1 points from their %2 to %3's %4. %1 disegna una freccia dal suo %2 a %4 di %3. - + %1 points from %2's %3 to their own %4. %1 disegna una freccia da %3 di %2 al suo %4. - + %1 points from %2's %3 to %4's %5. %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 creates a face down token. %1 crea una pedina a faccia in giù. - + %1 creates token: %2%3. %1 crea una pedina: %2%3. - + %1 has loaded a deck (%2). %1 ha caricato un mazzo (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 ha caricato un mazzo con %2 carte nella sideboard (%3). - + %1 destroys %2. %1 distrugge %2. - + a card una carta - + %1 gives %2 control over %3. %1 da il controllo di %3 a %2. - + %1 puts %2 into play%3 face down. %1 mette %2 sul campo di battaglia %3 a faccia in giù. - + %1 puts %2 into play%3. %1 mette %2 sul campo di battaglia%3. - + %1 puts %2%3 into their graveyard. %1 mette %2 nel suo cimitero%3. - + %1 exiles %2%3. %1 esilia %2%3. - + %1 moves %2%3 to their hand. %1 mette %2 in mano%3. - + %1 puts %2%3 into their library. %1 mette %2 nel suo grimorio%3. - + %1 puts %2%3 onto the bottom of their library. %1 mette %2%3 in fondo al proprio grimorio. - + %1 puts %2%3 on top of their library. %1 mette %2 in cima al suo grimorio%3. - + %1 puts %2%3 into their library %4 cards from the top. %1 mette %2%3 nel suo grimorio %4 carte dalla cima. - + %1 moves %2%3 to sideboard. %1 mette %2 nella sideboard%3. - + %1 plays %2%3. %1 gioca %2%3. - + %1 moves %2%3 to custom zone '%4'. %1 mette %2 nella zona personalizzata '%4'%3. - + %1 tries to draw from an empty library %1 prova a pescare da un grimorio vuoto - + %1 draws %2 card(s). %1 pesca una carta.%1 pesca %2 carte.%1 pesca %2 carte. - + %1 is looking at %2. %1 sta guardando %2. - + %1 is looking at the %4 %3 card(s) %2. %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural %1 sta guardando %3 carta %4 %2.%1 sta guardando %3 carte %4 %2.%1 sta guardando %3 carte %4 %2. - + bottom in fondo - + top in cima - + %1 turns %2 face-down. %1 gira %2 a faccia in giù. - + %1 turns %2 face-up. %1 gira %2 a faccia in su. - + The game has been closed. La partita è stata chiusa. - + The game has started. La partita è iniziata. - + + You are flooding the game. Please wait a couple of seconds. + Stai spammando la partita. Attendi un paio di secondi. + + + %1 has joined the game. %1 è entrato nella partita. - + %1 is now watching the game. %1 sta osservando la partita. - + You have been kicked out of the game. Sei stato cacciato dalla partita. - + %1 has left the game (%2). %1 ha abbandonato la partita (%2). - + %1 is not watching the game any more (%2). %1 non sta più guardando la partita (%2). - + %1 is not ready to start the game any more. %1 non è più pronto a iniziare la partita. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischia il proprio mazzo e pesca una nuova mano di %2 carta.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte. - + %1 shuffles their deck and draws a new hand. %1 mischia il proprio mazzo e pesca una nuova mano. - + You are watching a replay of game #%1. Stai guardando il replay della partita #%1. - + %1 is ready to start the game. %1 è pronto a iniziare la partita. - + cards an unknown amount of cards carte - + %1 card(s) a card for singular, %1 cards for plural una carta%1 carte%1 carte - + %1 lends %2 to %3. %1 presta %2 a %3. - + %1 reveals %2 to %3. %1 rivela %2 a %3. - + %1 reveals %2. %1 rivela %2. - + %1 randomly reveals %2%3 to %4. %1 rivela a caso %2%3 a %4. - + %1 randomly reveals %2%3. %1 rivela a caso %2%3. - + %1 peeks at face down card #%2. %1 sbircia la carta a faccia in giù #%2. - + %1 peeks at face down card #%2: %3. %1 sbircia la carta a faccia in giù #%2: %3. - + %1 reveals %2%3 to %4. %1 rivela %2%3 a %4. - + %1 reveals %2%3. %1 rivela %2%3. - + %1 reversed turn order, now it's %2. %1 ha rovesciato l'ordine dei turni, ora è %2. - + reversed invertito - + normal normale - + Heads Testa - + Tails Croce - + %1 flipped a coin. It landed as %2. %1 ha lanciato una moneta. Il risultato è %2. - + %1 rolls a %2 with a %3-sided die. %1 lancia un dado a %3 facce e ottiene %2. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 lancia %2 monete. Sono uscite %3 teste e %4 croci. - + %1 rolls a %2-sided dice %3 times: %4. %1 lancia un dado a %2 facce %3 volte: %4. - + %1's turn. Turno di %1 - + %1 sets annotation of %2 to %3. %1 imposta le note di %2 a %3. - + %1 places %2 "%3" counter(s) on %4 (now %5). %1 mette %2 segnalino "%3" su %4 (totale %5).%1 mette %2 segnalini "%3" su %4 (totale %5).%1 mette %2 segnalino(i) "%3" su %4 (totale %5). - + %1 removes %2 "%3" counter(s) from %4 (now %5). %1 toglie %2 segnalino "%3" su %4 (totale %5).%1 toglie %2 segnalini "%3" su %4 (totale %5).%1 toglie %2 segnalino(i) "%3" su %4 (totale %5). - + %1 sets counter %2 to %3 (%4%5). %1 imposta il contatore %2 a %3 (%4%5). - + %1 sets %2 to not untap normally. %1 imposta che %2 non STAPpi normalmente. - + %1 sets %2 to untap normally. %1 imposta che %2 STAPpi normalmente. - + %1 removes the PT of %2. %1 elimina i valori F/C di %2. - + %1 changes the PT of %2 from nothing to %4. %1 cambia F/C di %2 da vuota a %4. - + %1 changes the PT of %2 from %3 to %4. %1 cambia F/C di %2 da %3 a %4. - + %1 has locked their sideboard. %1 ha bloccato la sua sideboard. - + %1 has unlocked their sideboard. %1 ha sbloccato la sua sideboard. - + %1 taps their permanents. %1 TAPpa i suoi permanenti. - + %1 untaps their permanents. %1 STAPpa i suoi permanenti. - + %1 taps %2. %1 TAPpa %2. - + %1 untaps %2. %1 STAPpa %2. - + %1 shuffles %2. %1 mescola %2. - + %1 shuffles the bottom %3 cards of %2. %1 mescola le %3 carte sul fondo da %2. - + %1 shuffles the top %3 cards of %2. %1 mescola le prime %3 carte da %2. - + %1 shuffles cards %3 - %4 of %2. %1 mescola le carte %3 - %4 da %2. - + %1 unattaches %2. %1 toglie %2. - + %1 undoes their last draw. %1 annulla la sua ultima pescata. - + %1 undoes their last draw (%2). %1 annulla la sua ultima pescata (%2). @@ -5050,163 +5652,201 @@ Il database delle carte verrà ricaricato. MessagesSettingsPage - + Word1 Word2 Word3 Parola1 Parola2 Parola3 - + Add New Message Aggiungi Nuovo Messaggio - + Edit Message Modifica Messaggio - + Remove Message Rimuovi Messaggio - + Add message Aggiungi messaggio - - + + Message: Messaggio: - + Edit message Modifica messaggio - + Chat settings Impostazioni chat - + Custom alert words Lista parole evidenziate - + Enable chat mentions Abilita menzioni in chat - + Enable mention completer Abilita completamento menzioni - + In-game message macros Messaggi rapidi in partita - + How to use in-game message macros Come usare i messaggi macro in gioco - + Ignore chat room messages sent by unregistered users Ignora i messaggi in chat inviati dagli utenti non registrati - + Ignore private messages sent by unregistered users Ignora i messaggi privati inviati dagli utenti non registrati - - + + Invert text color Inverti colore testo - + Enable desktop notifications for private messages Abilita notifiche desktop per i messaggi privati - + Enable desktop notification for mentions Abilita notifiche sul desktop per le menzioni - + Enable room message history on join Abilita messaggi recenti all'ingresso - - + + (Color is hexadecimal) (Colore in esadecimale) - + Separate words with a space, alphanumeric characters only Separare le parole con uno spazio; solo caratteri alfanumerici + + MoveMenu + + + Move to + &Metti in + + + + &Top of library in random order + &In cima al grimorio in ordine casuale + + + + X cards from the top of library... + Posizione X dalla cima del grimorio... + + + + &Bottom of library in random order + In &fondo al grimorio in ordine casuale + + + + &Hand + &Mano + + + + &Graveyard + &Cimitero + + + + &Exile + &Esilio + + Mtg - + Card Type Tipo - + Mana Value Valore di Mana - + Color(s) Colore - + Loyalty Fedeltà - + Main Card Type Tipo principale - + Mana Cost Costo di mana - + P/T F/C - + Side Lato - + Layout Disposizione - + Color Identity Identità di colore @@ -5353,703 +5993,207 @@ Il database delle carte verrà ricaricato. PictureLoader - + en code for scryfall's language property, not available for all languages it - Player + PlayerActions - - Reveal top cards of library - Rivela le prime carte del grimorio - - - - - - - - - - - - - - - Number of cards: (max. %1) - Numero di carte: (max %1) - - - - &View graveyard - &Guarda il cimitero - - - - &View exile - &Guarda la zona di esilio - - - - Player "%1" - Giocatore "%1" - - - - - - - &Graveyard - &Cimitero - - - - - - - &Exile - &Esilio - - - - &Move hand to... - &Sposta mano in... - - - - - - &Top of library - &Cima al grimorio - - - - - - &Bottom of library - &Fondo al grimorio - - - - &Move graveyard to... - &Muovi cimitero in... - - - - - - - &Hand - &Mano - - - - &Move exile to... - &Muovi esilio in... - - - - &View library - &Guarda il grimorio - - - - &View hand - &Vedi mano - - - - View &top cards of library... - Guarda &le carte in cima al grimorio... - - - - Reveal &library to... - Rive&la grimorio a... - - - - &Always reveal top card - Rivela &sempre la prima carta - - - - &View sideboard - &Guarda la sideboard - - - - &Draw card - &Pesca una carta - - - - D&raw cards... - P&esca carte... - - - - &Undo last draw - &Annulla l'ultima pescata - - - - Take &mulligan - Mu&lliga - - - - Play top card &face down - Gioca la prima carta a faccia in giù - - - - Move top card to grave&yard - Metti la prima carta nel c&imitero - - - - Move top card to e&xile - E&silia la prima carta - - - - Move top cards to &graveyard... - Metti le prime carte nel &cimitero... - - - - Move top cards to &exile... - &Esilia le prime carte... - - - - Put top card on &bottom - Metti la prima carta in &fondo - - - - View bottom cards of library... - Guarda le carte in fondo al grimorio... - - - - Lend library to... - Presta il grimorio a... - - - - Shuffle - Mescola - - - - Put top cards on stack &until... - Take top cards &until... - Metti le carte in cima alla pila fino a... - - - - Shuffle top cards... - Mescola le carte in cima... - - - - Shuffle bottom cards... - Mescola le carte in fondo... - - - - &Reveal hand to... - &Rivela mano a... - - - - Reveal r&andom card to... - Rivela carta c&asuale a... - - - - Reveal random card to... - Rivela carta casuale a... - - - - &Sideboard - &Sideboard - - - - &Library - &Grimorio - - - - &Counters - &Contatori - - - - C&ustom Zones - &Zone personalizzate - - - - - View custom zone '%1' - Visualizza la zona personalizzata '%1' - - - - &Untap all permanents - &STAPpa tutti i permanenti - - - - R&oll die... - L&ancia un dado... - - - - &Create token... - &Crea una pedina... - - - - C&reate another token - C&rea un'altra pedina - - - - Cr&eate predefined token - Cr&ea pedina predefinita - - - - Ca&rd counters - Segnalini delle ca&rte - - - - - &All players - &Tutti i giocatori - - - - S&ay - P&arla - - - - &Select All - Seleziona tutto - - - - S&elect Row - Seleziona fila - - - - S&elect Column - Seleziona colonna - - - - &Play - &Gioca - - - - &Hide - &Nascondi - - - - Play &Face Down - Gioca a &faccia in giù - - - - &Tap / Untap - Turn sideways or back again - &TAPpa/STAPpa - - - - Toggle &normal untapping - Blocca/Sblocca &STAP normale - - - - T&urn Over - Turn face up/face down - Capovolgi - - - - &Peek at card face - &Sbircia la faccia della carta - - - - &Clone - &Copia - - - - Attac&h to card... - Asse&gna alla carta... - - - - Unattac&h - Tog&li - - - - &Draw arrow... - &Disegna una freccia... - - - - &Increase power - &Aumenta forza - - - - &Decrease power - &Diminuisci forza - - - - I&ncrease toughness - A&umenta costituzione - - - - D&ecrease toughness - D&iminuisci costituzione - - - - In&crease power and toughness - Au&menta forza e costituzione - - - - Dec&rease power and toughness - Dim&inuisci forza e costituzione - - - - Increase power and decrease toughness - Aumenta forza e diminuisci costituzione - - - - Decrease power and increase toughness - Diminuisci forza e aumenta costituzione - - - - Set &power and toughness... - Imposta &forza e costituzione... - - - - Reset p&ower and toughness - Resetta &forza e costituzione - - - - &Set annotation... - &Imposta note... - - - - &Add counter (%1) - &Aggiungi segnalino (%1) - - - - &Remove counter (%1) - &Rimuovi segnalino (%1) - - - - &Set counters (%1)... - Imposta &segnalini (%1)... - - - - &Top of library in random order - &In cima al mazzo in ordine casuale - - - - X cards from the top of library... - Posizione X dalla cima del grimorio... - - - - &Bottom of library in random order - In &fondo al grimorio in ordine casuale - - - + View top cards of library Guarda le carte in cima al grimorio - + + + + + + + + + + + + Number of cards: (max. %1) + Numero di carte: (max %1) + + + View bottom cards of library Guarda le carte in fondo al grimorio - + Shuffle top cards of library Mescola le carte in cima al grimorio - + Shuffle bottom cards of library Mescola le carte in fondo al grimorio - - Which position should this card be placed: - In che posizione dovrebbe essere messa questa carta: - - - - (max. %1) - (max %1) - - - + Draw hand Pesca mano - + 0 and lower are in comparison to current hand size 0 e inferiore sono relativi al numero attuale di carte in mano - + Draw cards Pesca carte - - - Number: - Numero: - - - + Move top cards to grave Metti le prime carte nel cimitero - - Reveal &top cards to... - Rivela le &prime carte a... - - - - &Top of library... - &In cima al grimorio... - - - - &Bottom of library... - &In fondo al grimorio... - - - - &Always look at top card - &Vedi sempre la prima carta - - - - &Open deck in deck editor - &Apri il mazzo nell'editor dei mazzi - - - - &Play top card - &Gioca la prima carta - - - - &Draw bottom card - &Pesca l'ultima carta dal fondo - - - - D&raw bottom cards... - P&esca carte dal fondo... - - - - &Play bottom card - &Gioca l'ultima carta dal fondo - - - - Play bottom card &face down - Gioca l'ultima carta dal fondo a &faccia in giù - - - - Move bottom card to grave&yard - Metti l'ultima carta nel cimitero - - - - Move bottom card to e&xile - Metti l'ultima carta in esilio - - - - Move bottom cards to &graveyard... - Metti le ultime carte nel cimitero... - - - - Move bottom cards to &exile... - Metti le ultime carte in esilio... - - - - Put bottom card on &top - Metti l'ultima carta in cima - - - - Selec&ted cards - Carte selezionate - - - + Move top cards to exile Esilia le prime carte - + Move bottom cards to grave Metti le ultime care nel cimitero - + Move bottom cards to exile - Metti le ultime carte in esilio + Esilia le ultime carte - + Draw bottom cards Pesca le ultime carte - - + + C&reate another %1 token C&rea un'altra pedina %1 - + Create tokens Crea pedine - + + + Number: + Numero: + + + Place card X cards from top of library Metti la carta in posizione X dalla cima del grimorio - + + Which position should this card be placed: + In che posizione dovrebbe essere messa questa carta: + + + + (max. %1) + (max %1) + + + Change power/toughness Cambia forza/costituzione - + Change stats to: Cambia valori a: - + Set annotation - Imposta nota + Imposta note - + Please enter the new annotation: Inserisci le nuove note: - + Set counters Imposta i segnalini + + + PlayerMenu - - Re&veal to... - Ri&vela a... + + Reveal top cards of library + Rivela le prime carte del grimorio - - View related cards - Guarda carte correlate + + Number of cards: (max. %1) + Numero di carte: (max %1) - - Token: - Pedina: + + Player "%1" + Giocatore "%1" - - All tokens - Tutte le pedine + + &Counters + &Contatori + + + + &All players + &Tutti i giocatori + + + + S&ay + Invi&a PrintingSelector - + Display Navigation Buttons Visualizza pulsanti di navigazione - - - <b>Warning:</b> You appear to be using custom card art, which has known bugs when also using the printing selector in this version of Cockatrice. - <b>Attenzione:</b> Stai utilizzando la cartella delle immagini personalizzate, che causa dei bug se usato insieme al selettore di stampa in questa versione di Cockatrice. - PrintingSelectorCardOverlayWidget - + Preference Preferenza - + Pin Printing Fissa stampa in cima alla lista - + Unpin Printing Rimuovi stampa dalla cima della lista - + Show Related cards Mostra carte correlate @@ -6109,6 +6253,64 @@ Il database delle carte verrà ricaricato. Ascendente + + PtMenu + + + Power / toughness + Forza / costituzione + + + + &Increase power + &Aumenta forza + + + + &Decrease power + &Diminuisci forza + + + + I&ncrease toughness + A&umenta costituzione + + + + D&ecrease toughness + D&iminuisci costituzione + + + + In&crease power and toughness + Au&menta forza e costituzione + + + + Dec&rease power and toughness + Dimi&nuisci forza e costituzione + + + + Increase power and decrease toughness + Aumenta forza e diminuisci costituzione + + + + Decrease power and increase toughness + Diminuisci forza e aumenta costituzione + + + + Set &power and toughness... + Imposta &forza e costituzione... + + + + Reset p&ower and toughness + Resetta f&orza e costituzione + + QMenuBar @@ -6165,17 +6367,17 @@ Il database delle carte verrà ricaricato. Replay di Cockatrice (*.cor) - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Token @@ -6334,6 +6536,44 @@ Il database delle carte verrà ricaricato. Durata (sec) + + RfgMenu + + + &Exile + &Esilio + + + + &View exile + &Guarda la zona di esilio + + + + &Move exile to... + &Muovi esilio in... + + + + &Top of library + &Cima al grimorio + + + + &Bottom of library + &Fondo al grimorio + + + + &Hand + &Mano + + + + &Graveyard + &Cimitero + + RoomSelector @@ -6436,53 +6676,53 @@ Il database delle carte verrà ricaricato. ShortcutSettingsPage - - + + Restore all default shortcuts Ripristina scorciatoie predefinite - + Do you really want to restore all default shortcuts? Sei scuro di voler ripristinare tutte le scorciatoie predefinite? - + Clear all default shortcuts Rimuovi tutte le scorciatoie predefinite - + Do you really want to clear all shortcuts? Sei sicuro di voler rimuovere tutte le scorciatoie? - + Section: Sezione: - + Action: Azione: - + Shortcut: Scorciatoia: - + How to set custom shortcuts Come impostare scorciatoie personalizzate - + Clear all shortcuts Elimina tutte le scorciatoie - + Search by shortcut name Cerca per nome della scorciatoia @@ -6535,30 +6775,43 @@ Controlla le impostazioni! Spegni il server + + SideboardMenu + + + &Sideboard + &Sideboard + + + + &View sideboard + &Guarda la sideboard + + SoundSettingsPage - + Enable &sounds Abilita &suoni - + Current sounds theme: Tema sonoro attuale: - + Test system sound engine Prova il funzionamento dei suoni - + Sound settings Impostazioni suoni - + Master volume Volume @@ -6828,68 +7081,61 @@ Controlla le impostazioni! TabDeckEditorVisual - + Visual Deck: %1 Mazzo visuale: %1 - + &Visual Deck Editor Editor &visuale - - + + Card Info Info carta - - + + Deck Mazzo - - + + Filters Filtri - + &View &Visualizza - - Deck Analytics - Analisi mazzo - - - + Printing Stampa - - - - - + + + + Visible Mostra - - - - - + + + + Floating Separata - + Reset layout Reimposta disposizione @@ -7111,226 +7357,215 @@ Please enter a name: - EDHREC: + EDHRec: + EDHREC: EDHREC: TabGame - - - + + + Replay Replay - - + + Game Partita - - + + Player List Giocatori - - + + Card Info Info carta - - + + Messages Messaggi - - + + Replay Timeline Replay - + &Phases &Fasi - + &Game &Partita - + Next &phase Prossima &fase - + Next phase with &action Prossima sottofase + &azione - + Next &turn Prossimo &turno - + Reverse turn order Inverti l'ordine dei turni - + &Remove all local arrows &Rimuovi tutte le frecce - + Rotate View Cl&ockwise Ruota vista in senso &orario - + Rotate View Co&unterclockwise R&uota vista in senso antiorario - + Game &information &Informazioni partita - + Un&concede Rientra in gioco - + &Concede &Concedi - + &Leave game &Lascia partita - + C&lose replay C&hiudi replay - + &Focus Chat Vai alla &chat - + &Say: &Parla: - + + Selected cards + Carte selezionate + + + &View &Visualizza - - - - + + + + Visible Mostra - - - - + + + + Floating Separata - + Reset layout Reimposta disposizione - + Concede Concedi - + Are you sure you want to concede this game? Vuoi veramente concedere la partita? - + Unconcede Annulla concedi - + You have already conceded. Do you want to return to this game? Hai già concesso. Vuoi ritornare in questa partita? - + Leave game Lascia la partita - + Are you sure you want to leave this game? Sei sicuro di voler lasciare la partita? - - You are flooding the game. Please wait a couple of seconds. - Stai spammando la partita. Attendi un paio di secondi. - - - + A player has joined game #%1 Un giocatore si è unito alla partita #%1 - + %1 has joined the game %1 si è unito alla partita - - kicked by game host or moderator - espulso da proprietario del gioco o moderatore - - - - player left the game - il giocatore ha lasciato la partita - - - - player disconnected from server - il giocatore si è disconnesso dal server - - - - reason unknown - ragione sconosciuta - - - + You have been kicked out of the game. Sei stato kickato fuori dalla partita. + + TabHome + + + Home + Home + + TabLog @@ -7531,105 +7766,186 @@ Più informazioni inserisci, più specifici saranno i risultati. TabReplays - + Local file system File locali - + Server replay storage Replay sul server - - + + Watch replay Guarda replay - + Rename Rinomina - - + + New folder Nuova cartella - - + + Delete Elimina - + Open replays folder Apri cartella replay - + Download replay Scarica replay - + Toggle expiration lock Metti/togli blocco scadenza - + + Get replay share code + Ottieni codice di condivisione replay + + + + + Look up replay by share code + Cerca replay dal codice di condivisione + + + Rename local folder Rinomina cartella locale - + Rename local file Rinomina file locale - + New name: Nuovo nome: - + Error Errore - + Rename failed Rinomina non riuscita - + Name of new folder: Nome della nuova cartella: - + Delete local file Elimina il file locale - + Are you sure you want to delete the selected files? Vuoi davvero eliminare i file selezionati? - + Are you sure you want to delete the selected replays? Vuoi davvero eliminare i replay selezionati? - + + Failed to get code + Impossibile ottenere il codice + + + + + Either this server does not support replay sharing, or does not permit replay sharing for you. + O questo server non supporta la condivisione dei replay, o non consente la condivisione dei replay per te. + + + + + + Failed + Fallito + + + + Could not get replay code + Impossibile ottenere il codice replay + + + + Replay Share Code + Codice di condivisione replay + + + + Others can use this code to add the replay to their list of remote replays: +%1 + Altre persone possono usare questo codice per aggiungere il replay alla loro lista di replay remoti: +%1 + + + + Copy to clipboard + Copia negli appunti + + + + Replay share code + Codice di condivisione replay + + + + Replay code found + Codice replay trovato + + + + Replay was added, or you already had access to it. + Il replay è stato aggiunto oppure ne avevi già accesso. + + + + Replay code not found + Codice replay non trovato + + + + Failed to submit code + Impossibile inviare il codice + + + + Unexpected error + Errore inatteso + + + Delete remote replay Elimina replay remoto - + Game Replays Replay partite @@ -7721,87 +8037,92 @@ Più informazioni inserisci, più specifici saranno i risultati. TabSupervisor - + Deck Editor &Editor dei mazzi - + Visual Deck Editor Editor visuale - + EDHRec EDHRec - + + Home + Home + + + &Visual Deck Storage &Galleria mazzi - + Visual Database Display Galleria database - + Server Server - + Account Account - + Deck Storage &Archivio mazzi - + Game Replays &Replay partite - + Administration Amministrazione - + Logs Registri - + Are you sure? Sei sicuro? - + There are still open games. Are you sure you want to quit? Ci sono ancora delle partite aperte. Sei sicuro di voler uscire? - + Click to view Clicca per visualizzare - + Your buddy %1 has signed on! Il tuo amico %1 si è collegato - + Unknown Event Evento sconosciuto. - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -7812,39 +8133,39 @@ Questo messaggio può significare che è disponibile una nuove versione di Cocka Per aggiornare il tuo client, vai su Aiuto -> Controlla aggiornamenti client - + Idle Timeout Timeout inattività - + You are about to be logged out due to inactivity. Stai per essere disconnesso per inattività. - + Promotion Promozione - + You have been promoted. Please log out and back in for changes to take effect. Sei stato promosso. Esci e rientra per dare effetto alle modifiche. - + Warned Avviso - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Hai ricevuto un avviso a causa di %1. Se pregato di evitare di continuare questa attività o potrebbero venire presi ulteriori provvedimenti nel tuoi confronti. Per qualsiasi domanda, manda un messaggio ad un moderatore. - + You have received the following message from the server. (custom messages like these could be untranslated) Hai ricevuto il seguente messaggio dal server. @@ -7862,13 +8183,13 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u TappedOutInterface - - + + Error Errore - + Unable to analyze the deck. Impossibile analizzare il mazzo. @@ -8029,99 +8350,121 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Visualizza le note degli amministratori - + + + + Error + Errore + + + + This user does not exist. + L'utente non esiste. + + + + You are being ignored by %1 and can't see their games. + Stai venendo ignorato da %1 e non puoi vedere le sue partite. + + + + Could not get %1's games. + Impossibile ottenere le partite di %1. + + + %1's games Partite di %1 - - - + + + Ban History Storico ban - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ora Ban;Moderatore;Durata Ban;Ragione Ban;Ragione Visibile - + User has never been banned. L'utente non è mai stato bannato. - + Failed to collect ban information. Impossibile recuperare le informazioni sui ban. - - - + + + Warning History Storico avvisi - + Warning Time;Moderator;User Name;Reason Ora Avviso;Moderatore;Nome Utente;Ragione - + User has never been warned. L'utente non ha mai ricevuto avvisi. - + Failed to collect warning information. Impossibile recuperare le informazioni sugli avvisi. - + Failed to get admin notes. Impossibile ottenere le note degli amministratori - - + + Success Successo - + Successfully promoted user. Utente promosso. - + Successfully demoted user. Utente degradato. - - - + + + Failed Fallito - + Failed to promote user. Promozione fallita. - + Failed to demote user. Degradazione fallita. - + Copy hash to clipboard Copia hash negli appunti - + Remove this user's messages Rimuovi i messaggi di questo utente @@ -8303,137 +8646,137 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserInterfaceSettingsPage - + General interface settings Impostazioni generali di interfaccia - + &Double-click cards to play them (instead of single-click) &Doppio click sulle carte per giocarle (anzichè un solo click) - + &Clicking plays all selected cards (instead of just the clicked card) &Cliccare gioca tutte le carte selezionate (anziché solo quella cliccata) - + &Play all nonlands onto the stack (not the battlefield) by default Gioca tutte le carte non terra nella &pila (invece che sul campo di battaglia) - + Close card view window when last card is removed Chiudi la finestra di visualizzazione carte quando l'ultima carta viene rimossa - + Auto focus search bar when card view window is opened Passa alla barra di ricerca quando si apre la finestra di visualizzazione carta - + Annotate card text on tokens Annota il testo della carta sulle pedine - + Use tear-off menus, allowing right click menus to persist on screen Usa menù a strappo, permettendo ai menù del tasto destro del mouse di rimanere sullo schermo - + Notifications settings Impostazioni notifiche - + Enable notifications in taskbar Abilita notifiche nella barra delle applicazioni - + Notify in the taskbar for game events while you are spectating Notifica anche per le partite in cui si è solo uno spettatore - + Notify in the taskbar when users in your buddy list connect Notifca quando utenti nella tua lista amici si connettono - + Animation settings Impostazioni delle animazioni - + &Tap/untap animation Animazioni &TAPpa/STAPpa - + Deck editor/storage settings Impostazioni editor/archivio mazzi - + Open deck in new tab by default Apri il mazzo in una nuova scheda automaticamente - + Use visual deck storage in game lobby Usa galleria mazzi nella lobby - + Use selection animation for Visual Deck Storage Mostra animazione al passaggio del mouse nella galleria mazzi - + When adding a tag in the visual deck storage to a .txt deck: Quando aggiungi un'etichetta ad un mazzo in formato .txt nella Galleria mazzi: - + do nothing non fare nulla - + ask to convert to .cod chiedi se convertire in file .cod - + always convert to .cod converti sempre in file .cod - + Default deck editor type Editor dei mazzi predefinito - + Classic Deck Editor Editor classico - + Visual Deck Editor Editor visuale - + Replay settings Impostazioni di riproduzione - + Buffer time for backwards skip via shortcut: Tempo di attesa per saltare indietro dopo aver premuto la scorciatoia @@ -8461,6 +8804,39 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Utenti ignorati online: %1 / %2 + + UtilityMenu + + + Increment all card counters + Aumenta tutti i segnalini della carta + + + + &Untap all permanents + &STAPpa tutti i permanenti + + + + R&oll die... + L&ancia un dado... + + + + &Create token... + &Crea una pedina... + + + + C&reate another token + C&rea un'altra pedina + + + + Cr&eate predefined token + Cr&ea pedina predefinita + + VisualDatabaseDisplayColorFilterWidget @@ -8617,27 +8993,32 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Cerca per nome della carta (o espressioni di ricerca) - + + Loading database ... + Caricamento database... + + + Clear all filters Elimina tutti i filtri - + Save and load filters Salva e carica filtri - + Filter by exact card name Filtra per nome della carta esatto - + Filter by card sub-type Filtra per sottotipo - + Filter by set Filtra per set @@ -8658,38 +9039,38 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckEditorWidget - + Click and drag to change the sort order within the groups Clicca e trascina per modificare i criteri di ordinamento all'interno dei gruppi - + Quick search and add card Cerca e aggiungi carta al mazzo - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Cerca per la corrispondenza più prossima nel database (con auto-completamento) e premendo Invio, aggiungi la stampa preferita della carta al mazzo. - + Configure how cards are sorted within their groups Configura l'ordinamento delle carte all'interno dei gruppi - - + + Overlap Layout Disposizione sovrapposta - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Imposta come le carte vengono mostrate all'interno delle relative sezioni (sovrapposte o affiancate) - + Flat Layout Disposizione affiancata @@ -8985,67 +9366,67 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Cerca per nome della carta (o espressioni di ricerca) - + Ungrouped Non raggruppare - + Group by Type Raggruppa per tipo - + Group by Mana Value Raggruppa per costo - + Group by Color Raggruppa per colore - + Unsorted Non ordinato - + Sort by Name Ordina per nome - + Sort by Type Ordina per tipo - + Sort by Mana Cost Ordina per costo - + Sort by Colors Ordina per colori - + Sort by P/T Ordina per F/C - + Sort by Set Ordina per Set - + shuffle when closing Mescola alla chiusura - + pile view Raggruppa per tipo @@ -9080,7 +9461,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - + Deck Editor Editor dei mazzi @@ -9161,7 +9542,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - + Replays Replay @@ -9314,7 +9695,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u - + Reset Layout Reimposta Disposizione @@ -9594,471 +9975,486 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Imposta Contatore Altro... - + + Increment all card counters + Aumenta tutti i segnalini della carta + + + Add Power (+1/+0) Aumenta Forza (+1/+0) - + Remove Power (-1/-0) Diminuisci Forza (-1/-0) - + Move Toughness to Power (+1/-1) Sposta Costituzione a Forza (+1/-1) - + Add Toughness (+0/+1) Aumenta Costituzione (+0/+1) - + Remove Toughness (-0/-1) Riduci Costituzione (-0/-1) - + Move Power to Toughness (-1/+1) Sposta Forza a Costituzione (-1/+1) - + Add Power and Toughness (+1/+1) Aumenta Forza e Costituzione (+1/+1) - + Remove Power and Toughness (-1/-1) Diminuisci Forza e Costituzione (-1/-1) - + Set Power and Toughness... Imposta Forza e Costituzione... - + Reset Power and Toughness Resetta Forza e Costituzione - + Untap STAP - + Upkeep Mantenimento - + Draw Acquisizione - + First Main Phase Prima Fase Principale - + Start Combat Inizio Combattimento - + Attack Dichiarazione attaccanti - + Block Dichiarazione bloccanti - + Damage Danno da combattimento - + End Combat Fine Combattimento - + Second Main Phase Seconda Fase Principale - + End Finale - + Next Phase Fase Successiva - + Next Phase Action Azione della Fase Successiva - + Next Turn Turno Successivo - + Hide Card in Reveal Window Nascondi Carta nella Finestra di Visualizzazione - + Tap / Untap Card TAPpa / STAPpa carta - + Untap All STAPpa Tutto - + Toggle Untap Abilita STAPpa - + Turn Card Over Gira Carta - + Peek Card Sbircia Carta - + Play Card Gioca Carta - + Attach Card... Assegna Carta... - + Unattach Card Togli Carta - + Clone Card Clona Carta - + Create Token... Crea Pedina... - + Create All Related Tokens Crea Tutte le Pedine Correlate - + Create Another Token Crea un'Altra Pedina - + Set Annotation... Imposta Note... - + Select All Cards in Zone Seleziona Tutte le Carte nella Zona - + Select All Cards in Row Seleziona Tutte le Carte nella Riga - + Select All Cards in Column Seleziona Tutte le Carte nella Colonna - - + + Bottom of Library Fondo al Grimorio - - - - + + + + Exile Esilio - - - - + + + + Graveyard Cimitero - - + + Hand Mano - - + + Top of Library Cima al Grimorio - - - + + + Battlefield, Face Down Campo di Battaglia, faccia in giù - + Battlefield Campo di Battaglia - + + Sort Hand + Ordina mano + + + Library Grimorio - + Sideboard Sideboard - + Top Cards of Library Carte in Cima al Grimorio - + Bottom Cards of Library Carte in Fondo al Grimorio - + Close Recent View Chiudi Viste di Recente - - + + Stack Pila - - + + Graveyard (Multiple) Cimitero (multiplo) - - + + Exile (Multiple) Esilia (multiplo) - + Stack Until Found Impila Fino a Trovare - + Draw Bottom Card Pesca Carta in Fondo - + Draw Multiple Cards from Bottom... Pesca Multiple Carte in Fondo - + Draw Arrow... Disegna Freccia... - + Remove Local Arrows Rimuovi Frecce Locali - + Leave Game Abbandona Partita - + Concede Concedi - + Roll Dice... Tira un Dado... - + Shuffle Library Mescola il Grimorio - + Shuffle Top Cards of Library Mescola Carte in Cima al Grimorio - + Shuffle Bottom Cards of Library Mescola Carte in Fondo al Grimorio - + Mulligan Mulligan - + Draw a Card Pesca una Carta - + Draw Multiple Cards... Pesca più Carte... - + Undo Draw Annulla Pescata - + Always Reveal Top Card Rivela Sempre la Carta in Cima - + Always Look At Top Card Guarda Sempre la Carta in Cima - + Rotate View Clockwise Ruota Vista in Senso Orario - + Rotate View Counterclockwise Ruota Vista in Senso Antiorario - + Unfocus Text Box Togli Focus dalla Casella di Testo - + Focus Chat Vai alla chat - + Clear Chat Cancella chat - + Refresh Aggiorna - + Skip Forward Salta Avanti - + Skip Backward Salta Indietro - + Skip Forward by a lot Salta Avanti di Molto - + Skip Backward by a lot Salta Indietro di Molto - + Play/Pause Riproduci/Pausa - + Toggle Fast Forward Attiva/Disattiva Avanzamento Rapido - + + Home + Home + + + Visual Deck Storage Galleria mazzi - + Deck Storage Archivio mazzi - + Server Server - + Account Account - + Administration Amministrazione - + Logs Registri diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index cf2f15db1..19651c948 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -18,6 +18,7 @@ set(common_SOURCES rng_abstract.cpp rng_sfmt.cpp server/game/server_abstract_participant.cpp + server/game/server_abstract_player.cpp server/game/server_arrow.cpp server/game/server_arrowtarget.cpp server/game/server_card.cpp diff --git a/common/server/game/server_abstract_participant.cpp b/common/server/game/server_abstract_participant.cpp index 1ef765d8b..cdbf29d47 100644 --- a/common/server/game/server_abstract_participant.cpp +++ b/common/server/game/server_abstract_participant.cpp @@ -202,7 +202,7 @@ Server_AbstractParticipant::cmdJudge(const Command_Judge &cmd, ResponseContainer return Response::RespFunctionNotAllowed; } - Server_Player *player = this->game->getPlayer(cmd.target_id()); + auto *player = this->game->getPlayer(cmd.target_id()); ges.setForcedByJudge(playerId); if (player == nullptr) { diff --git a/common/server/game/server_abstract_player.cpp b/common/server/game/server_abstract_player.cpp new file mode 100644 index 000000000..c94e54f10 --- /dev/null +++ b/common/server/game/server_abstract_player.cpp @@ -0,0 +1,1611 @@ +#include "server_abstract_player.h" + +#include "../../deck_list.h" +#include "../../rng_abstract.h" +#include "../../trice_limits.h" +#include "pb/command_attach_card.pb.h" +#include "pb/command_change_zone_properties.pb.h" +#include "pb/command_create_arrow.pb.h" +#include "pb/command_create_token.pb.h" +#include "pb/command_delete_arrow.pb.h" +#include "pb/command_dump_zone.pb.h" +#include "pb/command_flip_card.pb.h" +#include "pb/command_game_say.pb.h" +#include "pb/command_inc_card_counter.pb.h" +#include "pb/command_inc_counter.pb.h" +#include "pb/command_move_card.pb.h" +#include "pb/command_next_turn.pb.h" +#include "pb/command_ready_start.pb.h" +#include "pb/command_reveal_cards.pb.h" +#include "pb/command_roll_die.pb.h" +#include "pb/command_set_card_attr.pb.h" +#include "pb/command_set_card_counter.pb.h" +#include "pb/context_concede.pb.h" +#include "pb/context_move_card.pb.h" +#include "pb/context_ready_start.pb.h" +#include "pb/context_undo_draw.pb.h" +#include "pb/event_attach_card.pb.h" +#include "pb/event_change_zone_properties.pb.h" +#include "pb/event_create_arrow.pb.h" +#include "pb/event_create_token.pb.h" +#include "pb/event_delete_arrow.pb.h" +#include "pb/event_destroy_card.pb.h" +#include "pb/event_dump_zone.pb.h" +#include "pb/event_flip_card.pb.h" +#include "pb/event_move_card.pb.h" +#include "pb/event_player_properties_changed.pb.h" +#include "pb/event_reveal_cards.pb.h" +#include "pb/event_roll_die.pb.h" +#include "pb/event_set_card_attr.pb.h" +#include "pb/event_set_card_counter.pb.h" +#include "pb/response.pb.h" +#include "pb/response_dump_zone.pb.h" +#include "pb/serverinfo_player.pb.h" +#include "pb/serverinfo_user.pb.h" +#include "server_arrow.h" +#include "server_card.h" +#include "server_cardzone.h" +#include "server_game.h" +#include "server_move_card_struct.h" + +#include +#include +#include + +Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game, + int _playerId, + const ServerInfo_User &_userInfo, + bool _judge, + Server_AbstractUserInterface *_userInterface) + : Server_AbstractParticipant(_game, _playerId, _userInfo, _judge, _userInterface), conceded(false), deck(nullptr), + sideboardLocked(true), readyStart(false), nextCardId(0) +{ + spectator = false; +} + +Server_AbstractPlayer::~Server_AbstractPlayer() = default; + +void Server_AbstractPlayer::prepareDestroy() +{ + delete deck; + deck = nullptr; + + removeFromGame(); + clearZones(); + + deleteLater(); +} + +int Server_AbstractPlayer::newCardId() +{ + return nextCardId++; +} + +int Server_AbstractPlayer::newArrowId() const +{ + int id = 0; + for (Server_Arrow *a : arrows) { + if (a->getId() > id) { + id = a->getId(); + } + } + return id + 1; +} + +void Server_AbstractPlayer::setupZones() +{ + nextCardId = 0; +} + +void Server_AbstractPlayer::clearZones() +{ + for (Server_CardZone *zone : zones) { + delete zone; + } + zones.clear(); + + for (Server_Arrow *arrow : arrows) { + delete arrow; + } + arrows.clear(); +} + +void Server_AbstractPlayer::addZone(Server_CardZone *zone) +{ + zones.insert(zone->getName(), zone); +} + +void Server_AbstractPlayer::addArrow(Server_Arrow *arrow) +{ + arrows.insert(arrow->getId(), arrow); +} + +void Server_AbstractPlayer::updateArrowId(int id) +{ + auto *arrow = arrows.take(id); + arrows.insert(arrow->getId(), arrow); +} + +bool Server_AbstractPlayer::deleteArrow(int arrowId) +{ + Server_Arrow *arrow = arrows.value(arrowId, 0); + if (!arrow) { + return false; + } + arrows.remove(arrowId); + delete arrow; + return true; +} + +/** + * Creates the create token event. + * By default, will set event's name and color fields to empty if the token is face-down + */ +static Event_CreateToken +makeCreateTokenEvent(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord, bool revealFacedownInfo = false) +{ + Event_CreateToken event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + event.set_face_down(card->getFaceDown()); + + if (!card->getFaceDown() || revealFacedownInfo) { + event.set_card_name(card->getName().toStdString()); + event.set_card_provider_id(card->getProviderId().toStdString()); + } + + event.set_color(card->getColor().toStdString()); + event.set_pt(card->getPT().toStdString()); + event.set_annotation(card->getAnnotation().toStdString()); + event.set_destroy_on_zone_change(card->getDestroyOnZoneChange()); + event.set_x(xCoord); + event.set_y(yCoord); + return event; +} + +static Event_AttachCard makeAttachCardEvent(Server_Card *attachedCard, Server_Card *parentCard = nullptr) +{ + Event_AttachCard event; + event.set_start_zone(attachedCard->getZone()->getName().toStdString()); + event.set_card_id(attachedCard->getId()); + + if (parentCard) { + event.set_target_player_id(parentCard->getZone()->getPlayer()->getPlayerId()); + event.set_target_zone(parentCard->getZone()->getName().toStdString()); + event.set_target_card_id(parentCard->getId()); + } + + return event; +} + +/** + * Determines whether moving the card from startZone to targetZone should cause the card to be destroyed. + */ +static bool +shouldDestroyOnMove(const Server_Card *card, const Server_CardZone *startZone, const Server_CardZone *targetZone) +{ + if (!card->getDestroyOnZoneChange()) { + return false; + } + + if (startZone->getName() == targetZone->getName()) { + return false; + } + + // Allow tokens on the stack + if ((startZone->getName() == "table" || startZone->getName() == "stack") && + (targetZone->getName() == "table" || targetZone->getName() == "stack")) { + return false; + } + + return true; +} + +Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, + Server_CardZone *startzone, + const QList &_cards, + Server_CardZone *targetzone, + int xCoord, + int yCoord, + bool fixFreeSpaces, + bool undoingDraw, + bool isReversed) +{ + // Disallow controller change to other zones than the table. + if (((targetzone->getType() != ServerInfo_Zone::PublicZone) || !targetzone->hasCoords()) && + (startzone->getPlayer() != targetzone->getPlayer()) && !judge) { + return Response::RespContextError; + } + + if (!targetzone->hasCoords() && (xCoord <= -1)) { + xCoord = targetzone->getCards().size(); + } + + std::set cardsToMove; + QSet cardIdsToMove; + for (auto _card : _cards) { + // The same card being moved twice would lead to undefined behaviour. + if (cardIdsToMove.contains(_card->card_id())) { + continue; + } + cardIdsToMove.insert(_card->card_id()); + + // Consistency checks. In case the command contains illegal moves, try to resolve the legal ones still. + int position; + Server_Card *card = startzone->getCard(_card->card_id(), &position); + if (!card) { + return Response::RespNameNotFound; + } + + // do not allow attached cards to move around on the table + if (card->getParentCard() && targetzone->getName() == "table") { + continue; + } + + // do not allow cards with attachments to stack with other cards + if (!card->getAttachedCards().isEmpty() && !targetzone->isColumnEmpty(xCoord, yCoord)) { + continue; + } + + cardsToMove.insert(MoveCardStruct{card, position, _card}); + } + // In case all moves were filtered out, abort. + if (cardsToMove.empty()) { + return Response::RespContextError; + } + + int xIndex = -1; + bool revealTopStart = false; + bool revealTopTarget = false; + + for (auto cardStruct : cardsToMove) { + Server_Card *card = cardStruct.card; + const CardToMove *thisCardProperties = cardStruct.cardToMove; + int originalPosition = cardStruct.position; + bool faceDown = targetzone->hasCoords() && + (thisCardProperties->has_face_down() ? thisCardProperties->face_down() : card->getFaceDown()); + + bool sourceBeingLookedAt; + int position = startzone->removeCard(card, sourceBeingLookedAt); + + // Attachment relationships can be retained when moving a card onto the opponent's table + if (startzone->getName() != targetzone->getName()) { + // Delete all attachment relationships + if (card->getParentCard()) { + card->setParentCard(nullptr); + } + + // Make a copy of the list because the original one gets modified during the loop + QList attachedCards = card->getAttachedCards(); + for (auto &attachedCard : attachedCards) { + attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard); + } + } + + if (startzone != targetzone) { + // Delete all arrows from and to the card + for (auto *player : game->getPlayers().values()) { + QList arrowsToDelete; + for (Server_Arrow *arrow : player->getArrows()) { + if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) + arrowsToDelete.append(arrow->getId()); + } + for (int j : arrowsToDelete) { + player->deleteArrow(j); + } + } + } + + if (shouldDestroyOnMove(card, startzone, targetzone)) { + Event_DestroyCard event; + event.set_zone_name(startzone->getName().toStdString()); + event.set_card_id(static_cast(card->getId())); + ges.enqueueGameEvent(event, playerId); + + if (Server_Card *stashedCard = card->takeStashedCard()) { + stashedCard->setId(newCardId()); + ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), + playerId); + card->deleteLater(); + card = stashedCard; + } else { + card->deleteLater(); + card = nullptr; + } + } + + if (card) { + ++xIndex; + int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex; + + if (targetzone->hasCoords()) { + newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); + } else { + yCoord = 0; + card->resetState(targetzone->getName() == "stack"); + } + + targetzone->insertCard(card, newX, yCoord); + int targetLookedCards = targetzone->getCardsBeingLookedAt(); + bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown()); + if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) { + if (sourceKnownToPlayer) { + targetLookedCards += 1; + } else { + targetLookedCards = newX; + } + targetzone->setCardsBeingLookedAt(targetLookedCards); + } + + bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone); + bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone); + + int oldCardId = card->getId(); + if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) { + card->setId(targetzone->getPlayer()->newCardId()); + } + card->setFaceDown(faceDown); + + Event_MoveCard eventOthers; + eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId()); + eventOthers.set_start_zone(startzone->getName().toStdString()); + eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId()); + if (startzone != targetzone) { + eventOthers.set_target_zone(targetzone->getName().toStdString()); + } + eventOthers.set_y(yCoord); + eventOthers.set_face_down(faceDown); + + Event_MoveCard eventPrivate(eventOthers); + if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone || + startzone->getType() != ServerInfo_Zone::HiddenZone) { + eventPrivate.set_card_id(oldCardId); + eventPrivate.set_new_card_id(card->getId()); + } else { + eventPrivate.set_card_id(-1); + eventPrivate.set_new_card_id(-1); + } + if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) { + QString privateCardName = card->getName(); + eventPrivate.set_card_name(privateCardName.toStdString()); + eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString()); + } + if (startzone->getType() == ServerInfo_Zone::HiddenZone) { + eventPrivate.set_position(position); + } else { + eventPrivate.set_position(-1); + } + + eventPrivate.set_x(newX); + + // Other players do not get to see the start and/or target position of the card if the respective + // part of the zone is being looked at. The information is not needed anyway because in hidden zones, + // all cards are equal. + if (((startzone->getType() == ServerInfo_Zone::HiddenZone) && + ((startzone->getCardsBeingLookedAt() > position) || (startzone->getCardsBeingLookedAt() == -1))) || + (startzone->getType() == ServerInfo_Zone::PublicZone)) { + eventOthers.set_position(-1); + } else { + eventOthers.set_position(position); + } + if ((targetzone->getType() == ServerInfo_Zone::HiddenZone) && + ((targetzone->getCardsBeingLookedAt() > newX) || (targetzone->getCardsBeingLookedAt() == -1))) { + eventOthers.set_x(-1); + } else { + eventOthers.set_x(newX); + } + + if ((startzone->getType() == ServerInfo_Zone::PublicZone) || + (targetzone->getType() == ServerInfo_Zone::PublicZone)) { + eventOthers.set_card_id(oldCardId); + if (!(sourceHiddenToOthers && targetHiddenToOthers)) { + QString publicCardName = card->getName(); + eventOthers.set_card_name(publicCardName.toStdString()); + eventOthers.set_new_card_provider_id(card->getProviderId().toStdString()); + } + eventOthers.set_new_card_id(card->getId()); + } + + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + + if (originalPosition == 0) { + revealTopStart = true; + } + if (newX == 0) { + revealTopTarget = true; + } + + // handle side effects for this card + onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw); + } + } + if (revealTopStart) { + revealTopCardIfNeeded(startzone, ges); + } + if (targetzone != startzone && revealTopTarget) { + revealTopCardIfNeeded(targetzone, ges); + } + if (undoingDraw) { + ges.setGameEventContext(Context_UndoDraw()); + } else { + ges.setGameEventContext(Context_MoveCard()); + } + + if (startzone->hasCoords() && fixFreeSpaces) { + startzone->fixFreeSpaces(ges); + } + + return Response::RespOk; +} + +void Server_AbstractPlayer::onCardBeingMoved(GameEventStorage &ges, + const MoveCardStruct &cardStruct, + Server_CardZone *startzone, + Server_CardZone *targetzone, + bool /*undoingDraw*/) +{ + Server_Card *card = cardStruct.card; + const CardToMove *thisCardProperties = cardStruct.cardToMove; + + // set card to be tapped + if (thisCardProperties->tapped()) { + setCardAttrHelper(ges, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), card->getId(), AttrTapped, + "1"); + } + + // set card pt + QString ptString = QString::fromStdString(thisCardProperties->pt()); + if (!ptString.isEmpty()) { + setCardAttrHelper(ges, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), card->getId(), AttrPT, + ptString); + } + + // If card is transferring to a different player, leave an annotation of who actually "owns" the card + const auto &priorAnnotation = card->getAnnotation(); + if (startzone->getPlayer() != targetzone->getPlayer() && !priorAnnotation.contains("Owner:")) { + const auto &ownerAnnotation = "Owner: " + QString::fromStdString(startzone->getPlayer()->getUserInfo()->name()); + const auto &newAnnotation = + priorAnnotation.isEmpty() ? ownerAnnotation : ownerAnnotation + "\n\n" + priorAnnotation; + setCardAttrHelper(ges, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), card->getId(), + AttrAnnotation, newAnnotation, card); + } +} + +void Server_AbstractPlayer::revealTopCardIfNeeded(Server_CardZone *zone, GameEventStorage &ges) +{ + if (zone->getCards().isEmpty()) { + return; + } + if (zone->getAlwaysRevealTopCard()) { + Event_RevealCards revealEvent; + revealEvent.set_zone_name(zone->getName().toStdString()); + revealEvent.add_card_id(0); + zone->getCards().first()->getInfo(revealEvent.add_cards()); + + ges.enqueueGameEvent(revealEvent, playerId); + return; + } + if (zone->getAlwaysLookAtTopCard()) { + Event_DumpZone dumpEvent; + dumpEvent.set_zone_owner_id(playerId); + dumpEvent.set_zone_name(zone->getName().toStdString()); + dumpEvent.set_number_cards(1); + ges.enqueueGameEvent(dumpEvent, playerId, GameEventStorageItem::SendToOthers); + + Event_RevealCards revealEvent; + revealEvent.set_zone_name(zone->getName().toStdString()); + revealEvent.set_number_of_cards(1); + revealEvent.add_card_id(0); + zone->getCards().first()->getInfo(revealEvent.add_cards()); + ges.enqueueGameEvent(revealEvent, playerId, GameEventStorageItem::SendToPrivate, playerId); + } +} + +void Server_AbstractPlayer::unattachCard(GameEventStorage &ges, Server_Card *card) +{ + Server_CardZone *zone = card->getZone(); + Server_Card *parentCard = card->getParentCard(); + card->setParentCard(nullptr); + + ges.enqueueGameEvent(makeAttachCardEvent(card), playerId); + + auto *cardToMove = new CardToMove; + cardToMove->set_card_id(card->getId()); + moveCard(ges, zone, QList() << cardToMove, zone, -1, card->getY(), card->getFaceDown()); + delete cardToMove; + + if (parentCard->getZone()) { + parentCard->getZone()->updateCardCoordinates(parentCard, parentCard->getX(), parentCard->getY()); + } +} + +Response::ResponseCode Server_AbstractPlayer::setCardAttrHelper(GameEventStorage &ges, + int targetPlayerId, + const QString &zoneName, + int cardId, + CardAttribute attribute, + const QString &attrValue, + Server_Card *unzonedCard) +{ + Server_CardZone *zone = getZones().value(zoneName); + if (!zone) { + return Response::RespNameNotFound; + } + if (!zone->hasCoords()) { + return Response::RespContextError; + } + + QString result; + if (cardId == -1) { + QListIterator CardIterator(zone->getCards()); + while (CardIterator.hasNext()) { + result = CardIterator.next()->setAttribute(attribute, attrValue, true); + if (result.isNull()) { + return Response::RespInvalidCommand; + } + } + } else { + Server_Card *card = unzonedCard == nullptr ? zone->getCard(cardId) : unzonedCard; + if (!card) { + return Response::RespNameNotFound; + } + result = card->setAttribute(attribute, attrValue, false); + if (result.isNull()) { + return Response::RespInvalidCommand; + } + } + + Event_SetCardAttr event; + event.set_zone_name(zone->getName().toStdString()); + if (cardId != -1) { + event.set_card_id(cardId); + } + event.set_attribute(attribute); + event.set_attr_value(result.toStdString()); + ges.enqueueGameEvent(event, targetPlayerId); + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdConcede(const Command_Concede & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + setConceded(true); + game->removeArrowsRelatedToPlayer(ges, this); + game->unattachCards(ges, this); + + playerMutex.lock(); + + // Return cards to their rightful owners before conceding the game + static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"}; + for (const auto &card : zones.value("table")->getCards()) { + if (card == nullptr) { + continue; + } + + const auto ®exResult = ownerRegex.match(card->getAnnotation()); + if (!regexResult.hasMatch()) { + continue; + } + + CardToMove cardToMove; + cardToMove.set_card_id(card->getId()); + + for (const auto *player : game->getPlayers()) { + if (player == nullptr || player->getUserInfo() == nullptr) { + continue; + } + + const auto &ownerToReturnTo = regexResult.captured(1); + const auto &correctOwner = QString::compare(QString::fromStdString(player->getUserInfo()->name()), + ownerToReturnTo, Qt::CaseInsensitive) == 0; + if (!correctOwner) { + continue; + } + + const auto &startZone = zones.value("table"); + const auto &targetZone = player->getZones().value("table"); + + if (startZone == nullptr || targetZone == nullptr) { + continue; + } + + moveCard(ges, startZone, QList() << &cardToMove, targetZone, 0, 0, false); + break; + } + } + + playerMutex.unlock(); + + // All borrowed cards have been returned, can now continue cleanup process + clearZones(); + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_conceded(true); + ges.enqueueGameEvent(event, playerId); + ges.setGameEventContext(Context_Concede()); + + game->stopGameIfFinished(); + if (game->getGameStarted() && (game->getActivePlayer() == playerId)) { + game->nextTurn(); + } + + return Response::RespOk; +} + +Response::ResponseCode Server_AbstractPlayer::cmdUnconcede(const Command_Unconcede & /*cmd*/, + ResponseContainer & /*rc*/, + GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (!conceded) { + return Response::RespContextError; + } + + setConceded(false); + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_conceded(false); + ges.enqueueGameEvent(event, playerId); + ges.setGameEventContext(Context_Unconcede()); + + setupZones(); + + game->sendGameStateToPlayers(); + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!deck || game->getGameStarted()) { + return Response::RespContextError; + } + + if (readyStart == cmd.ready() && !cmd.force_start()) { + return Response::RespContextError; + } + + setReadyStart(cmd.ready()); + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_ready_start(cmd.ready()); + ges.enqueueGameEvent(event, playerId); + ges.setGameEventContext(Context_ReadyStart()); + + if (cmd.force_start()) { + if (game->getHostId() != playerId) { + return Response::RespFunctionNotAllowed; + } + game->startGameIfReady(true); + } else if (cmd.ready()) { + game->startGameIfReady(false); + } + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdRollDie(const Command_RollDie &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) const +{ + if (conceded) { + return Response::RespContextError; + } + + const auto validatedSides = static_cast(std::min(std::max(cmd.sides(), MINIMUM_DIE_SIDES), MAXIMUM_DIE_SIDES)); + const auto validatedDiceToRoll = + static_cast(std::min(std::max(cmd.count(), MINIMUM_DICE_TO_ROLL), MAXIMUM_DICE_TO_ROLL)); + + Event_RollDie event; + event.set_sides(validatedSides); + for (auto i = 0; i < validatedDiceToRoll; ++i) { + const auto roll = rng->rand(1, validatedSides); + if (i == 0) { + // Backwards compatibility + event.set_value(roll); + } + event.add_values(roll); + } + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + Server_AbstractPlayer *startPlayer = game->getPlayer(cmd.has_start_player_id() ? cmd.start_player_id() : playerId); + if (!startPlayer) { + return Response::RespNameNotFound; + } + Server_CardZone *startZone = startPlayer->getZones().value(nameFromStdString(cmd.start_zone())); + if (!startZone) { + return Response::RespNameNotFound; + } + + if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId)) && !judge) { + return Response::RespContextError; + } + + Server_AbstractPlayer *targetPlayer = game->getPlayer(cmd.target_player_id()); + if (!targetPlayer) { + return Response::RespNameNotFound; + } + Server_CardZone *targetZone = targetPlayer->getZones().value(nameFromStdString(cmd.target_zone())); + if (!targetZone) { + return Response::RespNameNotFound; + } + + if ((startPlayer != this) && (targetPlayer != this) && !judge) { + return Response::RespContextError; + } + + QList cardsToMove; + for (int i = 0; i < cmd.cards_to_move().card_size(); ++i) { + cardsToMove.append(&cmd.cards_to_move().card(i)); + } + + return moveCard(ges, startZone, cardsToMove, targetZone, cmd.x(), cmd.y(), true, false, cmd.is_reversed()); +} + +Response::ResponseCode +Server_AbstractPlayer::cmdFlipCard(const Command_FlipCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone())); + if (!zone) { + return Response::RespNameNotFound; + } + if (!zone->hasCoords()) { + return Response::RespContextError; + } + + Server_Card *card = zone->getCard(cmd.card_id()); + if (!card) { + return Response::RespNameNotFound; + } + + const bool faceDown = cmd.face_down(); + if (faceDown == card->getFaceDown()) { + return Response::RespContextError; + } + + card->setFaceDown(faceDown); + + Event_FlipCard event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + if (!faceDown) { + event.set_card_name(card->getName().toStdString()); + event.set_card_provider_id(card->getProviderId().toStdString()); + } + event.set_face_down(faceDown); + ges.enqueueGameEvent(event, playerId); + + QString ptString = nameFromStdString(cmd.pt()); + if (!ptString.isEmpty() && !faceDown) { + setCardAttrHelper(ges, playerId, zone->getName(), card->getId(), AttrPT, ptString); + } + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdAttachCard(const Command_AttachCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + Server_CardZone *startzone = zones.value(nameFromStdString(cmd.start_zone())); + if (!startzone) { + return Response::RespNameNotFound; + } + + Server_Card *card = startzone->getCard(cmd.card_id()); + if (!card) { + return Response::RespNameNotFound; + } + + Server_AbstractPlayer *targetPlayer = nullptr; + Server_CardZone *targetzone = nullptr; + Server_Card *targetCard = nullptr; + + if (cmd.has_target_player_id()) { + targetPlayer = game->getPlayer(cmd.target_player_id()); + if (!targetPlayer) { + return Response::RespNameNotFound; + } + } else if (!card->getParentCard()) { + return Response::RespContextError; + } + if (targetPlayer) { + targetzone = targetPlayer->getZones().value(nameFromStdString(cmd.target_zone())); + } + if (targetzone) { + // This is currently enough to make sure cards don't get attached to a card that is not on the table. + // Possibly a flag will have to be introduced for this sometime. + if (!targetzone->hasCoords()) { + return Response::RespContextError; + } + if (cmd.has_target_card_id()) { + targetCard = targetzone->getCard(cmd.target_card_id()); + } + if (targetCard) { + if (targetCard->getParentCard()) { + return Response::RespContextError; + } + } else { + return Response::RespNameNotFound; + } + } + + // prevent attaching from non-table zones + // (attaching from non-table zones is handled client-side by moving the card to table zone first) + if (!startzone->hasCoords()) { + return Response::RespContextError; + } + + for (auto *player : game->getPlayers()) { + QList _arrows = player->getArrows().values(); + QList toDelete; + for (auto a : _arrows) { + auto *tCard = qobject_cast(a->getTargetItem()); + if ((tCard == card) || (a->getStartCard() == card)) { + toDelete.append(a); + } + } + for (auto &i : toDelete) { + Event_DeleteArrow event; + event.set_arrow_id(i->getId()); + ges.enqueueGameEvent(event, player->getPlayerId()); + player->deleteArrow(i->getId()); + } + } + + if (targetCard) { + // Unattach all cards attached to the card being attached. + // Make a copy of the list because its contents change during the loop otherwise. + QList attachedList = card->getAttachedCards(); + for (const auto &i : attachedList) { + i->getZone()->getPlayer()->unattachCard(ges, i); + } + + card->setParentCard(targetCard); + const int oldX = card->getX(); + card->setCoords(-1, card->getY()); + startzone->updateCardCoordinates(card, oldX, card->getY()); + + if (targetzone->isColumnStacked(targetCard->getX(), targetCard->getY())) { + auto *cardToMove = new CardToMove; + cardToMove->set_card_id(targetCard->getId()); + targetPlayer->moveCard(ges, targetzone, QList() << cardToMove, targetzone, + targetzone->getFreeGridColumn(-2, targetCard->getY(), targetCard->getName(), false), + targetCard->getY(), targetCard->getFaceDown()); + delete cardToMove; + } + + ges.enqueueGameEvent(makeAttachCardEvent(card, targetCard), playerId); + + startzone->fixFreeSpaces(ges); + } else { + unattachCard(ges, card); + } + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer &rc, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone())); + if (!zone) { + return Response::RespNameNotFound; + } + + int xCoord = cmd.x(); + int yCoord = cmd.y(); + + Server_Card *targetCard = nullptr; + if (cmd.has_target_card_id()) { + Server_CardZone *targetZone = zones.value(nameFromStdString(cmd.target_zone())); + if (targetZone) { + targetCard = targetZone->getCard(cmd.target_card_id()); + if (targetCard && cmd.target_mode() == Command_CreateToken::TRANSFORM_INTO) { + if (targetCard->getParentCard()) { + ges.enqueueGameEvent(makeAttachCardEvent(targetCard), playerId); + } + + for (Server_Card *attachedCard : targetCard->getAttachedCards()) { + ges.enqueueGameEvent(makeAttachCardEvent(attachedCard), + attachedCard->getZone()->getPlayer()->getPlayerId()); + } + + if (zone->hasCoords() && zone == targetZone) { + xCoord = targetCard->getX(); + yCoord = targetCard->getY(); + } + + targetZone->removeCard(targetCard); + + Event_DestroyCard event; + event.set_zone_name(targetZone->getName().toStdString()); + event.set_card_id(static_cast<::google::protobuf::uint32>(cmd.target_card_id())); + ges.enqueueGameEvent(event, playerId); + } + } + } + + const QString cardName = nameFromStdString(cmd.card_name()); + const QString cardProviderId = nameFromStdString(cmd.card_provider_id()); + if (zone->hasCoords()) { + bool dontStackSameName = cmd.face_down(); + xCoord = zone->getFreeGridColumn(xCoord, yCoord, cardName, dontStackSameName); + } + if (xCoord < 0) { + xCoord = 0; + } + if (yCoord < 0) { + yCoord = 0; + } + + auto *card = new Server_Card({cardName, cardProviderId}, newCardId(), xCoord, yCoord); + card->moveToThread(thread()); + // Client should already prevent face-down tokens from having attributes; this just an extra server-side check + if (!cmd.face_down()) { + card->setColor(nameFromStdString(cmd.color())); + card->setPT(nameFromStdString(cmd.pt())); + } + card->setAnnotation(nameFromStdString(cmd.annotation())); + card->setDestroyOnZoneChange(cmd.destroy_on_zone_change()); + card->setFaceDown(cmd.face_down()); + + zone->insertCard(card, xCoord, yCoord); + sendCreateTokenEvents(zone, card, xCoord, yCoord, ges); + + // check if the token is a replacement for an existing card + if (!targetCard) { + return Response::RespOk; + } + + switch (cmd.target_mode()) { + case Command_CreateToken::ATTACH_TO: { + Command_AttachCard cmd2; + cmd2.set_start_zone(cmd.target_zone()); + cmd2.set_card_id(cmd.target_card_id()); + + cmd2.set_target_player_id(zone->getPlayer()->getPlayerId()); + cmd2.set_target_zone(cmd.zone()); + cmd2.set_target_card_id(card->getId()); + + return cmdAttachCard(cmd2, rc, ges); + } + + case Command_CreateToken::TRANSFORM_INTO: { + // Copy attributes that are not present in the CreateToken event + Event_SetCardAttr event; + event.set_zone_name(card->getZone()->getName().toStdString()); + event.set_card_id(card->getId()); + + if (card->getTapped() != targetCard->getTapped()) { + card->setAttribute(AttrTapped, QVariant(targetCard->getTapped()).toString(), &event); + ges.enqueueGameEvent(event, playerId); + } + + if (card->getAttacking() != targetCard->getAttacking()) { + card->setAttribute(AttrAttacking, QVariant(targetCard->getAttacking()).toString(), &event); + ges.enqueueGameEvent(event, playerId); + } + + if (card->getFaceDown() != targetCard->getFaceDown()) { + card->setAttribute(AttrFaceDown, QVariant(targetCard->getFaceDown()).toString(), &event); + ges.enqueueGameEvent(event, playerId); + } + + if (card->getDoesntUntap() != targetCard->getDoesntUntap()) { + card->setAttribute(AttrDoesntUntap, QVariant(targetCard->getDoesntUntap()).toString(), &event); + ges.enqueueGameEvent(event, playerId); + } + + // Copy counters + QMapIterator i(targetCard->getCounters()); + while (i.hasNext()) { + i.next(); + + Event_SetCardCounter _event; + _event.set_zone_name(card->getZone()->getName().toStdString()); + _event.set_card_id(card->getId()); + + card->setCounter(i.key(), i.value(), &_event); + ges.enqueueGameEvent(_event, playerId); + } + + // Copy parent card + if (Server_Card *parentCard = targetCard->getParentCard()) { + targetCard->setParentCard(nullptr); + card->setParentCard(parentCard); + + ges.enqueueGameEvent(makeAttachCardEvent(card, parentCard), playerId); + } + + // Copy attachments + while (!targetCard->getAttachedCards().isEmpty()) { + Server_Card *attachedCard = targetCard->getAttachedCards().last(); + attachedCard->setParentCard(card); + + ges.enqueueGameEvent(makeAttachCardEvent(attachedCard, card), + attachedCard->getZone()->getPlayer()->getPlayerId()); + } + + // Copy Arrows + for (auto *player : game->getPlayers().values()) { + QList changedArrowIds; + for (Server_Arrow *arrow : player->getArrows()) { + bool sendGameEvent = false; + const auto *startCard = arrow->getStartCard(); + if (startCard == targetCard) { + sendGameEvent = true; + arrow->setStartCard(card); + startCard = card; + } + const auto *targetItem = arrow->getTargetItem(); + if (targetItem == targetCard) { + sendGameEvent = true; + arrow->setTargetItem(card); + targetItem = card; + } + if (sendGameEvent) { + Event_CreateArrow _event; + ServerInfo_Arrow *arrowInfo = _event.mutable_arrow_info(); + changedArrowIds.append(arrow->getId()); + int id = player->newArrowId(); + arrow->setId(id); + arrowInfo->set_id(id); + arrowInfo->set_start_player_id(player->getPlayerId()); + arrowInfo->set_start_zone(startCard->getZone()->getName().toStdString()); + arrowInfo->set_start_card_id(startCard->getId()); + const Server_AbstractPlayer *arrowTargetPlayer = + qobject_cast(targetItem); + if (arrowTargetPlayer != nullptr) { + arrowInfo->set_target_player_id(arrowTargetPlayer->getPlayerId()); + } else { + const Server_Card *arrowTargetCard = qobject_cast(targetItem); + arrowInfo->set_target_player_id(arrowTargetCard->getZone()->getPlayer()->getPlayerId()); + arrowInfo->set_target_zone(arrowTargetCard->getZone()->getName().toStdString()); + arrowInfo->set_target_card_id(arrowTargetCard->getId()); + } + arrowInfo->mutable_arrow_color()->CopyFrom(arrow->getColor()); + ges.enqueueGameEvent(_event, player->getPlayerId()); + } + } + for (int id : changedArrowIds) { + player->updateArrowId(id); + } + } + + targetCard->resetState(); + card->setStashedCard(targetCard); + break; + } + } + + return Response::RespOk; +} + +/** + * Creates and sends the events required to properly communicate the given token creation. + * Primarily written to handle creating face-down tokens. + */ +void Server_AbstractPlayer::sendCreateTokenEvents(Server_CardZone *zone, + Server_Card *card, + int xCoord, + int yCoord, + GameEventStorage &ges) +{ + // Token is not face-down; things are easy + if (!card->getFaceDown()) { + ges.enqueueGameEvent(makeCreateTokenEvent(zone, card, xCoord, yCoord), playerId); + return; + } + + // Token is face-down. We have to send different info to each player + auto eventOthers = makeCreateTokenEvent(zone, card, xCoord, yCoord, false); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + + auto eventPrivate = makeCreateTokenEvent(zone, card, xCoord, yCoord, true); + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); + + // Event_CreateToken didn't use to have face_down field; send attribute event afterward for backwards compatibility + Event_SetCardAttr event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + event.set_attribute(AttrFaceDown); + event.set_attr_value("1"); + ges.enqueueGameEvent(event, playerId); +} + +Response::ResponseCode +Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + Server_AbstractPlayer *startPlayer = game->getPlayer(cmd.start_player_id()); + Server_AbstractPlayer *targetPlayer = game->getPlayer(cmd.target_player_id()); + if (!startPlayer || !targetPlayer) { + return Response::RespNameNotFound; + } + QString startZoneName = nameFromStdString(cmd.start_zone()); + Server_CardZone *startZone = startPlayer->getZones().value(startZoneName); + bool playerTarget = !cmd.has_target_zone(); + Server_CardZone *targetZone = nullptr; + if (!playerTarget) { + targetZone = targetPlayer->getZones().value(nameFromStdString(cmd.target_zone())); + } + if (!startZone || (!targetZone && !playerTarget)) { + return Response::RespNameNotFound; + } + if (startZone->getType() != ServerInfo_Zone::PublicZone) { + return Response::RespContextError; + } + Server_Card *startCard = startZone->getCard(cmd.start_card_id()); + if (!startCard) { + return Response::RespNameNotFound; + } + Server_Card *targetCard = nullptr; + if (!playerTarget) { + if (targetZone->getType() != ServerInfo_Zone::PublicZone) { + return Response::RespContextError; + } + targetCard = targetZone->getCard(cmd.target_card_id()); + } + + Server_ArrowTarget *targetItem; + if (playerTarget) { + targetItem = targetPlayer; + } else { + targetItem = targetCard; + } + if (!targetItem) { + return Response::RespNameNotFound; + } + + for (Server_Arrow *temp : arrows) { + if ((temp->getStartCard() == startCard) && (temp->getTargetItem() == targetItem)) { + return Response::RespContextError; + } + } + + auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color()); + addArrow(arrow); + + Event_CreateArrow event; + ServerInfo_Arrow *arrowInfo = event.mutable_arrow_info(); + arrowInfo->set_id(arrow->getId()); + arrowInfo->set_start_player_id(startPlayer->getPlayerId()); + arrowInfo->set_start_zone(startZoneName.toStdString()); + arrowInfo->set_start_card_id(startCard->getId()); + arrowInfo->set_target_player_id(targetPlayer->getPlayerId()); + if (!playerTarget) { + arrowInfo->set_target_zone(cmd.target_zone()); + arrowInfo->set_target_card_id(cmd.target_card_id()); + } + arrowInfo->mutable_arrow_color()->CopyFrom(cmd.arrow_color()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdDeleteArrow(const Command_DeleteArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + if (!deleteArrow(cmd.arrow_id())) { + return Response::RespNameNotFound; + } + + Event_DeleteArrow event; + event.set_arrow_id(cmd.arrow_id()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdSetCardAttr(const Command_SetCardAttr &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + return setCardAttrHelper(ges, playerId, nameFromStdString(cmd.zone()), cmd.card_id(), cmd.attribute(), + nameFromStdString(cmd.attr_value())); +} + +Response::ResponseCode Server_AbstractPlayer::cmdSetCardCounter(const Command_SetCardCounter &cmd, + ResponseContainer & /*rc*/, + GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone())); + if (!zone) { + return Response::RespNameNotFound; + } + if (!zone->hasCoords()) { + return Response::RespContextError; + } + + Server_Card *card = zone->getCard(cmd.card_id()); + if (!card) { + return Response::RespNameNotFound; + } + + Event_SetCardCounter event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + card->setCounter(cmd.counter_id(), cmd.counter_value(), &event); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_AbstractPlayer::cmdIncCardCounter(const Command_IncCardCounter &cmd, + ResponseContainer & /*rc*/, + GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone())); + if (!zone) { + return Response::RespNameNotFound; + } + if (!zone->hasCoords()) { + return Response::RespContextError; + } + + Server_Card *card = zone->getCard(cmd.card_id()); + if (!card) { + return Response::RespNameNotFound; + } + + int newValue = card->getCounter(cmd.counter_id()) + cmd.counter_delta(); + card->setCounter(cmd.counter_id(), newValue); + + Event_SetCardCounter event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + event.set_counter_id(cmd.counter_id()); + event.set_counter_value(newValue); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + + Server_AbstractPlayer *otherPlayer = game->getPlayer(cmd.player_id()); + if (!otherPlayer) { + return Response::RespNameNotFound; + } + Server_CardZone *zone = otherPlayer->getZones().value(nameFromStdString(cmd.zone_name())); + if (!zone) { + return Response::RespNameNotFound; + } + if (!((zone->getType() == ServerInfo_Zone::PublicZone) || (this == otherPlayer))) { + return Response::RespContextError; + } + + int numberCards = cmd.number_cards(); + const QList &cards = zone->getCards(); + + auto *re = new Response_DumpZone; + ServerInfo_Zone *zoneInfo = re->mutable_zone_info(); + zoneInfo->set_name(zone->getName().toStdString()); + zoneInfo->set_type(zone->getType()); + zoneInfo->set_with_coords(zone->hasCoords()); + zoneInfo->set_card_count(numberCards < cards.size() ? cards.size() : numberCards); + + for (int i = 0; (i < cards.size()) && (i < numberCards || numberCards == -1); ++i) { + const auto &findId = cmd.is_reversed() ? cards.size() - numberCards + i : i; + Server_Card *card = cards[findId]; + QString displayedName = card->getFaceDown() ? QString() : card->getName(); + ServerInfo_Card *cardInfo = zoneInfo->add_card_list(); + cardInfo->set_provider_id(card->getProviderId().toStdString()); + cardInfo->set_name(displayedName.toStdString()); + if (zone->getType() == ServerInfo_Zone::HiddenZone) { + cardInfo->set_id(findId); + } else { + cardInfo->set_id(card->getId()); + cardInfo->set_x(card->getX()); + cardInfo->set_y(card->getY()); + cardInfo->set_face_down(card->getFaceDown()); + cardInfo->set_tapped(card->getTapped()); + cardInfo->set_attacking(card->getAttacking()); + cardInfo->set_color(card->getColor().toStdString()); + cardInfo->set_pt(card->getPT().toStdString()); + cardInfo->set_annotation(card->getAnnotation().toStdString()); + cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); + cardInfo->set_doesnt_untap(card->getDoesntUntap()); + + QMapIterator cardCounterIterator(card->getCounters()); + while (cardCounterIterator.hasNext()) { + cardCounterIterator.next(); + ServerInfo_CardCounter *counterInfo = cardInfo->add_counter_list(); + counterInfo->set_id(cardCounterIterator.key()); + counterInfo->set_value(cardCounterIterator.value()); + } + + if (card->getParentCard()) { + cardInfo->set_attach_player_id(card->getParentCard()->getZone()->getPlayer()->getPlayerId()); + cardInfo->set_attach_zone(card->getParentCard()->getZone()->getName().toStdString()); + cardInfo->set_attach_card_id(card->getParentCard()->getId()); + } + } + } + if (zone->getType() == ServerInfo_Zone::HiddenZone) { + zone->setCardsBeingLookedAt(numberCards); + + Event_DumpZone event; + event.set_zone_owner_id(otherPlayer->getPlayerId()); + event.set_zone_name(zone->getName().toStdString()); + event.set_number_cards(numberCards); + event.set_is_reversed(cmd.is_reversed()); + ges.enqueueGameEvent(event, playerId); + } + rc.setResponseExtension(re); + return Response::RespOk; +} + +Response::ResponseCode +Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) { + return Response::RespGameNotStarted; + } + if (conceded) { + return Response::RespContextError; + } + + if (cmd.has_player_id()) { + Server_AbstractPlayer *otherPlayer = game->getPlayer(cmd.player_id()); + if (!otherPlayer) + return Response::RespNameNotFound; + } + Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone_name())); + if (!zone) { + return Response::RespNameNotFound; + } + + QList cardsToReveal; + if (cmd.top_cards() != -1) { + for (int i = 0; i < cmd.top_cards(); i++) { + Server_Card *card = zone->getCard(i); + if (!card) { + return Response::RespNameNotFound; + } + cardsToReveal.append(card); + } + } else if (cmd.card_id_size() == 0) { + cardsToReveal = zone->getCards(); + } else if (cmd.card_id_size() == 1 && cmd.card_id(0) == -2) { + // If there is a single card_id with value -2 (ie + // Player::RANDOM_CARD_FROM_ZONE), pick a random card. + // + // This is to be compatible with clients supporting a single card_id + // value, which send value -2 to request a random card. + if (zone->getCards().isEmpty()) { + return Response::RespContextError; + } + + cardsToReveal.append(zone->getCards().at(rng->rand(0, zone->getCards().size() - 1))); + } else { + for (auto cardId : cmd.card_id()) { + Server_Card *card = zone->getCard(cardId); + if (!card) { + return Response::RespNameNotFound; + } + cardsToReveal.append(card); + } + } + + Event_RevealCards eventOthers; + eventOthers.set_grant_write_access(cmd.grant_write_access()); + eventOthers.set_zone_name(zone->getName().toStdString()); + eventOthers.set_number_of_cards(cardsToReveal.size()); + for (auto cardId : cmd.card_id()) { + eventOthers.add_card_id(cardId); + } + if (cmd.has_player_id()) { + eventOthers.set_other_player_id(cmd.player_id()); + } + + Event_RevealCards eventPrivate(eventOthers); + + for (auto card : cardsToReveal) { + ServerInfo_Card *cardInfo = eventPrivate.add_cards(); + + cardInfo->set_id(card->getId()); + cardInfo->set_provider_id(card->getProviderId().toStdString()); + cardInfo->set_name(card->getName().toStdString()); + cardInfo->set_x(card->getX()); + cardInfo->set_y(card->getY()); + cardInfo->set_face_down(card->getFaceDown()); + cardInfo->set_tapped(card->getTapped()); + cardInfo->set_attacking(card->getAttacking()); + cardInfo->set_color(card->getColor().toStdString()); + cardInfo->set_pt(card->getPT().toStdString()); + cardInfo->set_annotation(card->getAnnotation().toStdString()); + cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); + cardInfo->set_doesnt_untap(card->getDoesntUntap()); + + QMapIterator cardCounterIterator(card->getCounters()); + while (cardCounterIterator.hasNext()) { + cardCounterIterator.next(); + ServerInfo_CardCounter *counterInfo = cardInfo->add_counter_list(); + counterInfo->set_id(cardCounterIterator.key()); + counterInfo->set_value(cardCounterIterator.value()); + } + + if (card->getParentCard()) { + cardInfo->set_attach_player_id(card->getParentCard()->getZone()->getPlayer()->getPlayerId()); + cardInfo->set_attach_zone(card->getParentCard()->getZone()->getName().toStdString()); + cardInfo->set_attach_card_id(card->getParentCard()->getId()); + } + } + + if (cmd.has_player_id()) { + if (cmd.grant_write_access()) { + zone->addWritePermission(cmd.player_id()); + } + + if (getJudge()) { + ges.setOverwriteOwnership(true); + } + + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, cmd.player_id()); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + } else { + if (cmd.grant_write_access()) { + const QList &participantIds = game->getParticipants().keys(); + for (int anyParticipantId : participantIds) { + zone->addWritePermission(anyParticipantId); + } + } + + ges.enqueueGameEvent(eventPrivate, playerId); + } + + return Response::RespOk; +} + +Response::ResponseCode Server_AbstractPlayer::cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, + ResponseContainer & /* rc */, + GameEventStorage &ges) +{ + Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone_name())); + if (!zone) { + return Response::RespNameNotFound; + } + + Event_ChangeZoneProperties event; + event.set_zone_name(cmd.zone_name()); + + // Neither value set -> error. + if (!cmd.has_always_look_at_top_card() && !cmd.has_always_reveal_top_card()) { + return Response::RespContextError; + } + + // Neither value changed -> error. + bool alwaysRevealChanged = + cmd.has_always_reveal_top_card() && zone->getAlwaysRevealTopCard() != cmd.always_reveal_top_card(); + bool alwaysLookAtTopChanged = + cmd.has_always_look_at_top_card() && zone->getAlwaysLookAtTopCard() != cmd.always_look_at_top_card(); + if (!alwaysRevealChanged && !alwaysLookAtTopChanged) { + return Response::RespContextError; + } + + if (cmd.has_always_reveal_top_card()) { + zone->setAlwaysRevealTopCard(cmd.always_reveal_top_card()); + event.set_always_reveal_top_card(cmd.always_reveal_top_card()); + } + if (cmd.has_always_look_at_top_card()) { + zone->setAlwaysLookAtTopCard(cmd.always_look_at_top_card()); + event.set_always_look_at_top_card(cmd.always_look_at_top_card()); + } + ges.enqueueGameEvent(event, playerId); + return Response::RespOk; +} + +void Server_AbstractPlayer::getInfo(ServerInfo_Player *info, + Server_AbstractParticipant *recipient, + bool omniscient, + bool withUserInfo) +{ + getProperties(*info->mutable_properties(), withUserInfo); + if (recipient == this) { + if (deck) { + info->set_deck_list(deck->writeToString_Native().toStdString()); + } + } + + for (Server_Arrow *arrow : arrows) { + arrow->getInfo(info->add_arrow_list()); + } + + for (Server_CardZone *zone : zones) { + zone->getInfo(info->add_zone_list(), recipient, omniscient); + } +} + +void Server_AbstractPlayer::getPlayerProperties(ServerInfo_PlayerProperties &result) +{ + result.set_conceded(conceded); + result.set_sideboard_locked(sideboardLocked); + result.set_ready_start(readyStart); + if (deck) { + result.set_deck_hash(deck->getDeckHash().toStdString()); + } +} diff --git a/common/server/game/server_abstract_player.h b/common/server/game/server_abstract_player.h new file mode 100644 index 000000000..51828adf7 --- /dev/null +++ b/common/server/game/server_abstract_player.h @@ -0,0 +1,152 @@ +#ifndef ABSTRACT_PLAYER_H +#define ABSTRACT_PLAYER_H + +#include "../../serverinfo_user_container.h" +#include "server_abstract_participant.h" + +#include +#include +#include + +class CardToMove; +class DeckList; +class Server_Arrow; +class Server_Card; +class Server_CardZone; +class Server_Counter; +struct MoveCardStruct; + +class Server_AbstractPlayer : public Server_AbstractParticipant +{ + Q_OBJECT +private: + class MoveCardCompareFunctor; + QMap arrows; + + void sendCreateTokenEvents(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord, GameEventStorage &ges); + void getPlayerProperties(ServerInfo_PlayerProperties &result) override; + +protected: + bool conceded; + DeckList *deck; + bool sideboardLocked; + QMap zones; + bool readyStart; + int nextCardId; + + void revealTopCardIfNeeded(Server_CardZone *zone, GameEventStorage &ges); + +public: + Server_AbstractPlayer(Server_Game *_game, + int _playerId, + const ServerInfo_User &_userInfo, + bool _judge, + Server_AbstractUserInterface *_handler); + ~Server_AbstractPlayer() override; + void prepareDestroy() override; + const DeckList *getDeckList() const + { + return deck; + } + bool getReadyStart() const + { + return readyStart; + } + void setReadyStart(bool _readyStart) + { + readyStart = _readyStart; + } + bool getConceded() const + { + return conceded; + } + void setConceded(bool _conceded) + { + conceded = _conceded; + } + + const QMap &getZones() const + { + return zones; + } + const QMap &getArrows() const + { + return arrows; + } + + int newCardId(); + int newArrowId() const; + + void addZone(Server_CardZone *zone); + void addArrow(Server_Arrow *arrow); + void updateArrowId(int id); + bool deleteArrow(int arrowId); + + virtual void setupZones(); + virtual void clearZones(); + + Response::ResponseCode moveCard(GameEventStorage &ges, + Server_CardZone *startzone, + const QList &_cards, + Server_CardZone *targetzone, + int xCoord, + int yCoord, + bool fixFreeSpaces = true, + bool undoingDraw = false, + bool isReversed = false); + virtual void onCardBeingMoved(GameEventStorage &ges, + const MoveCardStruct &cardStruct, + Server_CardZone *startzone, + Server_CardZone *targetzone, + bool undoingDraw); + + void unattachCard(GameEventStorage &ges, Server_Card *card); + Response::ResponseCode setCardAttrHelper(GameEventStorage &ges, + int targetPlayerId, + const QString &zone, + int cardId, + CardAttribute attribute, + const QString &attrValue, + Server_Card *unzonedCard = nullptr); + + virtual Response::ResponseCode + cmdConcede(const Command_Concede &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdUnconcede(const Command_Unconcede &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdRollDie(const Command_RollDie &cmd, ResponseContainer &rc, GameEventStorage &ges) const override; + virtual Response::ResponseCode + cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdFlipCard(const Command_FlipCard &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdAttachCard(const Command_AttachCard &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdCreateArrow(const Command_CreateArrow &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdDeleteArrow(const Command_DeleteArrow &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdSetCardAttr(const Command_SetCardAttr &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdSetCardCounter(const Command_SetCardCounter &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdIncCardCounter(const Command_IncCardCounter &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode + cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer &rc, GameEventStorage &ges) override; + virtual Response::ResponseCode cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, + ResponseContainer &rc, + GameEventStorage &ges) override; + + virtual void getInfo(ServerInfo_Player *info, + Server_AbstractParticipant *playerWhosAsking, + bool omniscient, + bool withUserInfo) override; +}; + +#endif diff --git a/common/server/game/server_cardzone.cpp b/common/server/game/server_cardzone.cpp index 44b946fa0..8f019f746 100644 --- a/common/server/game/server_cardzone.cpp +++ b/common/server/game/server_cardzone.cpp @@ -21,13 +21,13 @@ #include "../rng_abstract.h" #include "pb/command_move_card.pb.h" +#include "server_abstract_player.h" #include "server_card.h" -#include "server_player.h" #include #include -Server_CardZone::Server_CardZone(Server_Player *_player, +Server_CardZone::Server_CardZone(Server_AbstractPlayer *_player, const QString &_name, bool _has_coords, ServerInfo_Zone::ZoneType _type) diff --git a/common/server/game/server_cardzone.h b/common/server/game/server_cardzone.h index 4c7318f08..3f83be9d4 100644 --- a/common/server/game/server_cardzone.h +++ b/common/server/game/server_cardzone.h @@ -28,7 +28,7 @@ #include class Server_Card; -class Server_Player; +class Server_AbstractPlayer; class Server_AbstractParticipant; class Server_Game; class GameEventStorage; @@ -36,7 +36,7 @@ class GameEventStorage; class Server_CardZone { private: - Server_Player *player; + Server_AbstractPlayer *player; QString name; bool has_coords; // having coords means this zone has x and y coordinates ServerInfo_Zone::ZoneType type; @@ -52,7 +52,10 @@ private: void insertCardIntoCoordMap(Server_Card *card, int x, int y); public: - Server_CardZone(Server_Player *_player, const QString &_name, bool _has_coords, ServerInfo_Zone::ZoneType _type); + Server_CardZone(Server_AbstractPlayer *_player, + const QString &_name, + bool _has_coords, + ServerInfo_Zone::ZoneType _type); ~Server_CardZone(); const QList &getCards() const @@ -84,7 +87,7 @@ public: { return name; } - Server_Player *getPlayer() const + Server_AbstractPlayer *getPlayer() const { return player; } diff --git a/common/server/game/server_game.cpp b/common/server/game/server_game.cpp index eb064d878..278fcfdc1 100644 --- a/common/server/game/server_game.cpp +++ b/common/server/game/server_game.cpp @@ -41,6 +41,7 @@ #include "pb/event_set_active_player.pb.h" #include "pb/game_replay.pb.h" #include "pb/serverinfo_playerping.pb.h" +#include "server_abstract_player.h" #include "server_arrow.h" #include "server_card.h" #include "server_cardzone.h" @@ -221,24 +222,24 @@ void Server_Game::pingClockTimeout() } } -QMap Server_Game::getPlayers() const // copies pointers to new map +QMap Server_Game::getPlayers() const // copies pointers to new map { - QMap players; + QMap players; QMutexLocker locker(&gameMutex); for (int id : participants.keys()) { auto *participant = participants[id]; if (!participant->getSpectator()) { - players[id] = static_cast(participant); + players[id] = static_cast(participant); } } return players; } -Server_Player *Server_Game::getPlayer(int id) const +Server_AbstractPlayer *Server_Game::getPlayer(int id) const { auto *participant = participants.value(id); if (!participant->getSpectator()) { - return static_cast(participant); + return static_cast(participant); } else { return nullptr; } @@ -339,7 +340,7 @@ void Server_Game::doStartGameIfReady(bool forceStartGame) } } - for (Server_Player *player : players.values()) { + for (Server_AbstractPlayer *player : players.values()) { player->setupZones(); } @@ -534,7 +535,7 @@ void Server_Game::removeParticipant(Server_AbstractParticipant *participant, Eve bool spectator = participant->getSpectator(); GameEventStorage ges; if (!spectator) { - auto *player = static_cast(participant); + auto *player = static_cast(participant); removeArrowsRelatedToPlayer(ges, player); unattachCards(ges, player); } @@ -577,14 +578,14 @@ void Server_Game::removeParticipant(Server_AbstractParticipant *participant, Eve emit gameInfoChanged(gameInfo); } -void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Player *player) +void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_AbstractPlayer *player) { QMutexLocker locker(&gameMutex); // Remove all arrows of other players pointing to the player being removed or to one of his cards. // Also remove all arrows starting at one of his cards. This is necessary since players can create // arrows that start at another person's cards. - for (Server_Player *anyPlayer : getPlayers().values()) { + for (Server_AbstractPlayer *anyPlayer : getPlayers().values()) { QList arrows = anyPlayer->getArrows().values(); QList toDelete; for (int i = 0; i < arrows.size(); ++i) { @@ -593,7 +594,7 @@ void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Play if (targetCard) { if (targetCard->getZone() != nullptr && targetCard->getZone()->getPlayer() == player) toDelete.append(arrow); - } else if (static_cast(arrow->getTargetItem()) == player) + } else if (static_cast(arrow->getTargetItem()) == player) toDelete.append(arrow); // Don't use else here! It has to happen regardless of whether targetCard == 0. @@ -610,7 +611,7 @@ void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Play } } -void Server_Game::unattachCards(GameEventStorage &ges, Server_Player *player) +void Server_Game::unattachCards(GameEventStorage &ges, Server_AbstractPlayer *player) { QMutexLocker locker(&gameMutex); diff --git a/common/server/game/server_game.h b/common/server/game/server_game.h index 6bb162763..b3a56be3e 100644 --- a/common/server/game/server_game.h +++ b/common/server/game/server_game.h @@ -36,7 +36,7 @@ class QTimer; class GameEventContainer; class GameReplay; class Server_Room; -class Server_Player; +class Server_AbstractPlayer; class Server_AbstractParticipant; class ServerInfo_User; class ServerInfo_Game; @@ -130,8 +130,8 @@ public: } int getPlayerCount() const; int getSpectatorCount() const; - QMap getPlayers() const; - Server_Player *getPlayer(int id) const; + QMap getPlayers() const; + Server_AbstractPlayer *getPlayer(int id) const; const QMap &getParticipants() const { return participants; @@ -185,8 +185,8 @@ public: bool judge, bool broadcastUpdate = true); void removeParticipant(Server_AbstractParticipant *participant, Event_Leave::LeaveReason reason); - void removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Player *player); - void unattachCards(GameEventStorage &ges, Server_Player *player); + void removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_AbstractPlayer *player); + void unattachCards(GameEventStorage &ges, Server_AbstractPlayer *player); bool kickParticipant(int playerId); void startGameIfReady(bool forceStartGame); void stopGameIfFinished(); diff --git a/common/server/game/server_move_card_struct.h b/common/server/game/server_move_card_struct.h new file mode 100644 index 000000000..c5b293b6d --- /dev/null +++ b/common/server/game/server_move_card_struct.h @@ -0,0 +1,26 @@ +#ifndef MOVE_CARD_STRUCT +#define MOVE_CARD_STRUCT + +#include "server_card.h" +class CardToMove; + +struct MoveCardStruct +{ + Server_Card *card; + int position; + const CardToMove *cardToMove; + int xCoord, yCoord; + MoveCardStruct(Server_Card *_card, int _position, const CardToMove *_cardToMove) + : card(_card), position(_position), cardToMove(_cardToMove), xCoord(_card->getX()), yCoord(_card->getY()) + + { + } + bool operator<(const MoveCardStruct &other) const + { + return (yCoord == other.yCoord && + ((xCoord == other.xCoord && position < other.position) || xCoord < other.xCoord)) || + yCoord < other.yCoord; + } +}; + +#endif diff --git a/common/server/game/server_player.cpp b/common/server/game/server_player.cpp index b8b9b7cd5..d5598e138 100644 --- a/common/server/game/server_player.cpp +++ b/common/server/game/server_player.cpp @@ -3,137 +3,55 @@ #include "../../color.h" #include "../../deck_list.h" #include "../../deck_list_card_node.h" -#include "../../get_pb_extension.h" -#include "../../rng_abstract.h" #include "../../trice_limits.h" #include "../server.h" -#include "../server_abstractuserinterface.h" #include "../server_database_interface.h" #include "../server_room.h" -#include "pb/command_attach_card.pb.h" #include "pb/command_change_zone_properties.pb.h" -#include "pb/command_concede.pb.h" -#include "pb/command_create_arrow.pb.h" #include "pb/command_create_counter.pb.h" -#include "pb/command_create_token.pb.h" #include "pb/command_deck_select.pb.h" #include "pb/command_del_counter.pb.h" -#include "pb/command_delete_arrow.pb.h" #include "pb/command_draw_cards.pb.h" -#include "pb/command_dump_zone.pb.h" -#include "pb/command_flip_card.pb.h" -#include "pb/command_game_say.pb.h" -#include "pb/command_inc_card_counter.pb.h" #include "pb/command_inc_counter.pb.h" -#include "pb/command_kick_from_game.pb.h" -#include "pb/command_leave_game.pb.h" #include "pb/command_move_card.pb.h" #include "pb/command_mulligan.pb.h" -#include "pb/command_next_turn.pb.h" -#include "pb/command_ready_start.pb.h" -#include "pb/command_reveal_cards.pb.h" #include "pb/command_reverse_turn.pb.h" -#include "pb/command_roll_die.pb.h" #include "pb/command_set_active_phase.pb.h" -#include "pb/command_set_card_attr.pb.h" -#include "pb/command_set_card_counter.pb.h" #include "pb/command_set_counter.pb.h" #include "pb/command_set_sideboard_lock.pb.h" #include "pb/command_set_sideboard_plan.pb.h" #include "pb/command_shuffle.pb.h" #include "pb/command_undo_draw.pb.h" -#include "pb/context_concede.pb.h" -#include "pb/context_connection_state_changed.pb.h" #include "pb/context_deck_select.pb.h" -#include "pb/context_move_card.pb.h" #include "pb/context_mulligan.pb.h" -#include "pb/context_ready_start.pb.h" #include "pb/context_set_sideboard_lock.pb.h" #include "pb/context_undo_draw.pb.h" -#include "pb/event_attach_card.pb.h" -#include "pb/event_change_zone_properties.pb.h" -#include "pb/event_create_arrow.pb.h" #include "pb/event_create_counter.pb.h" -#include "pb/event_create_token.pb.h" #include "pb/event_del_counter.pb.h" -#include "pb/event_delete_arrow.pb.h" -#include "pb/event_destroy_card.pb.h" #include "pb/event_draw_cards.pb.h" -#include "pb/event_dump_zone.pb.h" -#include "pb/event_flip_card.pb.h" -#include "pb/event_game_say.pb.h" -#include "pb/event_move_card.pb.h" #include "pb/event_player_properties_changed.pb.h" -#include "pb/event_reveal_cards.pb.h" #include "pb/event_reverse_turn.pb.h" -#include "pb/event_roll_die.pb.h" -#include "pb/event_set_card_attr.pb.h" -#include "pb/event_set_card_counter.pb.h" #include "pb/event_set_counter.pb.h" #include "pb/event_shuffle.pb.h" -#include "pb/response.pb.h" #include "pb/response_deck_download.pb.h" -#include "pb/response_dump_zone.pb.h" #include "pb/serverinfo_player.pb.h" -#include "pb/serverinfo_user.pb.h" -#include "server_arrow.h" #include "server_card.h" #include "server_cardzone.h" #include "server_counter.h" #include "server_game.h" - -#include -#include -#include - -struct MoveCardStruct -{ - Server_Card *card; - int position; - const CardToMove *cardToMove; - int xCoord, yCoord; - MoveCardStruct(Server_Card *_card, int _position, const CardToMove *_cardToMove) - : card(_card), position(_position), cardToMove(_cardToMove), xCoord(_card->getX()), yCoord(_card->getY()) - - { - } - bool operator<(const MoveCardStruct &other) const - { - return (yCoord == other.yCoord && - ((xCoord == other.xCoord && position < other.position) || xCoord < other.xCoord)) || - yCoord < other.yCoord; - } -}; +#include "server_move_card_struct.h" Server_Player::Server_Player(Server_Game *_game, int _playerId, const ServerInfo_User &_userInfo, bool _judge, Server_AbstractUserInterface *_userInterface) - : Server_AbstractParticipant(_game, _playerId, _userInfo, _judge, _userInterface), deck(nullptr), nextCardId(0), - readyStart(false), conceded(false), sideboardLocked(true) + : Server_AbstractPlayer(_game, _playerId, _userInfo, _judge, _userInterface) { - spectator = false; } Server_Player::~Server_Player() = default; -void Server_Player::prepareDestroy() -{ - delete deck; - deck = nullptr; - - removeFromGame(); - clearZones(); - - deleteLater(); -} - -int Server_Player::newCardId() -{ - return nextCardId++; -} - int Server_Player::newCounterId() const { int id = 0; @@ -147,19 +65,10 @@ int Server_Player::newCounterId() const return id + 1; } -int Server_Player::newArrowId() const -{ - int id = 0; - for (Server_Arrow *a : arrows) { - if (a->getId() > id) { - id = a->getId(); - } - } - return id + 1; -} - void Server_Player::setupZones() { + Server_AbstractPlayer::setupZones(); + // This may need to be customized according to the game rules. // ------------------------------------------------------------------ @@ -187,7 +96,6 @@ void Server_Player::setupZones() // Assign card ids and create deck from deck list InnerDecklistNode *listRoot = deck->getRoot(); - nextCardId = 0; for (int i = 0; i < listRoot->size(); ++i) { auto *currentZone = dynamic_cast(listRoot->at(i)); Server_CardZone *z; @@ -245,51 +153,15 @@ void Server_Player::setupZones() void Server_Player::clearZones() { - for (Server_CardZone *zone : zones) { - delete zone; - } - zones.clear(); - + Server_AbstractPlayer::clearZones(); for (Server_Counter *counter : counters) { delete counter; } counters.clear(); - for (Server_Arrow *arrow : arrows) { - delete arrow; - } - arrows.clear(); - lastDrawList.clear(); } -void Server_Player::addZone(Server_CardZone *zone) -{ - zones.insert(zone->getName(), zone); -} - -void Server_Player::addArrow(Server_Arrow *arrow) -{ - arrows.insert(arrow->getId(), arrow); -} - -void Server_Player::updateArrowId(int id) -{ - auto *arrow = arrows.take(id); - arrows.insert(arrow->getId(), arrow); -} - -bool Server_Player::deleteArrow(int arrowId) -{ - Server_Arrow *arrow = arrows.value(arrowId, 0); - if (!arrow) { - return false; - } - arrows.remove(arrowId); - delete arrow; - return true; -} - void Server_Player::addCounter(Server_Counter *counter) { counters.insert(counter->getId(), counter); @@ -330,434 +202,34 @@ Response::ResponseCode Server_Player::drawCards(GameEventStorage &ges, int numbe return Response::RespOk; } -void Server_Player::revealTopCardIfNeeded(Server_CardZone *zone, GameEventStorage &ges) +void Server_Player::onCardBeingMoved(GameEventStorage &ges, + const MoveCardStruct &cardStruct, + Server_CardZone *startzone, + Server_CardZone *targetzone, + bool undoingDraw) { - if (zone->getCards().isEmpty()) { - return; - } - if (zone->getAlwaysRevealTopCard()) { - Event_RevealCards revealEvent; - revealEvent.set_zone_name(zone->getName().toStdString()); - revealEvent.add_card_id(0); - zone->getCards().first()->getInfo(revealEvent.add_cards()); + Server_AbstractPlayer::onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw); - ges.enqueueGameEvent(revealEvent, playerId); - return; - } - if (zone->getAlwaysLookAtTopCard()) { - Event_DumpZone dumpEvent; - dumpEvent.set_zone_owner_id(playerId); - dumpEvent.set_zone_name(zone->getName().toStdString()); - dumpEvent.set_number_cards(1); - ges.enqueueGameEvent(dumpEvent, playerId, GameEventStorageItem::SendToOthers); + Server_Card *card = cardStruct.card; - Event_RevealCards revealEvent; - revealEvent.set_zone_name(zone->getName().toStdString()); - revealEvent.set_number_of_cards(1); - revealEvent.add_card_id(0); - zone->getCards().first()->getInfo(revealEvent.add_cards()); - ges.enqueueGameEvent(revealEvent, playerId, GameEventStorageItem::SendToPrivate, playerId); - } -} - -/** - * Creates the create token event. - * By default, will set event's name and color fields to empty if the token is face-down - */ -static Event_CreateToken -makeCreateTokenEvent(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord, bool revealFacedownInfo = false) -{ - Event_CreateToken event; - event.set_zone_name(zone->getName().toStdString()); - event.set_card_id(card->getId()); - event.set_face_down(card->getFaceDown()); - - if (!card->getFaceDown() || revealFacedownInfo) { - event.set_card_name(card->getName().toStdString()); - event.set_card_provider_id(card->getProviderId().toStdString()); - } - - event.set_color(card->getColor().toStdString()); - event.set_pt(card->getPT().toStdString()); - event.set_annotation(card->getAnnotation().toStdString()); - event.set_destroy_on_zone_change(card->getDestroyOnZoneChange()); - event.set_x(xCoord); - event.set_y(yCoord); - return event; -} -static Event_AttachCard makeAttachCardEvent(Server_Card *attachedCard, Server_Card *parentCard = nullptr) -{ - Event_AttachCard event; - event.set_start_zone(attachedCard->getZone()->getName().toStdString()); - event.set_card_id(attachedCard->getId()); - - if (parentCard) { - event.set_target_player_id(parentCard->getZone()->getPlayer()->getPlayerId()); - event.set_target_zone(parentCard->getZone()->getName().toStdString()); - event.set_target_card_id(parentCard->getId()); - } - - return event; -} - -/** - * Determines whether moving the card from startZone to targetZone should cause the card to be destroyed. - */ -static bool -shouldDestroyOnMove(const Server_Card *card, const Server_CardZone *startZone, const Server_CardZone *targetZone) -{ - if (!card->getDestroyOnZoneChange()) { - return false; - } - - if (startZone->getName() == targetZone->getName()) { - return false; - } - - // Allow tokens on the stack - if ((startZone->getName() == "table" || startZone->getName() == "stack") && - (targetZone->getName() == "table" || targetZone->getName() == "stack")) { - return false; - } - - return true; -} - -Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges, - Server_CardZone *startzone, - const QList &_cards, - Server_CardZone *targetzone, - int xCoord, - int yCoord, - bool fixFreeSpaces, - bool undoingDraw, - bool isReversed) -{ - // Disallow controller change to other zones than the table. - if (((targetzone->getType() != ServerInfo_Zone::PublicZone) || !targetzone->hasCoords()) && - (startzone->getPlayer() != targetzone->getPlayer()) && !judge) { - return Response::RespContextError; - } - - if (!targetzone->hasCoords() && (xCoord <= -1)) { - xCoord = targetzone->getCards().size(); - } - - std::set cardsToMove; - QSet cardIdsToMove; - for (auto _card : _cards) { - // The same card being moved twice would lead to undefined behaviour. - if (cardIdsToMove.contains(_card->card_id())) { - continue; - } - cardIdsToMove.insert(_card->card_id()); - - // Consistency checks. In case the command contains illegal moves, try to resolve the legal ones still. - int position; - Server_Card *card = startzone->getCard(_card->card_id(), &position); - if (!card) { - return Response::RespNameNotFound; - } - - // do not allow attached cards to move around on the table - if (card->getParentCard() && targetzone->getName() == "table") { - continue; - } - - // do not allow cards with attachments to stack with other cards - if (!card->getAttachedCards().isEmpty() && !targetzone->isColumnEmpty(xCoord, yCoord)) { - continue; - } - - cardsToMove.insert(MoveCardStruct{card, position, _card}); - } - // In case all moves were filtered out, abort. - if (cardsToMove.empty()) { - return Response::RespContextError; - } - - int xIndex = -1; - bool revealTopStart = false; - bool revealTopTarget = false; - - for (auto cardStruct : cardsToMove) { - Server_Card *card = cardStruct.card; - const CardToMove *thisCardProperties = cardStruct.cardToMove; - int originalPosition = cardStruct.position; - bool faceDown = targetzone->hasCoords() && - (thisCardProperties->has_face_down() ? thisCardProperties->face_down() : card->getFaceDown()); - - bool sourceBeingLookedAt; - int position = startzone->removeCard(card, sourceBeingLookedAt); - - // "Undo draw" should only remain valid if the just-drawn card stays within the user's hand (e.g., they only - // reorder their hand). If a just-drawn card leaves the hand then remove cards before it from the list - // (Ignore the case where the card is currently being un-drawn.) - if (startzone->getName() == "hand" && targetzone->getName() != "hand" && !undoingDraw) { - int index = lastDrawList.lastIndexOf(card->getId()); - if (index != -1) { - lastDrawList.erase(lastDrawList.begin(), lastDrawList.begin() + index); - } - } - - // Attachment relationships can be retained when moving a card onto the opponent's table - if (startzone->getName() != targetzone->getName()) { - // Delete all attachment relationships - if (card->getParentCard()) { - card->setParentCard(nullptr); - } - - // Make a copy of the list because the original one gets modified during the loop - QList attachedCards = card->getAttachedCards(); - for (auto &attachedCard : attachedCards) { - attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard); - } - } - - if (startzone != targetzone) { - // Delete all arrows from and to the card - for (auto *player : game->getPlayers().values()) { - QList arrowsToDelete; - for (Server_Arrow *arrow : player->getArrows()) { - if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) - arrowsToDelete.append(arrow->getId()); - } - for (int j : arrowsToDelete) { - player->deleteArrow(j); - } - } - } - - if (shouldDestroyOnMove(card, startzone, targetzone)) { - Event_DestroyCard event; - event.set_zone_name(startzone->getName().toStdString()); - event.set_card_id(static_cast(card->getId())); - ges.enqueueGameEvent(event, playerId); - - if (Server_Card *stashedCard = card->takeStashedCard()) { - stashedCard->setId(newCardId()); - ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), - playerId); - card->deleteLater(); - card = stashedCard; - } else { - card->deleteLater(); - card = nullptr; - } - } - - if (card) { - ++xIndex; - int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex; - - if (targetzone->hasCoords()) { - newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); - } else { - yCoord = 0; - card->resetState(targetzone->getName() == "stack"); - } - - targetzone->insertCard(card, newX, yCoord); - int targetLookedCards = targetzone->getCardsBeingLookedAt(); - bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown()); - if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) { - if (sourceKnownToPlayer) { - targetLookedCards += 1; - } else { - targetLookedCards = newX; - } - targetzone->setCardsBeingLookedAt(targetLookedCards); - } - - bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone); - bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone); - - int oldCardId = card->getId(); - if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) { - card->setId(targetzone->getPlayer()->newCardId()); - } - card->setFaceDown(faceDown); - - Event_MoveCard eventOthers; - eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId()); - eventOthers.set_start_zone(startzone->getName().toStdString()); - eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId()); - if (startzone != targetzone) { - eventOthers.set_target_zone(targetzone->getName().toStdString()); - } - eventOthers.set_y(yCoord); - eventOthers.set_face_down(faceDown); - - Event_MoveCard eventPrivate(eventOthers); - if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone || - startzone->getType() != ServerInfo_Zone::HiddenZone) { - eventPrivate.set_card_id(oldCardId); - eventPrivate.set_new_card_id(card->getId()); - } else { - eventPrivate.set_card_id(-1); - eventPrivate.set_new_card_id(-1); - } - if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) { - QString privateCardName = card->getName(); - eventPrivate.set_card_name(privateCardName.toStdString()); - eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString()); - } - if (startzone->getType() == ServerInfo_Zone::HiddenZone) { - eventPrivate.set_position(position); - } else { - eventPrivate.set_position(-1); - } - - eventPrivate.set_x(newX); - - // Other players do not get to see the start and/or target position of the card if the respective - // part of the zone is being looked at. The information is not needed anyway because in hidden zones, - // all cards are equal. - if (((startzone->getType() == ServerInfo_Zone::HiddenZone) && - ((startzone->getCardsBeingLookedAt() > position) || (startzone->getCardsBeingLookedAt() == -1))) || - (startzone->getType() == ServerInfo_Zone::PublicZone)) { - eventOthers.set_position(-1); - } else { - eventOthers.set_position(position); - } - if ((targetzone->getType() == ServerInfo_Zone::HiddenZone) && - ((targetzone->getCardsBeingLookedAt() > newX) || (targetzone->getCardsBeingLookedAt() == -1))) { - eventOthers.set_x(-1); - } else { - eventOthers.set_x(newX); - } - - if ((startzone->getType() == ServerInfo_Zone::PublicZone) || - (targetzone->getType() == ServerInfo_Zone::PublicZone)) { - eventOthers.set_card_id(oldCardId); - if (!(sourceHiddenToOthers && targetHiddenToOthers)) { - QString publicCardName = card->getName(); - eventOthers.set_card_name(publicCardName.toStdString()); - eventOthers.set_new_card_provider_id(card->getProviderId().toStdString()); - } - eventOthers.set_new_card_id(card->getId()); - } - - ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); - ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); - - if (thisCardProperties->tapped()) { - setCardAttrHelper(ges, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), card->getId(), - AttrTapped, "1"); - } - QString ptString = QString::fromStdString(thisCardProperties->pt()); - if (!ptString.isEmpty()) { - setCardAttrHelper(ges, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), card->getId(), - AttrPT, ptString); - } - - // If card is transferring to a different player, leave an annotation of who actually "owns" the card - const auto &priorAnnotation = card->getAnnotation(); - if (startzone->getPlayer() != targetzone->getPlayer() && !priorAnnotation.contains("Owner:")) { - const auto &ownerAnnotation = - "Owner: " + QString::fromStdString(startzone->getPlayer()->getUserInfo()->name()); - const auto &newAnnotation = - priorAnnotation.isEmpty() ? ownerAnnotation : ownerAnnotation + "\n\n" + priorAnnotation; - setCardAttrHelper(ges, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), card->getId(), - AttrAnnotation, newAnnotation, card); - } - - if (originalPosition == 0) { - revealTopStart = true; - } - if (newX == 0) { - revealTopTarget = true; - } + // "Undo draw" should only remain valid if the just-drawn card stays within the user's hand (e.g., they only + // reorder their hand). If a just-drawn card leaves the hand then remove cards before it from the list + // (Ignore the case where the card is currently being un-drawn.) + if (startzone->getName() == "hand" && targetzone->getName() != "hand" && !undoingDraw) { + int index = lastDrawList.lastIndexOf(card->getId()); + if (index != -1) { + lastDrawList.erase(lastDrawList.begin(), lastDrawList.begin() + index); } } - if (revealTopStart) { - revealTopCardIfNeeded(startzone, ges); - } - if (targetzone != startzone && revealTopTarget) { - revealTopCardIfNeeded(targetzone, ges); - } - if (undoingDraw) { - ges.setGameEventContext(Context_UndoDraw()); - } else { - ges.setGameEventContext(Context_MoveCard()); - } - - if (startzone->hasCoords() && fixFreeSpaces) { - startzone->fixFreeSpaces(ges); - } - - return Response::RespOk; -} - -void Server_Player::unattachCard(GameEventStorage &ges, Server_Card *card) -{ - Server_CardZone *zone = card->getZone(); - Server_Card *parentCard = card->getParentCard(); - card->setParentCard(nullptr); - - ges.enqueueGameEvent(makeAttachCardEvent(card), playerId); - - auto *cardToMove = new CardToMove; - cardToMove->set_card_id(card->getId()); - moveCard(ges, zone, QList() << cardToMove, zone, -1, card->getY(), card->getFaceDown()); - delete cardToMove; - - if (parentCard->getZone()) { - parentCard->getZone()->updateCardCoordinates(parentCard, parentCard->getX(), parentCard->getY()); - } -} - -Response::ResponseCode Server_Player::setCardAttrHelper(GameEventStorage &ges, - int targetPlayerId, - const QString &zoneName, - int cardId, - CardAttribute attribute, - const QString &attrValue, - Server_Card *unzonedCard) -{ - Server_CardZone *zone = getZones().value(zoneName); - if (!zone) { - return Response::RespNameNotFound; - } - if (!zone->hasCoords()) { - return Response::RespContextError; - } - - QString result; - if (cardId == -1) { - QListIterator CardIterator(zone->getCards()); - while (CardIterator.hasNext()) { - result = CardIterator.next()->setAttribute(attribute, attrValue, true); - if (result.isNull()) { - return Response::RespInvalidCommand; - } - } - } else { - Server_Card *card = unzonedCard == nullptr ? zone->getCard(cardId) : unzonedCard; - if (!card) { - return Response::RespNameNotFound; - } - result = card->setAttribute(attribute, attrValue, false); - if (result.isNull()) { - return Response::RespInvalidCommand; - } - } - - Event_SetCardAttr event; - event.set_zone_name(zone->getName().toStdString()); - if (cardId != -1) { - event.set_card_id(cardId); - } - event.set_attribute(attribute); - event.set_attr_value(result.toStdString()); - ges.enqueueGameEvent(event, targetPlayerId); - - return Response::RespOk; } Response::ResponseCode Server_Player::cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &rc, GameEventStorage &ges) { + if (game->getGameStarted()) { + return Response::RespContextError; + } + DeckList *newDeck; if (cmd.has_deck_id()) { try { @@ -848,133 +320,6 @@ Response::ResponseCode Server_Player::cmdSetSideboardLock(const Command_SetSideb return Response::RespOk; } -Response::ResponseCode -Server_Player::cmdConcede(const Command_Concede & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - setConceded(true); - game->removeArrowsRelatedToPlayer(ges, this); - game->unattachCards(ges, this); - - playerMutex.lock(); - - // Return cards to their rightful owners before conceding the game - static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"}; - for (const auto &card : zones.value("table")->getCards()) { - if (card == nullptr) { - continue; - } - - const auto ®exResult = ownerRegex.match(card->getAnnotation()); - if (!regexResult.hasMatch()) { - continue; - } - - CardToMove cardToMove; - cardToMove.set_card_id(card->getId()); - - for (const auto *player : game->getPlayers()) { - if (player == nullptr || player->getUserInfo() == nullptr) { - continue; - } - - const auto &ownerToReturnTo = regexResult.captured(1); - const auto &correctOwner = QString::compare(QString::fromStdString(player->getUserInfo()->name()), - ownerToReturnTo, Qt::CaseInsensitive) == 0; - if (!correctOwner) { - continue; - } - - const auto &startZone = zones.value("table"); - const auto &targetZone = player->getZones().value("table"); - - if (startZone == nullptr || targetZone == nullptr) { - continue; - } - - moveCard(ges, startZone, QList() << &cardToMove, targetZone, 0, 0, false); - break; - } - } - - playerMutex.unlock(); - - // All borrowed cards have been returned, can now continue cleanup process - clearZones(); - - Event_PlayerPropertiesChanged event; - event.mutable_player_properties()->set_conceded(true); - ges.enqueueGameEvent(event, playerId); - ges.setGameEventContext(Context_Concede()); - - game->stopGameIfFinished(); - if (game->getGameStarted() && (game->getActivePlayer() == playerId)) { - game->nextTurn(); - } - - return Response::RespOk; -} - -Response::ResponseCode -Server_Player::cmdUnconcede(const Command_Unconcede & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (!conceded) { - return Response::RespContextError; - } - - setConceded(false); - - Event_PlayerPropertiesChanged event; - event.mutable_player_properties()->set_conceded(false); - ges.enqueueGameEvent(event, playerId); - ges.setGameEventContext(Context_Unconcede()); - - setupZones(); - - game->sendGameStateToPlayers(); - - return Response::RespOk; -} - -Response::ResponseCode -Server_Player::cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!deck || game->getGameStarted()) { - return Response::RespContextError; - } - - if (readyStart == cmd.ready() && !cmd.force_start()) { - return Response::RespContextError; - } - - setReadyStart(cmd.ready()); - - Event_PlayerPropertiesChanged event; - event.mutable_player_properties()->set_ready_start(cmd.ready()); - ges.enqueueGameEvent(event, playerId); - ges.setGameEventContext(Context_ReadyStart()); - - if (cmd.force_start()) { - if (game->getHostId() != playerId) { - return Response::RespFunctionNotAllowed; - } - game->startGameIfReady(true); - } else if (cmd.ready()) { - game->startGameIfReady(false); - } - - return Response::RespOk; -} - Response::ResponseCode Server_Player::cmdShuffle(const Command_Shuffle &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) { @@ -1044,32 +389,6 @@ Server_Player::cmdMulligan(const Command_Mulligan &cmd, ResponseContainer & /*rc return Response::RespOk; } -Response::ResponseCode -Server_Player::cmdRollDie(const Command_RollDie &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) const -{ - if (conceded) { - return Response::RespContextError; - } - - const auto validatedSides = static_cast(std::min(std::max(cmd.sides(), MINIMUM_DIE_SIDES), MAXIMUM_DIE_SIDES)); - const auto validatedDiceToRoll = - static_cast(std::min(std::max(cmd.count(), MINIMUM_DICE_TO_ROLL), MAXIMUM_DICE_TO_ROLL)); - - Event_RollDie event; - event.set_sides(validatedSides); - for (auto i = 0; i < validatedDiceToRoll; ++i) { - const auto roll = rng->rand(1, validatedSides); - if (i == 0) { - // Backwards compatibility - event.set_value(roll); - } - event.add_values(roll); - } - ges.enqueueGameEvent(event, playerId); - - return Response::RespOk; -} - Response::ResponseCode Server_Player::cmdDrawCards(const Command_DrawCards &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) { @@ -1107,620 +426,6 @@ Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer & return retVal; } -Response::ResponseCode -Server_Player::cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - Server_Player *startPlayer = game->getPlayer(cmd.has_start_player_id() ? cmd.start_player_id() : playerId); - if (!startPlayer) { - return Response::RespNameNotFound; - } - Server_CardZone *startZone = startPlayer->getZones().value(nameFromStdString(cmd.start_zone())); - if (!startZone) { - return Response::RespNameNotFound; - } - - if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId)) && !judge) { - return Response::RespContextError; - } - - Server_Player *targetPlayer = game->getPlayer(cmd.target_player_id()); - if (!targetPlayer) { - return Response::RespNameNotFound; - } - Server_CardZone *targetZone = targetPlayer->getZones().value(nameFromStdString(cmd.target_zone())); - if (!targetZone) { - return Response::RespNameNotFound; - } - - if ((startPlayer != this) && (targetPlayer != this) && !judge) { - return Response::RespContextError; - } - - QList cardsToMove; - for (int i = 0; i < cmd.cards_to_move().card_size(); ++i) { - cardsToMove.append(&cmd.cards_to_move().card(i)); - } - - return moveCard(ges, startZone, cardsToMove, targetZone, cmd.x(), cmd.y(), true, false, cmd.is_reversed()); -} - -Response::ResponseCode -Server_Player::cmdFlipCard(const Command_FlipCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone())); - if (!zone) { - return Response::RespNameNotFound; - } - if (!zone->hasCoords()) { - return Response::RespContextError; - } - - Server_Card *card = zone->getCard(cmd.card_id()); - if (!card) { - return Response::RespNameNotFound; - } - - const bool faceDown = cmd.face_down(); - if (faceDown == card->getFaceDown()) { - return Response::RespContextError; - } - - card->setFaceDown(faceDown); - - Event_FlipCard event; - event.set_zone_name(zone->getName().toStdString()); - event.set_card_id(card->getId()); - if (!faceDown) { - event.set_card_name(card->getName().toStdString()); - event.set_card_provider_id(card->getProviderId().toStdString()); - } - event.set_face_down(faceDown); - ges.enqueueGameEvent(event, playerId); - - QString ptString = nameFromStdString(cmd.pt()); - if (!ptString.isEmpty() && !faceDown) { - setCardAttrHelper(ges, playerId, zone->getName(), card->getId(), AttrPT, ptString); - } - - return Response::RespOk; -} - -Response::ResponseCode -Server_Player::cmdAttachCard(const Command_AttachCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - Server_CardZone *startzone = zones.value(nameFromStdString(cmd.start_zone())); - if (!startzone) { - return Response::RespNameNotFound; - } - - Server_Card *card = startzone->getCard(cmd.card_id()); - if (!card) { - return Response::RespNameNotFound; - } - - Server_Player *targetPlayer = nullptr; - Server_CardZone *targetzone = nullptr; - Server_Card *targetCard = nullptr; - - if (cmd.has_target_player_id()) { - targetPlayer = game->getPlayer(cmd.target_player_id()); - if (!targetPlayer) { - return Response::RespNameNotFound; - } - } else if (!card->getParentCard()) { - return Response::RespContextError; - } - if (targetPlayer) { - targetzone = targetPlayer->getZones().value(nameFromStdString(cmd.target_zone())); - } - if (targetzone) { - // This is currently enough to make sure cards don't get attached to a card that is not on the table. - // Possibly a flag will have to be introduced for this sometime. - if (!targetzone->hasCoords()) { - return Response::RespContextError; - } - if (cmd.has_target_card_id()) { - targetCard = targetzone->getCard(cmd.target_card_id()); - } - if (targetCard) { - if (targetCard->getParentCard()) { - return Response::RespContextError; - } - } else { - return Response::RespNameNotFound; - } - } - - // prevent attaching from non-table zones - // (attaching from non-table zones is handled client-side by moving the card to table zone first) - if (!startzone->hasCoords()) { - return Response::RespContextError; - } - - for (auto *player : game->getPlayers()) { - QList _arrows = player->getArrows().values(); - QList toDelete; - for (auto a : _arrows) { - auto *tCard = qobject_cast(a->getTargetItem()); - if ((tCard == card) || (a->getStartCard() == card)) { - toDelete.append(a); - } - } - for (auto &i : toDelete) { - Event_DeleteArrow event; - event.set_arrow_id(i->getId()); - ges.enqueueGameEvent(event, player->getPlayerId()); - player->deleteArrow(i->getId()); - } - } - - if (targetCard) { - // Unattach all cards attached to the card being attached. - // Make a copy of the list because its contents change during the loop otherwise. - QList attachedList = card->getAttachedCards(); - for (const auto &i : attachedList) { - i->getZone()->getPlayer()->unattachCard(ges, i); - } - - card->setParentCard(targetCard); - const int oldX = card->getX(); - card->setCoords(-1, card->getY()); - startzone->updateCardCoordinates(card, oldX, card->getY()); - - if (targetzone->isColumnStacked(targetCard->getX(), targetCard->getY())) { - auto *cardToMove = new CardToMove; - cardToMove->set_card_id(targetCard->getId()); - targetPlayer->moveCard(ges, targetzone, QList() << cardToMove, targetzone, - targetzone->getFreeGridColumn(-2, targetCard->getY(), targetCard->getName(), false), - targetCard->getY(), targetCard->getFaceDown()); - delete cardToMove; - } - - ges.enqueueGameEvent(makeAttachCardEvent(card, targetCard), playerId); - - startzone->fixFreeSpaces(ges); - } else { - unattachCard(ges, card); - } - - return Response::RespOk; -} - -Response::ResponseCode -Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer &rc, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone())); - if (!zone) { - return Response::RespNameNotFound; - } - - int xCoord = cmd.x(); - int yCoord = cmd.y(); - - Server_Card *targetCard = nullptr; - if (cmd.has_target_card_id()) { - Server_CardZone *targetZone = zones.value(nameFromStdString(cmd.target_zone())); - if (targetZone) { - targetCard = targetZone->getCard(cmd.target_card_id()); - if (targetCard && cmd.target_mode() == Command_CreateToken::TRANSFORM_INTO) { - if (targetCard->getParentCard()) { - ges.enqueueGameEvent(makeAttachCardEvent(targetCard), playerId); - } - - for (Server_Card *attachedCard : targetCard->getAttachedCards()) { - ges.enqueueGameEvent(makeAttachCardEvent(attachedCard), - attachedCard->getZone()->getPlayer()->getPlayerId()); - } - - if (zone->hasCoords() && zone == targetZone) { - xCoord = targetCard->getX(); - yCoord = targetCard->getY(); - } - - targetZone->removeCard(targetCard); - - Event_DestroyCard event; - event.set_zone_name(targetZone->getName().toStdString()); - event.set_card_id(static_cast<::google::protobuf::uint32>(cmd.target_card_id())); - ges.enqueueGameEvent(event, playerId); - } - } - } - - const QString cardName = nameFromStdString(cmd.card_name()); - const QString cardProviderId = nameFromStdString(cmd.card_provider_id()); - if (zone->hasCoords()) { - bool dontStackSameName = cmd.face_down(); - xCoord = zone->getFreeGridColumn(xCoord, yCoord, cardName, dontStackSameName); - } - if (xCoord < 0) { - xCoord = 0; - } - if (yCoord < 0) { - yCoord = 0; - } - - auto *card = new Server_Card({cardName, cardProviderId}, newCardId(), xCoord, yCoord); - card->moveToThread(thread()); - // Client should already prevent face-down tokens from having attributes; this just an extra server-side check - if (!cmd.face_down()) { - card->setColor(nameFromStdString(cmd.color())); - card->setPT(nameFromStdString(cmd.pt())); - } - card->setAnnotation(nameFromStdString(cmd.annotation())); - card->setDestroyOnZoneChange(cmd.destroy_on_zone_change()); - card->setFaceDown(cmd.face_down()); - - zone->insertCard(card, xCoord, yCoord); - sendCreateTokenEvents(zone, card, xCoord, yCoord, ges); - - // check if the token is a replacement for an existing card - if (!targetCard) { - return Response::RespOk; - } - - switch (cmd.target_mode()) { - case Command_CreateToken::ATTACH_TO: { - Command_AttachCard cmd2; - cmd2.set_start_zone(cmd.target_zone()); - cmd2.set_card_id(cmd.target_card_id()); - - cmd2.set_target_player_id(zone->getPlayer()->getPlayerId()); - cmd2.set_target_zone(cmd.zone()); - cmd2.set_target_card_id(card->getId()); - - return cmdAttachCard(cmd2, rc, ges); - } - - case Command_CreateToken::TRANSFORM_INTO: { - // Copy attributes that are not present in the CreateToken event - Event_SetCardAttr event; - event.set_zone_name(card->getZone()->getName().toStdString()); - event.set_card_id(card->getId()); - - if (card->getTapped() != targetCard->getTapped()) { - card->setAttribute(AttrTapped, QVariant(targetCard->getTapped()).toString(), &event); - ges.enqueueGameEvent(event, playerId); - } - - if (card->getAttacking() != targetCard->getAttacking()) { - card->setAttribute(AttrAttacking, QVariant(targetCard->getAttacking()).toString(), &event); - ges.enqueueGameEvent(event, playerId); - } - - if (card->getFaceDown() != targetCard->getFaceDown()) { - card->setAttribute(AttrFaceDown, QVariant(targetCard->getFaceDown()).toString(), &event); - ges.enqueueGameEvent(event, playerId); - } - - if (card->getDoesntUntap() != targetCard->getDoesntUntap()) { - card->setAttribute(AttrDoesntUntap, QVariant(targetCard->getDoesntUntap()).toString(), &event); - ges.enqueueGameEvent(event, playerId); - } - - // Copy counters - QMapIterator i(targetCard->getCounters()); - while (i.hasNext()) { - i.next(); - - Event_SetCardCounter _event; - _event.set_zone_name(card->getZone()->getName().toStdString()); - _event.set_card_id(card->getId()); - - card->setCounter(i.key(), i.value(), &_event); - ges.enqueueGameEvent(_event, playerId); - } - - // Copy parent card - if (Server_Card *parentCard = targetCard->getParentCard()) { - targetCard->setParentCard(nullptr); - card->setParentCard(parentCard); - - ges.enqueueGameEvent(makeAttachCardEvent(card, parentCard), playerId); - } - - // Copy attachments - while (!targetCard->getAttachedCards().isEmpty()) { - Server_Card *attachedCard = targetCard->getAttachedCards().last(); - attachedCard->setParentCard(card); - - ges.enqueueGameEvent(makeAttachCardEvent(attachedCard, card), - attachedCard->getZone()->getPlayer()->getPlayerId()); - } - - // Copy Arrows - for (auto *player : game->getPlayers().values()) { - QList changedArrowIds; - for (Server_Arrow *arrow : player->getArrows()) { - bool sendGameEvent = false; - const auto *startCard = arrow->getStartCard(); - if (startCard == targetCard) { - sendGameEvent = true; - arrow->setStartCard(card); - startCard = card; - } - const auto *targetItem = arrow->getTargetItem(); - if (targetItem == targetCard) { - sendGameEvent = true; - arrow->setTargetItem(card); - targetItem = card; - } - if (sendGameEvent) { - Event_CreateArrow _event; - ServerInfo_Arrow *arrowInfo = _event.mutable_arrow_info(); - changedArrowIds.append(arrow->getId()); - int id = player->newArrowId(); - arrow->setId(id); - arrowInfo->set_id(id); - arrowInfo->set_start_player_id(player->getPlayerId()); - arrowInfo->set_start_zone(startCard->getZone()->getName().toStdString()); - arrowInfo->set_start_card_id(startCard->getId()); - const Server_Player *arrowTargetPlayer = qobject_cast(targetItem); - if (arrowTargetPlayer != nullptr) { - arrowInfo->set_target_player_id(arrowTargetPlayer->getPlayerId()); - } else { - const Server_Card *arrowTargetCard = qobject_cast(targetItem); - arrowInfo->set_target_player_id(arrowTargetCard->getZone()->getPlayer()->getPlayerId()); - arrowInfo->set_target_zone(arrowTargetCard->getZone()->getName().toStdString()); - arrowInfo->set_target_card_id(arrowTargetCard->getId()); - } - arrowInfo->mutable_arrow_color()->CopyFrom(arrow->getColor()); - ges.enqueueGameEvent(_event, player->getPlayerId()); - } - } - for (int id : changedArrowIds) { - player->updateArrowId(id); - } - } - - targetCard->resetState(); - card->setStashedCard(targetCard); - break; - } - } - - return Response::RespOk; -} - -/** - * Creates and sends the events required to properly communicate the given token creation. - * Primarily written to handle creating face-down tokens. - */ -void Server_Player::sendCreateTokenEvents(Server_CardZone *zone, - Server_Card *card, - int xCoord, - int yCoord, - GameEventStorage &ges) -{ - // Token is not face-down; things are easy - if (!card->getFaceDown()) { - ges.enqueueGameEvent(makeCreateTokenEvent(zone, card, xCoord, yCoord), playerId); - return; - } - - // Token is face-down. We have to send different info to each player - auto eventOthers = makeCreateTokenEvent(zone, card, xCoord, yCoord, false); - ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); - - auto eventPrivate = makeCreateTokenEvent(zone, card, xCoord, yCoord, true); - ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); - - // Event_CreateToken didn't use to have face_down field; send attribute event afterward for backwards compatibility - Event_SetCardAttr event; - event.set_zone_name(zone->getName().toStdString()); - event.set_card_id(card->getId()); - event.set_attribute(AttrFaceDown); - event.set_attr_value("1"); - ges.enqueueGameEvent(event, playerId); -} - -Response::ResponseCode -Server_Player::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - Server_Player *startPlayer = game->getPlayer(cmd.start_player_id()); - Server_Player *targetPlayer = game->getPlayer(cmd.target_player_id()); - if (!startPlayer || !targetPlayer) { - return Response::RespNameNotFound; - } - QString startZoneName = nameFromStdString(cmd.start_zone()); - Server_CardZone *startZone = startPlayer->getZones().value(startZoneName); - bool playerTarget = !cmd.has_target_zone(); - Server_CardZone *targetZone = nullptr; - if (!playerTarget) { - targetZone = targetPlayer->getZones().value(nameFromStdString(cmd.target_zone())); - } - if (!startZone || (!targetZone && !playerTarget)) { - return Response::RespNameNotFound; - } - if (startZone->getType() != ServerInfo_Zone::PublicZone) { - return Response::RespContextError; - } - Server_Card *startCard = startZone->getCard(cmd.start_card_id()); - if (!startCard) { - return Response::RespNameNotFound; - } - Server_Card *targetCard = nullptr; - if (!playerTarget) { - if (targetZone->getType() != ServerInfo_Zone::PublicZone) { - return Response::RespContextError; - } - targetCard = targetZone->getCard(cmd.target_card_id()); - } - - Server_ArrowTarget *targetItem; - if (playerTarget) { - targetItem = targetPlayer; - } else { - targetItem = targetCard; - } - if (!targetItem) { - return Response::RespNameNotFound; - } - - for (Server_Arrow *temp : arrows) { - if ((temp->getStartCard() == startCard) && (temp->getTargetItem() == targetItem)) { - return Response::RespContextError; - } - } - - auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color()); - addArrow(arrow); - - Event_CreateArrow event; - ServerInfo_Arrow *arrowInfo = event.mutable_arrow_info(); - arrowInfo->set_id(arrow->getId()); - arrowInfo->set_start_player_id(startPlayer->getPlayerId()); - arrowInfo->set_start_zone(startZoneName.toStdString()); - arrowInfo->set_start_card_id(startCard->getId()); - arrowInfo->set_target_player_id(targetPlayer->getPlayerId()); - if (!playerTarget) { - arrowInfo->set_target_zone(cmd.target_zone()); - arrowInfo->set_target_card_id(cmd.target_card_id()); - } - arrowInfo->mutable_arrow_color()->CopyFrom(cmd.arrow_color()); - ges.enqueueGameEvent(event, playerId); - - return Response::RespOk; -} - -Response::ResponseCode -Server_Player::cmdDeleteArrow(const Command_DeleteArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - if (!deleteArrow(cmd.arrow_id())) { - return Response::RespNameNotFound; - } - - Event_DeleteArrow event; - event.set_arrow_id(cmd.arrow_id()); - ges.enqueueGameEvent(event, playerId); - - return Response::RespOk; -} - -Response::ResponseCode -Server_Player::cmdSetCardAttr(const Command_SetCardAttr &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - return setCardAttrHelper(ges, playerId, nameFromStdString(cmd.zone()), cmd.card_id(), cmd.attribute(), - nameFromStdString(cmd.attr_value())); -} - -Response::ResponseCode -Server_Player::cmdSetCardCounter(const Command_SetCardCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone())); - if (!zone) { - return Response::RespNameNotFound; - } - if (!zone->hasCoords()) { - return Response::RespContextError; - } - - Server_Card *card = zone->getCard(cmd.card_id()); - if (!card) { - return Response::RespNameNotFound; - } - - Event_SetCardCounter event; - event.set_zone_name(zone->getName().toStdString()); - event.set_card_id(card->getId()); - card->setCounter(cmd.counter_id(), cmd.counter_value(), &event); - ges.enqueueGameEvent(event, playerId); - - return Response::RespOk; -} - -Response::ResponseCode -Server_Player::cmdIncCardCounter(const Command_IncCardCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone())); - if (!zone) { - return Response::RespNameNotFound; - } - if (!zone->hasCoords()) { - return Response::RespContextError; - } - - Server_Card *card = zone->getCard(cmd.card_id()); - if (!card) { - return Response::RespNameNotFound; - } - - int newValue = card->getCounter(cmd.counter_id()) + cmd.counter_delta(); - card->setCounter(cmd.counter_id(), newValue); - - Event_SetCardCounter event; - event.set_zone_name(zone->getName().toStdString()); - event.set_card_id(card->getId()); - event.set_counter_id(cmd.counter_id()); - event.set_counter_value(newValue); - ges.enqueueGameEvent(event, playerId); - - return Response::RespOk; -} - Response::ResponseCode Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) { @@ -1859,245 +564,19 @@ Response::ResponseCode Server_Player::cmdSetActivePhase(const Command_SetActiveP return Response::RespOk; } -Response::ResponseCode -Server_Player::cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - - Server_Player *otherPlayer = game->getPlayer(cmd.player_id()); - if (!otherPlayer) { - return Response::RespNameNotFound; - } - Server_CardZone *zone = otherPlayer->getZones().value(nameFromStdString(cmd.zone_name())); - if (!zone) { - return Response::RespNameNotFound; - } - if (!((zone->getType() == ServerInfo_Zone::PublicZone) || (this == otherPlayer))) { - return Response::RespContextError; - } - - int numberCards = cmd.number_cards(); - const QList &cards = zone->getCards(); - - auto *re = new Response_DumpZone; - ServerInfo_Zone *zoneInfo = re->mutable_zone_info(); - zoneInfo->set_name(zone->getName().toStdString()); - zoneInfo->set_type(zone->getType()); - zoneInfo->set_with_coords(zone->hasCoords()); - zoneInfo->set_card_count(numberCards < cards.size() ? cards.size() : numberCards); - - for (int i = 0; (i < cards.size()) && (i < numberCards || numberCards == -1); ++i) { - const auto &findId = cmd.is_reversed() ? cards.size() - numberCards + i : i; - Server_Card *card = cards[findId]; - QString displayedName = card->getFaceDown() ? QString() : card->getName(); - ServerInfo_Card *cardInfo = zoneInfo->add_card_list(); - cardInfo->set_provider_id(card->getProviderId().toStdString()); - cardInfo->set_name(displayedName.toStdString()); - if (zone->getType() == ServerInfo_Zone::HiddenZone) { - cardInfo->set_id(findId); - } else { - cardInfo->set_id(card->getId()); - cardInfo->set_x(card->getX()); - cardInfo->set_y(card->getY()); - cardInfo->set_face_down(card->getFaceDown()); - cardInfo->set_tapped(card->getTapped()); - cardInfo->set_attacking(card->getAttacking()); - cardInfo->set_color(card->getColor().toStdString()); - cardInfo->set_pt(card->getPT().toStdString()); - cardInfo->set_annotation(card->getAnnotation().toStdString()); - cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); - cardInfo->set_doesnt_untap(card->getDoesntUntap()); - - QMapIterator cardCounterIterator(card->getCounters()); - while (cardCounterIterator.hasNext()) { - cardCounterIterator.next(); - ServerInfo_CardCounter *counterInfo = cardInfo->add_counter_list(); - counterInfo->set_id(cardCounterIterator.key()); - counterInfo->set_value(cardCounterIterator.value()); - } - - if (card->getParentCard()) { - cardInfo->set_attach_player_id(card->getParentCard()->getZone()->getPlayer()->getPlayerId()); - cardInfo->set_attach_zone(card->getParentCard()->getZone()->getName().toStdString()); - cardInfo->set_attach_card_id(card->getParentCard()->getId()); - } - } - } - if (zone->getType() == ServerInfo_Zone::HiddenZone) { - zone->setCardsBeingLookedAt(numberCards); - - Event_DumpZone event; - event.set_zone_owner_id(otherPlayer->getPlayerId()); - event.set_zone_name(zone->getName().toStdString()); - event.set_number_cards(numberCards); - event.set_is_reversed(cmd.is_reversed()); - ges.enqueueGameEvent(event, playerId); - } - rc.setResponseExtension(re); - return Response::RespOk; -} - -Response::ResponseCode -Server_Player::cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) -{ - if (!game->getGameStarted()) { - return Response::RespGameNotStarted; - } - if (conceded) { - return Response::RespContextError; - } - - if (cmd.has_player_id()) { - Server_Player *otherPlayer = game->getPlayer(cmd.player_id()); - if (!otherPlayer) - return Response::RespNameNotFound; - } - Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone_name())); - if (!zone) { - return Response::RespNameNotFound; - } - - QList cardsToReveal; - if (cmd.top_cards() != -1) { - for (int i = 0; i < cmd.top_cards(); i++) { - Server_Card *card = zone->getCard(i); - if (!card) { - return Response::RespNameNotFound; - } - cardsToReveal.append(card); - } - } else if (cmd.card_id_size() == 0) { - cardsToReveal = zone->getCards(); - } else if (cmd.card_id_size() == 1 && cmd.card_id(0) == -2) { - // If there is a single card_id with value -2 (ie - // Player::RANDOM_CARD_FROM_ZONE), pick a random card. - // - // This is to be compatible with clients supporting a single card_id - // value, which send value -2 to request a random card. - if (zone->getCards().isEmpty()) { - return Response::RespContextError; - } - - cardsToReveal.append(zone->getCards().at(rng->rand(0, zone->getCards().size() - 1))); - } else { - for (auto cardId : cmd.card_id()) { - Server_Card *card = zone->getCard(cardId); - if (!card) { - return Response::RespNameNotFound; - } - cardsToReveal.append(card); - } - } - - Event_RevealCards eventOthers; - eventOthers.set_grant_write_access(cmd.grant_write_access()); - eventOthers.set_zone_name(zone->getName().toStdString()); - eventOthers.set_number_of_cards(cardsToReveal.size()); - for (auto cardId : cmd.card_id()) { - eventOthers.add_card_id(cardId); - } - if (cmd.has_player_id()) { - eventOthers.set_other_player_id(cmd.player_id()); - } - - Event_RevealCards eventPrivate(eventOthers); - - for (auto card : cardsToReveal) { - ServerInfo_Card *cardInfo = eventPrivate.add_cards(); - - cardInfo->set_id(card->getId()); - cardInfo->set_provider_id(card->getProviderId().toStdString()); - cardInfo->set_name(card->getName().toStdString()); - cardInfo->set_x(card->getX()); - cardInfo->set_y(card->getY()); - cardInfo->set_face_down(card->getFaceDown()); - cardInfo->set_tapped(card->getTapped()); - cardInfo->set_attacking(card->getAttacking()); - cardInfo->set_color(card->getColor().toStdString()); - cardInfo->set_pt(card->getPT().toStdString()); - cardInfo->set_annotation(card->getAnnotation().toStdString()); - cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); - cardInfo->set_doesnt_untap(card->getDoesntUntap()); - - QMapIterator cardCounterIterator(card->getCounters()); - while (cardCounterIterator.hasNext()) { - cardCounterIterator.next(); - ServerInfo_CardCounter *counterInfo = cardInfo->add_counter_list(); - counterInfo->set_id(cardCounterIterator.key()); - counterInfo->set_value(cardCounterIterator.value()); - } - - if (card->getParentCard()) { - cardInfo->set_attach_player_id(card->getParentCard()->getZone()->getPlayer()->getPlayerId()); - cardInfo->set_attach_zone(card->getParentCard()->getZone()->getName().toStdString()); - cardInfo->set_attach_card_id(card->getParentCard()->getId()); - } - } - - if (cmd.has_player_id()) { - if (cmd.grant_write_access()) { - zone->addWritePermission(cmd.player_id()); - } - - if (getJudge()) { - ges.setOverwriteOwnership(true); - } - - ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, cmd.player_id()); - ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); - } else { - if (cmd.grant_write_access()) { - const QList &participantIds = game->getParticipants().keys(); - for (int anyParticipantId : participantIds) { - zone->addWritePermission(anyParticipantId); - } - } - - ges.enqueueGameEvent(eventPrivate, playerId); - } - - return Response::RespOk; -} - Response::ResponseCode Server_Player::cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, - ResponseContainer & /* rc */, + ResponseContainer &rc, GameEventStorage &ges) { + auto ret = Server_AbstractPlayer::cmdChangeZoneProperties(cmd, rc, ges); + Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone_name())); if (!zone) { return Response::RespNameNotFound; } - Event_ChangeZoneProperties event; - event.set_zone_name(cmd.zone_name()); - - // Neither value set -> error. - if (!cmd.has_always_look_at_top_card() && !cmd.has_always_reveal_top_card()) { - return Response::RespContextError; - } - - // Neither value changed -> error. - bool alwaysRevealChanged = - cmd.has_always_reveal_top_card() && zone->getAlwaysRevealTopCard() != cmd.always_reveal_top_card(); - bool alwaysLookAtTopChanged = - cmd.has_always_look_at_top_card() && zone->getAlwaysLookAtTopCard() != cmd.always_look_at_top_card(); - if (!alwaysRevealChanged && !alwaysLookAtTopChanged) { - return Response::RespContextError; - } - - if (cmd.has_always_reveal_top_card()) { - zone->setAlwaysRevealTopCard(cmd.always_reveal_top_card()); - event.set_always_reveal_top_card(cmd.always_reveal_top_card()); - } - if (cmd.has_always_look_at_top_card()) { - zone->setAlwaysLookAtTopCard(cmd.always_look_at_top_card()); - event.set_always_look_at_top_card(cmd.always_look_at_top_card()); - } - ges.enqueueGameEvent(event, playerId); revealTopCardIfNeeded(zone, ges); - return Response::RespOk; + return ret; } Response::ResponseCode @@ -2115,32 +594,9 @@ void Server_Player::getInfo(ServerInfo_Player *info, bool omniscient, bool withUserInfo) { - getProperties(*info->mutable_properties(), withUserInfo); - if (recipient == this) { - if (deck) { - info->set_deck_list(deck->writeToString_Native().toStdString()); - } - } - - for (Server_Arrow *arrow : arrows) { - arrow->getInfo(info->add_arrow_list()); - } + Server_AbstractPlayer::getInfo(info, recipient, omniscient, withUserInfo); for (Server_Counter *counter : counters) { counter->getInfo(info->add_counter_list()); } - - for (Server_CardZone *zone : zones) { - zone->getInfo(info->add_zone_list(), recipient, omniscient); - } -} - -void Server_Player::getPlayerProperties(ServerInfo_PlayerProperties &result) -{ - result.set_conceded(conceded); - result.set_sideboard_locked(sideboardLocked); - result.set_ready_start(readyStart); - if (deck) { - result.set_deck_hash(deck->getDeckHash().toStdString()); - } } diff --git a/common/server/game/server_player.h b/common/server/game/server_player.h index fc3e69ee8..5925ed3c2 100644 --- a/common/server/game/server_player.h +++ b/common/server/game/server_player.h @@ -1,37 +1,14 @@ #ifndef PLAYER_H #define PLAYER_H -#include "../../serverinfo_user_container.h" -#include "server_abstract_participant.h" +#include "server_abstract_player.h" -#include -#include -#include - -class DeckList; -class Server_CardZone; -class Server_Counter; -class Server_Arrow; -class Server_Card; -class CardToMove; - -class Server_Player : public Server_AbstractParticipant +class Server_Player : public Server_AbstractPlayer { Q_OBJECT private: - class MoveCardCompareFunctor; - DeckList *deck; - QMap zones; QMap counters; - QMap arrows; QList lastDrawList; - int nextCardId; - bool readyStart; - bool conceded; - bool sideboardLocked; - void revealTopCardIfNeeded(Server_CardZone *zone, GameEventStorage &ges); - void sendCreateTokenEvents(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord, GameEventStorage &ges); - void getPlayerProperties(ServerInfo_PlayerProperties &result) override; public: Server_Player(Server_Game *_game, @@ -40,79 +17,23 @@ public: bool _judge, Server_AbstractUserInterface *_handler); ~Server_Player() override; - void prepareDestroy() override; - const DeckList *getDeckList() const - { - return deck; - } - bool getReadyStart() const - { - return readyStart; - } - void setReadyStart(bool _readyStart) - { - readyStart = _readyStart; - } - bool getConceded() const - { - return conceded; - } - void setConceded(bool _conceded) - { - conceded = _conceded; - } - - const QMap &getZones() const - { - return zones; - } const QMap &getCounters() const { return counters; } - const QMap &getArrows() const - { - return arrows; - } - - int newCardId(); int newCounterId() const; - int newArrowId() const; - - void addZone(Server_CardZone *zone); - void addArrow(Server_Arrow *arrow); - void updateArrowId(int id); - bool deleteArrow(int arrowId); void addCounter(Server_Counter *counter); - void clearZones(); - void setupZones(); + void setupZones() override; + void clearZones() override; Response::ResponseCode drawCards(GameEventStorage &ges, int number); - Response::ResponseCode moveCard(GameEventStorage &ges, - Server_CardZone *startzone, - const QList &_cards, - Server_CardZone *targetzone, - int xCoord, - int yCoord, - bool fixFreeSpaces = true, - bool undoingDraw = false, - bool isReversed = false); - void unattachCard(GameEventStorage &ges, Server_Card *card); - Response::ResponseCode setCardAttrHelper(GameEventStorage &ges, - int targetPlayerId, - const QString &zone, - int cardId, - CardAttribute attribute, - const QString &attrValue, - Server_Card *unzonedCard = nullptr); + void onCardBeingMoved(GameEventStorage &ges, + const MoveCardStruct &cardStruct, + Server_CardZone *startzone, + Server_CardZone *targetzone, + bool undoingDraw) override; - Response::ResponseCode - cmdConcede(const Command_Concede &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdUnconcede(const Command_Unconcede &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer &rc, GameEventStorage &ges) override; Response::ResponseCode cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &rc, GameEventStorage &ges) override; Response::ResponseCode @@ -124,30 +45,10 @@ public: Response::ResponseCode cmdMulligan(const Command_Mulligan &cmd, ResponseContainer &rc, GameEventStorage &ges) override; Response::ResponseCode - cmdRollDie(const Command_RollDie &cmd, ResponseContainer &rc, GameEventStorage &ges) const override; - Response::ResponseCode cmdDrawCards(const Command_DrawCards &cmd, ResponseContainer &rc, GameEventStorage &ges) override; Response::ResponseCode cmdUndoDraw(const Command_UndoDraw &cmd, ResponseContainer &rc, GameEventStorage &ges) override; Response::ResponseCode - cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdFlipCard(const Command_FlipCard &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdAttachCard(const Command_AttachCard &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdCreateArrow(const Command_CreateArrow &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdDeleteArrow(const Command_DeleteArrow &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdSetCardAttr(const Command_SetCardAttr &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdSetCardCounter(const Command_SetCardCounter &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdIncCardCounter(const Command_IncCardCounter &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer &rc, GameEventStorage &ges) override; Response::ResponseCode cmdCreateCounter(const Command_CreateCounter &cmd, ResponseContainer &rc, GameEventStorage &ges) override; @@ -160,10 +61,6 @@ public: Response::ResponseCode cmdSetActivePhase(const Command_SetActivePhase &cmd, ResponseContainer &rc, GameEventStorage &ges) override; Response::ResponseCode - cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode - cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer &rc, GameEventStorage &ges) override; - Response::ResponseCode cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges) override; Response::ResponseCode cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer &rc, diff --git a/format.sh b/format.sh index 8c60f3f8a..1e88c9862 100755 --- a/format.sh +++ b/format.sh @@ -12,18 +12,18 @@ olddir="$PWD" cd "${BASH_SOURCE%/*}/" || exit 2 # could not find path, this could happen with special links etc. # defaults -include=("common" \ -"cockatrice/src" \ +include=("cockatrice/src" \ +"common" \ "dbconverter/src" \ "oracle/src" \ "servatrice/src" \ "tests") -exclude=("servatrice/src/smtp" \ -"common/sfmt" \ +exclude=("common/sfmt" \ "common/lib" \ "oracle/src/zip" \ "oracle/src/lzma" \ -"oracle/src/qt-json") +"oracle/src/qt-json" \ +"servatrice/src/smtp") exts=("cpp" "h" "proto") cf_cmd="clang-format" branch="origin/master" diff --git a/vcpkg b/vcpkg index 29ff5b813..623240005 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 29ff5b8131d0c6c8fcb8fbaef35992f0d507cd7c +Subproject commit 62324000504cdd27282f8275c99135cfb2bd1dc0