diff --git a/projects/mtg/bin/Res/sets/primitives/borderline.txt b/projects/mtg/bin/Res/sets/primitives/borderline.txt index 643351b0f..2210f6bed 100644 --- a/projects/mtg/bin/Res/sets/primitives/borderline.txt +++ b/projects/mtg/bin/Res/sets/primitives/borderline.txt @@ -40978,8 +40978,8 @@ type=Sorcery [/card] [card] name=Muxus, Goblin Grandee -aicode=activate moveto(mybattlefield) target(goblin[manacost<=5;zpos<=6]|mylibrary) -auto=reveal:6 optionone target(goblin[manacost<=5]|reveal) moveto(ownerlibrary) and!( becomes(tobecast) ueot )! optiontwo name(put back) target(<1>*|reveal) moveto(ownerlibrary) and!( all(*|reveal) bottomoflibrary and!(shuffle)! )! optiontwoend afterrevealed all(tobecast|mylibrary) moveto(ownerlibrary) and!( activate castcard(normal) )! afterrevealedend revealend +aicode=activate all(creature[goblin;manacost<=5;zpos<=6]|mylibrary) moveto(mybattlefield) and!( all(*[zpos<=6]|mylibrary) bottomoflibrary )! +auto=name(Look top 6) reveal:6 optionone name(Get goblins) target(<6>creature[goblin;manacost<=5]|reveal) moveto(ownerlibrary) and!( becomes(tobecast) ueot )! optiononeend optiontwo name(put back) target(<1>*|reveal) bottomoflibrary and!( all(*|reveal) bottomoflibrary )! optiontwoend afterrevealed all(tobecast|mylibrary) moveto(ownerlibrary) and!(moveto(ownerbattlefield))! afterrevealedend revealend text=When Muxus, Goblin Grandee enters the battlefield, reveal the top six cards of your library. Put all Goblin creature cards with converted mana cost 5 or less from among them onto the battlefield and the rest on the bottom of your library in a random order. -- Whenever Muxus attacks, it gets +1/+1 until end of turn for each other Goblin you control. auto=_ATTACKING_all(this) foreach(other goblin[attacking]|myBattlefield) 1/1 ueot mana={4}{R}{R} diff --git a/projects/mtg/bin/Res/sets/primitives/planeswalkers.txt b/projects/mtg/bin/Res/sets/primitives/planeswalkers.txt index 87d6c133e..f252337ee 100644 --- a/projects/mtg/bin/Res/sets/primitives/planeswalkers.txt +++ b/projects/mtg/bin/Res/sets/primitives/planeswalkers.txt @@ -1679,7 +1679,7 @@ auto=counter(0/0,3,Loyalty) auto={C(0/0,1,Loyalty)}:name(+1: Do not target any creature) donothing auto={C(0/0,1,Loyalty)}:name(+1: Target creature gets -2/-1) target(creature) -2/-1 uynt auto={C(0/0,-2,Loyalty)}:name(-2: Mils two and return a creature) deplete:2 controller && ability$!name(Move to hand) moveto(myhand) target(creature|mygraveyard)!$ controller -auto={C(0/0,-7,Loyalty)}:name(-7: Emblem: "Each my end of turn create X 2/2 Zombie plus two) emblem transforms((,newability[phaseactionmulti[my endofturn] foreach(zombie|mybattlefield) token(Zombie Lil) && token(Zombie Lil)*2])) forever dontremove +auto={C(0/0,-7,Loyalty)}:name(-7: Emblem: "Each my end of turn create X 2/2 Zombie plus two") emblem transforms((,newability[phaseactionmulti[my endofturn] foreach(zombie|mybattlefield) token(Zombie Lil) && token(Zombie Lil)*2])) forever dontremove text=+1: Up to one target creature gets -2/-1 until your next turn. -- -2: Put the top two cards of your library into your graveyard, then you may return a creature card from your graveyard to your hand. -- -7: You get an emblem with "At the beginning of your end step, put X 2/2 black Zombie creature tokens onto the battlefield, where X is two plus the number of Zombies you control." mana={1}{B}{B} type=Legendary Planeswalker diff --git a/projects/mtg/include/AllAbilities.h b/projects/mtg/include/AllAbilities.h index 5cdfbe6d5..47a4a65ff 100644 --- a/projects/mtg/include/AllAbilities.h +++ b/projects/mtg/include/AllAbilities.h @@ -1512,6 +1512,22 @@ public: int resolve(); const string getMenuText(); AAImprint * clone() const; + ~AAImprint(); +}; +//AAConjure +class AAConjure: public ActivatedAbility +{ +public: + MTGAbility * andAbility; + string cardNamed; + string cardZone; + MTGCardInstance * theNamedCard; + AAConjure(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, ManaCost * _cost, string _namedCard, string _cardZone); + MTGCardInstance * makeCard(); + int resolve(); + const string getMenuText(); + AAConjure * clone() const; + ~AAConjure(); }; //AAForetell class AAForetell: public ActivatedAbility @@ -1569,6 +1585,7 @@ public: class AARandomMover: public ActivatedAbility { public: + MTGAbility * andAbility; string abilityTC; string fromZone; string toZone; diff --git a/projects/mtg/include/MTGDefinitions.h b/projects/mtg/include/MTGDefinitions.h index 321d16500..c36256e41 100644 --- a/projects/mtg/include/MTGDefinitions.h +++ b/projects/mtg/include/MTGDefinitions.h @@ -314,7 +314,9 @@ class Constants CANPLAYAURAEQUIPTOPLIBRARY = 187,//aurasequipment COUNTERDEATH = 188, DUNGEONCOMPLETED = 189, - NB_BASIC_ABILITIES = 190, + PERPETUALLIFELINK = 190, + PERPETUALDEATHTOUCH = 191, + NB_BASIC_ABILITIES = 192, RARITY_S = 'S', //Special Rarity RARITY_M = 'M', //Mythics diff --git a/projects/mtg/src/AIPlayerBaka.cpp b/projects/mtg/src/AIPlayerBaka.cpp index b0dd68892..e5eaaaf60 100644 --- a/projects/mtg/src/AIPlayerBaka.cpp +++ b/projects/mtg/src/AIPlayerBaka.cpp @@ -149,8 +149,7 @@ int OrderedAIAction::getEfficiency() break; if ((target->defenser || target->blockers.size()) && target->preventable < nextOpponent->power) NeedPreventing = true; - if (p == target->controller() && target->controller()->isAI() && NeedPreventing && !(nextOpponent->has(Constants::DEATHTOUCH) - ||nextOpponent->has(Constants::WITHER))) + if (p == target->controller() && target->controller()->isAI() && NeedPreventing && !(nextOpponent->has(Constants::DEATHTOUCH) || nextOpponent->has(Constants::PERPETUALDEATHTOUCH) || nextOpponent->has(Constants::WITHER))) { efficiency = 20 * (target->DangerRanking());//increase this chance to be used in combat if the creature blocking/blocked could kill the creature this chance is taking into consideration how good the creature is, best creature will always be the first "saved".. if (target->toughness == 1 && nextOpponent->power == 1) @@ -824,8 +823,7 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) break; if ((target->defenser || target->blockers.size()) && target->preventable < nextOpponent->power) NeedPreventing = true; - if (p == target->controller() && target->controller()->isAI() && NeedPreventing && !(nextOpponent->has(Constants::DEATHTOUCH) - ||nextOpponent->has(Constants::WITHER))) + if (p == target->controller() && target->controller()->isAI() && NeedPreventing && !(nextOpponent->has(Constants::DEATHTOUCH) || nextOpponent->has(Constants::PERPETUALDEATHTOUCH) || nextOpponent->has(Constants::WITHER))) { eff2 = 20 * (target->DangerRanking());//increase this chance to be used in combat if the creature blocking/blocked could kill the creature this chance is taking into consideration how good the creature is, best creature will always be the first "saved".. if (target->toughness == 1 && nextOpponent->power == 1) diff --git a/projects/mtg/src/AllAbilities.cpp b/projects/mtg/src/AllAbilities.cpp index bf96d959e..da63dc697 100644 --- a/projects/mtg/src/AllAbilities.cpp +++ b/projects/mtg/src/AllAbilities.cpp @@ -2270,6 +2270,82 @@ AAImprint * AAImprint::clone() const return NEW AAImprint(*this); } +AAImprint::~AAImprint() +{ + SAFE_DELETE(andAbility); +} + +//AAConjure +AAConjure::AAConjure(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, ManaCost * _cost, string _namedCard, string _cardZone) : + ActivatedAbility(observer, _id, _source, _cost, 0),cardNamed(_namedCard),cardZone(_cardZone) +{ + target = _target; + andAbility = NULL; + theNamedCard = NULL; +} + +MTGCardInstance * AAConjure::makeCard() +{ + string newName = cardNamed; + if(newName.find(";")){//if it's a list of cards we choose one randomly (e.g. Tome of the Infinite) + vector names = split(newName, ';'); + newName = names.at(std::rand() % names.size()); + } + MTGCardInstance * card = NULL; + MTGCard * cardData = MTGCollection()->getCardByName(newName); + if(!cardData) return NULL; + card = NEW MTGCardInstance(cardData, source->controller()->game); + card->owner = source->controller(); + card->lastController = source->controller(); + source->controller()->game->sideboard->addCard(card); + return card; +} + +int AAConjure::resolve() +{ + MTGCardInstance * _target = (MTGCardInstance *) target; + if (_target) + { + if(_target->mutation && _target->parentCards.size() > 0) return 0; // Mutated down cards cannot be conjured, they will follow the fate of top-card + theNamedCard = makeCard(); + if(theNamedCard){ + MTGCardInstance * copy = source->controller()->game->putInZone(theNamedCard, theNamedCard->currentZone, MTGGameZone::stringToZone(game, cardZone, theNamedCard, NULL)); + if(andAbility) + { + MTGAbility * andAbilityClone = andAbility->clone(); + andAbilityClone->target = copy; + if(andAbility->oneShot) + { + andAbilityClone->resolve(); + SAFE_DELETE(andAbilityClone); + } + else + { + andAbilityClone->addToGame(); + } + } + } + this->forceDestroy = true; + return 1; + } + return 0; +} + +const string AAConjure::getMenuText() +{ + return "Conjure"; +} + +AAConjure * AAConjure::clone() const +{ + return NEW AAConjure(*this); +} + +AAConjure::~AAConjure() +{ + SAFE_DELETE(andAbility); +} + //AAForetell AAForetell::AAForetell(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, ManaCost * _cost) : ActivatedAbility(observer, _id, _source, _cost, 0) @@ -5685,7 +5761,6 @@ AInstantCastRestrictionUEOT::~AInstantCastRestrictionUEOT() SAFE_DELETE(ability); } - //AAMover AAMover::AAMover(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, string dest,string newName, ManaCost * _cost, bool undying, bool persist) : ActivatedAbility(observer, _id, _source, _cost, 0), destination(dest),named(newName),undying(undying),persist(persist) @@ -5943,6 +6018,7 @@ AARandomMover::AARandomMover(GameObserver* observer, int _id, MTGCardInstance * { if (_target) target = _target; + andAbility = NULL; } MTGGameZone * AARandomMover::destinationZone(Targetable * target,string zone) @@ -5996,7 +6072,24 @@ int AARandomMover::resolve() return 1; } } - p->game->putInZone(toMove, fromDest, toDest); + MTGCardInstance *newTarget = p->game->putInZone(toMove, fromDest, toDest); + if(newTarget) + { + if(andAbility) + { + MTGAbility * andAbilityClone = andAbility->clone(); + andAbilityClone->target = newTarget; + if(andAbility->oneShot) + { + andAbilityClone->resolve(); + SAFE_DELETE(andAbilityClone); + } + else + { + andAbilityClone->addToGame(); + } + } + } return 1; } } @@ -6016,6 +6109,7 @@ AARandomMover * AARandomMover::clone() const AARandomMover::~AARandomMover() { + SAFE_DELETE(andAbility); } //Random Discard @@ -9401,6 +9495,12 @@ void AACastCard::Update(float dt) return; if(cardNamed.size() && !theNamedCard) { + if(cardNamed.find("randomcard") != string::npos){ //cast a random card from collection. + MTGCard *rndCard = NULL; + while(!rndCard || rndCard->data->isLand()) + rndCard = MTGCollection()->getCardById(MTGCollection()->ids.at(std::rand() % (MTGCollection()->ids).size())); + cardNamed = rndCard->data->name; + } if (cardNamed.find("imprintedcard") != string::npos) { if (source && source->currentimprintName.size()) diff --git a/projects/mtg/src/GuiCombat.cpp b/projects/mtg/src/GuiCombat.cpp index 897067b48..7b8bf04d5 100644 --- a/projects/mtg/src/GuiCombat.cpp +++ b/projects/mtg/src/GuiCombat.cpp @@ -131,7 +131,7 @@ void GuiCombat::autoaffectDamage(AttackerDamaged* attacker, CombatStep step) { (*it)->clearDamage(); unsigned actual_damage = MIN(damage, (unsigned)MAX((*it)->card->toughness, 0)); - if (attacker->card->has(Constants::DEATHTOUCH) && actual_damage > 1) + if ((attacker->card->has(Constants::DEATHTOUCH) || attacker->card->has(Constants::PERPETUALDEATHTOUCH)) && actual_damage > 1) actual_damage = 1; (*it)->addDamage(actual_damage, attacker); attacker->addDamage((*it)->card->stepPower(step), *it); @@ -159,7 +159,7 @@ void GuiCombat::removeOne(DefenserDamaged* blocker, CombatStep) { blocker->addDamage(-1, activeAtk); for (vector::iterator it = activeAtk->blockers.begin(); it != activeAtk->blockers.end(); ++it) - if (activeAtk->card->has(Constants::DEATHTOUCH) ? ((*it)->sumDamages() < 1) : (!(*it)->hasLethalDamage())) + if ((activeAtk->card->has(Constants::DEATHTOUCH) || activeAtk->card->has(Constants::PERPETUALDEATHTOUCH)) ? ((*it)->sumDamages() < 1) : (!(*it)->hasLethalDamage())) { (*it)->addDamage(1, activeAtk); return; @@ -363,7 +363,7 @@ bool GuiCombat::CheckUserInput(JButton key) damage -= now; if (damage > 0) addOne(active, step); - else if (activeAtk->card->has(Constants::DEATHTOUCH)) + else if (activeAtk->card->has(Constants::DEATHTOUCH) || activeAtk->card->has(Constants::PERPETUALDEATHTOUCH)) for (; now >= 1; --now) removeOne(active, step); else diff --git a/projects/mtg/src/MTGAbility.cpp b/projects/mtg/src/MTGAbility.cpp index a0f1041e3..22d912962 100644 --- a/projects/mtg/src/MTGAbility.cpp +++ b/projects/mtg/src/MTGAbility.cpp @@ -3269,6 +3269,12 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return NULL; MTGAbility * a = NEW AARandomMover(observer, id, card, target, splitRandomMove[1],splitfrom[1],splitto[1]); a->oneShot = true; + if(storedAndAbility.size()) + { + string stored = storedAndAbility; + storedAndAbility.clear(); + ((AARandomMover*)a)->andAbility = parseMagicLine(stored, id, spell, card); + } return a; } @@ -3349,6 +3355,36 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } } + //Conjure a card + found = s.find("conjure"); + if (found != string::npos) + { + replace(s.begin(), s.end(), '^', ','); // To allow the usage of ^ instead of , char (e.g. using conjure keyword inside transforms) + string cardName = ""; + vector splitCard = parseBetween(s, "cards(", ")"); + if (splitCard.size()) + { + cardName = splitCard[1]; + } + string cardZone = ""; + vector splitZone = parseBetween(s, "zone(", ")"); + if (splitZone.size()) + { + cardZone = splitZone[1]; + } + MTGAbility * a = NEW AAConjure(observer, id, card, target, NULL, cardName, cardZone); + a->oneShot = 1; + a->canBeInterrupted = false; + //andability + if(storedAndAbility.size()) + { + string stored = storedAndAbility; + storedAndAbility.clear(); + ((AAConjure*)a)->andAbility = parseMagicLine(stored, id, spell, card); + } + return a; + } + //foretell found = s.find("doforetell"); if (found != string::npos) diff --git a/projects/mtg/src/MTGDefinitions.cpp b/projects/mtg/src/MTGDefinitions.cpp index 0dfcdab01..a4df78c72 100644 --- a/projects/mtg/src/MTGDefinitions.cpp +++ b/projects/mtg/src/MTGDefinitions.cpp @@ -220,7 +220,9 @@ const char* Constants::MTGBasicAbilities[] = { "nodngplr", //Controller can't venture "canplayauraequiplibrarytop", //auras and equipment "counterdeath", //It gains a 1/1 counter when it returns from graveyard (to use with inplaydeath and inplaytapdeath)" - "dungeoncompleted" //This dungeon has been completed + "dungeoncompleted", //This dungeon has been completed + "perpetuallifelink", //It gains lifelink perpetually + "perpetualdeathtouch" //It gains deathtouch perpetually }; map Constants::MTGBasicAbilitiesMap; diff --git a/projects/mtg/src/MTGGameZones.cpp b/projects/mtg/src/MTGGameZones.cpp index 4f6e9b55e..b38d0175d 100644 --- a/projects/mtg/src/MTGGameZones.cpp +++ b/projects/mtg/src/MTGGameZones.cpp @@ -726,12 +726,14 @@ MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone WEvent * e = NEW WEventZoneChange(copy, from, to); g->receiveEvent(e); - // Erasing counters from copy after the event has been triggered (no counter can survive to a zone changing) + // Erasing counters from copy after the event has been triggered (no counter can survive to a zone changing except the perpetual ones) if(doCopy && copy->counters && copy->counters->mCount > 0){ for (unsigned int i = 0; i < copy->counters->counters.size(); i++){ Counter * counter = copy->counters->counters[i]; - for(int j = counter->nb; j > 0; j--) - copy->counters->removeCounter(counter->name.c_str(), counter->power, counter->toughness, true); + for(int j = counter->nb; j > 0; j--){ + if(counter->name.find("perpetual") == string::npos) + copy->counters->removeCounter(counter->name.c_str(), counter->power, counter->toughness, true); + } } } } @@ -863,6 +865,8 @@ MTGCardInstance * MTGGameZone::removeCard(MTGCardInstance * card, int createCopy copy->basicAbilities[Constants::GAINEDHANDDEATH] = card->basicAbilities[Constants::GAINEDHANDDEATH]; copy->basicAbilities[Constants::GAINEDDOUBLEFACEDEATH] = card->basicAbilities[Constants::GAINEDDOUBLEFACEDEATH]; copy->basicAbilities[Constants::DUNGEONCOMPLETED] = card->basicAbilities[Constants::DUNGEONCOMPLETED]; + copy->basicAbilities[Constants::PERPETUALDEATHTOUCH] = card->basicAbilities[Constants::PERPETUALDEATHTOUCH]; + copy->basicAbilities[Constants::PERPETUALLIFELINK] = card->basicAbilities[Constants::PERPETUALLIFELINK]; copy->damageInflictedAsCommander = card->damageInflictedAsCommander; copy->numofcastfromcommandzone = card->numofcastfromcommandzone; for (int i = 0; i < ManaCost::MANA_PAID_WITH_BESTOW +1; i++) diff --git a/projects/mtg/src/MTGRules.cpp b/projects/mtg/src/MTGRules.cpp index bfc3e41ab..080dc3bd8 100644 --- a/projects/mtg/src/MTGRules.cpp +++ b/projects/mtg/src/MTGRules.cpp @@ -3982,7 +3982,7 @@ int MTGLifelinkRule::receiveEvent(WEvent * event) WEventDamage * e = (WEventDamage *) event; Damage * d = e->damage; MTGCardInstance * card = d->source; - if (d->damage > 0 && card && (card->basicAbilities[(int)Constants::LIFELINK]||card->LKIbasicAbilities[(int)Constants::LIFELINK])) + if (d->damage > 0 && card && (card->basicAbilities[(int)Constants::LIFELINK]||card->basicAbilities[(int)Constants::PERPETUALLIFELINK]||card->LKIbasicAbilities[(int)Constants::LIFELINK]||card->LKIbasicAbilities[(int)Constants::PERPETUALLIFELINK])) { card->controller()->gainLife(d->damage, card); return 1; @@ -4026,7 +4026,7 @@ int MTGDeathtouchRule::receiveEvent(WEvent * event) return 0; MTGCardInstance * _target = (MTGCardInstance *) (d->target); - if (card->basicAbilities[(int)Constants::DEATHTOUCH]||card->LKIbasicAbilities[(int)Constants::DEATHTOUCH]) + if (card->basicAbilities[(int)Constants::DEATHTOUCH]||card->basicAbilities[(int)Constants::PERPETUALDEATHTOUCH]||card->LKIbasicAbilities[(int)Constants::DEATHTOUCH]||card->LKIbasicAbilities[(int)Constants::PERPETUALDEATHTOUCH]) { _target->destroy(); return 1;