mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2025-12-31 23:17:29 -08:00
Better support Double-Faced Cards (#4753)
* Better support Double-Faced Cards This patch allows cards to be (virtually) transformed into other cards while preserving their state, essentially implemeting the MTG mechanic of the same name. On the server side, this is implemented by allowing cards to be "stashed away". A card that is stashed away is not in any zone, but is instead owned by another card. When a token is destroyed due to a zone change, if it had a card stashed away, that card is placed in the target zone instead of the token. On the database side, `attach="transform"` is used on `<reverse>` and `<reverse-related>` to indicate that the created token should be transformed this way. Old servers ignore the new field in `Command_CreateToken` and will perform a regular attachment, as currently. * Address review comments * Prevent tokens from being stashed * format.sh
This commit is contained in:
@@ -376,6 +376,36 @@ void Server_Player::revealTopCardIfNeeded(Server_CardZone *zone, GameEventStorag
|
||||
}
|
||||
}
|
||||
|
||||
static Event_CreateToken makeCreateTokenEvent(Server_CardZone *zone, Server_Card *card, int xCoord, int yCoord)
|
||||
{
|
||||
Event_CreateToken event;
|
||||
event.set_zone_name(zone->getName().toStdString());
|
||||
event.set_card_id(card->getId());
|
||||
event.set_card_name(card->getName().toStdString());
|
||||
event.set_color(card->getColor().toStdString());
|
||||
event.set_pt(card->getPT().toStdString());
|
||||
event.set_annotation(card->getAnnotation().toStdString());
|
||||
event.set_destroy_on_zone_change(card->getDestroyOnZoneChange());
|
||||
event.set_x(xCoord);
|
||||
event.set_y(yCoord);
|
||||
return event;
|
||||
}
|
||||
|
||||
static Event_AttachCard makeAttachCardEvent(Server_Card *attachedCard, Server_Card *parentCard = nullptr)
|
||||
{
|
||||
Event_AttachCard event;
|
||||
event.set_start_zone(attachedCard->getZone()->getName().toStdString());
|
||||
event.set_card_id(attachedCard->getId());
|
||||
|
||||
if (parentCard) {
|
||||
event.set_target_player_id(parentCard->getZone()->getPlayer()->getPlayerId());
|
||||
event.set_target_zone(parentCard->getZone()->getName().toStdString());
|
||||
event.set_target_card_id(parentCard->getId());
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges,
|
||||
Server_CardZone *startzone,
|
||||
const QList<const CardToMove *> &_cards,
|
||||
@@ -485,8 +515,19 @@ Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges,
|
||||
event.set_card_id(static_cast<google::protobuf::uint32>(card->getId()));
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
|
||||
card->deleteLater();
|
||||
} else {
|
||||
if (Server_Card *stashedCard = card->takeStashedCard()) {
|
||||
stashedCard->setId(newCardId());
|
||||
ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()),
|
||||
playerId);
|
||||
card->deleteLater();
|
||||
card = stashedCard;
|
||||
} else {
|
||||
card->deleteLater();
|
||||
card = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (card) {
|
||||
++xIndex;
|
||||
int newX = xCoord + xIndex;
|
||||
|
||||
@@ -622,10 +663,7 @@ void Server_Player::unattachCard(GameEventStorage &ges, Server_Card *card)
|
||||
Server_Card *parentCard = card->getParentCard();
|
||||
card->setParentCard(nullptr);
|
||||
|
||||
Event_AttachCard event;
|
||||
event.set_start_zone(zone->getName().toStdString());
|
||||
event.set_card_id(card->getId());
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
ges.enqueueGameEvent(makeAttachCardEvent(card), playerId);
|
||||
|
||||
auto *cardToMove = new CardToMove;
|
||||
cardToMove->set_card_id(card->getId());
|
||||
@@ -1280,13 +1318,7 @@ Server_Player::cmdAttachCard(const Command_AttachCard &cmd, ResponseContainer &
|
||||
delete cardToMove;
|
||||
}
|
||||
|
||||
Event_AttachCard event;
|
||||
event.set_start_zone(startzone->getName().toStdString());
|
||||
event.set_card_id(card->getId());
|
||||
event.set_target_player_id(targetPlayer->getPlayerId());
|
||||
event.set_target_zone(targetzone->getName().toStdString());
|
||||
event.set_target_card_id(targetCard->getId());
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
ges.enqueueGameEvent(makeAttachCardEvent(card, targetCard), playerId);
|
||||
|
||||
startzone->fixFreeSpaces(ges);
|
||||
} else {
|
||||
@@ -1315,9 +1347,40 @@ Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
|
||||
QString cardName = nameFromStdString(cmd.card_name());
|
||||
int xCoord = cmd.x();
|
||||
int yCoord = cmd.y();
|
||||
|
||||
Server_Card *targetCard = nullptr;
|
||||
if (cmd.has_target_card_id()) {
|
||||
Server_CardZone *targetZone = zones.value(nameFromStdString(cmd.target_zone()));
|
||||
if (targetZone) {
|
||||
targetCard = targetZone->getCard(cmd.target_card_id());
|
||||
if (targetCard && cmd.target_mode() == Command_CreateToken::TRANSFORM_INTO) {
|
||||
if (targetCard->getParentCard()) {
|
||||
ges.enqueueGameEvent(makeAttachCardEvent(targetCard), playerId);
|
||||
}
|
||||
|
||||
for (Server_Card *attachedCard : targetCard->getAttachedCards()) {
|
||||
ges.enqueueGameEvent(makeAttachCardEvent(attachedCard),
|
||||
attachedCard->getZone()->getPlayer()->getPlayerId());
|
||||
}
|
||||
|
||||
if (zone->hasCoords() && zone == targetZone) {
|
||||
xCoord = targetCard->getX();
|
||||
yCoord = targetCard->getY();
|
||||
}
|
||||
|
||||
targetZone->removeCard(targetCard);
|
||||
|
||||
Event_DestroyCard event;
|
||||
event.set_zone_name(targetZone->getName().toStdString());
|
||||
event.set_card_id(static_cast<::google::protobuf::uint32>(cmd.target_card_id()));
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString cardName = nameFromStdString(cmd.card_name());
|
||||
if (zone->hasCoords()) {
|
||||
xCoord = zone->getFreeGridColumn(xCoord, yCoord, cardName, false);
|
||||
}
|
||||
@@ -1336,33 +1399,89 @@ Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer
|
||||
card->setDestroyOnZoneChange(cmd.destroy_on_zone_change());
|
||||
|
||||
zone->insertCard(card, xCoord, yCoord);
|
||||
|
||||
Event_CreateToken event;
|
||||
event.set_zone_name(zone->getName().toStdString());
|
||||
event.set_card_id(card->getId());
|
||||
event.set_card_name(card->getName().toStdString());
|
||||
event.set_color(card->getColor().toStdString());
|
||||
event.set_pt(card->getPT().toStdString());
|
||||
event.set_annotation(card->getAnnotation().toStdString());
|
||||
event.set_destroy_on_zone_change(card->getDestroyOnZoneChange());
|
||||
event.set_x(xCoord);
|
||||
event.set_y(yCoord);
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
ges.enqueueGameEvent(makeCreateTokenEvent(zone, card, xCoord, yCoord), playerId);
|
||||
|
||||
// check if the token is a replacement for an existing card
|
||||
if (cmd.target_card_id() < 0) {
|
||||
if (!targetCard) {
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Command_AttachCard cmd2;
|
||||
cmd2.set_start_zone(cmd.target_zone());
|
||||
cmd2.set_card_id(cmd.target_card_id());
|
||||
switch (cmd.target_mode()) {
|
||||
case Command_CreateToken::ATTACH_TO: {
|
||||
Command_AttachCard cmd2;
|
||||
cmd2.set_start_zone(cmd.target_zone());
|
||||
cmd2.set_card_id(cmd.target_card_id());
|
||||
|
||||
cmd2.set_target_player_id(zone->getPlayer()->getPlayerId());
|
||||
cmd2.set_target_zone(cmd.zone());
|
||||
cmd2.set_target_card_id(card->getId());
|
||||
cmd2.set_target_player_id(zone->getPlayer()->getPlayerId());
|
||||
cmd2.set_target_zone(cmd.zone());
|
||||
cmd2.set_target_card_id(card->getId());
|
||||
|
||||
return cmdAttachCard(cmd2, rc, ges);
|
||||
return cmdAttachCard(cmd2, rc, ges);
|
||||
}
|
||||
|
||||
case Command_CreateToken::TRANSFORM_INTO: {
|
||||
// Copy attributes that are not present in the CreateToken event
|
||||
Event_SetCardAttr event;
|
||||
event.set_zone_name(card->getZone()->getName().toStdString());
|
||||
event.set_card_id(card->getId());
|
||||
|
||||
if (card->getTapped() != targetCard->getTapped()) {
|
||||
card->setAttribute(AttrTapped, QVariant(targetCard->getTapped()).toString(), &event);
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
}
|
||||
|
||||
if (card->getAttacking() != targetCard->getAttacking()) {
|
||||
card->setAttribute(AttrAttacking, QVariant(targetCard->getAttacking()).toString(), &event);
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
}
|
||||
|
||||
if (card->getFaceDown() != targetCard->getFaceDown()) {
|
||||
card->setAttribute(AttrFaceDown, QVariant(targetCard->getFaceDown()).toString(), &event);
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
}
|
||||
|
||||
if (card->getDoesntUntap() != targetCard->getDoesntUntap()) {
|
||||
card->setAttribute(AttrDoesntUntap, QVariant(targetCard->getDoesntUntap()).toString(), &event);
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
}
|
||||
|
||||
// Copy counters
|
||||
QMapIterator<int, int> i(targetCard->getCounters());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
|
||||
Event_SetCardCounter event;
|
||||
event.set_zone_name(card->getZone()->getName().toStdString());
|
||||
event.set_card_id(card->getId());
|
||||
|
||||
card->setCounter(i.key(), i.value(), &event);
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
}
|
||||
|
||||
// Copy parent card
|
||||
if (Server_Card *parentCard = targetCard->getParentCard()) {
|
||||
targetCard->setParentCard(nullptr);
|
||||
card->setParentCard(parentCard);
|
||||
|
||||
ges.enqueueGameEvent(makeAttachCardEvent(card, parentCard), playerId);
|
||||
}
|
||||
|
||||
// Copy attachments
|
||||
while (!targetCard->getAttachedCards().isEmpty()) {
|
||||
Server_Card *attachedCard = targetCard->getAttachedCards().last();
|
||||
attachedCard->setParentCard(card);
|
||||
|
||||
ges.enqueueGameEvent(makeAttachCardEvent(attachedCard, card),
|
||||
attachedCard->getZone()->getPlayer()->getPlayerId());
|
||||
}
|
||||
|
||||
targetCard->resetState();
|
||||
card->setStashedCard(targetCard);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode
|
||||
@@ -1517,13 +1636,10 @@ Server_Player::cmdSetCardCounter(const Command_SetCardCounter &cmd, ResponseCont
|
||||
return Response::RespNameNotFound;
|
||||
}
|
||||
|
||||
card->setCounter(cmd.counter_id(), cmd.counter_value());
|
||||
|
||||
Event_SetCardCounter event;
|
||||
event.set_zone_name(zone->getName().toStdString());
|
||||
event.set_card_id(card->getId());
|
||||
event.set_counter_id(cmd.counter_id());
|
||||
event.set_counter_value(cmd.counter_value());
|
||||
card->setCounter(cmd.counter_id(), cmd.counter_value(), &event);
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
|
||||
return Response::RespOk;
|
||||
|
||||
Reference in New Issue
Block a user