Support viewing the bottom X cards of library (#5410)

* Get cardIds to update properly in bottom view (#5414)

* Get bottom view to update properly when card is inserted into known portion (#5415)

---------

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
This commit is contained in:
Zach H 2025-01-05 21:17:18 -05:00 committed by GitHub
parent 81b85e97df
commit 6078dd092a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 154 additions and 41 deletions

View File

@ -142,7 +142,7 @@ void GameScene::rearrange()
processViewSizeChange(viewSize);
}
void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards)
void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards, bool isReversed)
{
for (auto &view : zoneViews) {
ZoneViewZone *temp = view->getZone();
@ -151,7 +151,8 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb
}
}
ZoneViewWidget *item = new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false);
ZoneViewWidget *item =
new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false, false, {}, isReversed);
zoneViews.append(item);
connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *)));
addItem(item);

View File

@ -47,7 +47,7 @@ public:
void registerAnimationItem(AbstractCardItem *item);
void unregisterAnimationItem(AbstractCardItem *card);
public slots:
void toggleZoneView(Player *player, const QString &zoneName, int numberCards);
void toggleZoneView(Player *player, const QString &zoneName, int numberCards, bool isReversed = false);
void addRevealedZoneView(Player *player,
CardZone *zone,
const QList<const ServerInfo_Card *> &cardList,

View File

@ -217,6 +217,8 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T
aViewTopCards = new QAction(this);
connect(aViewTopCards, SIGNAL(triggered()), this, SLOT(actViewTopCards()));
aViewBottomCards = new QAction(this);
connect(aViewBottomCards, &QAction::triggered, this, &Player::actViewBottomCards);
aAlwaysRevealTopCard = new QAction(this);
aAlwaysRevealTopCard->setCheckable(true);
connect(aAlwaysRevealTopCard, SIGNAL(triggered()), this, SLOT(actAlwaysRevealTopCard()));
@ -315,6 +317,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T
libraryMenu->addSeparator();
libraryMenu->addAction(aViewLibrary);
libraryMenu->addAction(aViewTopCards);
libraryMenu->addAction(aViewBottomCards);
libraryMenu->addSeparator();
playerLists.append(mRevealLibrary = libraryMenu->addMenu(QString()));
singlePlayerLists.append(mLendLibrary = libraryMenu->addMenu(QString()));
@ -776,6 +779,7 @@ void Player::retranslateUi()
aViewLibrary->setText(tr("&View library"));
aViewHand->setText(tr("&View hand"));
aViewTopCards->setText(tr("View &top cards of library..."));
aViewBottomCards->setText(tr("View bottom cards of library..."));
mRevealLibrary->setTitle(tr("Reveal &library to..."));
mLendLibrary->setTitle(tr("Lend library to..."));
mRevealTopCard->setTitle(tr("Reveal &top cards to..."));
@ -969,6 +973,7 @@ void Player::setShortcutsActive()
aViewLibrary->setShortcut(shortcuts.getSingleShortcut("Player/aViewLibrary"));
aViewHand->setShortcut(shortcuts.getSingleShortcut("Player/aViewHand"));
aViewTopCards->setShortcut(shortcuts.getSingleShortcut("Player/aViewTopCards"));
aViewBottomCards->setShortcut(shortcuts.getSingleShortcut("Player/aViewBottomCards"));
aViewGraveyard->setShortcut(shortcuts.getSingleShortcut("Player/aViewGraveyard"));
aDrawCard->setShortcut(shortcuts.getSingleShortcut("Player/aDrawCard"));
aDrawCards->setShortcut(shortcuts.getSingleShortcut("Player/aDrawCards"));
@ -1018,6 +1023,7 @@ void Player::setShortcutsInactive()
aViewLibrary->setShortcut(QKeySequence());
aViewHand->setShortcut(QKeySequence());
aViewTopCards->setShortcut(QKeySequence());
aViewBottomCards->setShortcut(QKeySequence());
aViewGraveyard->setShortcut(QKeySequence());
aDrawCard->setShortcut(QKeySequence());
aDrawCards->setShortcut(QKeySequence());
@ -1128,6 +1134,19 @@ void Player::actViewTopCards()
}
}
void Player::actViewBottomCards()
{
int deckSize = zones.value("deck")->getCards().size();
bool ok;
int number =
QInputDialog::getInt(game, tr("View bottom cards of library"), tr("Number of cards: (max. %1)").arg(deckSize),
defaultNumberTopCards, 1, deckSize, 1, &ok);
if (ok) {
defaultNumberTopCards = number;
static_cast<GameScene *>(scene())->toggleZoneView(this, "deck", number, true);
}
}
void Player::actAlwaysRevealTopCard()
{
Command_ChangeZoneProperties cmd;
@ -2227,7 +2246,7 @@ void Player::eventDumpZone(const Event_DumpZone &event)
if (!zone) {
return;
}
emit logDumpZone(this, zone, event.number_cards());
emit logDumpZone(this, zone, event.number_cards(), event.is_reversed());
}
void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext &context)

