diff --git a/projects/mtg/bin/Res/sets/10E/_cards.dat b/projects/mtg/bin/Res/sets/10E/_cards.dat index 740c45325..bab384bb6 100644 --- a/projects/mtg/bin/Res/sets/10E/_cards.dat +++ b/projects/mtg/bin/Res/sets/10E/_cards.dat @@ -175,6 +175,19 @@ type=Sorcery mana={2}{B} [/card] [card] +text=Flying (This creature can't be blocked except by creatures with flying or reach.) When Aven Cloudchaser comes into play, destroy target enchantment. +id=129470 +name=Aven Cloudchaser +auto=destroy target(enchantment) +rarity=C +color=White +type=Creature +mana={3}{W} +power=2 +subtype=Bird Soldier +toughness=2 +[/card] +[card] text=Flying (This creature can't be blocked except by creatures with flying or reach.) When Aven Fisher is put into a graveyard from play, you may draw a card. abilities=flying auto=@movedTo(this|graveyard):draw:1 diff --git a/projects/mtg/bin/Res/test/_tests.txt b/projects/mtg/bin/Res/test/_tests.txt index 7e25a912f..619d6a426 100644 --- a/projects/mtg/bin/Res/test/_tests.txt +++ b/projects/mtg/bin/Res/test/_tests.txt @@ -31,6 +31,8 @@ generic/legendary.txt generic/lifelink.txt generic/m10_blockers.txt generic/m10_blockers2.txt +generic/must1.txt +generic/must2.txt generic/nofizzle.txt generic/persist.txt generic/persist2.txt diff --git a/projects/mtg/bin/Res/test/generic/must1.txt b/projects/mtg/bin/Res/test/generic/must1.txt new file mode 100644 index 000000000..dde85c7cd --- /dev/null +++ b/projects/mtg/bin/Res/test/generic/must1.txt @@ -0,0 +1,19 @@ +#Test:"must" abilities +[INIT] +FIRSTMAIN +[PLAYER1] +manapool:{3}{W} +hand:Aven Cloudchaser +inplay:lifeforce +[PLAYER2] +[DO] +Aven cloudchaser +choice 0 +lifeforce +[ASSERT] +FIRSTMAIN +[PLAYER1] +inplay:aven cloudchaser +graveyard:lifeforce +[PLAYER2] +[END] \ No newline at end of file diff --git a/projects/mtg/bin/Res/test/generic/must2.txt b/projects/mtg/bin/Res/test/generic/must2.txt new file mode 100644 index 000000000..941cfb074 --- /dev/null +++ b/projects/mtg/bin/Res/test/generic/must2.txt @@ -0,0 +1,16 @@ +#Test:"must" abilities +[INIT] +FIRSTMAIN +[PLAYER1] +manapool:{3}{W}{1} +hand:Aven Cloudchaser,black vise +[PLAYER2] +[DO] +Aven cloudchaser +black vise +[ASSERT] +FIRSTMAIN +[PLAYER1] +inplay:aven cloudchaser,black vise +[PLAYER2] +[END] \ No newline at end of file diff --git a/projects/mtg/include/ActionLayer.h b/projects/mtg/include/ActionLayer.h index 90e95f990..bc34dce94 100644 --- a/projects/mtg/include/ActionLayer.h +++ b/projects/mtg/include/ActionLayer.h @@ -37,7 +37,7 @@ class ActionLayer: public GuiLayer, public JGuiListener{ int reactToClick(ActionElement * ability,MTGCardInstance * card); int reactToTargetClick(ActionElement * ability,Targetable * card); int stillInUse(MTGCardInstance * card); - void setMenuObject(Targetable * object); + void setMenuObject(Targetable * object, bool must = false); void ButtonPressed(int controllerid, int controlid); void doReactTo(int menuIndex); TargetChooser * getCurrentTargetChooser(); @@ -47,6 +47,7 @@ class ActionLayer: public GuiLayer, public JGuiListener{ int cleanGarbage(); protected: ActionElement * currentWaitingAction; + int cantCancel; }; diff --git a/projects/mtg/include/AllAbilities.h b/projects/mtg/include/AllAbilities.h index ec914825a..c2d77b336 100644 --- a/projects/mtg/include/AllAbilities.h +++ b/projects/mtg/include/AllAbilities.h @@ -267,9 +267,10 @@ class AAFizzler:public ActivatedAbility{ class MayAbility:public MTGAbility{ public: int triggered; + bool must; MTGAbility * ability; MTGAbility * mClone; - MayAbility(int _id, MTGAbility * _ability, MTGCardInstance * _source):MTGAbility(_id,_source),ability(_ability){ + MayAbility(int _id, MTGAbility * _ability, MTGCardInstance * _source, bool must = false):MTGAbility(_id,_source),must(must),ability(_ability){ triggered = 0; mClone = NULL; } @@ -279,7 +280,10 @@ public: MTGAbility::Update(dt); if (!triggered){ triggered = 1; - game->mLayers->actionLayer()->setMenuObject(source); + if (TargetAbility * ta = dynamic_cast(ability)){ + if (!ta->tc->validTargetsExist()) return; + } + game->mLayers->actionLayer()->setMenuObject(source,must); game->mLayers->stackLayer()->setIsInterrupting(source->controller()); OutputDebugString("ALLABILITIES SetMenuObject!\n"); } diff --git a/projects/mtg/include/TargetChooser.h b/projects/mtg/include/TargetChooser.h index f64d5286c..cd46db4af 100644 --- a/projects/mtg/include/TargetChooser.h +++ b/projects/mtg/include/TargetChooser.h @@ -42,7 +42,7 @@ class TargetChooser: public TargetsList { MTGCardInstance * targetter; //Optional, usually equals source, used for protection from... int maxtargets; //Set to -1 for "unlimited" - + bool validTargetsExist(); virtual int setAllZones(){return 0;} virtual bool targetsZone(MTGGameZone * z){return false;}; int ForceTargetListReady(); diff --git a/projects/mtg/src/ActionLayer.cpp b/projects/mtg/src/ActionLayer.cpp index 7fe1c2302..eaf4d7ab4 100644 --- a/projects/mtg/src/ActionLayer.cpp +++ b/projects/mtg/src/ActionLayer.cpp @@ -94,6 +94,14 @@ void ActionLayer::Update(float dt){ } } + if (cantCancel){ + ActionElement * ae = isWaitingForAnswer(); + if (ae && !ae->tc->validTargetsExist()) { + cantCancel = 0; + cancelCurrentAction(); + } + } + } void ActionLayer::Render (){ @@ -113,6 +121,7 @@ void ActionLayer::Render (){ void ActionLayer::setCurrentWaitingAction(ActionElement * ae){ assert(!ae || !currentWaitingAction); currentWaitingAction = ae; + if (!ae) cantCancel = 0; } TargetChooser * ActionLayer::getCurrentTargetChooser(){ @@ -124,6 +133,7 @@ TargetChooser * ActionLayer::getCurrentTargetChooser(){ int ActionLayer::cancelCurrentAction(){ ActionElement * ae = isWaitingForAnswer(); if (!ae) return 0; + if (cantCancel && ae->tc->validTargetsExist()) return 0; ae->waitingForAnswer = 0; //TODO MOVE THIS IN ActionElement setCurrentWaitingAction(NULL); return 1; @@ -206,12 +216,12 @@ int ActionLayer::reactToClick(MTGCardInstance * card){ } -void ActionLayer::setMenuObject(Targetable * object){ +void ActionLayer::setMenuObject(Targetable * object, bool must){ menuObject = object; SAFE_DELETE(abilitiesMenu); - abilitiesMenu = NEW SimpleMenu(10, this, Constants::MAIN_FONT, 100, 100); + abilitiesMenu = NEW SimpleMenu(10, this, Constants::MAIN_FONT, 100, 100,object->getDisplayName().c_str()); for (int i=0;iAdd(i,currentAction->getMenuText()); } } - abilitiesMenu->Add(-1, "Cancel"); + if (!must) abilitiesMenu->Add(-1, "Cancel"); + else cantCancel = 1; modal = 1; } @@ -252,6 +263,7 @@ ActionLayer::ActionLayer(){ abilitiesMenu = NULL; stuffHappened = 0; currentWaitingAction = NULL; + cantCancel = 0; } ActionLayer::~ActionLayer(){ diff --git a/projects/mtg/src/MTGAbility.cpp b/projects/mtg/src/MTGAbility.cpp index 906b3c702..43d8e932b 100644 --- a/projects/mtg/src/MTGAbility.cpp +++ b/projects/mtg/src/MTGAbility.cpp @@ -206,17 +206,35 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG int restrictions = parseRestriction(s); - 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); + TargetChooser * tc = NULL; + string sWithoutTc = s; + //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 && s.find("notatarget(") != string::npos){ + tc->targetter = NULL; + found = found - 4; + } + string temp = s.substr(0,found); + temp.append(s.substr(end+1)); + sWithoutTc = temp; + } + + size_t delimiter = sWithoutTc.find("}:"); + size_t firstNonSpace = sWithoutTc.find_first_not_of(" "); + if (delimiter!= string::npos && firstNonSpace !=string::npos && sWithoutTc[firstNonSpace] == '{'){ + ManaCost * cost = ManaCost::parseManaCost(sWithoutTc.substr(0,delimiter+1),NULL,card); if (doTap || cost){ - string s1 = s.substr(delimiter+2); + string s1 = sWithoutTc.substr(delimiter+2); MTGAbility * a = parseMagicLine(s1, id, spell, card, 1); if (!a){ OutputDebugString("ABILITYFACTORY Error parsing:"); - OutputDebugString(s.c_str()); + OutputDebugString(sWithoutTc.c_str()); OutputDebugString("\n"); return NULL; } @@ -231,21 +249,12 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } int limit = 0; - unsigned int limit_str = s.find("limit:"); + unsigned int limit_str = sWithoutTc.find("limit:"); if (limit_str != string::npos){ - limit = atoi(s.substr(limit_str+6).c_str()); + limit = atoi(sWithoutTc.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 && s.find("notatarget(") != string::npos) tc->targetter = NULL; - } + AEquip *ae = dynamic_cast(a); if (ae){ @@ -263,7 +272,8 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } SAFE_DELETE(cost); } - + + //kicker cost found = s.find("kicker "); if (found == 0){ @@ -276,28 +286,20 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG //When...comes into play, you may... found = s.find("may "); if (found == 0){ - string s1 = s.substr(found+4); + string s1 = sWithoutTc.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 && s.find("notatarget(") != string::npos) tc->targetter = NULL; - } + 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){ + SAFE_DELETE(tc); string s1 = s.substr(0,found); string s2 = s.substr(found+2); MultiAbility * multi = NEW MultiAbility(id, card,target,NULL,NULL); @@ -321,6 +323,7 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } } if (found != string::npos){ + SAFE_DELETE(tc); size_t header = lords[i].size(); size_t end = s.find(")", found+header); string s1; @@ -381,6 +384,21 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return NULL; } + if (!activated && tc){ + + MTGAbility * a = parseMagicLine(sWithoutTc, id, spell, card); + if (!a){ + OutputDebugString("ABILITYFACTORY Error parsing:"); + OutputDebugString(s.c_str()); + OutputDebugString("\n"); + return NULL; + } + a = NEW GenericTargetAbility(id,card,tc,a); + return NEW MayAbility(id,a,card,true); + } + + SAFE_DELETE(tc); + //Cycling found = s.find("cycling"); if (found != string::npos){ @@ -472,7 +490,7 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } //Copy a target - found = s.find("copy "); + found = s.find("copy"); if (found != string::npos){ MTGAbility * a = NEW AACopier(id,card,target); a->oneShot = 1; diff --git a/projects/mtg/src/TargetChooser.cpp b/projects/mtg/src/TargetChooser.cpp index f3e53a194..538620261 100644 --- a/projects/mtg/src/TargetChooser.cpp +++ b/projects/mtg/src/TargetChooser.cpp @@ -386,6 +386,23 @@ int TargetChooser::targetListSet(){ return 0; } +bool TargetChooser::validTargetsExist(){ + for (int i = 0; i < 2; ++i){ + Player *p = GameObserver::GetInstance()->players[i]; + if (canTarget(p)) return true; + MTGGameZone * zones[] = {p->game->inPlay,p->game->graveyard,p->game->hand,p->game->library}; + for (int k = 0; k < 4; k++){ + MTGGameZone * z = zones[k]; + if (targetsZone(z)){ + for (int j = 0; j < z->nb_cards; j++){ + if (canTarget(z->cards[j])) return true; + } + } + } + } + return false; +} + /** a specific Card **/