#include "card_item.h" #include "../../client/settings/cache_settings.h" #include "../../game_graphics/zones/table_zone.h" #include "../../game_graphics/zones/view_zone.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../game_scene.h" #include "../phase.h" #include "../player/player.h" #include "../player/player_actions.h" #include "../zones/view_zone_logic.h" #include "arrow_item.h" #include "card_drag_item.h" #include <../../client/settings/card_counter_settings.h> #include #include #include #include #include #include CardItem::CardItem(Player *_owner, QGraphicsItem *parent, const CardRef &cardRef, int _cardid, CardZoneLogic *_zone) : AbstractCardItem(parent, cardRef, _owner, _cardid), zone(_zone), attacking(false), destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr) { owner->addCard(this); connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, this, [this](int counterId) { if (counters.contains(counterId)) { update(); } }); } void CardItem::prepareDelete() { if (owner != nullptr) { if (owner->getGame()->getActiveCard() == this) { owner->getPlayerMenu()->updateCardMenu(nullptr); owner->getGame()->setActiveCard(nullptr); } owner = nullptr; } while (!attachedCards.isEmpty()) { attachedCards.first()->setZone(nullptr); // so that it won't try to call reorganizeCards() attachedCards.first()->setAttachedTo(nullptr); } if (attachedTo != nullptr) { attachedTo->removeAttachedCard(this); attachedTo = nullptr; } } void CardItem::deleteLater() { prepareDelete(); if (scene()) { static_cast(scene())->unregisterAnimationItem(this); } AbstractCardItem::deleteLater(); } void CardItem::setZone(CardZoneLogic *_zone) { zone = _zone; } void CardItem::retranslateUi() { } void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { auto &cardCounterSettings = SettingsCache::instance().cardCounters(); painter->save(); AbstractCardItem::paint(painter, option, widget); int i = 0; QMapIterator counterIterator(counters); while (counterIterator.hasNext()) { counterIterator.next(); QColor _color = cardCounterSettings.color(counterIterator.key()); paintNumberEllipse(counterIterator.value(), 14, _color, i, counters.size(), painter); ++i; } QSizeF translatedSize = getTranslatedSize(painter); qreal scaleFactor = translatedSize.width() / boundingRect().width(); if (!pt.isEmpty()) { painter->save(); transformPainter(painter, translatedSize, tapAngle); if (!getFaceDown() && pt == exactCard.getInfo().getPowTough()) { painter->setPen(Qt::white); } else { painter->setPen(QColor(255, 150, 0)); // dark orange } painter->setBackground(Qt::black); painter->setBackgroundMode(Qt::OpaqueMode); painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 10 * scaleFactor, translatedSize.height() - 8 * scaleFactor), Qt::AlignRight | Qt::AlignBottom, pt); painter->restore(); } if (!annotation.isEmpty()) { painter->save(); transformPainter(painter, translatedSize, tapAngle); painter->setBackground(Qt::black); painter->setBackgroundMode(Qt::OpaqueMode); painter->setPen(Qt::white); painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor, translatedSize.height() - 8 * scaleFactor), Qt::AlignCenter | Qt::TextWrapAnywhere, annotation); painter->restore(); } if (getBeingPointedAt()) { painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100))); } if (doesntUntap) { painter->save(); painter->setRenderHint(QPainter::Antialiasing, false); QPen pen; pen.setColor(Qt::magenta); pen.setWidth(0); // Cosmetic pen painter->setPen(pen); painter->drawPath(shape()); painter->restore(); } painter->restore(); } void CardItem::setAttacking(bool _attacking) { attacking = _attacking; update(); } void CardItem::setCounter(int _id, int _value) { if (_value) { counters.insert(_id, _value); } else { counters.remove(_id); } update(); } void CardItem::setAnnotation(const QString &_annotation) { annotation = _annotation; update(); } void CardItem::setDoesntUntap(bool _doesntUntap) { doesntUntap = _doesntUntap; update(); } void CardItem::setPT(const QString &_pt) { pt = _pt; update(); } void CardItem::setAttachedTo(CardItem *_attachedTo) { if (attachedTo != nullptr) { attachedTo->removeAttachedCard(this); } gridPoint.setX(-1); attachedTo = _attachedTo; if (attachedTo != nullptr) { // If the zone is being torn down, it might already be null by the time a card tries to un-attach all its // attached cards if (attachedTo->zone == nullptr) { deleteLater(); } else { emit attachedTo->zone->cardAdded(this); attachedTo->addAttachedCard(this); if (zone != attachedTo->getZone()) { attachedTo->getZone()->reorganizeCards(); } } } else { // If the zone is being torn down, it might already be null by the time a card tries to un-attach all its // attached cards if (zone == nullptr) { deleteLater(); } else { emit zone->cardAdded(this); } } if (zone != nullptr) { zone->reorganizeCards(); } } /** * @brief Resets the fields that should be reset after a zone transition */ void CardItem::resetState(bool keepAnnotations) { attacking = false; counters.clear(); pt.clear(); if (!keepAnnotations) { annotation.clear(); } attachedTo = 0; attachedCards.clear(); setTapped(false, false); setDoesntUntap(false); if (scene()) { static_cast(scene())->unregisterAnimationItem(this); } update(); } void CardItem::processCardInfo(const ServerInfo_Card &_info) { counters.clear(); const int counterListSize = _info.counter_list_size(); for (int i = 0; i < counterListSize; ++i) { const ServerInfo_CardCounter &counterInfo = _info.counter_list(i); counters.insert(counterInfo.id(), counterInfo.value()); } setId(_info.id()); setCardRef({QString::fromStdString(_info.name()), QString::fromStdString(_info.provider_id())}); setAttacking(_info.attacking()); setFaceDown(_info.face_down()); setPT(QString::fromStdString(_info.pt())); setAnnotation(QString::fromStdString(_info.annotation())); setColor(QString::fromStdString(_info.color())); setTapped(_info.tapped()); setDestroyOnZoneChange(_info.destroy_on_zone_change()); setDoesntUntap(_info.doesnt_untap()); } CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown) { deleteDragItem(); dragItem = new CardDragItem(this, _id, _pos, forceFaceDown); dragItem->setVisible(false); scene()->addItem(dragItem); dragItem->updatePosition(_scenePos); dragItem->setVisible(true); return dragItem; } void CardItem::deleteDragItem() { if (dragItem) { dragItem->deleteLater(); } dragItem = nullptr; } void CardItem::drawArrow(const QColor &arrowColor) { if (owner->getGame()->getPlayerManager()->isSpectator()) { return; } auto *game = owner->getGame(); Player *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); int phase = 0; // 0 means to not set the phase if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) { int currentPhase = game->getGameState()->getCurrentPhase(); phase = Phases::getLastSubphase(currentPhase) + 1; } ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor, phase); scene()->addItem(arrow); arrow->grabMouse(); for (const auto &item : scene()->selectedItems()) { CardItem *card = qgraphicsitem_cast(item); if (card == nullptr || card == this) { continue; } if (card->getZone() != zone) { continue; } ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase); scene()->addItem(childArrow); arrow->addChildArrow(childArrow); } } void CardItem::drawAttachArrow() { if (owner->getGame()->getPlayerManager()->isSpectator()) { return; } auto *arrow = new ArrowAttachItem(this); scene()->addItem(arrow); arrow->grabMouse(); for (const auto &item : scene()->selectedItems()) { CardItem *card = qgraphicsitem_cast(item); if (card == nullptr) { continue; } if (card->getZone() != zone) { continue; } ArrowAttachItem *childArrow = new ArrowAttachItem(card); scene()->addItem(childArrow); arrow->addChildArrow(childArrow); } } void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons().testFlag(Qt::RightButton)) { if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() < 2 * QApplication::startDragDistance()) { return; } QColor arrowColor = Qt::red; if (event->modifiers().testFlag(Qt::ControlModifier)) { arrowColor = Qt::yellow; } else if (event->modifiers().testFlag(Qt::AltModifier)) { arrowColor = Qt::blue; } else if (event->modifiers().testFlag(Qt::ShiftModifier)) { arrowColor = Qt::green; } drawArrow(arrowColor); } else if (event->buttons().testFlag(Qt::LeftButton)) { if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < 2 * QApplication::startDragDistance()) { return; } if (const ZoneViewZoneLogic *view = qobject_cast(zone)) { if (view->getRevealZone() && !view->getWriteableRevealZone()) { return; } } else if (!owner->getPlayerInfo()->getLocalOrJudge()) { return; } bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier); // Use the buttonDownPos to align the hot spot with the position when // the user originally clicked createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), forceFaceDown); dragItem->grabMouse(); int childIndex = 0; for (const auto &item : scene()->selectedItems()) { CardItem *card = static_cast(item); if ((card == this) || (card->getZone() != zone)) { continue; } ++childIndex; QPointF childPos; if (zone->getHasCardAttr()) { childPos = card->pos() - pos(); } else { childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0); } CardDragItem *drag = new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem); drag->setPos(dragItem->pos() + childPos); scene()->addItem(drag); } } setCursor(Qt::OpenHandCursor); } void CardItem::playCard(bool faceDown) { // Do nothing if the card belongs to another player if (!owner->getPlayerInfo()->getLocalOrJudge()) { return; } TableZoneLogic *tz = qobject_cast(zone); if (tz) { emit tz->toggleTapped(); } else { if (SettingsCache::instance().getClickPlaysAllSelected()) { faceDown ? zone->getPlayer()->getPlayerActions()->actPlayFacedown() : zone->getPlayer()->getPlayerActions()->actPlay(); } else { zone->getPlayer()->getPlayerActions()->playCard(this, faceDown); } } } QVariantList CardItem::parsePT(const QString &pt) { QVariantList ptList = QVariantList(); if (!pt.isEmpty()) { int sep = pt.indexOf('/'); if (sep == 0) { ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string } else { int start = 0; for (;;) { QString item = pt.mid(start, sep - start); if (item.isEmpty()) { ptList.append(QVariant(QString())); } else if (item[0] == '+') { ptList.append(QVariant(item.mid(1).toInt())); // add as int } else if (item[0] == '-') { ptList.append(QVariant(item.toInt())); // add as int } else { ptList.append(QVariant(item)); // add as qstring } if (sep == -1) { break; } start = sep + 1; sep = pt.indexOf('/', start); } } } return ptList; } /** * @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone * is nullptr. */ static bool isUnwritableRevealZone(CardZoneLogic *zone) { if (auto *view = qobject_cast(zone)) { return view->getRevealZone() && !view->getWriteableRevealZone(); } return false; } /** * This method is called when a "click to play" is done on the card. * This is either triggered by a single click or double click, depending on the settings. * * @param shiftHeld if the shift key was held during the click */ void CardItem::handleClickedToPlay(bool shiftHeld) { if (isUnwritableRevealZone(zone)) { if (SettingsCache::instance().getClickPlaysAllSelected()) { zone->getPlayer()->getPlayerActions()->actHide(); } else { zone->removeCard(this); } } else { playCard(shiftHeld); } } void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::RightButton) { if (owner != nullptr) { owner->getGame()->setActiveCard(this); if (QMenu *cardMenu = owner->getPlayerMenu()->updateCardMenu(this)) { cardMenu->popup(event->screenPos()); return; } } } else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) && (!SettingsCache::instance().getDoubleClickToPlay())) { handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); } if (owner != nullptr) { // cards without owner will be deleted setCursor(Qt::OpenHandCursor); } AbstractCardItem::mouseReleaseEvent(event); } void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) && (SettingsCache::instance().getDoubleClickToPlay())) { handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); } event->accept(); } bool CardItem::animationEvent() { int rotation = ROTATION_DEGREES_PER_FRAME; bool animationIncomplete = true; if (!tapped) { rotation *= -1; } tapAngle += rotation; if (tapped && (tapAngle > 90)) { tapAngle = 90; animationIncomplete = false; } if (!tapped && (tapAngle < 0)) { tapAngle = 0; animationIncomplete = false; } setTransform(QTransform() .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) .rotate(tapAngle) .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F)); setHovered(false); update(); return animationIncomplete; } QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value) { if ((change == ItemSelectedHasChanged) && owner != nullptr) { if (value == true) { owner->getGame()->setActiveCard(this); owner->getPlayerMenu()->updateCardMenu(this); } else if (owner->getGameScene()->selectedItems().isEmpty()) { owner->getGame()->setActiveCard(nullptr); owner->getPlayerMenu()->updateCardMenu(nullptr); } } return AbstractCardItem::itemChange(change, value); }