Compare commits

..

No commits in common. "6c8fcf7d197dacc7b49a86cbff7c8dd6282608b2" and "d99798111eb92cb380df5b81ed4d643cb6eb8aea" have entirely different histories.

12 changed files with 63 additions and 186 deletions

View file

@ -246,7 +246,6 @@ set(cockatrice_SOURCES
src/interface/widgets/replay/replay_widget.cpp src/interface/widgets/replay/replay_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp src/interface/widgets/server/chat_view/chat_view.cpp
src/interface/widgets/server/game_filter_configs.cpp src/interface/widgets/server/game_filter_configs.cpp
src/interface/widgets/server/game_link.cpp
src/interface/widgets/server/game_selector.cpp src/interface/widgets/server/game_selector.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp src/interface/widgets/server/games_model.cpp

View file

@ -1,23 +0,0 @@
#include "game_link.h"
#include <QUrl>
#include <QUrlQuery>
QString makeGameJoinLink(const QString &hostname, int port, int roomId, int gameId, const QString &description)
{
QUrl url;
url.setScheme("cockatrice");
url.setHost("joingame");
QUrlQuery query;
query.addQueryItem("hostname", hostname);
query.addQueryItem("port", QString::number(port));
query.addQueryItem("roomid", QString::number(roomId));
query.addQueryItem("gameid", QString::number(gameId));
if (!description.isEmpty()) {
// addQueryItem percent-encodes, so arbitrary descriptions (quotes,
// ampersands, non-ASCII…) survive the trip through chat.
query.addQueryItem("game", description);
}
url.setQuery(query);
return url.toString(QUrl::FullyEncoded);
}

View file

@ -1,41 +0,0 @@
/**
* @file game_link.h
* @ingroup UI
* @brief Builds cockatrice://joingame links that let another user join a server game.
*/
#ifndef GAME_LINK_H
#define GAME_LINK_H
#include <QString>
/**
* Builds a cockatrice://joingame link for the given server game. The receiver's
* client opens it through the intent chain (connect -> join room -> join game).
* @p description, when non-empty, is embedded in the link as the URL-encoded
* "game" query item so the receiving client can name the game in its confirm
* prompt and chat anchor instead of only its numeric id. Links built without it
* stay valid: the parser and chat renderer fall back to the id alone.
*/
QString
makeGameJoinLink(const QString &hostname, int port, int roomId, int gameId, const QString &description = QString());
/**
* One game the inviter is currently in and can invite another user to.
* @p label is meant for display in menus, @p url is the ready-made invite link.
* @p description is the raw game description for building tr()-wrapped invite
* messages (the label already embeds it, but the send sites need the raw value).
* @p onlyBuddies and @p creatorName mirror the server game's room settings so
* callers can gate the invite to the creator's buddies.
*/
struct GameInviteOption
{
int gameId = 0;
QString label;
QString url;
QString description;
bool onlyBuddies = false;
QString creatorName;
};
#endif // GAME_LINK_H

View file

@ -7,7 +7,6 @@
#include "../interface/widgets/tabs/tab_room.h" #include "../interface/widgets/tabs/tab_room.h"
#include "../interface/widgets/tabs/tab_supervisor.h" #include "../interface/widgets/tabs/tab_supervisor.h"
#include "../interface/widgets/utility/get_text_with_max.h" #include "../interface/widgets/utility/get_text_with_max.h"
#include "game_link.h"
#include "games_model.h" #include "games_model.h"
#include "user/user_list_manager.h" #include "user/user_list_manager.h"
@ -19,6 +18,8 @@
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QTreeView> #include <QTreeView>
#include <QUrl>
#include <QUrlQuery>
#include <libcockatrice/network/client/abstract/abstract_client.h> #include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/response.pb.h> #include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pb/room_commands.pb.h> #include <libcockatrice/protocol/pb/room_commands.pb.h>
@ -319,12 +320,19 @@ void GameSelector::customContextMenu(const QPoint &point)
dlg.exec(); dlg.exec();
}); });
QAction copyLink(tr("Cop&y game link")); QAction copyLink(tr("Copy Game Link"));
connect(&copyLink, &QAction::triggered, this, [=, this]() { connect(&copyLink, &QAction::triggered, this, [=, this]() {
const ServerInfo_Game &gameInfo = gameListModel->getGame(index.data(Qt::UserRole).toInt()); const ServerInfo_Game &gameInfo = gameListModel->getGame(index.data(Qt::UserRole).toInt());
QGuiApplication::clipboard()->setText(makeGameJoinLink(client->serverName(), client->serverPort(), QUrl url;
gameInfo.room_id(), gameInfo.game_id(), url.setScheme("cockatrice");
QString::fromStdString(gameInfo.description()))); url.setHost("joingame");
QUrlQuery query;
query.addQueryItem("hostname", client->serverName());
query.addQueryItem("port", QString::number(client->serverPort()));
query.addQueryItem("roomid", QString::number(gameInfo.room_id()));
query.addQueryItem("gameid", QString::number(gameInfo.game_id()));
url.setQuery(query);
QGuiApplication::clipboard()->setText(url.toString(QUrl::FullyEncoded));
}); });
QMenu menu; QMenu menu;
@ -364,31 +372,13 @@ void GameSelector::joinGame(const bool asSpectator, const bool asJudge)
return; return;
} }
bool spectator = asSpectator || game.player_count() == game.max_players();
bool overrideRestrictions = !tabSupervisor->getAdminLocked(); bool overrideRestrictions = !tabSupervisor->getAdminLocked();
// Joining a full game without override privileges silently becomes a
// spectator join, so ask first instead of surprising the player.
const bool gameFull = game.player_count() == game.max_players();
if (gameFull && !asSpectator && !asJudge && !overrideRestrictions) {
const QMessageBox::StandardButton answer =
QMessageBox::question(this, tr("Join game"), tr("The game is full. Join as a spectator instead?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (answer != QMessageBox::Yes) {
return;
}
}
bool spectator = asSpectator || gameFull;
QString password; QString password;
if (game.with_password() && !(spectator && !game.spectators_need_password()) && !overrideRestrictions) { if (game.with_password() && !(spectator && !game.spectators_need_password()) && !overrideRestrictions) {
bool ok; bool ok;
// Games without a description have no sensible label — fall back to the password = getTextWithMax(this, tr("Join game"), tr("Password:"), QLineEdit::Password, QString(), &ok);
// game id so the prompt still tells the user which game they're entering.
const QString gameLabel = QString::fromStdString(game.description());
const QString prompt = gameLabel.isEmpty() ? tr("Password for game #%1:").arg(game.game_id())
: tr("Password for \"%1\":").arg(gameLabel);
password = getTextWithMax(this, tr("Join game"), prompt, QLineEdit::Password, QString(), &ok);
if (!ok) { if (!ok) {
return; return;
} }

View file

@ -20,7 +20,6 @@
#include "../interface/card_picture_loader/card_picture_loader.h" #include "../interface/card_picture_loader/card_picture_loader.h"
#include "../interface/widgets/cards/card_info_frame_widget.h" #include "../interface/widgets/cards/card_info_frame_widget.h"
#include "../interface/widgets/dialogs/dlg_create_game.h" #include "../interface/widgets/dialogs/dlg_create_game.h"
#include "../interface/widgets/server/game_link.h"
#include "../interface/widgets/server/user/user_list_manager.h" #include "../interface/widgets/server/user/user_list_manager.h"
#include "../interface/widgets/utility/completer_utils.h" #include "../interface/widgets/utility/completer_utils.h"
#include "../interface/widgets/utility/line_edit_completer.h" #include "../interface/widgets/utility/line_edit_completer.h"
@ -34,8 +33,6 @@
#include "tab_supervisor.h" #include "tab_supervisor.h"
#include <QAction> #include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QCompleter> #include <QCompleter>
#include <QDebug> #include <QDebug>
#include <QDockWidget> #include <QDockWidget>
@ -334,9 +331,6 @@ void TabGame::retranslateUi()
if (aGameInfo) { if (aGameInfo) {
aGameInfo->setText(tr("Game &information")); aGameInfo->setText(tr("Game &information"));
} }
if (aCopyGameLink) {
aCopyGameLink->setText(tr("Cop&y game link"));
}
if (aConcede) { if (aConcede) {
if (game->getPlayerManager()->isMainPlayerConceded()) { if (game->getPlayerManager()->isMainPlayerConceded()) {
aConcede->setText(tr("Un&concede")); aConcede->setText(tr("Un&concede"));
@ -504,15 +498,6 @@ void TabGame::actGameInfo()
dlg.exec(); dlg.exec();
} }
void TabGame::actCopyGameLink()
{
const QString link =
makeGameJoinLink(tabSupervisor->getClient()->serverName(), tabSupervisor->getClient()->serverPort(),
game->getGameMetaInfo()->proto().room_id(), game->getGameMetaInfo()->gameId(),
QString::fromStdString(game->getGameMetaInfo()->proto().description()));
QApplication::clipboard()->setText(link);
}
void TabGame::actConcede() void TabGame::actConcede()
{ {
PlayerLogic *player = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); PlayerLogic *player = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
@ -1001,9 +986,6 @@ void TabGame::createMenuItems()
connect(aRotateViewCCW, &QAction::triggered, this, &TabGame::actRotateViewCCW); connect(aRotateViewCCW, &QAction::triggered, this, &TabGame::actRotateViewCCW);
aGameInfo = new QAction(this); aGameInfo = new QAction(this);
connect(aGameInfo, &QAction::triggered, this, &TabGame::actGameInfo); connect(aGameInfo, &QAction::triggered, this, &TabGame::actGameInfo);
aCopyGameLink = new QAction(this);
aCopyGameLink->setEnabled(!tabSupervisor->getIsLocalGame() && !tabSupervisor->getClient()->serverName().isEmpty());
connect(aCopyGameLink, &QAction::triggered, this, &TabGame::actCopyGameLink);
aConcede = new QAction(this); aConcede = new QAction(this);
connect(aConcede, &QAction::triggered, this, &TabGame::actConcede); connect(aConcede, &QAction::triggered, this, &TabGame::actConcede);
if (!game->getGameMetaInfo()->started()) { if (!game->getGameMetaInfo()->started()) {
@ -1042,7 +1024,6 @@ void TabGame::createMenuItems()
gameMenu->addAction(aRotateViewCCW); gameMenu->addAction(aRotateViewCCW);
gameMenu->addSeparator(); gameMenu->addSeparator();
gameMenu->addAction(aGameInfo); gameMenu->addAction(aGameInfo);
gameMenu->addAction(aCopyGameLink);
gameMenu->addAction(aConcede); gameMenu->addAction(aConcede);
gameMenu->addAction(aFocusChat); gameMenu->addAction(aFocusChat);
gameMenu->addAction(aLeaveGame); gameMenu->addAction(aLeaveGame);
@ -1065,7 +1046,6 @@ void TabGame::createReplayMenuItems()
aRotateViewCCW = nullptr; aRotateViewCCW = nullptr;
aResetLayout = nullptr; aResetLayout = nullptr;
aGameInfo = nullptr; aGameInfo = nullptr;
aCopyGameLink = nullptr;
aConcede = nullptr; aConcede = nullptr;
aFocusChat = nullptr; aFocusChat = nullptr;
aLeaveGame = new QAction(this); aLeaveGame = new QAction(this);

View file

@ -83,8 +83,8 @@ private:
QAction *playersSeparator; QAction *playersSeparator;
QMenu *gameMenu, *viewMenu; QMenu *gameMenu, *viewMenu;
TearOffMenu *phasesMenu; TearOffMenu *phasesMenu;
QAction *aGameInfo, *aConcede, *aCopyGameLink, *aLeaveGame, *aNextPhase, *aNextPhaseAction, *aNextTurn, QAction *aGameInfo, *aConcede, *aLeaveGame, *aNextPhase, *aNextPhaseAction, *aNextTurn, *aReverseTurn,
*aReverseTurn, *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout; *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout;
QAction *aFocusChat; QAction *aFocusChat;
QList<QAction *> phaseActions; QList<QAction *> phaseActions;
QAction *aCardMenu; QAction *aCardMenu;
@ -148,7 +148,6 @@ private slots:
void actGameInfo(); void actGameInfo();
void actConcede(); void actConcede();
void actCopyGameLink();
void actRemoveLocalArrows(); void actRemoveLocalArrows();
void actRotateViewCW(); void actRotateViewCW();
void actRotateViewCCW(); void actRotateViewCCW();

View file

@ -10,7 +10,6 @@ set(HEADERS
game/server_card.h game/server_card.h
game/server_cardzone.h game/server_cardzone.h
game/server_counter.h game/server_counter.h
game/game_config.h
game/server_game.h game/server_game.h
game/server_player.h game/server_player.h
game/server_spectator.h game/server_spectator.h

View file

@ -1,26 +0,0 @@
#ifndef GAME_CONFIG_H
#define GAME_CONFIG_H
#include <QList>
#include <QString>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
struct GameConfig
{
ServerInfo_User creatorInfo;
int gameId = -1;
QString description;
QString password;
int maxPlayers = 2;
QList<int> gameTypes;
bool onlyBuddies = false;
bool onlyRegistered = false;
bool spectatorsAllowed = false;
bool spectatorsNeedPassword = false;
bool spectatorsCanTalk = true;
bool spectatorsSeeEverything = true;
int startingLifeTotal = 20;
bool shareDecklistsOnLoad = true;
};
#endif

View file

@ -53,19 +53,34 @@
#include <libcockatrice/protocol/pb/game_replay.pb.h> #include <libcockatrice/protocol/pb/game_replay.pb.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
Server_Game::Server_Game(const GameConfig &config, Server_Room *_room) Server_Game::Server_Game(const ServerInfo_User &_creatorInfo,
: QObject(), room(_room), nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(config.creatorInfo)), int _gameId,
gameStarted(false), gameClosed(false), gameId(config.gameId), description(config.description.simplified()), const QString &_description,
password(config.password), maxPlayers(config.maxPlayers), gameTypes(config.gameTypes), activePlayer(-1), const QString &_password,
activePhase(-1), onlyBuddies(config.onlyBuddies), onlyRegistered(config.onlyRegistered), int _maxPlayers,
spectatorsAllowed(config.spectatorsAllowed), spectatorsNeedPassword(config.spectatorsNeedPassword), const QList<int> &_gameTypes,
spectatorsCanTalk(config.spectatorsCanTalk), spectatorsSeeEverything(config.spectatorsSeeEverything), bool _onlyBuddies,
startingLifeTotal(config.startingLifeTotal), shareDecklistsOnLoad(config.shareDecklistsOnLoad), bool _onlyRegistered,
inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false), bool _spectatorsAllowed,
turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr), gameMutex() bool _spectatorsNeedPassword,
bool _spectatorsCanTalk,
bool _spectatorsSeeEverything,
int _startingLifeTotal,
bool _shareDecklistsOnLoad,
Server_Room *_room)
: QObject(), room(_room), nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(_creatorInfo)),
gameStarted(false), gameClosed(false), gameId(_gameId), password(_password), maxPlayers(_maxPlayers),
gameTypes(_gameTypes), activePlayer(-1), activePhase(-1), onlyBuddies(_onlyBuddies),
onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed),
spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk),
spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal),
shareDecklistsOnLoad(_shareDecklistsOnLoad), inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0),
firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr),
gameMutex()
{ {
currentReplay = new GameReplay; currentReplay = new GameReplay;
currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId()); currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId());
description = _description.simplified();
connect(this, &Server_Game::sigStartGameIfReady, this, &Server_Game::doStartGameIfReady, Qt::QueuedConnection); connect(this, &Server_Game::sigStartGameIfReady, this, &Server_Game::doStartGameIfReady, Qt::QueuedConnection);

