[Game] Move graphics out of game and into game_graphics (#6928)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run

* [Game][Player] Pull out graphics_items out of player_logic

Took 25 seconds


Took 9 minutes

* [Game] Move graphics files into game_graphics

Took 1 minute

Took 2 minutes

Took 23 seconds

Took 1 minute

Took 2 seconds

* Include.

Took 4 minutes

Took 3 minutes

Took 4 minutes

Took 1 minute

Took 3 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-06-09 09:51:13 +02:00 committed by GitHub
parent cbfd286908
commit da4ba222c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
116 changed files with 208 additions and 198 deletions

View file

@ -0,0 +1,72 @@
#include "abstract_card_drag_item.h"
#include "../../client/settings/cache_settings.h"
#include "../z_values.h"
#include <QCursor>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
const QColor GHOST_MASK = QColor(255, 255, 255, 50);
AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
const QPointF &_hotSpot,
AbstractCardDragItem *parentDrag)
: QGraphicsItem(), item(_item), hotSpot(_hotSpot)
{
if (parentDrag) {
parentDrag->addChildDrag(this);
setZValue(ZValues::childDragZValue(hotSpot.x(), hotSpot.y()));
connect(parentDrag, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
} else {
hotSpot = QPointF{qBound(0.0, hotSpot.x(), CardDimensions::WIDTH_F - 1),
qBound(0.0, hotSpot.y(), CardDimensions::HEIGHT_F - 1)};
setCursor(Qt::ClosedHandCursor);
setZValue(ZValues::DRAG_ITEM);
}
if (item->getTapped()) {
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(90)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
}
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
prepareGeometryChange();
update();
});
connect(item, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
}
QPainterPath AbstractCardDragItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
void AbstractCardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
item->paint(painter, option, widget);
// adds a mask to the card so it looks like the card hasnt been placed yet
painter->fillPath(shape(), GHOST_MASK);
}
void AbstractCardDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
event->accept();
updatePosition(event->scenePos());
}
void AbstractCardDragItem::addChildDrag(AbstractCardDragItem *child)
{
childDrags << child;
}

View file

@ -0,0 +1,56 @@
/**
* @file abstract_card_drag_item.h
* @ingroup GameGraphicsCards
*/
//! \todo Document this file.
#ifndef ABSTRACTCARDDRAGITEM_H
#define ABSTRACTCARDDRAGITEM_H
#include "abstract_card_item.h"
class QGraphicsScene;
class CardZone;
class CardInfo;
class AbstractCardDragItem : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
protected:
AbstractCardItem *item;
QPointF hotSpot;
QList<AbstractCardDragItem *> childDrags;
public:
enum
{
Type = typeCardDrag
};
[[nodiscard]] int type() const override
{
return Type;
}
AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0);
[[nodiscard]] QRectF boundingRect() const override
{
return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
}
[[nodiscard]] QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] AbstractCardItem *getItem() const
{
return item;
}
[[nodiscard]] QPointF getHotSpot() const
{
return hotSpot;
}
void addChildDrag(AbstractCardDragItem *child);
virtual void updatePosition(const QPointF &cursorScenePos) = 0;
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif

View file