View File

@ -138,7 +138,7 @@ signals:
void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap);
void logSetPT(Player *player, CardItem *card, QString newPT);
void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation);
void logDumpZone(Player *player, CardZone *zone, int numberCards);
void logDumpZone(Player *player, CardZone *zone, int numberCards, bool isReversed = false);
void logRevealCards(Player *player,
CardZone *zone,
int cardId,
@ -192,6 +192,7 @@ public slots:
void actViewLibrary();
void actViewHand();
void actViewTopCards();
void actViewBottomCards();
void actAlwaysRevealTopCard();
void actAlwaysLookAtTopCard();
void actViewGraveyard();
@ -256,11 +257,11 @@ private:
QAction *aMoveHandToTopLibrary, *aMoveHandToBottomLibrary, *aMoveHandToGrave, *aMoveHandToRfg,
*aMoveGraveToTopLibrary, *aMoveGraveToBottomLibrary, *aMoveGraveToHand, *aMoveGraveToRfg, *aMoveRfgToTopLibrary,
*aMoveRfgToBottomLibrary, *aMoveRfgToHand, *aMoveRfgToGrave, *aViewHand, *aViewLibrary, *aViewTopCards,
*aAlwaysRevealTopCard, *aAlwaysLookAtTopCard, *aOpenDeckInDeckEditor, *aMoveTopCardToGraveyard,
*aMoveTopCardToExile, *aMoveTopCardsToGraveyard, *aMoveTopCardsToExile, *aMoveTopCardsUntil,
*aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg, *aViewSideboard, *aDrawCard, *aDrawCards, *aUndoDraw,
*aMulligan, *aShuffle, *aMoveTopToPlay, *aMoveTopToPlayFaceDown, *aUntapAll, *aRollDie, *aCreateToken,
*aCreateAnotherToken, *aMoveBottomToPlay, *aMoveBottomToPlayFaceDown, *aMoveBottomCardToTop,
*aViewBottomCards, *aAlwaysRevealTopCard, *aAlwaysLookAtTopCard, *aOpenDeckInDeckEditor,
*aMoveTopCardToGraveyard, *aMoveTopCardToExile, *aMoveTopCardsToGraveyard, *aMoveTopCardsToExile,
*aMoveTopCardsUntil, *aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg, *aViewSideboard, *aDrawCard,
*aDrawCards, *aUndoDraw, *aMulligan, *aShuffle, *aMoveTopToPlay, *aMoveTopToPlayFaceDown, *aUntapAll, *aRollDie,
*aCreateToken, *aCreateAnotherToken, *aMoveBottomToPlay, *aMoveBottomToPlayFaceDown, *aMoveBottomCardToTop,
*aMoveBottomCardToGraveyard, *aMoveBottomCardToExile, *aMoveBottomCardsToGraveyard, *aMoveBottomCardsToExile,
*aDrawBottomCard, *aDrawBottomCards;

View File

@ -5,6 +5,7 @@
#include "../player/player.h"
#include "pb/command_move_card.pb.h"
#include "pb/serverinfo_user.pb.h"
#include "pile_zone.h"
#include "view_zone.h"
#include <QAction>
@ -151,7 +152,7 @@ void CardZone::addCard(CardItem *card, const bool reorganize, const int x, const
}
for (auto *view : views) {
if ((x <= view->getCards().size()) || (view->getNumberCards() == -1)) {
if (view->getIsReversed() || (x <= view->getCards().size()) || (view->getNumberCards() == -1)) {
view->addCard(new CardItem(player, nullptr, card->getName(), card->getProviderId(), card->getId()),
reorganize, x, y);
}
@ -199,12 +200,12 @@ CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/)
if (position >= cards.size())
return nullptr;
CardItem *c = cards.takeAt(position);
for (auto *view : views) {
view->removeCard(position);
}
CardItem *c = cards.takeAt(position);
c->setId(cardId);
reorganizeCards();

View File

@ -28,10 +28,12 @@ ZoneViewZone::ZoneViewZone(Player *_p,
int _numberCards,
bool _revealZone,
bool _writeableRevealZone,
QGraphicsItem *parent)
QGraphicsItem *parent,
bool _isReversed)
: SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0),
numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone),
writeableRevealZone(_writeableRevealZone), groupBy(CardList::NoSort), sortBy(CardList::NoSort)
writeableRevealZone(_writeableRevealZone), groupBy(CardList::NoSort), sortBy(CardList::NoSort),
isReversed(_isReversed)
{
if (!(revealZone && !writeableRevealZone)) {
origZone->getViews().append(this);
@ -72,6 +74,7 @@ void ZoneViewZone::initializeCards(const QList<const ServerInfo_Card *> &cardLis
cmd.set_player_id(player->getId());
cmd.set_zone_name(name.toStdString());
cmd.set_number_cards(numberCards);
cmd.set_is_reversed(isReversed);
PendingCommand *pend = player->prepareGameCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
@ -100,18 +103,49 @@ void ZoneViewZone::zoneDumpReceived(const Response &r)
auto *card = new CardItem(player, this, cardName, cardProviderId, cardInfo.id(), revealZone, this);
cards.insert(i, card);
}
updateCardIds(INITIALIZE);
reorganizeCards();
emit cardCountChanged();
}
void ZoneViewZone::updateCardIds(CardAction action)
{
if (origZone->contentsKnown()) {
return;
}
if (cards.isEmpty()) {
return;
}
int cardCount = cards.size();
auto startId = 0;
if (isReversed) {
// the card has not been added to origZone's cardList at this point
startId = origZone->getCards().size() - cardCount;
switch (action) {
case INITIALIZE:
break;
case ADD_CARD:
startId += 1;
break;
case REMOVE_CARD:
startId -= 1;
break;
}
}
for (int i = 0; i < cardCount; ++i) {
cards[i]->setId(i + startId);
}
}
// Because of boundingRect(), this function must not be called before the zone was added to a scene.
void ZoneViewZone::reorganizeCards()
{
int cardCount = cards.size();
if (!origZone->contentsKnown())
for (int i = 0; i < cardCount; ++i)
cards[i]->setId(i);
CardList cardsToDisplay(cards);
// sort cards
@ -244,13 +278,30 @@ void ZoneViewZone::setPileView(int _pileView)
void ZoneViewZone::addCardImpl(CardItem *card, int x, int /*y*/)
{
// if x is negative set it to add at end
if (x < 0 || x >= cards.size()) {
x = cards.size();
if (!isReversed) {
// if x is negative set it to add at end
if (x < 0) {
x = cards.size();
}
cards.insert(x, card);
} else {
// map x (which is in origZone indexes) to this viewZone's cardList index
int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId();
int insertionIndex = x - firstId;
if (insertionIndex >= 0) {
// card was put into a portion of the deck that's in the view
cards.insert(insertionIndex, card);
} else {
// card was put into a portion of the deck that's not in the view
updateCardIds(ADD_CARD);
return;
}
}
cards.insert(x, card);
card->setParentItem(this);
card->update();
updateCardIds(ADD_CARD);
reorganizeCards();
}
@ -265,6 +316,7 @@ void ZoneViewZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
cmd.set_target_zone(getName().toStdString());
cmd.set_x(0);
cmd.set_y(0);
cmd.set_is_reversed(isReversed);
for (int i = 0; i < dragItems.size(); ++i)
cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
@ -274,11 +326,21 @@ void ZoneViewZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
void ZoneViewZone::removeCard(int position)
{
if (position >= cards.size())
if (isReversed) {
position -= cards.first()->getId();
if (position < 0 || position >= cards.size()) {
updateCardIds(REMOVE_CARD);
return;
}
}
if (position >= cards.size()) {
return;
}
CardItem *card = cards.takeAt(position);
card->deleteLater();
updateCardIds(REMOVE_CARD);
reorganizeCards();
}

View File

@ -34,6 +34,16 @@ private:
bool revealZone, writeableRevealZone;
CardList::SortOption groupBy, sortBy;
bool pileView;
bool isReversed;
enum CardAction
{
INITIALIZE,
ADD_CARD,
REMOVE_CARD
};
void updateCardIds(CardAction action);
struct GridSize
{
@ -49,7 +59,8 @@ public:
int _numberCards = -1,
bool _revealZone = false,
bool _writeableRevealZone = false,
QGraphicsItem *parent = nullptr);
QGraphicsItem *parent = nullptr,
bool _isReversed = false);
~ZoneViewZone();
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
@ -74,6 +85,10 @@ public:
return writeableRevealZone;
}
void setWriteableRevealZone(bool _writeableRevealZone);
bool getIsReversed() const
{
return isReversed;
}
public slots:
void setGroupBy(CardList::SortOption _groupBy);
void setSortBy(CardList::SortOption _sortBy);

View File

@ -31,7 +31,8 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
int numberCards,
bool _revealZone,
bool _writeableRevealZone,
const QList<const ServerInfo_Card *> &cardList)
const QList<const ServerInfo_Card *> &cardList,
bool _isReversed)
: QGraphicsWidget(0, Qt::Window), canBeShuffled(_origZone->getIsShufflable()), player(_player)
{
setAcceptHoverEvents(true);
@ -107,7 +108,8 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
vbox->addItem(zoneHBox);
zone = new ZoneViewZone(player, _origZone, numberCards, _revealZone, _writeableRevealZone, zoneContainer);
zone =
new ZoneViewZone(player, _origZone, numberCards, _revealZone, _writeableRevealZone, zoneContainer, _isReversed);
connect(zone, SIGNAL(wheelEventReceived(QGraphicsSceneWheelEvent *)), scrollBarProxy,
SLOT(recieveWheelEvent(QGraphicsSceneWheelEvent *)));

View File

@ -75,7 +75,8 @@ public:
int numberCards = 0,
bool _revealZone = false,
bool _writeableRevealZone = false,
const QList<const ServerInfo_Card *> &cardList = QList<const ServerInfo_Card *>());
const QList<const ServerInfo_Card *> &cardList = QList<const ServerInfo_Card *>(),
bool _isReversed = false);
ZoneViewZone *getZone() const
{
return zone;

View File

@ -378,7 +378,7 @@ void MessageLogWidget::logDrawCards(Player *player, int number, bool deckIsEmpty
}
}
void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCards)
void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCards, bool isReversed)
{
if (numberCards == -1) {
appendHtmlServerMessage(tr("%1 is looking at %2.")
@ -386,10 +386,11 @@ void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCar
.arg(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone)));
} else {
appendHtmlServerMessage(
tr("%1 is looking at the top %3 card(s) %2.", "top card for singular, top %3 cards for plural", numberCards)
tr("%1 is looking at the %4 %3 card(s) %2.", "top card for singular, top %3 cards for plural", numberCards)
.arg(sanitizeHtml(player->getName()))
.arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone))
.arg("<font class=\"blue\">" + QString::number(numberCards) + "</font>"));
.arg("<font class=\"blue\">" + QString::number(numberCards) + "</font>")
.arg(isReversed ? tr("bottom") : tr("top")));
}
}
@ -850,7 +851,7 @@ void MessageLogWidget::connectToPlayer(Player *player)
connect(player, SIGNAL(logAttachCard(Player *, QString, Player *, QString)), this,
SLOT(logAttachCard(Player *, QString, Player *, QString)));
connect(player, SIGNAL(logUnattachCard(Player *, QString)), this, SLOT(logUnattachCard(Player *, QString)));
connect(player, SIGNAL(logDumpZone(Player *, CardZone *, int)), this, SLOT(logDumpZone(Player *, CardZone *, int)));
connect(player, &Player::logDumpZone, this, &MessageLogWidget::logDumpZone);
connect(player, SIGNAL(logDrawCards(Player *, int, bool)), this, SLOT(logDrawCards(Player *, int, bool)));
connect(player, SIGNAL(logUndoDraw(Player *, QString)), this, SLOT(logUndoDraw(Player *, QString)));
connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int, bool)), this,

