From 2ca03bb1f0a35b5a19a8b117b9107d1bcb2813be Mon Sep 17 00:00:00 2001 From: Vittorio Alfieri Date: Wed, 4 Aug 2021 12:33:28 +0200 Subject: [PATCH] Fixed Muxus, Goblin Grandee and fixed Liliana, the Last Hope, added conjure keyword for J21 set, added perpetual counters and abilities for J21 set, improved imprint keyword, improved moverandom keyword for J21 set. --- .../bin/Res/sets/primitives/borderline.txt | 4 +- .../bin/Res/sets/primitives/planeswalkers.txt | 2 +- projects/mtg/include/AllAbilities.h | 17 +++ projects/mtg/include/MTGDefinitions.h | 4 +- projects/mtg/src/AIPlayerBaka.cpp | 6 +- projects/mtg/src/AllAbilities.cpp | 104 +++++++++++++++++- projects/mtg/src/GuiCombat.cpp | 6 +- projects/mtg/src/MTGAbility.cpp | 36 ++++++ projects/mtg/src/MTGDefinitions.cpp | 4 +- projects/mtg/src/MTGGameZones.cpp | 10 +- projects/mtg/src/MTGRules.cpp | 4 +- 11 files changed, 178 insertions(+), 19 deletions(-) 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;