From a20f3c0fb449bcc7d3ed6b9f49914eb1e83646fd Mon Sep 17 00:00:00 2001 From: Vorliz <39461522+Vorliz@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:05:31 +0100 Subject: [PATCH] Fix #6659: Correct logging for bottom-of-library card moves (#6764) * Fix #6659: Correct logging for bottom-of-library card moves Cause: - This issue happens due to logic of moving the card from the top of the deck being reused when moving from the bottom of the deck, in a way that makes it impossible to check if the card came from the bottom. Resolution: - Updated the logging logic in the client for card moves. - Added a gRPC parameter ('is_from_bottom') for card moves. - Updates the server logic to reverse the order of the card move if the 'is_from_bottom' parameter is true. - Added a test to show the expected behaviour of the fix. NOTE: While the changes in this patch seem big, this is due to changing the loop in the moveCard function to a helper function, in order to make the bug fix change. The only change to the loop was to pass a variable attribution to the moveCard function because it was redundant to be in the loop. * chore: run format on test * refactor: new way to check if a move is from the bottom of the deck * refactor: change isFromBottom check to static function * update comments Co-authored-by: ebbit1q --------- Co-authored-by: ebbit1q --- .../src/game/log/message_log_widget.cpp | 2 +- .../remote/game/server_abstract_player.cpp | 372 ++++++++++-------- .../remote/game/server_abstract_player.h | 13 + tests/CMakeLists.txt | 1 + tests/movecard_tests/CMakeLists.txt | 16 + .../movecard_tests/reverse_card_move_test.cpp | 91 +++++ tests/movecard_tests/server_test_helpers.h | 42 ++ 7 files changed, 380 insertions(+), 157 deletions(-) create mode 100755 tests/movecard_tests/CMakeLists.txt create mode 100644 tests/movecard_tests/reverse_card_move_test.cpp create mode 100644 tests/movecard_tests/server_test_helpers.h diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game/log/message_log_widget.cpp index c38e433eb..09f6e656b 100644 --- a/cockatrice/src/game/log/message_log_widget.cpp +++ b/cockatrice/src/game/log/message_log_widget.cpp @@ -54,7 +54,7 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position fromStr = tr(" from the top of their library"); } } - } else if (position >= zone->getCards().size() - 1) { + } else if (position == zone->getCards().size()) { if (cardName.isEmpty()) { if (ownerChange) { cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName()); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp index f04bcc849..7c0437bf0 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp @@ -49,6 +49,7 @@ #include #include #include +#include Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game, int _playerId, @@ -228,6 +229,37 @@ shouldBeFaceDown(const MoveCardStruct &cardStruct, const Server_CardZone *startZ return false; } +/** + * @brief Determines whether a set of moved cards is from the bottom of the deck + */ +static bool shouldBeFromTheBottom(const Server_CardZone *startZone, const std::set &cardsToMove) +{ + if (!startZone) { + return false; + } + + if (startZone->getName() != ZoneNames::DECK) { + return false; + } + + int movedCount = static_cast(cardsToMove.size()); + int tailStart = startZone->getCards().size() - movedCount; + if (tailStart <= 0) { // if the entire deck is moved it should not be considered from the bottom + return false; + } + + // check if the move is a contiguous block at the end of the deck, fail fast when not + int expectedPosition = tailStart; + for (const auto &card : cardsToMove) { + if (card.position != expectedPosition) { + return false; + } + ++expectedPosition; + } + + return true; +} + Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, Server_CardZone *startzone, const QList &_cards, @@ -244,8 +276,11 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, return Response::RespContextError; } - if (!targetzone->hasCoords() && (xCoord <= -1)) { - xCoord = targetzone->getCards().size(); + if (!targetzone->hasCoords()) { + yCoord = 0; + if (xCoord <= -1) { + xCoord = targetzone->getCards().size(); + } } std::set cardsToMove; @@ -285,164 +320,21 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, bool revealTopStart = false; bool revealTopTarget = false; - for (auto cardStruct : cardsToMove) { - Server_Card *card = cardStruct.card; - int originalPosition = cardStruct.position; + bool isFromBottom = shouldBeFromTheBottom(startzone, cardsToMove); - 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 (isFromBottom) { + std::ranges::reverse_view reversedCardsToMove{cardsToMove}; + for (auto card : reversedCardsToMove) { + processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget, + isReversed, undoingDraw); } - - 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; - - bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone); - - if (targetzone->hasCoords()) { - newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); - } else { - yCoord = 0; - card->resetState(targetzone->getName() == ZoneNames::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); - - if ( - // cards from public zones have their id known, their previous position is already known, the event does - // not accomodate for previous locations in zones with coordinates (which are always public) - (startzone->getType() != ServerInfo_Zone::PublicZone) && - // other players are not allowed to be able to track which card is which in private zones like the hand - (startzone->getType() != ServerInfo_Zone::PrivateZone)) { - eventOthers.set_position(position); - } - if ( - // other players are not allowed to be able to track which card is which in private zones like the hand - (targetzone->getType() != ServerInfo_Zone::PrivateZone)) { - 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); + } else { + for (auto card : cardsToMove) { + processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget, + isReversed, undoingDraw); } } + if (revealTopStart) { revealTopCardIfNeeded(startzone, ges); } @@ -462,6 +354,174 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, return Response::RespOk; } +void Server_AbstractPlayer::processMoveCard(GameEventStorage &ges, + Server_CardZone *startzone, + Server_CardZone *targetzone, + MoveCardStruct cardStruct, + int xCoord, + int yCoord, + int &xIndex, + bool &revealTopStart, + bool &revealTopTarget, + bool isReversed, + bool undoingDraw) +{ + Server_Card *card = cardStruct.card; + int originalPosition = cardStruct.position; + + 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; + + bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone); + + if (targetzone->hasCoords()) { + newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown); + } else { + card->resetState(targetzone->getName() == ZoneNames::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); + + if ( + // cards from public zones have their id known, their previous position is already known, the event does + // not accomodate for previous locations in zones with coordinates (which are always public) + (startzone->getType() != ServerInfo_Zone::PublicZone) && + // other players are not allowed to be able to track which card is which in private zones like the hand + (startzone->getType() != ServerInfo_Zone::PrivateZone)) { + eventOthers.set_position(position); + } + if ( + // other players are not allowed to be able to track which card is which in private zones like the hand + (targetzone->getType() != ServerInfo_Zone::PrivateZone)) { + 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); + } +} + void Server_AbstractPlayer::onCardBeingMoved(GameEventStorage &ges, const MoveCardStruct &cardStruct, Server_CardZone *startzone, diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h index 40fe84aa1..9d9809298 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h @@ -93,6 +93,19 @@ public: bool fixFreeSpaces = true, bool undoingDraw = false, bool isReversed = false); + + void processMoveCard(GameEventStorage &ges, + Server_CardZone *startzone, + Server_CardZone *targetzone, + MoveCardStruct cardStruct, + int xCoord, + int yCoord, + int &xIndex, + bool &revealTopStart, + bool &revealTopTarget, + bool isReversed, + bool undoingDraw); + virtual void onCardBeingMoved(GameEventStorage &ges, const MoveCardStruct &cardStruct, Server_CardZone *startzone, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c5346e59f..fffaf1bda 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,4 +65,5 @@ target_link_libraries( add_subdirectory(card_zone_algorithms) add_subdirectory(carddatabase) add_subdirectory(loading_from_clipboard) +add_subdirectory(movecard_tests) add_subdirectory(oracle) diff --git a/tests/movecard_tests/CMakeLists.txt b/tests/movecard_tests/CMakeLists.txt new file mode 100755 index 000000000..769047148 --- /dev/null +++ b/tests/movecard_tests/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(reverse_card_move_test reverse_card_move_test.cpp) + +if(NOT GTEST_FOUND) + add_dependencies(reverse_card_move_test gtest) +endif() + +target_link_libraries( + reverse_card_move_test + PRIVATE libcockatrice_network_server_remote + PRIVATE libcockatrice_rng + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} + PRIVATE ${TEST_QT_MODULES} +) + +add_test(NAME reverse_card_move_test COMMAND reverse_card_move_test) diff --git a/tests/movecard_tests/reverse_card_move_test.cpp b/tests/movecard_tests/reverse_card_move_test.cpp new file mode 100644 index 000000000..2231a7e3b --- /dev/null +++ b/tests/movecard_tests/reverse_card_move_test.cpp @@ -0,0 +1,91 @@ +#include "game/server_abstract_player.h" +#include "game/server_card.h" +#include "game/server_cardzone.h" +#include "game/server_game.h" +#include "server_response_containers.h" +#include "server_room.h" +#include "server_test_helpers.h" + +#include +#include +#include +#include +#include + +RNG_Abstract *rng = nullptr; // this needs to be defined due to other functions in server + +TEST(ReverseCardMoveTest, MoveCardFromBottomTest) +{ + ServerInfo_User user; + user.set_name("test-user"); + + // instantiate a fake server instance + FakeServer server; + Server_Room room(0, 0, "", "", "", "", false, "", {}, &server); + Server_Game game(user, 1, "", "", 2, QList(), false, false, false, false, false, false, 20, false, &room); + Server_AbstractPlayer player(&game, 1, user, false, nullptr); + Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone); + Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone); + + // setup the deck with 20 useless cards + for (int i = 0; i < 20; i++) { + auto *cardUseless = new Server_Card({"Card Useless", "card-Useless"}, player.newCardId(), i, 0); + deckZone.insertCard(cardUseless, i, 0); + } + + // add 4 cards to the end of it + auto *cardA = new Server_Card({"Card A", "card-a"}, player.newCardId(), 20, 0); + auto *cardB = new Server_Card({"Card B", "card-b"}, player.newCardId(), 21, 0); + auto *cardC = new Server_Card({"Card C", "card-c"}, player.newCardId(), 22, 0); + auto *cardD = new Server_Card({"Card D", "card-d"}, player.newCardId(), 23, 0); + + deckZone.insertCard(cardA, 20, 0); + deckZone.insertCard(cardB, 21, 0); + deckZone.insertCard(cardC, 22, 0); + deckZone.insertCard(cardD, 23, 0); + + // try to move them, with the expected client given order (n-3, n-2, n-1, n) + CardToMove moveA; + moveA.set_card_id(cardA->getId()); + CardToMove moveB; + moveB.set_card_id(cardB->getId()); + CardToMove moveC; + moveC.set_card_id(cardC->getId()); + CardToMove moveD; + moveD.set_card_id(cardD->getId()); + + QList cardsToMove = {&moveA, &moveB, &moveC, &moveD}; + GameEventStorage ges; + + const auto response = player.moveCard(ges, &deckZone, cardsToMove, &exileZone, 0, 0, false, false, false); + + EXPECT_EQ(response, Response::RespOk); + + int positionA; + int positionB; + int positionC; + int positionD; + // find the cards in the destination zone and check they are the right card + EXPECT_EQ(exileZone.getCard(cardA->getId(), &positionA), cardA); + EXPECT_EQ(exileZone.getCard(cardB->getId(), &positionB), cardB); + EXPECT_EQ(exileZone.getCard(cardC->getId(), &positionC), cardC); + EXPECT_EQ(exileZone.getCard(cardD->getId(), &positionD), cardD); + + // check that they are at the expected index + EXPECT_EQ(cardA->getX(), 3); + EXPECT_EQ(cardB->getX(), 2); + EXPECT_EQ(cardC->getX(), 1); + EXPECT_EQ(cardD->getX(), 0); + + // also check if the given positions are correct + EXPECT_EQ(positionA, 3); + EXPECT_EQ(positionB, 2); + EXPECT_EQ(positionC, 1); + EXPECT_EQ(positionD, 0); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/movecard_tests/server_test_helpers.h b/tests/movecard_tests/server_test_helpers.h new file mode 100644 index 000000000..fd2ed6c17 --- /dev/null +++ b/tests/movecard_tests/server_test_helpers.h @@ -0,0 +1,42 @@ +#include "server.h" +#include "server_database_interface.h" + +class MockDatabaseInterface : public Server_DatabaseInterface +{ +public: + AuthenticationResult checkUserPassword(Server_ProtocolHandler *, + const QString &, + const QString &, + const QString &, + QString &, + int &, + bool) override + { + return NotLoggedIn; + } + ServerInfo_User getUserData(const QString &, bool) override + { + return ServerInfo_User(); + } + int getNextGameId() override + { + return 1; + } + int getNextReplayId() override + { + return 1; + } + int getActiveUserCount(QString) override + { + return 1; + } +}; + +class FakeServer : public Server +{ +public: + FakeServer() + { + setDatabaseInterface(new MockDatabaseInterface()); + } +};