servatrice/common/server_card.cpp
Basile Clement 42e7a8b423
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
2023-03-03 16:54:51 +01:00

172 lines
5.5 KiB
C++

/***************************************************************************
* Copyright (C) 2008 by Max-Wilhelm Bruker *
* brukie@laptop *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#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 "server_cardzone.h"
#include "server_player.h"
#include <QVariant>
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),
facedown(false), color(), ptString(), annotation(), destroyOnZoneChange(false), doesntUntap(false), parentCard(0),
stashedCard(nullptr)
{
}
Server_Card::~Server_Card()
{
// setParentCard(0) leads to the item being removed from our list, so we can't iterate properly
while (!attachedCards.isEmpty())
attachedCards.first()->setParentCard(0);
if (parentCard)
parentCard->removeAttachedCard(this);
if (stashedCard) {
stashedCard->deleteLater();
stashedCard = nullptr;
}
}
void Server_Card::resetState()
{
counters.clear();
setTapped(false);
setAttacking(false);
setPT(QString());
setAnnotation(QString());
setDoesntUntap(false);
}
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) {
case AttrTapped: {
setTapped(avalue == "1");
break;
}
case AttrAttacking:
setAttacking(avalue == "1");
break;
case AttrFaceDown:
setFaceDown(avalue == "1");
break;
case AttrColor:
setColor(avalue);
break;
case AttrPT:
setPT(avalue);
if (event)
event->set_attr_value(getPT().toStdString());
return getPT();
case AttrAnnotation:
setAnnotation(avalue);
break;
case AttrDoesntUntap:
setDoesntUntap(avalue == "1");
break;
}
if (event)
event->set_attr_value(avalue.toStdString());
return avalue;
}
void Server_Card::setCounter(int id, int value, Event_SetCardCounter *event)
{
if (value)
counters.insert(id, value);
else
counters.remove(id);
if (event) {
event->set_counter_id(id);
event->set_counter_value(value);
}
}
void Server_Card::setParentCard(Server_Card *_parentCard)
{
if (parentCard)
parentCard->removeAttachedCard(this);
parentCard = _parentCard;
if (parentCard)
parentCard->addAttachedCard(this);
}
void Server_Card::getInfo(ServerInfo_Card *info)
{
QString displayedName = facedown ? QString() : name;
info->set_id(id);
info->set_name(displayedName.toStdString());
info->set_x(coord_x);
info->set_y(coord_y);
if (facedown) {
info->set_face_down(true);
}
info->set_tapped(tapped);
if (attacking) {
info->set_attacking(true);
}
if (!color.isEmpty()) {
info->set_color(color.toStdString());
}
if (!ptString.isEmpty()) {
info->set_pt(ptString.toStdString());
}
if (!annotation.isEmpty()) {
info->set_annotation(annotation.toStdString());
}
if (destroyOnZoneChange) {
info->set_destroy_on_zone_change(true);
}
if (doesntUntap) {
info->set_doesnt_untap(true);
}
QMapIterator<int, int> cardCounterIterator(counters);
while (cardCounterIterator.hasNext()) {
cardCounterIterator.next();
ServerInfo_CardCounter *counterInfo = info->add_counter_list();
counterInfo->set_id(cardCounterIterator.key());
counterInfo->set_value(cardCounterIterator.value());
}
if (parentCard) {
info->set_attach_player_id(parentCard->getZone()->getPlayer()->getPlayerId());
info->set_attach_zone(parentCard->getZone()->getName().toStdString());
info->set_attach_card_id(parentCard->getId());
}
}