diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index bd79b62c5..0b2192399 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -59,6 +59,7 @@ set(cockatrice_SOURCES src/game/board/abstract_card_drag_item.cpp src/game/board/abstract_card_item.cpp src/game/board/abstract_counter.cpp + src/game/board/arrow_data.cpp src/game/board/arrow_item.cpp src/game/board/arrow_target.cpp src/game/board/card_drag_item.cpp diff --git a/cockatrice/src/game/board/arrow_data.cpp b/cockatrice/src/game/board/arrow_data.cpp new file mode 100644 index 000000000..bbb70f474 --- /dev/null +++ b/cockatrice/src/game/board/arrow_data.cpp @@ -0,0 +1,19 @@ +#include "arrow_data.h" + +ArrowData ArrowData::fromProto(const ServerInfo_Arrow &arrow) +{ + ArrowData data; + data.id = arrow.id(); + data.startPlayerId = arrow.start_player_id(); + data.startZone = QString::fromStdString(arrow.start_zone()); + data.startCardId = arrow.start_card_id(); + data.targetPlayerId = arrow.target_player_id(); + data.color = convertColorToQColor(arrow.arrow_color()); + + if (arrow.has_target_zone()) { + data.targetZone = QString::fromStdString(arrow.target_zone()); + data.targetCardId = arrow.target_card_id(); + } + + return data; +} \ No newline at end of file diff --git a/cockatrice/src/game/board/arrow_data.h b/cockatrice/src/game/board/arrow_data.h new file mode 100644 index 000000000..a8b35dad6 --- /dev/null +++ b/cockatrice/src/game/board/arrow_data.h @@ -0,0 +1,28 @@ +#ifndef COCKATRICE_ARROW_DATA_H +#define COCKATRICE_ARROW_DATA_H + +#include +#include +#include +#include + +struct ArrowData +{ + int id; + int startPlayerId; + QString startZone; + int startCardId; + int targetPlayerId; + QString targetZone; // empty = targeting a player + int targetCardId = -1; // -1 = targeting a player + QColor color; + + static ArrowData fromProto(const ServerInfo_Arrow &arrow); + + bool isPlayerTargeted() const + { + return targetZone.isEmpty(); + } +}; + +#endif // COCKATRICE_ARROW_DATA_H diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index b9d01c40e..bfc219a3f 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -26,16 +26,23 @@ ArrowItem::ArrowItem(PlayerLogic *_player, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color) - : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false), - color(_color), fullColor(true) + : player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color) { setZValue(ZValues::ARROWS); + auto doUpdate = [this]() { + if (startItem && targetItem) { + updatePath(); + } + }; + if (startItem) { - startItem->addArrowFrom(this); + connect(startItem, &ArrowTarget::scenePositionChanged, this, doUpdate); + connect(startItem, &QObject::destroyed, this, &ArrowItem::delArrow); } if (targetItem) { - targetItem->addArrowTo(this); + connect(targetItem, &ArrowTarget::scenePositionChanged, this, doUpdate); + connect(targetItem, &QObject::destroyed, this, &ArrowItem::delArrow); } if (startItem && targetItem) { @@ -43,24 +50,11 @@ ArrowItem::ArrowItem(PlayerLogic *_player, } } -ArrowItem::~ArrowItem() -{ -} - void ArrowItem::delArrow() { - if (startItem) { - startItem->removeArrowFrom(this); - startItem = 0; - } - if (targetItem) { targetItem->setBeingPointedAt(false); - targetItem->removeArrowTo(this); - targetItem = 0; } - - player->removeArrow(this); deleteLater(); } @@ -148,8 +142,7 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) return; } - QList colliding = scene()->items(event->scenePos()); - for (QGraphicsItem *item : colliding) { + for (auto *item : scene()->items(event->scenePos())) { if (qgraphicsitem_cast(item)) { event->ignore(); return; @@ -164,60 +157,62 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) } } +// ArrowDragItem + ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase) - : ArrowItem(_owner, -1, _startItem, 0, _color), deleteInPhase(_deleteInPhase) + : ArrowItem(_owner, -1, _startItem, nullptr, _color), deleteInPhase(_deleteInPhase) { } -void ArrowDragItem::addChildArrow(ArrowDragItem *childArrow) +void ArrowDragItem::addChildArrow(ArrowDragItem *child) { - childArrows.append(childArrow); + childArrows.append(child); } void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - // This ensures that if a mouse move event happens after a call to delArrow(), - // the event will be discarded as it would create some stray pointers. if (targetLocked || !startItem) { return; } - QPointF endPos = event->scenePos(); + const QPointF endPos = event->scenePos(); - QList colliding = scene()->items(endPos); - ArrowTarget *cursorItem = 0; + ArrowTarget *cursorItem = nullptr; qreal cursorItemZ = -1; - for (int i = colliding.size() - 1; i >= 0; i--) { - if (qgraphicsitem_cast(colliding.at(i)) || qgraphicsitem_cast(colliding.at(i))) { - if (colliding.at(i)->zValue() > cursorItemZ) { - cursorItem = static_cast(colliding.at(i)); - cursorItemZ = cursorItem->zValue(); - } + for (auto *item : scene()->items(endPos)) { + ArrowTarget *candidate = nullptr; + if (auto *card = qgraphicsitem_cast(item)) { + candidate = card; + } else if (auto *pt = qgraphicsitem_cast(item)) { + candidate = pt; + } + + if (candidate && candidate->zValue() > cursorItemZ) { + cursorItem = candidate; + cursorItemZ = candidate->zValue(); } } - if ((cursorItem != targetItem) && targetItem) { - targetItem->setBeingPointedAt(false); - targetItem->removeArrowTo(this); - } - if (!cursorItem) { - fullColor = false; - targetItem = 0; - updatePath(endPos); - } else { - if (cursorItem != targetItem) { - fullColor = true; - if (cursorItem != startItem) { - cursorItem->setBeingPointedAt(true); - cursorItem->addArrowTo(this); - } - targetItem = cursorItem; + if (cursorItem != targetItem) { + if (targetItem) { + disconnect(positionConnection); + targetItem->setBeingPointedAt(false); + } + + targetItem = cursorItem; + fullColor = (cursorItem != nullptr); + + if (cursorItem && cursorItem != startItem) { + cursorItem->setBeingPointedAt(true); + positionConnection = + connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); }); } - updatePath(); } + + targetItem ? updatePath() : updatePath(endPos); update(); - for (ArrowDragItem *child : childArrows) { + for (auto *child : childArrows) { child->mouseMoveEvent(event); } } @@ -228,12 +223,16 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) return; } - if (targetItem && (targetItem != startItem)) { - CardZoneLogic *startZone = static_cast(startItem)->getZone(); + if (targetItem && targetItem != startItem) { + CardItem *startCard = qgraphicsitem_cast(startItem); // For now, we can safely assume that the start item is always a card. // The target item can be a player as well. - CardItem *startCard = qgraphicsitem_cast(startItem); - CardItem *targetCard = qgraphicsitem_cast(targetItem); + if (!startCard) { + delArrow(); + return; + } + + CardZoneLogic *startZone = startCard->getZone(); Command_CreateArrow cmd; cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color)); @@ -241,14 +240,16 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) cmd.set_start_zone(startZone->getName().toStdString()); cmd.set_start_card_id(startCard->getId()); - if (targetCard) { + if (auto *targetCard = qgraphicsitem_cast(targetItem)) { CardZoneLogic *targetZone = targetCard->getZone(); cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_target_zone(targetZone->getName().toStdString()); cmd.set_target_card_id(targetCard->getId()); - } else { // failed to cast target to card, this means it's a player - PlayerTarget *targetPlayer = qgraphicsitem_cast(targetItem); + } else if (auto *targetPlayer = qgraphicsitem_cast(targetItem)) { cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId()); + } else { + delArrow(); + return; } // if the card is in hand then we will move the card to stack or table as part of drawing the arrow @@ -271,21 +272,22 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) player->getPlayerActions()->sendGameCommand(cmd); } - delArrow(); - for (ArrowDragItem *child : childArrows) { + delArrow(); + for (auto *child : childArrows) { child->mouseReleaseEvent(event); } } +// ArrowAttachItem ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem) - : ArrowItem(_startItem->getOwner(), -1, _startItem, 0, Qt::green) + : ArrowItem(_startItem->getOwner(), -1, _startItem, nullptr, Qt::green) { } -void ArrowAttachItem::addChildArrow(ArrowAttachItem *childArrow) +void ArrowAttachItem::addChildArrow(ArrowAttachItem *child) { - childArrows.append(childArrow); + childArrows.append(child); } void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) @@ -294,67 +296,43 @@ void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) return; } - QPointF endPos = event->scenePos(); + const QPointF endPos = event->scenePos(); - QList colliding = scene()->items(endPos); - ArrowTarget *cursorItem = 0; + ArrowTarget *cursorItem = nullptr; qreal cursorItemZ = -1; - for (int i = colliding.size() - 1; i >= 0; i--) { - if (qgraphicsitem_cast(colliding.at(i))) { - if (colliding.at(i)->zValue() > cursorItemZ) { - cursorItem = static_cast(colliding.at(i)); - cursorItemZ = cursorItem->zValue(); + for (auto *item : scene()->items(endPos)) { + if (auto *card = qgraphicsitem_cast(item)) { + if (card->zValue() > cursorItemZ) { + cursorItem = card; + cursorItemZ = card->zValue(); } } } - if ((cursorItem != targetItem) && targetItem) { - targetItem->setBeingPointedAt(false); - } - if (!cursorItem) { - fullColor = false; - targetItem = 0; - updatePath(endPos); - } else { - fullColor = true; - if (cursorItem != startItem) { - cursorItem->setBeingPointedAt(true); + if (cursorItem != targetItem) { + if (targetItem) { + disconnect(positionConnection); + targetItem->setBeingPointedAt(false); } + targetItem = cursorItem; - updatePath(); + fullColor = (cursorItem != nullptr); + + if (cursorItem && cursorItem != startItem) { + cursorItem->setBeingPointedAt(true); + positionConnection = + connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); }); + } } + + targetItem ? updatePath() : updatePath(endPos); update(); - for (ArrowAttachItem *child : childArrows) { + for (auto *child : childArrows) { child->mouseMoveEvent(event); } } -void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard) -{ - // do nothing if target is already attached to another card or is not in play - if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) { - return; - } - - CardZoneLogic *startZone = startCard->getZone(); - CardZoneLogic *targetZone = targetCard->getZone(); - - // move card onto table first if attaching from some other zone - if (startZone->getName() != ZoneNames::TABLE) { - player->getPlayerActions()->playCardToTable(startCard, false); - } - - Command_AttachCard cmd; - cmd.set_start_zone(ZoneNames::TABLE); - cmd.set_card_id(startCard->getId()); - cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); - cmd.set_target_zone(targetZone->getName().toStdString()); - cmd.set_target_card_id(targetCard->getId()); - - player->getPlayerActions()->sendGameCommand(cmd); -} - void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (!startItem) { @@ -363,21 +341,40 @@ void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) // Attaching could move startItem under the current cursor position, causing all children to retarget to it right // before they are processed. Prevent that. - for (ArrowAttachItem *child : childArrows) { + for (auto *child : childArrows) { child->setTargetLocked(true); } - if (targetItem && (targetItem != startItem)) { - auto startCard = qgraphicsitem_cast(startItem); - auto targetCard = qgraphicsitem_cast(targetItem); + if (targetItem && targetItem != startItem) { + auto *startCard = qgraphicsitem_cast(startItem); + auto *targetCard = qgraphicsitem_cast(targetItem); if (startCard && targetCard) { attachCards(startCard, targetCard); } } delArrow(); - - for (ArrowAttachItem *child : childArrows) { + for (auto *child : childArrows) { child->mouseReleaseEvent(event); } } + +void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard) +{ + if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) { + return; + } + + // move card onto table first if attaching from some other zone + if (startCard->getZone()->getName() != ZoneNames::TABLE) { + player->getPlayerActions()->playCardToTable(startCard, false); + } + + Command_AttachCard cmd; + cmd.set_start_zone(ZoneNames::TABLE); + cmd.set_card_id(startCard->getId()); + cmd.set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerInfo()->getId()); + cmd.set_target_zone(targetCard->getZone()->getName().toStdString()); + cmd.set_target_card_id(targetCard->getId()); + player->getPlayerActions()->sendGameCommand(cmd); +} \ No newline at end of file diff --git a/cockatrice/src/game/board/arrow_item.h b/cockatrice/src/game/board/arrow_item.h index f2de94d2f..43c0d2b91 100644 --- a/cockatrice/src/game/board/arrow_item.h +++ b/cockatrice/src/game/board/arrow_item.h @@ -7,13 +7,15 @@ #ifndef ARROWITEM_H #define ARROWITEM_H +#include "arrow_target.h" + #include +#include class CardItem; class QGraphicsSceneMouseEvent; class QMenu; class PlayerLogic; -class ArrowTarget; class ArrowItem : public QObject, public QGraphicsItem { @@ -21,20 +23,21 @@ class ArrowItem : public QObject, public QGraphicsItem Q_INTERFACES(QGraphicsItem) private: QPainterPath path; - QMenu *menu; protected: PlayerLogic *player; int id; - ArrowTarget *startItem, *targetItem; - bool targetLocked; + QPointer startItem; + QPointer targetItem; + bool targetLocked = false; QColor color; - bool fullColor; + bool fullColor = true; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; public: - ArrowItem(PlayerLogic *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color); - ~ArrowItem() override; + ArrowItem(PlayerLogic *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; [[nodiscard]] QRectF boundingRect() const override { @@ -44,6 +47,7 @@ public: { return path; } + void updatePath(); void updatePath(const QPointF &endPoint); @@ -55,14 +59,6 @@ public: { return player; } - void setStartItem(ArrowTarget *_item) - { - startItem = _item; - } - void setTargetItem(ArrowTarget *_item) - { - targetItem = _item; - } [[nodiscard]] ArrowTarget *getStartItem() const { return startItem; @@ -75,6 +71,7 @@ public: { targetLocked = _targetLocked; } + void delArrow(); }; @@ -84,10 +81,11 @@ class ArrowDragItem : public ArrowItem private: int deleteInPhase; QList childArrows; + QMetaObject::Connection positionConnection; public: ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase); - void addChildArrow(ArrowDragItem *childArrow); + void addChildArrow(ArrowDragItem *child); protected: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; @@ -99,12 +97,12 @@ class ArrowAttachItem : public ArrowItem Q_OBJECT private: QList childArrows; - + QMetaObject::Connection positionConnection; void attachCards(CardItem *startCard, const CardItem *targetCard); public: explicit ArrowAttachItem(ArrowTarget *_startItem); - void addChildArrow(ArrowAttachItem *childArrow); + void addChildArrow(ArrowAttachItem *child); protected: void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; diff --git a/cockatrice/src/game/board/arrow_target.cpp b/cockatrice/src/game/board/arrow_target.cpp index bcd067bdb..edf526e4e 100644 --- a/cockatrice/src/game/board/arrow_target.cpp +++ b/cockatrice/src/game/board/arrow_target.cpp @@ -3,41 +3,21 @@ #include "../player/player_logic.h" #include "arrow_item.h" -ArrowTarget::ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent) - : AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false) +ArrowTarget::ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent) : AbstractGraphicsItem(parent), owner(_owner) { setFlag(ItemSendsScenePositionChanges); } -ArrowTarget::~ArrowTarget() -{ - for (int i = 0; i < arrowsFrom.size(); ++i) { - arrowsFrom[i]->setStartItem(0); - arrowsFrom[i]->delArrow(); - } - for (int i = 0; i < arrowsTo.size(); ++i) { - arrowsTo[i]->setTargetItem(0); - arrowsTo[i]->delArrow(); - } -} - void ArrowTarget::setBeingPointedAt(bool _beingPointedAt) { beingPointedAt = _beingPointedAt; update(); } -QVariant ArrowTarget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) +QVariant ArrowTarget::itemChange(GraphicsItemChange change, const QVariant &value) { - if (change == ItemScenePositionHasChanged && scene()) { - for (auto *arrow : arrowsFrom) { - arrow->updatePath(); - } - - for (auto *arrow : arrowsTo) { - arrow->updatePath(); - } + if (change == ItemScenePositionHasChanged) { + emit scenePositionChanged(); } - - return QGraphicsItem::itemChange(change, value); -} + return AbstractGraphicsItem::itemChange(change, value); +} \ No newline at end of file diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h index 59ef48277..9c9aaa315 100644 --- a/cockatrice/src/game/board/arrow_target.h +++ b/cockatrice/src/game/board/arrow_target.h @@ -21,12 +21,14 @@ protected: PlayerLogic *owner; private: - bool beingPointedAt; - QList arrowsFrom, arrowsTo; + bool beingPointedAt = false; + +signals: + void scenePositionChanged(); public: explicit ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent = nullptr); - ~ArrowTarget() override; + ~ArrowTarget() override = default; [[nodiscard]] PlayerLogic *getOwner() const { @@ -39,32 +41,7 @@ public: return beingPointedAt; } - [[nodiscard]] const QList &getArrowsFrom() const - { - return arrowsFrom; - } - void addArrowFrom(ArrowItem *arrow) - { - arrowsFrom.append(arrow); - } - void removeArrowFrom(ArrowItem *arrow) - { - arrowsFrom.removeOne(arrow); - } - [[nodiscard]] const QList &getArrowsTo() const - { - return arrowsTo; - } - void addArrowTo(ArrowItem *arrow) - { - arrowsTo.append(arrow); - } - void removeArrowTo(ArrowItem *arrow) - { - arrowsTo.removeOne(arrow); - } - protected: - QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; }; #endif diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index 91ac0adc6..71b9caa4b 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -82,9 +82,25 @@ void GameScene::addPlayer(PlayerLogic *player) { qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getPlayerInfo()->getName(); - players << player->getGraphicsItem(); + playerViews.insert(player->getPlayerInfo()->getId(), player->getGraphicsItem()); addItem(player->getGraphicsItem()); connect(player->getGraphicsItem(), &PlayerGraphicsItem::sizeChanged, this, &GameScene::rearrange); + + connect(player, &PlayerLogic::concededChanged, this, [this](int id, bool conceded) { + if (conceded) { + clearArrowsForPlayer(id); + } + rearrange(); + }); + + connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::onArrowCreateRequested); + connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::onArrowDeleteRequested); + connect(player, &PlayerLogic::arrowsCleared, this, + [this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayer(id); }); + + connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged); + + rearrange(); } /** @@ -97,13 +113,15 @@ void GameScene::removePlayer(PlayerLogic *player) { qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::removePlayer name=" << player->getPlayerInfo()->getName(); + clearArrowsForPlayer(player->getPlayerInfo()->getId()); + for (ZoneViewWidget *zone : zoneViews) { if (zone->getPlayer() == player) { zone->close(); } } - players.removeOne(player->getGraphicsItem()); - removeItem(player->getGraphicsItem()); + auto *view = playerViews.take(player->getPlayerInfo()->getId()); + removeItem(view); rearrange(); } @@ -184,7 +202,7 @@ QList GameScene::collectActivePlayers(int &firstPlayerIndex) cons firstPlayerIndex = 0; bool firstPlayerFound = false; - for (auto *pgItem : players) { + for (auto *pgItem : playerViews.values()) { PlayerLogic *p = pgItem->getPlayer(); if (p && !p->getConceded()) { activePlayers.append(p); @@ -348,6 +366,77 @@ void GameScene::resizeColumnsAndPlayers(const QList &minWidthByColumn, qr } } +void GameScene::onArrowCreateRequested(const ArrowData &data) +{ + auto *startView = playerViews.value(data.startPlayerId); + auto *targetView = playerViews.value(data.targetPlayerId); + if (!startView || !targetView) { + return; + } + + PlayerLogic *startLogic = startView->getPlayer(); + auto *startZone = startLogic->getZones().value(data.startZone); + if (!startZone) { + return; + } + + CardItem *startCard = startZone->getCard(data.startCardId); + if (!startCard) { + return; + } + + ArrowTarget *targetItem = nullptr; + if (data.isPlayerTargeted()) { + targetItem = targetView->getPlayerTarget(); + } else { + auto *zone = targetView->getPlayer()->getZones().value(data.targetZone); + if (zone) { + targetItem = zone->getCard(data.targetCardId); + } + } + if (!targetItem) { + return; + } + + auto *arrow = new ArrowItem(startView->getPlayer(), data.id, startCard, targetItem, data.color); + addItem(arrow); + arrowRegistry.insert(data.id, arrow); + connect(arrow, &QObject::destroyed, this, [this, id = data.id]() { arrowRegistry.remove(id); }); +} + +void GameScene::onArrowDeleteRequested(int arrowId) +{ + if (arrowRegistry.contains(arrowId)) { + emit requestArrowDeletion(arrowId); + } +} + +void GameScene::onCardZoneChanged(CardItem *card, bool sameZone) +{ + QList toDelete; + for (auto *arrow : arrowRegistry.values()) { + if (arrow->getStartItem() == card || arrow->getTargetItem() == card) { + if (sameZone) { + arrow->updatePath(); + } else { + toDelete.append(arrow); + } + } + } + for (auto *arrow : toDelete) { + emit requestArrowDeletion(arrow->getId()); + } +} + +void GameScene::clearArrowsForPlayer(int playerId) +{ + for (auto *arrow : arrowRegistry.values()) { + if (arrow->getPlayer()->getPlayerInfo()->getId() == playerId) { + emit requestArrowDeletion(arrow->getId()); + } + } +} + // ---------- Hover Handling ---------- void GameScene::updateHover(const QPointF &scenePos) diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index a2d712609..aadbd536e 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -1,6 +1,8 @@ #ifndef GAMESCENE_H #define GAMESCENE_H +#include "board/arrow_data.h" +#include "board/arrow_item.h" #include "zones/card_zone_logic.h" #include @@ -41,8 +43,9 @@ private: static const int playerAreaSpacing = 5; ///< Space between player areas PhasesToolbar *phasesToolbar; ///< Toolbar showing game phases - QList players; ///< All player graphics items + QMap playerViews; ///< ID lookup for player graphics items QList> playersByColumn; ///< Players organized by column + QMap arrowRegistry; ///< ID registry for arrow graphics items QList zoneViews; ///< Active zone view widgets QSize viewSize; ///< Current view size QPointer hoveredCard; ///< Currently hovered card @@ -196,6 +199,11 @@ public slots: QTransform getViewTransform() const; QTransform getViewportTransform() const; + void onArrowCreateRequested(const ArrowData &data); + void onArrowDeleteRequested(int arrowId); + void onCardZoneChanged(CardItem *card, bool sameZone); + void clearArrowsForPlayer(int playerId); + protected: /** Handles hover updates. */ bool event(QEvent *event) override; @@ -207,6 +215,7 @@ signals: void sigStartRubberBand(const QPointF &selectionOrigin); void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount); void sigStopRubberBand(); + void requestArrowDeletion(int arrowId); }; #endif diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 1345924b6..24030057b 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -2,9 +2,11 @@ #include "../../game_graphics/zones/view_zone.h" #include "../../interface/widgets/tabs/tab_game.h" +#include "../board/arrow_data.h" #include "../board/arrow_item.h" #include "../board/card_item.h" #include "../board/card_list.h" +#include "libcockatrice/utility/color.h" #include "player_actions.h" #include "player_logic.h" @@ -90,25 +92,43 @@ void PlayerEventHandler::eventRollDie(const Event_RollDie &event) void PlayerEventHandler::eventCreateArrow(const Event_CreateArrow &event) { - ArrowItem *arrow = player->addArrow(event.arrow_info()); - if (!arrow) { - return; + const ArrowData data = ArrowData::fromProto(event.arrow_info()); + + // Resolve names for logging + const auto &playerList = player->getGame()->getPlayerManager()->getPlayers(); + PlayerLogic *startPlayer = playerList.value(data.startPlayerId); + PlayerLogic *targetPlayer = playerList.value(data.targetPlayerId); + + QString startCardName, targetCardName; + if (startPlayer) { + auto *zone = startPlayer->getZones().value(data.startZone); + if (zone) { + if (auto *card = zone->getCard(data.startCardId)) { + startCardName = card->getName(); + } + } + } + if (!data.isPlayerTargeted() && targetPlayer) { + auto *zone = targetPlayer->getZones().value(data.targetZone); + if (zone) { + if (auto *card = zone->getCard(data.targetCardId)) { + targetCardName = card->getName(); + } + } } - auto *startCard = static_cast(arrow->getStartItem()); - auto *targetCard = qgraphicsitem_cast(arrow->getTargetItem()); - if (targetCard) { - emit logCreateArrow(player, startCard->getOwner(), startCard->getName(), targetCard->getOwner(), - targetCard->getName(), false); - } else { - emit logCreateArrow(player, startCard->getOwner(), startCard->getName(), arrow->getTargetItem()->getOwner(), - QString(), true); + emit player->arrowCreateRequested(data); + + const bool validForLogging = !startCardName.isEmpty() && (data.isPlayerTargeted() || !targetCardName.isEmpty()); + + if (startPlayer && targetPlayer && validForLogging) { + emit logCreateArrow(player, startPlayer, startCardName, targetPlayer, targetCardName, data.isPlayerTargeted()); } } void PlayerEventHandler::eventDeleteArrow(const Event_DeleteArrow &event) { - player->delArrow(event.arrow_id()); + emit player->arrowDeleteRequested(event.arrow_id()); } void PlayerEventHandler::eventCreateToken(const Event_CreateToken &event) @@ -352,28 +372,7 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv targetZone->addCard(card, true, x, y); - // Look at all arrows from and to the card. - // If the card was moved to another zone, delete the arrows, otherwise update them. - QMapIterator playerIterator(player->getGame()->getPlayerManager()->getPlayers()); - while (playerIterator.hasNext()) { - PlayerLogic *p = playerIterator.next().value(); - - QList arrowsToDelete; - QMapIterator arrowIterator(p->getArrows()); - while (arrowIterator.hasNext()) { - ArrowItem *arrow = arrowIterator.next().value(); - if ((arrow->getStartItem() == card) || (arrow->getTargetItem() == card)) { - if (startZone == targetZone) { - arrow->updatePath(); - } else { - arrowsToDelete.append(arrow); - } - } - } - for (auto &i : arrowsToDelete) { - i->delArrow(); - } - } + emit cardZoneChanged(card, startZone == targetZone); player->getPlayerMenu()->updateCardMenu(card); if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == ZoneNames::DECK && diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index c9bdb98ae..84acbb8ea 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -82,6 +82,7 @@ signals: bool isLentToAnotherPlayer = false); void logAlwaysRevealTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); + void cardZoneChanged(CardItem *card, bool sameZone); public: PlayerEventHandler(PlayerLogic *player); diff --git a/cockatrice/src/game/player/player_logic.cpp b/cockatrice/src/game/player/player_logic.cpp index 677cc0d91..7fdb48868 100644 --- a/cockatrice/src/game/player/player_logic.cpp +++ b/cockatrice/src/game/player/player_logic.cpp @@ -74,7 +74,7 @@ PlayerLogic::~PlayerLogic() void PlayerLogic::clear() { - clearArrows(); + emit arrowsCleared(); QMapIterator i(zones); while (i.hasNext()) { @@ -115,7 +115,7 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info) /* HandZone */ ZoneNames::HAND}; clearCounters(); - clearArrows(); + emit arrowsCleared(); QMutableMapIterator zoneIt(zones); while (zoneIt.hasNext()) { @@ -231,7 +231,7 @@ void PlayerLogic::processCardAttachment(const ServerInfo_Player &info) const int arrowListSize = info.arrow_list_size(); for (int i = 0; i < arrowListSize; ++i) { - addArrow(info.arrow_list(i)); + emit arrowCreateRequested(ArrowData::fromProto(info.arrow_list(i))); } } @@ -340,75 +340,6 @@ void PlayerLogic::incrementAllCardCounters() } } -ArrowItem *PlayerLogic::addArrow(const ServerInfo_Arrow &arrow) -{ - const QMap &playerList = game->getPlayerManager()->getPlayers(); - PlayerLogic *startPlayer = playerList.value(arrow.start_player_id(), 0); - PlayerLogic *targetPlayer = playerList.value(arrow.target_player_id(), 0); - if (!startPlayer || !targetPlayer) { - return nullptr; - } - - CardZoneLogic *startZone = startPlayer->getZones().value(QString::fromStdString(arrow.start_zone()), 0); - CardZoneLogic *targetZone = nullptr; - if (arrow.has_target_zone()) { - targetZone = targetPlayer->getZones().value(QString::fromStdString(arrow.target_zone()), 0); - } - if (!startZone || (!targetZone && arrow.has_target_zone())) { - return nullptr; - } - - CardItem *startCard = startZone->getCard(arrow.start_card_id()); - CardItem *targetCard = nullptr; - if (targetZone) { - targetCard = targetZone->getCard(arrow.target_card_id()); - } - if (!startCard || (!targetCard && arrow.has_target_card_id())) { - return nullptr; - } - - if (targetCard) { - return addArrow(arrow.id(), startCard, targetCard, convertColorToQColor(arrow.arrow_color())); - } else { - return addArrow(arrow.id(), startCard, targetPlayer->getGraphicsItem()->getPlayerTarget(), - convertColorToQColor(arrow.arrow_color())); - } -} - -ArrowItem *PlayerLogic::addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color) -{ - auto *arrow = new ArrowItem(this, arrowId, startCard, targetItem, color); - arrows.insert(arrowId, arrow); - - getGameScene()->addItem(arrow); - return arrow; -} - -void PlayerLogic::delArrow(int arrowId) -{ - ArrowItem *arr = arrows.value(arrowId, 0); - if (!arr) { - return; - } - arr->delArrow(); -} - -void PlayerLogic::removeArrow(ArrowItem *arrow) -{ - if (arrow->getId() != -1) { - arrows.remove(arrow->getId()); - } -} - -void PlayerLogic::clearArrows() -{ - QMapIterator arrowIterator(arrows); - while (arrowIterator.hasNext()) { - arrowIterator.next().value()->delArrow(); - } - arrows.clear(); -} - bool PlayerLogic::clearCardsToDelete() { if (cardsToDelete.isEmpty()) { diff --git a/cockatrice/src/game/player/player_logic.h b/cockatrice/src/game/player/player_logic.h index 01d7052df..138ed94ef 100644 --- a/cockatrice/src/game/player/player_logic.h +++ b/cockatrice/src/game/player/player_logic.h @@ -8,6 +8,7 @@ #define PLAYER_H #include "../../interface/widgets/menus/tearoff_menu.h" +#include "../board/arrow_data.h" #include "../interface/deck_loader/loaded_deck.h" #include "../zones/hand_zone_logic.h" #include "../zones/pile_zone_logic.h" @@ -77,6 +78,9 @@ signals: void clearCustomZonesMenu(); void addViewCustomZoneActionToCustomZoneMenu(QString zoneName); void resetTopCardMenuActions(); + void arrowCreateRequested(ArrowData data); + void arrowDeleteRequested(int arrowId); + void arrowsCleared(); // fires on clear() and processPlayerInfo public slots: void setActive(bool _active); @@ -205,17 +209,6 @@ public: */ CounterState *getLifeCounter() const; - ArrowItem *addArrow(const ServerInfo_Arrow &arrow); - ArrowItem *addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color); - void delArrow(int arrowId); - void removeArrow(ArrowItem *arrow); - void clearArrows(); - - const QMap &getArrows() const - { - return arrows; - } - void setConceded(bool _conceded); bool getConceded() const { @@ -252,12 +245,9 @@ private: int zoneId; QMap zones; QMap counters; - QMap arrows; bool dialogSemaphore; QList cardsToDelete; - - // void eventConnectionStateChanged(const Event_ConnectionStateChanged &event); }; class AnnotationDialog : public QInputDialog diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 4dc1d1cb1..6b7b3539c 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -606,17 +606,9 @@ void TabGame::actNextPhaseAction() void TabGame::actRemoveLocalArrows() { - QMapIterator playerIterator(game->getPlayerManager()->getPlayers()); - while (playerIterator.hasNext()) { - PlayerLogic *player = playerIterator.next().value(); - if (!player->getPlayerInfo()->getLocal()) { - continue; - } - QMapIterator arrowIterator(player->getArrows()); - while (arrowIterator.hasNext()) { - ArrowItem *a = arrowIterator.next().value(); - emit arrowDeletionRequested(a->getId()); - } + auto *local = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); + if (local) { + scene->clearArrowsForPlayer(local->getPlayerInfo()->getId()); } } @@ -970,8 +962,6 @@ void TabGame::createMenuItems() connect(aReverseTurn, &QAction::triggered, game->getGameEventHandler(), &GameEventHandler::handleReverseTurn); aRemoveLocalArrows = new QAction(this); connect(aRemoveLocalArrows, &QAction::triggered, this, &TabGame::actRemoveLocalArrows); - connect(this, &TabGame::arrowDeletionRequested, game->getGameEventHandler(), - &GameEventHandler::handleArrowDeletion); aRotateViewCW = new QAction(this); connect(aRotateViewCW, &QAction::triggered, this, &TabGame::actRotateViewCW); aRotateViewCCW = new QAction(this); @@ -1155,6 +1145,8 @@ void TabGame::createPlayAreaWidget(bool bReplay) scene = new GameScene(phasesToolbar, this); connect(game->getPlayerManager(), &PlayerManager::playerConceded, scene, &GameScene::rearrange); connect(game->getPlayerManager(), &PlayerManager::playerCountChanged, scene, &GameScene::rearrange); + connect(scene, &GameScene::requestArrowDeletion, game->getGameEventHandler(), + &GameEventHandler::handleArrowDeletion); gameView = new GameView(scene); auto gamePlayAreaVBox = new QVBoxLayout; diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.h b/cockatrice/src/interface/widgets/tabs/tab_game.h index 66f23ba09..c8dfc3093 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.h +++ b/cockatrice/src/interface/widgets/tabs/tab_game.h @@ -137,7 +137,6 @@ signals: void gameLeft(); void chatMessageSent(QString chatMessage); void turnAdvanced(); - void arrowDeletionRequested(int arrowId); private slots: void adminLockChanged(bool lock);