diff --git a/projects/mtg/include/AllAbilities.h b/projects/mtg/include/AllAbilities.h index 044d19b96..c4a50f859 100644 --- a/projects/mtg/include/AllAbilities.h +++ b/projects/mtg/include/AllAbilities.h @@ -19,6 +19,7 @@ #include #include #include "IconButton.h" +#include "ExtraCost.h" #include using std::map; @@ -1102,7 +1103,8 @@ public: class AAFakeAbility: public ActivatedAbility { public: - AAFakeAbility(GameObserver* observer, int id, MTGCardInstance * source, MTGCardInstance * target, ManaCost * cost = NULL); + string named; + AAFakeAbility(GameObserver* observer, int id, MTGCardInstance * source, MTGCardInstance * target,string _newName, ManaCost * cost = NULL); int resolve(); const char* getMenuText(); AAFakeAbility * clone() const; @@ -1157,7 +1159,6 @@ public: string Cond; Player * previousInterrupter; MTGAbility * mClone; - ManaCost * optionalCost; MayAbility(GameObserver* observer, int _id, MTGAbility * _ability, MTGCardInstance * _source, bool must = false, string restriction = ""); @@ -1182,11 +1183,15 @@ public: int triggered; bool removeMenu; bool must; + bool processed; MTGAbility * mClone; + ManaCost * toPay; vectorabilities; + vectoroptionalCosts; Player * who; string newNameString; MenuAbility(GameObserver* observer, int _id, Targetable * target, MTGCardInstance * _source, bool must = false, vectorabilities = vector(),Player * who = NULL,string _newName = ""); + bool CheckUserInput(JButton key); void Update(float dt); int resolve(); const char * getMenuText(); @@ -1194,6 +1199,7 @@ public: int isReactingToTargetClick(Targetable * card); int reactToTargetClick(Targetable * object); int reactToChoiceClick(Targetable * object,int choice,int control); + int processAbility(); MenuAbility * clone() const; ~MenuAbility(); @@ -1301,7 +1307,8 @@ class AAMover: public ActivatedAbility public: string destination; MTGAbility * andAbility; - AAMover(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, string dest, ManaCost * _cost = NULL); + string named; + AAMover(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, string dest,string _name, ManaCost * _cost = NULL); MTGGameZone * destinationZone(Targetable * target = NULL); int resolve(); const char * getMenuText(); @@ -1331,6 +1338,7 @@ class AABuryCard: public ActivatedAbility { public: MTGAbility * andAbility; + string menu; AABuryCard(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target); int resolve(); const char * getMenuText(); @@ -5569,6 +5577,32 @@ public: } }; +class AACastCard: public MTGAbility +{ +public: + MTGAbility * andAbility; + bool processed; + bool restricted; + bool asCopy; + bool normal; + string cardNamed; + string nameThis; + MTGCardInstance * theNamedCard; + bool noEvent; + AACastCard(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target,bool restricted,bool copied,bool _asNormal,string nameCard,string abilityName,bool _noEvent); + + int testDestroy(){return 0;}; + void Update(float dt); + const char * getMenuText(); + int isReactingToTargetClick(Targetable * card); + int reactToTargetClick(Targetable * object); + MTGCardInstance * makeCard(); + int resolveSpell(); + + AACastCard * clone() const; + ~AACastCard(); +}; + //A Spirit Link Ability class ASpiritLinkAbility: public MTGAbility { @@ -5736,6 +5770,25 @@ public: GenericFlipACoin * clone() const; ~GenericFlipACoin(); +}; +//------------ +class GenericPaidAbility: public ActivatedAbility +{ +public: + MTGAbility * baseAbility; + ManaCost * optionalCost; + + string newName; + string restrictions; + string baseCost; + string baseAbilityStr; + + GenericPaidAbility(GameObserver* observer, int id, MTGCardInstance * source, Targetable * target,string _newName,string _castRestriction,string _mayCost, string toAdd, ManaCost * cost = NULL); + int resolve(); + const char* getMenuText(); + GenericPaidAbility * clone() const; + ~GenericPaidAbility(); + }; // utility functions diff --git a/projects/mtg/include/ExtraCost.h b/projects/mtg/include/ExtraCost.h index c0db8f3f4..e08f5939f 100644 --- a/projects/mtg/include/ExtraCost.h +++ b/projects/mtg/include/ExtraCost.h @@ -4,6 +4,7 @@ #include #include "Counters.h" #include "ObjectAnalytics.h" +#include "ManaCost.h" using std::vector; @@ -18,11 +19,12 @@ class ExtraCost { public: TargetChooser * tc; + ManaCost * costToPay; MTGCardInstance * source; MTGCardInstance * target; std::string mCostRenderString; - ExtraCost(const std::string& inCostRenderString, TargetChooser *_tc = NULL); + ExtraCost(const std::string& inCostRenderString, TargetChooser *_tc = NULL,ManaCost * _costToPay = NULL); virtual ~ExtraCost(); virtual int setPayment(MTGCardInstance * card); @@ -58,6 +60,18 @@ public: ExtraCosts * clone() const; }; +//extraextra +class extraManaCost : public ExtraCost +{ +public: + extraManaCost(ManaCost * cost = NULL); + virtual int tryToSetPayment(MTGCardInstance * card); + virtual int isPaymentSet(); + virtual int canPay(); + virtual int doPay(); + virtual extraManaCost * clone() const; +}; + class SacrificeCost : public ExtraCost { public: diff --git a/projects/mtg/include/MTGAbility.h b/projects/mtg/include/MTGAbility.h index 018b602bf..71e04fd07 100644 --- a/projects/mtg/include/MTGAbility.h +++ b/projects/mtg/include/MTGAbility.h @@ -444,6 +444,7 @@ public: class AbilityFactory { private: + string storedPayString; string storedString; string storedAbilityString; string storedAndAbility; diff --git a/projects/mtg/include/MTGCardInstance.h b/projects/mtg/include/MTGCardInstance.h index d466a1ebb..7317c4142 100644 --- a/projects/mtg/include/MTGCardInstance.h +++ b/projects/mtg/include/MTGCardInstance.h @@ -115,12 +115,11 @@ public: ManaCost increasedCost; ManaCost * getReducedManaCost(); ManaCost * getIncreasedManaCost(); - bool matchesCastFilter(int castMethod); // The recommended method to test for summoning Sickness ! int hasSummoningSickness(); - MTGCardInstance * changeController(Player * newcontroller); + MTGCardInstance * changeController(Player * newcontroller,bool notZone = false); Player * owner; Counters * counters; const string getDisplayName() const; diff --git a/projects/mtg/include/MTGGameZones.h b/projects/mtg/include/MTGGameZones.h index fa1c78468..dbb7798b7 100644 --- a/projects/mtg/include/MTGGameZones.h +++ b/projects/mtg/include/MTGGameZones.h @@ -209,7 +209,7 @@ public: MTGCardInstance * putInExile(MTGCardInstance * card); MTGCardInstance * putInLibrary(MTGCardInstance * card); MTGCardInstance * putInHand(MTGCardInstance * card); - MTGCardInstance * putInZone(MTGCardInstance * card, MTGGameZone * from, MTGGameZone * to); + MTGCardInstance * putInZone(MTGCardInstance * card, MTGGameZone * from, MTGGameZone * to, bool asCopy = false); int isInPlay(MTGCardInstance * card); int isInGrave(MTGCardInstance * card); int isInZone(MTGCardInstance * card,MTGGameZone * zone); diff --git a/projects/mtg/include/ManaCost.h b/projects/mtg/include/ManaCost.h index e0077af18..8934c8593 100644 --- a/projects/mtg/include/ManaCost.h +++ b/projects/mtg/include/ManaCost.h @@ -50,6 +50,9 @@ public: ManaCost * Retrace; ManaCost * morph; ManaCost * suspend; + + ManaCost * manaUsedToCast; + string alternativeName; bool isMulti; static ManaCost * parseManaCost(string value, ManaCost * _manacost = NULL, MTGCardInstance * c = NULL); diff --git a/projects/mtg/src/AIPlayerBaka.cpp b/projects/mtg/src/AIPlayerBaka.cpp index a10e438f0..fe4ee88e8 100644 --- a/projects/mtg/src/AIPlayerBaka.cpp +++ b/projects/mtg/src/AIPlayerBaka.cpp @@ -1354,7 +1354,24 @@ int AIPlayerBaka::selectHintAbility() int AIPlayerBaka::selectAbility() { - observer->mExtraPayment = NULL; + if(observer->mExtraPayment && observer->mExtraPayment->source->controller() == this) + { + extraManaCost * check = NULL; + check = dynamic_cast(observer->mExtraPayment->costs[0]); + if(check) + { + vector CostToPay = canPayMana(observer->mExtraPayment->source,check->costToPay); + if(CostToPay.size()) + { + payTheManaCost(check->costToPay,check->source,CostToPay); + } + else + { + observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); + observer->mExtraPayment = NULL; + } + } + } // Try Deck hints first if (selectHintAbility()) return 1; @@ -1479,7 +1496,11 @@ int AIPlayerBaka::effectBadOrGood(MTGCardInstance * card, int mode, TargetChoose int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCardInstance * chosenCard,bool checkOnly) { + if(observer->mExtraPayment) + { + observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); observer->mExtraPayment = NULL; + } //there should never be a case where a extra cost target selection is happening at the same time as this.. //extracost uses "chooseCard()" to determine its targets. vector potentialTargets; @@ -2132,8 +2153,11 @@ int AIPlayerBaka::computeActions() else { if(observer->mExtraPayment) + { //no extra payment should be waiting before selecting an ability. + observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); observer->mExtraPayment = NULL; + } //this is a fix for a rare bug that somehow ai trips over an extra payment without paying //then locks in a loop of trying to choose something different to do and trying to pay the extra payment. selectAbility(); @@ -2538,6 +2562,25 @@ int AIPlayerBaka::Act(float dt) { if (observer->isInterrupting == this) { + if(observer->mExtraPayment && observer->mExtraPayment->source->controller() == this) + { + extraManaCost * check = NULL; + check = dynamic_cast(observer->mExtraPayment->costs[0]); + if(check) + { + vector CostToPay = canPayMana(observer->mExtraPayment->source,check->costToPay); + if(CostToPay.size()) + { + payTheManaCost(check->costToPay,check->source,CostToPay); + } + else + { + observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); + observer->mExtraPayment = NULL; + } + } + return 0; + } observer->mLayers->stackLayer()->cancelInterruptOffer(); //endOfInterruption(); } else diff --git a/projects/mtg/src/ActionLayer.cpp b/projects/mtg/src/ActionLayer.cpp index aea0ea294..083c3c6f9 100644 --- a/projects/mtg/src/ActionLayer.cpp +++ b/projects/mtg/src/ActionLayer.cpp @@ -95,6 +95,16 @@ bool ActionLayer::CheckUserInput(JButton key) { if (observer->mExtraPayment && key == JGE_BTN_SEC) { + for (size_t i = 0; i < mObjects.size(); i++) + { + if (mObjects[i] != NULL) + { + ActionElement * currentAction = (ActionElement *) mObjects[i]; + currentAction->CheckUserInput(key); + //check first with a mock up to see if any abilities will care about the extra payment + //being cancelled. currently only menuability and paidability will care. + } + } observer->mExtraPayment = NULL; return 1; } diff --git a/projects/mtg/src/ActionStack.cpp b/projects/mtg/src/ActionStack.cpp index 82d6266cb..78ad38ef2 100644 --- a/projects/mtg/src/ActionStack.cpp +++ b/projects/mtg/src/ActionStack.cpp @@ -309,6 +309,10 @@ int Spell::resolve() Player * p = source->controller(); int castMethod = source->castMethod; vectorbackupTgt = source->backupTargets; + if(from != source->currentZone) + { + from = source->currentZone;//this happens when casting spells that belong to another player or casting a copy of someone elses spell. + } source = p->game->putInZone(source, from, p->game->battlefield); // We need to get the information about the cast method on both the card in the stack AND the card in play, @@ -326,6 +330,8 @@ int Spell::resolve() if(observer->getResourceManager()) observer->getResourceManager()->PlaySample(source->getSample()); } + if(this->cost) + source->getManaCost()->manaUsedToCast = NEW ManaCost(this->cost); AbilityFactory af(observer); af.addAbilities(observer->mLayers->actionLayer()->getMaxId(), this); return 1; @@ -419,6 +425,11 @@ Interruptible(observer, id) int PutInGraveyard::resolve() { MTGGameZone * zone = card->getCurrentZone(); + if (card->basicAbilities[(int)Constants::EXILEDEATH]) + { + card->owner->game->putInZone(card, zone, card->owner->game->exile); + return 1; + } if (zone == observer->players[0]->game->inPlay || zone == observer->players[1]->game->inPlay) { card->owner->game->putInZone(card, zone, card->owner->game->graveyard); @@ -900,7 +911,7 @@ void ActionStack::Update(float dt) if (mode == ACTIONSTACK_STANDARD) { modal = 0; - if (getLatest(NOT_RESOLVED)) + if (getLatest(NOT_RESOLVED) && !tc) { Interruptible * currentSpell = (Interruptible *)getLatest(NOT_RESOLVED); MTGCardInstance * card = currentSpell->source; @@ -1054,6 +1065,11 @@ bool ActionStack::CheckUserInput(JButton inputKey) { if (JGE_BTN_SEC == key) { + if(observer->mExtraPayment) + { + observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); + observer->mExtraPayment = NULL; + } endOfInterruption(); return true; } @@ -1167,7 +1183,12 @@ void ActionStack::Render() { if (!askIfWishesToInterrupt || !askIfWishesToInterrupt->displayStack()) return; - + /*observer->mExtraPayment = NULL*/;//end any payment request from extra cost as we open the stack to display items. + if(observer->mExtraPayment) + { + observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); + observer->mExtraPayment = NULL; + } for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; diff --git a/projects/mtg/src/AllAbilities.cpp b/projects/mtg/src/AllAbilities.cpp index d1b62db9a..01164942b 100644 --- a/projects/mtg/src/AllAbilities.cpp +++ b/projects/mtg/src/AllAbilities.cpp @@ -544,7 +544,7 @@ AACounter * AACounter::clone() const } //shield a card from a certain type of counter. -ACounterShroud::ACounterShroud(GameObserver* observer, int id, MTGCardInstance * source, MTGCardInstance *,TargetChooser * tc, Counter * counter) : +ACounterShroud::ACounterShroud(GameObserver* observer, int id, MTGCardInstance * source, MTGCardInstance * target,TargetChooser * tc, Counter * counter) : MTGAbility(observer, id, source),csTc(tc),counter(counter),re(NULL) { } @@ -732,7 +732,7 @@ AARemoveAllCounter * AARemoveAllCounter::clone() const } //proliferate a target -AAProliferate::AAProliferate(GameObserver* observer, int id, MTGCardInstance * source, Targetable *,ManaCost * cost) : +AAProliferate::AAProliferate(GameObserver* observer, int id, MTGCardInstance * source, Targetable * target,ManaCost * cost) : ActivatedAbility(observer, id, source, cost, 0) { this->GetId(); @@ -974,7 +974,7 @@ AASetTypeChosen::~AASetTypeChosen() // //choosing a type or color GenericFlipACoin::GenericFlipACoin(GameObserver* observer, int id, MTGCardInstance * source, Targetable *,string _toAdd, ManaCost * cost) : -ActivatedAbility(observer, id, source, cost, 0), baseAbility(_toAdd),chooseColor(true) +ActivatedAbility(observer, id, source, cost, 0), baseAbility(_toAdd),chooseColor(chooseColor) { this->GetId(); setCoin = NULL; @@ -1111,6 +1111,87 @@ AASetCoin * AASetCoin::clone() const AASetCoin::~AASetCoin() { } +//paying for an ability as an effect but as a cost +GenericPaidAbility::GenericPaidAbility(GameObserver* observer, int id, MTGCardInstance * source, Targetable * target,string _newName,string _castRestriction,string mayCost,string _toAdd,ManaCost * cost) : +ActivatedAbility(observer, id, source, cost, 0),newName(_newName),restrictions(_castRestriction),baseCost(mayCost), baseAbilityStr(_toAdd) +{ + this->GetId(); + baseAbility = NULL; + optionalCost = NULL; +} + +int GenericPaidAbility::resolve() +{ + if (!target) + return 0; + + + if(restrictions.size()) + { + AbilityFactory af(game); + int checkCond = af.parseCastRestrictions(source,source->controller(),restrictions); + if(!checkCond) + { + return 0; + } + } + AbilityFactory Af(game); + vector baseAbilityStrSplit = split(baseAbilityStr,'?'); + vectorselection; + if(baseAbilityStrSplit.size() > 1) + { + baseAbility = Af.parseMagicLine(baseAbilityStrSplit[0], this->GetId(), NULL, source); + baseAbility->target = target; + optionalCost = ManaCost::parseManaCost(baseCost, NULL, source); + MTGAbility * set = baseAbility->clone(); + set->oneShot = true; + selection.push_back(set); + SAFE_DELETE(baseAbility); + baseAbility = Af.parseMagicLine(baseAbilityStrSplit[1], this->GetId(), NULL, source); + baseAbility->target = target; + set = baseAbility->clone(); + set->oneShot = true; + selection.push_back(set); + } + else + { + baseAbility = Af.parseMagicLine(baseAbilityStrSplit[0], this->GetId(), NULL, source); + baseAbility->target = target; + optionalCost = ManaCost::parseManaCost(baseCost, NULL, source); + MTGAbility * set = baseAbility->clone(); + set->oneShot = true; + selection.push_back(set); + } + + if(selection.size()) + { + MTGAbility * a1 = NEW MenuAbility(game, this->GetId(), target, source,baseAbilityStrSplit.size() >1?true:false,selection,NULL,newName); + dynamic_cast(a1)->optionalCosts.push_back(NEW ManaCost(optionalCost)); + game->mLayers->actionLayer()->currentActionCard = (MTGCardInstance *)target; + a1->resolve(); + } + return 1; + +} + +const char* GenericPaidAbility::getMenuText() +{ + if( newName.size()) + return newName.c_str(); + return "Pay For Effect"; +} + +GenericPaidAbility * GenericPaidAbility::clone() const +{ + GenericPaidAbility * a = NEW GenericPaidAbility(*this); + return a; +} + +GenericPaidAbility::~GenericPaidAbility() +{ + SAFE_DELETE(optionalCost); + SAFE_DELETE(baseAbility); +} //saves a listed mana type until end of turn. AManaPoolSaver::AManaPoolSaver(GameObserver* observer, int id, MTGCardInstance * source,string color, bool otherPlayer) : @@ -1206,8 +1287,8 @@ AAResetDamage * AAResetDamage::clone() const } //ability that resolves to do nothing. - AAFakeAbility::AAFakeAbility(GameObserver* observer, int id, MTGCardInstance * source, MTGCardInstance * _target, ManaCost * cost): - ActivatedAbility(observer, id, source, cost, 0) + AAFakeAbility::AAFakeAbility(GameObserver* observer, int id, MTGCardInstance * source, MTGCardInstance * _target, string _named,ManaCost * cost): + ActivatedAbility(observer, id, source, cost, 0),named(_named) { this->target = _target; } @@ -1218,6 +1299,8 @@ int AAFakeAbility::resolve() const char* AAFakeAbility::getMenuText() { + if(named.size()) + return named.c_str(); return "Ability"; } @@ -1249,7 +1332,9 @@ int AAFizzler::resolve() target = stack->getActionElementFromCard(cTarget); } Spell * sTarget = (Spell *) target; - MTGCardInstance* sCard = sTarget->source; + MTGCardInstance* sCard = NULL; + if(sTarget) + sCard = sTarget->source; if(!sCard || !sTarget || sCard->has(Constants::NOFIZZLE)) return 0; stack->Fizzle(sTarget); @@ -1304,6 +1389,8 @@ int AABuryCard::resolve() const char * AABuryCard::getMenuText() { + if(menu.size()) + return menu.c_str(); return "Bury"; } @@ -1483,6 +1570,9 @@ AADiscardCard::~AADiscardCard() { SAFE_DELETE(andAbility); } + + +// AADrawer::AADrawer(GameObserver* observer, int _id, MTGCardInstance * card, Targetable * _target, ManaCost * _cost, string nbcardsStr, int who, bool noreplace) : ActivatedAbilityTP(observer, _id, card, _target, _cost, who), nbcardsStr(nbcardsStr),noReplace(noreplace) @@ -1881,23 +1971,28 @@ int AADynamic::resolve() break; case DYNAMIC_ABILITY_WHO_ITSELF: source = ((MTGCardInstance *) _target); + _target = _target; break; case DYNAMIC_ABILITY_WHO_TARGETCONTROLLER: + _target = _target; secondaryTarget = ((MTGCardInstance *) _target)->controller(); break; case DYNAMIC_ABILITY_WHO_TARGETOPPONENT: + _target = _target; secondaryTarget = ((MTGCardInstance *) _target)->controller()->opponent(); break; case DYNAMIC_ABILITY_WHO_TOSOURCE: tosrc = true; break; case DYNAMIC_ABILITY_WHO_SOURCECONTROLLER: + _target = _target; secondaryTarget = ((MTGCardInstance *) OriginalSrc)->controller(); break; case DYNAMIC_ABILITY_WHO_SOURCEOPPONENT: secondaryTarget = OriginalSrc->controller()->opponent(); break; default: + _target = _target; break; } if(amountsource == DYNAMIC_MYSELF_AMOUNT) @@ -2530,8 +2625,8 @@ AInstantCastRestrictionUEOT::~AInstantCastRestrictionUEOT() //AAMover -AAMover::AAMover(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, string dest, ManaCost * _cost) : - ActivatedAbility(observer, _id, _source, _cost, 0), destination(dest) +AAMover::AAMover(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, string dest,string newName, ManaCost * _cost) : + ActivatedAbility(observer, _id, _source, _cost, 0), destination(dest),named(newName) { if (_target) target = _target; @@ -2607,11 +2702,15 @@ int AAMover::resolve() const char * AAMover::getMenuText() { + if(named.size()) + return named.c_str(); return "Move"; } const char * AAMover::getMenuText(TargetChooser * tc) { + if(named.size()) + return named.c_str(); MTGGameZone * dest = destinationZone(); for (int i = 0; i < 2; i++) @@ -2862,6 +2961,7 @@ int AARemoveMana::resolve() { if (player->doesntEmpty->getConvertedCost() && !player->poolDoesntEmpty->getConvertedCost()) { + ManaCost * toRemove = manaPool->Diff(player->doesntEmpty); player->getManaPool()->pay(manaPool->Diff(player->doesntEmpty)); return 1; } @@ -3174,7 +3274,6 @@ MayAbility::MayAbility(GameObserver* observer, int _id, MTGAbility * _ability, M { triggered = 0; mClone = NULL; - optionalCost = NULL; } void MayAbility::Update(float dt) @@ -3183,8 +3282,6 @@ void MayAbility::Update(float dt) if (!triggered && !game->getCurrentTargetChooser() && (!game->mLayers->actionLayer()->menuObject||game->mLayers->actionLayer()->menuObject == source)) { triggered = 1; - if(optionalCost && !source->controller()->getManaPool()->canAfford(optionalCost)) - return; if(Cond.size()) { AbilityFactory af(game); @@ -3229,8 +3326,16 @@ int MayAbility::isReactingToTargetClick(Targetable * card) { if (card == source) { - if(!optionalCost || source->controller()->getManaPool()->canAfford(optionalCost)) - return 1; + if(Cond.size()) + { + AbilityFactory af(game); + int checkCond = af.parseCastRestrictions(source,source->controller(),Cond); + if(!checkCond) + { + return 0; + } + } + return 1; } return 0; } @@ -3238,12 +3343,6 @@ int MayAbility::isReactingToTargetClick(Targetable * card) int MayAbility::reactToTargetClick(Targetable * object) { mClone = ability->clone(); - if(optionalCost) - { - source->controller()->getManaPool()->pay(optionalCost); - optionalCost->setExtraCostsAction(this, source); - optionalCost->doPayExtra(); - } mClone->addToGame(); mClone->forceDestroy = 1; return mClone->reactToTargetClick(object); @@ -3253,18 +3352,15 @@ MayAbility * MayAbility::clone() const { MayAbility * a = NEW MayAbility(*this); a->ability = ability->clone(); - a->optionalCost = this->optionalCost; return a; } MayAbility::~MayAbility() { SAFE_DELETE(ability); - SAFE_DELETE(optionalCost); } //Menu building ability Abilities -//this will eventaully handle choosen discards/sacrifices. MenuAbility::MenuAbility(GameObserver* observer, int _id, Targetable * mtarget, MTGCardInstance * _source, bool must,vectorabilities,Player * who, string newName) : MayAbility(observer, _id,NULL,_source,must), must(must),abilities(abilities),who(who),newNameString(newName) { @@ -3272,12 +3368,46 @@ MayAbility(observer, _id,NULL,_source,must), must(must),abilities(abilities),who mClone = NULL; this->target = mtarget; removeMenu = false; + vectoroptionalCost = vector(); + toPay = NULL; + processed = false; +} + +bool MenuAbility::CheckUserInput(JButton key) +{ + if (game->mExtraPayment && key == JGE_BTN_SEC) + { + if(toPay && toPay->extraCosts == game->mExtraPayment) + { + //the user cancelled the paidability. fireAbility() on the second menu item. + //paidability will always occupy the abilities[0]; in the vector. + if(abilities.size() > 1) + { + abilities[1]->target = abilities[0]->target; + abilities[1]->fireAbility(); + } + } + return false; + } + return false; } void MenuAbility::Update(float dt) { MTGAbility::Update(dt); ActionLayer * object = game->mLayers->actionLayer(); + if(toPay && game->mExtraPayment && !processed) + { + if(game->mExtraPayment->isPaymentSet() && game->mExtraPayment->canPay() ) + { + game->mExtraPayment->doPay(); + game->mLayers->actionLayer()->reactToClick(game->mExtraPayment->action, game->mExtraPayment->source); + game->mExtraPayment = NULL; + processAbility(); + return; + } + + } if (!triggered && !object->menuObject && !object->getCurrentTargetChooser()) { @@ -3293,7 +3423,7 @@ void MenuAbility::Update(float dt) { triggered = 0; } - if(triggered) + if(triggered && !game->mExtraPayment && !processed) { game->mLayers->actionLayer()->setCustomMenuObject(source, must,abilities,newNameString.size()?newNameString.c_str():""); previousInterrupter = game->isInterrupting; @@ -3317,6 +3447,8 @@ const char * MenuAbility::getMenuText() int MenuAbility::testDestroy() { + if (game->mExtraPayment) + return 0; if (!removeMenu) return 0; if (game->mLayers->actionLayer()->menuObject) @@ -3327,8 +3459,38 @@ int MenuAbility::testDestroy() return 1; } -int MenuAbility::isReactingToTargetClick(Targetable * card){return MayAbility::isReactingToTargetClick(card);} -int MenuAbility::reactToTargetClick(Targetable *){return 1;} +int MenuAbility::isReactingToTargetClick(Targetable * card){return 0/*MayAbility::isReactingToTargetClick(card)*/;} +int MenuAbility::reactToTargetClick(Targetable * object){return 1;} + +int MenuAbility::processAbility() +{ + if(!mClone) + return 0; + if(processed) + return 0; + if(abilities[0]) + mClone->target = abilities[0]->target; + if(MayAbility * toCheck = dynamic_cast(mClone)) + { + toCheck->must = true; + mClone->addToGame(); + } + else + { + mClone->oneShot = true; + mClone->forceDestroy = 1; + mClone->canBeInterrupted = false; + mClone->resolve(); + SAFE_DELETE(mClone); + if (source->controller() == game->isInterrupting) + game->mLayers->stackLayer()->cancelInterruptOffer(ActionStack::DONT_INTERRUPT, false); + } + + processed = true; + this->forceDestroy = 1; + removeMenu = true; + return 1; +} int MenuAbility::reactToChoiceClick(Targetable * object,int choice,int control) { @@ -3339,12 +3501,21 @@ int MenuAbility::reactToChoiceClick(Targetable * object,int choice,int control) return 0; for(int i = 0;i < int(abilities.size());i++) { + if(choice == i) mClone = abilities[choice]->clone(); - else + else if(!optionalCosts.size()) SAFE_DELETE(abilities[i]); - //else - // abilities[i]->clone();//all get cloned for clean up purposes. EDIT:removed, cause memleaks. + if (mClone && !toPay && optionalCosts.size() && i < int(optionalCosts.size()) && optionalCosts[i])//paidability only supports the first ability as paid for now. + { + toPay = NEW ManaCost(); + if(optionalCosts[i]->extraCosts) + toPay->extraCosts = optionalCosts[i]->extraCosts->clone(); + toPay->addExtraCost(NEW extraManaCost(optionalCosts[i])); + toPay->setExtraCostsAction(this,source); + game->mExtraPayment = toPay->extraCosts; + return 0; + } } if(!mClone) { @@ -3353,15 +3524,7 @@ int MenuAbility::reactToChoiceClick(Targetable * object,int choice,int control) return 0; } mClone->target = abilities[choice]->target; - mClone->oneShot = true; - mClone->forceDestroy = 1; - mClone->canBeInterrupted = false; - mClone->resolve(); - SAFE_DELETE(mClone); - if (source->controller() == game->isInterrupting) - game->mLayers->stackLayer()->cancelInterruptOffer(ActionStack::DONT_INTERRUPT, false); - this->forceDestroy = 1; - removeMenu = true; + processAbility(); return reactToTargetClick(object); } @@ -3374,6 +3537,7 @@ MenuAbility * MenuAbility::clone() const for(int i = 0;i < int(abilities.size());i++) { a->abilities.push_back(abilities[i]->clone()); + a->optionalCosts.push_back(NEW ManaCost(optionalCosts[i])); a->abilities[i]->target = abilities[i]->target; } } @@ -3399,6 +3563,15 @@ MenuAbility::~MenuAbility() } else SAFE_DELETE(ability); + if(optionalCosts.size()) + for(int i = 0;i < int(optionalCosts.size());i++) + { + if(optionalCosts[i]) + { + SAFE_DELETE(optionalCosts[i]); + } + + } } /// //MultiAbility : triggers several actions for a cost @@ -5148,6 +5321,211 @@ AAConnect * AAConnect::clone() const return NEW AAConnect(*this); } +// casting a card for free, or casting a copy of a card. +AACastCard::AACastCard(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target,bool _restricted,bool _copied,bool asNormal,string _namedCard,string _name,bool _noEvent) : + MTGAbility(observer, _id, _source),restricted(_restricted),asCopy(_copied),normal(asNormal),cardNamed(_namedCard),nameThis(_name),noEvent(_noEvent) +{ + target = _target; + andAbility = NULL; + processed = false; + theNamedCard = NULL; +} + + + void AACastCard::Update(float dt) + { + MTGAbility::Update(dt); + if (processed) + return; + if(cardNamed.size() && !theNamedCard) + { + theNamedCard = makeCard(); + } + if (restricted) + { + MTGCardInstance * toCheck = (MTGCardInstance*)target; + if(theNamedCard) + toCheck = theNamedCard; + if (game->currentActionPlayer->game->playRestrictions->canPutIntoZone(toCheck, source->controller()->game->stack) == PlayRestriction::CANT_PLAY) + { + processed = true; + this->forceDestroy = 1; + return ; + } + if(!allowedToCast(toCheck,source->controller())) + { + processed = true; + this->forceDestroy = 1; + return; + } + if(!toCheck->hasType(Subtypes::TYPE_INSTANT) && !(game->getCurrentGamePhase() == MTG_PHASE_FIRSTMAIN || game->getCurrentGamePhase() == MTG_PHASE_SECONDMAIN)) + { + processed = true; + this->forceDestroy = 1; + return; + } + } + MTGCardInstance * toCheck = (MTGCardInstance*)target; + if(theNamedCard) + toCheck = theNamedCard; + if (Spell * checkSpell = dynamic_cast(target)) + { + toCheck = checkSpell->source; + } + if (!game->targetListIsSet(toCheck)) + { + if(game->targetChooser) + game->targetChooser->Owner = source->controller();//sources controller is the caster + return; + } + resolveSpell(); + this->forceDestroy = 1; + return; + } + int AACastCard::isReactingToTargetClick(Targetable * card){return 0;} + int AACastCard::reactToTargetClick(Targetable * object) + { + if (MTGCardInstance * cObject = dynamic_cast(object)) + return reactToClick(cObject); + + if (waitingForAnswer) + { + if (tc->toggleTarget(object) == TARGET_OK_FULL) + { + waitingForAnswer = 0; + game->mLayers->actionLayer()->setCurrentWaitingAction(NULL); + return MTGAbility::reactToClick(source); + } + return 1; + } + return 0; + } + + MTGCardInstance * AACastCard::makeCard() + { + MTGCardInstance * card = NULL; + MTGCard * cardData = MTGCollection()->getCardByName(cardNamed); + card = NEW MTGCardInstance(cardData, source->controller()->game); + source->controller()->game->temp->addCard(card); + return card; + } + +int AACastCard::resolveSpell() +{ + if (processed) + return 0; + MTGCardInstance * _target = (MTGCardInstance *) target; + if(theNamedCard) + _target = theNamedCard; + if (Spell * checkSpell = dynamic_cast(target)) + { + _target = checkSpell->source; + } + if(asCopy) + { + MTGCard * cardToCopy = MTGCollection()->getCardById(_target->getId()); + MTGCardInstance * myDummy = NULL; + myDummy = NEW MTGCardInstance(cardToCopy, source->controller()->game); + source->controller()->game->garbage->addCard(myDummy); + _target = myDummy; + _target->isToken = 1; + _target->changeController(source->controller(),true); + } + if (_target) + { + + if (_target->isLand()) + { + MTGCardInstance * copy = _target->controller()->game->putInZone(_target, _target->currentZone, source->controller()->game->temp,noEvent); + copy->changeController(source->controller(),true); + Spell * spell = NEW Spell(game, 0,copy,NULL,NULL, 1); + spell->resolve(); + delete spell; + } + else + { + Spell * spell = NULL; + MTGCardInstance * copy = NULL; + if (normal ||(!_target->hasType(Subtypes::TYPE_INSTANT) && !_target->hasType(Subtypes::TYPE_SORCERY))) + { + copy =_target->controller()->game->putInZone(_target, _target->currentZone, source->controller()->game->stack,noEvent); + copy->changeController(source->controller(),true); + } + else + { + copy =_target->controller()->game->putInZone(_target, _target->currentZone, _target->controller()->game->stack,noEvent); + copy->changeController(source->controller(),true); + } + if (game->targetChooser) + { + game->targetChooser->Owner = source->controller(); + spell = game->mLayers->stackLayer()->addSpell(copy, game->targetChooser, NULL, 1, 0); + game->targetChooser = NULL; + } + else + { + spell = game->mLayers->stackLayer()->addSpell(copy, NULL, NULL, 1, 0); + } + + if (copy->has(Constants::STORM)) + { + int storm = _target->controller()->game->stack->seenThisTurn("*", Constants::CAST_ALL) + source->controller()->opponent()->game->stack->seenThisTurn("*", Constants::CAST_ALL); + + for (int i = storm; i > 1; i--) + { + spell = game->mLayers->stackLayer()->addSpell(copy, NULL, 0, 1, 1); + + } + } + if (!copy->has(Constants::STORM)) + { + copy->X = 0; + copy->castX = copy->X; + } + if(andAbility) + { + MTGAbility * andAbilityClone = andAbility->clone(); + andAbilityClone->target = copy; + if(andAbility->oneShot) + { + andAbilityClone->resolve(); + SAFE_DELETE(andAbilityClone); + } + else + { + andAbilityClone->addToGame(); + } + } + } + this->forceDestroy = true; + processed = true; + return 1; + } + return 0; +} + +const char * AACastCard::getMenuText() +{ + if(nameThis.size()) + return nameThis.c_str(); + return "Cast For Free"; +} + +AACastCard * AACastCard::clone() const +{ + AACastCard * a = NEW AACastCard(*this); + if(tc) + a->tc = tc->clone(); + if(andAbility) + a->andAbility = andAbility->clone(); + return a; +} +AACastCard::~AACastCard() +{ + SAFE_DELETE(tc); + SAFE_DELETE(andAbility); +} + //Tutorial Messaging ATutorialMessage::ATutorialMessage(GameObserver* observer, MTGCardInstance * source, string message, int limit) diff --git a/projects/mtg/src/Counters.cpp b/projects/mtg/src/Counters.cpp index f0c3a08af..4ff5893b3 100644 --- a/projects/mtg/src/Counters.cpp +++ b/projects/mtg/src/Counters.cpp @@ -153,11 +153,12 @@ int Counters::removeCounter(const char * _name, int _power, int _toughness) dynamic_cast(e)->targetCard = this->target; g->receiveEvent(e); //special case:if a card is suspended and no longer has a time counter when the last is removed, the card is cast. + if (target->suspended && !target->counters->hasCounter("time",0,0)) { GameObserver * game = target->getObserver(); MTGCardInstance * copy = target->controller()->game->putInZone(target, target->currentZone, target->controller()->game->stack); - + game->mLayers->stackLayer()->addSpell(copy, game->targetChooser, NULL,1, 0); game->targetChooser = NULL; } diff --git a/projects/mtg/src/DeckStats.cpp b/projects/mtg/src/DeckStats.cpp index 3789d4288..c18d66cd3 100644 --- a/projects/mtg/src/DeckStats.cpp +++ b/projects/mtg/src/DeckStats.cpp @@ -200,6 +200,7 @@ void DeckStats::save(const std::string& filename) playerDeckMeta->setColorIndex( manaColorIndex ); } file << "MANA:" << manaColorIndex << endl; + if(file) for (it = stats.begin(); it != stats.end(); it++) { sprintf(writer, "%s\n", it->first.c_str()); diff --git a/projects/mtg/src/ExtraCost.cpp b/projects/mtg/src/ExtraCost.cpp index 07912812d..3a563f7df 100644 --- a/projects/mtg/src/ExtraCost.cpp +++ b/projects/mtg/src/ExtraCost.cpp @@ -10,8 +10,8 @@ SUPPORT_OBJECT_ANALYTICS(ExtraCost) -ExtraCost::ExtraCost(const std::string& inCostRenderString, TargetChooser *_tc) - : tc(_tc), source(NULL), target(NULL), mCostRenderString(inCostRenderString) +ExtraCost::ExtraCost(const std::string& inCostRenderString, TargetChooser *_tc, ManaCost * _costToPay) + : tc(_tc),costToPay(_costToPay), source(NULL), target(NULL), mCostRenderString(inCostRenderString) { if (tc) tc->targetter = NULL; @@ -19,6 +19,7 @@ ExtraCost::ExtraCost(const std::string& inCostRenderString, TargetChooser *_tc) ExtraCost::~ExtraCost() { + SAFE_DELETE(costToPay); SAFE_DELETE(tc); } @@ -61,8 +62,56 @@ int ExtraCost::setPayment(MTGCardInstance * card) target = card; } } + if (costToPay && source->controller()->getManaPool()->canAfford(costToPay)) + { + result = 1; + } return result; } +//extra added manacost, or add a manacost as the cost of extra +extraManaCost * extraManaCost::clone() const +{ + extraManaCost * ec = NEW extraManaCost(*this); + return ec; +} + +extraManaCost::extraManaCost(ManaCost * costToPay) + : ExtraCost("Pay The Cost",NULL, costToPay) +{ +} + +int extraManaCost::tryToSetPayment(MTGCardInstance * card) +{ + return 1; +} + +int extraManaCost::isPaymentSet() +{ + if (!source->controller()->getManaPool()->canAfford(costToPay)) + { + return 0; + } + return 1; +} + +int extraManaCost::canPay() +{ + MTGCardInstance * _target = (MTGCardInstance *) target; + if(!source->controller()->getManaPool()->canAfford(costToPay)) + { + return 0; + } + return 1; +} + +int extraManaCost::doPay() +{ + if (!source->controller()->getManaPool()->canAfford(costToPay)) + return 0; + + source->controller()->getManaPool()->pay(costToPay); + return 1; +} //life cost LifeCost * LifeCost::clone() const diff --git a/projects/mtg/src/GameObserver.cpp b/projects/mtg/src/GameObserver.cpp index 15cad1690..acfd37a63 100644 --- a/projects/mtg/src/GameObserver.cpp +++ b/projects/mtg/src/GameObserver.cpp @@ -564,6 +564,7 @@ void GameObserver::Update(float dt) if(getCurrentTargetChooser()->Owner != currentlyActing()) { player = getCurrentTargetChooser()->Owner; + isInterrupting = player; } } currentActionPlayer = player; @@ -1273,8 +1274,12 @@ int GameObserver::cardClick(MTGCardInstance * card, Targetable * object, bool lo break; } } + extraManaCost * costType = NULL; + if( mExtraPayment && mExtraPayment->costs.size()) + costType = dynamic_cast(mExtraPayment->costs[0]); - if (WaitForExtraPayment(card)) { + if (WaitForExtraPayment(card) && !costType) + { toReturn = 1; break; } diff --git a/projects/mtg/src/GameStateDuel.cpp b/projects/mtg/src/GameStateDuel.cpp index ba5292fc5..103a1ad71 100644 --- a/projects/mtg/src/GameStateDuel.cpp +++ b/projects/mtg/src/GameStateDuel.cpp @@ -1716,7 +1716,7 @@ void GameStateDuel::OnScroll(int inXVelocity, int inYVelocity) if (abs(inXVelocity) > 300) { bool flickLeft = (inYVelocity > 0); - if(flickLeft) + if(flickLeft && OptionClosedHand::INVISIBLE == options[Options::CLOSEDHAND].number) { JButton trigger = (options[Options::REVERSETRIGGERS].number ? JGE_BTN_PREV : JGE_BTN_NEXT); mEngine->HoldKey_NoRepeat(trigger); diff --git a/projects/mtg/src/MTGAbility.cpp b/projects/mtg/src/MTGAbility.cpp index 48cecee25..58f7dd6bc 100644 --- a/projects/mtg/src/MTGAbility.cpp +++ b/projects/mtg/src/MTGAbility.cpp @@ -337,26 +337,80 @@ int AbilityFactory::parseCastRestrictions(MTGCardInstance * card, Player * playe check = restriction[i].find("miracle"); if(check != string::npos) { - if(!card->miracle) + if(observer->turn < 1 && card->controller()->drawCounter < 1) + return 0; + if(card->previous && !card->previous->miracle) return 0; } check = restriction[i].find("prowl"); if(check != string::npos) { + vectortypeToCheck = parseBetween(restriction[i],"prowl(",")"); bool isProwled = false; - for (size_t i = 0; i < card->controller()->prowledTypes.size(); ++i) + if(typeToCheck.size()) { - if ( card->hasSubtype( card->controller()->prowledTypes[i] )) + vectorsplitTypes = split(typeToCheck[1],' '); + if(splitTypes.size()) { - isProwled = true; - break; + for (size_t k = 0; k < splitTypes.size(); ++k) + { + string theType = splitTypes[k]; + for (size_t j = 0; j < card->controller()->prowledTypes.size(); ++j) + { + if ( card->controller()->prowledTypes[j] == splitTypes[k]) + { + isProwled = true; + break; + } + } + } + } + else + { + for (size_t j = 0; j < card->controller()->prowledTypes.size(); ++j) + { + if ( card->controller()->prowledTypes[j] == typeToCheck[1]) + { + isProwled = true; + break; + } + } + } + + } + else + { + for (size_t j = 0; j < card->controller()->prowledTypes.size(); ++j) + { + if ( card->hasSubtype( card->controller()->prowledTypes[j] )) + { + isProwled = true; + break; + } } } if(!isProwled) return 0; } + check = restriction[i].find("spent("); + if(check != string::npos) + { + vectorspentMana = parseBetween(restriction[i],"spent(",")"); + if(spentMana.size()) + { + ManaCost * costToCheck = ManaCost::parseManaCost(restriction[i]); + ManaCost * spent = card->getManaCost()->manaUsedToCast; + if(spent && costToCheck && !spent->canAfford(costToCheck)) + { + SAFE_DELETE(costToCheck); + return 0; + } + SAFE_DELETE(costToCheck); + } + } + check = restriction[i].find("ownerscontrol"); if(check != string::npos) { @@ -1104,6 +1158,19 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG //actual abilities, this is a limitation. string unchangedS = ""; unchangedS.append(s); + found = s.find("pay("); + if (found != string::npos && storedPayString.empty()) + { + vector splitMayPaystr = parseBetween(s, "pay(", ")", true); + if(splitMayPaystr.size()) + { + storedPayString.append(splitMayPaystr[2]); + s = splitMayPaystr[0]; + s.append("pay("); + s.append(splitMayPaystr[1]); + s.append(")"); + } + } found = s.find("transforms(("); if (found != string::npos && storedString.empty()) { @@ -1133,6 +1200,20 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } } + found = s.find("pay[["); + if (found != string::npos && storedPayString.empty()) + { + vector splitMayPaystr = parseBetween(s, "pay[[", " ", true); + if(splitMayPaystr.size()) + { + storedPayString.append(splitMayPaystr[2]); + s = splitMayPaystr[0]; + s.append("pay[["); + s.append(splitMayPaystr[1]); + s.append("]]"); + } + } + found = s.find("and!("); if (found != string::npos && found + 6 != ')' && storedAndAbility.empty()) { @@ -1377,29 +1458,26 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return a; } } - + //may pay ability + vector splitMayPay = parseBetween(s, "pay(", ")", true); + if(splitMayPay.size()) + { + MTGAbility * a1 = NULL; + GenericPaidAbility * a = NEW GenericPaidAbility(observer, id, card, target,newName,castRestriction,splitMayPay[1],storedPayString); + a->oneShot = 1; + a->canBeInterrupted = false; + return a; + } //When...comes into play, you may... //When...comes into play, choose one... const string mayKeywords[] = {"may ", "choice "}; const bool mayMust[] = { false, true }; - ManaCost * mayCost = NULL; for (size_t i =0; i < sizeof(mayMust)/sizeof(mayMust[0]); ++i) { if (sWithoutTc.find(mayKeywords[i]) == 0) { string s1 = sWithoutTc.substr(mayKeywords[i].length()); - MTGAbility * a1 = NULL; - //may pay a cost for this ability - vector splitMayPay = parseBetween(s1, "pay(", ")", true); - if(splitMayPay.size()) - { - a1 = parseMagicLine(splitMayPay[2], id, spell, card); - mayCost = ManaCost::parseManaCost(splitMayPay[1], NULL, card); - } - else - { - a1 = parseMagicLine(s1, id, spell, card); - } + MTGAbility * a1 = parseMagicLine(s1, id, spell, card); if (!a1) return NULL; @@ -1408,8 +1486,6 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG else a1 = NEW GenericActivatedAbility(observer, newName,castRestriction,id, card, a1, NULL); MayAbility * mainAbility = NEW MayAbility(observer, id, a1, card,mayMust[i],castRestriction); - if(mayCost) - mainAbility->optionalCost = mayCost; return mainAbility; } } @@ -1424,6 +1500,17 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return NEW GenericInstantAbility(observer, 1, card, (Damageable *) target, a1); } + //add an ability to the game, this is the "addToGame" version of the above ability. + if (s.find("activate ") == 0 || s.find(" activate ") == 0) + { + string s1 = s.substr(9); + MTGAbility * a1 = parseMagicLine(s1, id, spell, card); + if (!a1) + return NULL; + + return NEW GenericAddToGame(observer,1, card, (Damageable *) target,a1); + } + // neverending effect if (s.find("emblem ") == 0) { @@ -1876,7 +1963,16 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG a->canBeInterrupted = false; return a; } - + //may pay ability + vector splitMayPaysub = parseBetween(s, "pay[[","]]", true); + if(splitMayPaysub.size()) + { + MTGAbility * a1 = NULL; + GenericPaidAbility * a = NEW GenericPaidAbility(observer, id, card, target,newName,castRestriction,splitMayPaysub[1],storedPayString); + a->oneShot = 1; + a->canBeInterrupted = false; + return a; + } //Upkeep Cost found = s.find("upcost"); if (found != string::npos) @@ -2058,7 +2154,7 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG if (card->hasType(Subtypes::TYPE_AURA)) target = card; - MTGAbility * a = NEW AAMover(observer, id, card, target, splitMove[1]); + MTGAbility * a = NEW AAMover(observer, id, card, target, splitMove[1],newName); a->oneShot = true; if(storedAndAbility.size()) { @@ -2182,6 +2278,46 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } return a; } + + //cast a card without paying it's manacost + vector splitCastCard = parseBetween(s, "castcard(", ")"); + if (splitCastCard.size()) + { + string builtHow = splitCastCard[1]; + bool withRestrictions = splitCastCard[1].find("restricted") != string::npos; + bool asCopy = splitCastCard[1].find("copied") != string::npos; + bool asNormal = splitCastCard[1].find("normal") != string::npos; + bool sendNoEvent = splitCastCard[1].find("noevent") != string::npos; + string nameCard = ""; + if(splitCastCard[1].find("named!:") != string::npos) + { + vector splitCastName = parseBetween(splitCastCard[1], "named!:", ":!"); + if(splitCastName.size()) + { + nameCard = splitCastName[1]; + } + } + MTGAbility *a = NEW AACastCard(observer, id, card, target,withRestrictions,asCopy,asNormal,nameCard,newName,sendNoEvent); + a->oneShot = false; + if(splitCastCard[1].find("trigger[to]") != string::npos) + { + a->setActionTC(NEW TriggerTargetChooser(observer, WEvent::TARGET_TO)); + } + if(storedAndAbility.size()) + { + string stored = storedAndAbility; + storedAndAbility.clear(); + ((AACastCard*)a)->andAbility = parseMagicLine(stored, id, spell, card); + } + MTGCardInstance * _target = NULL; + if (spell) + _target = spell->getNextCardTarget(); + if(!_target) + _target = target; + a->target = _target; + return a; + } + bool oneShot = false; bool forceForever = false; bool untilYourNextTurn = false; @@ -2244,7 +2380,8 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG found = s.find("donothing"); if (found != string::npos) { - MTGAbility * a = NEW AAFakeAbility(observer, id, card, target); + + MTGAbility * a = NEW AAFakeAbility(observer, id, card, target,newName); a->oneShot = 1; return a; } @@ -4106,18 +4243,26 @@ void AbilityFactory::addAbilities(int _id, Spell * spell) if (card->hasType(Subtypes::TYPE_INSTANT) || card->hasType(Subtypes::TYPE_SORCERY)) { - MTGPlayerCards * zones = card->controller()->game; - if (card->alternateCostPaid[ManaCost::MANA_PAID_WITH_BUYBACK] > 0) + MTGPlayerCards * zones = card->owner->game; + if(card->getCurrentZone()) + card->currentZone->owner->game;//grab it from where ever it is. + MTGPlayerCards * Endzones = card->owner->game;//put them in thier owners respective zones as per rules. + if (card->basicAbilities[(int)Constants::EXILEDEATH]) { - zones->putInZone(card, zones->stack, zones->hand); + card->owner->game->putInZone(card, card->getCurrentZone(), card->owner->game->exile); + + } + else if (card->alternateCostPaid[ManaCost::MANA_PAID_WITH_BUYBACK] > 0) + { + zones->putInZone(card, zones->stack, Endzones->hand); } else if (card->alternateCostPaid[ManaCost::MANA_PAID_WITH_FLASHBACK] > 0) { - zones->putInZone(card, zones->stack, zones->exile); + zones->putInZone(card, zones->stack, Endzones->exile); } else { - zones->putInZone(card, zones->stack, zones->graveyard); + zones->putInZone(card, zones->stack, Endzones->graveyard); } } @@ -4763,6 +4908,14 @@ int TriggeredAbility::receiveEvent(WEvent * e) //that resolves instantly before the event that targetted it. resolve(); return 1; + } + WEventZoneChange * stackCheck = dynamic_cast(e); + if(stackCheck && (stackCheck->to == game->currentPlayer->game->stack||stackCheck->to == game->currentPlayer->opponent()->game->stack)) + { + resolve(); + return 1; + //triggers that resolve from stack events must resolve instantly or by the time they do the cards that triggered them + //have already been put in play or graveyard. } fireAbility(); return 1; diff --git a/projects/mtg/src/MTGCardInstance.cpp b/projects/mtg/src/MTGCardInstance.cpp index a9c3e7042..a9841fc20 100644 --- a/projects/mtg/src/MTGCardInstance.cpp +++ b/projects/mtg/src/MTGCardInstance.cpp @@ -541,8 +541,13 @@ int MTGCardInstance::hasSummoningSickness() return 1; } -MTGCardInstance * MTGCardInstance::changeController(Player * newController) +MTGCardInstance * MTGCardInstance::changeController(Player * newController,bool notZone) { + if(notZone) + { + lastController = newController; + return this; + } Player * originalOwner = controller(); MTGCardInstance * copy = originalOwner->game->putInZone(this, this->currentZone, newController->game->inPlay); copy->summoningSickness = 1; diff --git a/projects/mtg/src/MTGGameZones.cpp b/projects/mtg/src/MTGGameZones.cpp index fc1726f1d..e204978f4 100644 --- a/projects/mtg/src/MTGGameZones.cpp +++ b/projects/mtg/src/MTGGameZones.cpp @@ -222,6 +222,11 @@ void MTGPlayerCards::drawFromLibrary() } MTGCardInstance * toMove = library->cards[library->nb_cards - 1]; library->lastCardDrawn = toMove; + if (!library->miracle) + { + library->miracle = true; + toMove->miracle = true; + } // useability tweak - assume that the user is probably going to want to see the new card, // so prefetch it. @@ -243,12 +248,7 @@ void MTGPlayerCards::drawFromLibrary() if(putInZone(toMove, library, hand)) { toMove->currentZone = hand; - if (!library->miracle) - { - library->miracle = true; - toMove->miracle = true; } - } } void MTGPlayerCards::resetLibrary() @@ -285,6 +285,11 @@ void MTGPlayerCards::showHand() // Moves a card to its owner's graveyard MTGCardInstance * MTGPlayerCards::putInGraveyard(MTGCardInstance * card) { + if (card->basicAbilities[(int)Constants::EXILEDEATH]) + { + putInZone(card, card->getCurrentZone(), card->owner->game->exile); + + } return putInZone(card, card->currentZone, card->owner->game->graveyard); } @@ -308,7 +313,7 @@ MTGCardInstance * MTGPlayerCards::putInHand(MTGCardInstance * card) // Moves a card from one zone to another // If the card is not actually in the expected "from" zone, does nothing and returns null -MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone * from, MTGGameZone * to) +MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone * from, MTGGameZone * to,bool asCopy) { MTGCardInstance * copy = NULL; GameObserver *g = owner->getObserver(); @@ -325,7 +330,10 @@ MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone if (!(copy = from->removeCard(card, doCopy))) return NULL; //ERROR - + if (card->miracle) + { + copy->miracle = true; + } if (options[Options::SFXVOLUME].number > 0) { if (to == g->players[0]->game->graveyard || to == g->players[1]->game->graveyard) @@ -374,8 +382,11 @@ MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone SAFE_DELETE(previous); } } + if(!asCopy) + { WEvent * e = NEW WEventZoneChange(copy, from, to); g->receiveEvent(e); + } return ret; } diff --git a/projects/mtg/src/MTGRules.cpp b/projects/mtg/src/MTGRules.cpp index 29bb9dc65..13ebd3127 100644 --- a/projects/mtg/src/MTGRules.cpp +++ b/projects/mtg/src/MTGRules.cpp @@ -690,8 +690,8 @@ int MTGAlternativeCostRule::reactToClick(MTGCardInstance * card, ManaCost *alter assert(alternateCost); if (alternateCost->isExtraPaymentSet() ) - { - if (!game->targetListIsSet(card)) + { + if (!game->targetListIsSet(card)) return 0; } else @@ -2457,7 +2457,7 @@ int MTGLegendRule::canBeInList(MTGCardInstance * card) { if(card->isPhased) return 0; - if (card->hasType(Subtypes::TYPE_LEGENDARY) && game->isInPlay(card)) + if (card->hasType(Subtypes::TYPE_LEGENDARY) && card->controller()->game->inPlay->hasCard(card)) { if(card->has(Constants::NOLEGEND)) return 0; @@ -2471,18 +2471,38 @@ int MTGLegendRule::added(MTGCardInstance * card) { map::iterator it; int destroy = 0; + + vectoroldCards; for (it = cards.begin(); it != cards.end(); it++) { MTGCardInstance * comparison = (*it).first; - if (comparison != card && !(comparison->getName().compare(card->getName()))) + if (comparison != card && comparison->controller() == card->controller() && !(comparison->getName().compare(card->getName()))) { - comparison->controller()->game->putInGraveyard(comparison); + oldCards.push_back(comparison); destroy = 1; } } - if (destroy) + if(destroy) { - card->owner->game->putInGraveyard(card); + vectorselection; + MultiAbility * multi = NEW MultiAbility(game, game->mLayers->actionLayer()->getMaxId(), card, card, NULL); + for(unsigned int i = 0;i < oldCards.size();i++) + { + AABuryCard *a = NEW AABuryCard(game, game->mLayers->actionLayer()->getMaxId(), card, oldCards[i]); + a->menu = "Keep New"; + a->oneShot = true; + multi->Add(a); + } + multi->oneShot = 1; + MTGAbility * a1 = multi; + selection.push_back(a1); + AABuryCard *b = NEW AABuryCard(game, game->mLayers->actionLayer()->getMaxId(), card, card); + b->menu = "Keep Old"; + b->oneShot = true; + MTGAbility * b1 = b; + selection.push_back(b1); + MTGAbility * menuChoice = NEW MenuAbility(game, game->mLayers->actionLayer()->getMaxId(), card, card,true,selection,card->controller(),"Choose Legend"); + menuChoice->addToGame(); } return 1; } @@ -2517,7 +2537,7 @@ int MTGPlaneWalkerRule::canBeInList(MTGCardInstance * card) { if(card->isPhased) return 0; - if (card->hasType(Subtypes::TYPE_PLANESWALKER) && game->isInPlay(card)) + if (card->hasType(Subtypes::TYPE_PLANESWALKER) && card->controller()->game->inPlay->hasCard(card)) { return 1; } @@ -2528,18 +2548,38 @@ int MTGPlaneWalkerRule::added(MTGCardInstance * card) { map::iterator it; int destroy = 0; + vectoroldCards; for (it = cards.begin(); it != cards.end(); it++) { MTGCardInstance * comparison = (*it).first; - if (comparison != card && comparison->types == card->types) + if (comparison != card && comparison->types == card->types && comparison->controller() == card->controller()) { - comparison->controller()->game->putInGraveyard(comparison); + oldCards.push_back(comparison); destroy = 1; } } if (destroy) { - card->owner->game->putInGraveyard(card); + vectorselection; + + MultiAbility * multi = NEW MultiAbility(game,game->mLayers->actionLayer()->getMaxId(), card, card, NULL); + for(unsigned int i = 0;i < oldCards.size();i++) + { + AABuryCard *a = NEW AABuryCard(game, game->mLayers->actionLayer()->getMaxId(), card, oldCards[i]); + a->menu = "Keep New"; + a->oneShot = true; + multi->Add(a); + } + multi->oneShot = 1; + MTGAbility * a1 = multi; + selection.push_back(a1); + AABuryCard *b = NEW AABuryCard(game, game->mLayers->actionLayer()->getMaxId(), card, card); + b->menu = "Keep Old"; + b->oneShot = true; + MTGAbility * b1 = b; + selection.push_back(b1); + MTGAbility * menuChoice = NEW MenuAbility(game, game->mLayers->actionLayer()->getMaxId(), card, card,true,selection,card->controller(),"Choose Planeswalker"); + menuChoice->addToGame(); } return 1; } diff --git a/projects/mtg/src/ManaCost.cpp b/projects/mtg/src/ManaCost.cpp index f01d30584..25dd77a18 100644 --- a/projects/mtg/src/ManaCost.cpp +++ b/projects/mtg/src/ManaCost.cpp @@ -35,6 +35,12 @@ ManaCost * ManaCost::parseManaCost(string s, ManaCost * _manaCost, MTGCardInstan { case 0: start = s.find_first_of("{"); + if(s.find_first_of("{") != string::npos && start > 0) + { + string value = s.substr(start -1,end); + if(value == "n{")//"restrictio n{m orbid} would read the n{m as {m} millcost + return manaCost; + } if (start == string::npos) { return manaCost; @@ -323,6 +329,7 @@ ManaCost::ManaCost(ManaCost * manaCost) suspend = NEW ManaCost( manaCost->suspend ); extraCosts = manaCost->extraCosts ? manaCost->extraCosts->clone() : NULL; + manaUsedToCast = NULL; xColor = manaCost->xColor; } @@ -350,6 +357,7 @@ ManaCost::ManaCost(const ManaCost& manaCost) suspend = NEW ManaCost( manaCost.suspend ); extraCosts = manaCost.extraCosts ? manaCost.extraCosts->clone() : NULL; + manaUsedToCast = NULL; xColor = manaCost.xColor; } @@ -370,6 +378,7 @@ ManaCost & ManaCost::operator= (const ManaCost & manaCost) FlashBack = manaCost.FlashBack; morph = manaCost.morph; suspend = manaCost.suspend; + manaUsedToCast = manaCost.manaUsedToCast; xColor = manaCost.xColor; } return *this; @@ -385,6 +394,7 @@ ManaCost::~ManaCost() SAFE_DELETE(Retrace); SAFE_DELETE(morph); SAFE_DELETE(suspend); + SAFE_DELETE(manaUsedToCast); cost.erase(cost.begin() ,cost.end()); } @@ -468,6 +478,7 @@ void ManaCost::init() Retrace = NULL; morph = NULL; suspend = NULL; + manaUsedToCast = NULL; isMulti = false; } diff --git a/projects/mtg/src/TargetChooser.cpp b/projects/mtg/src/TargetChooser.cpp index 716f5316c..df9d73b31 100644 --- a/projects/mtg/src/TargetChooser.cpp +++ b/projects/mtg/src/TargetChooser.cpp @@ -87,7 +87,17 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta if (s.length() > 7 && s.find("[") == 7) { string s1 = s.substr(7, s.find("]")); - if (s1.find("to") != string::npos) return NEW TriggerTargetChooser(observer, WEvent::TARGET_TO); + if (s1.find("to") != string::npos) + { + if(s1.find("<1>") != string::npos) + { + TriggerTargetChooser * ttc = NEW TriggerTargetChooser(observer, WEvent::TARGET_TO); + ttc->maxtargets = 1; + return ttc; + } + else + return NEW TriggerTargetChooser(observer, WEvent::TARGET_TO); + } if (s1.find("from") != string::npos) return NEW TriggerTargetChooser(observer, WEvent::TARGET_FROM); } return NEW TriggerTargetChooser(observer, 1); @@ -606,6 +616,16 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta } } + if (attribute.find("iscolorless") != string::npos) + { + attributefound = 1; + for (int cid = 1; cid < Constants::NB_Colors; cid++) + { + cd->SetExclusionColor(cid); + } + cd->mode = CD_OR; + } + if (attribute.find("chosencolor") != string::npos) { attributefound = 1; @@ -688,6 +708,12 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta { return NEW CardTargetChooser(observer, card, card, zones, nbzones); } + else if (typeName.compare("sourcecard") == 0) + { + CardTargetChooser * ctc = NEW CardTargetChooser(observer, card, card, zones, nbzones); + ctc->setAllZones(); + return ctc; + } else if (typeName.compare("mystored") == 0) { return NEW CardTargetChooser(observer, card->storedSourceCard, card, zones, nbzones); @@ -1488,6 +1514,13 @@ bool TriggerTargetChooser::targetsZone(MTGGameZone *) bool TriggerTargetChooser::canTarget(Targetable * _target,bool) { + //something is wrong with trigger[to] not allowing us to target stack. + if(Spell * spell = dynamic_cast(_target)) + { + MTGCardInstance * card = spell->source; + if(card == target) + return true; + } if (_target == target) return true; return false; } diff --git a/projects/mtg/src/TargetsList.cpp b/projects/mtg/src/TargetsList.cpp index 020cfc8c8..0a55009ec 100644 --- a/projects/mtg/src/TargetsList.cpp +++ b/projects/mtg/src/TargetsList.cpp @@ -104,6 +104,11 @@ MTGCardInstance * TargetsList::getNextCardTarget(MTGCardInstance * previous) size_t nextIndex = iterateTarget(previous); for (size_t i = nextIndex; i < targets.size(); ++i) { + if (Spell * spell = dynamic_cast(targets[i])) + { + if (MTGCardInstance * c = dynamic_cast(spell->source)) + return c; + } if (MTGCardInstance * c = dynamic_cast(targets[i])) return c; }