@ -0,0 +1,349 @@
#include "abstract_card_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../game_scene.h"
#include "../z_values.h"
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <algorithm>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id)
: ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0),
bgColor(Qt::transparent), isHovered(false), realZValue(0)
{
setCursor(Qt::OpenHandCursor);
setFlag(ItemIsSelectable);
setCacheMode(DeviceCoordinateCache);
connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); });
refreshCardInfo();
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
prepareGeometryChange();
update();
});
}
AbstractCardItem::~AbstractCardItem()
{
emit deleteCardInfoPopup(cardRef.name);
}
QRectF AbstractCardItem::boundingRect() const
{
return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
}
QPainterPath AbstractCardItem::shape() const
{
QPainterPath shape;
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
void AbstractCardItem::pixmapUpdated()
{
update();
emit sigPixmapUpdated();
}
void AbstractCardItem::refreshCardInfo()
{
exactCard = CardDatabaseManager::query()->getCard(cardRef);
if (!exactCard && !cardRef.name.isEmpty()) {
CardInfo::UiAttributes attributes = {.tableRow = -1};
auto info = CardInfo::newInstance(cardRef.name, "", true, {}, {}, {}, {}, attributes);
exactCard = ExactCard(info);
}
if (exactCard) {
connect(exactCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &AbstractCardItem::pixmapUpdated);
}
cacheBgColor();
update();
}
/**
* Convenience method to get the CardInfo of the exactCard
* @return A const reference to the CardInfo, or an empty CardInfo if card was null
*/
const CardInfo &AbstractCardItem::getCardInfo() const
{
return exactCard.getInfo();
}
void AbstractCardItem::setRealZValue(qreal _zValue)
{
realZValue = _zValue;
// During hover, zValue is overridden to HOVERED_CARD. Layout operations
// like reorganizeCards() call setRealZValue() on all cards including the
// hovered one — skip setZValue() here to avoid clobbering the override.
if (!isHovered) {
setZValue(_zValue);
}
}
QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
{
return QSizeF(painter->combinedTransform().map(QLineF(0, 0, boundingRect().width(), 0)).length(),
painter->combinedTransform().map(QLineF(0, 0, 0, boundingRect().height())).length());
}
void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle)
{
const int MAX_FONT_SIZE = SettingsCache::instance().getMaxFontSize();
const int fontSize = std::max(9, MAX_FONT_SIZE);
QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect());
int scale = resetPainterTransform(painter);
painter->translate(totalBoundingRect.width() / 2, totalBoundingRect.height() / 2);
painter->rotate(angle);
painter->translate(-translatedSize.width() / 2, -translatedSize.height() / 2);
QFont f;
f.setPixelSize(fontSize * scale);
painter->setFont(f);
}
void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle)
{
qreal scaleFactor = translatedSize.width() / boundingRect().width();
QPixmap translatedPixmap;
bool paintImage = true;
if (facedown || cardRef.name.isEmpty()) {
// never reveal card color, always paint the card back
CardPictureLoader::getCardBackPixmap(translatedPixmap, translatedSize.toSize());
} else {
// don't even spend time trying to load the picture if our size is too small
if (translatedSize.width() > 10) {
CardPictureLoader::getPixmap(translatedPixmap, exactCard, translatedSize.toSize());
if (translatedPixmap.isNull()) {
paintImage = false;
}
} else {
paintImage = false;
}
}
painter->save();
if (paintImage) {
painter->save();
painter->setClipPath(shape());
painter->drawPixmap(boundingRect(), translatedPixmap, QRectF({0, 0}, translatedPixmap.size()));
painter->restore();
} else {
painter->setBrush(bgColor);
painter->drawPath(shape());
}
if (translatedPixmap.isNull() || SettingsCache::instance().getDisplayCardNames() || facedown) {
painter->save();
transformPainter(painter, translatedSize, angle);
painter->setPen(Qt::white);
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
QString nameStr;
if (facedown) {
nameStr = "# " + QString::number(id);
} else {
QString prefix = "";
if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(id) + " ";
}
nameStr = prefix + cardRef.name;
}
painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor,
translatedSize.height() - 6 * scaleFactor),
Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere, nameStr);
painter->restore();
}
painter->restore();
}
void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
painter->save();
QSizeF translatedSize = getTranslatedSize(painter);
paintPicture(painter, translatedSize, tapAngle);
painter->setRenderHint(QPainter::Antialiasing, false);
if (isSelected() || isHovered) {
QPen pen;
if (isHovered) {
pen.setColor(Qt::yellow);
}
if (isSelected()) {
pen.setColor(Qt::red);
}
pen.setWidth(0); // Cosmetic pen
painter->setPen(pen);
painter->drawPath(shape());
}
painter->restore();
}
void AbstractCardItem::setCardRef(const CardRef &_cardRef)
{
if (cardRef == _cardRef) {
return;
}
emit deleteCardInfoPopup(cardRef.name);
if (exactCard) {
disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr);
}
cardRef = _cardRef;
refreshCardInfo();
}
void AbstractCardItem::setHovered(bool _hovered)
{
if (isHovered == _hovered) {
return;
}
if (_hovered) {
processHoverEvent();
} else {
// Mark the hovered card's current scene footprint dirty so overlapped
// sibling zones (e.g. StackZone) repaint after the card moves away.
if (scene()) {
scene()->update(sceneBoundingRect());
}
}
isHovered = _hovered;
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
setTransformOriginPoint(_hovered ? CardDimensions::WIDTH_HALF_F : 0, _hovered ? CardDimensions::HEIGHT_HALF_F : 0);
update();
}
void AbstractCardItem::setColor(const QString &_color)
{
color = _color;
cacheBgColor();
update();
}
void AbstractCardItem::cacheBgColor()
{
QChar colorChar;
if (color.isEmpty()) {
colorChar = exactCard.getInfo().getColorChar();
} else {
colorChar = color.at(0);
}
switch (colorChar.toLower().toLatin1()) {
case 'b':
bgColor = QColor(0, 0, 0);
break;
case 'u':
bgColor = QColor(0, 140, 180);
break;
case 'w':
bgColor = QColor(255, 250, 140);
break;
case 'r':
bgColor = QColor(230, 0, 0);
break;
case 'g':
bgColor = QColor(0, 160, 0);
break;
case 'm':
bgColor = QColor(250, 190, 30);
break;
default:
bgColor = QColor(230, 230, 230);
break;
}
}
void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
{
if (tapped == _tapped) {
return;
}
tapped = _tapped;
if (SettingsCache::instance().getTapAnimation() && canAnimate) {
static_cast<GameScene *>(scene())->registerAnimationItem(this);
} else {
tapAngle = tapped ? 90 : 0;
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(tapAngle)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
update();
}
}
void AbstractCardItem::setFaceDown(bool _facedown)
{
facedown = _facedown;
update();
}
void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->modifiers() & Qt::AltModifier) && event->button() == Qt::LeftButton) {
emit cardShiftClicked(cardRef.name);
} else if ((event->modifiers() & Qt::ControlModifier)) {
setSelected(!isSelected());
} else if (!isSelected()) {
scene()->clearSelection();
setSelected(true);
}
if (event->button() == Qt::LeftButton) {
setCursor(Qt::ClosedHandCursor);
} else if (event->button() == Qt::MiddleButton) {
emit showCardInfoPopup(event->screenPos(), cardRef);
}
event->accept();
}
void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::MiddleButton) {
emit deleteCardInfoPopup(cardRef.name);
}
// This function ensures the parent function doesn't mess around with our selection.
event->accept();
}
void AbstractCardItem::processHoverEvent()
{
emit hovered(this);
}
QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == ItemSelectedHasChanged) {
update();
return value;
} else {
return ArrowTarget::itemChange(change, value);
}
}

View file

