Cockatrice/cockatrice/src/game_graphics/game_scene.h
BruebachL 345b0e8ba9
[Refactor] Move game logic into libcockatrice_game (#7437)
* [Refactor] Move game logic into libcockatrice_game

* [Refactor] Include card_state.h in player_actions.h

* [Game] Inject settings providers into libcockatrice_game, drop Widgets

* [Game] Follow base rename to createCardRequested after rebase

* [Game] Drop unused CardItem forward declaration

* [Game] Fix includes after moving game logic into libcockatrice_game

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-10-31 18:44:15 +01:00

310 lines
11 KiB
C++

#ifndef GAMESCENE_H
#define GAMESCENE_H
#include "animated_item.h"
#include "board/arrow_item.h"
#include "board/arrow_registry.h"
#include <QGraphicsScene>
#include <QHash>
#include <QList>
#include <QLoggingCategory>
#include <QMap>
#include <QMetaObject>
#include <QPointF>
#include <QPointer>
#include <QSize>
#include <QSizeF>
#include <QString>
#include <QTransform>
#include <QtGlobal>
#include <libcockatrice/game/board/arrow_data.h>
#include <libcockatrice/game/zones/card_zone_logic.h>
#include <qtmetamacros.h>
// IWYU pragma: keep
// ServerInfo_Card appears inside QList<...> in signal and slot
// signatures, which the moc-generated code needs to be complete.
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
class CardZoneLogic;
class IAnimatedItem;
class QGraphicsItem;
class QPoint;
struct ArrowData;
template <class T> class QSharedPointer;
inline Q_LOGGING_CATEGORY(GameSceneLog, "game_scene");
inline Q_LOGGING_CATEGORY(GameScenePlayerAdditionRemovalLog, "game_scene.player_addition_removal");
class PlayerLogic;
class PlayerGraphicsItem;
class ZoneViewWidget;
class CardZone;
class AbstractCardItem;
class CardItem;
class CardState;
class ServerInfo_Card;
class PhasesToolbar;
class QBasicTimer;
class QObject;
/**
* @class GameScene
* @ingroup GameGraphics
* @brief Manages the game board display including players, zones, cards, and animations.
*
* GameScene handles:
* - Dynamic arrangement of players in columns/rows.
* - Player rotation adjustments.
* - Zone views for cards (e.g., graveyard, library, revealed zones).
* - Hover and animation handling for cards.
* - Scene resizing and responsive layout.
*/
class GameScene : public QGraphicsScene
{
Q_OBJECT
private:
static const int playerAreaSpacing = 5; ///< Space between player areas
PhasesToolbar *phasesToolbar; ///< Toolbar showing game phases
QMap<int, PlayerGraphicsItem *> playerViews; ///< ID lookup for player graphics items
QList<QList<PlayerGraphicsItem *>> playersByColumn; ///< Players organized by column
ArrowRegistry arrowRegistry; ///< ID registry for arrow graphics items
QList<ZoneViewWidget *> zoneViews; ///< Active zone view widgets
QSize viewSize; ///< Current view size
QPointer<CardItem> hoveredCard; ///< Currently hovered card
QPointer<CardItem> activeCard; ///< Card the currently open context menu targets
QBasicTimer *animationTimer; ///< Timer for scene animations
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
QHash<QObject *, QMetaObject::Connection>
animationItemConnections; ///< destroyed->removeAnimatedItem handles per animated item
int playerRotation; ///< Rotation offset for player layout
bool rearranging = false; ///< Guard against re-entrant rearrange
bool needsReArrange = false; ///< Pending rearrange requested during a pass
/**
* @brief Updates which card is currently hovered based on scene coordinates.
* @param scenePos Scene position of the cursor.
*/
void updateHover(const QPointF &scenePos);
/**
* @brief Activates hover state and escapes the card from its clip container
* so hover scaling is visible beyond zone bounds.
*/
void beginCardHover(CardItem *card);
/** @brief Deactivates hover state and restores the card to its clip container. */
void endCardHover(CardItem *card);
public:
/**
* @brief Constructs the GameScene.
* @param _phasesToolbar Toolbar widget for phases.
* @param parent Optional parent QObject.
*/
explicit GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr);
/** @brief Destructor, cleans up timer and zone views. */
~GameScene() override;
/** @brief Updates UI text for all zone views. */
void retranslateUi();
/** @brief Gets all selected CardItems. */
QList<CardItem *> selectedCards() const;
QList<CardState *> selectedCardStates() const;
/** @brief Selects every card sharing the zone of @p card. */
void selectAllInZone(const CardItem *card);
/** @brief Selects the cards laid out in the same visual row as @p card. */
void selectSameRow(const CardItem *card);
/** @brief Selects the cards laid out in the same visual column as @p card. */
void selectSameColumn(const CardItem *card);
/** @brief Card the currently open context menu targets, or null. */
CardItem *getActiveCard() const;
/** @brief Sets the card the currently open context menu targets. */
void setActiveCard(CardItem *card);
/**
* @brief Adds a player to the scene and stores their graphics item.
* @param player Player to add.
*/
void addPlayer(PlayerLogic *player);
/**
* @brief Removes a player from the scene.
* @param player Player to remove.
*/
void removePlayer(PlayerLogic *player);
QMap<int, PlayerGraphicsItem *> getPlayers() const
{
return playerViews;
}
PlayerGraphicsItem *viewForPlayer(int playerId)
{
return playerViews.value(playerId);
}
/**
* @brief Adjusts the global rotation offset for player layout.
* @param rotationAdjustment Number of positions to rotate.
*/
void adjustPlayerRotation(int rotationAdjustment);
/** @brief Recomputes the layout of players and the scene size. */
void rearrange();
/**
* @brief Handles view resize and redistributes player positions.
* @param newSize New view size.
*/
void processViewSizeChange(const QSize &newSize);
/**
* @brief Collects all active (non-conceded) players.
* @param firstPlayerIndex Output index of first local player.
* @return List of active players.
*/
QList<PlayerLogic *> collectActivePlayers(int &firstPlayerIndex) const;
/**
* @brief Rotates the list of players for layout.
* @param players Original list of players.
* @param firstPlayerIndex Index of first local player.
* @return Rotated list.
*/
QList<PlayerLogic *> rotatePlayers(const QList<PlayerLogic *> &players, int firstPlayerIndex) const;
/**
* @brief Determines the number of columns to display players in.
* @param playerCount Total number of active players.
* @return Number of columns (1 or 2).
*/
static int determineColumnCount(int playerCount);
/**
* @brief Computes layout positions and scene size based on players and columns.
* @param playersPlaying List of active players.
* @param columns Number of columns to split into.
* @return Calculated scene size.
*/
QSizeF computeSceneSizeAndPlayerLayout(const QList<PlayerLogic *> &playersPlaying, int columns);
/**
* @brief Computes the minimum width for each column based on player minimum widths.
* @return List of minimum widths per column.
*/
QList<qreal> calculateMinWidthByColumn() const;
/**
* @brief Calculates new scene width considering window aspect ratio.
* @param newSize View size.
* @param minWidth Minimum width needed to fit all players.
* @return Scene width respecting window and content.
*/
qreal calculateNewSceneWidth(const QSize &newSize, qreal minWidth) const;
/**
* @brief Resizes columns and distributes extra width to players.
* @param minWidthByColumn Minimum widths per column.
* @param newWidth Total scene width.
*/
void resizeColumnsAndPlayers(const QList<qreal> &minWidthByColumn, qreal newWidth);
/** @brief Finds the topmost card zone under the cursor. */
static CardZone *findTopmostZone(const QList<QGraphicsItem *> &items);
/** @brief Finds the topmost card in a given zone, considering attachments and Z-order. */
static CardItem *findTopmostCardInZone(const QList<QGraphicsItem *> &items, CardZone *zone);
/** @brief Updates hovered card highlighting. */
void updateHoveredCard(CardItem *newCard);
/**
* @brief Registers an item for animation updates with the shared scene timer.
*
* The item must inherit QObject; it is unregistered automatically when it is
* destroyed, so it may be deleted mid-animation without a dangling pointer.
*/
void registerAnimationItem(IAnimatedItem *item);
/** @brief Unregisters an item from animation updates. */
void unregisterAnimationItem(IAnimatedItem *item);
void startRubberBand(const QPointF &selectionOrigin);
void resizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void stopRubberBand();
private slots:
/** @brief Removes a destroyed item from the animation set. */
void removeAnimatedItem(QObject *item);
public slots:
void onCardSelectionChanged(AbstractCardItem *card, bool selected);
void onCardRightClicked(AbstractCardItem *card, QPoint screenPos);
void playSelected(AbstractCardItem *card);
void playSelectedFaceDown(AbstractCardItem *card);
void hideSelected(AbstractCardItem *card);
/** @brief Toggles a zone view for a player. */
void toggleZoneView(PlayerLogic *player, const QString &zoneName, int numberCards, bool isReversed = false);
/** @brief Adds a revealed zone view (for shown cards). */
void addRevealedZoneView(PlayerLogic *player,
CardZoneLogic *zone,
const QList<const ServerInfo_Card *> &cardList,
bool withWritePermission);
/** @brief Removes a zone view widget from the scene. */
void removeZoneView(ZoneViewWidget *item);
/** @brief Closes all zone views. */
void clearViews();
/** @brief Empties the contents of all non-reveal zone views. Used for replay rewinding */
void emptyActiveViews();
/** @brief Closes the most recently added zone view. */
void closeMostRecentZoneView();
QTransform getViewTransform() const;
QTransform getViewportTransform() const;
/// Directly modifies the scene
void addArrow(QSharedPointer<ArrowData> data);
void deleteArrow(int playerId, int arrowId);
void clearArrowsForPlayer(int playerId);
void clearArrowsForPlayerLocally(int playerId);
/// Queues up arrow deletion but doesn't directly modify the scene
void requestArrowDeletion(int playerId, int arrowId);
void onCardZoneChanged(CardState *card, bool sameZone);
/**
* @brief Refreshes the view of a card that the server just moved.
*
* @param card The card that moved.
* @param zoneChanged Whether the card crossed a zone boundary.
* @param newOwner The player the card now belongs to, or nullptr to leave the owner alone.
*/
void onCardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner);
protected:
/** @brief Handles hover updates. */
bool event(QEvent *event) override;
/** @brief Handles animation timer updates. */
void timerEvent(QTimerEvent *event) override;
signals:
void sigStartRubberBand(const QPointF &selectionOrigin);
void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void sigStopRubberBand();
void arrowDeletionRequested(int creatorId, int arrowId);
};
#endif