View File

@ -57,7 +57,7 @@ public slots:
void logDeckSelect(Player *player, QString deckHash, int sideboardSize);
void logDestroyCard(Player *player, QString cardName);
void logDrawCards(Player *player, int number, bool deckIsEmpty);
void logDumpZone(Player *player, CardZone *zone, int numberCards);
void logDumpZone(Player *player, CardZone *zone, int numberCards, bool isReversed = false);
void logFlipCard(Player *player, QString cardName, bool faceDown);
void logGameClosed();
void logGameStart();

View File

@ -496,6 +496,9 @@ private:
{"Player/aViewTopCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top Cards of Library"),
parseSequenceString("Ctrl+W"),
ShortcutGroup::View)},
{"Player/aViewBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom Cards of Library"),
parseSequenceString("Ctrl+Shift+W"),
ShortcutGroup::View)},
{"Player/aCloseMostRecentZoneView", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Close Recent View"),
parseSequenceString("Esc"),
ShortcutGroup::View)},

View File

@ -7,4 +7,5 @@ message Command_DumpZone {
optional sint32 player_id = 1 [default = -1];
optional string zone_name = 2;
optional sint32 number_cards = 3;
optional bool is_reversed = 4 [default = false];
}

View File

@ -22,4 +22,5 @@ message Command_MoveCard {
optional string target_zone = 5;
optional sint32 x = 6 [default = -1];
optional sint32 y = 7 [default = -1];
optional bool is_reversed = 8 [default = false];
}