@ -0,0 +1,137 @@
/**
* @file abstract_card_item.h
* @ingroup GameGraphicsCards
* @brief Base class for graphical card items, providing shared rendering, identity, and interaction logic.
*/
#ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H
#include "../card_dimensions.h"
#include "arrow_target.h"
#include "graphics_item_type.h"
#include <libcockatrice/card/printing/exact_card.h>
#include <libcockatrice/utility/card_ref.h>
class PlayerLogic;
class AbstractCardItem : public ArrowTarget
{
Q_OBJECT
protected:
ExactCard exactCard;
int id;
CardRef cardRef;
bool tapped;
bool facedown;
int tapAngle;
QString color;
QColor bgColor;
private:
bool isHovered;
qreal realZValue;
private slots:
void pixmapUpdated();
public slots:
void refreshCardInfo();
signals:
void hovered(AbstractCardItem *card);
void showCardInfoPopup(const QPoint &pos, const CardRef &cardRef);
void deleteCardInfoPopup(QString cardName);
void sigPixmapUpdated();
void cardShiftClicked(QString cardName);
void rightClicked(AbstractCardItem *card, QPoint screenPos);
void playSelected(AbstractCardItem *card);
void playSelectedFaceDown(AbstractCardItem *card);
void hideSelected(AbstractCardItem *card);
void selectionChanged(AbstractCardItem *card, bool selected);
public:
enum
{
Type = typeCard
};
int type() const override
{
return Type;
}
explicit AbstractCardItem(QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {},
PlayerLogic *_owner = nullptr,
int _id = -1);
~AbstractCardItem() override;
QRectF boundingRect() const override;
QPainterPath shape() const override;
QSizeF getTranslatedSize(QPainter *painter) const;
void paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
ExactCard getCard() const
{
return exactCard;
}
const CardInfo &getCardInfo() const;
int getId() const
{
return id;
}
void setId(int _id)
{
id = _id;
}
QString getName() const
{
return cardRef.name;
}
QString getProviderId() const
{
return cardRef.providerId;
}
void setCardRef(const CardRef &_cardRef);
CardRef getCardRef() const
{
return cardRef;
}
qreal getRealZValue() const
{
return realZValue;
}
void setRealZValue(qreal _zValue);
void setHovered(bool _hovered);
bool getIsHovered() const
{
return isHovered;
}
QString getColor() const
{
return color;
}
void setColor(const QString &_color);
bool getTapped() const
{
return tapped;
}
void setTapped(bool _tapped, bool canAnimate = false);
bool getFaceDown() const
{
return facedown;
}
void setFaceDown(bool _facedown);
void processHoverEvent();
void deleteCardInfoPopup()
{
emit deleteCardInfoPopup(cardRef.name);
}
protected:
void transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle);
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
void cacheBgColor();
};
#endif

View file

@ -0,0 +1,229 @@
#include "abstract_counter.h"
#include "../../client/settings/cache_settings.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game_graphics/board/translate_counter_name.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include <QAction>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QKeyEvent>
#include <QMenu>
#include <QString>
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
#include <libcockatrice/utility/expression.h>
AbstractCounter::AbstractCounter(CounterState *state,
PlayerLogic *_player,
bool _shownInCounterArea,
bool _useNameForShortcut,
QGraphicsItem *parent)
: QGraphicsItem(parent), player(_player), id(state->getId()), name(state->getName()), value(state->getValue()),
color(state->getColor()), radius(state->getRadius()), useNameForShortcut(_useNameForShortcut),
shownInCounterArea(_shownInCounterArea)
{
setAcceptHoverEvents(true);
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) {
value = newValue;
update();
});
if (player->getPlayerInfo()->getLocalOrJudge()) {
menu = new TearOffMenu(TranslateCounterName::getDisplayName(state->getName()));
aSet = new QAction(this);
connect(aSet, &QAction::triggered, this, &AbstractCounter::setCounter);
menu->addAction(aSet);
menu->addSeparator();
for (int i = 10; i >= -10; --i) {
if (i == 0) {
menu->addSeparator();
continue;
}
auto *a = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this);
if (i == -1) {
aDec = a;
}
if (i == 1) {
aInc = a;
}
a->setData(i);
connect(a, &QAction::triggered, this, &AbstractCounter::incrementCounter);
menu->addAction(a);
}
} else {
menu = nullptr;
}
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&AbstractCounter::refreshShortcuts);
refreshShortcuts();
retranslateUi();
}
AbstractCounter::~AbstractCounter()
{
delete menu;
}
void AbstractCounter::delCounter()
{
if (dialogSemaphore) {
deleteAfterDialog = true;
} else {
deleteLater();
}
}
void AbstractCounter::retranslateUi()
{
if (aSet) {
aSet->setText(tr("&Set counter..."));
}
}
void AbstractCounter::setShortcutsActive()
{
if (!menu || !player->getPlayerInfo()->getLocal()) {
return;
}
ShortcutsSettings &sc = SettingsCache::instance().shortcuts();
shortcutActive = true;
if (name == "life") {
aSet->setShortcuts(sc.getShortcut("Player/aSet"));
aDec->setShortcuts(sc.getShortcut("Player/aDec"));
aInc->setShortcuts(sc.getShortcut("Player/aInc"));
} else if (useNameForShortcut) {
aSet->setShortcuts(sc.getShortcut("Player/aSetCounter_" + name));
aDec->setShortcuts(sc.getShortcut("Player/aDecCounter_" + name));
aInc->setShortcuts(sc.getShortcut("Player/aIncCounter_" + name));
}
}
void AbstractCounter::setShortcutsInactive()
{
if (!menu) {
return;
}
shortcutActive = false;
if (name == "life" || useNameForShortcut) {
aSet->setShortcut(QKeySequence());
aDec->setShortcut(QKeySequence());
aInc->setShortcut(QKeySequence());
}
}
void AbstractCounter::refreshShortcuts()
{
if (shortcutActive) {
setShortcutsActive();
}
}
void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (!isUnderMouse() || !player->getPlayerInfo()->getLocalOrJudge()) {
event->ignore();
return;
}
if (event->button() == Qt::MiddleButton || QApplication::keyboardModifiers() & Qt::ShiftModifier) {
if (menu) {
menu->exec(event->screenPos());
}
} else {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(event->button() == Qt::LeftButton ? 1 : -1);
player->getPlayerActions()->sendGameCommand(cmd);
}
event->accept();
}
void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
hovered = true;
update();
}
void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
hovered = false;
update();
}
void AbstractCounter::incrementCounter()
{
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(static_cast<QAction *>(sender())->data().toInt());
player->getPlayerActions()->sendGameCommand(cmd);
}
void AbstractCounter::setCounter()
{
QWidget *parent = nullptr;
if (auto *view = scene() ? scene()->views().value(0) : nullptr) {
parent = view->window();
}
dialogSemaphore = true;
AbstractCounterDialog dlg(name, QString::number(value), parent);
const int ok = dlg.exec();
dialogSemaphore = false;
if (deleteAfterDialog) {
deleteLater();
return;
}
if (!ok) {
return;
}
Expression exp(value);
Command_SetCounter cmd;
cmd.set_counter_id(id);
cmd.set_value(static_cast<int>(exp.parse(dlg.textValue())));
player->getPlayerActions()->sendGameCommand(cmd);
}
AbstractCounterDialog::AbstractCounterDialog(const QString &name, const QString &value, QWidget *parent)
: QInputDialog(parent)
{
setWindowTitle(tr("Set counter"));
setLabelText(tr("New value for counter '%1':").arg(name));
setTextValue(value);
qApp->installEventFilter(this);
}
bool AbstractCounterDialog::eventFilter(QObject *obj, QEvent *event)
{
Q_UNUSED(obj);
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch (keyEvent->key()) {
case Qt::Key_Up:
changeValue(+1);
return true;
case Qt::Key_Down:
changeValue(-1);
return true;
}
}
return false;
}
void AbstractCounterDialog::changeValue(int diff)
{
bool ok;
int curValue = textValue().toInt(&ok);
if (!ok) {
return;
}
curValue += diff;
setTextValue(QString::number(curValue));
}

