Files
wagic/projects/mtg/src/MTGCardInstance.cpp

964 lines
24 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"
using namespace std;
MTGCardInstance MTGCardInstance::AnyCard = MTGCardInstance();
MTGCardInstance MTGCardInstance::NoCard = MTGCardInstance();
MTGCardInstance MTGCardInstance::ExtraRules[] = { MTGCardInstance(), MTGCardInstance() };
MTGCardInstance::MTGCardInstance() :
CardPrimitive(), MTGCard(), Damageable(0), view(NULL)
{
initMTGCI();
}
MTGCardInstance::MTGCardInstance(MTGCard * card, MTGPlayerCards * arg_belongs_to) :
CardPrimitive(card->data), MTGCard(card), Damageable(card->data->getToughness()), view(NULL)
{
initMTGCI();
model = card;
attacker = 0;
lifeOrig = life;
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;
flanked = 0;
}
void MTGCardInstance::copy(MTGCardInstance * card)
{
MTGCard * source = card->model;
CardPrimitive * data = source->data;
for (map<int, int>::const_iterator it = data->basicAbilities.begin(); it != data->basicAbilities.end(); ++it)
{
int i = it->first;
basicAbilities[i] = data->basicAbilities[i];
}
for (size_t i = 0; i < data->types.size(); i++)
{
types.push_back(data->types[i]);
}
for (int i = 0; i < Constants::MTG_NB_COLORS; i++)
{
colors[i] = data->colors[i];
}
manaCost.copy(data->getManaCost());
text = data->text;
setName(data->name);
power = data->power;
toughness = data->toughness;
life = toughness;
lifeOrig = life;
magicText = data->magicText;
spellTargetType = data->spellTargetType;
alias = data->alias;
//Now this is dirty...
int backupid = mtgid;
mtgid = source->getId();
Spell * spell = NEW Spell(this);
AbilityFactory af;
GameObserver * g = GameObserver::GetInstance();
af.addAbilities(g->mLayers->actionLayer()->getMaxId(), spell);
delete spell;
mtgid = backupid;
}
MTGCardInstance::~MTGCardInstance()
{
SAFE_DELETE(counters);
SAFE_DELETE(previous);
}
int MTGCardInstance::init()
{
MTGCard::init();
CardPrimitive::init();
data = this;
X = 0;
XX = 0;
return 1;
}
void MTGCardInstance::initMTGCI()
{
sample = "";
model = NULL;
isToken = false;
lifeOrig = 0;
doDamageTest = 1;
belongs_to = NULL;
tapped = 0;
untapping = 0;
frozen = 0;
fresh = 0;
didattacked = 0;
didblocked = 0;
notblocked = 0;
sunburst = NULL;
equipment = NULL;
boughtback = 0;
flashedback = 0;
paymenttype = 0;
reduxamount = 0;
summoningSickness = 1;
preventable = 0;
flanked = 0;
target = 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;
currentZone = NULL;
data = this; //an MTGCardInstance point to itself for data, allows to update it without killing the underlying database item
if (basicAbilities[Constants::CHANGELING])
{//if the card is a changeling.
for (int i = Subtypes::LAST_TYPE + 1;; i++)
{
string s = Subtypes::subtypesList->find(i);
if (!hasSubtype(i))
{
if (s == "")
break;
if (s.find(" ") != string::npos)
continue;
if (s == "Nothing" || s == "Swamp" || s == "Plains" || s == "Mountain" || s == "Forest" ||
s == "Island" || s == "Shrine" || s == "Basic" || s == "Colony" || s == "Desert" ||
s == "Dismiss" || s == "Equipment" || s == "Everglades" || s == "Grasslands" || s == "Lair" ||
s == "Level" || s == "Levelup" || s == "Mine" || s == "Oasis" || s == "World" || s == "Aura"
)
continue;
addType(i);
}
}
}
}
const string MTGCardInstance::getDisplayName() const
{
return getName();
}
void MTGCardInstance::addType(int type)
{
bool before = hasType(type);
CardPrimitive::addType(type);
WEvent * e = NEW WEventCardChangeType(this, type, before, true);
GameObserver * go = GameObserver::GetInstance();
if (go)
go->receiveEvent(e);
else
SAFE_DELETE(e);
}
void MTGCardInstance::addType(char * type_text)
{
setSubtype(type_text);
}
void MTGCardInstance::setType(const char * type_text)
{
setSubtype(type_text);
}
void MTGCardInstance::setSubtype(string value)
{
int id = Subtypes::subtypesList->find(value);
addType(id);
}
int MTGCardInstance::removeType(string value, int removeAll)
{
int id = Subtypes::subtypesList->find(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);
WEvent * e = NEW WEventCardChangeType(this, id, before, after);
GameObserver * go = GameObserver::GetInstance();
if (go)
go->receiveEvent(e);
else
SAFE_DELETE(e);
return result;
}
int MTGCardInstance::isInPlay()
{
GameObserver * game = GameObserver::GetInstance();
for (int i = 0; i < 2; i++)
{
MTGGameZone * zone = game->players[i]->game->inPlay;
if (zone->hasCard(this))
return 1;
}
return 0;
}
int MTGCardInstance::afterDamage()
{
if (!doDamageTest)
return 0;
doDamageTest = 0;
if (!isCreature())
return 0;
if (life <= 0 && isInPlay())
{
return destroy();
}
return 0;
}
int MTGCardInstance::bury()
{
Player * p = controller();
if (basicAbilities[Constants::EXILEDEATH])
{
p->game->putInZone(this, p->game->inPlay, owner->game->exile);
return 1;
}
if (!basicAbilities[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];
}
//sets card as attacked and sends events
void MTGCardInstance::eventattacked()
{
didattacked = 1;
WEvent * e = NEW WEventCardAttacked(this);
GameObserver * game = GameObserver::GetInstance();
game->receiveEvent(e);
}
//sets card as attacked alone and sends events
void MTGCardInstance::eventattackedAlone()
{
WEvent * e = NEW WEventCardAttackedAlone(this);
GameObserver * game = GameObserver::GetInstance();
game->receiveEvent(e);
}
//sets card as attacked and sends events
void MTGCardInstance::eventattackednotblocked()
{
didattacked = 1;
WEvent * e = NEW WEventCardAttackedNotBlocked(this);
GameObserver * game = GameObserver::GetInstance();
game->receiveEvent(e);
}
//sets card as attacked and sends events
void MTGCardInstance::eventattackedblocked()
{
didattacked = 1;
WEvent * e = NEW WEventCardAttackedBlocked(this);
GameObserver * game = GameObserver::GetInstance();
game->receiveEvent(e);
}
//sets card as blocking and sends events
void MTGCardInstance::eventblocked()
{
didblocked = 1;
WEvent * e = NEW WEventCardBlocked(this);
GameObserver * game = GameObserver::GetInstance();
game->receiveEvent(e);
}
//Taps the card
void MTGCardInstance::tap()
{
if (tapped)
return;
tapped = 1;
WEvent * e = NEW WEventCardTap(this, 0, 1);
GameObserver * game = GameObserver::GetInstance();
game->receiveEvent(e);
}
void MTGCardInstance::untap()
{
if (!tapped)
return;
tapped = 0;
WEvent * e = NEW WEventCardTap(this, 1, 0);
GameObserver * game = GameObserver::GetInstance();
game->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();
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 = false;
didattacked = 0;
didblocked = 0;
return 1;
}
//Function to call to remove all damages, etc to a card (generally at the end of the turn)
int MTGCardInstance::cleanup()
{
initAttackersDefensers();
life = toughness;
GameObserver * game = GameObserver::GetInstance();
if (!game || game->currentPlayer == controller())
{
summoningSickness = 0;
}
if (previous && !previous->stillInUse())
{
SAFE_DELETE(previous);
}
regenerateTokens = 0;
preventable = 0;
return 1;
}
int MTGCardInstance::stillInUse()
{
GameObserver * game = GameObserver::GetInstance();
if (game->mLayers->actionLayer()->stillInUse(this))
return 1;
if (!previous)
return 0;
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[Constants::HASTE])
return 0;
if (!isCreature())
return 0;
return 1;
}
MTGCardInstance * MTGCardInstance::changeController(Player * newController)
{
Player * originalOwner = controller();
if (originalOwner == newController)
return this;
MTGCardInstance * copy = originalOwner->game->putInZone(this, originalOwner->game->inPlay, newController->game->inPlay);
copy->summoningSickness = 1;
return copy;
}
Player * MTGCardInstance::controller()
{
return lastController;
}
int MTGCardInstance::canAttack()
{
if (tapped)
return 0;
if (hasSummoningSickness())
return 0;
if (basicAbilities[Constants::DEFENSER] || basicAbilities[Constants::CANTATTACK])
return 0;
if (!isCreature())
return 0;
if (!isInPlay())
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;
}
int MTGCardInstance::canBlock()
{
if (tapped)
return 0;
if (basicAbilities[Constants::CANTBLOCK])
return 0;
if (!isCreature())
return 0;
if (!isInPlay())
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 (opponent->basicAbilities[Constants::UNBLOCKABLE])
return 0;
if (opponent->basicAbilities[Constants::ONEBLOCKER] && opponent->blocked)
return 0;
if (opponent->basicAbilities[Constants::FEAR] && !(hasType(Subtypes::TYPE_ARTIFACT) || hasColor(Constants::MTG_COLOR_BLACK)))
return 0;
//intimidate
if (opponent->basicAbilities[Constants::INTIMIDATE] && !(hasType(Subtypes::TYPE_ARTIFACT)))
{
int canblock = 0;
for (int i = Constants::MTG_COLOR_GREEN; i <= Constants::MTG_COLOR_WHITE; ++i)
{
if (hasColor(i) && opponent->hasColor(i))
{
canblock = 1;
break;
}
}
if (!canblock)
return 0;
}
if (opponent->basicAbilities[Constants::FLYING] && !(basicAbilities[Constants::FLYING] || basicAbilities[Constants::REACH]))
return 0;
//Can block only creatures with flying if has cloud
if (basicAbilities[Constants::CLOUD] && !(opponent->basicAbilities[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[Constants::SHADOW] && !(basicAbilities[Constants::SHADOW]
|| basicAbilities[Constants::REACHSHADOW]))
return 0;
// If opponent does not have shadow and a creature has shadow it cannot be blocked
if (!opponent->basicAbilities[Constants::SHADOW] && basicAbilities[Constants::SHADOW])
return 0;
if (opponent->basicAbilities[Constants::HORSEMANSHIP] && !basicAbilities[Constants::HORSEMANSHIP])
return 0;
if (opponent->basicAbilities[Constants::SWAMPWALK] && controller()->game->inPlay->hasType("swamp"))
return 0;
if (opponent->basicAbilities[Constants::FORESTWALK] && controller()->game->inPlay->hasType("forest"))
return 0;
if (opponent->basicAbilities[Constants::ISLANDWALK] && controller()->game->inPlay->hasType("island"))
return 0;
if (opponent->basicAbilities[Constants::MOUNTAINWALK] && controller()->game->inPlay->hasType("mountain"))
return 0;
if (opponent->basicAbilities[Constants::PLAINSWALK] && controller()->game->inPlay->hasType("plains"))
return 0;
return 1;
}
JQuad * MTGCardInstance::getIcon()
{
return resources.RetrieveCard(this, CACHE_THUMB);
}
MTGCardInstance * MTGCardInstance::getNextPartner()
{
MTGInPlay * inplay = controller()->game->inPlay;
MTGCardInstance * bandingPartner = inplay->getNextAttacker(banding);
while (bandingPartner)
{
if (basicAbilities[Constants::BANDING] || bandingPartner->basicAbilities[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);
GameObserver * go = GameObserver::GetInstance();
if (go)
go->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);
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);
GameObserver * go = GameObserver::GetInstance();
if (go)
go->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);
if (!blockers.size())
{
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)
{
GameObserver * game = GameObserver::GetInstance();
int foundprevious = 0;
if (!previous)
foundprevious = 1;
if (attacker)
{
MTGInPlay * inPlay = game->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 = game->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)
{
GameObserver * g = GameObserver::GetInstance();
if (defenser)
{
if (g->players[0]->game->battlefield->hasCard(defenser) || g->players[1]->game->battlefield->hasCard(defenser))
{
defenser->removeBlocker(this);
}
}
WEvent * e = NULL;
if (defenser != opponent)
e = NEW WEventCreatureBlocker(this, defenser, opponent);
defenser = opponent;
if (defenser)
defenser->addBlocker(this);
if (e)
g->receiveEvent(e);
return 1;
}
int MTGCardInstance::toggleDefenser(MTGCardInstance * opponent)
{
if (canBlock())
{
if (canBlock(opponent))
{
setDefenser(opponent);
didblocked = 1;
if (opponent && opponent->controller()->isAI())
{
opponent->view->actZ += .8f;
opponent->view->actT -= .2f;
}
if (!opponent)
didblocked = 0;
return 1;
}
}
return 0;
}
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::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))
return 1;
}
return 0;
}
/* Choose a sound sample to associate to that card */
JSample * MTGCardInstance::getSample()
{
JSample * js;
if (sample.size())
return resources.RetrieveSample(sample);
for (int i = types.size() - 1; i > 0; i--)
{
string type = Subtypes::subtypesList->find(types[i]);
type = type + ".wav";
js = resources.RetrieveSample(type);
if (js)
{
sample = string(type);
return js;
}
}
for (map<int, int>::const_iterator it = basicAbilities.begin(); it != basicAbilities.end(); ++it)
{
int i = it->first;
if (!basicAbilities[i])
continue;
string type = Constants::MTGBasicAbilities[i];
type = type + ".wav";
js = resources.RetrieveSample(type);
if (js)
{
sample = string(type);
return js;
}
}
string type = Subtypes::subtypesList->find(types[0]);
type = type + ".wav";
js = resources.RetrieveSample(type);
if (js)
{
sample = string(type);
return js;
}
return NULL;
}
int MTGCardInstance::stepPower(CombatStep step)
{
switch (step)
{
case FIRST_STRIKE:
case END_FIRST_STRIKE:
if (has(Constants::FIRSTSTRIKE) || has(Constants::DOUBLESTRIKE))
return MAX(0, power);
else
return 0;
case DAMAGE:
case END_DAMAGE:
default:
if (has(Constants::FIRSTSTRIKE) && !has(Constants::DOUBLESTRIKE))
return 0;
else
return MAX(0, power);
}
}
std::ostream& MTGCardInstance::toString(std::ostream& out) const
{
return out << name;
}
std::ostream& operator<<(std::ostream& out, const MTGCardInstance& c)
{
return c.toString(out);
}