View file

@ -21,7 +21,6 @@
#define SERVERGAME_H #define SERVERGAME_H
#include "../server_response_containers.h" #include "../server_response_containers.h"
#include "game_config.h"
#include <QDateTime> #include <QDateTime>
#include <QMap> #include <QMap>
@ -93,7 +92,21 @@ private slots:
public: public:
mutable QRecursiveMutex gameMutex; mutable QRecursiveMutex gameMutex;
Server_Game(const GameConfig &config, Server_Room *parent); Server_Game(const ServerInfo_User &_creatorInfo,
int _gameId,
const QString &_description,
const QString &_password,
int _maxPlayers,
const QList<int> &_gameTypes,
bool _onlyBuddies,
bool _onlyRegistered,
bool _spectatorsAllowed,
bool _spectatorsNeedPassword,
bool _spectatorsCanTalk,
bool _spectatorsSeeEverything,
int _startingLifeTotal,
bool _shareDecklistsOnLoad,
Server_Room *parent);
~Server_Game() override; ~Server_Game() override;
Server_Room *getRoom() const Server_Room *getRoom() const
{ {

View file

@ -1,6 +1,5 @@
#include "server_protocolhandler.h" #include "server_protocolhandler.h"
#include "game/game_config.h"
#include "game/server_game.h" #include "game/server_game.h"
#include "game/server_player.h" #include "game/server_player.h"
#include "server_database_interface.h" #include "server_database_interface.h"
@ -921,22 +920,10 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
// When server doesn't permit registered users to exist, do not honor only-reg setting // When server doesn't permit registered users to exist, do not honor only-reg setting
bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers()); bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers());
GameConfig config{.creatorInfo = copyUserInfo(false), auto *game = new Server_Game(copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()),
.gameId = gameId, cmd.max_players(), gameTypes, cmd.only_buddies(), onlyRegisteredUsers,
.description = description, cmd.spectators_allowed(), cmd.spectators_need_password(), cmd.spectators_can_talk(),
.password = QString::fromStdString(cmd.password()), cmd.spectators_see_everything(), startingLifeTotal, shareDecklistsOnLoad, room);
.maxPlayers = static_cast<int>(cmd.max_players()),
.gameTypes = gameTypes,
.onlyBuddies = cmd.only_buddies(),
.onlyRegistered = onlyRegisteredUsers,
.spectatorsAllowed = cmd.spectators_allowed(),
.spectatorsNeedPassword = cmd.spectators_need_password(),
.spectatorsCanTalk = cmd.spectators_can_talk(),
.spectatorsSeeEverything = cmd.spectators_see_everything(),
.startingLifeTotal = startingLifeTotal,
.shareDecklistsOnLoad = shareDecklistsOnLoad};
auto *game = new Server_Game(config, room);
game->addPlayer(this, rc, asSpectator, asJudge, false); game->addPlayer(this, rc, asSpectator, asJudge, false);
room->addGame(game); room->addGame(game);

