diff --git a/projects/mtg/include/AllAbilities.h b/projects/mtg/include/AllAbilities.h index 1b770e0d0..73d5a0029 100644 --- a/projects/mtg/include/AllAbilities.h +++ b/projects/mtg/include/AllAbilities.h @@ -1958,6 +1958,11 @@ public: MTGAbility * toAdd = ability->clone(); toAdd->forceDestroy = -1; toAdd->target = target; + if(toAdd->getActionTc()) + { + toAdd->reactToTargetClick(source); + return 1; + } toAdd->addToGame(); return 1; } @@ -1979,7 +1984,48 @@ public: SAFE_DELETE(ability); } }; +//generic addtogame +class GenericAddToGame: public InstantAbility, public NestedAbility +{ +public: + GenericAddToGame(GameObserver* observer, int _id, MTGCardInstance * _source, Damageable * _target, MTGAbility * ability) : + InstantAbility(observer, _id, _source,_target), NestedAbility(ability) + { + ability->target = _target; + } + int addToGame() + { + InstantAbility::addToGame(); + return 1; + } + + int resolve() + { + MTGAbility * toAdd = ability->clone(); + toAdd->target = target; + if(toAdd->getActionTc()) + return toAdd->reactToTargetClick(source); + return toAdd->addToGame(); + } + + const char * getMenuText() + { + return ability->getMenuText(); + } + + GenericAddToGame * clone() const + { + GenericAddToGame * a = NEW GenericAddToGame(*this); + a->ability = ability->clone(); + return a; + } + + ~GenericAddToGame() + { + SAFE_DELETE(ability); + } +}; //Circle of Protections class ACircleOfProtection: public TargetAbility { @@ -2233,7 +2279,7 @@ public: if (a->oneShot) { a->resolve(); - delete (a); + SAFE_DELETE(a); } else { @@ -2344,7 +2390,7 @@ public: if (a->oneShot) { a->resolve(); - delete (a); + SAFE_DELETE(a); } else { @@ -2463,8 +2509,11 @@ public: if (skills.find(card) != skills.end()) { if(!game->removeObserver(skills[card])) + { skills[card]->destroy(); - skills.erase(card); + } + if(skills[card]) + skills.erase(card); } return 1; } @@ -2489,7 +2538,7 @@ public: if (a->oneShot) { a->resolve(); - delete (a); + SAFE_DELETE(a); } else { @@ -2534,6 +2583,15 @@ public: AABlock * clone() const; }; +/* assign a creature as a pair to target */ +class PairCard: public InstantAbility +{ +public: + PairCard(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost = NULL); + int resolve(); + PairCard * clone() const; +}; + /* create a parent child association between cards */ class AAConnect: public InstantAbility { @@ -2990,6 +3048,101 @@ public: }; /// +//a paired lord +class APaired: public MTGAbility, public NestedAbility +{ +public: + MTGAbility * a; + MTGAbility * b; + APaired(GameObserver* observer, int _id, MTGCardInstance * _source, Damageable * _target, MTGAbility * ability) : + MTGAbility(observer, _id, _source, _target), NestedAbility(ability) + { + ability->source = source; + ability->target = target; + a = NULL; + } + + int removeFromGame() + { + return removeAbilityFromGame(); + } + + int addToGame() + { + return MTGAbility::addToGame(); + } + + void Update(float dt) + { + resolve(); + } + + int resolve() + { + if (source->myPair) + { + addAbilityToGame(); + } + else + { + removeAbilityFromGame(); + } + if (ability->oneShot) a = NULL; //allows to call the effect several times + return 1; + } + + int addAbilityToGame() + { + if (a && b) return 0; + a = ability->clone(); + b = ability->clone(); + a->source = source; + a->target = source; + b->source = source->myPair; + b->target = source->myPair; + if (a->oneShot) + { + a->resolve(); + b->resolve(); + SAFE_DELETE(a); + SAFE_DELETE(b); + } + else + { + a->addToGame(); + b->addToGame(); + } + return 1; + } + + int destroy() + { + return removeAbilityFromGame(); + } + + int removeAbilityFromGame() + { + if (!a && !b) return 0; + game->removeObserver(a); + a = NULL; + game->removeObserver(b); + b = NULL; + return 1; + } + + ~APaired() + { + SAFE_DELETE(ability); + } + + APaired * clone() const + { + APaired * a = NEW APaired(*this); + a->ability = ability->clone(); + return a; + } +}; + //Foreach (plague rats...) class AForeach: public ListMaintainerAbility, public NestedAbility { @@ -3028,7 +3181,7 @@ public: if (a->oneShot) { a->resolve(); - delete (a); + SAFE_DELETE(a); } else { @@ -3138,7 +3291,7 @@ public: if (a->oneShot) { a->resolve(); - delete (a); + SAFE_DELETE(a); } else { @@ -3229,7 +3382,7 @@ public: if (a->oneShot) { a->resolve(); - delete (a); + SAFE_DELETE(a); } else { diff --git a/projects/mtg/include/MTGCardInstance.h b/projects/mtg/include/MTGCardInstance.h index fbf8a2184..d839df687 100644 --- a/projects/mtg/include/MTGCardInstance.h +++ b/projects/mtg/include/MTGCardInstance.h @@ -155,6 +155,7 @@ public: int isAttacker(); Targetable * isAttacking; MTGCardInstance * storedCard; + MTGCardInstance * myPair; MTGCardInstance * createSnapShot(); MTGCardInstance * storedSourceCard; MTGCardInstance * isDefenser(); diff --git a/projects/mtg/include/MTGDefinitions.h b/projects/mtg/include/MTGDefinitions.h index 2995578a8..59a6e8f3a 100644 --- a/projects/mtg/include/MTGDefinitions.h +++ b/projects/mtg/include/MTGDefinitions.h @@ -218,8 +218,9 @@ class Constants NOMANA = 97, ONLYMANA = 98, POISONDAMAGER = 99, + soulbond = 100, - NB_BASIC_ABILITIES = 100, + NB_BASIC_ABILITIES = 101, RARITY_S = 'S', //Special Rarity diff --git a/projects/mtg/include/MTGRules.h b/projects/mtg/include/MTGRules.h index 774a1164a..4fc99b772 100644 --- a/projects/mtg/include/MTGRules.h +++ b/projects/mtg/include/MTGRules.h @@ -255,7 +255,24 @@ public: virtual MTGBlockRule * clone() const; ~MTGBlockRule(); }; - +//soulbond rule +class MTGSoulbondRule: public PermanentAbility +{ +public: + vectorsoulbonders; + TargetChooser * tcb; + MTGAbility * pairAbility; + MTGAbility * targetAbility; + MTGAbility * targetAbility1; + MTGAbility * mod; + MTGAbility * activatePairing; + vectorpairing; + MTGSoulbondRule(GameObserver* observer, int _id); + int receiveEvent(WEvent * event); + virtual ostream& toString(ostream& out) const; + virtual MTGSoulbondRule * clone() const; + ~MTGSoulbondRule(); +}; /* Persist Rule */ class MTGPersistRule: public PermanentAbility { diff --git a/projects/mtg/include/TargetChooser.h b/projects/mtg/include/TargetChooser.h index 493fb46e3..5323ddaf4 100644 --- a/projects/mtg/include/TargetChooser.h +++ b/projects/mtg/include/TargetChooser.h @@ -279,6 +279,25 @@ public: virtual bool equals(TargetChooser * tc); }; +class pairableChooser: public TypeTargetChooser +{ +public: + bool withoutProtections; + pairableChooser(GameObserver *observer, int * _zones, int _nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false, bool targetMin = false) : + TypeTargetChooser(observer, "creature|mybattlefield",_zones, _nbzones, card, _maxtargets, other, targetMin) + { + } + ; + pairableChooser(GameObserver *observer, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false,bool targetMin = false) : + TypeTargetChooser(observer, "creature|mybattlefield", card, _maxtargets, other,targetMin) + { + } + ; + virtual bool canTarget(Targetable * target, bool withoutProtections = false); + virtual pairableChooser * clone() const; + virtual bool equals(TargetChooser * tc); +}; + class myCursesChooser: public TypeTargetChooser { public: diff --git a/projects/mtg/src/AllAbilities.cpp b/projects/mtg/src/AllAbilities.cpp index 76dac9cba..03325e5b1 100644 --- a/projects/mtg/src/AllAbilities.cpp +++ b/projects/mtg/src/AllAbilities.cpp @@ -3161,9 +3161,9 @@ int MenuAbility::resolve() const char * MenuAbility::getMenuText() { - if(abilities.size()) + if((abilities.size() > 1 && must)||(abilities.size() > 2 && !must)) return "choose one"; - return ability->getMenuText(); + return "Action"; } int MenuAbility::testDestroy() @@ -3198,7 +3198,11 @@ int MenuAbility::reactToChoiceClick(Targetable * object,int choice,int control) // abilities[i]->clone();//all get cloned for clean up purposes. EDIT:removed, cause memleaks. } if(!mClone) + { + if (source->controller() == game->isInterrupting) + game->mLayers->stackLayer()->cancelInterruptOffer(ActionStack::DONT_INTERRUPT, false); return 0; + } mClone->target = abilities[choice]->target; mClone->oneShot = true; mClone->forceDestroy = 1; @@ -4853,6 +4857,32 @@ AABlock * AABlock::clone() const return NEW AABlock(*this); } +// target becomes pair of source +PairCard::PairCard(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost) : +InstantAbility(observer, id, card, target) +{ + target = _target; + oneShot = true; + forceDestroy = 1; +} + +int PairCard::resolve() +{ + MTGCardInstance * _target = (MTGCardInstance *) target; + source = (MTGCardInstance*)source; + if (_target && !_target->myPair && source) + { + source->myPair = _target; + _target->myPair = source; + } + return 1; +} + +PairCard * PairCard::clone() const +{ + return NEW PairCard(*this); +} + // target becomes a parent of card(source) AAConnect::AAConnect(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost) : diff --git a/projects/mtg/src/GameObserver.cpp b/projects/mtg/src/GameObserver.cpp index e3967a971..9eda0386f 100644 --- a/projects/mtg/src/GameObserver.cpp +++ b/projects/mtg/src/GameObserver.cpp @@ -598,6 +598,11 @@ void GameObserver::gameStateBasedEffects() card->mPropertiesChangedSinceLastUpdate = false; if(card->hasType(Subtypes::TYPE_PLANESWALKER) && (!card->counters||!card->counters->hasCounter("loyalty",0,0))) players[i]->game->putInGraveyard(card); + if(card->myPair && !isInPlay(card->myPair)) + { + card->myPair->myPair = NULL; + card->myPair = NULL; + } /////////////////////////////////////////////////////// //Remove auras that don't have a valid target anymore// /////////////////////////////////////////////////////// diff --git a/projects/mtg/src/MTGAbility.cpp b/projects/mtg/src/MTGAbility.cpp index a1e6adc9d..9c9193dee 100644 --- a/projects/mtg/src/MTGAbility.cpp +++ b/projects/mtg/src/MTGAbility.cpp @@ -910,6 +910,13 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG observer->addObserver(NEW MTGBlockRule(observer, -1)); return NULL; } + //this rule handles blocking ability during blocker phase + found = s.find("soulbondrule"); + if(found != string::npos) + { + observer->addObserver(NEW MTGSoulbondRule(observer, -1)); + return NULL; + } //this rule handles combat related triggers. note, combat related triggered abilities will not work without it. found = s.find("combattriggerrule"); if(found != string::npos) @@ -1625,6 +1632,17 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return NULL; } + //soulbond lord style ability. + found = s.find("soulbond "); + if (found != string::npos) + { + string s1 = s.substr(found + 9); + MTGAbility * a = parseMagicLine(s1, id, spell, card, false, activated); + if(a) + return NEW APaired(observer,id, card,card->myPair,a); + return NULL; + } + if (!activated && tc) { MTGAbility * a = parseMagicLine(sWithoutTc, id, spell, card); diff --git a/projects/mtg/src/MTGCardInstance.cpp b/projects/mtg/src/MTGCardInstance.cpp index fcd638037..5bada3877 100644 --- a/projects/mtg/src/MTGCardInstance.cpp +++ b/projects/mtg/src/MTGCardInstance.cpp @@ -169,6 +169,7 @@ void MTGCardInstance::initMTGCI() isAttacking = NULL; storedCard = NULL; storedSourceCard = NULL; + myPair = NULL; for (int i = 0; i < ManaCost::MANA_PAID_WITH_RETRACE +1; i++) alternateCostPaid[i] = 0; diff --git a/projects/mtg/src/MTGDefinitions.cpp b/projects/mtg/src/MTGDefinitions.cpp index 8765091dd..b4b2cd10b 100644 --- a/projects/mtg/src/MTGDefinitions.cpp +++ b/projects/mtg/src/MTGDefinitions.cpp @@ -128,7 +128,8 @@ const char* Constants::MTGBasicAbilities[] = { "notapability", "nomanaability", "onlymanaability", - "poisondamager"//deals damage to players as poison counters. + "poisondamager",//deals damage to players as poison counters. + "soulbond" }; map Constants::MTGBasicAbilitiesMap; diff --git a/projects/mtg/src/MTGRules.cpp b/projects/mtg/src/MTGRules.cpp index 947ba72bd..477316ba5 100644 --- a/projects/mtg/src/MTGRules.cpp +++ b/projects/mtg/src/MTGRules.cpp @@ -1981,6 +1981,114 @@ HUDDisplay * HUDDisplay::clone() const return NEW HUDDisplay(*this); } +/* soulbond */ +MTGSoulbondRule::MTGSoulbondRule(GameObserver* observer, int _id) : +PermanentAbility(observer, _id) +{ + tcb = NULL; + pairAbility = NULL; + targetAbility = NULL; + mod = NULL; +} +; + +int MTGSoulbondRule::receiveEvent(WEvent * event) +{ + if (event->type == WEvent::CHANGE_ZONE) + { + WEventZoneChange * e = (WEventZoneChange *) event; + MTGCardInstance * card = e->card; + if (!card || !card->isCreature()) return 0; + int ok = 0; + if(card->basicAbilities[(int)Constants::soulbond] || soulbonders.size()) + { + for (int i = 0; i < 2; i++) + { + Player * p = game->players[i]; + if (e->to == p->game->inPlay) + { + ok = 1; + if(card->basicAbilities[(int)Constants::soulbond]) + soulbonders.push_back(e->card); + } + } + if(!soulbonders.size()) + ok = 0; + else + { + MTGCardInstance * pairable = NULL; + for(unsigned int k = 0;k < soulbonders.size();k++) + { + MTGCardInstance * check = soulbonders[k]; + if(check->controller() == e->card->controller()) + { + if(!check->myPair) + { + if(check != card) + pairable = check; + else + { + MTGInPlay * zone = check->controller()->game->battlefield; + for(unsigned int d = 0;d < zone->cards.size();++d) + { + if(zone->cards[d]->isCreature() && !zone->cards[d]->myPair && zone->cards[d] != check) + { + pairable = check; + break; + } + } + } + } + } + + } + if(!pairable) + ok = 0; + } + if (!ok) + return 0; + + for (int i = 0; i < 2; i++) + { + Player * p = game->players[i]; + if (e->to == p->game->inPlay) + { + TargetChooserFactory tf(card->getObserver()); + tcb = tf.createTargetChooser("pairable",card); + tcb->targetter = NULL; + pairAbility = NEW PairCard(game, game->mLayers->actionLayer()->getMaxId(), card,NULL); + targetAbility = NEW GenericTargetAbility(game, "Pair Creature","",game->mLayers->actionLayer()->getMaxId(), card,tcb,pairAbility); + targetAbility1 = NEW MayAbility(game,game->mLayers->actionLayer()->getMaxId(),targetAbility,card,false); + activatePairing = NEW GenericAddToGame(game, game->mLayers->actionLayer()->getMaxId(), card,NULL,targetAbility1); + //this next line is ugly, but fixes a long running memleak which seems to be unfixable while maintaining the same order of activation. + game->mLayers->actionLayer()->garbage.push_back(activatePairing); + activatePairing->fireAbility(); + + return 1; + } + } + } + } + return 0; +} + +ostream& MTGSoulbondRule::toString(ostream& out) const +{ + out << "MTGSoulbondRule ::: ("; + return MTGAbility::toString(out) << ")"; +} + +MTGSoulbondRule * MTGSoulbondRule::clone() const +{ + return NEW MTGSoulbondRule(*this); +} +MTGSoulbondRule::~MTGSoulbondRule() +{ + for(size_t k = pairing.size()-1;k > 0; k--) + { + //SAFE_DELETE(pairing[k]); + } +} /* Persist */ MTGPersistRule::MTGPersistRule(GameObserver* observer, int _id) : PermanentAbility(observer, _id) diff --git a/projects/mtg/src/TargetChooser.cpp b/projects/mtg/src/TargetChooser.cpp index 6ca32747e..896d05002 100644 --- a/projects/mtg/src/TargetChooser.cpp +++ b/projects/mtg/src/TargetChooser.cpp @@ -25,6 +25,13 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta return NEW BlockableChooser(observer, card, maxtargets); } + found = s.find("pairable"); + if (found != string::npos) + { + int maxtargets = 1; + return NEW pairableChooser(observer, card, maxtargets); + } + found = s.find("mytgt"); if (found == 0) { @@ -1548,6 +1555,40 @@ bool BlockableChooser::equals(TargetChooser * tc) return TypeTargetChooser::equals(tc); } +/*display cards pairable by source */ +bool pairableChooser::canTarget(Targetable * target,bool withoutProtections) +{ + if (MTGCardInstance * card = dynamic_cast(target)) + { + if(card->myPair || card == source) + return false; + if(!card->isCreature() || !card->isInPlay(observer)) + return false; + if(card->controller() != source->controller()) + return false; + if(!card->has(Constants::soulbond) && !source->has(Constants::soulbond)) + return false; + return true; + } + return false; +} + +pairableChooser* pairableChooser::clone() const +{ + pairableChooser * a = NEW pairableChooser(*this); + return a; +} + +bool pairableChooser::equals(TargetChooser * tc) +{ + + pairableChooser * dtc = dynamic_cast (tc); + if (!dtc) + return false; + + return TypeTargetChooser::equals(tc); +} + //----------- /*Proliferate Target */ bool ProliferateChooser::canTarget(Targetable * target,bool withoutProtections)