View file

@ -0,0 +1,109 @@
/**
* @file abstract_counter.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef COUNTER_H
#define COUNTER_H
#include "../../game/board/counter_state.h"
#include "../../interface/widgets/menus/tearoff_menu.h"
#include "../player/menu/abstract_player_component.h"
#include <QGraphicsItem>
#include <QInputDialog>
class PlayerLogic;
class QAction;
class QKeyEvent;
class QMenu;
class QString;
class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPlayerComponent
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
protected:
PlayerLogic *player;
int id;
QString name;
int value;
QColor color;
int radius;
bool hovered = false;
bool useNameForShortcut;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private:
QAction *aSet = nullptr, *aDec = nullptr, *aInc = nullptr;
TearOffMenu *menu = nullptr;
bool dialogSemaphore = false;
bool deleteAfterDialog = false;
bool shownInCounterArea;
bool shortcutActive = false;
private slots:
void refreshShortcuts();
void incrementCounter();
void setCounter();
public:
AbstractCounter(CounterState *state,
PlayerLogic *player,
bool shownInCounterArea,
bool useNameForShortcut = false,
QGraphicsItem *parent = nullptr);
~AbstractCounter() override;
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
void delCounter();
QMenu *getMenu() const
{
return menu;
}
int getId() const
{
return id;
}
QString getName() const
{
return name;
}
QColor getColor() const
{
return color;
}
int getRadius() const
{
return radius;
}
int getValue() const
{
return value;
}
bool getShownInCounterArea() const
{
return shownInCounterArea;
}
};
class AbstractCounterDialog : public QInputDialog
{
Q_OBJECT
public:
AbstractCounterDialog(const QString &name, const QString &value, QWidget *parent = nullptr);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
void changeValue(int diff);
};
#endif

View file

@ -0,0 +1,392 @@
#define _USE_MATH_DEFINES
#include "arrow_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../player/player_target.h"
#include "../z_values.h"
#include "../zones/card_zone.h"
#include "card_item.h"
#include <QDebug>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QtMath>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem)
: data(std::move(_data)), startItem(_startItem), targetItem(_targetItem)
{
setZValue(ZValues::ARROWS);
auto doUpdate = [this]() {
if (startItem && targetItem) {
updatePath();
}
};
if (startItem) {
connect(startItem, &ArrowTarget::scenePositionChanged, this, doUpdate);
connect(startItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed);
}
if (targetItem) {
connect(targetItem, &ArrowTarget::scenePositionChanged, this, doUpdate);
connect(targetItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed);
}
if (startItem && targetItem) {
updatePath();
}
}
void ArrowItem::onTargetDestroyed()
{
emit requestDeletion(data->creatorId, data->id);
}
void ArrowItem::delArrow()
{
if (targetItem) {
targetItem->setBeingPointedAt(false);
}
deleteLater();
}
void ArrowItem::updatePath()
{
if (!targetItem) {
return;
}
QPointF endPoint = targetItem->mapToScene(
QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2));
updatePath(endPoint);
}
void ArrowItem::updatePath(const QPointF &endPoint)
{
const double arrowWidth = 15.0;
const double headWidth = 40.0;
const double headLength =
headWidth / qPow(2, 0.5); // aka headWidth / sqrt (2) but this produces a compile error with MSVC++
const double phi = 15;
if (!startItem) {
return;
}
QPointF startPoint =
startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2));
QLineF line(startPoint, endPoint);
qreal lineLength = line.length();
prepareGeometryChange();
if (lineLength < 30) {
path = QPainterPath();
} else {
QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength);
QPainterPath centerLine;
centerLine.moveTo(0, 0);
centerLine.quadTo(c, QPointF(lineLength, 0));
double percentage = 1 - headLength / lineLength;
QPointF arrowBodyEndPoint = centerLine.pointAtPercent(percentage);
QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(percentage + 0.001));
qreal alpha = testLine.angle() - 90;
QPointF endPoint1 =
arrowBodyEndPoint + arrowWidth / 2 * QPointF(qCos(alpha * M_PI / 180), -qSin(alpha * M_PI / 180));
QPointF endPoint2 =
arrowBodyEndPoint + arrowWidth / 2 * QPointF(-qCos(alpha * M_PI / 180), qSin(alpha * M_PI / 180));
QPointF point1 =
endPoint1 + (headWidth - arrowWidth) / 2 * QPointF(qCos(alpha * M_PI / 180), -qSin(alpha * M_PI / 180));
QPointF point2 =
endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-qCos(alpha * M_PI / 180), qSin(alpha * M_PI / 180));
path = QPainterPath(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
path.quadTo(c, endPoint1);
path.lineTo(point1);
path.lineTo(QPointF(lineLength, 0));
path.lineTo(point2);
path.lineTo(endPoint2);
path.quadTo(c, arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
path.lineTo(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
}
setPos(startPoint);
setTransform(QTransform().rotate(-line.angle()));
}
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QColor paintColor(data->color);
if (fullColor) {
paintColor.setAlpha(200);
} else {
paintColor.setAlpha(150);
}
painter->setBrush(paintColor);
painter->drawPath(path);
}
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (!data->isLocalCreator) {
event->ignore();
return;
}
for (auto *item : scene()->items(event->scenePos())) {
if (qgraphicsitem_cast<CardItem *>(item)) {
event->ignore();
return;
}
}
event->accept();
if (event->button() == Qt::RightButton) {
emit requestDeletion(data->creatorId, data->id);
}
}
// ArrowDragItem
ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _owner->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = _color}),
_startItem,
nullptr),
player(_owner), deleteInPhase(_deleteInPhase)
{
}
void ArrowDragItem::addChildArrow(ArrowDragItem *child)
{
childArrows.append(child);
}
void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (targetLocked || !startItem) {
return;
}
const QPointF endPos = event->scenePos();
ArrowTarget *cursorItem = nullptr;
qreal cursorItemZ = -1;
for (auto *item : scene()->items(endPos)) {
ArrowTarget *candidate = nullptr;
if (auto *card = qgraphicsitem_cast<CardItem *>(item)) {
candidate = card;
} else if (auto *pt = qgraphicsitem_cast<PlayerTarget *>(item)) {
candidate = pt;
}
if (candidate && candidate->zValue() > cursorItemZ) {
cursorItem = candidate;
cursorItemZ = candidate->zValue();
}
}
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(); });
}
}
targetItem ? updatePath() : updatePath(endPos);
update();
for (auto *child : childArrows) {
child->mouseMoveEvent(event);
}
}
void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem) {
return;
}
if (targetItem && targetItem != startItem) {
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
// For now, we can safely assume that the start item is always a card.
// The target item can be a player as well.
if (!startCard) {
delArrow();
return;
}
CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(data->color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId());
if (auto *targetCard = qgraphicsitem_cast<CardItem *>(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 if (auto *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(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
if (startZone->getName() == ZoneNames::HAND) {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = SettingsCache::instance().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK))) {
cmd.set_start_zone(ZoneNames::STACK);
} else {
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
}
if (deleteInPhase != 0) {
cmd.set_delete_in_phase(deleteInPhase);
}
player->getPlayerActions()->sendGameCommand(cmd);
}
delArrow();
for (auto *child : childArrows) {
child->mouseReleaseEvent(event);
}
}
// ArrowAttachItem
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
: ArrowItem(
QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _startItem->getOwner()->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = Qt::green}),
_startItem,
nullptr),
player(_startItem->getOwner())
{
}
void ArrowAttachItem::addChildArrow(ArrowAttachItem *child)
{
childArrows.append(child);
}
void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (targetLocked || !startItem) {
return;
}
const QPointF endPos = event->scenePos();
ArrowTarget *cursorItem = nullptr;
qreal cursorItemZ = -1;
for (auto *item : scene()->items(endPos)) {
if (auto *card = qgraphicsitem_cast<CardItem *>(item)) {
if (card->zValue() > cursorItemZ) {
cursorItem = card;
cursorItemZ = card->zValue();
}
}
}
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(); });
}
}
targetItem ? updatePath() : updatePath(endPos);
update();
for (auto *child : childArrows) {
child->mouseMoveEvent(event);
}
}
void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem) {
return;
}
// Attaching could move startItem under the current cursor position, causing all children to retarget to it right
// before they are processed. Prevent that.
for (auto *child : childArrows) {
child->setTargetLocked(true);
}
if (targetItem && targetItem != startItem) {
auto *startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (startCard && targetCard) {
attachCards(startCard, targetCard);
}
}
delArrow();
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);
}

View file

@ -0,0 +1,109 @@
#ifndef ARROWITEM_H
#define ARROWITEM_H
#include "../../game/board/arrow_data.h"
#include "arrow_target.h"
#include <QGraphicsItem>
#include <QPointer>
#include <QSharedPointer>
class CardItem;
class QGraphicsSceneMouseEvent;
class PlayerLogic;
class ArrowItem : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
signals:
void requestDeletion(int creatorId, int id);
private:
QPainterPath path;
protected:
QSharedPointer<const ArrowData> data;
QPointer<ArrowTarget> startItem;
QPointer<ArrowTarget> targetItem;
bool targetLocked = false;
bool fullColor = true;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public:
ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem);
void onTargetDestroyed();
void delArrow();
void updatePath();
void updatePath(const QPointF &endPoint);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QRectF boundingRect() const override
{
return path.boundingRect();
}
[[nodiscard]] QPainterPath shape() const override
{
return path;
}
[[nodiscard]] int getId() const
{
return data->id;
}
[[nodiscard]] int getCreatorId() const
{
return data->creatorId;
}
[[nodiscard]] ArrowTarget *getStartItem() const
{
return startItem;
}
[[nodiscard]] ArrowTarget *getTargetItem() const
{
return targetItem;
}
void setTargetLocked(bool _targetLocked)
{
targetLocked = _targetLocked;
}
};
class ArrowDragItem : public ArrowItem
{
Q_OBJECT
private:
PlayerLogic *player;
int deleteInPhase;
QList<ArrowDragItem *> childArrows;
QMetaObject::Connection positionConnection;
public:
ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase);
void addChildArrow(ArrowDragItem *child);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
class ArrowAttachItem : public ArrowItem
{
Q_OBJECT
private:
PlayerLogic *player;
QList<ArrowAttachItem *> childArrows;
QMetaObject::Connection positionConnection;
void attachCards(CardItem *startCard, const CardItem *targetCard);
public:
explicit ArrowAttachItem(ArrowTarget *_startItem);
void addChildArrow(ArrowAttachItem *child);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif

View file

@ -0,0 +1,23 @@
#include "arrow_target.h"
#include "../../game/player/player_logic.h"
#include "arrow_item.h"
ArrowTarget::ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent) : AbstractGraphicsItem(parent), owner(_owner)
{
setFlag(ItemSendsScenePositionChanges);
}
void ArrowTarget::setBeingPointedAt(bool _beingPointedAt)
{
beingPointedAt = _beingPointedAt;
update();
}
QVariant ArrowTarget::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemScenePositionHasChanged) {
emit scenePositionChanged();
}
return AbstractGraphicsItem::itemChange(change, value);
}

View file

@ -0,0 +1,47 @@
/**
* @file arrow_target.h
* @ingroup GameGraphics
*/
//! \todo Document this file.
#ifndef ARROWTARGET_H
#define ARROWTARGET_H
#include "abstract_graphics_item.h"
#include <QList>
class PlayerLogic;
class ArrowItem;
class ArrowTarget : public AbstractGraphicsItem
{
Q_OBJECT
protected:
PlayerLogic *owner;
private:
bool beingPointedAt = false;
signals:
void scenePositionChanged();
public:
explicit ArrowTarget(PlayerLogic *_owner, QGraphicsItem *parent = nullptr);
~ArrowTarget() override = default;
[[nodiscard]] PlayerLogic *getOwner() const
{
return owner;
}
void setBeingPointedAt(bool _beingPointedAt);
[[nodiscard]] bool getBeingPointedAt() const
{
return beingPointedAt;
}
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
};
#endif