View file

@ -1,4 +1,3 @@
#include "game/game_config.h"
#include "game/server_abstract_player.h" #include "game/server_abstract_player.h"
#include "game/server_card.h" #include "game/server_card.h"
#include "game/server_cardzone.h" #include "game/server_cardzone.h"
@ -23,21 +22,7 @@ TEST(ReverseCardMoveTest, MoveCardFromBottomTest)
// instantiate a fake server instance // instantiate a fake server instance
FakeServer server; FakeServer server;
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server); Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
GameConfig config{.creatorInfo = user, Server_Game game(user, 1, "", "", 2, QList<int>(), false, false, false, false, false, false, 20, false, &room);
.gameId = 1,
.description = QString(),
.password = QString(),
.maxPlayers = 2,
.gameTypes = QList<int>(),
.onlyBuddies = false,
.onlyRegistered = false,
.spectatorsAllowed = false,
.spectatorsNeedPassword = false,
.spectatorsCanTalk = false,
.spectatorsSeeEverything = false,
.startingLifeTotal = 20,
.shareDecklistsOnLoad = false};
Server_Game game(config, &room);
Server_AbstractPlayer player(&game, 1, user, false, nullptr); Server_AbstractPlayer player(&game, 1, user, false, nullptr);
Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone); Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone);
Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone); Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone);