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:
Basile Clement 2023-03-03 15:54:51 +00:00 committed by GitHub
parent 4558b1c7ef
commit 42e7a8b423
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 300 additions and 83 deletions

View file

@ -588,7 +588,7 @@ void CardDatabase::refreshCachedReverseRelatedCards()
} }
auto *newCardRelation = new CardRelation( auto *newCardRelation = new CardRelation(
card->getName(), cardRelation->getDoesAttach(), cardRelation->getIsCreateAllExclusion(), card->getName(), cardRelation->getAttachType(), cardRelation->getIsCreateAllExclusion(),
cardRelation->getIsVariable(), cardRelation->getDefaultCount(), cardRelation->getIsPersistent()); cardRelation->getIsVariable(), cardRelation->getDefaultCount(), cardRelation->getIsPersistent());
cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation); cards.value(targetCard)->addReverseRelatedCards2Me(newCardRelation);
} }
@ -672,12 +672,12 @@ bool CardDatabase::saveCustomTokensToFile()
} }
CardRelation::CardRelation(const QString &_name, CardRelation::CardRelation(const QString &_name,
bool _doesAttach, AttachType _attachType,
bool _isCreateAllExclusion, bool _isCreateAllExclusion,
bool _isVariableCount, bool _isVariableCount,
int _defaultCount, int _defaultCount,
bool _isPersistent) bool _isPersistent)
: name(_name), doesAttach(_doesAttach), isCreateAllExclusion(_isCreateAllExclusion), : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion),
isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent) isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent)
{ {
} }

View file