View file

@ -0,0 +1,144 @@
#include "card_drag_item.h"
#include "../game_scene.h"
#include "../zones/card_zone.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "card_item.h"
#include <QCursor>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
CardDragItem::CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
bool _forceFaceDown,
AbstractCardDragItem *parentDrag)
: AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), forceFaceDown(_forceFaceDown), occupied(false),
currentZone(0)
{
}
void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
AbstractCardDragItem::paint(painter, option, widget);
if (occupied) {
painter->fillPath(shape(), QColor(200, 0, 0, 100));
}
}
void CardDragItem::updatePosition(const QPointF &cursorScenePos)
{
QList<QGraphicsItem *> colliding =
scene()->items(cursorScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder,
static_cast<GameScene *>(scene())->getViewTransform());
CardZone *cardZone = 0;
ZoneViewZone *zoneViewZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--) {
CardZone *temp = qgraphicsitem_cast<CardZone *>(colliding.at(i));
if (!cardZone) {
cardZone = temp;
}
if (!zoneViewZone) {
zoneViewZone = qobject_cast<ZoneViewZone *>(temp);
}
}
CardZone *cursorZone = 0;
if (zoneViewZone) {
cursorZone = zoneViewZone;
} else if (cardZone) {
cursorZone = cardZone;
}
// Always update the current zone, even if its null, to cancel the drag
// instead of dropping cards into an non-intuitive location.
currentZone = cursorZone;
if (!cursorZone) {
// Avoid the cards getting stuck visually when not over
// any zone.
QPointF newPos = cursorScenePos - hotSpot;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++) {
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos);
}
return;
}
QPointF zonePos = currentZone->scenePos();
QPointF cursorPosInZone = cursorScenePos - zonePos;
// If we are on a Table, we center the card around the cursor, because we
// snap it into place and no longer see it being dragged.
//
// For other zones (where we do display the card under the cursor), we use
// the hotspot to feel like the card was dragged at the corresponding
// position.
TableZone *tableZone = qobject_cast<TableZone *>(cursorZone);
QPointF closestGridPoint;
if (tableZone) {
closestGridPoint = tableZone->closestGridPoint(cursorPosInZone);
} else {
closestGridPoint = cursorPosInZone - hotSpot;
}
QPointF newPos = zonePos + closestGridPoint;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++) {
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos);
bool newOccupied = false;
TableZone *table = qobject_cast<TableZone *>(cursorZone);
if (table) {
if (table->getCardFromCoords(closestGridPoint)) {
newOccupied = true;
}
}
if (newOccupied != occupied) {
occupied = newOccupied;
update();
}
}
}
void CardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
QGraphicsScene *sc = scene();
QPointF sp = pos();
sc->removeItem(this);
QList<CardDragItem *> dragItemList;
CardZoneLogic *startZone = static_cast<CardItem *>(item)->getZone();
if (currentZone && !(static_cast<CardItem *>(item)->getAttachedTo() && (startZone == currentZone->getLogic()))) {
if (!occupied) {
dragItemList.append(this);
}
for (int i = 0; i < childDrags.size(); i++) {
CardDragItem *c = static_cast<CardDragItem *>(childDrags[i]);
if (!occupied &&
!(static_cast<CardItem *>(c->item)->getAttachedTo() && (startZone == currentZone->getLogic())) &&
!c->occupied) {
dragItemList.append(c);
}
sc->removeItem(c);
}
}
if (currentZone) {
currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint());
}
event->accept();
}

