diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 160d8d336..9fff6b3df 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -144,7 +144,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T PileZone *sb = addZone(new PileZone(this, "sb", false, false, playerArea)); sb->setVisible(false); - table = addZone(new TableZone(this, this)); + table = addZone(new TableZone(this, "table", this)); connect(table, &TableZone::sizeChanged, this, &Player::updateBoundingRect); stack = addZone(new StackZone(this, (int)table->boundingRect().height(), this)); @@ -400,6 +400,9 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T sbMenu->addAction(aViewSideboard); sb->setMenu(sbMenu, aViewSideboard); + mCustomZones = playerMenu->addMenu(QString()); + mCustomZones->menuAction()->setVisible(false); + aUntapAll = new QAction(this); connect(aUntapAll, &QAction::triggered, this, &Player::actUntapAll); @@ -455,6 +458,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T if (!local && !judge) { countersMenu = nullptr; sbMenu = nullptr; + mCustomZones = nullptr; aCreateAnotherToken = nullptr; createPredefinedTokenMenu = nullptr; } @@ -829,6 +833,11 @@ void Player::retranslateUi() sbMenu->setTitle(tr("&Sideboard")); libraryMenu->setTitle(tr("&Library")); countersMenu->setTitle(tr("&Counters")); + mCustomZones->setTitle(tr("C&ustom Zones")); + + for (auto aViewZone : mCustomZones->actions()) { + aViewZone->setText(tr("View custom zone '%1'").arg(aViewZone->data().toString())); + } aUntapAll->setText(tr("&Untap all permanents")); aRollDie->setText(tr("R&oll die...")); @@ -2715,19 +2724,79 @@ void Player::paint(QPainter * /*painter*/, const QStyleOptionGraphicsItem * /*op void Player::processPlayerInfo(const ServerInfo_Player &info) { + static QSet builtinZones{/* PileZones */ + "deck", "grave", "rfg", "sb", + /* TableZone */ + "table", + /* StackZone */ + "stack", + /* HandZone */ + "hand"}; clearCounters(); clearArrows(); - QMapIterator zoneIt(zones); + QMutableMapIterator zoneIt(zones); while (zoneIt.hasNext()) { zoneIt.next().value()->clearContents(); + + if (!builtinZones.contains(zoneIt.key())) { + zoneIt.remove(); + } + } + + // Can be null if we are not the local player! + if (mCustomZones) { + mCustomZones->clear(); + mCustomZones->menuAction()->setVisible(false); } const int zoneListSize = info.zone_list_size(); for (int i = 0; i < zoneListSize; ++i) { const ServerInfo_Zone &zoneInfo = info.zone_list(i); - CardZone *zone = zones.value(QString::fromStdString(zoneInfo.name()), 0); + + QString zoneName = QString::fromStdString(zoneInfo.name()); + CardZone *zone = zones.value(zoneName, 0); if (!zone) { + // Create a new CardZone if it doesn't exist + + if (zoneInfo.with_coords()) { + // Visibility not currently supported for TableZone + zone = addZone(new TableZone(this, zoneName, this)); + } else { + // Zones without coordinats are always treated as non-shufflable + // PileZones, although supporting alternate hand or stack zones + // might make sense in some scenarios. + bool contentsKnown; + + switch (zoneInfo.type()) { + case ServerInfo_Zone::PrivateZone: + contentsKnown = local || judge || (game->getSpectator() && game->getSpectatorsSeeEverything()); + break; + + case ServerInfo_Zone::PublicZone: + contentsKnown = true; + break; + + case ServerInfo_Zone::HiddenZone: + contentsKnown = false; + break; + } + + zone = addZone(new PileZone(this, zoneName, /* isShufflable */ false, contentsKnown, this)); + } + + // Non-builtin zones are hidden by default and can't be interacted + // with, except through menus. + zone->setVisible(false); + + if (mCustomZones) { + mCustomZones->menuAction()->setVisible(true); + QAction *aViewZone = mCustomZones->addAction(tr("View custom zone '%1'").arg(zoneName)); + aViewZone->setData(zoneName); + connect(aViewZone, &QAction::triggered, this, + [zoneName, this]() { static_cast(scene())->toggleZoneView(this, zoneName, -1); }); + } + continue; } diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index ea5278572..d802ef796 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -253,7 +253,7 @@ public: private: TabGame *game; QMenu *sbMenu, *countersMenu, *sayMenu, *createPredefinedTokenMenu, *mRevealLibrary, *mLendLibrary, *mRevealTopCard, - *mRevealHand, *mRevealRandomHandCard, *mRevealRandomGraveyardCard; + *mRevealHand, *mRevealRandomHandCard, *mRevealRandomGraveyardCard, *mCustomZones; TearOffMenu *moveGraveMenu, *moveRfgMenu, *graveMenu, *moveHandMenu, *handMenu, *libraryMenu, *topLibraryMenu, *bottomLibraryMenu, *rfgMenu, *playerMenu; QList playerLists; diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index 05b4c35b9..089ed0a2d 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -92,6 +92,10 @@ QString CardZone::getTranslatedName(bool theirOwn, GrammaticalCase gc) const default: break; } + else { + return (theirOwn ? tr("their custom zone '%1'", "nominative").arg(name) + : tr("%1's custom zone '%2'", "nominative").arg(ownerName).arg(name)); + } return QString(); } diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index e214b2e09..cee782560 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -19,8 +19,8 @@ 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(Player *_p, QGraphicsItem *parent) - : SelectZone(_p, "table", true, false, true, parent), active(false) +TableZone::TableZone(Player *_p, const QString &name, QGraphicsItem *parent) + : SelectZone(_p, name, true, false, true, parent), active(false) { connect(themeManager, &ThemeManager::themeChanged, this, &TableZone::updateBg); connect(&SettingsCache::instance(), &SettingsCache::invertVerticalCoordinateChanged, this, diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index f3fbdccb6..3d464e6f3 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -98,7 +98,7 @@ public: @param _p the Player @param parent defaults to null */ - explicit TableZone(Player *_p, QGraphicsItem *parent = nullptr); + explicit TableZone(Player *_p, const QString &name, QGraphicsItem *parent = nullptr); /** @return a QRectF of the TableZone bounding box. diff --git a/cockatrice/src/server/message_log_widget.cpp b/cockatrice/src/server/message_log_widget.cpp index 0d21be045..a61a7ba2c 100644 --- a/cockatrice/src/server/message_log_widget.cpp +++ b/cockatrice/src/server/message_log_widget.cpp @@ -86,6 +86,8 @@ QPair MessageLogWidget::getFromStr(CardZone *zone, QString car fromStr = tr(" from sideboard"); } else if (zoneName == STACK_ZONE_NAME) { fromStr = tr(" from the stack"); + } else { + fromStr = tr(" from custom zone '%1'").arg(zoneName); } if (!cardNameContainsStartZone) { @@ -321,13 +323,16 @@ void MessageLogWidget::logMoveCard(Player *player, } else if (targetZoneName == STACK_ZONE_NAME) { soundEngine->playSound("play_card"); finalStr = tr("%1 plays %2%3."); + } else { + finalStr = tr("%1 moves %2%3 to custom zone '%4'."); } if (usesNewX) { appendHtmlServerMessage( finalStr.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(nameFrom.second).arg(newX)); } else { - appendHtmlServerMessage(finalStr.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(nameFrom.second)); + appendHtmlServerMessage( + finalStr.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(nameFrom.second).arg(targetZoneName)); } } diff --git a/common/pb/serverinfo_zone.proto b/common/pb/serverinfo_zone.proto index 0efa2d9be..f0ad5d709 100644 --- a/common/pb/serverinfo_zone.proto +++ b/common/pb/serverinfo_zone.proto @@ -11,6 +11,10 @@ message ServerInfo_Zone { // setting beingLookedAt to true. // Cards in a zone with the type HiddenZone are referenced by their // list index, whereas cards in any other zone are referenced by their ids. + // + // WARNING: Adding new zone types will break compatibility with older + // clients. Older clients will read new zone types as PrivateZone, which + // is likely *NOT* what you want. PrivateZone = 0; PublicZone = 1;