@ -452,9 +452,17 @@ signals:
class CardRelation : public QObject class CardRelation : public QObject
{ {
Q_OBJECT Q_OBJECT
public:
enum AttachType
{
DoesNotAttach = 0,
AttachTo = 1,
TransformInto = 2,
};
private: private:
QString name; QString name;
bool doesAttach; AttachType attachType;
bool isCreateAllExclusion; bool isCreateAllExclusion;
bool isVariableCount; bool isVariableCount;
int defaultCount; int defaultCount;
@ -462,7 +470,7 @@ private:
public: public:
explicit CardRelation(const QString &_name = QString(), explicit CardRelation(const QString &_name = QString(),
bool _doesAttach = false, AttachType _attachType = DoesNotAttach,
bool _isCreateAllExclusion = false, bool _isCreateAllExclusion = false,
bool _isVariableCount = false, bool _isVariableCount = false,
int _defaultCount = 1, int _defaultCount = 1,
@ -472,13 +480,32 @@ public:
{ {
return name; return name;
} }
AttachType getAttachType() const
{
return attachType;
}
bool getDoesAttach() const bool getDoesAttach() const
{ {
return doesAttach; return attachType != DoesNotAttach;
}
bool getDoesTransform() const
{
return attachType == TransformInto;
}
QString getAttachTypeAsString() const
{
switch (attachType) {
case AttachTo:
return "attach";
case TransformInto:
return "transform";
default:
return "";
}
} }
bool getCanCreateAnother() const bool getCanCreateAnother() const
{ {
return !doesAttach; return !getDoesAttach();
} }
bool getIsCreateAllExclusion() const bool getIsCreateAllExclusion() const
{ {

View file

@ -224,7 +224,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
sets.insert(setName, setInfo); sets.insert(setName, setInfo);
// related cards // related cards
} else if (xmlName == "related" || xmlName == "reverse-related") { } else if (xmlName == "related" || xmlName == "reverse-related") {
bool attach = false; CardRelation::AttachType attach = CardRelation::DoesNotAttach;
bool exclude = false; bool exclude = false;
bool variable = false; bool variable = false;
int count = 1; int count = 1;
@ -246,7 +246,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
} }
if (attrs.hasAttribute("attach")) { if (attrs.hasAttribute("attach")) {
attach = true; attach = CardRelation::AttachTo;
} }
if (attrs.hasAttribute("exclude")) { if (attrs.hasAttribute("exclude")) {

View file

@ -182,7 +182,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
} }
// related cards // related cards
} else if (xmlName == "related" || xmlName == "reverse-related") { } else if (xmlName == "related" || xmlName == "reverse-related") {
bool attach = false; CardRelation::AttachType attachType = CardRelation::DoesNotAttach;
bool exclude = false; bool exclude = false;
bool variable = false; bool variable = false;
bool persistent = false; bool persistent = false;
@ -205,7 +205,8 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
} }
if (attrs.hasAttribute("attach")) { if (attrs.hasAttribute("attach")) {
attach = true; attachType = attrs.value("attach").toString() == "transform" ? CardRelation::TransformInto
: CardRelation::AttachTo;
} }
if (attrs.hasAttribute("exclude")) { if (attrs.hasAttribute("exclude")) {
@ -216,7 +217,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
persistent = true; persistent = true;
} }
auto *relation = new CardRelation(cardName, attach, exclude, variable, count, persistent); auto *relation = new CardRelation(cardName, attachType, exclude, variable, count, persistent);
if (xmlName == "reverse-related") { if (xmlName == "reverse-related") {
reverseRelatedCards << relation; reverseRelatedCards << relation;
} else { } else {
@ -294,7 +295,7 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
for (auto i : related) { for (auto i : related) {
xml.writeStartElement("related"); xml.writeStartElement("related");
if (i->getDoesAttach()) { if (i->getDoesAttach()) {
xml.writeAttribute("attach", "attach"); xml.writeAttribute("attach", i->getAttachTypeAsString());
} }
if (i->getIsCreateAllExclusion()) { if (i->getIsCreateAllExclusion()) {
xml.writeAttribute("exclude", "exclude"); xml.writeAttribute("exclude", "exclude");
@ -318,7 +319,7 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
for (auto i : reverseRelated) { for (auto i : reverseRelated) {
xml.writeStartElement("reverse-related"); xml.writeStartElement("reverse-related");
if (i->getDoesAttach()) { if (i->getDoesAttach()) {
xml.writeAttribute("attach", "attach"); xml.writeAttribute("attach", i->getAttachTypeAsString());
} }
if (i->getIsCreateAllExclusion()) { if (i->getIsCreateAllExclusion()) {

View file

@ -1677,7 +1677,7 @@ void Player::actCreateAllRelatedCards()
dbName = cardRelationAll->getName(); dbName = cardRelationAll->getName();
bool persistent = cardRelationAll->getIsPersistent(); bool persistent = cardRelationAll->getIsPersistent();
for (int i = 0; i < cardRelationAll->getDefaultCount(); ++i) { for (int i = 0; i < cardRelationAll->getDefaultCount(); ++i) {
createCard(sourceCard, dbName, false, persistent); createCard(sourceCard, dbName, CardRelation::DoesNotAttach, persistent);
} }
++tokensTypesCreated; ++tokensTypesCreated;
if (tokensTypesCreated == 1) { if (tokensTypesCreated == 1) {
@ -1692,7 +1692,7 @@ void Player::actCreateAllRelatedCards()
dbName = cardRelationNotExcluded->getName(); dbName = cardRelationNotExcluded->getName();
bool persistent = cardRelationNotExcluded->getIsPersistent(); bool persistent = cardRelationNotExcluded->getIsPersistent();
for (int i = 0; i < cardRelationNotExcluded->getDefaultCount(); ++i) { for (int i = 0; i < cardRelationNotExcluded->getDefaultCount(); ++i) {
createCard(sourceCard, dbName, false, persistent); createCard(sourceCard, dbName, CardRelation::DoesNotAttach, persistent);
} }
++tokensTypesCreated; ++tokensTypesCreated;
if (tokensTypesCreated == 1) { if (tokensTypesCreated == 1) {
@ -1731,23 +1731,22 @@ bool Player::createRelatedFromRelation(const CardItem *sourceCard, const CardRel
return false; return false;
} }
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
createCard(sourceCard, dbName, false, persistent); createCard(sourceCard, dbName, CardRelation::DoesNotAttach, persistent);
} }
} else if (cardRelation->getDefaultCount() > 1) { } else if (cardRelation->getDefaultCount() > 1) {
for (int i = 0; i < cardRelation->getDefaultCount(); ++i) { for (int i = 0; i < cardRelation->getDefaultCount(); ++i) {
createCard(sourceCard, dbName, false, persistent); createCard(sourceCard, dbName, CardRelation::DoesNotAttach, persistent);
} }
} else { } else {
if (cardRelation->getDoesAttach()) { createCard(sourceCard, dbName, cardRelation->getAttachType(), persistent);
createAttachedCard(sourceCard, dbName, persistent);
} else {
createCard(sourceCard, dbName, false, persistent);
}
} }
return true; return true;
} }
void Player::createCard(const CardItem *sourceCard, const QString &dbCardName, bool attach, bool persistent) void Player::createCard(const CardItem *sourceCard,
const QString &dbCardName,
CardRelation::AttachType attachType,
bool persistent)
{ {
CardInfoPtr cardInfo = db->getCard(dbCardName); CardInfoPtr cardInfo = db->getCard(dbCardName);
@ -1786,18 +1785,24 @@ void Player::createCard(const CardItem *sourceCard, const QString &dbCardName, b
cmd.set_x(gridPoint.x()); cmd.set_x(gridPoint.x());
cmd.set_y(gridPoint.y()); cmd.set_y(gridPoint.y());
if (attach) { switch (attachType) {
cmd.set_target_card_id(sourceCard->getId()); case CardRelation::DoesNotAttach:
break;
case CardRelation::AttachTo:
cmd.set_target_card_id(sourceCard->getId());
cmd.set_target_mode(Command_CreateToken::ATTACH_TO);
break;
case CardRelation::TransformInto:
cmd.set_target_card_id(sourceCard->getId());
cmd.set_target_mode(Command_CreateToken::TRANSFORM_INTO);
break;
} }
sendGameCommand(cmd); sendGameCommand(cmd);
} }
void Player::createAttachedCard(const CardItem *sourceCard, const QString &dbCardName, bool persistent)
{
createCard(sourceCard, dbCardName, true, persistent);
}
void Player::actSayMessage() void Player::actSayMessage()
{ {
auto *a = qobject_cast<QAction *>(sender()); auto *a = qobject_cast<QAction *>(sender());
@ -3550,6 +3555,7 @@ void Player::addRelatedCardActions(const CardItem *card, QMenu *cardMenu)
CardInfoPtr relatedCard = db->getCard(cardRelation->getName()); CardInfoPtr relatedCard = db->getCard(cardRelation->getName());
if (relatedCard == nullptr) if (relatedCard == nullptr)
continue; continue;
QString relatedCardName; QString relatedCardName;
if (relatedCard->getPowTough().size() > 0) { if (relatedCard->getPowTough().size() > 0) {
relatedCardName = relatedCard->getPowTough() + " " + relatedCard->getName(); // "n/n name" relatedCardName = relatedCard->getPowTough() + " " + relatedCard->getName(); // "n/n name"
@ -3559,7 +3565,8 @@ void Player::addRelatedCardActions(const CardItem *card, QMenu *cardMenu)
QString text = tr("Token: "); QString text = tr("Token: ");
if (cardRelation->getDoesAttach()) { if (cardRelation->getDoesAttach()) {
text += tr("Attach to ") + "\"" + relatedCardName + "\""; text +=
tr(cardRelation->getDoesTransform() ? "Transform into " : "Attach to ") + "\"" + relatedCardName + "\"";
} else if (cardRelation->getIsVariable()) { } else if (cardRelation->getIsVariable()) {
text += "X " + relatedCardName; text += "X " + relatedCardName;
} else if (cardRelation->getDefaultCount() != 1) { } else if (cardRelation->getDefaultCount() != 1) {

View file

@ -287,9 +287,10 @@ private:
bool allCards); bool allCards);
void addRelatedCardActions(const CardItem *card, QMenu *cardMenu); void addRelatedCardActions(const CardItem *card, QMenu *cardMenu);
void addRelatedCardView(const CardItem *card, QMenu *cardMenu); void addRelatedCardView(const CardItem *card, QMenu *cardMenu);
void void createCard(const CardItem *sourceCard,
createCard(const CardItem *sourceCard, const QString &dbCardName, bool attach = false, bool persistent = false); const QString &dbCardName,
void createAttachedCard(const CardItem *sourceCard, const QString &dbCardName, bool persistent = false); CardRelation::AttachType attach = CardRelation::DoesNotAttach,
bool persistent = false);
bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation); bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation);
QRectF bRect; QRectF bRect;

View file

@ -1,6 +1,14 @@
syntax = "proto2"; syntax = "proto2";
import "game_commands.proto"; import "game_commands.proto";
message Command_CreateToken { message Command_CreateToken {
enum TargetMode {
// Attach the target to the token
ATTACH_TO = 0;
// Transform the target into the token
TRANSFORM_INTO = 1;
}
extend GameCommand { extend GameCommand {
optional Command_CreateToken ext = 1010; optional Command_CreateToken ext = 1010;
} }
@ -14,4 +22,7 @@ message Command_CreateToken {
optional sint32 y = 8; optional sint32 y = 8;
optional string target_zone = 9; optional string target_zone = 9;
optional sint32 target_card_id = 10 [default = -1]; optional sint32 target_card_id = 10 [default = -1];
// What to do with the target card. Ignored if there is no target card.
optional TargetMode target_mode = 11;
} }

View file

@ -19,13 +19,18 @@
***************************************************************************/ ***************************************************************************/
#include "server_card.h" #include "server_card.h"
#include "pb/event_set_card_attr.pb.h"
#include "pb/event_set_card_counter.pb.h"
#include "pb/serverinfo_card.pb.h" #include "pb/serverinfo_card.pb.h"
#include "server_cardzone.h" #include "server_cardzone.h"
#include "server_player.h" #include "server_player.h"
#include <QVariant>
Server_Card::Server_Card(QString _name, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone) Server_Card::Server_Card(QString _name, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone)
: zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), name(_name), tapped(false), attacking(false), : zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), name(_name), tapped(false), attacking(false),
facedown(false), color(), ptString(), annotation(), destroyOnZoneChange(false), doesntUntap(false), parentCard(0) facedown(false), color(), ptString(), annotation(), destroyOnZoneChange(false), doesntUntap(false), parentCard(0),
stashedCard(nullptr)
{ {
} }
@ -37,6 +42,11 @@ Server_Card::~Server_Card()
if (parentCard) if (parentCard)
parentCard->removeAttachedCard(this); parentCard->removeAttachedCard(this);
if (stashedCard) {
stashedCard->deleteLater();
stashedCard = nullptr;
}
} }
void Server_Card::resetState() void Server_Card::resetState()
@ -51,11 +61,20 @@ void Server_Card::resetState()
QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, bool allCards) QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, bool allCards)
{ {
if (attribute == AttrTapped && avalue != "1" && allCards && doesntUntap)
return QVariant(tapped).toString();
return setAttribute(attribute, avalue);
}
QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, Event_SetCardAttr *event)
{
if (event)
event->set_attribute(attribute);
switch (attribute) { switch (attribute) {
case AttrTapped: { case AttrTapped: {
bool value = avalue == "1"; setTapped(avalue == "1");
if (!(!value && allCards && doesntUntap))
setTapped(value);
break; break;
} }
case AttrAttacking: case AttrAttacking:
@ -69,6 +88,8 @@ QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue
break; break;
case AttrPT: case AttrPT:
setPT(avalue); setPT(avalue);
if (event)
event->set_attr_value(getPT().toStdString());
return getPT(); return getPT();
case AttrAnnotation: case AttrAnnotation:
setAnnotation(avalue); setAnnotation(avalue);
@ -77,15 +98,22 @@ QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue
setDoesntUntap(avalue == "1"); setDoesntUntap(avalue == "1");
break; break;
} }
if (event)
event->set_attr_value(avalue.toStdString());
return avalue; return avalue;
} }
void Server_Card::setCounter(int id, int value) void Server_Card::setCounter(int id, int value, Event_SetCardCounter *event)
{ {
if (value) if (value)
counters.insert(id, value); counters.insert(id, value);
else else
counters.remove(id); counters.remove(id);
if (event) {
event->set_counter_id(id);
event->set_counter_value(value);
}
} }
void Server_Card::setParentCard(Server_Card *_parentCard) void Server_Card::setParentCard(Server_Card *_parentCard)

View file

@ -28,6 +28,8 @@
#include <QString> #include <QString>
class Server_CardZone; class Server_CardZone;
class Event_SetCardCounter;
class Event_SetCardAttr;
class Server_Card : public Server_ArrowTarget class Server_Card : public Server_ArrowTarget
{ {
@ -49,6 +51,7 @@ private:
Server_Card *parentCard; Server_Card *parentCard;
QList<Server_Card *> attachedCards; QList<Server_Card *> attachedCards;
Server_Card *stashedCard;
public: public:
Server_Card(QString _name, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone = 0); Server_Card(QString _name, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone = 0);
@ -141,7 +144,7 @@ public:
{ {
name = _name; name = _name;
} }
void setCounter(int id, int value); void setCounter(int id, int value, Event_SetCardCounter *event = nullptr);
void setTapped(bool _tapped) void setTapped(bool _tapped)
{ {
tapped = _tapped; tapped = _tapped;
@ -183,9 +186,31 @@ public:
{ {
attachedCards.removeOne(card); attachedCards.removeOne(card);
} }
void setStashedCard(Server_Card *card)
{
// setStashedCard should only be called on creation of a new card, so
// there should never be an already existing stashed card.
Q_ASSERT(!stashedCard);
// Stashed cards can't themselves have stashed cards, and tokens can't
// be stashed.
if (card->stashedCard || card->getDestroyOnZoneChange()) {
stashedCard = card->takeStashedCard();
card->deleteLater();
} else {
stashedCard = card;
}
}
Server_Card *takeStashedCard()
{
Server_Card *oldStashedCard = stashedCard;
stashedCard = nullptr;
return oldStashedCard;
}
void resetState(); void resetState();
QString setAttribute(CardAttribute attribute, const QString &avalue, bool allCards); QString setAttribute(CardAttribute attribute, const QString &avalue, bool allCards);
QString setAttribute(CardAttribute attribute, const QString &avalue, Event_SetCardAttr *event = nullptr);
void getInfo(ServerInfo_Card *info); void getInfo(ServerInfo_Card *info);
}; };

View file

@ -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, Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges,
Server_CardZone *startzone, Server_CardZone *startzone,
const QList<const CardToMove *> &_cards, 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())); event.set_card_id(static_cast<google::protobuf::uint32>(card->getId()));
ges.enqueueGameEvent(event, playerId); ges.enqueueGameEvent(event, playerId);
card->deleteLater(); if (Server_Card *stashedCard = card->takeStashedCard()) {
} else { 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; ++xIndex;
int newX = xCoord + xIndex; int newX = xCoord + xIndex;
@ -622,10 +663,7 @@ void Server_Player::unattachCard(GameEventStorage &ges, Server_Card *card)
Server_Card *parentCard = card->getParentCard(); Server_Card *parentCard = card->getParentCard();
card->setParentCard(nullptr); card->setParentCard(nullptr);
Event_AttachCard event; ges.enqueueGameEvent(makeAttachCardEvent(card), playerId);
event.set_start_zone(zone->getName().toStdString());
event.set_card_id(card->getId());
ges.enqueueGameEvent(event, playerId);
auto *cardToMove = new CardToMove; auto *cardToMove = new CardToMove;
cardToMove->set_card_id(card->getId()); cardToMove->set_card_id(card->getId());
@ -1280,13 +1318,7 @@ Server_Player::cmdAttachCard(const Command_AttachCard &cmd, ResponseContainer &
delete cardToMove; delete cardToMove;
} }
Event_AttachCard event; ges.enqueueGameEvent(makeAttachCardEvent(card, targetCard), playerId);
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);
startzone->fixFreeSpaces(ges); startzone->fixFreeSpaces(ges);
} else { } else {
@ -1315,9 +1347,40 @@ Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer
return Response::RespNameNotFound; return Response::RespNameNotFound;
} }
QString cardName = nameFromStdString(cmd.card_name());
int xCoord = cmd.x(); int xCoord = cmd.x();
int yCoord = cmd.y(); 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()) { if (zone->hasCoords()) {
xCoord = zone->getFreeGridColumn(xCoord, yCoord, cardName, false); 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()); card->setDestroyOnZoneChange(cmd.destroy_on_zone_change());
zone->insertCard(card, xCoord, yCoord); zone->insertCard(card, xCoord, yCoord);
ges.enqueueGameEvent(makeCreateTokenEvent(zone, card, xCoord, yCoord), playerId);
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);
// check if the token is a replacement for an existing card // check if the token is a replacement for an existing card
if (cmd.target_card_id() < 0) { if (!targetCard) {
return Response::RespOk; return Response::RespOk;
} }
Command_AttachCard cmd2; switch (cmd.target_mode()) {
cmd2.set_start_zone(cmd.target_zone()); case Command_CreateToken::ATTACH_TO: {
cmd2.set_card_id(cmd.target_card_id()); 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_player_id(zone->getPlayer()->getPlayerId());
cmd2.set_target_zone(cmd.zone()); cmd2.set_target_zone(cmd.zone());
cmd2.set_target_card_id(card->getId()); 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 Response::ResponseCode
@ -1517,13 +1636,10 @@ Server_Player::cmdSetCardCounter(const Command_SetCardCounter &cmd, ResponseCont
return Response::RespNameNotFound; return Response::RespNameNotFound;
} }
card->setCounter(cmd.counter_id(), cmd.counter_value());
Event_SetCardCounter event; Event_SetCardCounter event;
event.set_zone_name(zone->getName().toStdString()); event.set_zone_name(zone->getName().toStdString());
event.set_card_id(card->getId()); event.set_card_id(card->getId());
event.set_counter_id(cmd.counter_id()); card->setCounter(cmd.counter_id(), cmd.counter_value(), &event);
event.set_counter_value(cmd.counter_value());
ges.enqueueGameEvent(event, playerId); ges.enqueueGameEvent(event, playerId);
return Response::RespOk; return Response::RespOk;

View file

@ -370,12 +370,12 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet,
static const QRegularExpression meldNameRegex{"then meld them into ([^\\.]*)"}; static const QRegularExpression meldNameRegex{"then meld them into ([^\\.]*)"};
QString additionalName = meldNameRegex.match(text).captured(1); QString additionalName = meldNameRegex.match(text).captured(1);
if (!additionalName.isNull()) { if (!additionalName.isNull()) {
relatedCards.append(new CardRelation(additionalName, true)); relatedCards.append(new CardRelation(additionalName, CardRelation::TransformInto));
} }
} else { } else {
for (const QString &additionalName : name.split(" // ")) { for (const QString &additionalName : name.split(" // ")) {
if (additionalName != faceName) { if (additionalName != faceName) {
relatedCards.append(new CardRelation(additionalName, true)); relatedCards.append(new CardRelation(additionalName, CardRelation::TransformInto));
} }
} }
} }
@ -389,7 +389,8 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet,
if (givenRelated.contains("spellbook")) { if (givenRelated.contains("spellbook")) {
auto spbk = givenRelated.value("spellbook").toStringList(); auto spbk = givenRelated.value("spellbook").toStringList();
for (const QString &spbkName : spbk) { for (const QString &spbkName : spbk) {
relatedCards.append(new CardRelation(spbkName, false, false, false, 1, true)); relatedCards.append(
new CardRelation(spbkName, CardRelation::DoesNotAttach, false, false, 1, true));
} }
} }
} }