View file

@ -0,0 +1,44 @@
/**
* @file card_drag_item.h
* @ingroup GameGraphicsCards
*/
//! \todo Document this file.
#ifndef CARDDRAGITEM_H
#define CARDDRAGITEM_H
#include "abstract_card_drag_item.h"
class CardItem;
class CardDragItem : public AbstractCardDragItem
{
Q_OBJECT
private:
int id;
bool forceFaceDown;
bool occupied;
CardZone *currentZone;
public:
CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
bool _forceFaceDown,
AbstractCardDragItem *parentDrag = 0);
int getId() const
{
return id;
}
bool isForceFaceDown() const
{
return forceFaceDown;
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void updatePosition(const QPointF &cursorScenePos) override;
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif

View file

@ -0,0 +1,541 @@
#include "card_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../game/phase.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game/zones/view_zone_logic.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../game_scene.h"
#include "../zones/table_zone.h"
#include "../zones/view_zone.h"
#include "arrow_item.h"
#include "card_drag_item.h"
#include <../../client/settings/card_counter_settings.h>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QMenu>
#include <QPainter>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
CardItem::CardItem(PlayerLogic *_owner,
QGraphicsItem *parent,
const CardRef &cardRef,
int _cardid,
CardZoneLogic *_zone)
: AbstractCardItem(parent, cardRef, _owner, _cardid), state(new CardState(this, _zone)), dragItem(nullptr)
{
owner->addCard(this);
connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, this, [this](int counterId) {
if (state->getCounters().contains(counterId)) {
update();
}
});
}
void CardItem::prepareDelete()
{
if (owner != nullptr) {
if (owner->getGame()->getActiveCard() == this) {
emit owner->requestCardMenuUpdate(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 (state->getAttachedTo() != nullptr) {
state->getAttachedTo()->removeAttachedCard(this);
state->setAttachedTo(nullptr);
}
}
void CardItem::deleteLater()
{
prepareDelete();
if (scene()) {
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
AbstractCardItem::deleteLater();
}
void CardItem::setZone(CardZoneLogic *_zone)
{
state->setZone(_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<int, int> counterIterator(state->getCounters());
while (counterIterator.hasNext()) {
counterIterator.next();
QColor _color = cardCounterSettings.color(counterIterator.key());
paintNumberEllipse(counterIterator.value(), 14, _color, i, state->getCounters().size(), painter);
++i;
}
QSizeF translatedSize = getTranslatedSize(painter);
qreal scaleFactor = translatedSize.width() / boundingRect().width();
if (!state->getPT().isEmpty()) {
painter->save();
transformPainter(painter, translatedSize, tapAngle);
if (!getFaceDown() && state->getPT() == 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, state->getPT());
painter->restore();
}
if (!state->getAnnotation().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, state->getAnnotation());
painter->restore();
}
if (getBeingPointedAt()) {
painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100)));
}
if (state->getDoesntUntap()) {
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)
{
state->setAttacking(_attacking);
update();
}
void CardItem::setCounter(int _id, int _value)
{
state->setCounter(_id, _value);
update();
}
void CardItem::setAnnotation(const QString &_annotation)
{
state->setAnnotation(_annotation);
update();
}
void CardItem::setDoesntUntap(bool _doesntUntap)
{
state->setDoesntUntap(_doesntUntap);
update();
}
void CardItem::setPT(const QString &_pt)
{
state->setPT(_pt);
update();
}
void CardItem::setAttachedTo(CardItem *_attachedTo)
{
if (state->getAttachedTo() != nullptr) {
state->getAttachedTo()->removeAttachedCard(this);
}
gridPoint.setX(-1);
state->setAttachedTo(_attachedTo);
if (state->getAttachedTo() != 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 (state->getAttachedTo()->getZone() == nullptr) {
deleteLater();
} else {
emit state->getAttachedTo()->getZone()->cardAdded(this);
state->getAttachedTo()->addAttachedCard(this);
if (state->getZone() != state->getAttachedTo()->getZone()) {
state->getAttachedTo()->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 (state->getZone() == nullptr) {
deleteLater();
} else {
emit state->getZone()->cardAdded(this);
}
}
if (state->getZone() != nullptr) {
state->getZone()->reorganizeCards();
}
}
/**
* @brief Resets the fields that should be reset after a zone transition
*/
void CardItem::resetState(bool keepAnnotations)
{
state->resetState(keepAnnotations);
attachedCards.clear();
setTapped(false, false);
setDoesntUntap(false);
if (scene()) {
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
update();
}
void CardItem::processCardInfo(const ServerInfo_Card &_info)
{
state->clearCounters();
const int counterListSize = _info.counter_list_size();
for (int i = 0; i < counterListSize; ++i) {
const ServerInfo_CardCounter &counterInfo = _info.counter_list(i);
state->insertCounter(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();
PlayerLogic *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<CardItem *>(item);
if (card == nullptr || card == this) {
continue;
}
if (card->getZone() != state->getZone()) {
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<CardItem *>(item);
if (card == nullptr) {
continue;
}
if (card->getZone() != state->getZone()) {
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<const ZoneViewZoneLogic *>(state->getZone())) {
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<CardItem *>(item);
if ((card == this) || (card->getZone() != state->getZone())) {
continue;
}
++childIndex;
QPointF childPos;
if (state->getZone()->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<TableZoneLogic *>(state->getZone());
if (tz) {
emit tz->toggleTapped();
} else {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
if (faceDown) {
emit playSelectedFaceDown(this);
} else {
emit playSelected(this);
}
} else {
state->getZone()->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<ZoneViewZoneLogic *>(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(state->getZone())) {
if (SettingsCache::instance().getClickPlaysAllSelected()) {
emit hideSelected(this);
} else {
state->getZone()->removeCard(this);
}
} else {
playCard(shiftHeld);
}
}
void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::RightButton && owner != nullptr) {
emit rightClicked(this, event->screenPos());
return;
}
if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
(!SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
}
if (owner != nullptr) {
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) {
bool selected = value.toBool();
if (selected) {
owner->getGame()->setActiveCard(this);
}
emit selectionChanged(this, selected);
}
return AbstractCardItem::itemChange(change, value);
}

View file

@ -0,0 +1,175 @@
/**
* @file card_item.h
* @ingroup GameGraphicsCards
*/
//! \todo Document this file.
#ifndef CARDITEM_H
#define CARDITEM_H
#include "../../game/board/card_state.h"
#include "../../game/zones/card_zone_logic.h"
#include "abstract_card_item.h"
#include <libcockatrice/network/server/remote/game/server_card.h>
#include <libcockatrice/utility/trice_limits.h>
class CardDatabase;
class CardDragItem;
class CardZone;
class ServerInfo_Card;
class PlayerLogic;
class QAction;
class QColor;
const int ROTATION_DEGREES_PER_FRAME = 10;
class CardItem : public AbstractCardItem
{
Q_OBJECT
private:
CardState *state;
QPoint gridPoint;
CardDragItem *dragItem;
QList<CardItem *> attachedCards;
void prepareDelete();
void handleClickedToPlay(bool shiftHeld);
public slots:
void deleteLater();
public:
enum
{
Type = typeCard
};
[[nodiscard]] int type() const override
{
return Type;
}
explicit CardItem(PlayerLogic *_owner,
QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {},
int _cardid = -1,
CardZoneLogic *_zone = nullptr);
void retranslateUi();
[[nodiscard]] CardState *getState() const
{
return state;
}
[[nodiscard]] CardZoneLogic *getZone() const
{
return state->getZone();
}
void setZone(CardZoneLogic *_zone);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QPoint getGridPoint() const
{
return gridPoint;
}
void setGridPoint(const QPoint &_gridPoint)
{
gridPoint = _gridPoint;
}
[[nodiscard]] QPoint getGridPos() const
{
return gridPoint;
}
[[nodiscard]] PlayerLogic *getOwner() const
{
return owner;
}
void setOwner(PlayerLogic *_owner)
{
owner = _owner;
}
[[nodiscard]] bool getAttacking() const
{
return state->getAttacking();
}
void setAttacking(bool _attacking);
[[nodiscard]] const QMap<int, int> &getCounters() const
{
return state->getCounters();
}
void setCounter(int _id, int _value);
[[nodiscard]] QString getAnnotation() const
{
return state->getAnnotation();
}
void setAnnotation(const QString &_annotation);
[[nodiscard]] bool getDoesntUntap() const
{
return state->getDoesntUntap();
}
void setDoesntUntap(bool _doesntUntap);
[[nodiscard]] QString getPT() const
{
return state->getPT();
}
void setPT(const QString &_pt);
[[nodiscard]] bool getDestroyOnZoneChange() const
{
return state->getDestroyOnZoneChange();
}
void setDestroyOnZoneChange(bool _destroy)
{
state->setDestroyOnZoneChange(_destroy);
}
[[nodiscard]] CardItem *getAttachedTo() const
{
return state->getAttachedTo();
}
void setAttachedTo(CardItem *_attachedTo);
void addAttachedCard(CardItem *card)
{
attachedCards.append(card);
}
void removeAttachedCard(CardItem *card)
{
attachedCards.removeOne(card);
}
[[nodiscard]] const QList<CardItem *> &getAttachedCards() const
{
return attachedCards;
}
void resetState(bool keepAnnotations = false);
void processCardInfo(const ServerInfo_Card &_info);
bool animationEvent();
CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown);
void deleteDragItem();
void drawArrow(const QColor &arrowColor);
void drawAttachArrow();
void playCard(bool faceDown);
/**
* @brief Parses a string representing a p/t in order to extract the values from it.
*
* If the string contains '/', the string will be split at the '/' and each side will be parsed separately,
* which means the result list will have two elements.
*
* If '/' is not found, then the entire string is parsed together, which means the result list will
* have a single element.
*
* If either side of the split is empty, there will also only be a single element in the result list.
*
* This function will attempt to parse each substring as an int first, handling plus and minus prefixes.
* If successful, it will put the parsed value into the QVariant as an int.
* If failed, it will just put the substring into the QVariant as a QString.
*
* @param pt The p/t string
* @return A QVariantList that can contain one or two elements, where each QVariant can be either int or QString
*/
static QVariantList parsePT(const QString &pt);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
};
#endif

View file

@ -0,0 +1,39 @@
#include "counter_general.h"
#include "../../interface/pixel_map_generator.h"
#include "abstract_graphics_item.h"
#include <QPainter>
GeneralCounter::GeneralCounter(CounterState *state, PlayerLogic *player, bool useNameForShortcut, QGraphicsItem *parent)
: AbstractCounter(state, player, true, useNameForShortcut, parent)
{
setCacheMode(DeviceCoordinateCache);
}
QRectF GeneralCounter::boundingRect() const
{
return QRectF(0, 0, radius * 2, radius * 2);
}
void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QRectF mapRect = painter->combinedTransform().mapRect(boundingRect());
int translatedHeight = mapRect.size().height();
qreal scaleFactor = translatedHeight / boundingRect().height();
QPixmap pixmap = CounterPixmapGenerator::generatePixmap(translatedHeight, name, hovered);
painter->save();
resetPainterTransform(painter);
painter->drawPixmap(QPoint(0, 0), pixmap);
if (value) {
QFont f("Serif");
f.setPixelSize(qMax((int)(radius * scaleFactor), 10));
f.setWeight(QFont::Bold);
painter->setPen(Qt::black);
painter->setFont(f);
painter->drawText(mapRect, Qt::AlignCenter, QString::number(value));
}
painter->restore();
}

View file

@ -0,0 +1,25 @@
/**
* @file counter_general.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef COUNTER_GENERAL_H
#define COUNTER_GENERAL_H
#include "abstract_counter.h"
class GeneralCounter : public AbstractCounter
{
Q_OBJECT
public:
GeneralCounter(CounterState *state,
PlayerLogic *player,
bool useNameForShortcut = false,
QGraphicsItem *parent = nullptr);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
};
#endif

View file

@ -0,0 +1,11 @@
#include "translate_counter_name.h"
const QMap<QString, QString> TranslateCounterName::translated = {
{"life", QT_TRANSLATE_NOOP("TranslateCounterName", "Life")},
{"w", QT_TRANSLATE_NOOP("TranslateCounterName", "White")},
{"u", QT_TRANSLATE_NOOP("TranslateCounterName", "Blue")},
{"b", QT_TRANSLATE_NOOP("TranslateCounterName", "Black")},
{"r", QT_TRANSLATE_NOOP("TranslateCounterName", "Red")},
{"g", QT_TRANSLATE_NOOP("TranslateCounterName", "Green")},
{"x", QT_TRANSLATE_NOOP("TranslateCounterName", "Colorless")},
{"storm", QT_TRANSLATE_NOOP("TranslateCounterName", "Other")}};

View file

@ -0,0 +1,30 @@
/**
* @file translate_counter_name.h
* @ingroup GameGraphicsPlayers
*/
//! \todo Document this file.
#ifndef TRANSLATECOUNTERNAME_H
#define TRANSLATECOUNTERNAME_H
#include <QString>
#include <QtCore>
class TranslateCounterName
{
Q_DECLARE_TR_FUNCTIONS(TranslateCounterName)
static const QMap<QString, QString> translated;
public:
static QString getDisplayName(const QString &name)
{
if (translated.contains(name)) {
return tr(translated[name].toLatin1());
} else {
return name;
}
}
};
#endif // TRANSLATECOUNTERNAME_H