1588 lines
43 KiB
C++
1588 lines
43 KiB
C++
/*---------------------------------------------
|
|
Card Instance
|
|
Instance of a given MTGCard in the game
|
|
Although there is only one MTGCard of each type, there can be as much Instances of it as needed in the game
|
|
--------------------------------------------
|
|
*/
|
|
|
|
#include "PrecompiledHeader.h"
|
|
|
|
#include "MTGCardInstance.h"
|
|
#include "CardDescriptor.h"
|
|
#include "Counters.h"
|
|
#include "Subtypes.h"
|
|
|
|
|
|
#include "AIPlayerBaka.h"
|
|
|
|
using namespace std;
|
|
|
|
SUPPORT_OBJECT_ANALYTICS(MTGCardInstance)
|
|
|
|
MTGCardInstance MTGCardInstance::AnyCard = MTGCardInstance();
|
|
MTGCardInstance MTGCardInstance::NoCard = MTGCardInstance();
|
|
|
|
MTGCardInstance::MTGCardInstance() :
|
|
CardPrimitive(), MTGCard(), Damageable(0, 0), view(NULL)
|
|
{
|
|
initMTGCI();
|
|
}
|
|
MTGCardInstance::MTGCardInstance(MTGCard * card, MTGPlayerCards * arg_belongs_to) :
|
|
CardPrimitive(card->data), MTGCard(card), Damageable(0, card->data->getToughness()), view(NULL)
|
|
{
|
|
if(arg_belongs_to)
|
|
if(arg_belongs_to->owner)
|
|
observer = arg_belongs_to->owner->getObserver();
|
|
|
|
initMTGCI();
|
|
model = card;
|
|
attacker = 0;
|
|
lifeOrig = life;
|
|
origpower = power;
|
|
basepower = origpower;
|
|
pbonus = 0;
|
|
origtoughness = toughness;
|
|
basetoughness = origtoughness;
|
|
tbonus = 0;
|
|
belongs_to = arg_belongs_to;
|
|
owner = NULL;
|
|
if (arg_belongs_to)
|
|
owner = arg_belongs_to->library->owner;
|
|
lastController = owner;
|
|
defenser = NULL;
|
|
banding = NULL;
|
|
life = toughness;
|
|
preventable = 0;
|
|
thatmuch = 0;
|
|
flanked = 0;
|
|
castMethod = Constants::NOT_CAST;
|
|
isSettingBase = 0;
|
|
isCDA = false;
|
|
isSwitchedPT = false;
|
|
isACopier = false;
|
|
bypassTC = false;
|
|
discarded = false;
|
|
copiedID = getId();
|
|
modifiedbAbi = 0;
|
|
LKIpower = power;
|
|
LKItoughness = toughness;
|
|
cardistargetted = 0;
|
|
cardistargetter = 0;
|
|
myconvertedcost = getManaCost()->getConvertedCost();
|
|
revealedLast = NULL;
|
|
MadnessPlay = false;
|
|
}
|
|
|
|
MTGCardInstance * MTGCardInstance::createSnapShot()
|
|
{
|
|
MTGCardInstance * snapShot = NEW MTGCardInstance(*this);
|
|
snapShot->previous = NULL;
|
|
snapShot->counters = NEW Counters(snapShot);
|
|
controller()->game->garbage->addCard(snapShot);
|
|
return snapShot;
|
|
}
|
|
|
|
void MTGCardInstance::copy(MTGCardInstance * card)
|
|
{
|
|
MTGCard * source = card->model;
|
|
CardPrimitive * data = source->data;
|
|
|
|
basicAbilities = card->basicAbilities;
|
|
origbasicAbilities = card->origbasicAbilities;
|
|
modifiedbAbi = card->modifiedbAbi;
|
|
for (size_t i = 0; i < data->types.size(); i++)
|
|
{
|
|
types.push_back(data->types[i]);
|
|
}
|
|
|
|
colors = data->colors;
|
|
|
|
manaCost.copy(data->getManaCost());
|
|
|
|
setText(""); //The text is retrieved from the data anyways
|
|
setName(data->name);
|
|
|
|
power = data->power;//layer 7a
|
|
toughness = data->toughness;//layer 7a
|
|
power += pbonus;//layer 7b
|
|
toughness += tbonus;//layer 7b
|
|
life = toughness;
|
|
lifeOrig = life;
|
|
magicText = data->magicText;
|
|
spellTargetType = data->spellTargetType;
|
|
alias = data->alias;
|
|
|
|
//Now this is dirty...
|
|
int backupid = mtgid;
|
|
int castMethodBackUP = this->castMethod;
|
|
mtgid = source->getId();
|
|
MTGCardInstance * oldStored = this->storedSourceCard;
|
|
Spell * spell = NEW Spell(observer, this);
|
|
observer = card->observer;
|
|
AbilityFactory af(observer);
|
|
af.addAbilities(observer->mLayers->actionLayer()->getMaxId(), spell);
|
|
delete spell;
|
|
if(observer->players[1]->playMode == Player::MODE_TEST_SUITE)
|
|
mtgid = backupid; // there must be a way to get the token id...
|
|
else
|
|
{
|
|
mtgid = card->getMTGId(); ///////////////////////////////////////////////////
|
|
setId = card->setId; // Copier/Cloner cards produces the same token...//
|
|
rarity = card->getRarity(); ///////////////////////////////////////////////////
|
|
}
|
|
castMethod = castMethodBackUP;
|
|
backupTargets = this->backupTargets;
|
|
storedCard = oldStored;
|
|
miracle = false;
|
|
}
|
|
|
|
MTGCardInstance::~MTGCardInstance()
|
|
{
|
|
SAFE_DELETE(counters);
|
|
if (previous != NULL)
|
|
{
|
|
//DebugTrace("MTGCardInstance::~MTGCardInstance(): deleting " << ToHex(previous));
|
|
SAFE_DELETE(previous);
|
|
}
|
|
}
|
|
|
|
int MTGCardInstance::init()
|
|
{
|
|
MTGCard::init();
|
|
CardPrimitive::init();
|
|
data = this;
|
|
X = 0;
|
|
castX = 0;
|
|
return 1;
|
|
}
|
|
|
|
void MTGCardInstance::initMTGCI()
|
|
{
|
|
X = 0;
|
|
sample = "";
|
|
model = NULL;
|
|
isToken = false;
|
|
lifeOrig = 0;
|
|
doDamageTest = 1;
|
|
skipDamageTestOnce = false;
|
|
belongs_to = NULL;
|
|
tapped = 0;
|
|
untapping = 0;
|
|
frozen = 0;
|
|
fresh = 0;
|
|
isMultiColored = 0;
|
|
isLeveler = 0;
|
|
enchanted = false;
|
|
CDenchanted = 0;
|
|
CDdamaged = 0;
|
|
blinked = false;
|
|
isExtraCostTarget = false;
|
|
morphed = false;
|
|
turningOver = false;
|
|
isMorphed = false;
|
|
isFlipped = false;
|
|
isPhased = false;
|
|
isCascaded = false;
|
|
phasedTurn = -1;
|
|
didattacked = 0;
|
|
didblocked = 0;
|
|
notblocked = 0;
|
|
sunburst = 0;
|
|
equipment = 0;
|
|
auras = 0;
|
|
damageToOpponent = false;
|
|
damageToController = false;
|
|
damageToCreature = false;
|
|
wasDealtDamage = false;
|
|
isDualWielding = false;
|
|
suspended = false;
|
|
isBestowed = false;
|
|
castMethod = Constants::NOT_CAST;
|
|
mPropertiesChangedSinceLastUpdate = false;
|
|
stillNeeded = true;
|
|
kicked = 0;
|
|
dredge = 0;
|
|
chooseacolor = -1;
|
|
chooseasubtype = "";
|
|
coinSide = -1;
|
|
isAttacking = NULL;
|
|
storedCard = NULL;
|
|
storedSourceCard = NULL;
|
|
myPair = NULL;
|
|
miracle = false;
|
|
countTrini = 0;
|
|
imprintedCards.clear();
|
|
attackCost = 0;
|
|
attackCostBackup = 0;
|
|
attackPlaneswalkerCost = 0;
|
|
attackPlaneswalkerCostBackup = 0;
|
|
blockCost = 0;
|
|
blockCostBackup = 0;
|
|
imprintG = 0;
|
|
imprintU = 0;
|
|
imprintR = 0;
|
|
imprintB = 0;
|
|
imprintW = 0;
|
|
currentimprintName = "";
|
|
imprintedNames.clear();
|
|
CountedObjects = 0;
|
|
|
|
for (int i = 0; i < ManaCost::MANA_PAID_WITH_SUSPEND +1; i++)
|
|
alternateCostPaid[i] = 0;
|
|
|
|
paymenttype = MTGAbility::PUT_INTO_PLAY;
|
|
reduxamount = 0;
|
|
summoningSickness = 1;
|
|
preventable = 0;
|
|
thatmuch = 0;
|
|
flanked = 0;
|
|
target = NULL;
|
|
playerTarget = NULL;
|
|
type_as_damageable = DAMAGEABLE_MTGCARDINSTANCE;
|
|
banding = NULL;
|
|
owner = NULL;
|
|
counters = NEW Counters(this);
|
|
previousZone = NULL;
|
|
previous = NULL;
|
|
next = NULL;
|
|
lastController = NULL;
|
|
regenerateTokens = 0;
|
|
blocked = false;
|
|
graveEffects = false;
|
|
exileEffects = false;
|
|
currentZone = NULL;
|
|
cardsAbilities = vector<MTGAbility *>();
|
|
data = this; //an MTGCardInstance point to itself for data, allows to update it without killing the underlying database item
|
|
|
|
if (observer && basicAbilities[(int)Constants::CHANGELING])
|
|
{//if the card is a changeling, it gains all creature subtypes
|
|
vector<string> values = MTGAllCards::getCreatureValuesById();
|
|
for (size_t i = 0; i < values.size(); ++i)
|
|
{
|
|
//Don' want to send any event to the gameObserver inside of initMCGI, so calling the parent setSubtype method instead of mine
|
|
CardPrimitive::setSubtype(values[i].c_str());
|
|
}
|
|
}
|
|
|
|
int colored = 0;
|
|
|
|
for (int i = Constants::MTG_COLOR_GREEN; i <= Constants::MTG_COLOR_WHITE; ++i)
|
|
{
|
|
if (this->hasColor(i))
|
|
++colored;
|
|
}
|
|
if (colored > 1)
|
|
{
|
|
isMultiColored = 1;
|
|
}
|
|
|
|
if(previous && previous->morphed && !turningOver)
|
|
{
|
|
morphed = true;
|
|
isMorphed = true;
|
|
}
|
|
}
|
|
|
|
const string MTGCardInstance::getDisplayName() const
|
|
{
|
|
return getName();
|
|
}
|
|
|
|
void MTGCardInstance::addType(int type)
|
|
{
|
|
bool before = hasType(type);
|
|
CardPrimitive::addType(type);
|
|
|
|
if (!before)
|
|
mPropertiesChangedSinceLastUpdate = true;
|
|
|
|
// If the card name is not set, set it to the type.
|
|
//This is a hack for "transform", used in combination with "losesubtypes" on Basic Lands
|
|
//See removeType below
|
|
if (!name.length())
|
|
setName(MTGAllCards::findType(type));
|
|
|
|
WEvent * e = NEW WEventCardChangeType(this, type, before, true);
|
|
if (observer)
|
|
observer->receiveEvent(e);
|
|
else
|
|
SAFE_DELETE(e);
|
|
}
|
|
|
|
void MTGCardInstance::addType(const string& type_text)
|
|
{
|
|
setSubtype(type_text);
|
|
}
|
|
|
|
void MTGCardInstance::setType(const string& type_text)
|
|
{
|
|
setSubtype(type_text);
|
|
}
|
|
|
|
void MTGCardInstance::setSubtype(const string& value)
|
|
{
|
|
int id = MTGAllCards::findType(value);
|
|
addType(id);
|
|
}
|
|
int MTGCardInstance::removeType(const string& value, int removeAll)
|
|
{
|
|
int id = MTGAllCards::findType(value);
|
|
return removeType(id, removeAll);
|
|
}
|
|
|
|
int MTGCardInstance::removeType(int id, int removeAll)
|
|
{
|
|
bool before = hasType(id);
|
|
int result = CardPrimitive::removeType(id, removeAll);
|
|
bool after = hasType(id);
|
|
if (before != after)
|
|
{
|
|
mPropertiesChangedSinceLastUpdate = true;
|
|
// Basic lands have the same name as their subtypes, and TargetChoosers don't make a distinction between name and type,
|
|
// so if we remove a subtype "Forest", we also need to remove its name.
|
|
//This means the card might lose its name, but usually when we force remove a type, we add another one just after that.
|
|
//see "AddType" above which force sets a name if necessary
|
|
if (name.compare(MTGAllCards::findType(id)) == 0)
|
|
setName("");
|
|
}
|
|
WEvent * e = NEW WEventCardChangeType(this, id, before, after);
|
|
if (observer)
|
|
observer->receiveEvent(e);
|
|
else
|
|
SAFE_DELETE(e);
|
|
return result;
|
|
}
|
|
|
|
int MTGCardInstance::isInPlay(GameObserver* game)
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
MTGGameZone * zone = game->players[i]->game->inPlay;
|
|
if (zone->hasCard(this) && !isPhased)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::afterDamage()
|
|
{
|
|
if(skipDamageTestOnce)
|
|
{
|
|
skipDamageTestOnce = false;
|
|
return 0;
|
|
}
|
|
if (!doDamageTest)
|
|
return 0;
|
|
doDamageTest = 0;
|
|
if (!isCreature())
|
|
return 0;
|
|
if (life <= 0 && isInPlay(observer))
|
|
{
|
|
return destroy();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::bury()
|
|
{
|
|
Player * p = controller();
|
|
if (basicAbilities[(int)Constants::EXILEDEATH])
|
|
{
|
|
p->game->putInZone(this, p->game->inPlay, owner->game->exile);
|
|
return 1;
|
|
}
|
|
if (!basicAbilities[(int)Constants::INDESTRUCTIBLE])
|
|
{
|
|
p->game->putInZone(this, p->game->inPlay, owner->game->graveyard);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
int MTGCardInstance::destroy()
|
|
{
|
|
if (!triggerRegenerate())
|
|
return bury();
|
|
return 0;
|
|
}
|
|
|
|
MTGGameZone * MTGCardInstance::getCurrentZone()
|
|
{
|
|
return currentZone;
|
|
}
|
|
|
|
int MTGCardInstance::has(int basicAbility)
|
|
{
|
|
return basicAbilities[basicAbility];
|
|
}
|
|
|
|
ManaCost* MTGCardInstance::getReducedManaCost()
|
|
{
|
|
return &reducedCost;
|
|
}
|
|
ManaCost* MTGCardInstance::getIncreasedManaCost()
|
|
{
|
|
return &increasedCost;
|
|
}
|
|
|
|
//sets card as attacked and sends events
|
|
void MTGCardInstance::eventattacked()
|
|
{
|
|
didattacked = 1;
|
|
WEvent * e = NEW WEventCardAttacked(this);
|
|
observer->receiveEvent(e);
|
|
}
|
|
|
|
//sets card as attacked alone and sends events
|
|
void MTGCardInstance::eventattackedAlone()
|
|
{
|
|
WEvent * e = NEW WEventCardAttackedAlone(this);
|
|
observer->receiveEvent(e);
|
|
}
|
|
|
|
//sets card as attacked and sends events
|
|
void MTGCardInstance::eventattackednotblocked()
|
|
{
|
|
didattacked = 1;
|
|
WEvent * e = NEW WEventCardAttackedNotBlocked(this);
|
|
observer->receiveEvent(e);
|
|
}
|
|
|
|
//sets card as attacked and sends events
|
|
void MTGCardInstance::eventattackedblocked(MTGCardInstance * opponent)
|
|
{
|
|
didattacked = 1;
|
|
WEvent * e = NEW WEventCardAttackedBlocked(this,opponent);
|
|
observer->receiveEvent(e);
|
|
}
|
|
|
|
//sets card as blocking and sends events
|
|
void MTGCardInstance::eventblocked(MTGCardInstance * opponent)
|
|
{
|
|
didblocked = 1;
|
|
WEvent * e = NEW WEventCardBlocked(this,opponent);
|
|
observer->receiveEvent(e);
|
|
}
|
|
|
|
//Taps the card
|
|
void MTGCardInstance::tap()
|
|
{
|
|
if (tapped)
|
|
return;
|
|
tapped = 1;
|
|
WEvent * e = NEW WEventCardTap(this, 0, 1);
|
|
observer->receiveEvent(e);
|
|
}
|
|
|
|
void MTGCardInstance::untap()
|
|
{
|
|
if (!tapped)
|
|
return;
|
|
tapped = 0;
|
|
WEvent * e = NEW WEventCardTap(this, 1, 0);
|
|
observer->receiveEvent(e);
|
|
}
|
|
|
|
void MTGCardInstance::setUntapping()
|
|
{
|
|
untapping = 1;
|
|
}
|
|
|
|
int MTGCardInstance::isUntapping()
|
|
{
|
|
return untapping;
|
|
}
|
|
|
|
//Tries to Untap the card
|
|
void MTGCardInstance::attemptUntap()
|
|
{
|
|
if (untapping)
|
|
{
|
|
untap();
|
|
untapping = 0;
|
|
}
|
|
}
|
|
|
|
//Tells if the card is tapped or not
|
|
int MTGCardInstance::isTapped()
|
|
{
|
|
return tapped;
|
|
}
|
|
|
|
int MTGCardInstance::regenerate()
|
|
{
|
|
if (has(Constants::CANTREGEN))
|
|
return 0;
|
|
return ++regenerateTokens;
|
|
}
|
|
|
|
int MTGCardInstance::triggerRegenerate()
|
|
{
|
|
if (!regenerateTokens)
|
|
return 0;
|
|
if (has(Constants::CANTREGEN))
|
|
return 0;
|
|
regenerateTokens--;
|
|
tap();
|
|
if(isCreature())
|
|
{
|
|
life = toughness;
|
|
initAttackersDefensers();
|
|
if (life < 1)
|
|
return 0; //regeneration didn't work (wither ?)
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::initAttackersDefensers()
|
|
{
|
|
setAttacker(0);
|
|
setDefenser(NULL);
|
|
banding = NULL;
|
|
blockers.clear();
|
|
blocked = 0;
|
|
didattacked = 0;
|
|
didblocked = 0;
|
|
return 1;
|
|
}
|
|
|
|
//Function to cleanup flags on a card (generally at the end of the turn)
|
|
int MTGCardInstance::cleanup()
|
|
{
|
|
initAttackersDefensers();
|
|
if (!observer || observer->currentPlayer == controller())
|
|
{
|
|
summoningSickness = 0;
|
|
}
|
|
if (previous && !previous->stillInUse())
|
|
{
|
|
//DebugTrace("MTGCardInstance::cleanup(): deleting " << ToHex(previous));
|
|
SAFE_DELETE(previous);
|
|
}
|
|
regenerateTokens = 0;
|
|
preventable = 0;
|
|
thatmuch = 0;
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::stillInUse()
|
|
{
|
|
if (observer->mLayers->actionLayer()->stillInUse(this))
|
|
return 1;
|
|
if (!previous)
|
|
return 0;
|
|
if (previous->stillNeeded)
|
|
{
|
|
previous->stillNeeded = false;
|
|
return 1;
|
|
}
|
|
return previous->stillInUse();
|
|
}
|
|
|
|
/* Summoning Sickness
|
|
* 212.3f A creature's activated ability with the tap symbol or the untap symbol in its activation cost
|
|
* can't be played unless the creature has been under its controller's control since the start of his or
|
|
* her most recent turn. A creature can't attack unless it has been under its controller's control
|
|
* since the start of his or her most recent turn. This rule is informally called the "summoning
|
|
* sickness" rule. Ignore this rule for creatures with haste (see rule 502.5).
|
|
*/
|
|
int MTGCardInstance::hasSummoningSickness()
|
|
{
|
|
if (!summoningSickness)
|
|
return 0;
|
|
if (basicAbilities[(int)Constants::HASTE])
|
|
return 0;
|
|
if (!isCreature())
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
MTGCardInstance * MTGCardInstance::changeController(Player * newController,bool notZone)
|
|
{
|
|
if(notZone)
|
|
{
|
|
lastController = newController;
|
|
return this;
|
|
}
|
|
Player * originalOwner = controller();
|
|
MTGCardInstance * copy = originalOwner->game->putInZone(this, this->currentZone, newController->game->inPlay);
|
|
//copy->summoningSickness = 1;
|
|
return copy;
|
|
}
|
|
|
|
Player * MTGCardInstance::controller()
|
|
{
|
|
return lastController;
|
|
}
|
|
|
|
int MTGCardInstance::canAttack()
|
|
{
|
|
if (basicAbilities[(int)Constants::CANTATTACK])
|
|
return 0;
|
|
if (tapped)
|
|
return 0;
|
|
if (hasSummoningSickness())
|
|
return 0;
|
|
if (basicAbilities[(int)Constants::DEFENSER] && !basicAbilities[(int)Constants::CANATTACK])
|
|
return 0;
|
|
if (!isCreature())
|
|
return 0;
|
|
if (!isInPlay(observer))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::addToToughness(int value)
|
|
{
|
|
toughness += value;
|
|
life += value;
|
|
doDamageTest = 1;
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::setToughness(int value)
|
|
{
|
|
toughness = value;
|
|
life = value;
|
|
doDamageTest = 1;
|
|
return 1;
|
|
}
|
|
|
|
void MTGCardInstance::stripPTbonus()
|
|
{
|
|
power -= pbonus;
|
|
addToToughness(-tbonus);
|
|
}
|
|
|
|
void MTGCardInstance::plusPTbonus(int p, int t)
|
|
{
|
|
pbonus += p;
|
|
tbonus += t;
|
|
}
|
|
|
|
void MTGCardInstance::minusPTbonus(int p, int t)
|
|
{
|
|
pbonus -= p;
|
|
tbonus -= t;
|
|
}
|
|
|
|
void MTGCardInstance::applyPTbonus()
|
|
{
|
|
power += pbonus;
|
|
addToToughness(tbonus);
|
|
}
|
|
|
|
void MTGCardInstance::addcounter(int p, int t)
|
|
{
|
|
stripPTbonus();
|
|
plusPTbonus(p,t);
|
|
applyPTbonus();
|
|
}
|
|
|
|
void MTGCardInstance::addptbonus(int p, int t)
|
|
{
|
|
stripPTbonus();
|
|
plusPTbonus(p,t);
|
|
applyPTbonus();
|
|
}
|
|
|
|
void MTGCardInstance::removecounter(int p, int t)
|
|
{
|
|
stripPTbonus();
|
|
minusPTbonus(p,t);
|
|
applyPTbonus();
|
|
}
|
|
|
|
void MTGCardInstance::removeptbonus(int p, int t)
|
|
{
|
|
stripPTbonus();
|
|
minusPTbonus(p,t);
|
|
applyPTbonus();
|
|
}
|
|
|
|
void MTGCardInstance::addbaseP(int p)
|
|
{
|
|
basepower = p;
|
|
power -= pbonus;
|
|
power = p;
|
|
power += pbonus;
|
|
}
|
|
|
|
void MTGCardInstance::addbaseT(int t)
|
|
{
|
|
basetoughness = t;
|
|
addToToughness(-tbonus);
|
|
addToToughness(t - toughness);
|
|
addToToughness(tbonus);
|
|
}
|
|
|
|
void MTGCardInstance::revertbaseP()
|
|
{
|
|
power -= pbonus;
|
|
power += origpower;
|
|
power -= basepower;
|
|
power += pbonus;
|
|
basepower = origpower;
|
|
}
|
|
|
|
void MTGCardInstance::revertbaseT()
|
|
{
|
|
addToToughness(-tbonus);
|
|
addToToughness(origtoughness);
|
|
addToToughness(-basetoughness);
|
|
addToToughness(tbonus);
|
|
basetoughness = origtoughness;
|
|
}
|
|
|
|
void MTGCardInstance::cdaPT(int p, int t)
|
|
{
|
|
origpower = p;
|
|
origtoughness = t;
|
|
setPower(p);
|
|
setToughness(t);
|
|
applyPTbonus();
|
|
}
|
|
|
|
void MTGCardInstance::switchPT(bool apply)
|
|
{
|
|
stripPTbonus();
|
|
swapP = power;
|
|
swapT = toughness;
|
|
power += origpower;
|
|
power -= swapP;
|
|
addToToughness(origtoughness);
|
|
addToToughness(-swapT);
|
|
applyPTbonus();
|
|
if(apply)
|
|
{
|
|
swapP = toughness;
|
|
swapT = power;
|
|
addToToughness(swapT);
|
|
addToToughness(-swapP);
|
|
setPower(swapP);
|
|
}
|
|
}
|
|
|
|
int MTGCardInstance::getCurrentPower()
|
|
{
|
|
if(!isInPlay(observer))
|
|
return LKIpower;
|
|
return power;
|
|
}
|
|
|
|
int MTGCardInstance::getCurrentToughness()
|
|
{
|
|
if(!isInPlay(observer))
|
|
return LKItoughness;
|
|
return toughness;
|
|
}
|
|
|
|
//check stack
|
|
bool MTGCardInstance::StackIsEmptyandSorcerySpeed()
|
|
{
|
|
if((getObserver()->mLayers->stackLayer()->count(0, NOT_RESOLVED) == 0) &&
|
|
(getObserver()->getCurrentGamePhase() == MTG_PHASE_FIRSTMAIN ||
|
|
getObserver()->getCurrentGamePhase() == MTG_PHASE_SECONDMAIN) &&
|
|
controller() == getObserver()->currentPlayer &&
|
|
!getObserver()->isInterrupting)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//check targetted?
|
|
bool MTGCardInstance::isTargetted()
|
|
{
|
|
if(controller()->game->reveal->cards.size() || controller()->opponent()->game->reveal->cards.size())
|
|
return false;
|
|
|
|
if(getObserver()->mLayers->stackLayer()->count(0, NOT_RESOLVED) != 0)
|
|
{
|
|
ActionStack * stack = observer->mLayers->stackLayer();
|
|
for (int i = stack->mObjects.size() - 1; i >= 0; i--)
|
|
{
|
|
Interruptible * current = ((Interruptible *) stack->mObjects[i]);
|
|
if ((current->type == ACTION_SPELL || current->type == ACTION_ABILITY) && current->state == NOT_RESOLVED)
|
|
{
|
|
if(current->type == ACTION_SPELL)
|
|
{
|
|
Spell * spell = (Spell *) current;
|
|
if(spell->getNextTarget() && spell->getNextTarget() == (Targetable*)this)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(cardistargetted)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
//check targetter?
|
|
bool MTGCardInstance::isTargetter()
|
|
{
|
|
if(controller()->game->reveal->cards.size() || controller()->opponent()->game->reveal->cards.size())
|
|
return false;
|
|
|
|
if(getObserver()->mLayers->stackLayer()->count(0, NOT_RESOLVED) != 0)
|
|
{
|
|
ActionStack * stack = observer->mLayers->stackLayer();
|
|
for (int i = stack->mObjects.size() - 1; i >= 0; i--)
|
|
{
|
|
Interruptible * current = ((Interruptible *) stack->mObjects[i]);
|
|
if ((current->type == ACTION_SPELL || current->type == ACTION_ABILITY) && current->state == NOT_RESOLVED)
|
|
{
|
|
if(current->type == ACTION_SPELL)
|
|
{
|
|
Spell * spell = (Spell *) current;
|
|
if(spell && spell->source == this)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(cardistargetter)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int MTGCardInstance::canBlock()
|
|
{
|
|
if (tapped)
|
|
return 0;
|
|
if (basicAbilities[(int)Constants::CANTBLOCK])
|
|
return 0;
|
|
if (!isCreature())
|
|
return 0;
|
|
if (!isInPlay(observer))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::canBlock(MTGCardInstance * opponent)
|
|
{
|
|
if (!canBlock())
|
|
return 0;
|
|
if (!opponent)
|
|
return 1;
|
|
if (!opponent->isAttacker())
|
|
return 0;
|
|
// Comprehensive rule 502.7f : If a creature with protection attacks, it can't be blocked by creatures that have the stated quality.
|
|
if (opponent->protectedAgainst(this))
|
|
return 0;
|
|
if (opponent->cantBeBlockedBy(this))
|
|
return 0;
|
|
if (this->cantBeBlockerOf(opponent))
|
|
return 0;
|
|
if (this->cantBeBlockerOfCard(opponent))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::UNBLOCKABLE])
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::ONEBLOCKER] && opponent->blocked)
|
|
return 0;
|
|
if((opponent->basicAbilities[(int)Constants::EVADEBIGGER]|| opponent->basicAbilities[(int)Constants::SKULK]) && power > opponent->power)
|
|
return 0;
|
|
if(opponent->basicAbilities[(int)Constants::STRONG] && power < opponent->power)
|
|
return 0;
|
|
if(this->basicAbilities[(int)Constants::WEAK] && power < opponent->power)
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::FEAR] && !(this->hasType(Subtypes::TYPE_ARTIFACT) || this->hasColor(Constants::MTG_COLOR_BLACK)))
|
|
return 0;
|
|
if (opponent->controller()->game->battlefield->hasAbility(Constants::LURE) && !opponent->has(Constants::LURE))
|
|
return 0;
|
|
//intimidate
|
|
if (opponent->basicAbilities[(int)Constants::INTIMIDATE] && !(this->hasType(Subtypes::TYPE_ARTIFACT)))
|
|
{
|
|
int canblock = 0;
|
|
for (int i = Constants::MTG_COLOR_GREEN; i <= Constants::MTG_COLOR_WHITE; ++i)
|
|
{
|
|
if (this->hasColor(i) && opponent->hasColor(i))
|
|
{
|
|
canblock = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!canblock)
|
|
return 0;
|
|
}
|
|
|
|
if (opponent->basicAbilities[(int)Constants::FLYING] && !(basicAbilities[(int)Constants::FLYING] || basicAbilities[(int)Constants::REACH]))
|
|
return 0;
|
|
//Can block only creatures with flying if has cloud
|
|
if (basicAbilities[(int)Constants::CLOUD] && !(opponent->basicAbilities[(int)Constants::FLYING]))
|
|
return 0;
|
|
// If opponent has shadow and a creature does not have either shadow or reachshadow it cannot be blocked
|
|
if (opponent->basicAbilities[(int)Constants::SHADOW] && !(basicAbilities[(int)Constants::SHADOW]
|
|
|| basicAbilities[(int)Constants::REACHSHADOW]))
|
|
return 0;
|
|
// If opponent does not have shadow and a creature has shadow it cannot be blocked
|
|
if (!opponent->basicAbilities[(int)Constants::SHADOW] && basicAbilities[(int)Constants::SHADOW])
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::HORSEMANSHIP] && !basicAbilities[(int)Constants::HORSEMANSHIP])
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::SWAMPWALK] && controller()->game->inPlay->hasType("swamp"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::FORESTWALK] && controller()->game->inPlay->hasType("forest"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::ISLANDWALK] && controller()->game->inPlay->hasType("island"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::MOUNTAINWALK] && controller()->game->inPlay->hasType("mountain"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::PLAINSWALK] && controller()->game->inPlay->hasType("plains"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::LEGENDARYWALK] && controller()->game->inPlay->hasPrimaryType("legendary","land"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::DESERTWALK] && controller()->game->inPlay->hasSpecificType("land","desert"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::SNOWSWAMPWALK] && controller()->game->inPlay->hasSpecificType("snow","swamp"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::SNOWFORESTWALK] && controller()->game->inPlay->hasSpecificType("snow","forest"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::SNOWISLANDWALK] && controller()->game->inPlay->hasSpecificType("snow","island"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::SNOWMOUNTAINWALK] && controller()->game->inPlay->hasSpecificType("snow","mountain"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::SNOWPLAINSWALK] && controller()->game->inPlay->hasSpecificType("snow","plains"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::SNOWWALK] && controller()->game->inPlay->hasPrimaryType("snow","land"))
|
|
return 0;
|
|
if (opponent->basicAbilities[(int)Constants::NONBASICWALK] && controller()->game->inPlay->hasTypeButNotType("land","basic"))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
JQuadPtr MTGCardInstance::getIcon()
|
|
{
|
|
return WResourceManager::Instance()->RetrieveCard(this, CACHE_THUMB);
|
|
}
|
|
|
|
ManaCost * MTGCardInstance::computeNewCost(MTGCardInstance * card,ManaCost * newCost, ManaCost * refCost, bool noTrinisphere)
|
|
{
|
|
if(!card)
|
|
return NULL;
|
|
|
|
if(card->getIncreasedManaCost()->getConvertedCost())
|
|
newCost->add(card->getIncreasedManaCost());
|
|
if(card->getReducedManaCost()->getConvertedCost())
|
|
newCost->remove(card->getReducedManaCost());
|
|
if(refCost->extraCosts)
|
|
newCost->extraCosts = refCost->extraCosts;
|
|
//affinity
|
|
int color = 0;
|
|
string type = "";
|
|
ManaCost * original = NEW ManaCost();
|
|
original->copy(newCost);
|
|
int reducem = 0;
|
|
bool resetCost = false;
|
|
for(unsigned int na = 0; na < card->cardsAbilities.size();na++)
|
|
{//start2
|
|
ANewAffinity * newAff = dynamic_cast<ANewAffinity*>(card->cardsAbilities[na]);
|
|
if(newAff)
|
|
{
|
|
if(!resetCost)
|
|
{
|
|
resetCost = true;
|
|
newCost->copy(original);
|
|
}
|
|
TargetChooserFactory tf(observer);
|
|
TargetChooser * tcn = tf.createTargetChooser(newAff->tcString,card,NULL);
|
|
|
|
for (int w = 0; w < 2; ++w)
|
|
{
|
|
Player *p = observer->players[w];
|
|
MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->stack, p->game->exile };
|
|
for (int k = 0; k < 6; k++)
|
|
{
|
|
MTGGameZone * z = zones[k];
|
|
if (tcn->targetsZone(z))
|
|
reducem += z->countByCanTarget(tcn);
|
|
}
|
|
}
|
|
SAFE_DELETE(tcn);
|
|
ManaCost * removingCost = ManaCost::parseManaCost(newAff->manaString);
|
|
for(int j = 0; j < reducem; j++)
|
|
newCost->remove(removingCost);
|
|
SAFE_DELETE(removingCost);
|
|
}
|
|
}//end2
|
|
if(card->has(Constants::AFFINITYARTIFACTS)||
|
|
card->has(Constants::AFFINITYFOREST)||
|
|
card->has(Constants::AFFINITYGREENCREATURES)||
|
|
card->has(Constants::AFFINITYISLAND)||
|
|
card->has(Constants::AFFINITYMOUNTAIN)||
|
|
card->has(Constants::AFFINITYPLAINS)||
|
|
card->has(Constants::AFFINITYSWAMP))
|
|
{//start3
|
|
if (card->has(Constants::AFFINITYARTIFACTS))
|
|
type = "artifact";
|
|
else if (card->has(Constants::AFFINITYSWAMP))
|
|
type = "swamp";
|
|
else if (card->has(Constants::AFFINITYMOUNTAIN))
|
|
type = "mountain";
|
|
else if (card->has(Constants::AFFINITYPLAINS))
|
|
type = "plains";
|
|
else if (card->has(Constants::AFFINITYISLAND))
|
|
type = "island";
|
|
else if (card->has(Constants::AFFINITYFOREST))
|
|
type = "forest";
|
|
else if (card->has(Constants::AFFINITYGREENCREATURES))
|
|
{
|
|
color = 1;
|
|
type = "creature";
|
|
}
|
|
newCost->copy(original);
|
|
int reduce = 0;
|
|
if(card->has(Constants::AFFINITYGREENCREATURES))
|
|
{
|
|
TargetChooserFactory tf(observer);
|
|
TargetChooser * tc = tf.createTargetChooser("creature[green]",NULL);
|
|
reduce = card->controller()->game->battlefield->countByCanTarget(tc);
|
|
SAFE_DELETE(tc);
|
|
}
|
|
else
|
|
reduce = card->controller()->game->battlefield->countByType(type);
|
|
for(int i = 0; i < reduce;i++)
|
|
if(newCost->getCost(color) > 0)
|
|
newCost->remove(color,1);
|
|
}//end3
|
|
|
|
if(!noTrinisphere)
|
|
{
|
|
//trinisphere... now how to implement kicker recomputation
|
|
if(card->has(Constants::TRINISPHERE))
|
|
{
|
|
for(int jj = newCost->getConvertedCost(); jj < 3; jj++)
|
|
{
|
|
newCost->add(Constants::MTG_COLOR_ARTIFACT, 1);
|
|
card->countTrini++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(card->countTrini)
|
|
{
|
|
newCost->remove(Constants::MTG_COLOR_ARTIFACT, card->countTrini);
|
|
card->countTrini=0;
|
|
}
|
|
}
|
|
}
|
|
|
|
SAFE_DELETE(original);
|
|
|
|
return newCost;
|
|
}
|
|
|
|
MTGCardInstance * MTGCardInstance::getNextPartner()
|
|
{
|
|
MTGInPlay * inplay = controller()->game->inPlay;
|
|
MTGCardInstance * bandingPartner = inplay->getNextAttacker(banding);
|
|
while (bandingPartner)
|
|
{
|
|
if (basicAbilities[(int)Constants::BANDING] || bandingPartner->basicAbilities[(int)Constants::BANDING])
|
|
return bandingPartner;
|
|
bandingPartner = inplay->getNextAttacker(bandingPartner);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int MTGCardInstance::DangerRanking()
|
|
{
|
|
int danger;
|
|
int result;
|
|
danger = 0;
|
|
result = 0;
|
|
result += power;
|
|
result += toughness;
|
|
result += getManaCost()->getConvertedCost();
|
|
for (int j = 0; j < Constants::NB_BASIC_ABILITIES; j++)
|
|
{
|
|
if (basicAbilities[j])
|
|
{
|
|
result += 1;
|
|
}
|
|
}
|
|
if (result > 1)
|
|
danger += 1;
|
|
if (result > 2)
|
|
danger += 1;
|
|
if (result > 4)
|
|
danger += 1;
|
|
if (result > 6)
|
|
danger += 1;
|
|
if (result > 10)
|
|
danger += 1;
|
|
return danger;
|
|
}
|
|
|
|
int MTGCardInstance::setAttacker(int value)
|
|
{
|
|
Targetable * previousTarget = NULL;
|
|
Targetable * target = NULL;
|
|
Player * p = controller()->opponent();
|
|
if (value)
|
|
target = p;
|
|
if (attacker)
|
|
previousTarget = p;
|
|
attacker = value;
|
|
WEvent * e = NEW WEventCreatureAttacker(this, previousTarget, target);
|
|
if (observer)
|
|
observer->receiveEvent(e);
|
|
else
|
|
SAFE_DELETE(e);
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::toggleAttacker()
|
|
{
|
|
if (!attacker)
|
|
{
|
|
//if (!basicAbilities[Constants::VIGILANCE]) tap();
|
|
setAttacker(1);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
//untap();
|
|
setAttacker(0);
|
|
isAttacking = NULL;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::isAttacker()
|
|
{
|
|
return attacker;
|
|
}
|
|
|
|
MTGCardInstance * MTGCardInstance::isDefenser()
|
|
{
|
|
return defenser;
|
|
}
|
|
|
|
int MTGCardInstance::nbOpponents()
|
|
{
|
|
int result = 0;
|
|
MTGCardInstance* opponent = getNextOpponent();
|
|
while (opponent)
|
|
{
|
|
result++;
|
|
opponent = getNextOpponent(opponent);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int MTGCardInstance::raiseBlockerRankOrder(MTGCardInstance * blocker)
|
|
{
|
|
list<MTGCardInstance *>::iterator it1 = find(blockers.begin(), blockers.end(), blocker);
|
|
list<MTGCardInstance *>::iterator it2 = it1;
|
|
if (blockers.begin() == it2)
|
|
++it2;
|
|
else
|
|
--it2;
|
|
|
|
std::iter_swap(it1, it2);
|
|
WEvent* e = NEW WEventCreatureBlockerRank(*it1, *it2, this);
|
|
if (observer)
|
|
observer->receiveEvent(e);
|
|
else
|
|
SAFE_DELETE(e);
|
|
//delete(e);
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::getDefenserRank(MTGCardInstance * blocker)
|
|
{
|
|
int result = 0;
|
|
for (list<MTGCardInstance *>::iterator it1 = blockers.begin(); it1 != blockers.end(); ++it1)
|
|
{
|
|
result++;
|
|
if ((*it1) == blocker)
|
|
return result;
|
|
}
|
|
return 0;
|
|
}
|
|
;
|
|
|
|
int MTGCardInstance::removeBlocker(MTGCardInstance * blocker)
|
|
{
|
|
blockers.remove(blocker);
|
|
// Blockers can be removed "manually" (by the blocking player) at the Blockers step,
|
|
// Or "automatically" in the damage phase, when they die and regenerate (see http://code.google.com/p/wagic/issues/detail?id=563 )
|
|
// In the second case, we still want the card to be marked as "blocked" this turn
|
|
if (!blockers.size() && observer->getCurrentGamePhase() == MTG_PHASE_COMBATBLOCKERS)
|
|
{
|
|
blocked = false;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::addBlocker(MTGCardInstance * blocker)
|
|
{
|
|
blockers.push_back(blocker);
|
|
blocked = true;
|
|
return 1;
|
|
}
|
|
|
|
//Returns opponents to this card for this turn. This * should * take into account banding
|
|
MTGCardInstance * MTGCardInstance::getNextOpponent(MTGCardInstance * previous)
|
|
{
|
|
int foundprevious = 0;
|
|
if (!previous)
|
|
foundprevious = 1;
|
|
if (attacker)
|
|
{
|
|
MTGInPlay * inPlay = observer->opponent()->game->inPlay;
|
|
for (int i = 0; i < inPlay->nb_cards; i++)
|
|
{
|
|
MTGCardInstance * current = inPlay->cards[i];
|
|
if (current == previous)
|
|
{
|
|
foundprevious = 1;
|
|
}
|
|
else if (foundprevious)
|
|
{
|
|
MTGCardInstance * defensersOpponent = current->isDefenser();
|
|
if (defensersOpponent && (defensersOpponent == this || (banding && defensersOpponent->banding == banding)))
|
|
{
|
|
return current;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (defenser)
|
|
{
|
|
MTGInPlay * inPlay = observer->currentPlayer->game->inPlay;
|
|
for (int i = 0; i < inPlay->nb_cards; i++)
|
|
{
|
|
MTGCardInstance * current = inPlay->cards[i];
|
|
if (current == previous)
|
|
{
|
|
foundprevious = 1;
|
|
}
|
|
else if (foundprevious)
|
|
{
|
|
if (defenser == current || (current->banding && defenser->banding == current->banding))
|
|
{
|
|
return current;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int MTGCardInstance::setDefenser(MTGCardInstance * opponent)
|
|
{
|
|
if (defenser)
|
|
{
|
|
if (observer->players[0]->game->battlefield->hasCard(defenser) || observer->players[1]->game->battlefield->hasCard(defenser))
|
|
{//remove blocker "this" from the attackers list of blockers.
|
|
defenser->removeBlocker(this);
|
|
}
|
|
}
|
|
WEvent * e = NULL;
|
|
if (defenser != opponent)
|
|
e = NEW WEventCreatureBlocker(this, defenser, opponent);
|
|
defenser = opponent;
|
|
if (defenser)
|
|
defenser->addBlocker(this);
|
|
if (e)
|
|
observer->receiveEvent(e);
|
|
return 1;
|
|
}
|
|
|
|
int MTGCardInstance::toggleDefenser(MTGCardInstance * opponent)
|
|
{
|
|
if (canBlock())
|
|
{
|
|
if (canBlock(opponent))
|
|
{
|
|
setDefenser(opponent);
|
|
didblocked = 1;
|
|
if (opponent && opponent->controller()->isAI() && opponent->controller()->playMode != Player::MODE_TEST_SUITE)
|
|
{
|
|
if(opponent->view != NULL)
|
|
{
|
|
//todo: quote wololo "change this into a cool blinking effects when opposing creature has cursor focus."
|
|
opponent->view->actZ += .8f;
|
|
opponent->view->actT -= .2f;
|
|
}
|
|
}
|
|
if (!opponent)
|
|
didblocked = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool MTGCardInstance::matchesCastFilter(int castFilter) {
|
|
if(castFilter == Constants::CAST_DONT_CARE)
|
|
return true; //everything
|
|
if(castFilter == Constants::CAST_ALL)
|
|
return (castMethod != Constants::NOT_CAST); //everything except "not cast"
|
|
if (castFilter == Constants::CAST_ALTERNATE && castMethod > Constants::CAST_NORMALLY)
|
|
return true; //all alternate casts
|
|
return (castFilter == castMethod);
|
|
};
|
|
|
|
int MTGCardInstance::addProtection(TargetChooser * tc)
|
|
{
|
|
tc->targetter = NULL;
|
|
protections.push_back(tc);
|
|
return protections.size();
|
|
}
|
|
|
|
int MTGCardInstance::removeProtection(TargetChooser * tc, int erase)
|
|
{
|
|
for (size_t i = 0; i < protections.size(); i++)
|
|
{
|
|
if (protections[i] == tc)
|
|
{
|
|
if (erase)
|
|
delete (protections[i]);
|
|
protections.erase(protections.begin() + i);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::protectedAgainst(MTGCardInstance * card)
|
|
{
|
|
//Basic protections
|
|
for (int i = Constants::PROTECTIONGREEN; i <= Constants::PROTECTIONWHITE; i++)
|
|
{
|
|
if (basicAbilities[i] && card->hasColor(i - Constants::PROTECTIONGREEN + Constants::MTG_COLOR_GREEN))
|
|
return 1;
|
|
}
|
|
|
|
//General protections
|
|
for (size_t i = 0; i < protections.size(); i++)
|
|
{
|
|
if (protections[i]->canTarget(card))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::addCantBeTarget(TargetChooser * tc)
|
|
{
|
|
tc->targetter = NULL;
|
|
canttarget.push_back(tc);
|
|
return canttarget.size();
|
|
}
|
|
|
|
int MTGCardInstance::removeCantBeTarget(TargetChooser * tc, int erase)
|
|
{
|
|
for (size_t i = 0; i < canttarget.size(); i++)
|
|
{
|
|
if (canttarget[i] == tc)
|
|
{
|
|
if (erase)
|
|
delete (canttarget[i]);
|
|
canttarget.erase(canttarget.begin() + i);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::CantBeTargetby(MTGCardInstance * card)
|
|
{
|
|
for (size_t i = 0; i < canttarget.size(); i++)
|
|
{
|
|
if (canttarget[i]->canTarget(card))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::addCantBeBlockedBy(TargetChooser * tc)
|
|
{
|
|
cantBeBlockedBys.push_back(tc);
|
|
return cantBeBlockedBys.size();
|
|
}
|
|
|
|
int MTGCardInstance::removeCantBeBlockedBy(TargetChooser * tc, int erase)
|
|
{
|
|
for (size_t i = 0; i < cantBeBlockedBys.size(); i++)
|
|
{
|
|
if (cantBeBlockedBys[i] == tc)
|
|
{
|
|
if (erase)
|
|
delete (cantBeBlockedBys[i]);
|
|
cantBeBlockedBys.erase(cantBeBlockedBys.begin() + i);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::cantBeBlockedBy(MTGCardInstance * card)
|
|
{
|
|
for (size_t i = 0; i < cantBeBlockedBys.size(); i++)
|
|
{
|
|
if (cantBeBlockedBys[i]->canTarget(card, true))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
//cant be the block of
|
|
int MTGCardInstance::addCantBeBlockerOf(TargetChooser * tc)
|
|
{
|
|
cantBeBlockerOfs.push_back(tc);
|
|
return cantBeBlockerOfs.size();
|
|
}
|
|
|
|
int MTGCardInstance::removeCantBeBlockerOf(TargetChooser * tc, int erase)
|
|
{
|
|
for (size_t i = 0; i < cantBeBlockerOfs.size(); i++)
|
|
{
|
|
if (cantBeBlockerOfs[i] == tc)
|
|
{
|
|
if (erase)
|
|
delete (cantBeBlockerOfs[i]);
|
|
cantBeBlockerOfs.erase(cantBeBlockerOfs.begin() + i);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::cantBeBlockerOf(MTGCardInstance * card)
|
|
{
|
|
for (size_t i = 0; i < cantBeBlockerOfs.size(); i++)
|
|
{
|
|
if (cantBeBlockerOfs[i]->canTarget(card, true))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//cant be the block of
|
|
int MTGCardInstance::addCantBeBlockerOfCard(MTGCardInstance * card)
|
|
{
|
|
cantBeBlockerOfCards.push_back(card);
|
|
return cantBeBlockerOfCards.size();
|
|
}
|
|
|
|
int MTGCardInstance::removeCantBeBlockerOfCard(MTGCardInstance * card, int erase)
|
|
{
|
|
for (size_t i = 0; i < cantBeBlockerOfCards.size(); i++)
|
|
{
|
|
if (cantBeBlockerOfCards[i] == card)
|
|
{
|
|
if (erase)
|
|
delete (cantBeBlockerOfCards[i]);
|
|
cantBeBlockerOfCards.erase(cantBeBlockerOfCards.begin() + i);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MTGCardInstance::cantBeBlockerOfCard(MTGCardInstance * card)
|
|
{
|
|
for (size_t i = 0; i < cantBeBlockerOfCards.size(); i++)
|
|
{
|
|
if (cantBeBlockerOfCards[i] == card)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
/* Choose a sound sample to associate to that card */
|
|
const string& MTGCardInstance::getSample()
|
|
{
|
|
if (sample.size())
|
|
return sample;
|
|
|
|
for (int i = types.size() - 1; i > 0; i--)
|
|
{
|
|
string type = MTGAllCards::findType(types[i]);
|
|
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
|
|
type = type + ".wav";
|
|
if(getObserver() && getObserver()->getResourceManager())
|
|
{
|
|
if (getObserver()->getResourceManager()->RetrieveSample(type))
|
|
{
|
|
sample = string(type);
|
|
return sample;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (basicAbilities.any())
|
|
{
|
|
for (size_t x = 0; x < basicAbilities.size(); ++x)
|
|
{
|
|
if (!basicAbilities.test(x))
|
|
continue;
|
|
string type = Constants::MTGBasicAbilities[x];
|
|
type = type + ".wav";
|
|
if(getObserver() && getObserver()->getResourceManager())
|
|
{
|
|
if (getObserver()->getResourceManager()->RetrieveSample(type))
|
|
{
|
|
sample = string(type);
|
|
return sample;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
string type = "";
|
|
if(!types.size())
|
|
return sample;
|
|
type = MTGAllCards::findType(types[0]);
|
|
std::transform(type.begin(), type.end(), type.begin(), ::tolower);
|
|
type.append(".wav");
|
|
if(getObserver() && getObserver()->getResourceManager())
|
|
{
|
|
if (getObserver()->getResourceManager()->RetrieveSample(type))
|
|
{
|
|
sample = string(type);
|
|
return sample;
|
|
}
|
|
}
|
|
|
|
return sample;
|
|
}
|
|
|
|
int MTGCardInstance::stepPower(CombatStep step)
|
|
{
|
|
int damage = has(Constants::COMBATTOUGHNESS) ? toughness : power;
|
|
switch (step)
|
|
{
|
|
case FIRST_STRIKE:
|
|
case END_FIRST_STRIKE:
|
|
if (has(Constants::FIRSTSTRIKE) || has(Constants::DOUBLESTRIKE))
|
|
return MAX(0, damage);
|
|
else
|
|
return 0;
|
|
case DAMAGE:
|
|
case END_DAMAGE:
|
|
default:
|
|
if (has(Constants::FIRSTSTRIKE) && !has(Constants::DOUBLESTRIKE))
|
|
return 0;
|
|
else
|
|
return MAX(0, damage);
|
|
}
|
|
}
|
|
|
|
std::ostream& MTGCardInstance::toString(std::ostream& out) const
|
|
{
|
|
return out << name;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& out, const MTGCardInstance& c)
|
|
{
|
|
return c.toString(out);
|
|
}
|
|
|
|
MTGCardInstance* MTGCardInstance::clone()
|
|
{
|
|
return NEW MTGCardInstance(model, owner->game);
|
|
}
|