5d42bfa88f
Two example codes:
[card]
name=Alabaster Leech
auto=lord(*[white]|myhand) white:+1
autoexile=all(*|myhand) resetcost
autograveyard=all(*|myhand) resetcost
autohand=all(*|myhand) resetcost
autolibrary=all(*|myhand) resetcost
text=White spells you cast cost {W} more to cast.
mana={W}
type=Creature
subtype=Leech
power=1
toughness=3
[/card]
[card]
name=Helm of Awakening
auto=lord(*|myhand) colorless:-1
auto=lord(*|opponenthand) colorless:-1
autoexile=all(*|myhand) resetcost
autograveyard=all(*|myhand) resetcost
autohand=all(*|myhand) resetcost
autolibrary=all(*|myhand) resetcost
text=Spells cost {1} less to cast.
mana={2}
type=Artifact
[/card]
autoexile=all(*|myhand) resetcost
autograveyard=all(*|myhand) resetcost
autohand=all(*|myhand) resetcost
autolibrary=all(*|myhand) resetcost
----> This code section is necessary, because manacost altering cards will keep their effect even when they have left the battlefield. RESETCOST erases all alterations which have no existing source on the battlefield anymore.
2) Added the new keyword TRANSFORM, which is similar to BECOMES. The main difference is that you can change single parameters of a permanent (color,type,...).
Example codes:
[card]
name=Memnarch
auto={1}{U}{U}:target(*) transforms(artifact)
auto={3}{U}:moveTo(myBattlefield) target(arifact)
text={1}{U}{U}: Target permanent becomes an artifact in addition to its other types. (This effect lasts indefinitely.) -- {3}{U}: Gain control of target artifact. (This effect lasts indefinitely.)
mana={7}
type=Legendary Artifact Creature
subtype=Wizard
power=4
toughness=5
[/card]
[card]
name=Dralnu's Crusade
auto=lord(goblin) 1/1
auto=lord(goblin) transforms(zombie,black)
text=Goblin creatures get +1/+1. -- All Goblins are black and are Zombies in addition to their other creature types.
mana={1}{B}{R}
type=Enchantment
[/card]
Important notes concerning TRANSFORM:
- IF YOU TARGET A CREATURE THE EFFECT IS PERMINENT.
- IF YOU TARGET THE SOURCE THE EFFECT IS UNTIL END OF TURN.
- IF YOU USE LORD THE EFFECT LAST TIL PERMINENT SOURCE LEAVES PLAY.
These restrictions will probably be changed in the near future!
3) Added 57 successfully tested cards.
Card list ---> first comment
4) Changed the name of several tokens: "()" used to cuase crashes when used in the name-line.
5) Added the new keyword NONBATTLEZONE for leaves play trigger optimizing. It can be used to replace the phrase "EXILE,GRAVEYARD,HAND,LIBRARY".
I will add tests for test suite in one of the next revisions!!
####### TEST SUITE PROVEN ########
4280 lines
113 KiB
C++
4280 lines
113 KiB
C++
#ifndef _CARDS_H_
|
|
#define _CARDS_H_
|
|
|
|
#include "MTGAbility.h"
|
|
#include "ManaCost.h"
|
|
#include "CardDescriptor.h"
|
|
#include "AIPlayer.h"
|
|
#include "CardDisplay.h"
|
|
#include "Subtypes.h"
|
|
#include "CardGui.h"
|
|
#include "GameOptions.h"
|
|
#include "Token.h"
|
|
#include "Counters.h"
|
|
#include "WEvent.h"
|
|
#include "GuiStatic.h"
|
|
#include "GameObserver.h"
|
|
#include "ThisDescriptor.h"
|
|
|
|
#include <JGui.h>
|
|
#include <hge/hgeparticle.h>
|
|
|
|
|
|
#include <map>
|
|
using std::map;
|
|
|
|
|
|
//
|
|
// Misc classes
|
|
//
|
|
class WParsedInt{
|
|
public:
|
|
int intValue;
|
|
|
|
int computeX(Spell * spell, MTGCardInstance * card){
|
|
if (spell) return spell->computeX(card);
|
|
return 1; //this should only hapen when the ai calls the ability. This is to give it an idea of the "direction" of X (positive/negative)
|
|
}
|
|
WParsedInt(int value = 0){
|
|
intValue = value;
|
|
}
|
|
|
|
WParsedInt(string s, Spell * spell, MTGCardInstance * card){
|
|
MTGCardInstance * target = card->target;
|
|
if (!target) target = card;
|
|
int multiplier = 1;
|
|
if (s[0] == '-'){
|
|
s = s.substr(1);
|
|
multiplier = -1;
|
|
}
|
|
if (s == "x" || s == "X"){
|
|
intValue = computeX(spell,card);
|
|
}else if (s == "manacost"){
|
|
intValue = target->getManaCost()->getConvertedCost();
|
|
}else if (s == "lifetotal"){
|
|
intValue = target->controller()->life;
|
|
}else if (s == "opponentlifetotal"){
|
|
intValue = target->controller()->opponent()->life;
|
|
}else if (s == "p"){
|
|
intValue = target->power;
|
|
}else if (s == "t"){
|
|
intValue = target->toughness;
|
|
}else{
|
|
intValue = atoi(s.c_str());
|
|
}
|
|
intValue *= multiplier;
|
|
}
|
|
|
|
int getValue(){
|
|
return intValue;
|
|
}
|
|
};
|
|
|
|
class WParsedPT{
|
|
public:
|
|
bool ok;
|
|
WParsedInt power,toughness;
|
|
|
|
WParsedPT(int p, int t){
|
|
power.intValue = p;
|
|
toughness.intValue = t;
|
|
ok = true;
|
|
}
|
|
|
|
WParsedPT(string s, Spell * spell, MTGCardInstance * card){
|
|
size_t found = s.find("/");
|
|
ok = false;
|
|
if (found != string::npos){
|
|
size_t end = s.find(" ", found);
|
|
if (end == string::npos) end = s.size();
|
|
size_t start = s.find_last_of(" ",found);
|
|
if (start == string::npos) start = 0;
|
|
else start++;
|
|
power = WParsedInt(s.substr(start,found - start), spell, card);
|
|
toughness = WParsedInt(s.substr(found+1,end-found-1), spell, card);
|
|
|
|
ok = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
//
|
|
//Triggers
|
|
//
|
|
|
|
class TrCardAddedToZone:public TriggeredAbility{
|
|
public:
|
|
TargetChooser * toTc;
|
|
TargetZoneChooser * fromTcZone;
|
|
TargetChooser * fromTcCard;
|
|
TrCardAddedToZone(int id, MTGCardInstance * source, TargetChooser * toTc, TargetZoneChooser * fromTcZone = NULL,TargetChooser * fromTcCard = NULL):TriggeredAbility(id,source), toTc(toTc), fromTcZone(fromTcZone), fromTcCard(fromTcCard){}
|
|
|
|
int resolve(){
|
|
return 0; //This is a trigger, this function should not be called
|
|
}
|
|
|
|
int triggerOnEvent(WEvent * event){
|
|
WEventZoneChange * e = dynamic_cast<WEventZoneChange*>(event);
|
|
if (!e) return 0;
|
|
|
|
if (!toTc->canTarget(e->card)) return 0;
|
|
if (fromTcZone && !fromTcZone->targetsZone(e->from)) return 0;
|
|
if (fromTcCard && !fromTcCard->canTarget(e->card->previous)) return 0;
|
|
|
|
//Battlefield is a special case. We usually don't want to trigger when a card comes from battlefield to battlefield
|
|
// http://code.google.com/p/wagic/issues/detail?id=179
|
|
if ((e->from == game->players[0]->game->battlefield || e->from == game->players[1]->game->battlefield) &&
|
|
(e->to == game->players[0]->game->battlefield || e->to == game->players[1]->game->battlefield)) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
~TrCardAddedToZone(){
|
|
SAFE_DELETE(toTc);
|
|
SAFE_DELETE(fromTcZone);
|
|
SAFE_DELETE(fromTcCard);
|
|
}
|
|
|
|
TrCardAddedToZone * clone() const{
|
|
TrCardAddedToZone * a = NEW TrCardAddedToZone(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
class TrCardTapped:public TriggeredAbility{
|
|
public:
|
|
TargetChooser * tc;
|
|
bool tap;
|
|
TrCardTapped(int id, MTGCardInstance * source, TargetChooser * tc, bool tap = true):TriggeredAbility(id,source), tc(tc),tap(tap){}
|
|
|
|
int resolve(){
|
|
return 0; //This is a trigger, this function should not be called
|
|
}
|
|
|
|
int triggerOnEvent(WEvent * event){
|
|
WEventCardTap * e = dynamic_cast<WEventCardTap *>(event);
|
|
if (!e) return 0;
|
|
if (e->before == e->after) return 0;
|
|
if (e->after != tap) return 0;
|
|
if (!tc->canTarget(e->card)) return 0;
|
|
return 1;
|
|
}
|
|
|
|
~TrCardTapped(){
|
|
SAFE_DELETE(tc);
|
|
}
|
|
|
|
TrCardTapped * clone() const{
|
|
TrCardTapped * a = NEW TrCardTapped(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
class TrDamaged:public TriggeredAbility{
|
|
public:
|
|
TargetChooser * tc;
|
|
TargetChooser * fromTc;
|
|
TrDamaged (int id, MTGCardInstance * source, TargetChooser * tc, TargetChooser * fromTc = NULL):TriggeredAbility(id,source), tc(tc), fromTc(fromTc){}
|
|
|
|
int resolve(){
|
|
return 0; //This is a trigger, this function should not be called
|
|
}
|
|
|
|
int triggerOnEvent(WEvent * event){
|
|
WEventDamage * e = dynamic_cast<WEventDamage *>(event);
|
|
if (!e) return 0;
|
|
if(!tc->canTarget(e->damage->target)) return 0;
|
|
if (fromTc && !fromTc->canTarget(e->damage->source)) return 0;
|
|
return 1;
|
|
}
|
|
|
|
~TrDamaged (){
|
|
SAFE_DELETE(tc);
|
|
SAFE_DELETE(fromTc);
|
|
}
|
|
|
|
TrDamaged * clone() const{
|
|
TrDamaged * a = NEW TrDamaged (*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//counters
|
|
class AACounter: public ActivatedAbility{
|
|
public:
|
|
int nb;
|
|
int power;
|
|
int toughness;
|
|
string name;
|
|
AACounter(int id, MTGCardInstance * source, MTGCardInstance * target, const char * _name, int power, int toughness, int nb, ManaCost * cost = NULL, int doTap = 0) : ActivatedAbility(id, source, cost, 0, doTap), nb(nb), power(power), toughness(toughness), name(_name) {
|
|
this->target = target;
|
|
}
|
|
|
|
|
|
int resolve(){
|
|
if (target){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
if (nb>0){
|
|
for (int i=0; i < nb; i++){
|
|
_target->counters->addCounter(name.c_str(), power, toughness);
|
|
}
|
|
}else{
|
|
for (int i=0; i < -nb; i++){
|
|
_target->counters->removeCounter(name.c_str(), power, toughness);
|
|
}
|
|
}
|
|
return nb;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char* getMenuText() {
|
|
return "Counter";
|
|
}
|
|
|
|
AACounter * clone() const{
|
|
AACounter * a = NEW AACounter(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
class AAFizzler:public ActivatedAbility{
|
|
public:
|
|
AAFizzler(int _id, MTGCardInstance * card, Spell * _target, ManaCost * _cost = NULL, int _tap = 0):ActivatedAbility(_id, card,_cost,0,_tap){
|
|
target = _target;
|
|
}
|
|
|
|
int resolve(){
|
|
Spell * _target = (Spell *) target;
|
|
if(target && _target->source->has(Constants::NOFIZZLE))
|
|
return 0;
|
|
game->mLayers->stackLayer()->Fizzle(_target);
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Fizzle";
|
|
}
|
|
|
|
AAFizzler* clone() const{
|
|
AAFizzler * a = NEW AAFizzler(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/*
|
|
Generic classes
|
|
*/
|
|
|
|
|
|
|
|
//MayAbility: May do ...
|
|
class MayAbility:public MTGAbility, public NestedAbility{
|
|
public:
|
|
int triggered;
|
|
bool must;
|
|
MTGAbility * mClone;
|
|
MayAbility(int _id, MTGAbility * _ability, MTGCardInstance * _source, bool must = false):MTGAbility(_id,_source),NestedAbility(_ability),must(must){
|
|
triggered = 0;
|
|
mClone = NULL;
|
|
}
|
|
|
|
|
|
void Update(float dt){
|
|
MTGAbility::Update(dt);
|
|
if (!triggered){
|
|
triggered = 1;
|
|
if (TargetAbility * ta = dynamic_cast<TargetAbility *>(ability)){
|
|
if (!ta->tc->validTargetsExist()) return;
|
|
}
|
|
game->mLayers->actionLayer()->setMenuObject(source,must);
|
|
game->mLayers->stackLayer()->setIsInterrupting(source->controller());
|
|
OutputDebugString("ALLABILITIES SetMenuObject!\n");
|
|
}
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return ability->getMenuText();
|
|
}
|
|
|
|
int testDestroy(){
|
|
if (!triggered) return 0;
|
|
if (game->mLayers->actionLayer()->menuObject) return 0;
|
|
if (game->mLayers->actionLayer()->getIndexOf(mClone) !=-1) return 0;
|
|
return 1;
|
|
}
|
|
|
|
int isReactingToTargetClick(Targetable * card){
|
|
if (card == source) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int reactToTargetClick(Targetable * object){
|
|
mClone = ability->clone();
|
|
mClone->addToGame();
|
|
mClone->forceDestroy = 1;
|
|
return mClone->reactToTargetClick(object);
|
|
}
|
|
|
|
~MayAbility(){
|
|
SAFE_DELETE(ability);
|
|
}
|
|
|
|
MayAbility * clone() const{
|
|
MayAbility * a = NEW MayAbility(*this);
|
|
a->ability = ability->clone();
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//MultiAbility : triggers several actions for a cost
|
|
class MultiAbility:public ActivatedAbility{
|
|
public:
|
|
vector<MTGAbility *> abilities;
|
|
|
|
|
|
MultiAbility(int _id, MTGCardInstance * card,Targetable * _target, ManaCost * _cost, int _tap):ActivatedAbility(_id, card,_cost,0,_tap){
|
|
if (_target) target = _target;
|
|
}
|
|
|
|
|
|
int Add(MTGAbility * ability){
|
|
abilities.push_back(ability);
|
|
return 1;
|
|
}
|
|
|
|
int resolve(){
|
|
vector<int>::size_type sz = abilities.size();
|
|
for (unsigned int i = 0; i < sz; i++){
|
|
Targetable * backup = abilities[i]->target;
|
|
if (target && target!= source && abilities[i]->target == abilities[i]->source) abilities[i]->target = target;
|
|
abilities[i]->resolve();
|
|
abilities[i]->target = backup;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
~MultiAbility(){
|
|
if (!isClone){
|
|
vector<int>::size_type sz = abilities.size();
|
|
for (size_t i = 0; i < sz; i++){
|
|
delete abilities[i];
|
|
}
|
|
}
|
|
abilities.clear();
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
if (abilities.size()) return abilities[0]->getMenuText();
|
|
return "";
|
|
}
|
|
|
|
MultiAbility * clone() const{
|
|
MultiAbility * a = NEW MultiAbility(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Generic Activated Ability
|
|
|
|
class GenericActivatedAbility:public ActivatedAbility, public NestedAbility{
|
|
public:
|
|
int limitPerTurn;
|
|
int counters;
|
|
MTGGameZone * activeZone;
|
|
GenericActivatedAbility(int _id, MTGCardInstance * card, MTGAbility * a, ManaCost * _cost, int _tap = 0, int limit = 0, int restrictions = 0, MTGGameZone * dest = NULL):ActivatedAbility(_id, card,_cost,restrictions,_tap),NestedAbility(a),limitPerTurn(limit),activeZone(dest){
|
|
counters = 0;
|
|
target = ability->target;
|
|
}
|
|
|
|
int resolve(){
|
|
counters++;
|
|
ManaCost * diff = abilityCost->Diff(cost);
|
|
source->X = diff->hasX();
|
|
SAFE_DELETE(diff);
|
|
//SAFE_DELETE(abilityCost); this line has been reported as a bug. removing it doesn't seem to break anything, although I didn't get any error in the test suite by leaving it either, so... leaving it for now as a comment, in case.
|
|
ability->target = target; //may have been updated...
|
|
if (ability) return ability->resolve();
|
|
return 0;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
if (ability) return ability->getMenuText();
|
|
return "Error";
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (limitPerTurn && counters >= limitPerTurn) return 0;
|
|
return ActivatedAbility::isReactingToClick(card,mana);
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase ==Constants::MTG_PHASE_AFTER_EOT){
|
|
counters = 0;
|
|
}
|
|
ActivatedAbility::Update(dt);
|
|
}
|
|
|
|
GenericActivatedAbility * clone() const{
|
|
GenericActivatedAbility * a = NEW GenericActivatedAbility(*this);
|
|
a->cost = NEW ManaCost();
|
|
a->cost->copy(cost);
|
|
a->ability = ability->clone();
|
|
return a;
|
|
}
|
|
|
|
~GenericActivatedAbility(){
|
|
SAFE_DELETE(ability);
|
|
}
|
|
|
|
int testDestroy(){
|
|
if (!activeZone) return ActivatedAbility::testDestroy();
|
|
if (activeZone->hasCard(source)) return 0;
|
|
return 1;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
//Copier. ActivatedAbility
|
|
class AACopier:public ActivatedAbility{
|
|
public:
|
|
AACopier(int _id, MTGCardInstance * _source, MTGCardInstance * _target = NULL, ManaCost * _cost=NULL):ActivatedAbility(_id,_source,_cost,0,0){
|
|
target = _target;
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if(_target){
|
|
source->copy(_target);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Copy";
|
|
}
|
|
|
|
|
|
AACopier * clone() const{
|
|
AACopier * a = NEW AACopier(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
class AAMover:public ActivatedAbility{
|
|
public:
|
|
string destination;
|
|
AAMover(int _id, MTGCardInstance * _source, MTGCardInstance * _target, string dest, ManaCost * _cost=NULL, int doTap=0):ActivatedAbility(_id,_source,_cost,0,doTap),destination(dest){
|
|
if (_target) target = _target;
|
|
}
|
|
|
|
MTGGameZone * destinationZone(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
return MTGGameZone::stringToZone(destination, source,_target);
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if(target){
|
|
Player* p = _target->controller();
|
|
if (p){
|
|
GameObserver * g = GameObserver::GetInstance();
|
|
MTGGameZone * fromZone = _target->getCurrentZone();
|
|
MTGGameZone * destZone = destinationZone();
|
|
|
|
//inplay is a special zone !
|
|
for (int i=0; i < 2; i++){
|
|
if (destZone == g->players[i]->game->inPlay && fromZone != g->players[i]->game->inPlay && fromZone != g->players[i]->opponent()->game->inPlay){
|
|
MTGCardInstance * copy = g->players[i]->game->putInZone(_target, fromZone, g->players[i]->game->temp);
|
|
Spell * spell = NEW Spell(copy);
|
|
spell->resolve();
|
|
delete spell;
|
|
return 1;
|
|
}
|
|
}
|
|
p->game->putInZone(_target,fromZone,destZone);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Move";
|
|
}
|
|
|
|
AAMover * clone() const{
|
|
AAMover * a = NEW AAMover(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Generic TargetAbility */
|
|
class GenericTargetAbility:public TargetAbility{
|
|
|
|
public:
|
|
int limitPerTurn;
|
|
int counters;
|
|
MTGGameZone * activeZone;
|
|
GenericTargetAbility(int _id, MTGCardInstance * _source, TargetChooser * _tc,MTGAbility * a, ManaCost * _cost = NULL, int _tap=0, int limit = 0, int restrictions = 0, MTGGameZone * dest = NULL):TargetAbility(_id,_source, _tc,_cost,restrictions,_tap),limitPerTurn(limit), activeZone(dest){
|
|
ability = a;
|
|
MTGAbility * core = AbilityFactory::getCoreAbility(a);
|
|
if (dynamic_cast<AACopier *>(core)) tc->other = true; //http://code.google.com/p/wagic/issues/detail?id=209 (avoid inifinite loop)
|
|
counters = 0;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
if (!ability) return "Error";
|
|
|
|
MTGAbility * core = AbilityFactory::getCoreAbility(ability);
|
|
if (AAMover * move = dynamic_cast<AAMover *>(core)) {
|
|
MTGGameZone * dest = move->destinationZone();
|
|
GameObserver * g = GameObserver::GetInstance();
|
|
for (int i=0; i < 2; i++){
|
|
if (dest == g->players[i]->game->hand && tc->targetsZone(g->players[i]->game->inPlay)){
|
|
return "Bounce";
|
|
}else if (dest == g->players[i]->game->hand && tc->targetsZone(g->players[i]->game->graveyard)){
|
|
return "Reclaim";
|
|
}else if (dest == g->players[i]->game->graveyard && tc->targetsZone(g->players[i]->game->inPlay)){
|
|
return "Sacrifice";
|
|
}else if (dest == g->players[i]->game->library && tc->targetsZone(g->players[i]->game->graveyard)){
|
|
return "Recycle";
|
|
}else if (dest == g->players[i]->game->battlefield && tc->targetsZone(g->players[i]->game->graveyard)){
|
|
return "Reanimate";
|
|
}else if (dest == g->players[i]->game->library){
|
|
return "Put in Library";
|
|
}else if (dest == g->players[i]->game->inPlay){
|
|
return "Put in Play";
|
|
}else if (dest == g->players[i]->game->graveyard && tc->targetsZone(g->players[i]->game->hand)){
|
|
return "Discard";
|
|
}else if (dest == g->players[i]->game->exile){
|
|
return "Exile";
|
|
}else if (tc->targetsZone(g->players[i]->game->library)){
|
|
return "Fetch";
|
|
}else if (dest == g->players[i]->game->hand && tc->targetsZone(g->opponent()->game->hand)){
|
|
return "Steal";
|
|
}else if (dest == g->players[i]->game->graveyard && tc->targetsZone(g->opponent()->game->hand)){
|
|
return "Opponent Discards";
|
|
}
|
|
}
|
|
}
|
|
|
|
return ability->getMenuText();
|
|
|
|
}
|
|
|
|
|
|
|
|
~GenericTargetAbility(){
|
|
SAFE_DELETE(ability);
|
|
}
|
|
|
|
GenericTargetAbility * clone() const{
|
|
GenericTargetAbility * a = NEW GenericTargetAbility(*this);
|
|
a->ability = ability->clone();
|
|
a->cost = NEW ManaCost();
|
|
a->cost->copy(cost);
|
|
if (tc) a->tc = tc->clone();
|
|
return a;
|
|
}
|
|
|
|
int resolve(){
|
|
counters++;
|
|
return TargetAbility::resolve();
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (limitPerTurn && counters >= limitPerTurn) return 0;
|
|
return TargetAbility::isReactingToClick(card,mana);
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase ==Constants::MTG_PHASE_AFTER_EOT){
|
|
counters = 0;
|
|
}
|
|
TargetAbility::Update(dt);
|
|
}
|
|
|
|
int testDestroy(){
|
|
if (!activeZone) return TargetAbility::testDestroy();
|
|
if (activeZone->hasCard(source)) return 0;
|
|
return 1;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
//Cycling
|
|
|
|
class ACycle:public ActivatedAbility{
|
|
public:
|
|
ACycle(int _id, MTGCardInstance * card,Targetable * _target):ActivatedAbility(_id, card){
|
|
target = _target;
|
|
}
|
|
|
|
int resolve(){
|
|
source->controller()->game->putInGraveyard(source);
|
|
source->controller()->game->drawFromLibrary();
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Cycling";
|
|
}
|
|
|
|
ACycle * clone() const{
|
|
ACycle * a = NEW ACycle(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
//Drawer, allows to draw a card for a cost:
|
|
|
|
class AADrawer:public ActivatedAbilityTP{
|
|
public:
|
|
WParsedInt *nbcards;
|
|
AADrawer(int _id, MTGCardInstance * card,Targetable * _target,ManaCost * _cost, WParsedInt * _nbcards, int _tap = 0, int who=TargetChooser::UNSET):ActivatedAbilityTP(_id, card,_target,_cost,_tap,who),nbcards(_nbcards){
|
|
}
|
|
|
|
int resolve(){
|
|
Targetable * _target = getTarget();
|
|
Player * player;
|
|
if (_target){
|
|
if (_target->typeAsTarget() == TARGET_CARD){
|
|
player = ((MTGCardInstance *)_target)->controller();
|
|
}else{
|
|
player = (Player *) _target;
|
|
}
|
|
game->mLayers->stackLayer()->addDraw(player,nbcards->getValue());
|
|
game->mLayers->stackLayer()->resolve();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Draw";
|
|
}
|
|
|
|
AADrawer * clone() const{
|
|
AADrawer * a = NEW AADrawer(*this);
|
|
a->nbcards = NEW WParsedInt(*(a->nbcards));
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~AADrawer(){
|
|
SAFE_DELETE(nbcards);
|
|
}
|
|
|
|
};
|
|
|
|
/*Gives life to target controller*/
|
|
class AALifer:public ActivatedAbilityTP{
|
|
public:
|
|
WParsedInt *life;
|
|
AALifer(int _id, MTGCardInstance * card, Targetable * _target, WParsedInt * life, ManaCost * _cost = NULL, int _tap = 0, int who = TargetChooser::UNSET):ActivatedAbilityTP(_id, card,_target,_cost,_tap,who),life(life){
|
|
}
|
|
|
|
int resolve(){
|
|
Damageable * _target = (Damageable *) getTarget();
|
|
if (_target){
|
|
if (_target->type_as_damageable == DAMAGEABLE_MTGCARDINSTANCE){
|
|
_target = ((MTGCardInstance *)_target)->controller();
|
|
}
|
|
_target->life +=life->getValue();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Life";
|
|
}
|
|
|
|
AALifer * clone() const{
|
|
AALifer * a = NEW AALifer(*this);
|
|
a->life = NEW WParsedInt(*(a->life));
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~AALifer(){
|
|
SAFE_DELETE(life);
|
|
}
|
|
|
|
};
|
|
|
|
/*Player Wins Game*/
|
|
class AAWinGame:public ActivatedAbilityTP{
|
|
public:
|
|
AAWinGame(int _id, MTGCardInstance * card, Targetable * _target, ManaCost * _cost = NULL, int _tap = 0, int who = TargetChooser::UNSET):ActivatedAbilityTP(_id, card,_target,_cost,_tap,who){
|
|
}
|
|
|
|
int resolve(){
|
|
Damageable * _target = (Damageable *) getTarget();
|
|
if (_target){
|
|
if (_target->type_as_damageable == DAMAGEABLE_MTGCARDINSTANCE){
|
|
_target = ((MTGCardInstance *)_target)->controller();
|
|
}
|
|
game->gameOver = ((Player *)_target)->opponent();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Win Game";
|
|
}
|
|
|
|
AAWinGame * clone() const{
|
|
AAWinGame * a = NEW AAWinGame(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class ATokenCreator:public ActivatedAbility{
|
|
public:
|
|
list<int>abilities;
|
|
list<int>types;
|
|
list<int>colors;
|
|
int power, toughness;
|
|
int tokenId;
|
|
string name;
|
|
WParsedInt * multiplier;
|
|
ATokenCreator(int _id,MTGCardInstance * _source,ManaCost * _cost, int tokenId, int _doTap, WParsedInt * multiplier = NULL):ActivatedAbility(_id,_source,_cost,0,_doTap), tokenId(tokenId), multiplier(multiplier){
|
|
if(!multiplier) this->multiplier = NEW WParsedInt(1);
|
|
MTGCard * card = GameApp::collection->getCardById(tokenId);
|
|
if (card) name = card->data->getName();
|
|
}
|
|
|
|
ATokenCreator(int _id,MTGCardInstance * _source,ManaCost * _cost, string sname, string stypes,int _power,int _toughness, string sabilities, int _doTap, WParsedInt * multiplier = NULL):ActivatedAbility(_id,_source,_cost,0,_doTap), multiplier(multiplier){
|
|
power = _power;
|
|
toughness = _toughness;
|
|
name = sname;
|
|
tokenId = 0;
|
|
if(!multiplier) this->multiplier = NEW WParsedInt(1);
|
|
|
|
//TODO this is a copy/past of other code that's all around the place, everything should be in a dedicated parser class;
|
|
|
|
for (int j = 0; j < Constants::NB_BASIC_ABILITIES; j++){
|
|
size_t found = sabilities.find(Constants::MTGBasicAbilities[j]);
|
|
if (found != string::npos){
|
|
abilities.push_back(j);
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < Constants::MTG_NB_COLORS; j++){
|
|
size_t found = sabilities.find(Constants::MTGColorStrings[j]);
|
|
if (found != string::npos){
|
|
colors.push_back(j);
|
|
}
|
|
}
|
|
|
|
string s = stypes;
|
|
while (s.size()){
|
|
size_t found = s.find(" ");
|
|
if (found != string::npos){
|
|
int id = Subtypes::subtypesList->find(s.substr(0,found));
|
|
types.push_back(id);
|
|
s = s.substr(found+1);
|
|
}else{
|
|
int id = Subtypes::subtypesList->find(s);
|
|
types.push_back(id);
|
|
s = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
int resolve(){
|
|
for (int i = 0; i < multiplier->getValue(); ++i){
|
|
MTGCardInstance * myToken;
|
|
if (tokenId){
|
|
MTGCard * card = GameApp::collection->getCardById(tokenId);
|
|
myToken = NEW MTGCardInstance(card,source->controller()->game);
|
|
} else {
|
|
myToken = NEW Token(name,source,power,toughness);
|
|
list<int>::iterator it;
|
|
for ( it=types.begin() ; it != types.end(); it++ ){
|
|
myToken->addType(*it);
|
|
}
|
|
for ( it=colors.begin() ; it != colors.end(); it++ ){
|
|
myToken->setColor(*it);
|
|
}
|
|
for ( it=abilities.begin() ; it != abilities.end(); it++ ){
|
|
myToken->basicAbilities[*it] = 1;
|
|
}
|
|
}
|
|
source->controller()->game->temp->addCard(myToken);
|
|
Spell * spell = NEW Spell(myToken);
|
|
spell->resolve();
|
|
spell->source->isToken = 1;
|
|
delete spell;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
sprintf(menuText, "Create %s",name.c_str());
|
|
return menuText;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ATokenCreator ::: abilities : ?" // << abilities
|
|
<< " ; types : ?" // << types
|
|
<< " ; colors : ?" // << colors
|
|
<< " ; power : " << power
|
|
<< " ; toughness : " << toughness
|
|
<< " ; name : " << name
|
|
<< " (";
|
|
return ActivatedAbility::toString(out) << ")";
|
|
}
|
|
|
|
ATokenCreator * clone() const{
|
|
ATokenCreator * a = NEW ATokenCreator(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~ATokenCreator(){
|
|
if (!isClone){
|
|
delete(multiplier);
|
|
}
|
|
}
|
|
|
|
};
|
|
//naming an ability line-------------------------------------------------------------------------
|
|
class ANamer:public ActivatedAbility{
|
|
public:
|
|
string name;
|
|
ANamer(int _id,MTGCardInstance * _source,ManaCost * _cost, string sname, int _doTap):ActivatedAbility(_id,_source,_cost,0,_doTap){
|
|
name = sname;
|
|
}
|
|
int resolve(){
|
|
return 0;
|
|
}
|
|
const char * getMenuText(){
|
|
sprintf(menuText, "%s",name.c_str());
|
|
return menuText;
|
|
}
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ANamer ::: name" << name
|
|
<< " (";
|
|
return ActivatedAbility::toString(out) << ")";
|
|
}
|
|
ANamer * clone() const{
|
|
ANamer * a = NEW ANamer(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
~ANamer(){
|
|
if (!isClone){}
|
|
}
|
|
};
|
|
//-----------------------------------------------------------------------------------------------
|
|
|
|
class AADestroyer:public ActivatedAbility{
|
|
public:
|
|
int bury;
|
|
AADestroyer(int _id, MTGCardInstance * _source, MTGCardInstance * _target, int _bury = 0, ManaCost * _cost=NULL):ActivatedAbility(_id,_source,_cost),bury(_bury){
|
|
if (_target) target = _target;
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if(_target){
|
|
if (bury) return _target->bury();
|
|
else return _target->destroy();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Destroy";
|
|
}
|
|
|
|
|
|
AADestroyer * clone() const{
|
|
AADestroyer * a = NEW AADestroyer(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/*Changes one of the basic abilities of target
|
|
source : spell
|
|
target : spell target (creature)
|
|
modifier : 1 to add the ability, 0 to remove it
|
|
_ability : Id of the ability, as described in mtgdefinitions
|
|
*/
|
|
class ABasicAbilityModifier:public MTGAbility{
|
|
public:
|
|
int modifier;
|
|
int ability;
|
|
int value_before_modification;
|
|
ABasicAbilityModifier(int _id, MTGCardInstance * _source, MTGCardInstance * _target, int _ability, int _modifier = 1): MTGAbility(_id,_source,_target),modifier(_modifier),ability(_ability){
|
|
|
|
}
|
|
|
|
int addToGame(){
|
|
value_before_modification = ((MTGCardInstance * )target)->basicAbilities[ability];
|
|
((MTGCardInstance * )target)->basicAbilities[ability]=modifier;
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
int destroy(){
|
|
if (((MTGCardInstance * )target)->basicAbilities[ability] == modifier){
|
|
((MTGCardInstance * )target)->basicAbilities[ability] = value_before_modification;
|
|
return 1;
|
|
}else{
|
|
//BUG !!!
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ABasicAbilityModifier ::: modifier : " << modifier
|
|
<< " ; ability : " << ability
|
|
<< " ; value_before_modification : " << value_before_modification
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
|
|
ABasicAbilityModifier * clone() const{
|
|
ABasicAbilityModifier * a = NEW ABasicAbilityModifier(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
//Modifies an ability until end of turn. Needs a target
|
|
class ABasicAbilityModifierUntilEOT:public TargetAbility{
|
|
public:
|
|
MTGCardInstance * mTargets[50];
|
|
int nbTargets;
|
|
int modifier;
|
|
int stateBeforeActivation[50];
|
|
int ability;
|
|
ABasicAbilityModifierUntilEOT(int _id, MTGCardInstance * _source, int _ability, ManaCost * _cost, TargetChooser * _tc = NULL, int _modifier = 1,int _tap=1): TargetAbility(_id,_source,_cost,0,_tap),modifier(_modifier), ability(_ability){
|
|
nbTargets = 0;
|
|
tc = _tc;
|
|
if (!tc) tc = NEW CreatureTargetChooser(_source);
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_UNTAP){
|
|
for (int i = 0; i < nbTargets; i++){
|
|
MTGCardInstance * mTarget = mTargets[i];
|
|
if(mTarget && mTarget->basicAbilities[ability]){
|
|
mTarget->basicAbilities[ability] = stateBeforeActivation[i];
|
|
}
|
|
}
|
|
nbTargets = 0;
|
|
}
|
|
TargetAbility::Update(dt);
|
|
}
|
|
|
|
|
|
int resolve(){
|
|
MTGCardInstance * mTarget = tc->getNextCardTarget();
|
|
if (mTarget){
|
|
mTargets[nbTargets] = mTarget;
|
|
stateBeforeActivation[nbTargets] = mTarget->basicAbilities[ability];
|
|
mTarget->basicAbilities[ability] = modifier;
|
|
nbTargets++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int addToGame(){
|
|
resolve();
|
|
return ActivatedAbility::addToGame();
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return Constants::MTGBasicAbilities[ability];
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ABasicAbilityModifierUntilEOT ::: mTargets : " << mTargets
|
|
<< " ; nbTargets : " << nbTargets
|
|
<< " ; modifier : " << modifier
|
|
<< " ; stateBeforeActivation : " << stateBeforeActivation
|
|
<< " ; ability : " << ability
|
|
<< " (";
|
|
return TargetAbility::toString(out) << ")";
|
|
}
|
|
|
|
ABasicAbilityModifierUntilEOT * clone() const{
|
|
ABasicAbilityModifierUntilEOT * a = NEW ABasicAbilityModifierUntilEOT(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
/*Instants that modifies a basic ability until end of turn */
|
|
class AInstantBasicAbilityModifierUntilEOT: public InstantAbility{
|
|
public:
|
|
int stateBeforeActivation;
|
|
int ability;
|
|
int value;
|
|
AInstantBasicAbilityModifierUntilEOT(int _id, MTGCardInstance * _source, MTGCardInstance * _target, int _ability, int value):InstantAbility(_id, _source, _target),ability(_ability),value(value){
|
|
|
|
}
|
|
|
|
int addToGame(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
stateBeforeActivation = _target->basicAbilities[ability];
|
|
_target->basicAbilities[ability] = value;
|
|
return InstantAbility::addToGame();
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return Constants::MTGBasicAbilities[ability];
|
|
}
|
|
|
|
int destroy(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
if (_target) _target->basicAbilities[ability] = stateBeforeActivation;
|
|
return 1;
|
|
}
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ABasicAbilityModifierUntilEOT ::: stateBeforeActivation : " << stateBeforeActivation
|
|
<< " ability : " << ability
|
|
<< " (";
|
|
return InstantAbility::toString(out) << ")";
|
|
}
|
|
|
|
AInstantBasicAbilityModifierUntilEOT * clone() const{
|
|
AInstantBasicAbilityModifierUntilEOT * a = NEW AInstantBasicAbilityModifierUntilEOT(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
//Alteration of Ability until of turn (Aura)
|
|
class ABasicAbilityAuraModifierUntilEOT: public ActivatedAbility{
|
|
public:
|
|
AInstantBasicAbilityModifierUntilEOT * ability;
|
|
ABasicAbilityAuraModifierUntilEOT(int _id, MTGCardInstance * _source, MTGCardInstance * _target, ManaCost * _cost, int _ability, int _value = 1):ActivatedAbility(_id,_source, _cost, 0,0){
|
|
target = _target;
|
|
ability = NEW AInstantBasicAbilityModifierUntilEOT(_id,_source,_target,_ability, _value);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * cost = NULL){
|
|
//The upper level "GenericTargetAbility" takes care of the click so we always return 0 here
|
|
return 0;
|
|
}
|
|
|
|
int resolve(){
|
|
MTGAbility * a = ability->clone();
|
|
a->target = target;
|
|
a->addToGame();
|
|
return 1;
|
|
}
|
|
|
|
int addToGame(){
|
|
resolve();
|
|
return ActivatedAbility::addToGame();
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return ability->getMenuText();
|
|
}
|
|
|
|
ABasicAbilityAuraModifierUntilEOT * clone() const{
|
|
ABasicAbilityAuraModifierUntilEOT * a = NEW ABasicAbilityAuraModifierUntilEOT(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~ABasicAbilityAuraModifierUntilEOT(){
|
|
if (!isClone) SAFE_DELETE(ability);
|
|
}
|
|
};
|
|
|
|
|
|
class AEquip:public TargetAbility{
|
|
public:
|
|
vector<MTGAbility *> currentAbilities;
|
|
AEquip(int _id, MTGCardInstance * _source, ManaCost * _cost=NULL, int doTap=0, int restrictions = ActivatedAbility::AS_SORCERY):TargetAbility(_id,_source,NULL,_cost,restrictions,doTap){
|
|
|
|
}
|
|
|
|
int unequip(){
|
|
source->target = NULL;
|
|
for (size_t i = 0; i < currentAbilities.size(); ++i){
|
|
MTGAbility * a = currentAbilities[i];
|
|
if(dynamic_cast<AEquip *>(a)){
|
|
SAFE_DELETE(a);
|
|
continue;
|
|
}
|
|
GameObserver::GetInstance()->removeObserver(currentAbilities[i]);
|
|
}
|
|
currentAbilities.clear();
|
|
return 1;
|
|
}
|
|
|
|
|
|
int equip(MTGCardInstance * equipped){
|
|
source->target = equipped;
|
|
AbilityFactory af;
|
|
af.getAbilities(¤tAbilities,NULL,source);
|
|
for (size_t i = 0; i < currentAbilities.size(); ++i){
|
|
MTGAbility * a = currentAbilities[i];
|
|
if(dynamic_cast<AEquip *>(a)) continue;
|
|
a->addToGame();
|
|
}
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
int resolve(){
|
|
MTGCardInstance * mTarget = tc->getNextCardTarget();
|
|
if (!mTarget) return 0;
|
|
if (mTarget == source) return 0;
|
|
unequip();
|
|
equip(mTarget);
|
|
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Equip";
|
|
}
|
|
|
|
|
|
int testDestroy(){
|
|
if (source->target && !game->isInPlay(source->target))
|
|
unequip();
|
|
return TargetAbility::testDestroy();
|
|
}
|
|
|
|
int destroy(){
|
|
unequip();
|
|
return TargetAbility::destroy();
|
|
}
|
|
|
|
AEquip * clone() const{
|
|
AEquip * a = NEW AEquip(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
/*Gives life each time a spell matching CardDescriptor's criteria are match . Optionnal manacost*/
|
|
class ASpellCastLife:public MTGAbility{
|
|
public:
|
|
CardDescriptor trigger;
|
|
ManaCost * cost;
|
|
int life;
|
|
MTGCardInstance * lastUsedOn;
|
|
MTGCardInstance * lastChecked;
|
|
ASpellCastLife(int id, MTGCardInstance * _source, CardDescriptor _trigger, ManaCost * _cost, int _life): MTGAbility(id, _source), trigger(_trigger), cost(_cost), life(_life), lastUsedOn(NULL), lastChecked(NULL) {
|
|
}
|
|
ASpellCastLife(int id, MTGCardInstance * _source, int color, ManaCost * _cost, int _life): MTGAbility(id, _source), cost(_cost), life(_life), lastUsedOn(NULL), lastChecked(NULL) {
|
|
trigger.setColor(color);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * _card, ManaCost * mana = NULL){
|
|
if (_card == source && game->currentlyActing()->game->inPlay->hasCard(source)){
|
|
if (game->currentlyActing()->getManaPool()->canAfford(cost)){
|
|
Interruptible * laststackitem = game->mLayers->stackLayer()->getAt(-1);
|
|
if (laststackitem && laststackitem->type == ACTION_SPELL){
|
|
Spell * spell = (Spell*)laststackitem;
|
|
if (spell->source != lastUsedOn && trigger.match(spell->source)){
|
|
lastChecked = spell->source;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reactToClick(MTGCardInstance * _card){
|
|
if (!isReactingToClick( _card)) return 0;
|
|
game->currentlyActing()->getManaPool()->pay(cost);
|
|
game->currentlyActing()->life+=life;
|
|
lastUsedOn = lastChecked;
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ASpellCastLife ::: trigger : ? " // << trigger
|
|
<< " ; cost : " << cost
|
|
<< " ; life : " << life
|
|
<< " ; lastUsedOn : " << lastUsedOn
|
|
<< " ; lastChecked : " << lastChecked
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
|
|
ASpellCastLife * clone() const{
|
|
ASpellCastLife * a = NEW ASpellCastLife(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~ASpellCastLife(){
|
|
SAFE_DELETE(cost);
|
|
}
|
|
|
|
};
|
|
|
|
//Allows to untap at any moment for an amount of mana
|
|
class AUnBlocker:public MTGAbility{
|
|
public:
|
|
ManaCost * cost;
|
|
AUnBlocker(int id, MTGCardInstance * _source, MTGCardInstance * _target, ManaCost * _cost):MTGAbility(id, _source, _target), cost(_cost){
|
|
}
|
|
|
|
|
|
int isReactingToClick(MTGCardInstance * _card,ManaCost * mana = NULL){
|
|
if (_card == target && game->currentlyActing()->game->inPlay->hasCard(source) && (MTGCardInstance *) _card->isTapped()){
|
|
if (game->currentlyActing()->getManaPool()->canAfford(cost)){
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reactToClick(MTGCardInstance * _card){
|
|
if (!isReactingToClick( _card)) return 0;
|
|
game->currentlyActing()->getManaPool()->pay(cost);
|
|
_card->attemptUntap();
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AUnBlocker ::: cost : " << cost
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
|
|
AUnBlocker * clone() const{
|
|
AUnBlocker * a = NEW AUnBlocker(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
//Protection From (creature/aura)
|
|
class AProtectionFrom: public MTGAbility{
|
|
public:
|
|
TargetChooser * fromTc;
|
|
AProtectionFrom(int id, MTGCardInstance * _source, MTGCardInstance * _target, TargetChooser *fromTc):MTGAbility(id,_source,_target),fromTc(fromTc){
|
|
|
|
}
|
|
|
|
int addToGame(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
_target->addProtection(fromTc);
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
int destroy(){
|
|
((MTGCardInstance *)target)->removeProtection(fromTc);
|
|
return 1;
|
|
}
|
|
|
|
AProtectionFrom * clone() const{
|
|
AProtectionFrom * a = NEW AProtectionFrom(*this);
|
|
a->fromTc = fromTc->clone();
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~AProtectionFrom(){
|
|
SAFE_DELETE(fromTc);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//Can't be blocked by...
|
|
class ACantBeBlockedBy: public MTGAbility{
|
|
public:
|
|
TargetChooser * fromTc;
|
|
ACantBeBlockedBy(int id, MTGCardInstance * _source, MTGCardInstance * _target, TargetChooser *fromTc):MTGAbility(id,_source,_target),fromTc(fromTc){
|
|
|
|
}
|
|
|
|
int addToGame(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
_target->addCantBeBlockedBy(fromTc);
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
int destroy(){
|
|
((MTGCardInstance *)target)->removeCantBeBlockedBy(fromTc);
|
|
return 1;
|
|
}
|
|
|
|
ACantBeBlockedBy * clone() const{
|
|
ACantBeBlockedBy * a = NEW ACantBeBlockedBy(*this);
|
|
a->fromTc = fromTc->clone();
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~ACantBeBlockedBy(){
|
|
SAFE_DELETE(fromTc);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//Alteration of Power and Toughness (enchantments)
|
|
class APowerToughnessModifier: public MTGAbility{
|
|
public:
|
|
WParsedPT * wppt;
|
|
APowerToughnessModifier(int id, MTGCardInstance * _source, MTGCardInstance * _target, WParsedPT * wppt):MTGAbility(id,_source,_target),wppt(wppt){
|
|
|
|
}
|
|
|
|
int addToGame(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
_target->power += wppt->power.getValue();
|
|
_target->addToToughness(wppt->toughness.getValue());
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
int destroy(){
|
|
((MTGCardInstance *)target)->power -= wppt->power.getValue();
|
|
((MTGCardInstance *)target)->addToToughness(- wppt->toughness.getValue());
|
|
return 1;
|
|
}
|
|
|
|
APowerToughnessModifier * clone() const{
|
|
APowerToughnessModifier * a = NEW APowerToughnessModifier(*this);
|
|
a->wppt = NEW WParsedPT(*(a->wppt));
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~APowerToughnessModifier(){
|
|
delete(wppt);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//Alteration of Power and toughness until end of turn (instant)
|
|
class AInstantPowerToughnessModifierUntilEOT: public InstantAbility{
|
|
public:
|
|
WParsedPT * wppt;
|
|
AInstantPowerToughnessModifierUntilEOT(int _id, MTGCardInstance * _source, MTGCardInstance * _target, WParsedPT * wppt): InstantAbility(_id, _source, _target), wppt(wppt){
|
|
}
|
|
|
|
int resolve(){
|
|
((MTGCardInstance *)target)->power +=wppt->power.getValue();
|
|
((MTGCardInstance *)target)->addToToughness(wppt->toughness.getValue());
|
|
return 1;
|
|
}
|
|
|
|
int destroy(){
|
|
((MTGCardInstance *)target)->power -=wppt->power.getValue();
|
|
((MTGCardInstance *)target)->addToToughness(-wppt->toughness.getValue());
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
sprintf(menuText, "%i/%i",wppt->power.getValue(),wppt->toughness.getValue());
|
|
return menuText;
|
|
}
|
|
|
|
|
|
AInstantPowerToughnessModifierUntilEOT * clone() const{
|
|
AInstantPowerToughnessModifierUntilEOT * a = NEW AInstantPowerToughnessModifierUntilEOT(*this);
|
|
a->wppt = NEW WParsedPT(*(a->wppt));
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~AInstantPowerToughnessModifierUntilEOT(){
|
|
delete wppt;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
//Alteration of Power and Toughness until end of turn (Aura)
|
|
class APowerToughnessModifierUntilEndOfTurn: public ActivatedAbility{
|
|
public:
|
|
AInstantPowerToughnessModifierUntilEOT * ability;
|
|
int counters;
|
|
int maxcounters;
|
|
APowerToughnessModifierUntilEndOfTurn(int id, MTGCardInstance * _source, MTGCardInstance * _target, WParsedPT * wppt, ManaCost * _cost = NULL, int _maxcounters = 0):ActivatedAbility(id,_source,_cost,0,0),maxcounters(_maxcounters){
|
|
counters = 0;
|
|
target=_target;
|
|
ability = NEW AInstantPowerToughnessModifierUntilEOT(id,_source,_target,wppt);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * cost = NULL){
|
|
//The upper level "GenericTargetAbility" takes care of the click so we always return 0 here
|
|
return 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_AFTER_EOT){
|
|
counters = 0;
|
|
}
|
|
ActivatedAbility::Update(dt);
|
|
}
|
|
|
|
int fireAbility(){
|
|
return resolve();
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return ability->getMenuText();
|
|
}
|
|
|
|
/* int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (!ActivatedAbility::isReactingToClick(card,mana)) return 0;
|
|
return (!maxcounters || (counters < maxcounters));
|
|
}*/
|
|
|
|
int resolve(){
|
|
MTGAbility * a = ability->clone();
|
|
a->target = target;
|
|
a->addToGame();
|
|
counters++;
|
|
return 1;
|
|
}
|
|
|
|
int addToGame(){
|
|
resolve();
|
|
return ActivatedAbility::addToGame();
|
|
}
|
|
|
|
APowerToughnessModifierUntilEndOfTurn * clone() const{
|
|
APowerToughnessModifierUntilEndOfTurn * a = NEW APowerToughnessModifierUntilEndOfTurn(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~APowerToughnessModifierUntilEndOfTurn(){
|
|
if (!isClone) SAFE_DELETE(ability);
|
|
}
|
|
};
|
|
|
|
|
|
class GenericInstantAbility: public InstantAbility, public NestedAbility{
|
|
public:
|
|
GenericInstantAbility(int _id, MTGCardInstance * _source, Damageable * _target, MTGAbility * ability): InstantAbility(_id, _source, _target), NestedAbility(ability){
|
|
ability->target = _target;
|
|
}
|
|
|
|
int addToGame(){
|
|
ability->forceDestroy = -1;
|
|
ability->addToGame();
|
|
return InstantAbility::addToGame();
|
|
}
|
|
|
|
int destroy(){
|
|
ability->forceDestroy = 1;
|
|
return InstantAbility::destroy();
|
|
}
|
|
|
|
GenericInstantAbility * clone() const{
|
|
GenericInstantAbility * a = NEW GenericInstantAbility(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//Circle of Protections
|
|
class ACircleOfProtection: public TargetAbility{
|
|
protected:
|
|
map<ReplacementEffect*, int> current;
|
|
public:
|
|
ACircleOfProtection(int _id, MTGCardInstance * source, int _color):TargetAbility(_id,source,NEW SpellOrPermanentTargetChooser(source,_color),NEW ManaCost(),0,0){
|
|
cost->add(Constants::MTG_COLOR_ARTIFACT,1);
|
|
tc->targetter = NULL; //Circle of Protection doesn't use the word "source"
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * _target = NULL;
|
|
if (! (_target = tc->getNextCardTarget())){
|
|
Spell * starget = tc->getNextSpellTarget();
|
|
_target = starget->source;
|
|
}
|
|
if (!_target) return 0;
|
|
REDamagePrevention * re = NEW REDamagePrevention (
|
|
this,
|
|
NEW CardTargetChooser(_target,NULL),
|
|
NEW PlayerTargetChooser(0,1,source->controller()));
|
|
current[re] = 1;
|
|
game->replacementEffects->add(re);
|
|
return 1;
|
|
}
|
|
|
|
void clear(){
|
|
for (map<ReplacementEffect*, int>::iterator it = current.begin(); it!=current.end(); it++){
|
|
ReplacementEffect* re = (*it).first;
|
|
game->replacementEffects->remove(re);
|
|
delete re;
|
|
}
|
|
current.clear();
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_UNTAP) clear();
|
|
TargetAbility::Update(dt);
|
|
}
|
|
|
|
~ACircleOfProtection(){
|
|
clear();
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ACircleOfProtection ::: (";
|
|
return TargetAbility::toString(out) << ")";
|
|
}
|
|
ACircleOfProtection * clone() const{
|
|
ACircleOfProtection * a = NEW ACircleOfProtection(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Basic regeneration mechanism for a Mana cost
|
|
class AStandardRegenerate:public ActivatedAbility{
|
|
public:
|
|
AStandardRegenerate(int _id, MTGCardInstance * _source, MTGCardInstance * _target, ManaCost * _cost = NULL):ActivatedAbility(_id,_source,_cost,0,0){
|
|
target = _target;
|
|
aType = MTGAbility::STANDARD_REGENERATE;
|
|
}
|
|
|
|
int resolve(){
|
|
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
_target->regenerate();
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Regenerate";
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AStandardRegenerate ::: (";
|
|
return ActivatedAbility::toString(out) << ")";
|
|
}
|
|
AStandardRegenerate * clone() const{
|
|
AStandardRegenerate * a = NEW AStandardRegenerate(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Aura Enchantments that provide controller of target life or damages at a given phase of their turn
|
|
class ARegularLifeModifierAura:public MTGAbility{
|
|
public:
|
|
int life;
|
|
int phase;
|
|
int onlyIfTargetTapped;
|
|
ARegularLifeModifierAura(int id, MTGCardInstance * _source, MTGCardInstance * _target, int _phase, int _life, int _onlyIfTargetTapped=0):MTGAbility(id,_source,_target),life(_life), phase(_phase),onlyIfTargetTapped(_onlyIfTargetTapped){
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase !=currentPhase && newPhase==phase && game->currentPlayer==((MTGCardInstance *)target)->controller()){
|
|
if (!onlyIfTargetTapped || ((MTGCardInstance *)target)->isTapped()){
|
|
if (life > 0){
|
|
game->currentPlayer->life+=life;
|
|
}else{
|
|
game->mLayers->stackLayer()->addDamage(source, game->currentPlayer, -life);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ARegularLifeModifierAura ::: life : " << life
|
|
<< " ; phase : " << phase
|
|
<< " ; onlyIfTargetTapped : " << onlyIfTargetTapped
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
ARegularLifeModifierAura * clone() const{
|
|
ARegularLifeModifierAura * a = NEW ARegularLifeModifierAura(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//ExaltedAbility (Shards of Alara)
|
|
class AExalted:public TriggeredAbility{
|
|
public:
|
|
int power, toughness;
|
|
MTGCardInstance * luckyWinner;
|
|
AExalted(int _id, MTGCardInstance * _source, int _power = 1, int _toughness = 1):TriggeredAbility(_id, _source),power(_power),toughness(_toughness){
|
|
luckyWinner = NULL;
|
|
}
|
|
|
|
int triggerOnEvent(WEvent * event) {
|
|
if (WEventPhaseChange* pe = dynamic_cast<WEventPhaseChange*>(event)) {
|
|
if (luckyWinner && Constants::MTG_PHASE_AFTER_EOT == pe->from->id){
|
|
luckyWinner->addToToughness(-toughness);
|
|
luckyWinner->power-=power;
|
|
luckyWinner = NULL;
|
|
}
|
|
|
|
if (Constants::MTG_PHASE_COMBATATTACKERS == pe->from->id) {
|
|
int nbattackers = 0;
|
|
MTGGameZone * z = source->controller()->game->inPlay;
|
|
int nbcards = z->nb_cards;
|
|
for (int i = 0; i < nbcards; ++i){
|
|
MTGCardInstance * c = z->cards[i];
|
|
if (c->attacker){
|
|
nbattackers++;
|
|
luckyWinner = c;
|
|
}
|
|
}
|
|
if (nbattackers == 1) return 1;
|
|
else luckyWinner = NULL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int resolve(){
|
|
if (!luckyWinner) return 0;
|
|
luckyWinner->addToToughness(toughness);
|
|
luckyWinner->power+=power;
|
|
return 1;
|
|
}
|
|
|
|
|
|
AExalted * clone() const{
|
|
AExalted * a = NEW AExalted(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Converts lands to creatures (Kormus bell, Living lands)
|
|
class AConvertLandToCreatures:public ListMaintainerAbility{
|
|
public:
|
|
int type;
|
|
int power, toughness;
|
|
AConvertLandToCreatures(int _id, MTGCardInstance * _source, const char * _type, int _power = 1, int _toughness = 1):ListMaintainerAbility(_id, _source),power(_power),toughness(_toughness){
|
|
type = Subtypes::subtypesList->find(_type);
|
|
}
|
|
|
|
|
|
int canBeInList(MTGCardInstance * card){
|
|
if (card->hasType(type) && game->isInPlay(card)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int added(MTGCardInstance * card){
|
|
card->power = 1;
|
|
card->setToughness(1);
|
|
card->setSubtype("creature");
|
|
return 1;
|
|
}
|
|
|
|
int removed(MTGCardInstance * card){
|
|
card->removeType("creature");
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AConvertLandToCreatures ::: power : " << power
|
|
<< " ; toughness : " << toughness
|
|
<< " ; type : " << type
|
|
<< " (";
|
|
return ListMaintainerAbility::toString(out) << ")";
|
|
}
|
|
AConvertLandToCreatures * clone() const{
|
|
AConvertLandToCreatures * a = NEW AConvertLandToCreatures(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Generic Kird Ape
|
|
class AAsLongAs:public ListMaintainerAbility, public NestedAbility{
|
|
public:
|
|
MTGAbility * a;
|
|
int includeSelf;
|
|
int mini,maxi;
|
|
AAsLongAs(int _id, MTGCardInstance * _source, Damageable * _target, TargetChooser * _tc, int _includeSelf, MTGAbility * ability,int mini = 0, int maxi = 0):ListMaintainerAbility(_id, _source,_target),NestedAbility(ability),mini(mini),maxi(maxi){
|
|
tc = _tc;
|
|
includeSelf = _includeSelf;
|
|
tc->targetter = NULL;
|
|
ability->source = source;
|
|
ability->target = target;
|
|
a = NULL;
|
|
}
|
|
|
|
int canBeInList(MTGCardInstance * card){
|
|
if ((includeSelf || card!=source) && tc->canTarget(card)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int resolve(){
|
|
//TODO check if ability is oneShot ?
|
|
updateTargets();
|
|
int size = (int) cards.size();
|
|
if (maxi && size < maxi && (!mini || size > mini)) addAbilityToGame(); //special case for 0
|
|
if (ability->oneShot) a = NULL; //allows to call the effect several times
|
|
cards.clear();
|
|
players.clear();
|
|
return 1;
|
|
}
|
|
|
|
int addAbilityToGame(){
|
|
if (a) return 0;
|
|
a = ability->clone();
|
|
if (a->oneShot){
|
|
a->resolve();
|
|
delete(a);
|
|
}else{
|
|
a->addToGame();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int removeAbilityFromGame(){
|
|
if (!a) return 0;
|
|
game->removeObserver(a);
|
|
a = NULL;
|
|
return 1;
|
|
}
|
|
|
|
int _added(Damageable * d){
|
|
int size = (int) cards.size();
|
|
if (maxi && size >= maxi) return removeAbilityFromGame();
|
|
if (maxi) return 0;
|
|
if (size <= mini) return 0;
|
|
return addAbilityToGame();
|
|
}
|
|
|
|
int added(MTGCardInstance * card){
|
|
return _added(card);
|
|
}
|
|
|
|
int added(Player * p){
|
|
return _added(p);
|
|
}
|
|
|
|
|
|
int removed(MTGCardInstance * card){
|
|
size_t size = cards.size();
|
|
if (mini && (int)size <= mini) return removeAbilityFromGame();
|
|
if (maxi && (int)size == maxi-1) return addAbilityToGame();
|
|
if (!mini && !maxi && size !=0) return 0;
|
|
return removeAbilityFromGame();
|
|
}
|
|
|
|
~AAsLongAs(){
|
|
if (!isClone) SAFE_DELETE(ability);
|
|
}
|
|
|
|
|
|
AAsLongAs * clone() const{
|
|
AAsLongAs * a = NEW AAsLongAs(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Lords (Merfolk lord...) give power and toughness to OTHER creatures of their type, they can give them special abilities, regeneration
|
|
class ALord:public ListMaintainerAbility, public NestedAbility{
|
|
public:
|
|
int includeSelf;
|
|
map<Damageable *, MTGAbility *> abilities;
|
|
|
|
ALord(int _id, MTGCardInstance * card, TargetChooser * _tc, int _includeSelf, MTGAbility * a):ListMaintainerAbility(_id,card), NestedAbility(a){
|
|
tc = _tc;
|
|
tc->targetter = NULL;
|
|
includeSelf = _includeSelf;
|
|
}
|
|
|
|
int canBeInList(Player *p){
|
|
if (tc->canTarget(p)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int canBeInList(MTGCardInstance * card){
|
|
if ( (includeSelf || card!=source) && tc->canTarget(card)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int resolve(){
|
|
//TODO check if ability is oneShot ?
|
|
updateTargets();
|
|
cards.clear();
|
|
players.clear();
|
|
return 1;
|
|
}
|
|
|
|
int _added(Damageable * d){
|
|
MTGAbility * a = ability->clone();
|
|
a->target = d;
|
|
if (a->oneShot){
|
|
a->resolve();
|
|
delete(a);
|
|
}else{
|
|
if (d->type_as_damageable == DAMAGEABLE_MTGCARDINSTANCE){
|
|
a->source = (MTGCardInstance *)d;
|
|
}
|
|
if (oneShot){
|
|
MTGAbility * wrapper = NEW GenericInstantAbility(1,source,d,a);
|
|
wrapper->addToGame();
|
|
}else{
|
|
a->addToGame();
|
|
abilities[d] = a;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int added(MTGCardInstance * card){
|
|
return _added(card);
|
|
}
|
|
|
|
int added(Player * p){
|
|
return _added(p);
|
|
}
|
|
|
|
int removed(MTGCardInstance * card){
|
|
if(abilities.find(card) != abilities.end()){
|
|
game->removeObserver(abilities[card]);
|
|
abilities.erase(card);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
~ALord(){
|
|
if (!isClone) SAFE_DELETE(ability);
|
|
}
|
|
|
|
ALord * clone() const{
|
|
ALord * a = NEW ALord(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Foreach (plague rats...)
|
|
class AForeach:public ListMaintainerAbility, public NestedAbility{
|
|
public:
|
|
int includeSelf;
|
|
int mini;
|
|
int maxi;
|
|
map<Damageable *, MTGAbility *> abilities;
|
|
AForeach(int _id, MTGCardInstance * card,Damageable * _target, TargetChooser * _tc, int _includeSelf, MTGAbility * a, int mini = 0, int maxi = 0):ListMaintainerAbility(_id,card,_target), NestedAbility(a),mini(mini),maxi(maxi){
|
|
tc = _tc;
|
|
tc->targetter = NULL;
|
|
includeSelf = _includeSelf;
|
|
ability->target = _target;
|
|
}
|
|
|
|
int canBeInList(MTGCardInstance * card){
|
|
if ( (includeSelf || card!=source) && tc->canTarget(card)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int added(MTGCardInstance * card){
|
|
if (mini && cards.size() <= (size_t)mini) return 0;
|
|
if (maxi && cards.size() >= (size_t)maxi) return 0;
|
|
|
|
MTGAbility * a = ability->clone();
|
|
a->target = target;
|
|
if (a->oneShot){
|
|
a->resolve();
|
|
delete(a);
|
|
}else{
|
|
a->addToGame();
|
|
abilities[card] = a;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int removed(MTGCardInstance * card){
|
|
if(abilities.find(card) != abilities.end()){
|
|
game->removeObserver(abilities[card]);
|
|
abilities.erase(card);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
AForeach * clone() const{
|
|
AForeach * a = NEW AForeach(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
int resolve(){
|
|
//TODO check if ability is oneShot ?
|
|
updateTargets();
|
|
cards.clear();
|
|
players.clear();
|
|
return 1;
|
|
}
|
|
|
|
|
|
~AForeach(){
|
|
if (!isClone) SAFE_DELETE(ability);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
class AThis:public MTGAbility, public NestedAbility{
|
|
public:
|
|
MTGAbility * a;
|
|
ThisDescriptor * td;
|
|
AThis(int _id, MTGCardInstance * _source, Damageable * _target, ThisDescriptor * _td, MTGAbility * ability):MTGAbility(_id, _source,_target),NestedAbility(ability){
|
|
td = _td;
|
|
ability->source = source;
|
|
ability->target = target;
|
|
a = NULL;
|
|
SAFE_DELETE(tc);
|
|
}
|
|
int removeFromGame(){
|
|
return removeAbilityFromGame();
|
|
}
|
|
|
|
int addToGame(){
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
void Update(float dt){
|
|
resolve();
|
|
}
|
|
|
|
int resolve(){
|
|
//TODO check if ability is oneShot ?
|
|
int match;
|
|
match = td->match(source);
|
|
if (match > 0){
|
|
addAbilityToGame();
|
|
}else{
|
|
removeAbilityFromGame();
|
|
}
|
|
if (ability->oneShot) a = NULL; //allows to call the effect several times
|
|
return 1;
|
|
}
|
|
|
|
int addAbilityToGame(){
|
|
if (a) return 0;
|
|
a = ability->clone();
|
|
if (a->oneShot){
|
|
a->resolve();
|
|
delete(a);
|
|
}else{
|
|
a->addToGame();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int removeAbilityFromGame(){
|
|
if (!a) return 0;
|
|
game->removeObserver(a);
|
|
a = NULL;
|
|
return 1;
|
|
}
|
|
|
|
~AThis(){
|
|
if (!isClone) SAFE_DELETE(ability); SAFE_DELETE(td);
|
|
}
|
|
|
|
|
|
AThis * clone() const{
|
|
AThis * a = NEW AThis(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
class AThisForEach:public MTGAbility, public NestedAbility{
|
|
public:
|
|
ThisDescriptor * td;
|
|
vector<MTGAbility *> abilities;
|
|
AThisForEach(int _id, MTGCardInstance * _source, Damageable * _target, ThisDescriptor * _td, MTGAbility * ability):MTGAbility(_id, _source,_target),NestedAbility(ability){
|
|
td = _td;
|
|
ability->source = source;
|
|
ability->target = target;
|
|
SAFE_DELETE(tc);
|
|
}
|
|
|
|
int removeFromGame(){
|
|
return removeAbilityFromGame();
|
|
}
|
|
|
|
int addToGame(){
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
void Update(float dt){
|
|
resolve();
|
|
}
|
|
|
|
int resolve(){
|
|
//TODO check if ability is oneShot ?
|
|
int matches;
|
|
matches = td->match(source);
|
|
if (matches > 0) {
|
|
if ((int)(abilities.size()) > matches){
|
|
removeAbilityFromGame();
|
|
}
|
|
for (int i = 0; i < matches - (int)(abilities.size()); i++) {
|
|
addAbilityToGame();
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int addAbilityToGame(){
|
|
MTGAbility * a = ability->clone();
|
|
a->target = target;
|
|
if (a->oneShot){
|
|
a->resolve();
|
|
delete(a);
|
|
}else{
|
|
a->addToGame();
|
|
abilities.push_back(a);
|
|
//abilities[abilities.size()] = a;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int removeAbilityFromGame(){
|
|
for (int i = abilities.size(); i > 0; i--){
|
|
game->removeObserver(abilities[i-1]);
|
|
}
|
|
abilities.clear();
|
|
return 1;
|
|
}
|
|
|
|
~AThisForEach(){
|
|
if (!isClone){
|
|
SAFE_DELETE(ability);
|
|
SAFE_DELETE(td);
|
|
}
|
|
if (abilities.size()){
|
|
removeAbilityFromGame();
|
|
}
|
|
}
|
|
|
|
|
|
AThisForEach * clone() const{
|
|
AThisForEach * a = NEW AThisForEach(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
//lifeset
|
|
class AALifeSet:public ActivatedAbilityTP{
|
|
public:
|
|
WParsedInt * life;
|
|
AALifeSet(int _id, MTGCardInstance * _source, Targetable * _target, WParsedInt * life, ManaCost * _cost=NULL, int doTap = 0, int who = TargetChooser::UNSET):ActivatedAbilityTP(_id,_source,_target,_cost,doTap,who),life(life){
|
|
}
|
|
|
|
int resolve(){
|
|
Damageable * _target = (Damageable *) getTarget();
|
|
if(_target){
|
|
if (_target->type_as_damageable == DAMAGEABLE_MTGCARDINSTANCE){
|
|
_target = ((MTGCardInstance *)_target)->controller();
|
|
}
|
|
_target->life =life->getValue();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Set Life";
|
|
}
|
|
|
|
AALifeSet * clone() const{
|
|
AALifeSet * a = NEW AALifeSet(*this);
|
|
a->life = NEW WParsedInt(*(a->life));
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~AALifeSet(){
|
|
SAFE_DELETE(life);
|
|
}
|
|
|
|
|
|
};
|
|
|
|
//lifesetend
|
|
class AADamager:public ActivatedAbilityTP{
|
|
public:
|
|
WParsedInt * damage;
|
|
AADamager(int _id, MTGCardInstance * _source, Targetable * _target, WParsedInt * damage, ManaCost * _cost=NULL, int doTap = 0, int who = TargetChooser::UNSET):ActivatedAbilityTP(_id,_source,_target,_cost,doTap,who),damage(damage){
|
|
aType = MTGAbility::DAMAGER;
|
|
}
|
|
|
|
int resolve(){
|
|
Damageable * _target = (Damageable *) getTarget();
|
|
if(_target){
|
|
game->mLayers->stackLayer()->addDamage(source,_target, damage->getValue());
|
|
game->mLayers->stackLayer()->resolve();
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Damage";
|
|
}
|
|
|
|
AADamager * clone() const{
|
|
AADamager * a = NEW AADamager(*this);
|
|
a->damage = NEW WParsedInt(*(a->damage));
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~AADamager(){
|
|
SAFE_DELETE(damage);
|
|
}
|
|
|
|
|
|
};
|
|
|
|
/* Standard Damager, can choose a NEW target each time the price is paid */
|
|
class TADamager:public TargetAbility{
|
|
public:
|
|
|
|
TADamager(int id, MTGCardInstance * card, ManaCost * _cost, WParsedInt * damage, TargetChooser * _tc = NULL, int _tap = 0):TargetAbility(id,card, _tc, _cost,0,_tap){
|
|
if (!tc) tc = NEW DamageableTargetChooser(card);
|
|
ability = NEW AADamager(id,card,NULL,damage);
|
|
}
|
|
|
|
TADamager * clone() const{
|
|
TADamager * a = NEW TADamager(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
/* Can tap a target for a cost */
|
|
class AATapper:public ActivatedAbility{
|
|
public:
|
|
AATapper(int id, MTGCardInstance * card, MTGCardInstance * _target,ManaCost * _cost = NULL, int doTap = 0):ActivatedAbility(id,card, _cost,0,doTap){
|
|
target = _target;
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if (_target){
|
|
while (_target->next) _target=_target->next; //This is for cards such as rampant growth
|
|
_target->tap();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Tap";
|
|
}
|
|
|
|
AATapper * clone() const{
|
|
AATapper * a = NEW AATapper(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
/* Can untap a target for a cost */
|
|
class AAUntapper:public ActivatedAbility{
|
|
public:
|
|
AAUntapper(int id, MTGCardInstance * card, MTGCardInstance * _target,ManaCost * _cost = NULL, int doTap = 0):ActivatedAbility(id,card, _cost,0,doTap){
|
|
target = _target;
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if (_target){
|
|
while (_target->next) _target=_target->next; //This is for cards such as rampant growth
|
|
_target->untap();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Untap";
|
|
}
|
|
|
|
AAUntapper * clone() const{
|
|
AAUntapper * a = NEW AAUntapper(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
// Add life of gives damage if a given zone has more or less than [condition] cards at the beginning of [phase]
|
|
//Ex : the rack, ivory tower...
|
|
class ALifeZoneLink:public MTGAbility{
|
|
public:
|
|
int phase;
|
|
int condition;
|
|
int life;
|
|
int controller;
|
|
int nbcards;
|
|
MTGGameZone * zone;
|
|
ALifeZoneLink(int _id ,MTGCardInstance * card, int _phase, int _condition, int _life = -1, int _controller = 0, MTGGameZone * _zone = NULL):MTGAbility(_id, card){
|
|
phase = _phase;
|
|
condition = _condition;
|
|
controller = _controller;
|
|
life = _life;
|
|
zone = _zone;
|
|
if (zone == NULL){
|
|
if (controller){
|
|
zone = game->currentPlayer->game->hand;
|
|
}else{
|
|
zone = game->opponent()->game->hand;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase == phase){
|
|
if ((controller && game->currentPlayer == source->controller()) ||(!controller && game->currentPlayer != source->controller()) ){
|
|
if ((condition < 0 && zone->nb_cards < - condition) ||(condition >0 && zone->nb_cards > condition)){
|
|
int diff = zone->nb_cards - condition;
|
|
if (condition < 0) diff = - condition - zone->nb_cards;
|
|
if (life > 0){
|
|
game->currentPlayer->life+=life*diff;
|
|
}else{
|
|
game->mLayers->stackLayer()->addDamage(source,game->currentPlayer,-life*diff);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ALifeZoneLink ::: phase : " << phase
|
|
<< " ; condition : " << condition
|
|
<< " ; life : " << life
|
|
<< " ; controller : " << controller
|
|
<< " ; nbcards : " << nbcards
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
ALifeZoneLink * clone() const{
|
|
ALifeZoneLink * a = NEW ALifeZoneLink(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Creatures that cannot attack if opponent has not a given type of land, and die if controller has not this type of land
|
|
//Ex : pirate ship...
|
|
class AStrongLandLinkCreature: public MTGAbility{
|
|
public:
|
|
char land[20];
|
|
AStrongLandLinkCreature(int _id, MTGCardInstance * _source, const char * _land):MTGAbility(_id, _source){
|
|
sprintf(land,"%s",_land);
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (source->isAttacker()){
|
|
if (!game->opponent()->game->inPlay->hasType(land)){
|
|
source->toggleAttacker();
|
|
//TODO Improve, there can be race conditions here
|
|
}
|
|
}
|
|
Player * player = source->controller();
|
|
if(!player->game->inPlay->hasType(land)){
|
|
player->game->putInGraveyard(source);
|
|
}
|
|
}
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AStrongLandLinkCreature ::: land : " << land
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AStrongLandLinkCreature * clone() const{
|
|
AStrongLandLinkCreature * a = NEW AStrongLandLinkCreature(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Steal control of a target
|
|
class AControlStealAura: public MTGAbility{
|
|
public:
|
|
Player * originalController;
|
|
AControlStealAura(int _id , MTGCardInstance * _source, MTGCardInstance * _target):MTGAbility(_id, _source, _target){
|
|
originalController = _target->controller();
|
|
MTGCardInstance * copy = _target->changeController(game->currentlyActing());
|
|
target = copy;
|
|
source->target = copy;
|
|
}
|
|
|
|
int destroy(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
Player * p = _target->controller();
|
|
if (p && p->game->inPlay->hasCard(_target)){ //if the target is still in game -> spell was destroyed
|
|
_target->changeController(originalController);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AControlStealAura ::: originalController : " << originalController
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AControlStealAura * clone() const{
|
|
AControlStealAura * a = NEW AControlStealAura(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Creatures that kill their blockers
|
|
//Ex : Cockatrice
|
|
class AOldSchoolDeathtouch:public MTGAbility{
|
|
public:
|
|
MTGCardInstance * opponents[20];
|
|
int nbOpponents;
|
|
AOldSchoolDeathtouch(int _id, MTGCardInstance * _source):MTGAbility(_id, _source){
|
|
nbOpponents = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase){
|
|
if( newPhase == Constants::MTG_PHASE_COMBATDAMAGE){
|
|
nbOpponents = 0;
|
|
MTGCardInstance * opponent = source->getNextOpponent();
|
|
while (opponent && !opponent->hasSubtype("wall")){
|
|
opponents[nbOpponents] = opponent;
|
|
nbOpponents ++;
|
|
opponent = source->getNextOpponent(opponent);
|
|
}
|
|
}else if (newPhase == Constants::MTG_PHASE_COMBATEND){
|
|
for (int i = 0; i < nbOpponents ; i++){
|
|
opponents[i]->destroy();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int testDestroy(){
|
|
if(!game->isInPlay(source) && currentPhase != Constants::MTG_PHASE_UNTAP){
|
|
return 0;
|
|
}else{
|
|
return MTGAbility::testDestroy();
|
|
}
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AOldSchoolDeathtouch ::: opponents : " << opponents
|
|
<< " ; nbOpponents : " << nbOpponents
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AOldSchoolDeathtouch * clone() const{
|
|
AOldSchoolDeathtouch * a = NEW AOldSchoolDeathtouch(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//reset card cost-----------------------------------------
|
|
class AResetCost:public MTGAbility{
|
|
public:
|
|
AResetCost(int id, MTGCardInstance * source, MTGCardInstance * target):MTGAbility(id,source,target){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
}
|
|
int addToGame(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
_target->controller()->game->putInZone(_target,_target->controller()->game->hand, _target->controller()->game->hand);
|
|
return MTGAbility::addToGame();
|
|
}
|
|
AResetCost * clone() const{
|
|
AResetCost * a = NEW AResetCost(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
~AResetCost(){
|
|
}
|
|
};
|
|
|
|
//reduce or increase manacost of target by color:amount------------------------------------------
|
|
class AManaRedux:public MTGAbility{
|
|
public:
|
|
int amount;
|
|
int type;
|
|
AManaRedux(int id, MTGCardInstance * source, MTGCardInstance * target,int amount,int type):MTGAbility(id,source,target),amount(amount),type(type){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
}
|
|
int addToGame(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
amount;
|
|
type;
|
|
_target->getManaCost()->add(type,amount);
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
AManaRedux * clone() const{
|
|
AManaRedux * a = NEW AManaRedux(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
~AManaRedux(){}
|
|
};
|
|
//------------------------------------
|
|
class ATransformer:public MTGAbility{
|
|
public:
|
|
list<int>abilities;
|
|
list<int>types;
|
|
list<int>colors;
|
|
ATransformer(int id, MTGCardInstance * source, MTGCardInstance * target, string stypes, string sabilities):MTGAbility(id,source,target){
|
|
//TODO this is a copy/past of other code that's all around the place, everything should be in a dedicated parser class;
|
|
|
|
for (int j = 0; j < Constants::NB_BASIC_ABILITIES; j++){
|
|
size_t found = sabilities.find(Constants::MTGBasicAbilities[j]);
|
|
if (found != string::npos){
|
|
abilities.push_back(j);
|
|
}
|
|
}
|
|
for (int j = 0; j < Constants::MTG_NB_COLORS; j++){
|
|
size_t found = sabilities.find(Constants::MTGColorStrings[j]);
|
|
if (found != string::npos){
|
|
colors.push_back(j);
|
|
}
|
|
}
|
|
|
|
string s = stypes;
|
|
while (s.size()){
|
|
size_t found = s.find(" ");
|
|
if (found != string::npos){
|
|
int id = Subtypes::subtypesList->find(s.substr(0,found));
|
|
types.push_back(id);
|
|
s = s.substr(found+1);
|
|
}else{
|
|
int id = Subtypes::subtypesList->find(s);
|
|
types.push_back(id);
|
|
s = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
int addToGame(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
list<int>::iterator it;
|
|
for ( it=types.begin() ; it != types.end(); it++ ){
|
|
_target->addType(*it);
|
|
}
|
|
for ( it=colors.begin() ; it != colors.end(); it++ ){
|
|
_target->setColor(*it);
|
|
}
|
|
for ( it=abilities.begin() ; it != abilities.end(); it++ ){
|
|
_target->basicAbilities[*it]++;
|
|
}
|
|
return MTGAbility::addToGame();
|
|
}
|
|
int destroy(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
list<int>::iterator it;
|
|
for ( it=types.begin() ; it != types.end(); it++ ){
|
|
_target->removeType(*it);
|
|
}
|
|
for ( it=colors.begin() ; it != colors.end(); it++ ){
|
|
_target->removeColor(*it);
|
|
}
|
|
for ( it=abilities.begin() ; it != abilities.end(); it++ ){
|
|
_target->basicAbilities[*it]--;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
ATransformer * clone() const{
|
|
ATransformer * a = NEW ATransformer(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~ATransformer(){
|
|
}
|
|
|
|
};
|
|
//Adds types/abilities/P/T to a card (aura)
|
|
class ABecomes:public MTGAbility{
|
|
public:
|
|
list<int>abilities;
|
|
list<int>types;
|
|
list<int>colors;
|
|
WParsedPT * wppt;
|
|
ABecomes(int id, MTGCardInstance * source, MTGCardInstance * target, string stypes, WParsedPT * wppt, string sabilities):MTGAbility(id,source,target),wppt(wppt){
|
|
//TODO this is a copy/past of other code that's all around the place, everything should be in a dedicated parser class;
|
|
|
|
for (int j = 0; j < Constants::NB_BASIC_ABILITIES; j++){
|
|
size_t found = sabilities.find(Constants::MTGBasicAbilities[j]);
|
|
if (found != string::npos){
|
|
abilities.push_back(j);
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < Constants::MTG_NB_COLORS; j++){
|
|
size_t found = sabilities.find(Constants::MTGColorStrings[j]);
|
|
if (found != string::npos){
|
|
colors.push_back(j);
|
|
}
|
|
}
|
|
|
|
string s = stypes;
|
|
while (s.size()){
|
|
size_t found = s.find(" ");
|
|
if (found != string::npos){
|
|
int id = Subtypes::subtypesList->find(s.substr(0,found));
|
|
types.push_back(id);
|
|
s = s.substr(found+1);
|
|
}else{
|
|
int id = Subtypes::subtypesList->find(s);
|
|
types.push_back(id);
|
|
s = "";
|
|
}
|
|
}
|
|
}
|
|
int addToGame(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
list<int>::iterator it;
|
|
for ( it=types.begin() ; it != types.end(); it++ ){
|
|
_target->addType(*it);
|
|
}
|
|
for ( it=colors.begin() ; it != colors.end(); it++ ){
|
|
_target->setColor(*it);
|
|
}
|
|
for ( it=abilities.begin() ; it != abilities.end(); it++ ){
|
|
_target->basicAbilities[*it]++;
|
|
}
|
|
|
|
if (wppt){
|
|
_target->power = wppt->power.getValue();
|
|
_target->toughness = wppt->toughness.getValue();
|
|
_target->life = _target->toughness;
|
|
}
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
int destroy(){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
list<int>::iterator it;
|
|
for ( it=types.begin() ; it != types.end(); it++ ){
|
|
_target->removeType(*it);
|
|
}
|
|
for ( it=colors.begin() ; it != colors.end(); it++ ){
|
|
_target->removeColor(*it);
|
|
}
|
|
for ( it=abilities.begin() ; it != abilities.end(); it++ ){
|
|
_target->basicAbilities[*it]--;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
ABecomes * clone() const{
|
|
ABecomes * a = NEW ABecomes(*this);
|
|
a->wppt = NEW WParsedPT(*(a->wppt));
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~ABecomes(){
|
|
delete(wppt);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//Adds types/abilities/P/T to a card (until end of turn)
|
|
class ABecomesUEOT: public InstantAbility{
|
|
public:
|
|
ABecomes * ability;
|
|
ABecomesUEOT(int id, MTGCardInstance * source, MTGCardInstance * target, string types, WParsedPT * wpt, string abilities):InstantAbility(id,source,target){
|
|
ability = NEW ABecomes(id,source,target,types,wpt,abilities);
|
|
}
|
|
|
|
int resolve(){
|
|
ABecomes * a = ability->clone();
|
|
GenericInstantAbility * wrapper = NEW GenericInstantAbility(1,source,(Damageable *)(this->target),a);
|
|
wrapper->addToGame();
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return ability->getMenuText();
|
|
}
|
|
|
|
|
|
ABecomesUEOT * clone() const{
|
|
ABecomesUEOT * a = NEW ABecomesUEOT(*this);
|
|
a->ability = this->ability->clone();
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~ABecomesUEOT(){
|
|
delete ability;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
class APreventAllCombatDamage:public MTGAbility{
|
|
public:
|
|
string to, from;
|
|
REDamagePrevention * re;
|
|
|
|
APreventAllCombatDamage(int id,MTGCardInstance * source,string to,string from):MTGAbility(id,source),to(to),from(from){
|
|
re = NULL;
|
|
}
|
|
|
|
int addToGame(){
|
|
if (re) {
|
|
OutputDebugString("FATAL:re shouldn't be already set in APreventAllCombatDAMAGE\n");
|
|
return 0;
|
|
}
|
|
TargetChooserFactory tcf;
|
|
TargetChooser *toTc = tcf.createTargetChooser(to,source,this);
|
|
if (toTc) toTc->targetter = NULL;
|
|
TargetChooser *fromTc = tcf.createTargetChooser(from,source,this);
|
|
if (fromTc) fromTc->targetter = NULL;
|
|
re = NEW REDamagePrevention (this, fromTc, toTc, -1, false, DAMAGE_COMBAT);
|
|
game->replacementEffects->add(re);
|
|
return MTGAbility::addToGame();
|
|
}
|
|
|
|
int destroy(){
|
|
game->replacementEffects->remove(re);
|
|
SAFE_DELETE(re);
|
|
return 1;
|
|
}
|
|
|
|
APreventAllCombatDamage * clone() const{
|
|
APreventAllCombatDamage * a = NEW APreventAllCombatDamage(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
//Adds types/abilities/P/T to a card (until end of turn)
|
|
class APreventAllCombatDamageUEOT: public InstantAbility{
|
|
public:
|
|
APreventAllCombatDamage * ability;
|
|
vector<APreventAllCombatDamage *> clones;
|
|
APreventAllCombatDamageUEOT(int id,MTGCardInstance * source,string to, string from):InstantAbility(id,source){
|
|
ability = NEW APreventAllCombatDamage(id,source,to, from);
|
|
}
|
|
|
|
|
|
int resolve(){
|
|
APreventAllCombatDamage * a = ability->clone();
|
|
GenericInstantAbility * wrapper = NEW GenericInstantAbility(1,source,(Damageable *)(this->target),a);
|
|
wrapper->addToGame();
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int destroy(){
|
|
for (size_t i = 0; i < clones.size(); ++i){
|
|
clones[i]->forceDestroy = 0;
|
|
}
|
|
clones.clear();
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return ability->getMenuText();
|
|
}
|
|
|
|
|
|
APreventAllCombatDamageUEOT * clone() const{
|
|
APreventAllCombatDamageUEOT * a = NEW APreventAllCombatDamageUEOT(*this);
|
|
a->ability = this->ability->clone();
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
~APreventAllCombatDamageUEOT(){
|
|
delete ability;
|
|
}
|
|
|
|
};
|
|
|
|
//Upkeep Cost
|
|
class AUpkeep:public ActivatedAbility, public NestedAbility{
|
|
public:
|
|
int paidThisTurn;
|
|
int phase;
|
|
int once;
|
|
|
|
AUpkeep(int _id, MTGCardInstance * card, MTGAbility * a, ManaCost * _cost, int _tap = 0, int restrictions = 0, int _phase = Constants::MTG_PHASE_UPKEEP, int _once = 0):ActivatedAbility(_id, card,_cost,restrictions,_tap),NestedAbility(a),phase(_phase),once(_once){
|
|
paidThisTurn = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
// once: 0 means always go off, 1 means go off only once, 2 means go off only once and already has.
|
|
if (newPhase != currentPhase && source->controller() == game->currentPlayer && once < 2){
|
|
if (newPhase == Constants::MTG_PHASE_UNTAP){
|
|
paidThisTurn = 0;
|
|
}else if( newPhase == phase + 1 && !paidThisTurn){
|
|
ability->resolve();
|
|
}
|
|
if(newPhase == phase + 1 && once) once = 2;
|
|
}
|
|
ActivatedAbility::Update(dt);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (currentPhase != phase || paidThisTurn || once >= 2) return 0;
|
|
return ActivatedAbility::isReactingToClick(card,mana);
|
|
}
|
|
|
|
int resolve(){
|
|
paidThisTurn = 1;
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Upkeep";
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AUpkeep ::: paidThisTurn : " << paidThisTurn
|
|
<< " (";
|
|
return ActivatedAbility::toString(out) << ")";
|
|
}
|
|
|
|
~AUpkeep(){
|
|
if(!isClone) SAFE_DELETE(ability);
|
|
}
|
|
|
|
AUpkeep * clone() const{
|
|
AUpkeep * a = NEW AUpkeep(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
/*
|
|
Specific Classes
|
|
*/
|
|
|
|
// 1092 Specific to Aladdin's Lamp
|
|
class AAladdinsLamp: public TargetAbility{
|
|
public:
|
|
CardDisplay cd;
|
|
int nbcards;
|
|
int init;
|
|
|
|
AAladdinsLamp(int id, MTGCardInstance * card) : TargetAbility(id,card) {
|
|
cost = NEW ManaCost();
|
|
cost->x();
|
|
cd = CardDisplay(1, game, SCREEN_WIDTH/2, SCREEN_HEIGHT/2, NULL);
|
|
int zones[] = {MTGGameZone::MY_LIBRARY};
|
|
tc = NEW TargetZoneChooser(zones,1,source);
|
|
nbcards = 0;
|
|
init = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (waitingForAnswer){
|
|
if (!init){
|
|
cd.resetObjects();
|
|
int wished = game->currentlyActing()->getManaPool()->getConvertedCost();
|
|
game->currentlyActing()->getManaPool()->pay(cost);
|
|
nbcards = 0;
|
|
MTGGameZone * library = game->currentlyActing()->game->library;
|
|
while (nbcards < wished && nbcards < library->nb_cards){
|
|
cd.AddCard(library->cards[library->nb_cards - 1 - nbcards]);
|
|
nbcards++;
|
|
}
|
|
init = 1;
|
|
}
|
|
cd.Update(dt);
|
|
// cd.CheckUserInput(dt);
|
|
}
|
|
}
|
|
|
|
void Render(float dt){
|
|
if (waitingForAnswer){
|
|
cd.Render();
|
|
}
|
|
}
|
|
|
|
|
|
int fireAbility(){
|
|
source->tap();
|
|
MTGLibrary * library = game->currentlyActing()->game->library;
|
|
MTGCardInstance * card = library->removeCard(tc->getNextCardTarget());
|
|
library->shuffleTopToBottom(nbcards - 1 );
|
|
library->addCard(card);
|
|
init = 0;
|
|
return 1;
|
|
}
|
|
|
|
int resolve(){return 1;};
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AAladdinsLamp ::: cd : " << cd
|
|
<< " ; nbcards : " << nbcards
|
|
<< " ; init : " << init
|
|
<< " (";
|
|
return TargetAbility::toString(out) << ")";
|
|
}
|
|
AAladdinsLamp * clone() const{
|
|
AAladdinsLamp * a = NEW AAladdinsLamp(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
// Armageddon Clock
|
|
class AArmageddonClock:public MTGAbility{
|
|
public:
|
|
int counters;
|
|
ManaCost cost;
|
|
AArmageddonClock(int id, MTGCardInstance * _source):MTGAbility(id, _source){
|
|
counters = 0;
|
|
int _cost[] = {Constants::MTG_COLOR_ARTIFACT, 4};
|
|
cost = ManaCost(_cost,1);
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase){
|
|
if (newPhase == Constants::MTG_PHASE_UPKEEP && game->currentPlayer->game->inPlay->hasCard(source)){
|
|
counters ++;
|
|
}else if (newPhase == Constants::MTG_PHASE_DRAW && counters > 0 && game->currentPlayer->game->inPlay->hasCard(source)){ //End of upkeep = beginning of draw
|
|
GameObserver::GetInstance()->mLayers->stackLayer()->addDamage(source,GameObserver::GetInstance()->players[0], counters);
|
|
GameObserver::GetInstance()->mLayers->stackLayer()->addDamage(source,GameObserver::GetInstance()->players[1], counters);
|
|
}
|
|
}
|
|
}
|
|
int isReactingToClick(MTGCardInstance * _card, ManaCost * mana = NULL){
|
|
if (counters > 0 && _card == source && currentPhase == Constants::MTG_PHASE_UPKEEP){
|
|
if (game->currentlyActing()->getManaPool()->canAfford( & cost)){
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reactToClick(MTGCardInstance * _card){
|
|
if (!isReactingToClick( _card)) return 0;
|
|
game->currentlyActing()->getManaPool()->pay(& cost);
|
|
counters --;
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AArmageddonClock ::: counters : " << counters
|
|
<< " ; cost : " << cost
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AArmageddonClock * clone() const{
|
|
AArmageddonClock * a = NEW AArmageddonClock(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
// Clockwork Beast
|
|
class AClockworkBeast:public MTGAbility{
|
|
public:
|
|
int counters;
|
|
ManaCost cost;
|
|
AClockworkBeast(int id, MTGCardInstance * _source):MTGAbility(id, _source){
|
|
counters = 7;
|
|
((MTGCardInstance *)target)->power+=7;
|
|
int _cost[] = {Constants::MTG_COLOR_ARTIFACT, 1};
|
|
cost = ManaCost(_cost,1);
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_COMBATEND){
|
|
if (((MTGCardInstance *)source)->isAttacker() || ((MTGCardInstance *)source)->isDefenser()){
|
|
counters--;
|
|
((MTGCardInstance *)target)->power-=1;
|
|
}
|
|
}
|
|
}
|
|
int isReactingToClick(MTGCardInstance * _card, ManaCost * mana = NULL){
|
|
if (counters < 7 && _card == source && currentPhase == Constants::MTG_PHASE_UPKEEP && game->currentPlayer->game->inPlay->hasCard(source)){
|
|
if (game->currentlyActing()->getManaPool()->canAfford( & cost)){
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reactToClick(MTGCardInstance * _card){
|
|
if (!isReactingToClick( _card)) return 0;
|
|
game->currentlyActing()->getManaPool()->pay(& cost);
|
|
counters ++;
|
|
((MTGCardInstance *)target)->power++;
|
|
((MTGCardInstance *)target)->tap();
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AClockworkBeast ::: counters : " << counters
|
|
<< " ; cost : " << cost
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AClockworkBeast * clone() const{
|
|
AClockworkBeast * a = NEW AClockworkBeast(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1102: Conservator
|
|
class AConservator: public MTGAbility{
|
|
public:
|
|
int canprevent;
|
|
ManaCost cost;
|
|
AConservator(int _id, MTGCardInstance * _source):MTGAbility(_id, _source){
|
|
canprevent = 0;
|
|
int _cost[] = {Constants::MTG_COLOR_ARTIFACT, 2};
|
|
cost = ManaCost(_cost, 1);
|
|
}
|
|
|
|
int alterDamage(Damage * damage){
|
|
if (canprevent && damage->target == source->controller()){
|
|
if (damage->damage >= canprevent){
|
|
damage->damage-=canprevent;
|
|
canprevent = 0;
|
|
}else{
|
|
canprevent-=damage->damage;
|
|
damage->damage = 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
int alterDamage(){
|
|
if (canprevent){
|
|
ActionStack * stack = game->mLayers->stackLayer();
|
|
for (int i = stack->mCount-1; i>=0; i--){
|
|
if (!canprevent) return 1;
|
|
Interruptible * current = ((Interruptible *)stack->mObjects[i]);
|
|
if (current->type == ACTION_DAMAGE && current->state==NOT_RESOLVED){
|
|
Damage * damage = (Damage *)current;
|
|
alterDamage(damage);
|
|
}else if (current->type == ACTION_DAMAGES && current->state == NOT_RESOLVED){
|
|
DamageStack * damages = (DamageStack *)current;
|
|
for (int j = damages->mCount-1;j >=0; j--){
|
|
alterDamage(((Damage *)damages->mObjects[j]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void Update(float dt){
|
|
alterDamage();
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * _card, ManaCost * mana = NULL){
|
|
if ( _card == source && game->currentlyActing()->game->inPlay->hasCard(source) && !_card->isTapped()){
|
|
if (game->currentlyActing()->getManaPool()->canAfford( & cost)){
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reactToClick(MTGCardInstance * _card){
|
|
if (!isReactingToClick( _card)) return 0;
|
|
game->currentlyActing()->getManaPool()->pay(& cost);
|
|
source->tap();
|
|
canprevent = 2;
|
|
alterDamage();
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AConservator ::: canprevent : " << canprevent
|
|
<< " ; cost : " << cost
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AConservator * clone() const{
|
|
AConservator * a = NEW AConservator(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//1345 Farmstead
|
|
class AFarmstead:public ActivatedAbility{
|
|
public:
|
|
int usedThisTurn;
|
|
AFarmstead(int _id, MTGCardInstance * source, MTGCardInstance * _target):ActivatedAbility(_id, source,0,1,0){
|
|
int _cost[] = {Constants::MTG_COLOR_WHITE, 2};
|
|
cost = NEW ManaCost(_cost,1);
|
|
target = _target;
|
|
usedThisTurn = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase != Constants::MTG_PHASE_UPKEEP){
|
|
usedThisTurn = 0;
|
|
}
|
|
ActivatedAbility::Update(dt);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (!ActivatedAbility::isReactingToClick(card,mana)) return 0;
|
|
if (currentPhase != Constants::MTG_PHASE_UPKEEP) return 0;
|
|
if (usedThisTurn) return 0;
|
|
return 1;
|
|
}
|
|
|
|
int resolve(){
|
|
source->controller()->life++;
|
|
usedThisTurn = 1;
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AFarmstead ::: usedThisTurn : " << usedThisTurn
|
|
<< " (";
|
|
return ActivatedAbility::toString(out) << ")";
|
|
}
|
|
AFarmstead * clone() const{
|
|
AFarmstead * a = NEW AFarmstead(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1112 Howling Mine
|
|
class AHowlingMine:public MTGAbility{
|
|
public:
|
|
AHowlingMine(int _id, MTGCardInstance * _source):MTGAbility(_id, _source){}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_DRAW && !source->isTapped()){
|
|
game->mLayers->stackLayer()->addDraw(game->currentPlayer);
|
|
}
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AHowlingMine ::: (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AHowlingMine * clone() const{
|
|
AHowlingMine * a = NEW AHowlingMine(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Kjeldoran Frostbeast
|
|
class AKjeldoranFrostbeast:public MTGAbility{
|
|
public:
|
|
MTGCardInstance * opponents[20];
|
|
int nbOpponents;
|
|
AKjeldoranFrostbeast(int _id, MTGCardInstance * _source):MTGAbility(_id, _source){
|
|
nbOpponents = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase){
|
|
if( newPhase == Constants::MTG_PHASE_COMBATEND){
|
|
nbOpponents = 0;
|
|
MTGCardInstance * opponent = source->getNextOpponent();
|
|
while (opponent && !opponent->hasSubtype("wall")){
|
|
opponents[nbOpponents] = opponent;
|
|
nbOpponents ++;
|
|
opponent = source->getNextOpponent(opponent);
|
|
}
|
|
if (source->isInPlay()) {
|
|
for (int i = 0; i < nbOpponents ; i++){
|
|
opponents[i]->destroy();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int testDestroy(){
|
|
if(!game->isInPlay(source) && currentPhase != Constants::MTG_PHASE_UNTAP){
|
|
return 0;
|
|
}else{
|
|
return MTGAbility::testDestroy();
|
|
}
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AKjeldoranFrostbeast ::: opponents : " << opponents
|
|
<< " ; nbOpponents : " << nbOpponents
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AKjeldoranFrostbeast * clone() const{
|
|
AKjeldoranFrostbeast * a = NEW AKjeldoranFrostbeast(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Living Artifact
|
|
class ALivingArtifact:public MTGAbility{
|
|
public:
|
|
int usedThisTurn;
|
|
int counters;
|
|
Damage * latest;
|
|
ALivingArtifact(int _id, MTGCardInstance * _source, MTGCardInstance * _target):MTGAbility(_id,_source,_target){
|
|
usedThisTurn = 0;
|
|
counters = 0;
|
|
latest = NULL;
|
|
}
|
|
|
|
int receiveEvent(WEvent * event){
|
|
WEventDamage * e = dynamic_cast<WEventDamage *>(event);
|
|
if (!e) return 0;
|
|
Player * p = dynamic_cast<Player *>(e->damage->target);
|
|
if (!p) return 0;
|
|
if (p != source->controller()) return 0;
|
|
counters+=e->damage->damage;
|
|
return 1; //is this meant to return 0 or 1?
|
|
}
|
|
|
|
int isReactingtoclick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (currentPhase == Constants::MTG_PHASE_UPKEEP && card == source && game->currentPlayer == source->controller() && counters && !usedThisTurn){
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reactToClick(MTGCardInstance * card){
|
|
source->controller()->life+=1;
|
|
counters--;
|
|
usedThisTurn = 1;
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ALivingArtifact ::: usedThisTurn : " << usedThisTurn
|
|
<< " ; counters : " << counters
|
|
<< " ; latest : " << latest
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
ALivingArtifact * clone() const{
|
|
ALivingArtifact * a = NEW ALivingArtifact(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1143 Animate Dead
|
|
class AAnimateDead:public MTGAbility{
|
|
public:
|
|
AAnimateDead(int _id, MTGCardInstance * _source, MTGCardInstance * _target):MTGAbility(_id, _source, _target){
|
|
MTGCardInstance * card = _target;
|
|
|
|
//Put the card in play again, with all its abilities !
|
|
//AbilityFactory af;
|
|
MTGCardInstance * copy = source->controller()->game->putInZone(card, _target->controller()->game->graveyard, source->controller()->game->temp);
|
|
Spell * spell = NEW Spell(copy);
|
|
spell->resolve();
|
|
target = spell->source;
|
|
card = spell->source;
|
|
card->power--;
|
|
card->life = card->toughness;
|
|
delete spell;
|
|
}
|
|
|
|
int destroy(){
|
|
MTGCardInstance * card = (MTGCardInstance *) target;
|
|
card->power++;
|
|
card->controller()->game->putInZone(card, card->controller()->game->inPlay,card->owner->game->graveyard);
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AAnimateDead ::: (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AAnimateDead * clone() const{
|
|
AAnimateDead * a = NEW AAnimateDead(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//1159 Erg Raiders
|
|
class AErgRaiders:public MTGAbility{
|
|
public:
|
|
int attackedThisTurn;
|
|
AErgRaiders(int _id, MTGCardInstance * _source):MTGAbility(_id, _source){
|
|
attackedThisTurn = 1;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase){
|
|
Player * controller = source->controller();
|
|
if (newPhase == Constants::MTG_PHASE_COMBATDAMAGE && game->currentPlayer == controller){
|
|
if (source->isAttacker()){
|
|
attackedThisTurn = 1;
|
|
}
|
|
}else if (newPhase == Constants::MTG_PHASE_UNTAP){
|
|
if (game->currentPlayer != controller && !attackedThisTurn){
|
|
game->mLayers->stackLayer()->addDamage(source, controller,2);
|
|
}else if (game->currentPlayer == controller) {
|
|
attackedThisTurn = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AErgRaiders * clone() const{
|
|
AErgRaiders * a = NEW AErgRaiders(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Fastbond
|
|
class AFastbond:public TriggeredAbility{
|
|
public:
|
|
int alreadyPlayedALand;
|
|
int previous;
|
|
AFastbond(int _id, MTGCardInstance * card):TriggeredAbility(_id, card){
|
|
alreadyPlayedALand = 0;
|
|
if (source->controller()->canPutLandsIntoPlay == 0){
|
|
alreadyPlayedALand = 1;
|
|
source->controller()->canPutLandsIntoPlay = 1;
|
|
}
|
|
previous = source->controller()->canPutLandsIntoPlay;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase!=currentPhase && newPhase == Constants::MTG_PHASE_UNTAP){
|
|
alreadyPlayedALand = 0;
|
|
}
|
|
TriggeredAbility::Update(dt);
|
|
}
|
|
|
|
int trigger(){
|
|
if(source->controller()->canPutLandsIntoPlay==0 && previous == 1){
|
|
previous = 0;
|
|
source->controller()->canPutLandsIntoPlay = 1;
|
|
if (alreadyPlayedALand) return 1;
|
|
alreadyPlayedALand = 1;
|
|
return 0;
|
|
}
|
|
previous = source->controller()->canPutLandsIntoPlay;
|
|
return 0;
|
|
}
|
|
|
|
int resolve(){
|
|
game->mLayers->stackLayer()->addDamage(source, source->controller(), 1);
|
|
game->mLayers->stackLayer()->resolve();
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AFastbond ::: alreadyPlayedALand : " << alreadyPlayedALand
|
|
<< " ; previous : " << previous
|
|
<< " (";
|
|
return TriggeredAbility::toString(out) << ")";
|
|
}
|
|
AFastbond * clone() const{
|
|
AFastbond * a = NEW AFastbond(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
//1165 Hypnotic Specter
|
|
class AHypnoticSpecter:public MTGAbility{
|
|
public:
|
|
|
|
AHypnoticSpecter(int _id, MTGCardInstance * _source):MTGAbility(_id, _source){
|
|
}
|
|
|
|
int receiveEvent(WEvent * event){
|
|
WEventDamage * e = dynamic_cast<WEventDamage *>(event);
|
|
if (!e) return 0;
|
|
if (e->damage->source != source) return 0;
|
|
Player * p = dynamic_cast<Player *>(e->damage->target);
|
|
if (!p) return 0;
|
|
p->game->discardRandom(p->game->hand);
|
|
return 1; //is this meant to return 0 or 1?
|
|
}
|
|
|
|
AHypnoticSpecter * clone() const{
|
|
AHypnoticSpecter * a = NEW AHypnoticSpecter(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1117 Jandor's Ring
|
|
class AJandorsRing:public ActivatedAbility{
|
|
public:
|
|
AJandorsRing(int _id, MTGCardInstance * _source):ActivatedAbility(_id,_source, NEW ManaCost()){
|
|
cost->add(Constants::MTG_COLOR_ARTIFACT, 2);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (!source->controller()->game->hand->hasCard(source->controller()->game->library->lastCardDrawn)) return 0;
|
|
return ActivatedAbility::isReactingToClick(card,mana);
|
|
}
|
|
|
|
int resolve(){
|
|
source->controller()->game->putInGraveyard(source->controller()->game->library->lastCardDrawn);
|
|
game->mLayers->stackLayer()->addDraw(source->controller());
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AJandorsRing ::: (";
|
|
return ActivatedAbility::toString(out) << ")";
|
|
}
|
|
AJandorsRing * clone() const{
|
|
AJandorsRing * a = NEW AJandorsRing(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Kudzu.
|
|
//What happens when there are no targets ???
|
|
class AKudzu: public TargetAbility{
|
|
public:
|
|
AKudzu(int _id, MTGCardInstance * card, MTGCardInstance * _target):TargetAbility(_id,card, NEW TypeTargetChooser("land",card)){
|
|
tc->toggleTarget(_target);
|
|
target = _target;
|
|
}
|
|
|
|
int receiveEvent(WEvent * event){
|
|
if (WEventCardTap* wect = dynamic_cast<WEventCardTap*>(event)) {
|
|
if (wect->before == false && wect->after == true && wect->card == target){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
if (!_target->isInPlay()) return 0;
|
|
target = _target->controller()->game->putInGraveyard(_target);
|
|
reactToClick(source);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
MTGCardInstance * _target = (MTGCardInstance *)target;
|
|
if (card == source && (!_target || !_target->isInPlay())){
|
|
#if defined (WIN32) || defined (LINUX)
|
|
OutputDebugString("Kudzu Reacts to click !\n");
|
|
#endif
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int resolve(){
|
|
target = tc->getNextCardTarget();
|
|
source->target = (MTGCardInstance *) target;
|
|
return 1;
|
|
}
|
|
|
|
int testDestroy(){
|
|
int stillLandsInPlay = 0;
|
|
for (int i = 0; i < 2; i++){
|
|
if (game->players[i]->game->inPlay->hasType("Land")) stillLandsInPlay = 1;
|
|
}
|
|
if (!stillLandsInPlay){
|
|
source->controller()->game->putInGraveyard(source);
|
|
return 1;
|
|
}
|
|
|
|
if (!game->isInPlay(source)){
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
AKudzu * clone() const{
|
|
AKudzu * a = NEW AKudzu(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1172 Pestilence
|
|
class APestilence: public ActivatedAbility{
|
|
public:
|
|
APestilence(int _id, MTGCardInstance * card):ActivatedAbility(_id, card, NEW ManaCost(), 0,0){
|
|
cost->add(Constants::MTG_COLOR_BLACK, 1);
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase !=currentPhase && newPhase == Constants::MTG_PHASE_EOT){
|
|
if (!game->players[0]->game->inPlay->hasType("creature") && !game->players[1]->game->inPlay->hasType("creature")){
|
|
source->controller()->game->putInGraveyard(source);
|
|
}
|
|
}
|
|
}
|
|
|
|
int resolve(){
|
|
for (int i = 0; i < 2 ; i++){
|
|
MTGInPlay * inplay = game->players[i]->game->inPlay;
|
|
for (int j = inplay->nb_cards - 1 ; j >=0; j--){
|
|
if (inplay->cards[j]->isCreature()) game->mLayers->stackLayer()->addDamage(source,inplay->cards[j],1);
|
|
}
|
|
game->mLayers->stackLayer()->addDamage(source,game->players[i],1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "APestilence ::: (";
|
|
return ActivatedAbility::toString(out) << ")";
|
|
}
|
|
APestilence * clone() const{
|
|
APestilence * a = NEW APestilence(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Power Leak
|
|
class APowerLeak:public TriggeredAbility{
|
|
public:
|
|
int damagesToDealThisTurn;
|
|
ManaCost cost;
|
|
APowerLeak(int _id, MTGCardInstance * _source, MTGCardInstance * _target):TriggeredAbility(_id, _source, _target){
|
|
cost.add(Constants::MTG_COLOR_ARTIFACT, 1);
|
|
damagesToDealThisTurn = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_UPKEEP && _target->controller() == game->currentPlayer){
|
|
damagesToDealThisTurn = 2;
|
|
}
|
|
TriggeredAbility::Update(dt);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if (damagesToDealThisTurn && currentPhase == Constants::MTG_PHASE_UPKEEP && card==source && _target->controller() == game->currentPlayer){
|
|
if (game->currentPlayer->getManaPool()->canAfford(& cost)) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reactToclick(MTGCardInstance * card){
|
|
game->currentPlayer->getManaPool()->pay( & cost);
|
|
damagesToDealThisTurn--;
|
|
return 1;
|
|
}
|
|
|
|
int trigger(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_DRAW && _target->controller() == game->currentPlayer){
|
|
if (damagesToDealThisTurn) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
game->mLayers->stackLayer()->addDamage(source,_target->controller(), damagesToDealThisTurn);
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "APowerLeak ::: damagesToDealThisTurn : " << damagesToDealThisTurn
|
|
<< " ; cost : " << cost
|
|
<< " (";
|
|
return TriggeredAbility::toString(out) << ")";
|
|
}
|
|
APowerLeak * clone() const{
|
|
APowerLeak * a = NEW APowerLeak(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1176 Sacrifice
|
|
class ASacrifice:public InstantAbility{
|
|
public:
|
|
ASacrifice(int _id, MTGCardInstance * _source, MTGCardInstance * _target):InstantAbility(_id, _source){
|
|
target = _target;
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if (_target->isInPlay()){
|
|
game->currentlyActing()->game->putInGraveyard(_target);
|
|
int x = _target->getManaCost()->getConvertedCost();
|
|
game->currentlyActing()->getManaPool()->add(Constants::MTG_COLOR_BLACK, x);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ASacrifice ::: (";
|
|
return InstantAbility::toString(out) << ")";
|
|
}
|
|
ASacrifice * clone() const{
|
|
ASacrifice * a = NEW ASacrifice(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1178 Scavenging Ghoul
|
|
class AScavengingGhoul:public MTGAbility{
|
|
public:
|
|
int counters;
|
|
AScavengingGhoul(int _id, MTGCardInstance * _source, MTGCardInstance * _target):MTGAbility(_id, _source, _target){
|
|
counters = 0;
|
|
}
|
|
|
|
|
|
void Update(float dt){
|
|
//TODO
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * _card, ManaCost * mana = NULL){
|
|
if (counters > 0 && _card == source && game->currentlyActing()->game->inPlay->hasCard(source)){
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reactToClick(MTGCardInstance * _card){
|
|
if (!isReactingToClick( _card)) return 0;
|
|
counters--;
|
|
source->regenerate();
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AScavengingGhoul ::: counters : " << counters
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AScavengingGhoul * clone() const{
|
|
AScavengingGhoul * a = NEW AScavengingGhoul(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//1235 Aspect of Wolf
|
|
class AAspectOfWolf:public ListMaintainerAbility{
|
|
public:
|
|
int color;
|
|
AAspectOfWolf(int _id, MTGCardInstance * _source, MTGCardInstance * _target):ListMaintainerAbility(_id, _source, _target){
|
|
}
|
|
|
|
int canBeInList(MTGCardInstance * card){
|
|
|
|
if (card->controller() == source->controller() && card->hasType("forest") && game->isInPlay(card)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int added(MTGCardInstance * card){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
int size = cards.size();
|
|
if (size % 2 == 0){
|
|
_target->power += 1;
|
|
}else{
|
|
_target->addToToughness(1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int removed(MTGCardInstance * card){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
int size = cards.size();
|
|
if (size % 2 == 1){
|
|
_target->power -= 1;
|
|
}else{
|
|
_target->addToToughness(-1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AAspectOfWolf ::: color : " << color
|
|
<< " (";
|
|
return ListMaintainerAbility::toString(out) << ")";
|
|
}
|
|
AAspectOfWolf * clone() const{
|
|
AAspectOfWolf * a = NEW AAspectOfWolf(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1284 Dragon Whelp
|
|
class ADragonWhelp: public APowerToughnessModifierUntilEndOfTurn{
|
|
public:
|
|
ADragonWhelp(int id, MTGCardInstance * card):APowerToughnessModifierUntilEndOfTurn(id, card, card, NEW WParsedPT(1, 0), NEW ManaCost()){
|
|
cost->add(Constants::MTG_COLOR_RED, 1);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (!ActivatedAbility::isReactingToClick(card,mana)) return 0;
|
|
return (!maxcounters || (counters < maxcounters));
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_AFTER_EOT && counters > 3){
|
|
source->controller()->game->putInGraveyard(source);
|
|
}
|
|
APowerToughnessModifierUntilEndOfTurn::Update(dt);
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ADragonWhelp ::: (";
|
|
return APowerToughnessModifierUntilEndOfTurn::toString(out) << ")";
|
|
}
|
|
ADragonWhelp * clone() const{
|
|
ADragonWhelp * a = NEW ADragonWhelp(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1288 EarthBind
|
|
class AEarthbind:public ABasicAbilityModifier{
|
|
public:
|
|
AEarthbind(int _id, MTGCardInstance * _source, MTGCardInstance * _target):ABasicAbilityModifier(_id,_source,_target,Constants::FLYING,0){
|
|
if (value_before_modification){
|
|
Damageable * _target = (Damageable *)target;
|
|
game->mLayers->stackLayer()->addDamage(source,_target,2);
|
|
}
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AEarthbind ::: (";
|
|
return ABasicAbilityModifier::toString(out) << ")";
|
|
}
|
|
AEarthbind * clone() const{
|
|
AEarthbind * a = NEW AEarthbind(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//1291 Fireball
|
|
class AFireball:public InstantAbility{
|
|
public:
|
|
AFireball(int _id, MTGCardInstance * card, Spell * spell, int x):InstantAbility(_id, card){
|
|
int nbtargets = spell->getNbTargets();
|
|
int totaldamage = x+1-nbtargets;
|
|
int individualdamage = 0;
|
|
if (nbtargets) individualdamage = totaldamage / nbtargets;
|
|
Damageable * _target = spell->getNextDamageableTarget();
|
|
while(_target){
|
|
game->mLayers->stackLayer()->addDamage(source,_target,individualdamage);
|
|
_target = spell->getNextDamageableTarget(_target);
|
|
}
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AFireball ::: (";
|
|
return InstantAbility::toString(out) << ")";
|
|
}
|
|
AFireball * clone() const{
|
|
AFireball * a = NEW AFireball(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//1351 Island Sanctuary
|
|
class AIslandSanctuary:public MTGAbility{
|
|
public:
|
|
int initThisTurn;
|
|
AIslandSanctuary(int _id, MTGCardInstance * _source):MTGAbility(_id, _source){
|
|
initThisTurn = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (currentPhase == Constants::MTG_PHASE_UNTAP && game->currentPlayer == source->controller()) initThisTurn = 0;
|
|
|
|
if (initThisTurn && currentPhase == Constants::MTG_PHASE_COMBATATTACKERS && game->currentPlayer != source->controller()){
|
|
MTGGameZone * zone = game->currentPlayer->game->inPlay;
|
|
for (int i = 0; i < zone->nb_cards; i++){
|
|
MTGCardInstance * card = zone->cards[i];
|
|
if (card->isAttacker() && !card->basicAbilities[Constants::FLYING] && !card->basicAbilities[Constants::ISLANDWALK]) source->toggleAttacker();
|
|
}
|
|
}
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (card==source && game->currentPlayer == card->controller() && currentPhase == Constants::MTG_PHASE_DRAW){
|
|
Interruptible * action = game->mLayers->stackLayer()->getAt(-1);
|
|
if (action->type == ACTION_DRAW) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int reactToClick(MTGCardInstance * card){
|
|
if (!isReactingToClick(card)) return 0;
|
|
game->mLayers->stackLayer()->Remove(game->mLayers->stackLayer()->getAt(-1));
|
|
initThisTurn = 1;
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AIslandSanctuary ::: initThisTurn : " << initThisTurn
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
AIslandSanctuary * clone() const{
|
|
AIslandSanctuary * a = NEW AIslandSanctuary(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Stasis
|
|
class AStasis:public ActivatedAbility{
|
|
public:
|
|
int paidThisTurn;
|
|
AStasis(int _id, MTGCardInstance * card):ActivatedAbility(_id,card, NEW ManaCost(),1,0){
|
|
paidThisTurn = 1;
|
|
cost->add(Constants::MTG_COLOR_BLUE,1);
|
|
}
|
|
|
|
void Update(float dt){
|
|
//Upkeep Cost
|
|
if (newPhase !=currentPhase){
|
|
if (newPhase == Constants::MTG_PHASE_UPKEEP){
|
|
paidThisTurn = 0;
|
|
}else if (!paidThisTurn && newPhase > Constants::MTG_PHASE_UPKEEP && game->currentPlayer==source->controller() ){
|
|
game->currentPlayer->game->putInGraveyard(source);
|
|
paidThisTurn = 1;
|
|
}
|
|
}
|
|
//Stasis Effect
|
|
for (int i = 0; i < 2; i++){
|
|
game->phaseRing->removePhase(Constants::MTG_PHASE_UNTAP,game->players[i]);
|
|
}
|
|
|
|
//Parent Class Method Call
|
|
ActivatedAbility::Update(dt);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
return (!paidThisTurn && currentPhase == Constants::MTG_PHASE_UPKEEP && ActivatedAbility::isReactingToClick(card,mana));
|
|
}
|
|
|
|
int resolve(){
|
|
paidThisTurn = 1;
|
|
return 1;
|
|
}
|
|
|
|
int destroy(){
|
|
for (int i = 0; i < 2; i++){
|
|
game->phaseRing->addPhaseBefore(Constants::MTG_PHASE_UNTAP,game->players[i],Constants::MTG_PHASE_UPKEEP,game->players[i]);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AStasis ::: paidThisTurn : " << paidThisTurn
|
|
<< " (";
|
|
return ActivatedAbility::toString(out) << ")";
|
|
}
|
|
AStasis * clone() const{
|
|
AStasis * a = NEW AStasis(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
//--------------Addon Abra------------------
|
|
|
|
//Basilik --> needs to be made more generic to avoid duplicate (also something like if opponent=type then ...)
|
|
class ABasilik:public MTGAbility{
|
|
public:
|
|
MTGCardInstance * opponents[20];
|
|
int nbOpponents;
|
|
ABasilik (int _id, MTGCardInstance * _source):MTGAbility(_id, _source){
|
|
nbOpponents = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase){
|
|
if( newPhase == Constants::MTG_PHASE_COMBATDAMAGE){
|
|
nbOpponents = 0;
|
|
MTGCardInstance * opponent = source->getNextOpponent();
|
|
while (opponent){
|
|
opponents[nbOpponents] = opponent;
|
|
nbOpponents ++;
|
|
opponent = source->getNextOpponent(opponent);
|
|
}
|
|
}else if (newPhase == Constants::MTG_PHASE_COMBATEND){
|
|
for (int i = 0; i < nbOpponents ; i++){
|
|
game->mLayers->stackLayer()->addPutInGraveyard(opponents[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ABasilik ::: opponents : " << opponents
|
|
<< " ; nbOpponents : " << nbOpponents
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
ABasilik * clone() const{
|
|
ABasilik * a = NEW ABasilik(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Lavaborn - quick and very dirty ;) copy of ALifezonelink but without the multiplier.
|
|
class ALavaborn:public MTGAbility{
|
|
public:
|
|
int phase;
|
|
int condition;
|
|
int life;
|
|
int controller;
|
|
int nbcards;
|
|
MTGGameZone * zone;
|
|
ALavaborn(int _id ,MTGCardInstance * card, int _phase, int _condition, int _life, int _controller = 0, MTGGameZone * _zone = NULL):MTGAbility(_id, card){
|
|
phase = _phase;
|
|
condition = _condition;
|
|
controller = _controller;
|
|
life = _life;
|
|
zone = _zone;
|
|
if (zone == NULL){
|
|
if (controller){
|
|
zone = game->currentPlayer->game->hand;
|
|
}else{
|
|
zone = game->opponent()->game->hand;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && newPhase == phase){
|
|
if ((controller && game->currentPlayer == source->controller()) ||(!controller && game->currentPlayer != source->controller()) ){
|
|
if ((condition < 0 && zone->nb_cards < - condition) ||(condition >0 && zone->nb_cards > condition)){
|
|
int diff = zone->nb_cards - condition;
|
|
if (condition < 0) diff = - condition - zone->nb_cards;
|
|
if (life > 0){
|
|
game->currentPlayer->life+=life;
|
|
}else{
|
|
game->mLayers->stackLayer()->addDamage(source,game->currentPlayer,-life);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "ALavaborn ::: phase : " << phase
|
|
<< " ; condition : " << condition
|
|
<< " ; life : " << life
|
|
<< " ; controller : " << controller
|
|
<< " ; nbcards : " << nbcards
|
|
<< " (";
|
|
return MTGAbility::toString(out) << ")";
|
|
}
|
|
|
|
ALavaborn * clone() const{
|
|
ALavaborn * a = NEW ALavaborn(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//Generic Millstone
|
|
class AADepleter:public ActivatedAbilityTP{
|
|
public:
|
|
int nbcards;
|
|
AADepleter(int _id, MTGCardInstance * card, Targetable * _target, int nbcards = 1, ManaCost * _cost=NULL, int _tap = 0, int who = TargetChooser::UNSET):ActivatedAbilityTP(_id,card, _target,_cost,_tap,who),nbcards(nbcards){
|
|
}
|
|
int resolve(){
|
|
Targetable * _target = getTarget();
|
|
Player * player;
|
|
if (_target){
|
|
if (_target->typeAsTarget() == TARGET_CARD){
|
|
player = ((MTGCardInstance *)_target)->controller();
|
|
}else{
|
|
player = (Player *) _target;
|
|
}
|
|
MTGLibrary * library = player->game->library;
|
|
for (int i = 0; i < nbcards; i++){
|
|
if (library->nb_cards)
|
|
player->game->putInZone(library->cards[library->nb_cards-1],library, player->game->graveyard);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Deplete";
|
|
}
|
|
|
|
AADepleter * clone() const{
|
|
AADepleter * a = NEW AADepleter(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Shuffle
|
|
class AAShuffle:public ActivatedAbilityTP{
|
|
public:
|
|
AAShuffle(int _id, MTGCardInstance * card, Targetable * _target, ManaCost * _cost=NULL, int _tap = 0, int who = TargetChooser::UNSET):ActivatedAbilityTP(_id,card, _target,_cost,_tap,who){
|
|
}
|
|
int resolve(){
|
|
Targetable * _target = getTarget();
|
|
Player * player;
|
|
if (_target){
|
|
if (_target->typeAsTarget() == TARGET_CARD){
|
|
player = ((MTGCardInstance *)_target)->controller();
|
|
}else{
|
|
player = (Player *) _target;
|
|
}
|
|
MTGLibrary * library = player->game->library;
|
|
library->shuffle();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Shuffle";
|
|
}
|
|
|
|
AAShuffle * clone() const{
|
|
AAShuffle * a = NEW AAShuffle(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Random Discard
|
|
class AARandomDiscarder:public ActivatedAbilityTP{
|
|
public:
|
|
int nbcards;
|
|
AARandomDiscarder(int _id, MTGCardInstance * card, Targetable * _target, int nbcards = 1, ManaCost * _cost=NULL, int _tap = 0,int who=TargetChooser::UNSET):ActivatedAbilityTP(_id,card, _target,_cost,_tap,who),nbcards(nbcards){
|
|
}
|
|
int resolve(){
|
|
Targetable * _target = getTarget();
|
|
Player * player;
|
|
if (_target){
|
|
if (_target->typeAsTarget() == TARGET_CARD){
|
|
player = ((MTGCardInstance *)_target)->controller();
|
|
}else{
|
|
player = (Player *) _target;
|
|
}
|
|
for (int i = 0; i < nbcards; i++){
|
|
player->game->discardRandom(player->game->hand);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
const char * getMenuText(){
|
|
return "Discard Random";
|
|
}
|
|
|
|
AARandomDiscarder * clone() const{
|
|
AARandomDiscarder * a = NEW AARandomDiscarder(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Minion of Leshrac
|
|
class AMinionofLeshrac: public TargetAbility{
|
|
public:
|
|
int paidThisTurn;
|
|
AMinionofLeshrac(int _id, MTGCardInstance * source):TargetAbility(_id, source, NEW CreatureTargetChooser(),0,1,0){
|
|
paidThisTurn = 1;
|
|
}
|
|
|
|
void Update(float dt){
|
|
if (newPhase != currentPhase && source->controller() == game->currentPlayer){
|
|
if (newPhase == Constants::MTG_PHASE_UNTAP){
|
|
paidThisTurn = 0;
|
|
}else if( newPhase == Constants::MTG_PHASE_UPKEEP + 1 && !paidThisTurn){
|
|
game->mLayers->stackLayer()->addDamage(source,source->controller(), 5);
|
|
source->tap();
|
|
}
|
|
}
|
|
TargetAbility::Update(dt);
|
|
}
|
|
|
|
int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL){
|
|
if (currentPhase != Constants::MTG_PHASE_UPKEEP || paidThisTurn) return 0;
|
|
return TargetAbility::isReactingToClick(card,mana);
|
|
}
|
|
|
|
int resolve(){
|
|
MTGCardInstance * card = tc->getNextCardTarget();
|
|
if (card && card != source && card->controller() == source->controller()){
|
|
card->controller()->game->putInGraveyard(card);
|
|
paidThisTurn = 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AMinionofLeshrac ::: paidThisTurn : " << paidThisTurn
|
|
<< " (";
|
|
return TargetAbility::toString(out) << ")";
|
|
}
|
|
|
|
AMinionofLeshrac * clone() const{
|
|
AMinionofLeshrac * a = NEW AMinionofLeshrac(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
//Rampage ability
|
|
class ARampageAbility:public MTGAbility{
|
|
public:
|
|
int nbOpponents;
|
|
int PowerModifier;
|
|
int ToughnessModifier;
|
|
int MaxOpponent;
|
|
|
|
ARampageAbility(int _id, MTGCardInstance * _source,int _PowerModifier, int _ToughnessModifier, int _MaxOpponent):MTGAbility(_id, _source){
|
|
PowerModifier = _PowerModifier;
|
|
ToughnessModifier = _ToughnessModifier;
|
|
MaxOpponent = _MaxOpponent;
|
|
nbOpponents = 0;
|
|
}
|
|
int receiveEvent(WEvent * event) {
|
|
if (dynamic_cast<WEventBlockersChosen*>(event)) {
|
|
nbOpponents = source->blockers.size();
|
|
if (nbOpponents <= MaxOpponent) return 0;
|
|
source->power += PowerModifier * (nbOpponents - MaxOpponent);
|
|
source->addToToughness(ToughnessModifier * (nbOpponents - MaxOpponent));
|
|
}
|
|
else if (WEventPhaseChange* pe = dynamic_cast<WEventPhaseChange*>(event)) {
|
|
if (Constants::MTG_PHASE_AFTER_EOT == pe->to->id && nbOpponents > MaxOpponent)
|
|
{
|
|
source->power -= PowerModifier * (nbOpponents - MaxOpponent);
|
|
source->addToToughness(-ToughnessModifier * (nbOpponents - MaxOpponent));
|
|
nbOpponents = 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
ARampageAbility * clone() const{
|
|
ARampageAbility * a = NEW ARampageAbility(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Instant Steal control of a target
|
|
class AInstantControlSteal: public InstantAbility{
|
|
public:
|
|
Player * TrueController;
|
|
Player * TheftController;
|
|
AInstantControlSteal(int _id , MTGCardInstance * _source, MTGCardInstance * _target):InstantAbility(_id, _source, _target){
|
|
TrueController = _target->controller();
|
|
TheftController = source->controller();
|
|
MTGCardInstance * copy = _target->changeController(TheftController);
|
|
target = copy;
|
|
source->target = copy;
|
|
copy->summoningSickness = 0;
|
|
}
|
|
|
|
int destroy(){
|
|
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
if (TheftController && TheftController->game->inPlay->hasCard(_target)){ //if the target is still in game -> spell was destroyed
|
|
_target->changeController(TrueController);
|
|
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AInstantControlSteal ::: TrueController : " << TrueController
|
|
<< " ; TheftController : " << TheftController
|
|
<< " (";
|
|
return InstantAbility::toString(out) << ")";
|
|
}
|
|
|
|
AInstantControlSteal * clone() const{
|
|
AInstantControlSteal * a = NEW AInstantControlSteal(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
//Angelic Chorus (10E)
|
|
class AAngelicChorus: public ListMaintainerAbility{
|
|
public:
|
|
int init;
|
|
AAngelicChorus(int id, MTGCardInstance * _source):ListMaintainerAbility(id, _source){
|
|
init = 0;
|
|
}
|
|
|
|
void Update(float dt){
|
|
ListMaintainerAbility::Update(dt);
|
|
init = 1;
|
|
}
|
|
|
|
int canBeInList(MTGCardInstance * card){
|
|
if (card->hasType(Subtypes::TYPE_CREATURE) && game->isInPlay(card)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
int added(MTGCardInstance * card){
|
|
if (!init) return 0;
|
|
if (source->controller() == game->currentlyActing()){
|
|
card->controller()->life+= card->toughness;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int removed(MTGCardInstance * card){
|
|
return 1;
|
|
}
|
|
|
|
virtual ostream& toString(ostream& out) const
|
|
{
|
|
out << "AAngelicChorus ::: init : " << init
|
|
<< " (";
|
|
return ListMaintainerAbility::toString(out) << ")";
|
|
}
|
|
|
|
AAngelicChorus * clone() const{
|
|
AAngelicChorus * a = NEW AAngelicChorus(*this);
|
|
a->isClone = 1;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
|
|
#endif
|