mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
* refactor: extract CARD_HEIGHT to shared CardDimensions header Move duplicated CARD_WIDTH/CARD_HEIGHT constants to card_dimensions.h. Fixed documentation in z_value_layer_manager.h. * WIDTH_F used directly instead of casting * Improved consistency and added missing newlines at end of files
383 lines
14 KiB
C++
383 lines
14 KiB
C++
#include "table_zone.h"
|
|
|
|
#include "../../client/settings/cache_settings.h"
|
|
#include "../../interface/theme_manager.h"
|
|
#include "../board/arrow_item.h"
|
|
#include "../board/card_drag_item.h"
|
|
#include "../board/card_item.h"
|
|
#include "../player/player.h"
|
|
#include "../player/player_actions.h"
|
|
#include "../z_values.h"
|
|
#include "logic/table_zone_logic.h"
|
|
|
|
#include <QGraphicsScene>
|
|
#include <QPainter>
|
|
#include <libcockatrice/card/card_info.h>
|
|
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
|
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
|
|
|
|
const QColor TableZone::BACKGROUND_COLOR = QColor(100, 100, 100);
|
|
const QColor TableZone::FADE_MASK = QColor(0, 0, 0, 80);
|
|
const QColor TableZone::GRADIENT_COLOR = QColor(255, 255, 255, 150);
|
|
const QColor TableZone::GRADIENT_COLORLESS = QColor(255, 255, 255, 0);
|
|
|
|
TableZone::TableZone(TableZoneLogic *_logic, QGraphicsItem *parent) : SelectZone(_logic, parent), active(false)
|
|
{
|
|
connect(_logic, &TableZoneLogic::contentSizeChanged, this, &TableZone::resizeToContents);
|
|
connect(_logic, &TableZoneLogic::toggleTapped, this, &TableZone::toggleTapped);
|
|
connect(themeManager, &ThemeManager::themeChanged, this, &TableZone::updateBg);
|
|
connect(&SettingsCache::instance(), &SettingsCache::invertVerticalCoordinateChanged, this,
|
|
&TableZone::reorganizeCards);
|
|
|
|
updateBg();
|
|
|
|
height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CardDimensions::HEIGHT + (TABLEROWS - 1) * PADDING_Y;
|
|
width = MIN_WIDTH;
|
|
currentMinimumWidth = width;
|
|
|
|
setCacheMode(DeviceCoordinateCache);
|
|
setAcceptHoverEvents(true);
|
|
}
|
|
|
|
void TableZone::updateBg()
|
|
{
|
|
update();
|
|
}
|
|
|
|
QRectF TableZone::boundingRect() const
|
|
{
|
|
return QRectF(0, 0, width, height);
|
|
}
|
|
|
|
bool TableZone::isInverted() const
|
|
{
|
|
return ((getLogic()->getPlayer()->getGraphicsItem()->getMirrored() &&
|
|
!SettingsCache::instance().getInvertVerticalCoordinate()) ||
|
|
(!getLogic()->getPlayer()->getGraphicsItem()->getMirrored() &&
|
|
SettingsCache::instance().getInvertVerticalCoordinate()));
|
|
}
|
|
|
|
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
|
{
|
|
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, getLogic()->getPlayer()->getZoneId());
|
|
painter->fillRect(boundingRect(), brush);
|
|
|
|
if (active) {
|
|
paintZoneOutline(painter);
|
|
} else {
|
|
// inactive player gets a darker table zone with a semi transparent black mask
|
|
// this means if the user provides a custom background it will fade
|
|
painter->fillRect(boundingRect(), FADE_MASK);
|
|
}
|
|
|
|
paintLandDivider(painter);
|
|
}
|
|
|
|
/**
|
|
Render a soft outline around the edge of the TableZone.
|
|
|
|
@param painter QPainter object
|
|
*/
|
|
void TableZone::paintZoneOutline(QPainter *painter)
|
|
{
|
|
QLinearGradient grad1(0, 0, 0, 1);
|
|
grad1.setCoordinateMode(QGradient::ObjectBoundingMode);
|
|
grad1.setColorAt(0, GRADIENT_COLOR);
|
|
grad1.setColorAt(1, GRADIENT_COLORLESS);
|
|
painter->fillRect(QRectF(0, 0, width, BOX_LINE_WIDTH), QBrush(grad1));
|
|
|
|
grad1.setFinalStop(1, 0);
|
|
painter->fillRect(QRectF(0, 0, BOX_LINE_WIDTH, height), QBrush(grad1));
|
|
|
|
grad1.setStart(0, 1);
|
|
grad1.setFinalStop(0, 0);
|
|
painter->fillRect(QRectF(0, height - BOX_LINE_WIDTH, width, BOX_LINE_WIDTH), QBrush(grad1));
|
|
|
|
grad1.setStart(1, 0);
|
|
painter->fillRect(QRectF(width - BOX_LINE_WIDTH, 0, BOX_LINE_WIDTH, height), QBrush(grad1));
|
|
}
|
|
|
|
/**
|
|
Render a division line for land placement
|
|
|
|
@painter QPainter object
|
|
*/
|
|
void TableZone::paintLandDivider(QPainter *painter)
|
|
{
|
|
// Place the line 2 grid heights down then back it off just enough to allow
|
|
// some space between a 3-card stack and the land area.
|
|
qreal separatorY = MARGIN_TOP + 2 * (CardDimensions::HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2;
|
|
if (isInverted())
|
|
separatorY = height - separatorY;
|
|
painter->setPen(QColor(255, 255, 255, 40));
|
|
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
|
|
}
|
|
|
|
void TableZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|
CardZoneLogic *startZone,
|
|
const QPoint &dropPoint)
|
|
{
|
|
handleDropEventByGrid(dragItems, startZone, mapToGrid(dropPoint));
|
|
}
|
|
|
|
void TableZone::handleDropEventByGrid(const QList<CardDragItem *> &dragItems,
|
|
CardZoneLogic *startZone,
|
|
const QPoint &gridPoint)
|
|
{
|
|
Command_MoveCard cmd;
|
|
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
|
|
cmd.set_start_zone(startZone->getName().toStdString());
|
|
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
|
cmd.set_target_zone(getLogic()->getName().toStdString());
|
|
cmd.set_x(gridPoint.x());
|
|
cmd.set_y(gridPoint.y());
|
|
|
|
for (const auto &item : dragItems) {
|
|
CardToMove *ctm = cmd.mutable_cards_to_move()->add_card();
|
|
ctm->set_card_id(item->getId());
|
|
if (item->isForceFaceDown()) {
|
|
ctm->set_face_down(true);
|
|
}
|
|
if (startZone->getName() != getLogic()->getName() && !item->isForceFaceDown()) {
|
|
const auto &card = item->getItem()->getCard();
|
|
if (card) {
|
|
ctm->set_pt(card.getInfo().getPowTough().toStdString());
|
|
}
|
|
}
|
|
}
|
|
|
|
startZone->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
|
}
|
|
|
|
void TableZone::reorganizeCards()
|
|
{
|
|
// Calculate card stack widths so mapping functions work properly
|
|
computeCardStackWidths();
|
|
|
|
for (int i = 0; i < getLogic()->getCards().size(); ++i) {
|
|
QPoint gridPoint = getLogic()->getCards()[i]->getGridPos();
|
|
if (gridPoint.x() == -1)
|
|
continue;
|
|
|
|
QPointF mapPoint = mapFromGrid(gridPoint);
|
|
qreal x = mapPoint.x();
|
|
qreal y = mapPoint.y();
|
|
|
|
int numberAttachedCards = getLogic()->getCards()[i]->getAttachedCards().size();
|
|
qreal actualX = x + numberAttachedCards * STACKED_CARD_OFFSET_X;
|
|
qreal actualY = y;
|
|
if (numberAttachedCards)
|
|
actualY += 15;
|
|
|
|
getLogic()->getCards()[i]->setPos(actualX, actualY);
|
|
getLogic()->getCards()[i]->setRealZValue(ZValues::tableCardZValue(actualX, actualY));
|
|
|
|
QListIterator<CardItem *> attachedCardIterator(getLogic()->getCards()[i]->getAttachedCards());
|
|
int j = 0;
|
|
while (attachedCardIterator.hasNext()) {
|
|
++j;
|
|
CardItem *attachedCard = attachedCardIterator.next();
|
|
qreal childX = actualX - j * STACKED_CARD_OFFSET_X;
|
|
qreal childY = y + 5;
|
|
attachedCard->setPos(childX, childY);
|
|
attachedCard->setRealZValue(ZValues::tableCardZValue(childX, childY));
|
|
}
|
|
}
|
|
|
|
resizeToContents();
|
|
update();
|
|
}
|
|
|
|
void TableZone::toggleTapped()
|
|
{
|
|
QList<QGraphicsItem *> selectedItemsRaw = scene()->selectedItems();
|
|
QList<QGraphicsItem *> selectedItems;
|
|
|
|
auto isCardOnTable = [](const QGraphicsItem *item) {
|
|
if (auto card = qgraphicsitem_cast<const CardItem *>(item)) {
|
|
return card->getZone()->getName() == "table";
|
|
}
|
|
return false;
|
|
};
|
|
|
|
std::copy_if(selectedItemsRaw.begin(), selectedItemsRaw.end(), std::back_inserter(selectedItems), isCardOnTable);
|
|
|
|
bool tapAll = std::any_of(selectedItems.begin(), selectedItems.end(), [](const QGraphicsItem *item) {
|
|
return !qgraphicsitem_cast<const CardItem *>(item)->getTapped();
|
|
});
|
|
QList<const ::google::protobuf::Message *> cmdList;
|
|
for (const auto &selectedItem : selectedItems) {
|
|
CardItem *temp = qgraphicsitem_cast<CardItem *>(selectedItem);
|
|
if (temp->getTapped() != tapAll) {
|
|
Command_SetCardAttr *cmd = new Command_SetCardAttr;
|
|
cmd->set_zone(getLogic()->getName().toStdString());
|
|
cmd->set_card_id(temp->getId());
|
|
cmd->set_attribute(AttrTapped);
|
|
cmd->set_attr_value(tapAll ? "1" : "0");
|
|
cmdList.append(cmd);
|
|
}
|
|
}
|
|
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(
|
|
getLogic()->getPlayer()->getPlayerActions()->prepareGameCommand(cmdList));
|
|
}
|
|
|
|
void TableZone::resizeToContents()
|
|
{
|
|
int xMax = 0;
|
|
|
|
// Find rightmost card position, which includes the left margin amount.
|
|
for (int i = 0; i < getLogic()->getCards().size(); ++i)
|
|
if (getLogic()->getCards()[i]->pos().x() > xMax)
|
|
xMax = (int)getLogic()->getCards()[i]->pos().x();
|
|
|
|
// Minimum width is the rightmost card position plus enough room for
|
|
// another card with padding, then margin.
|
|
currentMinimumWidth = xMax + (2 * CardDimensions::WIDTH) + PADDING_X + MARGIN_RIGHT;
|
|
|
|
if (currentMinimumWidth < MIN_WIDTH)
|
|
currentMinimumWidth = MIN_WIDTH;
|
|
|
|
if (currentMinimumWidth != width) {
|
|
prepareGeometryChange();
|
|
width = currentMinimumWidth;
|
|
emit sizeChanged();
|
|
}
|
|
}
|
|
|
|
CardItem *TableZone::getCardFromGrid(const QPoint &gridPoint) const
|
|
{
|
|
for (int i = 0; i < getLogic()->getCards().size(); i++)
|
|
if (getLogic()->getCards().at(i)->getGridPoint() == gridPoint)
|
|
return getLogic()->getCards().at(i);
|
|
return 0;
|
|
}
|
|
|
|
CardItem *TableZone::getCardFromCoords(const QPointF &point) const
|
|
{
|
|
QPoint gridPoint = mapToGrid(point);
|
|
return getCardFromGrid(gridPoint);
|
|
}
|
|
|
|
void TableZone::computeCardStackWidths()
|
|
{
|
|
// Each card stack is three grid points worth of card locations.
|
|
// First pass: compute the number of cards at each card stack.
|
|
QMap<int, int> cardStackCount;
|
|
for (int i = 0; i < getLogic()->getCards().size(); ++i) {
|
|
const QPoint &gridPoint = getLogic()->getCards()[i]->getGridPos();
|
|
if (gridPoint.x() == -1)
|
|
continue;
|
|
|
|
const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y());
|
|
cardStackCount.insert(key, cardStackCount.value(key, 0) + 1);
|
|
}
|
|
|
|
// Second pass: compute the width at each card stack.
|
|
cardStackWidth.clear();
|
|
for (int i = 0; i < getLogic()->getCards().size(); ++i) {
|
|
const QPoint &gridPoint = getLogic()->getCards()[i]->getGridPos();
|
|
if (gridPoint.x() == -1)
|
|
continue;
|
|
|
|
const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y());
|
|
const int stackCount = cardStackCount.value(key, 0);
|
|
if (stackCount == 1)
|
|
cardStackWidth.insert(key, CardDimensions::WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() *
|
|
STACKED_CARD_OFFSET_X);
|
|
else
|
|
cardStackWidth.insert(key, CardDimensions::WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X);
|
|
}
|
|
}
|
|
|
|
QPointF TableZone::mapFromGrid(QPoint gridPoint) const
|
|
{
|
|
qreal x, y;
|
|
|
|
// Start with margin plus stacked card offset
|
|
x = MARGIN_LEFT + (gridPoint.x() % 3) * STACKED_CARD_OFFSET_X;
|
|
|
|
// Add in width of card stack plus padding for each column
|
|
for (int i = 0; i < gridPoint.x() / 3; ++i) {
|
|
const int key = getCardStackMapKey(i, gridPoint.y());
|
|
x += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X;
|
|
}
|
|
|
|
if (isInverted())
|
|
gridPoint.setY(TABLEROWS - 1 - gridPoint.y());
|
|
|
|
// Start with margin plus stacked card offset
|
|
y = MARGIN_TOP + (gridPoint.x() % 3) * STACKED_CARD_OFFSET_Y;
|
|
|
|
// Add in card size and padding for each row
|
|
for (int i = 0; i < gridPoint.y(); ++i)
|
|
y += CardDimensions::HEIGHT + PADDING_Y;
|
|
|
|
return QPointF(x, y);
|
|
}
|
|
|
|
QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
|
|
{
|
|
// Begin by calculating the y-coordinate of the grid space, which will be
|
|
// used for the x-coordinate.
|
|
|
|
// Offset point by the margin amount to reference point within grid area.
|
|
int y = mapPoint.y() - MARGIN_TOP;
|
|
|
|
// Below calculation effectively rounds to the nearest grid point.
|
|
const int gridPointHeight = CardDimensions::HEIGHT + PADDING_Y;
|
|
int gridPointY = (y + PADDING_Y / 2) / gridPointHeight;
|
|
|
|
gridPointY = clampValidTableRow(gridPointY);
|
|
|
|
if (isInverted())
|
|
gridPointY = TABLEROWS - 1 - gridPointY;
|
|
|
|
// Calculating the x-coordinate of the grid space requires adding up the
|
|
// widths of each card stack along the row.
|
|
|
|
// Offset point by the margin amount to reference point within grid area.
|
|
int x = mapPoint.x() - MARGIN_LEFT + PADDING_X / 2;
|
|
|
|
// Maximum value is a card width from the right margin, referenced to the
|
|
// grid area.
|
|
const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CardDimensions::WIDTH;
|
|
|
|
int xStack = 0;
|
|
int xNextStack = 0;
|
|
int nextStackCol = 0;
|
|
while ((xNextStack <= x) && (xNextStack <= xMax)) {
|
|
xStack = xNextStack;
|
|
const int key = getCardStackMapKey(nextStackCol, gridPointY);
|
|
xNextStack += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X;
|
|
nextStackCol++;
|
|
}
|
|
int stackCol = qMax(nextStackCol - 1, 0);
|
|
|
|
// Have the stack column, need to refine to the grid column. Take the
|
|
// difference between the point and the stack point and divide by stacked
|
|
// card offsets.
|
|
int xDiff = x - xStack;
|
|
int gridPointX = stackCol * 3 + qMin(xDiff / STACKED_CARD_OFFSET_X, 2);
|
|
|
|
return QPoint(gridPointX, gridPointY);
|
|
}
|
|
|
|
QPointF TableZone::closestGridPoint(const QPointF &point)
|
|
{
|
|
QPoint gridPoint = mapToGrid(point);
|
|
gridPoint.setX((gridPoint.x() / 3) * 3);
|
|
if (getCardFromGrid(gridPoint))
|
|
gridPoint.setX(gridPoint.x() + 1);
|
|
if (getCardFromGrid(gridPoint))
|
|
gridPoint.setX(gridPoint.x() + 1);
|
|
return mapFromGrid(gridPoint);
|
|
}
|
|
|
|
int TableZone::clampValidTableRow(const int row)
|
|
{
|
|
if (row < 0)
|
|
return 0;
|
|
if (row >= TABLEROWS)
|
|
return TABLEROWS - 1;
|
|
return row;
|
|
}
|