mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-09 11:33:25 -08:00
[Game] Reveal multiple distinct random hand cards to a recipient (#7753)
* Add distinct random hand reveals with private recipient delivery * Route random hand reveals through PlayerActions Keep command creation, dispatch and response handling in the game action layer. HandMenu requests the typed operation and presents the count and failure dialogs. Show the chosen recipient, distinguish the count action from the immediate single-card reveal, and use the game window as parent. * Apply project formatting to random-reveal warning construction * Simplify random reveal to follow existing command patterns Forward server dispatch through cmdRevealRandomCards and use ordinary PlayerActions command sending. Remove the bespoke server-version error UI and long-lived-object guards. Reduce the test from 246 to 150 lines with two focused cases using the current RoleInfo player API, preserving distinct-card, private-event and rejected-count coverage. * Extract the shared reveal helper and name the test's CardRef fields RickyRister asked for cmdRevealCards and cmdRevealRandomCards to share a helper instead of threading a randomCount parameter through the reveal virtual. Restore the three-argument cmdRevealCards signature, resolve the reveal preconditions and zone in resolveRevealZone, and let both commands build their card list and hand it to revealCards, which owns the private and public Event_RevealCards construction and the enqueue. The random command no longer fabricates a Command_RevealCards with the -2 sentinel to re-enter the legacy path; its behaviour is unchanged. Name the CardRef fields in random_reveal_test instead of relying on declaration order, and state in the user documentation that a rejected reveal is not reported in the client, matching what the client does. --------- Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
This commit is contained in:
parent
39220a2ea9
commit
078d0858a3
14 changed files with 375 additions and 17 deletions
|
|
@ -405,6 +405,13 @@ Response::ResponseCode Server_AbstractParticipant::cmdRevealCards(const Command_
|
|||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_AbstractParticipant::cmdRevealRandomCards(const Command_RevealRandomCards & /*cmd*/,
|
||||
ResponseContainer & /*rc*/,
|
||||
GameEventStorage & /*ges*/)
|
||||
{
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_AbstractParticipant::cmdChangeZoneProperties(const Command_ChangeZoneProperties & /*cmd*/,
|
||||
ResponseContainer & /*rc*/,
|
||||
GameEventStorage & /*ges*/)
|
||||
|
|
@ -544,6 +551,9 @@ Server_AbstractParticipant::processGameCommand(const GameCommand &command, Respo
|
|||
case GameCommand::REVEAL_CARDS:
|
||||
return cmdRevealCards(command.GetExtension(Command_RevealCards::ext), rc, ges);
|
||||
break;
|
||||
case GameCommand::REVEAL_RANDOM_CARDS:
|
||||
return cmdRevealRandomCards(command.GetExtension(Command_RevealRandomCards::ext), rc, ges);
|
||||
break;
|
||||
case GameCommand::MOVE_CARD:
|
||||
return cmdMoveCard(command.GetExtension(Command_MoveCard::ext), rc, ges);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class Command_NextTurn;
|
|||
class Command_SetActivePhase;
|
||||
class Command_DumpZone;
|
||||
class Command_RevealCards;
|
||||
class Command_RevealRandomCards;
|
||||
class Command_ReverseTurn;
|
||||
class Command_MoveCard;
|
||||
class Command_SetSideboardPlan;
|
||||
|
|
@ -176,6 +177,8 @@ public:
|
|||
virtual Response::ResponseCode
|
||||
cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||
virtual Response::ResponseCode
|
||||
cmdRevealRandomCards(const Command_RevealRandomCards &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||
virtual Response::ResponseCode
|
||||
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
||||
virtual Response::ResponseCode
|
||||
cmdSetPlayerOrder(const Command_SetPlayerOrder &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||
|
|
|
|||
|
|
@ -1494,8 +1494,10 @@ Server_AbstractPlayer::cmdDumpZone(const Command_DumpZone &cmd, ResponseContaine
|
|||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode
|
||||
Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
|
||||
Response::ResponseCode Server_AbstractPlayer::resolveRevealZone(const std::string &zoneName,
|
||||
bool hasRecipient,
|
||||
int recipientId,
|
||||
Server_CardZone *&zone)
|
||||
{
|
||||
if (!game->getGameStarted()) {
|
||||
return Response::RespGameNotStarted;
|
||||
|
|
@ -1504,17 +1506,61 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo
|
|||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
if (cmd.has_player_id()) {
|
||||
Server_AbstractPlayer *otherPlayer = game->getPlayer(cmd.player_id());
|
||||
if (!otherPlayer) {
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
if (hasRecipient && !game->getPlayer(recipientId)) {
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
Server_CardZone *zone = zones.value(nameFromStdString(cmd.zone_name()));
|
||||
|
||||
zone = zones.value(nameFromStdString(zoneName));
|
||||
if (!zone) {
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_AbstractPlayer::cmdRevealRandomCards(const Command_RevealRandomCards &cmd,
|
||||
ResponseContainer & /*rc*/,
|
||||
GameEventStorage &ges)
|
||||
{
|
||||
if (cmd.number_of_cards() <= 0) {
|
||||
return Response::RespInvalidData;
|
||||
}
|
||||
|
||||
Server_CardZone *zone = nullptr;
|
||||
const Response::ResponseCode result =
|
||||
resolveRevealZone(cmd.zone_name(), cmd.has_player_id(), cmd.player_id(), zone);
|
||||
if (result != Response::RespOk) {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto candidates = zone->getCards();
|
||||
if (cmd.number_of_cards() > candidates.size()) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
// Partial Fisher-Yates on a copy: each physical card can appear only once,
|
||||
// and the hand's order and contents stay unchanged.
|
||||
QList<Server_Card *> cardsToReveal;
|
||||
for (int i = 0; i < cmd.number_of_cards(); ++i) {
|
||||
const int selected = rng->rand(i, candidates.size() - 1);
|
||||
candidates.swapItemsAt(i, selected);
|
||||
cardsToReveal.append(candidates.at(i));
|
||||
}
|
||||
|
||||
// -2 in the public event tells other participants only how many cards were revealed.
|
||||
return revealCards(zone, cardsToReveal, {-2}, cmd.has_player_id(), cmd.player_id(), false, ges);
|
||||
}
|
||||
|
||||
Response::ResponseCode
|
||||
Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
|
||||
{
|
||||
Server_CardZone *zone = nullptr;
|
||||
const Response::ResponseCode result =
|
||||
resolveRevealZone(cmd.zone_name(), cmd.has_player_id(), cmd.player_id(), zone);
|
||||
if (result != Response::RespOk) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<Server_Card *> cardsToReveal;
|
||||
if (cmd.top_cards() != -1) {
|
||||
for (int i = 0; i < cmd.top_cards(); i++) {
|
||||
|
|
@ -1547,15 +1593,28 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo
|
|||
}
|
||||
}
|
||||
|
||||
const QList<int> cardIdsForOthers(cmd.card_id().begin(), cmd.card_id().end());
|
||||
return revealCards(zone, cardsToReveal, cardIdsForOthers, cmd.has_player_id(), cmd.player_id(),
|
||||
cmd.grant_write_access(), ges);
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_AbstractPlayer::revealCards(Server_CardZone *zone,
|
||||
const QList<Server_Card *> &cardsToReveal,
|
||||
const QList<int> &cardIdsForOthers,
|
||||
bool hasRecipient,
|
||||
int recipientId,
|
||||
bool grantWriteAccess,
|
||||
GameEventStorage &ges)
|
||||
{
|
||||
Event_RevealCards eventOthers;
|
||||
eventOthers.set_grant_write_access(cmd.grant_write_access());
|
||||
eventOthers.set_grant_write_access(grantWriteAccess);
|
||||
eventOthers.set_zone_name(zone->getName().toStdString());
|
||||
eventOthers.set_number_of_cards(cardsToReveal.size());
|
||||
for (auto cardId : cmd.card_id()) {
|
||||
for (int cardId : cardIdsForOthers) {
|
||||
eventOthers.add_card_id(cardId);
|
||||
}
|
||||
if (cmd.has_player_id()) {
|
||||
eventOthers.set_other_player_id(cmd.player_id());
|
||||
if (hasRecipient) {
|
||||
eventOthers.set_other_player_id(recipientId);
|
||||
}
|
||||
|
||||
Event_RevealCards eventPrivate(eventOthers);
|
||||
|
|
@ -1592,19 +1651,19 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo
|
|||
}
|
||||
}
|
||||
|
||||
if (cmd.has_player_id()) {
|
||||
if (cmd.grant_write_access()) {
|
||||
zone->addWritePermission(cmd.player_id());
|
||||
if (hasRecipient) {
|
||||
if (grantWriteAccess) {
|
||||
zone->addWritePermission(recipientId);
|
||||
}
|
||||
|
||||
if (isJudge()) {
|
||||
ges.setOverwriteOwnership(true);
|
||||
}
|
||||
|
||||
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, cmd.player_id());
|
||||
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, recipientId);
|
||||
ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers);
|
||||
} else {
|
||||
if (cmd.grant_write_access()) {
|
||||
if (grantWriteAccess) {
|
||||
const QList<int> &participantIds = game->getParticipants().keys();
|
||||
for (int anyParticipantId : participantIds) {
|
||||
zone->addWritePermission(anyParticipantId);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,18 @@ private:
|
|||
void sendCreateTokenEvents(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord, GameEventStorage &ges);
|
||||
void getPlayerProperties(ServerInfo_PlayerProperties &result) override;
|
||||
|
||||
/// Validates the preconditions shared by every reveal and resolves the source zone.
|
||||
Response::ResponseCode
|
||||
resolveRevealZone(const std::string &zoneName, bool hasRecipient, int recipientId, Server_CardZone *&zone);
|
||||
/// Builds the private and public reveal events for an already chosen set of cards and enqueues them.
|
||||
Response::ResponseCode revealCards(Server_CardZone *zone,
|
||||
const QList<Server_Card *> &cardsToReveal,
|
||||
const QList<int> &cardIdsForOthers,
|
||||
bool hasRecipient,
|
||||
int recipientId,
|
||||
bool grantWriteAccess,
|
||||
GameEventStorage &ges);
|
||||
|
||||
protected:
|
||||
bool conceded;
|
||||
DeckList *deck;
|
||||
|
|
@ -163,6 +175,8 @@ public:
|
|||
cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||
virtual Response::ResponseCode
|
||||
cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||
Response::ResponseCode
|
||||
cmdRevealRandomCards(const Command_RevealRandomCards &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||
virtual Response::ResponseCode cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd,
|
||||
ResponseContainer &rc,
|
||||
GameEventStorage &ges) override;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue