[Client] Add tournament event plumbing and player extension points

Wires the client game layer for tournament state without touching
existing behavior:
- GameEventHandler dispatches Event_TournamentState (2027) to a new
  tournamentStateChanged signal
- GameMetaInfo exposes isTournament and parentGameId over the new
  ServerInfo_Game field
- PlayerEventHandler::processGameEvent is virtual and player is
  protected; PlayerLogic gains a protected constructor accepting a
  custom handler, so mode-specific subclasses can intercept events
This commit is contained in:
Lukas Brübach 2026-08-24 09:07:11 +02:00 committed by GitHub
parent de03428e51
commit 2d5734de21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 75 additions and 6 deletions

View file

@ -26,6 +26,7 @@
#include <libcockatrice/protocol/pb/event_reverse_turn.pb.h> #include <libcockatrice/protocol/pb/event_reverse_turn.pb.h>
#include <libcockatrice/protocol/pb/event_set_active_phase.pb.h> #include <libcockatrice/protocol/pb/event_set_active_phase.pb.h>
#include <libcockatrice/protocol/pb/event_set_active_player.pb.h> #include <libcockatrice/protocol/pb/event_set_active_player.pb.h>
#include <libcockatrice/protocol/pb/event_tournament_state.pb.h>
#include <libcockatrice/protocol/pb/game_event_container.pb.h> #include <libcockatrice/protocol/pb/game_event_container.pb.h>
#include <libcockatrice/protocol/pending_command.h> #include <libcockatrice/protocol/pending_command.h>
@ -158,6 +159,9 @@ void GameEventHandler::processGameEventContainer(const GameEventContainer &cont,
case GameEvent::REVERSE_TURN: case GameEvent::REVERSE_TURN:
eventReverseTurn(event.GetExtension(Event_ReverseTurn::ext), playerId, context); eventReverseTurn(event.GetExtension(Event_ReverseTurn::ext), playerId, context);
break; break;
case GameEvent::TOURNAMENT_STATE:
emit tournamentStateChanged(event.GetExtension(Event_TournamentState::ext));
break;
default: { default: {
PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0); PlayerLogic *player = game->getPlayerManager()->getPlayers().value(playerId, 0);
@ -263,6 +267,12 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
int /*eventPlayerId*/, int /*eventPlayerId*/,
const GameEventContext & /*context*/) const GameEventContext & /*context*/)
{ {
// Sub-games of a tournament report their parent hub game so the client can
// route "close game" back to the parent tab.
if (event.parent_game_id() != -1) {
game->getGameMetaInfo()->setParentGameId(event.parent_game_id());
}
const int playerListSize = event.player_list_size(); const int playerListSize = event.player_list_size();
QVector<QPair<int, QPair<QString, QString>>> opponentDecksToDisplay; QVector<QPair<int, QPair<QString, QString>>> opponentDecksToDisplay;

View file

@ -43,6 +43,7 @@ class Event_SetActivePhase;
class Event_GameSay; class Event_GameSay;
class Event_Kicked; class Event_Kicked;
class Event_ReverseTurn; class Event_ReverseTurn;
class Event_TournamentState;
class Event_Ping; class Event_Ping;
inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler"); inline Q_LOGGING_CATEGORY(GameEventHandlerLog, "game_event_handler");
@ -329,6 +330,7 @@ signals:
void gameStopped(); void gameStopped();
void gameClosed(); void gameClosed();
void tournamentStateChanged(const Event_TournamentState &state);
void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId); void playerPropertiesChanged(const ServerInfo_PlayerProperties &prop, int playerId);
void playerJoined(const ServerInfo_PlayerProperties &playerInfo); void playerJoined(const ServerInfo_PlayerProperties &playerInfo);
void playerLeft(int leavingPlayerId); void playerLeft(int leavingPlayerId);

View file

@ -84,6 +84,26 @@ public:
return roomGameTypes.find(gameInfo_.game_types(index)).value(); return roomGameTypes.find(gameInfo_.game_types(index)).value();
} }
bool isTournament() const
{
return gameInfo_.is_tournament();
}
void setIsTournament(bool t)
{
gameInfo_.set_is_tournament(t);
}
int parentGameId() const
{
return parentGameId_;
}
void setParentGameId(int id)
{
parentGameId_ = id;
}
public slots: public slots:
void setStarted(bool s) void setStarted(bool s)
{ {
@ -108,6 +128,7 @@ signals:
private: private:
ServerInfo_Game gameInfo_; ServerInfo_Game gameInfo_;
int parentGameId_ = -1;
}; };
#endif // GAME_META_INFO_H #endif // GAME_META_INFO_H

View file

@ -90,10 +90,10 @@ public:
* @param context Additional context (undo, judge, etc.). * @param context Additional context (undo, judge, etc.).
* @param options Processing options (UI suppression, reveal behavior). * @param options Processing options (UI suppression, reveal behavior).
*/ */
void processGameEvent(GameEvent::GameEventType type, virtual void processGameEvent(GameEvent::GameEventType type,
const GameEvent &event, const GameEvent &event,
const GameEventContext &context, const GameEventContext &context,
EventProcessingOptions options); EventProcessingOptions options);
/** @} */ /** @} */
@ -266,7 +266,7 @@ signals:
void cardZoneChanged(CardItem *card, bool sameZone); void cardZoneChanged(CardItem *card, bool sameZone);
void requestCardMenuUpdate(const CardItem *card); void requestCardMenuUpdate(const CardItem *card);
private: protected:
/** Owning player instance. */ /** Owning player instance. */
PlayerLogic *player; PlayerLogic *player;

View file

@ -37,6 +37,19 @@ PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool
initializeZones(); initializeZones();
} }
PlayerLogic::PlayerLogic(const ServerInfo_User &info,
int _id,
bool _local,
bool _judge,
AbstractGame *_parent,
PlayerEventHandler *customEventHandler)
: QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)),
playerEventHandler(customEventHandler), playerActions(new PlayerActions(this)), active(false), conceded(false),
zoneId(0), dialogSemaphore(false)
{
initializeZones();
}
void PlayerLogic::initializeZones() void PlayerLogic::initializeZones()
{ {
addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this)); addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this));

View file

@ -99,7 +99,30 @@ public:
PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent); PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent);
~PlayerLogic() override; ~PlayerLogic() override;
void initializeZones(); protected:
/**
* @brief Constructor for subclasses that need a custom event handler (e.g. DraftPlayerLogic).
*
* @p customEventHandler must be non-null and either QObject-parented to this PlayerLogic
* or deleted externally; it is not owned by PlayerLogic. The handler connects to @c player
* during its own constructor, so passing a freshly built subclass handler from an
* initializer list is safe.
*/
PlayerLogic(const ServerInfo_User &info,
int _id,
bool _local,
bool _judge,
AbstractGame *_parent,
PlayerEventHandler *customEventHandler);
public:
/**
* @brief Creates the standard zone set.
*
* Not virtually dispatched from constructors subclasses overriding this must add
* their extra zones in their own constructor body.
*/
virtual void initializeZones();
void updateZones(); void updateZones();
void clear(); void clear();