Files
wagic/projects/mtg/src/MTGAbility.cpp
wagic.the.homebrew@gmail.com ca35754273 Erwan
- Adding equipments. They work like auras, except you have to add an "auto={cost}:equip" line. See Behemoth sledge in ARB for an example. Please test a lot before committing, thanks :)
2009-12-10 13:44:05 +00:00

2251 lines
65 KiB
C++

#include "../include/config.h"
#include "../include/MTGAbility.h"
#include "../include/ManaCost.h"
#include "../include/MTGGameZones.h"
#include "../include/AllAbilities.h"
#include "../include/Damage.h"
#include "../include/TargetChooser.h"
#include "../include/CardGui.h"
#include "../include/MTGDeck.h"
#include "../include/Blocker.h"
#include "../include/Translate.h"
int AbilityFactory::countCards(TargetChooser * tc, Player * player, int option){
int result = 0;
GameObserver * game = GameObserver::GetInstance();
for (int i = 0; i < 2 ; i++){
if (player && player!= game->players[i]) continue;
MTGGameZone * zones[] = {game->players[i]->game->inPlay,game->players[i]->game->graveyard,game->players[i]->game->hand};
for (int k = 0; k < 3; k++){
for (int j = zones[k]->nb_cards-1; j >=0 ; j--){
MTGCardInstance * current = zones[k]->cards[j];
if (tc->canTarget(current)){
switch (option){
case COUNT_POWER:
result+= current->power;
break;
default:
result++;
break;
}
}
}
}
}
return result;
}
int AbilityFactory::parsePowerToughness(string s, int *power, int *toughness){
size_t found = s.find("/");
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 = -1;
*power = atoi(s.substr(start+1,s.size()-found).c_str());
*toughness = atoi(s.substr(found+1,end-found-1).c_str());
return 1;
}
return 0;
}
TriggeredAbility * AbilityFactory::parseTrigger(string magicText, int id, Spell * spell, MTGCardInstance *card, Targetable * target){
size_t found = magicText.find("@");
if (found == string::npos) return NULL;
found = magicText.find(":");
if (found == string::npos) return NULL;
string s = magicText.substr(0,found);
//Card Changed Zone
found = s.find("movedto(");
if (found != string::npos){
size_t end = s.find (")");
string starget = s.substr(found+8,end - found - 8);
TargetChooserFactory tcf;
TargetChooser *toTc = tcf.createTargetChooser(starget,card);
toTc->targetter = NULL;
TargetChooser *fromTc = NULL;
found = s.find("from(");
if (found != string::npos){
end = s.find (")", found);
starget = s.substr(found+5,end - found - 5);
if (starget.find("|") == string::npos) starget.insert(0,"*|");
fromTc = tcf.createTargetChooser(starget,card);
fromTc->targetter = NULL;
}
return NEW TrCardAddedToZone(id,card,toTc,(TargetZoneChooser *)fromTc);
}
//Card Tapped
found = s.find("tapped(");
if (found != string::npos){
size_t end = s.find (")");
string starget = s.substr(found+7,end - found - 7);
TargetChooserFactory tcf;
TargetChooser *tc = tcf.createTargetChooser(starget,card);
tc->targetter = NULL;
return NEW TrCardTapped(id,card,tc);
}
//Card Damaging
found = s.find("damaged(");
if (found != string::npos){
size_t end = s.find (")");
string starget = s.substr(found+8,end - found - 8);
TargetChooserFactory tcf;
TargetChooser *tc = tcf.createTargetChooser(starget,card);
tc->targetter = NULL;
found = s.find("from(");
TargetChooser *fromTc = NULL;
if (found != string::npos){
end = s.find (")", found);
starget = s.substr(found+5,end - found - 5);
fromTc = tcf.createTargetChooser(starget,card);
fromTc->targetter = NULL;
}
return NEW TrDamaged(id,card,tc,fromTc);
}
int who = 0;
if (s.find("my") != string::npos) who = 1;
if (s.find("opponent") != string::npos) who = -1;
if (s.find("targetcontroller") != string::npos) who = -2;
//Next Time...
found = s.find("next");
if (found != string::npos){
for (int i = 0; i < Constants::NB_MTG_PHASES; i++){
found = s.find(Constants::MTGPhaseCodeNames[i]);
if (found != string::npos){
return NEW TriggerNextPhase(id, card,target,i,who);
}
}
}
//Each Time...
found = magicText.find("each");
if (found != string::npos){
for (int i = 0; i < Constants::NB_MTG_PHASES; i++){
found = magicText.find(Constants::MTGPhaseCodeNames[i]);
if (found != string::npos){
return NEW TriggerAtPhase(id, card,target,i,who);
}
}
}
return NULL;
}
//Parses a string and returns the corresponding MTGAbility object
// Returns NULL if parsing failed
//Beware, Spell CAN be null when the function is called by the AI trying to analyze the effects of a given card
MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTGCardInstance *card, int activated, int forceUEOT){
size_t found;
string whitespaces (" \t\f\v\n\r");
found=s.find_last_not_of(whitespaces);
if (found!=string::npos)
s.erase(found+1);
else return NULL;
found=s.find_first_not_of(whitespaces);
if (found!=string::npos)
s.erase(0,found);
else return NULL;
//TODO This block redundant with calling function
if (!card && spell) card = spell->source;
if (!card) return NULL;
MTGCardInstance * target = card->target;
if (!target) target = card;
TriggeredAbility * trigger = NULL;
trigger = parseTrigger(s,id,spell,card,target);
//Dirty way to remove the trigger text (could get in the way)
if (trigger){
found = s.find(":");
string s1 = s.substr(found+1);
MTGAbility * a = parseMagicLine(s1, id, spell, card,activated);
if (!a){
delete trigger;
return NULL;
}
return NEW GenericTriggeredAbility(id,card,trigger,a,NULL,target);
}
int doTap = 0; //Tap in the cost ?
if (s.find("{t}") != string::npos) doTap = 1;
int myTurnOnly = 0;
if (s.find("myturnonly") != string::npos) myTurnOnly = 1;
size_t delimiter = s.find("}:");
size_t firstNonSpace = s.find_first_not_of(" ");
if (delimiter!= string::npos && firstNonSpace !=string::npos && s[firstNonSpace] == '{'){
ManaCost * cost = ManaCost::parseManaCost(s.substr(0,delimiter+1),NULL,card);
if (doTap || cost){
string s1 = s.substr(delimiter+2);
MTGAbility * a = parseMagicLine(s1, id, spell, card, 1);
if (!a){
OutputDebugString("ABILITYFACTORY Error parsing:");
OutputDebugString(s.c_str());
OutputDebugString("\n");
return NULL;
}
//A stupid Special case for ManaProducers because they don't use the stack :(
AManaProducer * amp = dynamic_cast<AManaProducer*>(a);
if (amp){
amp->cost = cost;
amp->oneShot = 0;
amp->tap = doTap;
return amp;
}
AEquip *ae = dynamic_cast<AEquip*>(a);
if (ae){
ae->cost = cost;
TargetChooserFactory tcf;
ae->tc = tcf.createTargetChooser("creature|myBattlefield", card);
return ae;
}
int limit = 0;
unsigned int limit_str = s.find("limit:");
if (limit_str != string::npos){
limit = atoi(s.substr(limit_str+6).c_str());
}
TargetChooser * tc = NULL;
//Target Abilities
found = s.find("target(");
if (found != string::npos){
int end = s.find(")", found);
string starget = s.substr(found + 7,end - found - 7);
TargetChooserFactory tcf;
tc = tcf.createTargetChooser(starget, card);
}
if (tc) return NEW GenericTargetAbility(id, card, tc, a,cost, doTap,limit,myTurnOnly);
return NEW GenericActivatedAbility(id, card, a,cost,doTap,limit,myTurnOnly);
}
SAFE_DELETE(cost);
}
//kicker cost
found = s.find("kicker ");
if (found == 0){
if (spell && spell->kickerWasPaid()){
string s1 = s.substr(found+7);
return parseMagicLine(s1,id,spell, card);
}
return NULL;
}
//When...comes into play, you may...
found = s.find("may ");
if (found == 0){
string s1 = s.substr(found+4);
MTGAbility * a1 = parseMagicLine(s1,id,spell, card);
if (!a1) return NULL;
TargetChooser * tc = NULL;
//Target Abilities
found = s.find("target(");
if (found != string::npos){
int end = s.find(")", found);
string starget = s.substr(found + 7,end - found - 7);
TargetChooserFactory tcf;
tc = tcf.createTargetChooser(starget, card);
}
if (tc) a1 = NEW GenericTargetAbility(id, card, tc, a1);
else a1 = NEW GenericActivatedAbility(id, card, a1,NULL);
return NEW MayAbility(id,a1,card);
}
//Multiple abilities for ONE cost
found = s.find("&&");
if (found != string::npos){
string s1 = s.substr(0,found);
string s2 = s.substr(found+2);
MultiAbility * multi = NEW MultiAbility(id, card,target,NULL,NULL);
MTGAbility * a1 = parseMagicLine(s1,id,spell, card,activated);
MTGAbility * a2 = parseMagicLine(s2,id,spell, card,activated);
multi->Add(a1);
multi->Add(a2);
multi->oneShot=1;
return multi;
}
//Lord, foreach, aslongas
string lords[] = {"lord(","foreach(", "aslongas(", "all("};
found = string::npos;
int i = -1;
for (int j = 0; j < 4; ++j){
size_t found2 = s.find(lords[j]);
if (found2!=string::npos && ((found == string::npos) || found2 < found)){
found = found2;
i = j;
}
}
if (found != string::npos){
size_t header = lords[i].size();
size_t end = s.find(")", found+header);
string s1;
if (found == 0 || end != s.size()-1){
s1 = s.substr(end+1);
}else{
s1 = s.substr(0, found);
}
if (end != string::npos){
int lordIncludeSelf = 1;
size_t other = s1.find("other");
if ( other != string::npos){
lordIncludeSelf = 0;
s1.replace(other, 5,"");
}
string lordTargetsString = s.substr(found+header,end-found-header);
TargetChooserFactory tcf;
TargetChooser * lordTargets = tcf.createTargetChooser(lordTargetsString, card);
if (!lordTargets){
OutputDebugString("MTGABILITY: Parsing Error:");
OutputDebugString(s.c_str());
OutputDebugString("\n");
return NULL;
}
MTGAbility * a = parseMagicLine(s1,id,spell, card,0,activated); //activated lords usually force an end of turn ability
if (!a){
SAFE_DELETE(lordTargets);
return NULL;
}
MTGAbility * result = NULL;
int oneShot = 0;
if (activated) oneShot = 1;
if (card->hasType("sorcery") || card->hasType("instant")) oneShot = 1;
if (i == 3) oneShot = 1;
if (a->oneShot) oneShot = 1;
Damageable * _target = NULL;
if (spell) _target = spell->getNextDamageableTarget();
if (!_target) _target = target;
int mini = 0;
int maxi = 0;
found = s.find(">");
if (found !=string::npos) mini = atoi(s.substr(found+1,1).c_str());
found = s.find("<");
if (found !=string::npos) maxi = atoi(s.substr(found+1,1).c_str());
switch(i){
case 0: result = NEW ALord(id, card, lordTargets, lordIncludeSelf, a); break;
case 1: result = NEW AForeach(id, card, _target,lordTargets, lordIncludeSelf, a,mini,maxi); break;
case 2: result = NEW AAsLongAs(id, card, _target,lordTargets, lordIncludeSelf, a,mini,maxi); break;
case 3: result = NEW ALord(id, card, lordTargets, lordIncludeSelf, a); break;
default: result = NULL;
}
if (result) result->oneShot = oneShot;
return result;
}
return NULL;
}
//Fizzle (counterspell...)
found = s.find("fizzle");
if (found != string::npos){
Spell * starget = NULL;
if (spell) starget = spell->getNextSpellTarget();
MTGAbility * a = NEW AAFizzler(id,card,starget);
a->oneShot = 1;
return a;
}
//Regeneration
found = s.find("regenerate");
if (found != string::npos){
MTGAbility * a = NEW AStandardRegenerate(id,card,target);
a->oneShot = 1;
return a;
}
//Token creator. Name, type, p/t, abilities
found = s.find("token(");
if (found != string::npos){
WParsedInt * multiplier = NULL;
size_t star = s.find("*");
if (star != string::npos) multiplier = NEW WParsedInt(s.substr(star+1),spell,card);
size_t end = s.find(")", found);
int tokenId = atoi(s.substr(found + 6,end - found - 6).c_str());
if (tokenId){
ATokenCreator * tok = NEW ATokenCreator(id,card,NULL,tokenId,0, multiplier);
tok->oneShot = 1;
return tok;
}
end = s.find(",", found);
string sname = s.substr(found + 6,end - found - 6);
size_t previous = end+1;
end = s.find(",",previous);
string stypes = s.substr(previous,end - previous);
previous = end+1;
end = s.find(",",previous);
string spt = s.substr(previous,end - previous);
int power, toughness;
parsePowerToughness(spt,&power, &toughness);
string sabilities = s.substr(end+1);
ATokenCreator * tok = NEW ATokenCreator(id,card,NULL,sname,stypes,power,toughness,sabilities,0, multiplier);
tok->oneShot = 1;
return tok;
}
//Equipment
found = s.find("equip");
if (found != string::npos){
MTGAbility * a = NEW AEquip(id,card);
return a;
}
//MoveTo Move a card from a zone to another
found = s.find("moveto(");
if (found != string::npos){
int end = s.find(")",found+1);
string szone = s.substr(found + 7,end - found - 7);
//hack for http://code.google.com/p/wagic/issues/detail?id=120
//We assume that auras don't move their own target...
if (card->hasType("aura")) target = card;
MTGAbility * a = NEW AAMover(id,card,target,szone);
a->oneShot = 1;
return a;
}
//Copy a target
found = s.find("copy ");
if (found != string::npos){
MTGAbility * a = NEW AACopier(id,card,target);
a->oneShot = 1;
return a;
}
//Bury, destroy
string destroys[] = {"bury","destroy"};
int destroyTypes[]= {1, 0};
for (int i = 0; i < 2; ++i){
found = s.find(destroys[i]);
if (found != string::npos){
int bury = destroyTypes[i];
MTGAbility * a = NEW AADestroyer(id,card,target,bury);
a->oneShot = 1;
return a;
}
}
int who = TargetChooser::UNSET;
if (s.find(" controller") != string::npos) who=TargetChooser::CONTROLLER;
if (s.find(" opponent") != string::npos) who=TargetChooser::OPPONENT;
if (s.find(" targetcontroller") != string::npos) who=TargetChooser::TARGET_CONTROLLER;
found = s.find("ueot");
if (found!= string::npos) forceUEOT = 1;
//PreventCombat Damage
found = s.find("preventallcombatdamage");
if (found != string::npos){
string to = "";
string from = "";
found = s.find("to(");
if (found != string::npos){
size_t end = s.find (")", found);
to = s.substr(found+3,end - found - 3);
}
found = s.find("from(");
if (found != string::npos){
size_t end = s.find (")", found);
from = s.substr(found+5,end - found - 5);
}
MTGAbility * ab;
if (forceUEOT){
ab = NEW APreventAllCombatDamageUEOT(id,card,to,from);
}else{
ab = NEW APreventAllCombatDamage(id,card,to,from);
}
return ab;
}
//Damage
found = s.find("damage");
if (found != string::npos){
unsigned int start = s.find(":",found);
if (start == string::npos) start = s.find(" ",found);
unsigned int end = s.find(" ",start);
string d;
if (end != string::npos){
d = s.substr(start+1,end-start-1);
}else{
d = s.substr(start+1);
}
WParsedInt * damage = NEW WParsedInt(d,spell,card);
Damageable * t = NULL;
if (spell) t = spell->getNextDamageableTarget();
MTGAbility * a = NEW AADamager(id,card,t, damage, NULL, 0, who);
a->oneShot = 1;
return a;
}
//gain/lose life
found = s.find("life:");
if (found != string::npos){
unsigned int start = found+4;
unsigned int end = s.find(" ",start);
string life_s;
if (end != string::npos){
life_s = s.substr(start+1,end-start-1);
}else{
life_s = s.substr(start+1);
}
WParsedInt * life = NEW WParsedInt(life_s,spell,card);
Damageable * d = NULL;
if (spell) d = spell->getNextDamageableTarget();
MTGAbility * a = NEW AALifer(id,card,d,life,NULL,0,who);
a->oneShot = 1;
return a;
}
//Draw
found = s.find("draw:");
if (found != string::npos){
unsigned int start = s.find(":",found);
unsigned int end = s.find(" ",start);
string nbcardsStr;
if (end != string::npos){
nbcardsStr = s.substr(start+1,end-start-1);
}else{
nbcardsStr = s.substr(start+1);
}
WParsedInt * nbcards = NEW WParsedInt(nbcardsStr,spell,card);
Targetable * t = NULL;
if (spell) t = spell->getNextTarget();
MTGAbility * a = NEW AADrawer(id,card,t,NULL,nbcards,0,who);
a->oneShot = 1;
return a;
}
//Deplete
found = s.find("deplete:");
if (found != string::npos){
unsigned int start = s.find(":",found);
unsigned int end = s.find(" ",start);
int nbcards;
if (end != string::npos){
nbcards = atoi(s.substr(start+1,end-start-1).c_str());
}else{
nbcards = atoi(s.substr(start+1).c_str());
}
Targetable * t = NULL;
if (spell) t = spell->getNextTarget();
MTGAbility * a = NEW AADepleter(id,card,t,nbcards,NULL,0,who);
a->oneShot = 1;
return a;
}
//Shuffle
found = s.find("shuffle");
if (found != string::npos){
Targetable * t = NULL;
if (spell) t = spell->getNextTarget();
MTGAbility * a = NEW AAShuffle(id,card,t,NULL,0,who);
a->oneShot = 1;
return a;
}
//Discard
found = s.find("discard:");
if (found != string::npos){
unsigned int start = s.find(":",found);
unsigned int end = s.find(" ",start);
int nbcards;
if (end != string::npos){
nbcards = atoi(s.substr(start+1,end-start-1).c_str());
}else{
nbcards = atoi(s.substr(start+1).c_str());
}
Targetable * t = NULL;
if (spell) t = spell->getNextPlayerTarget();
MTGAbility * a = NEW AARandomDiscarder (id, card, t,nbcards,NULL,0,who);
a->oneShot = 1;
return a;
}
//rampage
found = s.find("rampage(");
if (found != string::npos){
int end = s.find(",", found);
string spt = s.substr(8,end - 1);
int power, toughness;
if (parsePowerToughness(spt,&power, &toughness)){
int MaxOpponent = atoi(s.substr(end+1,end+2).c_str());
return NEW ARampageAbility(id,card,power,toughness,MaxOpponent);
}
return NULL;
}
//counter
found = s.find("counter(");
if (found != string::npos){
found+=8;
int nb = 1;
size_t end = s.find(")", found);
size_t separator = s.find(",", found);
if (separator != string::npos){
string nbstr = s.substr(separator+1,end-separator-1);
nb = atoi(nbstr.c_str());
end = separator;
}
string spt = s.substr(found,end-found);
int power, toughness;
if ( parsePowerToughness(spt,&power, &toughness)){
MTGAbility * a = NEW AACounter(id,card,target,power,toughness,nb);
a->oneShot = 1;
return a;
}
return NULL;
}
//Becomes... (animate artifact...: becomes(Creature, manacost/manacost)
found = s.find("becomes(");
if (found != string::npos){
size_t real_end = s.find(")", found);
size_t end = s.find(",", found);
if (end == string::npos) end = real_end;
string stypes = s.substr(found + 8,end - found - 8);
WParsedPT * pt = NULL;
string sabilities;
if (end != real_end){
int previous = end+1;
end = s.find(",",previous);
if (end == string::npos) end = real_end;
string temp = s.substr(previous, end - previous);
pt = NEW WParsedPT(temp,spell,card);
if (!pt->ok){
SAFE_DELETE(pt);
sabilities = temp;
}
}
if (pt && end != real_end){
sabilities = s.substr(end+1, real_end - end);
}
MTGAbility * ab;
if (forceUEOT){
ab = NEW ABecomesUEOT(id,card,target,stypes,pt,sabilities);
}else{
ab = NEW ABecomes(id,card,target,stypes,pt,sabilities);
}
return ab;
}
//Change Power/Toughness
WParsedPT * wppt = NEW WParsedPT(s,spell,card);
if (wppt->ok){
if (!activated){
if(card->hasType("instant") || card->hasType("sorcery") || forceUEOT){
return NEW AInstantPowerToughnessModifierUntilEOT(id, card, target,wppt);
}
return NEW APowerToughnessModifier(id, card, target,wppt);
}
return NEW APowerToughnessModifierUntilEndOfTurn(id,card,target,wppt);
}else{
delete wppt;
}
//Mana Producer
found = s.find("add");
if (found != string::npos){
ManaCost * output = ManaCost::parseManaCost(s.substr(found));
Targetable * t = NULL;
if (spell) t = spell->getNextTarget();
MTGAbility * a = NEW AManaProducer(id, card, t, output, NULL, 1, who);
a->oneShot = 1;
return a;
}
//Gain/loose Ability
for (int j = 0; j < Constants::NB_BASIC_ABILITIES; j++){
found = s.find(Constants::MTGBasicAbilities[j]);
if (found == 0 || found == 1){
int modifier = 1;
if (found > 0 && s[found-1] == '-') modifier = 0;
if (!activated){
if(card->hasType("instant") || card->hasType("sorcery") || forceUEOT){
return NEW AInstantBasicAbilityModifierUntilEOT(id, card,target, j,modifier);
}
return NEW ABasicAbilityModifier(id, card,target, j,modifier);
}
return NEW ABasicAbilityAuraModifierUntilEOT(id, card,target, NULL,j,modifier);
}
}
//Protection from...
found = s.find("protection from(");
if (found == 0){
size_t end = s.find (")", found);
string targets = s.substr(found+16,end - found - 16);
TargetChooserFactory tcf;
TargetChooser * fromTc = tcf.createTargetChooser(targets, card);
if (!fromTc) return NULL;
fromTc->setAllZones();
if (!activated){
if(card->hasType("instant") || card->hasType("sorcery") || forceUEOT){
return NULL; //TODO
}
return NEW AProtectionFrom(id, card,target,fromTc);
}
return NULL; //TODO
}
//Untapper (Ley Druid...)
found = s.find("untap");
if (found != string::npos){
MTGAbility * a = NEW AAUntapper(id,card,target);
a->oneShot = 1;
return a;
}
//Tapper (icy manipulator)
found = s.find("tap");
if (found != string::npos){
MTGAbility * a = NEW AATapper(id,card,target);
a->oneShot = 1;
return a;
}
return NULL;
}
//Tells the AI if the ability should target itself or an ennemy
int AbilityFactory::abilityEfficiency(MTGAbility * a, Player * p, int mode, TargetChooser * tc){
if (!a) return BAKA_EFFECT_DONTKNOW;
if (GenericTargetAbility * abi = dynamic_cast<GenericTargetAbility*>(a)) {
if (mode == MODE_PUTINTOPLAY) return BAKA_EFFECT_GOOD;
return abilityEfficiency(abi->ability,p, mode, abi->tc);
}
if (GenericActivatedAbility * abi = dynamic_cast<GenericActivatedAbility*>(a)) {
if (mode == MODE_PUTINTOPLAY) return BAKA_EFFECT_GOOD;
return abilityEfficiency(abi->ability,p, mode,tc);
}
if (MultiAbility * abi = dynamic_cast<MultiAbility*>(a)) return abilityEfficiency(abi->abilities[0],p, mode,tc );
if (MayAbility * abi = dynamic_cast<MayAbility*>(a)) return abilityEfficiency(abi->ability,p, mode,tc);
if (ALord * abi = dynamic_cast<ALord *>(a)) {
int myCards = countCards(abi->tc, p);
int theirCards = countCards(abi->tc, p->opponent());
int efficiency = abilityEfficiency(abi->ability,p, mode,tc);
if (myCards > theirCards) return efficiency;
return -efficiency;
}
if (AAsLongAs * abi = dynamic_cast<AAsLongAs *>(a)) return abilityEfficiency(abi->ability,p, mode,tc);
if (AForeach * abi = dynamic_cast<AForeach *>(a)) return abilityEfficiency(abi->ability,p, mode,tc);
if (dynamic_cast<AAFizzler *>(a)) return BAKA_EFFECT_BAD;
if (dynamic_cast<AAUntapper *>(a)) return BAKA_EFFECT_GOOD;
if (dynamic_cast<AATapper *>(a)) return BAKA_EFFECT_BAD;
if (AACounter * ac = dynamic_cast<AACounter *>(a)) {
bool negative_effect = ac->power < 0 || ac->toughness < 0;
if ((ac->nb > 0 && negative_effect) || (ac->nb < 0 && !negative_effect)) return BAKA_EFFECT_BAD;
return BAKA_EFFECT_GOOD ;
}
if (dynamic_cast<ATokenCreator *>(a)) return BAKA_EFFECT_GOOD;
if (AAMover * aam = dynamic_cast<AAMover *>(a)) {
MTGGameZone * z = aam->destinationZone();
if (tc && tc->targetsZone(p->game->library)){
if (z == p->game->hand || z == p->game->inPlay) return BAKA_EFFECT_GOOD;
}
return BAKA_EFFECT_BAD; //TODO
}
if (dynamic_cast<AACopier *>(a)) return BAKA_EFFECT_GOOD;
if (dynamic_cast<AADestroyer *>(a)) return BAKA_EFFECT_BAD;
if (dynamic_cast<AStandardRegenerate *>(a)) return BAKA_EFFECT_GOOD;
if (dynamic_cast<AStandardRegenerate *>(a)) return BAKA_EFFECT_BAD;
if (AALifer * abi = dynamic_cast<AALifer *>(a)) return abi->life > 0 ? BAKA_EFFECT_GOOD : BAKA_EFFECT_BAD;
if (dynamic_cast<AADepleter *>(a)) return BAKA_EFFECT_BAD;
if (dynamic_cast<AADrawer *>(a)) return BAKA_EFFECT_GOOD;
if (dynamic_cast<AARandomDiscarder *>(a)) return BAKA_EFFECT_BAD;
if (dynamic_cast<ARampageAbility *>(a)) return BAKA_EFFECT_GOOD;
if (AInstantPowerToughnessModifierUntilEOT * abi = dynamic_cast<AInstantPowerToughnessModifierUntilEOT *>(a)) return (abi->wppt->power.getValue()>=0 && abi->wppt->toughness.getValue()>=0) ? BAKA_EFFECT_GOOD : BAKA_EFFECT_BAD;
if (APowerToughnessModifier * abi = dynamic_cast<APowerToughnessModifier *>(a)) return (abi->wppt->power.getValue()>=0 && abi->wppt->toughness.getValue()>=0) ? BAKA_EFFECT_GOOD : BAKA_EFFECT_BAD;
if (APowerToughnessModifierUntilEndOfTurn * abi = dynamic_cast<APowerToughnessModifierUntilEndOfTurn *>(a)) return abilityEfficiency(abi->ability, p, mode,tc);
map<int,bool> badAbilities;
badAbilities[Constants::CANTATTACK] = true;
badAbilities[Constants::CANTBLOCK] = true;
badAbilities[Constants::CLOUD] = true;
badAbilities[Constants::DEFENDER] = true;
badAbilities[Constants::DOESNOTUNTAP] = true;
badAbilities[Constants::MUSTATTACK] = true;
badAbilities[Constants::CANTREGENERATE] = true;
if (AInstantBasicAbilityModifierUntilEOT * abi = dynamic_cast<AInstantBasicAbilityModifierUntilEOT *>(a)) {
int result = badAbilities[abi->ability] ? BAKA_EFFECT_BAD : BAKA_EFFECT_GOOD;
return (abi->value > 0) ? result : -result;
}
if (ABasicAbilityModifier * abi = dynamic_cast<ABasicAbilityModifier *>(a)){
int result = (badAbilities[abi->ability]) ? BAKA_EFFECT_BAD : BAKA_EFFECT_GOOD;
return (abi->modifier > 0) ? result : -result;
}
if (ABasicAbilityAuraModifierUntilEOT * abi = dynamic_cast<ABasicAbilityAuraModifierUntilEOT *>(a))
return abilityEfficiency(abi->ability, p, mode);
if (dynamic_cast<AManaProducer*>(a)) return BAKA_EFFECT_GOOD;
return BAKA_EFFECT_DONTKNOW;
}
//Returns the "X" cost that was paid for a spell
int AbilityFactory::computeX(Spell * spell, MTGCardInstance * card){
if (spell) return spell->computeX(card);
return 0;
}
int AbilityFactory::getAbilities(vector<MTGAbility *> * v, Spell * spell, MTGCardInstance * card, int id){
if (!card && spell) card = spell->source;
if (!card) return 0;
MTGCardInstance * target = card->target;
if (!target) target = card;
string magicText = card->magicText;
if (card->alias && magicText.size() == 0){
MTGCard * c = GameApp::collection->getCardById(card->alias);
if (!c) return 0;
magicText = c->magicText;
}
string line;
int size = magicText.size();
if (size == 0) return 0;
unsigned int found;
int result = id;
while (magicText.size()){
found = magicText.find("\n");
if (found != string::npos){
line = magicText.substr(0,found);
magicText = magicText.substr(found+1);
}else{
line = magicText;
magicText = "";
}
MTGAbility * a = parseMagicLine(line, result, spell, card);
if (a){
v->push_back(a);
result++;
}else{
OutputDebugString("ABILITYFACTORY ERROR: Parser returned NULL\n");
}
}
return result;
}
//Some basic functionalities that can be added automatically in the text file
/*
* Several objects are computed from the text string, and have a direct influence on what action we should take
* (direct impact on the game such as draw a card immediately, or create a New GameObserver and add it to the Abilities,etc..)
* These objects are:
* - trigger (if there is an "@" in the string, this is a triggered ability)
* - target (if there ie a "target(" in the string, then this is a TargetAbility)
* - doTap (a dirty way to know if tapping is included in the cost...
*/
int AbilityFactory::magicText(int id, Spell * spell, MTGCardInstance * card, int mode, TargetChooser * tc){
int dryMode = 0;
if (!spell) dryMode = 1;
vector<MTGAbility *> v;
int result = getAbilities(&v,spell,card,id);
for (size_t i = 0; i < v.size(); ++i){
MTGAbility * a = v[i];
if (dryMode){
result = abilityEfficiency(a, card->controller(),mode,tc);
SAFE_DELETE(a);
return result;
}
if (a){
if (a->oneShot){
a->resolve();
delete(a);
}else{
a->addToGame();
}
}else{
OutputDebugString("ABILITYFACTORY ERROR: Parser returned NULL\n");
}
}
return result;
}
void AbilityFactory::addAbilities(int _id, Spell * spell){
MTGCardInstance * card = spell->source;
if (spell->getNbTargets()==1){
card->target = spell->getNextCardTarget();
if (card->target && !spell->tc->canTarget(card->target)){
MTGPlayerCards * zones = card->controller()->game;
zones->putInZone(card,spell->from,card->owner->game->graveyard);
return; //fizzle
}
}
_id = magicText(_id, spell);
GameObserver * game = GameObserver::GetInstance();
MTGPlayerCards * zones = card->controller()->game;
int id = card->getId();
if (card->alias) id = card->alias;
switch (id){
case 1092: //Aladdin's lamp
{
AAladdinsLamp * ability = NEW AAladdinsLamp(_id, card);
game->addObserver(ability);
break;
}
case 1095: //Armageddon clock
{
AArmageddonClock * ability = NEW AArmageddonClock(_id,card);
game->addObserver(ability);
break;
}
case 1191: //Blue Elemental Blast
{
if (card->target){
card->target->controller()->game->putInGraveyard(card->target);
}else{
Spell * starget = spell->getNextSpellTarget();
game->mLayers->stackLayer()->Fizzle(starget);
}
break;
}
case 1237: //Channel
{
game->addObserver(NEW AChannel(_id, card));
break;
}
case 1282: //Chaoslace
{
if (card->target){
card->target->setColor(Constants::MTG_COLOR_RED, 1);
}else{
Spell * starget = spell->getNextSpellTarget();
starget->source->setColor(Constants::MTG_COLOR_RED, 1);
}
break;
}
case 1335: //Circle of protection : black
{
game->addObserver(NEW ACircleOfProtection( _id,card, Constants::MTG_COLOR_BLACK));
break;
}
case 1336: //Circle of protection : blue
{
game->addObserver(NEW ACircleOfProtection( _id,card, Constants::MTG_COLOR_BLUE));
break;
}
case 1337: //Circle of protection : green
{
game->addObserver(NEW ACircleOfProtection( _id,card, Constants::MTG_COLOR_GREEN));
break;
}
case 1338: //Circle of protection : red
{
game->addObserver(NEW ACircleOfProtection( _id,card, Constants::MTG_COLOR_RED));
break;
}
case 1339: //Circle of protection : white
{
game->addObserver(NEW ACircleOfProtection( _id,card, Constants::MTG_COLOR_WHITE));
break;
}
case 1101: //clockwork Beast
{
game->addObserver(NEW AClockworkBeast(_id,card));
break;
}
case 1102: //Conservator
{
game->addObserver(NEW AConservator(_id,card));
break;
}
case 1103: //Crystal Rod
{
int cost[] = {Constants::MTG_COLOR_ARTIFACT, 1};
ASpellCastLife* ability = NEW ASpellCastLife(_id, card, Constants::MTG_COLOR_BLUE,NEW ManaCost(cost,1) , 1);
game->addObserver(ability);
break;
}
case 1152: //Deathlace
{
if (card->target){
card->target->setColor(Constants::MTG_COLOR_BLACK, 1);
}else{
Spell * starget = spell->getNextSpellTarget();
starget->source->setColor(Constants::MTG_COLOR_BLACK, 1);
}
break;
}
case 1106: //Disrupting Scepter
{
ADisruptingScepter * ability = NEW ADisruptingScepter(_id,card);
game->addObserver(ability);
break;
}
case 1284: //Dragon Whelp
{
game->addObserver(NEW ADragonWhelp(_id,card));
break;
}
case 1345: //Farmstead
{
game->addObserver(NEW AFarmstead(_id, card,card->target));
break;
}
case 1291: //Fireball
{
int x = computeX(spell,card);
game->addObserver(NEW AFireball(_id, card,spell, x));
break;
}
case 1245: //Force of Nature
{
game->addObserver(NEW AForceOfNature(_id,card));
break;
}
case 1110: //Glasses Of Urza
{
AGlassesOfUrza * ability = NEW AGlassesOfUrza(_id,card);
game->addObserver(ability);
break;
}
case 1112: //Howling Mine
{
game->addObserver(NEW AHowlingMine(_id, card));
break;
}
case 1113: //Iron Star
{
int cost[] = {Constants::MTG_COLOR_ARTIFACT, 1};
ASpellCastLife* ability = NEW ASpellCastLife(_id, card, Constants::MTG_COLOR_RED,NEW ManaCost(cost,1) , 1);
game->addObserver(ability);
break;
}
case 1351: // Island Sanctuary
{
game->addObserver(NEW AIslandSanctuary(_id, card));
break;
}
case 1114: //Ivory cup
{
int cost[] = {Constants::MTG_COLOR_ARTIFACT, 1};
ASpellCastLife* ability = NEW ASpellCastLife(_id, card, Constants::MTG_COLOR_WHITE,NEW ManaCost(cost,1) , 1);
game->addObserver(ability);
break;
}
case 1117: //Jandors Ring
{
game->addObserver(NEW AJandorsRing( _id, card));
break;
}
case 1121: //Kormus Bell
{
game->addObserver(NEW AConvertLandToCreatures(id, card, "swamp"));
break;
}
case 1254: //Kudzu
{
game->addObserver(NEW AKudzu(id, card, card->target));
break;
}
case 1257: //Lifelace
{
if (card->target){
card->target->setColor(Constants::MTG_COLOR_GREEN, 1);
}else{
Spell * starget = spell->getNextSpellTarget();
starget->source->setColor(Constants::MTG_COLOR_GREEN, 1);
}
break;
}
case 1259: //Living lands
{
game->addObserver(NEW AConvertLandToCreatures(id, card, "forest"));
break;
}
case 1124: //Mana Vault
{
int output[] = {Constants::MTG_COLOR_ARTIFACT, 3};
game->addObserver(NEW AManaProducer(_id,card,card,NEW ManaCost(output,1)));
int cost[] = {Constants::MTG_COLOR_ARTIFACT, 4};
game->addObserver(NEW AUntapManaBlocker(_id+1, card, NEW ManaCost(cost,1)));
game->addObserver(NEW ARegularLifeModifierAura(_id+2, card, card, Constants::MTG_PHASE_DRAW, -1, 1));
break;
}
case 1215: //Power Leak
{
game->addObserver( NEW APowerLeak(_id ,card, card->target));
break;
}
case 1358: //Purelace
{
if (card->target){
card->target->setColor(Constants::MTG_COLOR_WHITE, 1);
}else{
Spell * starget = spell->getNextSpellTarget();
starget->source->setColor(Constants::MTG_COLOR_WHITE, 1);
}
break;
}
case 1312: //Red Elemental Blast
{
if (card->target){
card->target->controller()->game->putInGraveyard(card->target);
}else{
Spell * starget = spell->getNextSpellTarget();
game->mLayers->stackLayer()->Fizzle(starget);
}
break;
}
case 1136: //Soul Net
{
game->addObserver( NEW ASoulNet(_id ,card));
break;
}
case 1139: //The Rack
{
game->addObserver( NEW ALifeZoneLink(_id ,card, Constants::MTG_PHASE_UPKEEP, -3));
break;
}
case 1140: //Throne of Bone
{
int cost[] = {Constants::MTG_COLOR_ARTIFACT, 1};
ASpellCastLife* ability = NEW ASpellCastLife(_id, card, Constants::MTG_COLOR_BLACK,NEW ManaCost(cost,1) , 1);
game->addObserver(ability);
break;
}
case 1142: //Wooden Sphere
{
int cost[] = {Constants::MTG_COLOR_ARTIFACT, 1};
ASpellCastLife* ability = NEW ASpellCastLife(_id, card, Constants::MTG_COLOR_GREEN,NEW ManaCost(cost,1) , 1);
game->addObserver(ability);
break;
}
case 1143: //Animate Dead
{
AAnimateDead * a = NEW AAnimateDead(_id, card, card->target);
game->addObserver(a);
card->target = ((MTGCardInstance * )a->target);
break;
}
case 1156: //Drain Life
{
Damageable * target = spell->getNextDamageableTarget();
int x = spell->cost->getConvertedCost() - 2; //TODO Fix that !!! + X should be only black mana, that needs to be checked !
game->mLayers->stackLayer()->addDamage(card, target, x);
if (target->life < x) x = target->life;
game->currentlyActing()->life+=x;
break;
}
case 1159: //Erg Raiders
{
AErgRaiders* ability = NEW AErgRaiders(_id, card);
game->addObserver(ability);
break;
}
case 1202: //Hurkyl's Recall
{
Player * player = spell->getNextPlayerTarget();
if (player){
for (int i = 0; i < 2; i++){
MTGInPlay * inplay = game->players[i]->game->inPlay;
for (int j= inplay->nb_cards -1 ; j >=0 ; j--){
MTGCardInstance * card = inplay->cards[j];
if (card->owner == player && card->hasType("artifact")){
player->game->putInZone(card, inplay, player->game->hand);
}
}
}
}
break;
}
case 1165: //Hypnotic Specter
{
game->addObserver(NEW AHypnoticSpecter( _id, card));
break;
}
case 1258: //Living Artifact
{
game->addObserver(NEW ALivingArtifact( _id, card, card->target));
break;
}
case 1166: //Lord Of The Pit
{
game->addObserver(NEW ALordOfThePit( _id, card));
break;
}
case 1209: //Mana Short
{
Player * player = spell->getNextPlayerTarget();
if (player){
MTGInPlay * inplay = player->game->inPlay;
for (int i = 0; i < inplay->nb_cards; i++){
MTGCardInstance * current = inplay->cards[i];
if (current->hasType(Subtypes::TYPE_LAND)) current->tap();
}
player->getManaPool()->init();
}
break;
}
case 1167: //Mind Twist
{
int xCost = computeX(spell,card);
for (int i = 0; i < xCost; i++){
game->opponent()->game->discardRandom(game->opponent()->game->hand);
}
break;
}
case 1171: //Paralyze
{
int cost[] = {Constants::MTG_COLOR_ARTIFACT, 4};
game->addObserver(NEW AUntapManaBlocker(_id, card,card->target, NEW ManaCost(cost,1)));
card->target->tap();
break;
}
case 1172: //Pestilence
{
game->addObserver(NEW APestilence(_id, card));
break;
}
case 1176: //Sacrifice
{
ASacrifice * ability = NEW ASacrifice(_id, card, card->target);
game->addObserver(ability);
break;
}
case 1224: //Spell Blast
{
int x = computeX(spell,card);
Spell * starget = spell->getNextSpellTarget();
if (starget){
if (starget->cost->getConvertedCost() <= x) game->mLayers->stackLayer()->Fizzle(starget);
}
break;
}
case 1194: //Control Magic
{
game->addObserver(NEW AControlStealAura(_id, card, card->target));
break;
}
case 1235: //Aspect of Wolf
{
game->addObserver(NEW AAspectOfWolf(_id, card, card->target));
break;
}
case 1231: //Volcanic Eruption
{
int x = computeX(spell,card);
int _x = x;
MTGCardInstance * target = spell->getNextCardTarget();
while(target && _x){
target->destroy();
_x--;
target = spell->getNextCardTarget(target);
}
x-=_x;
for (int i = 0; i < 2 ; i++){
game->mLayers->stackLayer()->addDamage(card, game->players[i], x);
for (int j = 0; j < game->players[i]->game->inPlay->nb_cards; j++){
MTGCardInstance * current = game->players[i]->game->inPlay->cards[j];
if (current->isCreature()){
game->mLayers->stackLayer()->addDamage(card, current, x);
}
}
}
break;
}
case 1285: //Dwarven Warriors
{
CreatureTargetChooser * tc = NEW CreatureTargetChooser(card);
tc->maxpower = 2;
game->addObserver(NEW ABasicAbilityModifierUntilEOT(_id, card, Constants::UNBLOCKABLE, NULL,tc));
break;
}
case 1288: //EarthBind
{
game->addObserver(NEW AEarthbind(_id, card, card->target));
break;
}
case 1344: //Eye for an Eye
{
Damage * damage = spell->getNextDamageTarget();
if (damage){
game->mLayers->stackLayer()->addDamage(card,damage->source->controller(),damage->damage);
}
break;
}
case 1243: //Fastbond
{
game->addObserver(NEW AFastbond(_id, card));
break;
}
case 1238: //Cockatrice
{
game->addObserver(NEW AOldSchoolDeathtouch(_id,card));
break;
}
case 1362: //Reverse polarity
{
ActionStack * as = game->mLayers->stackLayer();
Player * controller = card->controller();
Damage * current = ((Damage *)as->getNext(NULL,ACTION_SPELL, RESOLVED_OK));
while(current){
if (current->target == controller && current->source->hasType("artifact")){
controller->life+= current->damage * 2;
}
current = ((Damage *)as->getNext(current,ACTION_SPELL, RESOLVED_OK));
}
break;
}
case 1225: //Stasis
{
game->addObserver(NEW AStasis(_id, card));
break;
}
case 1267: //Thicket Basilic
{
game->addObserver(NEW AOldSchoolDeathtouch(_id,card));
break;
}
case 1227: //Toughtlace
{
if (card->target){
card->target->setColor(Constants::MTG_COLOR_BLUE, 1);
}else{
Spell * starget = spell->getNextSpellTarget();
starget->source->setColor(Constants::MTG_COLOR_BLUE, 1);
}
break;
}
//Addons Legends
case 1533: //Livingplane
{
game->addObserver(NEW AConvertLandToCreatures(id, card, "land"));
break;
}
case 1480: //Energy Tap
{
card->target->tap();
int mana = card->target->getManaCost()->getConvertedCost();
game->currentlyActing()->getManaPool()->add(Constants::MTG_COLOR_ARTIFACT, mana);
}
case 1614: // Great Defender
{
int toughness = card->target->getManaCost()->getConvertedCost();
int power = 0;
game->addObserver(NEW AInstantPowerToughnessModifierUntilEOT(id, card, card->target, NEW WParsedPT(power,toughness)));
}
case 1703: //Pendelhaven
{
CreatureTargetChooser * tc = NEW CreatureTargetChooser(card);
tc->maxpower = 1;
tc->maxtoughness =1;
game->addObserver(NEW ATargetterPowerToughnessModifierUntilEOT(id, card, NEW WParsedPT(1,2), NEW ManaCost(),tc));
break;
}
//Addons ICE-AGE Cards
case 2474: //Minion of Leshrac
{
game->addObserver(NEW AMinionofLeshrac( _id, card));
break;
}
case 2421: //Shield of the Age
{
game->addObserver(NEW AShieldOfTheAge( _id, card));
break;
}
case 2435: //Whalebone Glider
{
int cost[] = {Constants::MTG_COLOR_ARTIFACT,2};
CreatureTargetChooser * tc = NEW CreatureTargetChooser(card);
tc->maxpower = 3;
game->addObserver(NEW ABasicAbilityModifierUntilEOT(_id, card, Constants::FLYING, NEW ManaCost(cost,1),tc));
break;
}
case 2393: //Aegis of the Meek work but work also for 0/1 creatures... :D
{
int cost[] = {Constants::MTG_COLOR_ARTIFACT,1};
CreatureTargetChooser * tc = NEW CreatureTargetChooser(card);
tc->maxpower = 1;
tc->maxtoughness =1;
game->addObserver(NEW ATargetterPowerToughnessModifierUntilEOT(id, card, NEW WParsedPT(1,2), NEW ManaCost(cost,1),tc));
break;
}
// --- addon Mirage ---
case 3410: //Seed of Innocence
{
GameObserver * game = GameObserver::GetInstance();
for (int i = 0; i < 2 ; i++){
for (int j = 0; j < game->players[i]->game->inPlay->nb_cards; j++){
MTGCardInstance * current = game->players[i]->game->inPlay->cards[j];
if (current->hasType("Artifact")){
game->players[i]->game->putInGraveyard(current);
current->controller()->life+= current->getManaCost()->getConvertedCost();
}
}
}
break;
}
//-- addon 10E---
case 129710: //Angelic Chorus
{
game->addObserver( NEW AAngelicChorus(_id,card));
break;
}
case 129767: //Threaten
{
game->addObserver( NEW AInstantControlSteal(_id,card,card->target));
break;
}
case 130373: //Lavaborn Muse
{
game->addObserver( NEW ALavaborn(_id ,card, Constants::MTG_PHASE_UPKEEP, -3,-3));
break;
}
case 129774: // Traumatize
{
int nbcards;
Player * player = spell->getNextPlayerTarget();
MTGLibrary * library = player->game->library;
nbcards = (library->nb_cards)/2;
for (int i = 0; i < nbcards; i++){
if (library->nb_cards)
player->game->putInZone(library->cards[library->nb_cards-1],library, player->game->graveyard);
}
break;
}
case 135215: //Sylvan Basilisk
{
game->addObserver( NEW ABasilik (_id ,card));
break;
}
case 130553:// Beacon of Immortality
{
Player * player = spell->getNextPlayerTarget();
if (player->life < (INT_MAX / 4) ) player->life += player->life;
zones->putInZone(card,spell->from,zones->library);
zones->library->shuffle();
break;
}
case 135262:// Beacon of Destruction & unrest
{
zones->putInZone(card,spell->from,zones->library);
zones->library->shuffle();
break;
}
case 129750: //Sudden Impact
{
Damageable * target = spell->getNextDamageableTarget();
Player * p = spell->getNextPlayerTarget();
MTGHand * hand = p->game->hand;
int damage = hand->nb_cards;
game->mLayers->stackLayer()->addDamage(card, target, damage);
break;
}
case 130369: // Soulblast
{
int damage = 0;
Damageable * target = spell->getNextDamageableTarget();
for (int j = card->controller()->game->inPlay->nb_cards-1; j >=0 ; --j){
MTGCardInstance * current = card->controller()->game->inPlay->cards[j];
if (current->hasType(Subtypes::TYPE_CREATURE)){
card->controller()->game->putInGraveyard(current);
damage+= current->power;
}
}
game->mLayers->stackLayer()->addDamage(card, target, damage);
break;
}
case 129698: // Reminisce
{
int nbcards;
Player * player = spell->getNextPlayerTarget();
MTGLibrary * library = player->game->library;
MTGGraveyard * graveyard = player->game->graveyard;
nbcards = (graveyard->nb_cards);
for (int i = 0; i < nbcards; i++){
if (graveyard->nb_cards)
player->game->putInZone(graveyard->cards[graveyard->nb_cards-1],graveyard, library);
}
library->shuffle();
break;
}
// --- addon Ravnica---
case 89114: //Psychic Drain
{
Player * player = spell->getNextPlayerTarget();
MTGLibrary * library = player->game->library;
int x = computeX(spell,card);
for (int i = 0; i < x; i++){
if (library->nb_cards)
player->game->putInZone(library->cards[library->nb_cards-1],library, player->game->graveyard);
}
game->currentlyActing()->life+= x;
break;
}
default:
break;
}
/* We want to get rid of these basicAbility things.
* basicAbilities themselves are alright, but creating New object depending on them is dangerous
* The main reason is that classes that add an ability to a card do NOT create these objects, and therefore do NOT
* Work.
* For example, setting EXALTED for a creature is not enough right now...
* It shouldn't be necessary to add an object. State based abilities could do the trick
*/
if (card->basicAbilities[Constants::EXALTED]){
game->addObserver(NEW AExalted(_id, card));
}
// Tested works the first r10 did not function because of the mistake in the array of the definition
if (card->basicAbilities[Constants::FORESTHOME]){
game->addObserver(NEW AStrongLandLinkCreature(_id, card, "forest"));
}
if (card->basicAbilities[Constants::ISLANDHOME]){
game->addObserver(NEW AStrongLandLinkCreature(_id, card, "island"));
}
if (card->basicAbilities[Constants::MOUNTAINHOME]){
game->addObserver(NEW AStrongLandLinkCreature(_id, card,"moutain"));
}
if (card->basicAbilities[Constants::SWAMPHOME]){
game->addObserver(NEW AStrongLandLinkCreature(_id, card,"swamp"));
}
if (card->basicAbilities[Constants::PLAINSHOME]){
game->addObserver(NEW AStrongLandLinkCreature(_id, card,"plains"));
}
if (card->hasType("instant") || card->hasType("sorcery")){
MTGPlayerCards * zones = card->controller()->game;
zones->putInZone(card,zones->stack,zones->graveyard);
}
}
MTGAbility::MTGAbility(int id, MTGCardInstance * card):ActionElement(id){
game = GameObserver::GetInstance();
source = card;
target = card;
aType = MTGAbility::UNKNOWN;
cost = NULL;
forceDestroy = 0;
oneShot = 0;
}
MTGAbility::MTGAbility(int id, MTGCardInstance * _source,Targetable * _target ):ActionElement(id){
game = GameObserver::GetInstance();
source = _source;
target = _target;
aType = MTGAbility::UNKNOWN;
cost = NULL;
forceDestroy = 0;
oneShot = 0;
}
int MTGAbility::stillInUse(MTGCardInstance * card){
if (card==source || card==target) return 1;
return 0;
}
MTGAbility::~MTGAbility(){
if (!isClone){
SAFE_DELETE(cost);
}
}
int MTGAbility::addToGame(){
GameObserver::GetInstance()->addObserver(this);
return 1;
}
int MTGAbility::removeFromGame(){
GameObserver::GetInstance()->removeObserver(this);
return 1;
}
//returns 1 if this ability needs to be removed from the list of active abilities
int MTGAbility::testDestroy(){
if (game->mLayers->stackLayer()->has(this)) return 0;
if (waitingForAnswer) return 0;
if (forceDestroy == 1) return 1;
if (forceDestroy == -1) return 0;
if (!game->isInPlay(source) ) return 1;
if (target && !game->isInPlay((MTGCardInstance *)target)) return 1;
return 0;
}
int MTGAbility::fireAbility(){
game->mLayers->stackLayer()->addAbility(this);
return 1;
}
ostream& MTGAbility::toString(ostream& out) const
{
return out << "MTGAbility ::: menuText : " << menuText
<< " ; game : " << game
<< " ; forceDestroy : " << forceDestroy
<< " ; cost : " << cost
<< " ; target : " << target
<< " ; aType : " << aType
<< " ; source : " << source;
}
//
ActivatedAbility::ActivatedAbility(int id, MTGCardInstance * card, ManaCost * _cost, int _playerturnonly,int tap):MTGAbility(id,card), playerturnonly(_playerturnonly), needsTapping(tap){
cost = _cost;
}
int ActivatedAbility::isReactingToClick(MTGCardInstance * card, ManaCost * mana){
Player * player = game->currentPlayer;
if (!playerturnonly) player = game->currentlyActing();
if (card == source && source->controller()==player && player==game->currentlyActing() && (!needsTapping || (!source->isTapped() && !source->hasSummoningSickness()))){
if (!cost) return 1;
if (!mana) mana = player->getManaPool();
if (!mana->canAfford(cost)) return 0;
return 1;
}
return 0;
}
int ActivatedAbility::reactToClick(MTGCardInstance * card){
if (!isReactingToClick(card)) return 0;
if (cost){
cost->setExtraCostsAction(this, card);
if (!cost->isExtraPaymentSet()){
game->waitForExtraPayment = cost->extraCosts;
return 0;
}
game->currentlyActing()->getManaPool()->pay(cost);
cost->doPayExtra();
}
if (needsTapping && source->isInPlay()) source->tap();
fireAbility();
return 1;
}
int ActivatedAbility::reactToTargetClick(Targetable * object){
if (!isReactingToTargetClick(object)) return 0;
if (cost){
if (object->typeAsTarget() == TARGET_CARD) cost->setExtraCostsAction(this, (MTGCardInstance *) object);
if (!cost->isExtraPaymentSet()){
game->waitForExtraPayment = cost->extraCosts;
return 0;
}
game->currentlyActing()->getManaPool()->pay(cost);
cost->doPayExtra();
}
if (needsTapping && source->isInPlay()) source->tap();
fireAbility();
return 1;
}
ostream& ActivatedAbility::toString(ostream& out) const
{
out << "ActivatedAbility ::: playerturnonly : " << playerturnonly
<< " ; needsTapping : " << needsTapping
<< " (";
return MTGAbility::toString(out) << ")";
}
//The whole targetAbility mechanism is messed up, mainly because of its interactions with
// the ActionLayer, GameObserver, and parent class ActivatedAbility.
// Currently choosing a target is a complete different mechanism for put into play and for other abilities.
// It probably shouldn't be the case.
TargetAbility::TargetAbility(int id, MTGCardInstance * card, TargetChooser * _tc,ManaCost * _cost, int _playerturnonly,int tap):ActivatedAbility(id, card,_cost,_playerturnonly, tap){
tc = _tc;
ability = NULL;
}
TargetAbility::TargetAbility(int id, MTGCardInstance * card,ManaCost * _cost, int _playerturnonly,int tap):ActivatedAbility(id, card,_cost,_playerturnonly, tap){
tc = NULL;
ability = NULL;
}
int TargetAbility::reactToTargetClick(Targetable * object){
if (object->typeAsTarget() == TARGET_CARD) return reactToClick((MTGCardInstance *)object);
if (waitingForAnswer){
if (tc->toggleTarget(object) == TARGET_OK_FULL){
waitingForAnswer = 0;
game->mLayers->actionLayer()->setCurrentWaitingAction(NULL);
return ActivatedAbility::reactToClick(source);
}
return 1;
}
return 0;
}
int TargetAbility::reactToClick(MTGCardInstance * card){
if (!waitingForAnswer) {
if (isReactingToClick(card)){
waitingForAnswer = 1;
game->mLayers->actionLayer()->setCurrentWaitingAction(this);
tc->initTargets();
return 1;
}
}else{
if (card == source && (tc->targetsReadyCheck() == TARGET_OK || tc->targetsReadyCheck() == TARGET_OK_FULL)){
waitingForAnswer = 0;
game->mLayers->actionLayer()->setCurrentWaitingAction(NULL);
return ActivatedAbility::reactToClick(source);
}else{
if (tc->toggleTarget(card) == TARGET_OK_FULL){
int result = ActivatedAbility::reactToClick(source);
if (result) {
waitingForAnswer = 0;
game->mLayers->actionLayer()->setCurrentWaitingAction(NULL);
}
return result;
}
return 1;
}
}
return 0;
}
void TargetAbility::Render(){
//TODO ?
}
int TargetAbility::resolve(){
Targetable * t = tc->getNextTarget();
if (t && ability){
ability->target = t;
return ability->resolve();
}
return 0;
}
const char * TargetAbility::getMenuText(){
if (ability) return ability->getMenuText();
return ActivatedAbility::getMenuText();
}
TargetAbility::~TargetAbility(){
if (!isClone) SAFE_DELETE(ability);
}
ostream& TargetAbility::toString(ostream& out) const
{
out << "TargetAbility ::: (";
return ActivatedAbility::toString(out) << ")";
}
//
TriggeredAbility::TriggeredAbility(int id, MTGCardInstance * card, Targetable * _target):MTGAbility(id,card, _target){
}
TriggeredAbility::TriggeredAbility(int id, MTGCardInstance * card):MTGAbility(id,card){
}
int TriggeredAbility::receiveEvent(WEvent * e){
if (triggerOnEvent(e)){
fireAbility();
return 1;
}
return 0;
}
void TriggeredAbility::Update(float dt){
if (trigger()) fireAbility();
}
ostream& TriggeredAbility::toString(ostream& out) const
{
out << "TriggeredAbility ::: (";
return MTGAbility::toString(out) << ")";
}
//
InstantAbility::InstantAbility(int _id, MTGCardInstance * source):MTGAbility(_id, source){
init = 0;
}
void InstantAbility::Update(float dt){
if (!init){
init = resolve();
}
}
InstantAbility::InstantAbility(int _id, MTGCardInstance * source, Damageable * _target):MTGAbility(_id, source, _target){
init = 0;
}
//Instant abilities last generally until the end of the turn
int InstantAbility::testDestroy(){
int newPhase = game->getCurrentGamePhase();
if (newPhase != currentPhase && newPhase == Constants::MTG_PHASE_AFTER_EOT) return 1;
currentPhase = newPhase;
return 0;
}
ostream& InstantAbility::toString(ostream& out) const
{
out << "InstantAbility ::: init : " << init
<< " (";
return MTGAbility::toString(out) << ")";
}
bool ListMaintainerAbility::canTarget(MTGGameZone * zone){
if (tc) return tc->targetsZone(zone);
for (int i = 0; i < 2; i++){
Player * p = game->players[i];
if (zone == p->game->inPlay) return true;
}
return false;
}
void ListMaintainerAbility::updateTargets(){
//remove invalid ones
map<MTGCardInstance *,bool> temp;
for (map<MTGCardInstance *,bool>::iterator it=cards.begin(); it != cards.end(); ++it){
MTGCardInstance * card = (*it).first;
if (!canBeInList(card)) temp[card] = true;
}
for (map<MTGCardInstance *,bool>::iterator it=temp.begin(); it != temp.end(); ++it){
MTGCardInstance * card = (*it).first;
cards.erase(card);
removed(card);
}
temp.clear();
//add New valid ones
for (int i = 0; i < 2; i++){
Player * p = game->players[i];
MTGGameZone * zones[] = {p->game->inPlay,p->game->graveyard,p->game->hand,p->game->library};
for (int k = 0; k < 4; k++){
MTGGameZone * zone = zones[k];
if (canTarget(zone)){
for (int j = 0; j < zone->nb_cards; j++){
if (canBeInList(zone->cards[j])){
if(cards.find(zone->cards[j]) == cards.end()){
temp[zone->cards[j]] = true;
}
}
}
}
}
}
for (map<MTGCardInstance *,bool>::iterator it=temp.begin(); it != temp.end(); ++it){
MTGCardInstance * card = (*it).first;
cards[card] = true;
added(card);
}
temp.clear();
for (int i = 0; i < 2; ++i){
Player * p = game->players[i];
if (!players[p] && canBeInList(p)){
players[p] = true;
added(p);
}else if (players[p] && !canBeInList(p)){
players[p] = false;
removed(p);
}
}
}
void ListMaintainerAbility::Update(float dt){
updateTargets();
}
//Destroy the spell -> remove all targets
int ListMaintainerAbility::destroy(){
map<MTGCardInstance *,bool>::iterator it = cards.begin();
while ( it!=cards.end()){
MTGCardInstance * card = (*it).first;
cards.erase(card);
removed(card);
it = cards.begin();
}
return 1;
}
ostream& ListMaintainerAbility::toString(ostream& out) const
{
out << "ListMaintainerAbility ::: (";
return MTGAbility::toString(out) << ")";
}
TriggerAtPhase::TriggerAtPhase(int id, MTGCardInstance * source, Targetable * target,int _phaseId, int who):TriggeredAbility(id, source,target),phaseId(_phaseId),who(who){
GameObserver * g = GameObserver::GetInstance();
if (g) {
newPhase = g->getCurrentGamePhase();
currentPhase = newPhase;
}
}
int TriggerAtPhase::trigger(){
GameObserver * g = GameObserver::GetInstance();
int result = 0;
if (currentPhase != newPhase && newPhase == phaseId){
result = 0;
switch(who){
case 1:
if(g->currentPlayer == source->controller()) result = 1;
break;
case -1:
if(g->currentPlayer != source->controller()) result = 1;
break;
case -2:
if(source->target) {
if (g->currentPlayer == source->target->controller()) result = 1;
}else {
if(g->currentPlayer == source->controller()) result = 1;
}
break;
default:
result = 1;
break;
}
}
return result;
}
TriggerAtPhase* TriggerAtPhase::clone() const{
TriggerAtPhase * a = NEW TriggerAtPhase(*this);
a->isClone = 1;
return a;
}
TriggerNextPhase::TriggerNextPhase(int id, MTGCardInstance * source, Targetable * target,int _phaseId,int who):TriggerAtPhase(id, source,target,_phaseId, who){
destroyActivated = 0;
}
int TriggerNextPhase::testDestroy(){
if (newPhase <= phaseId) destroyActivated = 1;
if ( newPhase > phaseId && destroyActivated){
return 1;
}
return 0;
}
TriggerNextPhase* TriggerNextPhase::clone() const{
TriggerNextPhase * a = NEW TriggerNextPhase(*this);
a->isClone = 1;
return a;
}
GenericTriggeredAbility::GenericTriggeredAbility(int id, MTGCardInstance * _source, TriggeredAbility * _t, MTGAbility * a , MTGAbility * dc, Targetable * _target ): TriggeredAbility(id, _source,_target){
if (!target) target = source;
t = _t;
ability = a;
destroyCondition = dc;
t->source = source;
t->target = target;
ability->source = source;
ability->target = target;
if (destroyCondition){
destroyCondition->source = source;
destroyCondition->target = target;;
}
}
int GenericTriggeredAbility::trigger(){
return t->trigger();
}
int GenericTriggeredAbility::triggerOnEvent(WEvent * e){
return t->triggerOnEvent(e);
}
void GenericTriggeredAbility::Update(float dt){
GameObserver * g = GameObserver::GetInstance();
int newPhase = g->getCurrentGamePhase();
t->newPhase = newPhase;
TriggeredAbility::Update(dt);
t->currentPhase = newPhase;
}
int GenericTriggeredAbility::resolve(){
if (ability->oneShot) return ability->resolve();
MTGAbility * clone = ability->clone();
clone->addToGame();
return 1;
}
int GenericTriggeredAbility::testDestroy(){
if (!TriggeredAbility::testDestroy()) return 0;
if (destroyCondition) return (destroyCondition->testDestroy());
return t->testDestroy();
}
GenericTriggeredAbility::~GenericTriggeredAbility(){
if (!isClone){
delete t;
delete ability;
SAFE_DELETE(destroyCondition);
}
}
const char * GenericTriggeredAbility::getMenuText(){
return ability->getMenuText();
}
GenericTriggeredAbility* GenericTriggeredAbility::clone() const{
GenericTriggeredAbility * a = NEW GenericTriggeredAbility(*this);
a->isClone = 1;
return a;
}
/*Mana Producers (lands)
//These have a reactToClick function, and therefore two manaProducers on the same card conflict with each other
//That means the player has to choose one. although that is perfect for cards such as birds of paradise or badlands,
other solutions need to be provided for abilities that add mana (ex: mana flare)
*/
AManaProducer::AManaProducer(int id, MTGCardInstance * card, Targetable * t, ManaCost * _output, ManaCost * _cost , int doTap, int who):ActivatedAbilityTP(id, card,t,_cost,doTap,who){
LOG("==Creating ManaProducer Object");
aType = MTGAbility::MANA_PRODUCER;
cost = _cost;
output = _output;
menutext = "";
LOG("==ManaProducer Object Creation successful !");
}
int AManaProducer::isReactingToClick(MTGCardInstance * _card, ManaCost * mana){
int result = 0;
if (!mana) mana = game->currentlyActing()->getManaPool();
if (_card == source && (!tap || !source->isTapped()) && game->currentlyActing()->game->inPlay->hasCard(source) && (source->hasType(Subtypes::TYPE_LAND) || !tap || !source->hasSummoningSickness()) ){
if (!cost || mana->canAfford(cost)) result = 1;
}
return result;
}
int AManaProducer::resolve(){
Targetable * _target = getTarget();
Player * player;
if (_target){
if (_target->typeAsTarget() == TARGET_CARD){
player = ((MTGCardInstance *)_target)->controller();
}else{
player = (Player *) _target;
}
player->getManaPool()->add(output,source);
return 1;
}
return 0;
}
int AManaProducer::reactToClick(MTGCardInstance * _card){
if (!isReactingToClick( _card)) return 0;
if (cost){
cost->setExtraCostsAction(this, _card);
if (!cost->isExtraPaymentSet()){
GameObserver::GetInstance()->waitForExtraPayment = cost->extraCosts;
return 0;
}
GameObserver::GetInstance()->currentlyActing()->getManaPool()->pay(cost);
cost->doPayExtra();
}
if (tap) source->tap();
if (options[Options::SFXVOLUME].number > 0){
JSample * sample = resources.RetrieveSample("mana.wav");
if (sample) JSoundSystem::GetInstance()->PlaySample(sample);
}
return resolve();
}
const char * AManaProducer::getMenuText(){
if (menutext.size())return menutext.c_str();
menutext = _("Add ");
char buffer[128];
int alreadyHasOne = 0;
for (int i= 0; i < 6; i++){
int value = output->getCost(i);
if (value){
if (alreadyHasOne) menutext.append(",");
sprintf(buffer, "%i ", value);
menutext.append(buffer);
switch (i){
case Constants::MTG_COLOR_RED:
menutext.append(_("red"));
break;
case Constants::MTG_COLOR_BLUE:
menutext.append(_("blue"));
break;
case Constants::MTG_COLOR_GREEN:
menutext.append(_("green"));
break;
case Constants::MTG_COLOR_WHITE:
menutext.append(_("white"));
break;
case Constants::MTG_COLOR_BLACK:
menutext.append(_("black"));
break;
default:
break;
}
alreadyHasOne = 1;
}
}
menutext.append(_(" mana"));
return menutext.c_str();
}
AManaProducer::~AManaProducer(){
if (isClone) return;
LOG("==Destroying ManaProducer Object");
SAFE_DELETE(cost);
SAFE_DELETE(output);
LOG("==Destroying ManaProducer Object Successful!");
}
AManaProducer * AManaProducer::clone() const{
AManaProducer * a = NEW AManaProducer(*this);
a->isClone = 1;
return a;
}
ActivatedAbilityTP::ActivatedAbilityTP(int id, MTGCardInstance * card, Targetable * _target, ManaCost * cost, int doTap, int who):ActivatedAbility(id,card,cost,0,doTap),who(who){
if (_target) target = _target;
}
Targetable * ActivatedAbilityTP::getTarget(){
switch(who){
case TargetChooser::TARGET_CONTROLLER:
if (target){
switch(target->typeAsTarget()) {
case TARGET_CARD:
return ((MTGCardInstance *)target)->controller();
case TARGET_STACKACTION:
return((Interruptible *)target)->source->controller();
default:
return (Player *)target;
}
}
return NULL;
case TargetChooser::CONTROLLER:
return source->controller();
case TargetChooser::OPPONENT:
return source->controller()->opponent();
default:
return target;
}
return NULL;
}