View File

@ -8,4 +8,5 @@ message Event_DumpZone {
optional sint32 zone_owner_id = 1;
optional string zone_name = 2;
optional sint32 number_cards = 3;
optional bool is_reversed = 4 [default = false];
}

View File

@ -424,7 +424,8 @@ Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges,
int xCoord,
int yCoord,
bool fixFreeSpaces,
bool undoingDraw)
bool undoingDraw,
bool isReversed)
{
// Disallow controller change to other zones than the table.
if (((targetzone->getType() != ServerInfo_Zone::PublicZone) || !targetzone->hasCoords()) &&
@ -542,7 +543,7 @@ Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges,
if (card) {
++xIndex;
int newX = xCoord + xIndex;
int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex;
if (targetzone->hasCoords()) {
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
@ -553,7 +554,7 @@ Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges,
targetzone->insertCard(card, newX, yCoord);
int targetLookedCards = targetzone->getCardsBeingLookedAt();
bool sourceKnownToPlayer = sourceBeingLookedAt && !card->getFaceDown();
bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown());
if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) {
if (sourceKnownToPlayer) {
targetLookedCards += 1;
@ -1247,7 +1248,7 @@ Server_Player::cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer & /*rc
cardsToMove.append(&cmd.cards_to_move().card(i));
}
return moveCard(ges, startZone, cardsToMove, targetZone, cmd.x(), cmd.y());
return moveCard(ges, startZone, cardsToMove, targetZone, cmd.x(), cmd.y(), true, false, cmd.is_reversed());
}
Response::ResponseCode
@ -2008,13 +2009,14 @@ Server_Player::cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, G
zoneInfo->set_card_count(numberCards < cards.size() ? cards.size() : numberCards);
for (int i = 0; (i < cards.size()) && (i < numberCards || numberCards == -1); ++i) {
Server_Card *card = cards[i];
const auto &findId = cmd.is_reversed() ? cards.size() - numberCards + i : i;
Server_Card *card = cards[findId];
QString displayedName = card->getFaceDown() ? QString() : card->getName();
ServerInfo_Card *cardInfo = zoneInfo->add_card_list();
cardInfo->set_provider_id(card->getProviderId().toStdString());
cardInfo->set_name(displayedName.toStdString());
if (zone->getType() == ServerInfo_Zone::HiddenZone) {
cardInfo->set_id(i);
cardInfo->set_id(findId);
} else {
cardInfo->set_id(card->getId());
cardInfo->set_x(card->getX());
@ -2050,6 +2052,7 @@ Server_Player::cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, G
event.set_zone_owner_id(otherPlayer->getPlayerId());
event.set_zone_name(zone->getName().toStdString());
event.set_number_cards(numberCards);
event.set_is_reversed(cmd.is_reversed());
ges.enqueueGameEvent(event, playerId);
}
rc.setResponseExtension(re);

View File

@ -179,7 +179,8 @@ public:
int xCoord,
int yCoord,
bool fixFreeSpaces = true,
bool undoingDraw = false);
bool undoingDraw = false,
bool isReversed = false);
void unattachCard(GameEventStorage &ges, Server_Card *card);
Response::ResponseCode setCardAttrHelper(GameEventStorage &ges,
int targetPlayerId,