diff --git a/projects/mtg/bin/Res/rules/mtg.txt b/projects/mtg/bin/Res/rules/mtg.txt index 202dcc3cb..ee66c6c62 100644 --- a/projects/mtg/bin/Res/rules/mtg.txt +++ b/projects/mtg/bin/Res/rules/mtg.txt @@ -35,6 +35,7 @@ auto=lifelinkrule auto=deathtouchrule auto=soulbondrule auto=dredgerule +auto=bestowrule [PLAYERS] life:20 diff --git a/projects/mtg/include/AllAbilities.h b/projects/mtg/include/AllAbilities.h index 6c2eb578b..87ae29c9d 100644 --- a/projects/mtg/include/AllAbilities.h +++ b/projects/mtg/include/AllAbilities.h @@ -38,6 +38,117 @@ public: virtual MTGEventText * clone() const; }; +class MTGRevealingCards : public MTGAbility, public CardDisplay +{ +public: + vector cards; + Player * playerForZone; + MTGGameZone * RevealZone; + MTGGameZone * RevealFromZone; + string revealCertainTypes; + string revealUntil; + + CardDisplay * revealDisplay; + vectortrashDisplays;//used for repeat + int nbCard; + string abilityString; + string number; + string abilityOne; + string abilityTwo; + string afterReveal; + bool afterEffectActivated; + MTGAbility * abilityToCast; + MTGAbility * abilityFirst; + MTGAbility * abilitySecond; + MTGAbility * abilityAfter; + vectorabilities; + bool repeat;//only the first ability can be repeated, and it must be targeted. + bool initCD; + + void Update(float dt); + int testDestroy(); + int toResolve(); + void CardViewBackup(MTGCardInstance * backup); + void Render(); + bool CheckUserInput(JButton key); + MTGAbility * contructAbility(string abilityToMake = ""); + MTGRevealingCards(GameObserver* observer, int _id, MTGCardInstance * card, string text); + virtual MTGRevealingCards * clone() const; + ~MTGRevealingCards(); + int receiveEvent(WEvent*); +}; + +class RevealDisplay : public CardDisplay +{ +public: + RevealDisplay(int id, GameObserver* game, int x, int y, JGuiListener * listener = NULL, TargetChooser * tc = NULL, + int nb_displayed_items = 7); + void AddCard(MTGCardInstance * _card); + bool CheckUserInput(JButton key); +}; + +class GenericRevealAbility : public ActivatedAbility +{ +public: + string howMany; + MTGRevealingCards * ability; + GenericRevealAbility(GameObserver* observer, int id, MTGCardInstance * source, Targetable * target, string _howMany); + int resolve(); + const string getMenuText(); + GenericRevealAbility * clone() const; + ~GenericRevealAbility(); + +}; + +class MTGScryCards : public MTGAbility, public CardDisplay +{ +public: + vector cards; + MTGGameZone * RevealZone; + MTGGameZone * RevealFromZone; + + CardDisplay * revealDisplay; + vectortrashDisplays;//used for repeat + int nbCard; + bool delayed; + bool dontRevealAfter; + int revealTopAmount; + string delayedAbilityString; + string abilityString; + string number; + string abilityOne; + string abilityTwo; + MTGAbility * abilityToCast; + MTGAbility * abilityFirst; + MTGAbility * abilitySecond; + vectorabilities; + bool initCD; + void Update(float dt); + int testDestroy(); + void initDisplay(int value = 0); + int toResolve(); + void Render(); + bool CheckUserInput(JButton key); + MTGAbility * contructAbility(string abilityToMake = ""); + MTGScryCards(GameObserver* observer, int _id, MTGCardInstance * card, string text); + virtual MTGScryCards * clone() const; + ~MTGScryCards(); + int receiveEvent(WEvent*); +}; + +class GenericScryAbility : public ActivatedAbility +{ +public: + string howMany; + MTGScryCards * ability; + GenericScryAbility(GameObserver* observer, int id, MTGCardInstance * source, Targetable * target, string _howMany); + int resolve(); + const string getMenuText(); + GenericScryAbility * clone() const; + ~GenericScryAbility(); + +}; + class WParsedInt { public: @@ -240,6 +351,10 @@ private: { intValue = countDevotionTo(card,card->controller()->inPlay(),Constants::MTG_COLOR_BLUE,Constants::MTG_COLOR_GREEN); } + else if (s == "Iroas")//devotion to red white + { + intValue = countDevotionTo(card, card->controller()->inPlay(), Constants::MTG_COLOR_RED, Constants::MTG_COLOR_WHITE); + } else if (s.find("type:") != string::npos) { size_t begins = s.find("type:"); @@ -556,6 +671,10 @@ private: { intValue = target->getCurrentToughness(); } + else if (s == "countedamount") + { + intValue = target->CountedObjects; + } else if (s == "kicked") { intValue = target->kicked; @@ -745,6 +864,21 @@ private: intValue += card->controller()->game->inPlay->cards[j]->power; } } + else if (s == "revealedp") + { + if (card->revealedLast) + intValue = card->revealedLast->power; + } + else if (s == "revealedt") + { + if (card->revealedLast) + intValue = card->revealedLast->toughness; + } + else if (s == "revealedmana") + { + if (card->revealedLast) + intValue = card->revealedLast->getManaCost()->getConvertedCost(); + } else { intValue = atoi(s.c_str()); @@ -2855,7 +2989,6 @@ public: return NEW ARegularLifeModifierAura(*this); } }; - //Generic Kird Ape class AAsLongAs: public ListMaintainerAbility, public NestedAbility { @@ -3334,6 +3467,7 @@ public: list colors; int power, toughness; int tokenId; + string _cardName; string name; string sabilities; string starfound; @@ -3345,6 +3479,7 @@ public: MTGCardInstance * myToken; vector currentAbilities; Player * tokenReciever; + //by id ATokenCreator(GameObserver* observer, int _id, MTGCardInstance * _source, Targetable *, ManaCost * _cost, int tokenId,string starfound, WParsedInt * multiplier = NULL, int who = 0,bool aLivingWeapon = false) : ActivatedAbility(observer, _id, _source, _cost, 0), tokenId(tokenId), starfound(starfound),multiplier(multiplier), who(who),aLivingWeapon(aLivingWeapon) @@ -3354,7 +3489,18 @@ public: if (card) name = card->data->getName(); battleReady = false; } - + //by name, card still require valid card.dat info, this just makes the primitive code far more readable. token(Eldrazi scion) instead of token(-1234234)... + ATokenCreator(GameObserver* observer, int _id, MTGCardInstance * _source, Targetable *, ManaCost * _cost, string cardName, string starfound, WParsedInt * multiplier = NULL, + int who = 0, bool aLivingWeapon = false) : + ActivatedAbility(observer, _id, _source, _cost, 0), _cardName(cardName), starfound(starfound), multiplier(multiplier), who(who), aLivingWeapon(aLivingWeapon) + { + if (!multiplier) this->multiplier = NEW WParsedInt(1); + MTGCard * card = MTGCollection()->getCardByName(_cardName); + tokenId = card->getId(); + if (card) name = card->data->getName(); + battleReady = false; + } + //by construction ATokenCreator(GameObserver* observer, int _id, MTGCardInstance * _source, Targetable *, ManaCost * _cost, string sname, string stypes, int _power, int _toughness, string sabilities, string starfound,WParsedInt * multiplier = NULL, int _who = 0,bool aLivingWeapon = false,string spt = "") : ActivatedAbility(observer, _id, _source, _cost, 0),sabilities(sabilities),starfound(starfound), multiplier(multiplier), who(_who),aLivingWeapon(aLivingWeapon),spt(spt) @@ -3877,10 +4023,12 @@ class AThis: public MTGAbility, public NestedAbility public: MTGAbility * a; ThisDescriptor * td; - AThis(GameObserver* observer, int _id, MTGCardInstance * _source, Damageable * _target, ThisDescriptor * _td, MTGAbility * ability) : + string restrictionCheck; + AThis(GameObserver* observer, int _id, MTGCardInstance * _source, Damageable * _target, ThisDescriptor * _td, MTGAbility * ability, string restriction = "") : MTGAbility(observer, _id, _source, _target), NestedAbility(ability) { td = _td; + restrictionCheck = restriction; ability->source = source; ability->target = target; a = NULL; @@ -3904,9 +4052,18 @@ public: int resolve() { - //TODO check if ability is oneShot ? - int match; - match = td->match(source); + int match = 0; + if (td) + { + match = td->match(source); + } + else + {//restriction check instead of Targetchooser + AbilityFactory abf(target->getObserver()); + int checkCond = abf.parseCastRestrictions(source, source->controller(), restrictionCheck); + if (checkCond) + match = 1; + } if (match > 0) { addAbilityToGame(); @@ -3953,6 +4110,7 @@ public: { AThis * a = NEW AThis(*this); a->ability = ability->clone(); + if(a->td) a->td = td->clone(); return a; } @@ -4166,6 +4324,16 @@ public: return NEW TADamager(*this); } }; +//bestow +class ABestow : public ActivatedAbility +{ +public: + MTGCardInstance * _card; + ABestow(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost = NULL); + int resolve(); + const string getMenuText(); + ABestow * clone() const; +}; /* Can tap a target for a cost */ class AATapper: public ActivatedAbility @@ -4197,12 +4365,22 @@ public: int resolve(); AAWhatsMax * clone() const; }; +//counts a targetchooser for use later by other effects +class AACountObject : public ActivatedAbility +{ +public: + string value; + AACountObject(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * source, ManaCost * _cost = NULL, string value =""); + int resolve(); + AACountObject * clone() const; +}; /* Can prevent a card from untapping next untap */ class AAFrozen: public ActivatedAbility { public: - AAFrozen(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost = NULL); + bool freeze; + AAFrozen(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, bool tap, ManaCost * _cost = NULL); int resolve(); const string getMenuText(); AAFrozen * clone() const; @@ -6205,7 +6383,8 @@ public: MTGCardInstance * theNamedCard; bool noEvent; bool putinplay; - AACastCard(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target,bool restricted,bool copied,bool _asNormal,string nameCard,string abilityName,bool _noEvent, bool putinplay); + bool asNormalMadness; + AACastCard(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target,bool restricted,bool copied,bool _asNormal,string nameCard,string abilityName,bool _noEvent, bool putinplay,bool asNormalMadness = false); int testDestroy(){return 0;}; void Update(float dt); diff --git a/projects/mtg/include/GameObserver.h b/projects/mtg/include/GameObserver.h index 72bbf687c..03fe778c2 100644 --- a/projects/mtg/include/GameObserver.h +++ b/projects/mtg/include/GameObserver.h @@ -80,6 +80,7 @@ class GameObserver{ ExtraCosts * mExtraPayment; int oldGamePhase; TargetChooser * targetChooser; + CardDisplay * OpenedDisplay; DuelLayers * mLayers; ReplacementEffects *replacementEffects; vector players; //created outside diff --git a/projects/mtg/include/MTGAbility.h b/projects/mtg/include/MTGAbility.h index ee2ca98f4..80f1d8447 100644 --- a/projects/mtg/include/MTGAbility.h +++ b/projects/mtg/include/MTGAbility.h @@ -121,6 +121,7 @@ public: ManaCost* BuyBack; ManaCost* FlashBack; ManaCost* Retrace; + ManaCost* Bestow; ManaCost* morph; ManaCost* suspend; diff --git a/projects/mtg/include/MTGCardInstance.h b/projects/mtg/include/MTGCardInstance.h index 045044d4b..2f1923635 100644 --- a/projects/mtg/include/MTGCardInstance.h +++ b/projects/mtg/include/MTGCardInstance.h @@ -95,6 +95,7 @@ public: bool exileEffects; bool suspended; bool miracle; + bool isBestowed; int chooseacolor; string chooseasubtype; int coinSide;//1 = tails @@ -105,6 +106,7 @@ public: int notblocked; int fresh; int MaxLevelUp; + int CountedObjects; int kicked; int dredge; bool isDualWielding; @@ -272,6 +274,8 @@ public: string currentimprintName; vectorimprintedNames; + MTGCardInstance * revealedLast;//last card revealed by a ability this card owns. + bool MadnessPlay; void eventattacked(); void eventattackedAlone(); void eventattackednotblocked(); diff --git a/projects/mtg/include/MTGDefinitions.h b/projects/mtg/include/MTGDefinitions.h index ef9547b56..ff19c6e15 100644 --- a/projects/mtg/include/MTGDefinitions.h +++ b/projects/mtg/include/MTGDefinitions.h @@ -82,6 +82,7 @@ class Constants static const string kRetraceKeyword; static const string kKickerKeyword; static const string kMorphKeyword; + static const string kBestowKeyword; // used for deck statistics static const int STATS_FOR_TURNS = 8; @@ -246,8 +247,11 @@ class Constants COMBATTOUGHNESS = 125, CANTPAYLIFE = 126, CANTBESACRIFIED = 127, - NB_BASIC_ABILITIES = 128, - + SKULK = 128, + MENACE = 129, + NOSOLO = 130,//cant attack alone + MUSTBLOCK = 131,//blocks each turn + NB_BASIC_ABILITIES = 132, RARITY_S = 'S', //Special Rarity RARITY_M = 'M', //Mythics @@ -308,6 +312,7 @@ class Constants CAST_WITH_RETRACE = 6, CAST_WITH_MORPH = 7, CAST_WITH_SUSPEND = 8, + CAST_WITH_BESTOW = 9, CAST_ALTERNATE = -1, //matches all alternate costs, including itself CAST_ALL = -2, // matches everything except NOT_CAST diff --git a/projects/mtg/include/MTGGameZones.h b/projects/mtg/include/MTGGameZones.h index bc44c76ac..b37b555bf 100644 --- a/projects/mtg/include/MTGGameZones.h +++ b/projects/mtg/include/MTGGameZones.h @@ -72,6 +72,14 @@ class MTGGameZone { OWNER_STACK = 66, TARGETED_PLAYER_STACK = 67, + MY_REVEAL = 71, + OPPONENT_REVEAL = 72, + TARGET_OWNER_REVEAL = 73, + TARGET_CONTROLLER_REVEAL = 74, + REVEAL = 75, + OWNER_REVEAL = 76, + TARGETED_PLAYER_REVEAL = 77, + }; Player * owner; @@ -194,6 +202,7 @@ public: MTGRemovedFromGame * exile; //alias to removedFromZone MTGGameZone * garbage; MTGGameZone * garbageLastTurn; + MTGGameZone * reveal; MTGGameZone * temp; MTGPlayerCards(); diff --git a/projects/mtg/include/MTGRules.h b/projects/mtg/include/MTGRules.h index a4f783111..355932ccf 100644 --- a/projects/mtg/include/MTGRules.h +++ b/projects/mtg/include/MTGRules.h @@ -206,6 +206,21 @@ public: virtual MTGOverloadRule * clone() const; }; +class MTGBestowRule : public MTGAlternativeCostRule +{ +public: + int isReactingToClick(MTGCardInstance * card, ManaCost * mana = NULL); + int reactToClick(MTGCardInstance * card); + virtual ostream& toString(ostream& out) const; + MTGBestowRule(GameObserver* observer, int _id); + const string getMenuText() + { + return "Bestow"; + } + virtual MTGBestowRule * clone() const; +}; + + class MTGSuspendRule: public MTGAlternativeCostRule { public: diff --git a/projects/mtg/include/ManaCost.h b/projects/mtg/include/ManaCost.h index c121c9500..205dead10 100644 --- a/projects/mtg/include/ManaCost.h +++ b/projects/mtg/include/ManaCost.h @@ -31,6 +31,7 @@ protected: ManaCost * manaUsedToCast; ManaCost * morph; ManaCost * Retrace; + ManaCost * Bestow; ManaCost * FlashBack; ManaCost * BuyBack; ManaCost * kicker; @@ -75,6 +76,9 @@ public: ManaCost * getSuspend(){ return suspend; }; void setSuspend(ManaCost * aMana){ SAFE_DELETE(suspend); suspend = aMana;}; + ManaCost * getBestow() { return Bestow; }; + void setBestow(ManaCost * aMana) { SAFE_DELETE(Bestow); Bestow = aMana; }; + ManaCost * getManaUsedToCast(){ return manaUsedToCast; }; void setManaUsedToCast(ManaCost * aMana){ SAFE_DELETE(manaUsedToCast); manaUsedToCast = aMana;}; diff --git a/projects/mtg/include/TargetChooser.h b/projects/mtg/include/TargetChooser.h index 97be56c8e..a457024cb 100644 --- a/projects/mtg/include/TargetChooser.h +++ b/projects/mtg/include/TargetChooser.h @@ -25,6 +25,7 @@ class TargetChooser: public TargetsList protected: int forceTargetListReady; public: + int forceTargetListReadyByPlayer; const static int UNLITMITED_TARGETS = 1000; enum { diff --git a/projects/mtg/src/AIPlayerBaka.cpp b/projects/mtg/src/AIPlayerBaka.cpp index 561b8ee65..5dabaa2fa 100644 --- a/projects/mtg/src/AIPlayerBaka.cpp +++ b/projects/mtg/src/AIPlayerBaka.cpp @@ -1221,7 +1221,7 @@ int AIPlayerBaka::createAbilityTargets(MTGAbility * a, MTGCardInstance * c, Rank for (int i = 0; i < 2; i++) { Player * p = observer->players[i]; - MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay,p->game->stack,p->game->exile }; + MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay,p->game->stack,p->game->exile, p->game->reveal }; if(a->getActionTc()->canTarget((Targetable*)p)) { if(a->getActionTc()->maxtargets == 1) @@ -1352,7 +1352,7 @@ int AIPlayerBaka::selectHintAbility() int AIPlayerBaka::selectAbility() { - if(observer->mExtraPayment && observer->mExtraPayment->source->controller() == this) + if(observer->mExtraPayment && observer->mExtraPayment->source && observer->mExtraPayment->source->controller() == this) { ExtraManaCost * check = NULL; check = dynamic_cast(observer->mExtraPayment->costs[0]); @@ -1560,8 +1560,8 @@ int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCard } } MTGPlayerCards * playerZones = target->game; - MTGGameZone * zones[] = { playerZones->hand, playerZones->library, playerZones->inPlay, playerZones->graveyard,playerZones->stack,playerZones->exile }; - for (int j = 0; j < 6; j++) + MTGGameZone * zones[] = { playerZones->hand, playerZones->library, playerZones->inPlay, playerZones->graveyard,playerZones->stack,playerZones->exile,playerZones->reveal }; + for (int j = 0; j < 7; j++) { MTGGameZone * zone = zones[j]; for (int k = 0; k < zone->nb_cards; k++) diff --git a/projects/mtg/src/ActionLayer.cpp b/projects/mtg/src/ActionLayer.cpp index 28f5453d9..cc68deee9 100644 --- a/projects/mtg/src/ActionLayer.cpp +++ b/projects/mtg/src/ActionLayer.cpp @@ -77,7 +77,7 @@ void ActionLayer::cleanGarbage() int ActionLayer::reactToClick(ActionElement * ability, MTGCardInstance * card) { - int result = ability->reactToClick(card); + int result = ability?ability->reactToClick(card):0; if (result) stuffHappened = 1; return result; @@ -186,8 +186,11 @@ void ActionLayer::Update(float dt) without this, the game locks into a freeze state while you try to select the targets and dont have enough to fill the maxtargets list. */ - if(int(ae->getActionTc()->getNbTargets()) == countTargets-1) - ae->getActionTc()->done = true; + if (int(ae->getActionTc()->getNbTargets()) == countTargets)//if the amount of targets is equal the all we can target + { + ae->getActionTc()->done = true;//were done + ae->getActionTc()->source->getObserver()->cardClick(ae->getActionTc()->source, 0, false);//click source. + } } } } diff --git a/projects/mtg/src/ActionStack.cpp b/projects/mtg/src/ActionStack.cpp index a0246881d..40546d735 100644 --- a/projects/mtg/src/ActionStack.cpp +++ b/projects/mtg/src/ActionStack.cpp @@ -578,6 +578,10 @@ int ActionStack::addAbility(MTGAbility * ability) if (!observer->players[0]->isAI() && ability->source->controller() == observer->players[0] && 0 == options[Options::INTERRUPTMYABILITIES].number) interruptDecision[0] = DONT_INTERRUPT; + if (observer->OpenedDisplay && observer->players[0]->game->reveal->cards.size()) + { + interruptDecision[0] = DONT_INTERRUPT; + } return result; } diff --git a/projects/mtg/src/AllAbilities.cpp b/projects/mtg/src/AllAbilities.cpp index 2aaaf1987..cbad00c7e 100644 --- a/projects/mtg/src/AllAbilities.cpp +++ b/projects/mtg/src/AllAbilities.cpp @@ -43,6 +43,838 @@ MTGEventText * MTGEventText::clone() const return NEW MTGEventText(*this); } +//generic activated ability for wrapping reveals. +GenericRevealAbility::GenericRevealAbility(GameObserver* observer, int id, MTGCardInstance * source, + Targetable * target, string _howMany) : + ActivatedAbility(observer, id, source, NULL), howMany(_howMany) +{ + this->GetId(); +} + +int GenericRevealAbility::resolve() +{ + MTGAbility * ability = NEW MTGRevealingCards(game, this->GetId(), source, howMany); + ability->addToGame(); + return 1; +} + +const string GenericRevealAbility::getMenuText() +{ + return "Reveal Cards"; +} + +GenericRevealAbility * GenericRevealAbility::clone() const +{ + GenericRevealAbility * a = NEW GenericRevealAbility(*this); + return a; +} + +GenericRevealAbility::~GenericRevealAbility() +{ + //SAFE_DELETE(ability); +} + +//carddisplay created for use in abilities. +RevealDisplay::RevealDisplay(int id, GameObserver* game, int x, int y, JGuiListener * listener, TargetChooser * tc, + int nb_displayed_items) : + CardDisplay(id, game, x, y, listener, tc, nb_displayed_items) +{ +} + +void RevealDisplay::AddCard(MTGCardInstance * _card) +{ + CardGui * card = NEW CardView(CardView::nullZone, _card, static_cast (x + 20 + (mObjects.size() - start_item) * 30), + static_cast (y + 25)); + Add(card); +} + +bool RevealDisplay::CheckUserInput(JButton key) +{ + if (JGE_BTN_SEC == key || JGE_BTN_PRI == key || JGE_BTN_UP == key || JGE_BTN_DOWN == key) + return false; + + return CardDisplay::CheckUserInput(key); +} + +//display card selector box of specified zone. +MTGRevealingCards::MTGRevealingCards(GameObserver* observer, int _id, MTGCardInstance * card, string coreAbility) : + MTGAbility(observer, _id, card), CardDisplay(_id, game, x, y, listener, NULL, nb_displayed_items) + +{ + abilityToCast = NULL; + revealDisplay = NULL; + abilityFirst = NULL; + abilitySecond = NULL; + abilityString = coreAbility; + initCD = false; + + afterReveal = ""; + afterEffectActivated = false; + + repeat = false; + playerForZone = NULL; + revealCertainTypes = ""; + revealUntil = ""; + + if (card->playerTarget) + playerForZone = card->playerTarget; + else + playerForZone = source->controller(); + + RevealZone = playerForZone->game->reveal; + zone = RevealZone; + RevealFromZone = playerForZone->game->library; + + vectoramount = parseBetween(coreAbility, "", " "); + if (amount.size()) + { + number = amount[1]; + } + + vectordifferentZone = parseBetween(coreAbility, "revealzone(", ")"); + if (differentZone.size()) + { + RevealFromZone = MTGGameZone::stringToZone(game,differentZone[1],source,NULL); + } + + vectorcertainTypes = parseBetween(coreAbility, "revealtype(", ")"); + if (certainTypes.size()) + { + revealCertainTypes = certainTypes[1]; + } + + vectorRevealCardUntil = parseBetween(coreAbility, "revealuntil(", ")"); + if (RevealCardUntil.size()) + { + revealUntil = RevealCardUntil[1]; + } + + vectorfirst = parseBetween(coreAbility, "optionone ", " optiononeend"); + if (first.size()) + { + abilityOne = first[1]; + } + vectorsecond = parseBetween(coreAbility, "optiontwo ", " optiontwoend"); + if (second.size()) + { + abilityTwo = second[1]; + } + vectorafterEffect = parseBetween(coreAbility, "afterrevealed ", " afterrevealedend"); + if (afterEffect.size()) + { + afterReveal = afterEffect[1]; + } + + repeat = coreAbility.find("repeat") != string::npos; + +} + +void MTGRevealingCards::Update(float dt) +{ + + if (game->OpenedDisplay != this->revealDisplay && !initCD)//wait your turn + { + //if any carddisplays are open, dont do anything until theyre closed, then wait your turn if multiple reveals trigger. + return; + } + if (game->mLayers->actionLayer()->menuObject) + return;//dont do any of this if a menuobject exist. + if (!source->getObserver()->mLayers->actionLayer()->getCurrentTargetChooser() && !revealDisplay && !initCD) + { + + WParsedInt nbCardP(number, NULL, source); + nbCard = nbCardP.getValue(); + int adjust = 0; + switch (nbCard) + { + //adjust length and location of carddisplay box. + case 1:adjust = 120; break; + case 2:adjust = 145; break; + case 3:adjust = 175; break; + case 4:adjust = 200; break; + case 5:adjust = 225; break; + default:adjust = 225; break; + } + if (revealUntil.size()) + { + adjust = 225; + revealDisplay = NEW RevealDisplay(1, game, SCREEN_WIDTH - adjust, SCREEN_HEIGHT, listener, NULL,5); + } + else + revealDisplay = NEW RevealDisplay(1, game, SCREEN_WIDTH - adjust, SCREEN_HEIGHT, listener, NULL, nbCard > 5 ? 5 : nbCard); + revealDisplay->zone = RevealFromZone; + trashDisplays.push_back(revealDisplay); + + if (revealCertainTypes.size())//revealing cards of a TARGETCHOOSER type. + { + TargetChooserFactory tcf(game); + TargetChooser * rTc = tcf.createTargetChooser(revealCertainTypes, source); + int startingNumber = RevealFromZone->nb_cards - 1; + if (rTc) + for (int i = startingNumber; i > -1; i--) + { + if (!RevealFromZone->cards.size()) + break; + MTGCardInstance * toMove = RevealFromZone->cards[i]; + if (toMove) + { + if (rTc->canTarget(toMove, true)) + { + CardViewBackup(toMove); + playerForZone->game->putInZone(toMove, RevealFromZone, RevealZone); + source->revealedLast = toMove; + } + } + + } + SAFE_DELETE(rTc); + } + else if(revealUntil.size())//reveal cards until you reveal a TARGETCHOOSER. + { + TargetChooserFactory tcf(game); + TargetChooser * rUc = tcf.createTargetChooser(revealUntil, source); + bool foundCard = false; + int howMany = nbCard; + int startingNumber = RevealFromZone->nb_cards; + for (int i = 0; i < startingNumber; i++) + { + if (foundCard && howMany == 0) + break; + if (howMany == 0) + break; //not allowed to reveal until 0 of something is revealed. + if (RevealFromZone->nb_cards - 1 < 0) + break; + MTGCardInstance * toMove = RevealFromZone->cards[RevealFromZone->nb_cards - 1]; + if (toMove) + { + if (rUc->canTarget(toMove, true)) + { + foundCard = true; + howMany--; + } + + CardViewBackup(toMove); + playerForZone->game->putInZone(toMove, RevealFromZone, RevealZone); + source->revealedLast = toMove; + } + + } + SAFE_DELETE(rUc); + } + else + { + for (int i = 0; i < nbCard; i++)//normal reveal + { + if (RevealFromZone->nb_cards - 1 < 0) + break; + MTGCardInstance * toMove = RevealFromZone->cards[RevealFromZone->nb_cards - 1]; + if (toMove) + { + CardViewBackup(toMove); + playerForZone->game->putInZone(toMove, RevealFromZone, RevealZone); + source->revealedLast = toMove; + } + + } + + } + + //build the zone, create the first ability. + revealDisplay->init(RevealZone); + revealDisplay->zone = RevealZone; + game->OpenedDisplay = revealDisplay; + toResolve(); + initCD = true; + } + + + //card display is ready and loaded, abilities have fired at this point. + //critical for testdestroy, a function that determines if a ability can + //exist in condiations such as source not being in play. + + if (!zone->cards.size()) + { + //all possible actions are done, the zone is empty, lets NULL it so it clears it off the screen. + //DO NOT SAFE_DELETE here, it destroys the card->view and backups kept for the second ability. + revealDisplay = NULL; + game->OpenedDisplay = revealDisplay; + + if (repeat) + { + initCD = false; + } + else if (afterReveal.size() && !afterEffectActivated) + { + afterEffectActivated = true; + abilityAfter = contructAbility(afterReveal); + game->addObserver(abilityAfter); + } + else + this->removeFromGame(); + } + + if (revealDisplay) + { + revealDisplay->Update(dt); + Render(); + } + + MTGAbility::Update(dt); +} + +void MTGRevealingCards::CardViewBackup(MTGCardInstance * backup) +{ + CardView* t; + + t = NEW CardView(CardView::nullZone, backup, 0, 0); + //we store copies of the card view since the safe_delete of card displays also deletes the guis stored in them. + t->actX = SCREEN_WIDTH; + t->actY = SCREEN_HEIGHT * -2; + //correct cards x and y, last known location was the reveal display. + cards.push_back(t); + return; +} + +int MTGRevealingCards::testDestroy() +{ + if (game->mExtraPayment) + return 0; + if (revealDisplay) + return 0; + if (zone->cards.size()) + return 0; + if (!initCD) + return 0; + if (game->mLayers->actionLayer()->menuObject) + return 0; + if (game->mLayers->actionLayer()->getIndexOf(abilityFirst) != -1) + return 0; + + return 1; +} + +int MTGRevealingCards::toResolve() +{ + + TargetChooserFactory tcf(game); + vectorsplitTarget = parseBetween(abilityOne, "target(", ")"); + //we build a tc to check if the first ability has any valid targets, if it doesnt, just add the 2nd one. + if (splitTarget.size()) + { + TargetChooser * rTc = tcf.createTargetChooser(splitTarget[1].c_str(), source); + + if (rTc && rTc->countValidTargets()) + { + abilityFirst = contructAbility(abilityOne); + game->addObserver(abilityFirst); + + } + else + { + repeat = false; + abilitySecond = contructAbility(abilityTwo); + game->addObserver(abilitySecond); + + } + SAFE_DELETE(rTc); + } + else//the first ability is not targeted + { + abilityFirst = contructAbility(abilityOne); + game->addObserver(abilityFirst); + } + return 1; +} + +MTGAbility * MTGRevealingCards::contructAbility(string abilityToMake) +{ + AbilityFactory af(game); + abilityToCast = af.parseMagicLine(abilityToMake, getMaxId(), NULL, source, false); + if (!abilityToCast) + return NULL; + abilityToCast->canBeInterrupted = false; + abilityToCast->forceDestroy = 1; + return abilityToCast; +} + +void MTGRevealingCards::Render() +{ + if (!revealDisplay) + return; + CheckUserInput(mEngine->ReadButton()); + revealDisplay->CheckUserInput(mEngine->ReadButton()); + revealDisplay->Render(); + return; +} + +bool MTGRevealingCards::CheckUserInput(JButton key) +{ + //DO NOT REFACTOR BELOW, IT KEPT SPLIT UP TO MAINTAIN READABILITY. + //we override check inputs, we MUST complete reveal and its effects before being allowed to do anything else. + TargetChooser * tc = this->observer->mLayers->actionLayer()->getCurrentTargetChooser(); + if (this->source->controller()->isAI()) + { + if (this->source->controller() != game->isInterrupting) + game->mLayers->stackLayer()->cancelInterruptOffer(ActionStack::DONT_INTERRUPT, false); + } + if (JGE_BTN_SEC == key || JGE_BTN_PREV == key || JGE_BTN_NEXT == key || JGE_BTN_MENU == key)//android back button + { + if (tc && (tc->targetMin == false || tc->maxtargets == TargetChooser::UNLITMITED_TARGETS)) + { + tc->done = true; + tc->forceTargetListReadyByPlayer = 1; + //this is for when we have targets but only want to move Y targets, it allows us to + //tell the targetchooser we are done. + if (!abilitySecond && !tc->getNbTargets() && tc->source) + {//we selected nothing for the first ability. + tc->source->getObserver()->cardClick(tc->source, 0, false); + if (abilityFirst)///some abilities resolve themselves and remove faster than you can removethem from the game. + { + abilityFirst->removeFromGame(); + game->mLayers->stackLayer()->Remove(abilityFirst); + } + game->Update(0); + //remove it from the game, update, and remove it from stack if needed. + //before adding next ability, otherwise we end up with a menu reactToClick. + if (zone->cards.size() && abilityFirst->testDestroy())//generally only want to add ability 2 if anything is left in the zone. + { + repeat = false; + abilitySecond = contructAbility(abilityTwo); + game->addObserver(abilitySecond); + } + } + else if (tc->source) + { + tc->source->getObserver()->cardClick(tc->source, 0, false); + } + } + else if (!tc && !abilitySecond)//the actions of the first card have finished and we're done looking at the cards. + { //or the first ability was an "all(" which was not a mover ability. + CheckUserInput(JGE_BTN_OK); + } + return false; + } + if (JGE_BTN_OK == key)//for ease if we're sitting there looking at the card display and click a card after first ability. + { //looks redundent and can be added above as another condiational, however we would end up with a massive + //if statement that becomes very very hard to follow. + if (!tc && !abilitySecond) + { + if (abilityFirst) + { + abilityFirst->removeFromGame(); + game->mLayers->stackLayer()->Remove(abilityFirst); + } + game->Update(1); + + if (zone->cards.size()) + { + repeat = false; + abilitySecond = contructAbility(abilityTwo); + game->addObserver(abilitySecond); + } + + } + } + if(revealDisplay) + return revealDisplay->CheckUserInput(key); + return false; +} + +MTGRevealingCards * MTGRevealingCards::clone() const +{ + return NEW MTGRevealingCards(*this); +} + +MTGRevealingCards::~MTGRevealingCards() +{ + for (vector::iterator it = trashDisplays.begin(); it != trashDisplays.end(); ++it) + SAFE_DELETE(*it); + for (vector::iterator it = cards.begin(); it != cards.end(); ++it) + SAFE_DELETE(*it); +} + +int MTGRevealingCards::receiveEvent(WEvent* e) +{ + + if (WEventZoneChange* event = dynamic_cast(e)) + { + if (event->from == zone) + { + CardView* t; + if (event->card->view) + t = NEW CardView(CardView::nullZone, event->card, *(event->card->view)); + //we store copies of the card view since moving to and from card displays also deletes the guis stored in cards. + //GuiLayer::resetObjects() is the main reason we need to back them up. card views are set to NULL maybe more often than + //they should be, possibly someone being to over cautious. + t->actX = SCREEN_WIDTH; + t->actY = SCREEN_HEIGHT * -2; + //correct cards x and y, last known location was the reveal display. + cards.push_back(t); + } + } + return 0; +} + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +////scry////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////// +//below the effect of "scry X, THEN reveal and do stuff, was impossible to accomplish with reveal alone. +//if a card simply states "scry X" and nothing else use reveal:x. +//this is for effects that want you to reveal AFTER you scry. +//this ability automatically creates effects, put on top, then whatever you dont get put on buttom, +//then it reveals the top card, and creates the ability written in primitive as core, then it +//handles putting the card back on top of the library when you are done. +///delayed changes the order, makes the ability fire after the 2nd reveal is finished. +/// +MTGScryCards::MTGScryCards(GameObserver* observer, int _id, MTGCardInstance * card, string coreAbility) : + MTGAbility(observer, _id, card), CardDisplay(_id, game, x, y, listener, NULL, nb_displayed_items) + +{ + abilityToCast = NULL; + revealDisplay = NULL; + abilityFirst = NULL; + abilitySecond = NULL; + abilityString = coreAbility; + delayedAbilityString = ""; + revealTopAmount = 1;//scry, then reveal the top card and do effect. + + initCD = false; + RevealZone = source->controller()->game->reveal; + zone =RevealZone; + RevealFromZone = source->controller()->game->library; + + vectoramount = parseBetween(coreAbility, "", " "); + if (amount.size()) + { + number = amount[1]; + } + + vectordifferentZone = parseBetween(coreAbility, "scryzone(", ")"); + if (differentZone.size()) + { + RevealFromZone = MTGGameZone::stringToZone(game, differentZone[1], source, NULL); + } + + abilityOne = "name(Place on top) target(*|myreveal) moveto(mylibrary)"; + delayed = coreAbility.find("delayed") != string::npos; + dontRevealAfter = coreAbility.find("dontshow") != string::npos; + if(dontRevealAfter) + revealTopAmount = 0; + vectorsecond = parseBetween(coreAbility, "scrycore ", " scrycoreend"); + if (second.size()) + { + if (delayed) + { + abilityTwo = "target(*|reveal) name(Reveal the top card) donothing"; + delayedAbilityString = second[1]; + } + else + abilityTwo = second[1]; + } + + +} + +void MTGScryCards::Update(float dt) +{ + if (game->OpenedDisplay != this->revealDisplay && !initCD) + return; + if (game->mLayers->actionLayer()->menuObject) + return; + if (!source->getObserver()->mLayers->actionLayer()->getCurrentTargetChooser() && !revealDisplay && !initCD) + { + WParsedInt nbCardP(number, NULL, source); + nbCard = nbCardP.getValue(); + initDisplay(nbCard); + toResolve(); + } + initCD = true; + if (!zone->cards.size() && abilitySecond) + { + revealDisplay = NULL; + game->OpenedDisplay = revealDisplay; + this->removeFromGame(); + } + if (revealDisplay) + { + revealDisplay->Update(dt); + Render(); + } + MTGAbility::Update(dt); +} + +void MTGScryCards::initDisplay(int value) +{ + + if (RevealZone->cards.size()) + { + do + { + MTGCardInstance * toMove = RevealZone->cards[0]; + if (toMove) + { + MTGAbility * a = NEW AALibraryBottom(game, getMaxId(), source, toMove); + a->oneShot = 1; + a->resolve(); + SAFE_DELETE(a); + } + } while (RevealZone->cards.size()); + + game->Update(0); + revealDisplay = NULL; + game->OpenedDisplay = revealDisplay; + } + int adjust = 0; + switch (value) + { + case 1:adjust = 120; break; + case 2:adjust = 145; break; + case 3:adjust = 175; break; + case 4:adjust = 200; break; + case 5:adjust = 225; break; + default:adjust = 225; break; + } + revealDisplay = NEW RevealDisplay(1, game, SCREEN_WIDTH - adjust, SCREEN_HEIGHT, listener, NULL, nbCard > 5 ? 5 : nbCard); + revealDisplay->zone = RevealFromZone; + trashDisplays.push_back(revealDisplay); + for (int i = 0; i < value; i++) + { + if (RevealFromZone->nb_cards - 1 < 0) + break; + MTGCardInstance * toMove = RevealFromZone->cards[RevealFromZone->nb_cards - 1]; + if (toMove) + { + CardView* t; + t = NEW CardView(CardView::nullZone, toMove, 0, 0); + t->actX = SCREEN_WIDTH; + t->actY = SCREEN_HEIGHT * -2; + cards.push_back(t); + source->controller()->game->putInZone(toMove, RevealFromZone, RevealZone); + source->revealedLast = toMove; + } + } + revealDisplay->init(RevealZone); + revealDisplay->zone = RevealZone; + game->OpenedDisplay = revealDisplay; +} + +int MTGScryCards::testDestroy() +{ + if (game->mExtraPayment) + return 0; + if (revealDisplay) + return 0; + if (zone->cards.size()) + return 0; + if (!initCD) + return 0; + if (game->mLayers->actionLayer()->menuObject) + return 0; + if (game->mLayers->actionLayer()->getIndexOf(abilityFirst) != -1) + return 0; + + return 1; +} + +int MTGScryCards::toResolve() +{ + //scry will always have valid targets. + abilityFirst = contructAbility(abilityOne); + game->addObserver(abilityFirst); + return 1; +} + +MTGAbility * MTGScryCards::contructAbility(string abilityToMake) +{ + AbilityFactory af(game); + abilityToCast = af.parseMagicLine(abilityToMake, getMaxId(), NULL, source, false); + if (!abilityToCast) + return NULL; + abilityToCast->canBeInterrupted = false; + abilityToCast->forceDestroy = 1; + return abilityToCast; +} + +void MTGScryCards::Render() +{ + if (!revealDisplay) + return; + CheckUserInput(mEngine->ReadButton()); + if (revealDisplay) + { + revealDisplay->CheckUserInput(mEngine->ReadButton()); + revealDisplay->Render(); + } + return; +} + +bool MTGScryCards::CheckUserInput(JButton key) +{ + //DO NOT REFACTOR BELOW + TargetChooser * tc = this->observer->mLayers->actionLayer()->getCurrentTargetChooser(); + if (this->source->controller()->isAI()) + {//ai doesnt click button, and the engine has no way of knowing whos clicking button + //for now we will cancel interrupts made when ai is making choice + //in the future we will need a way to find out if the human is pressing the keys and which player. + if (this->source->controller() != game->isInterrupting) + game->mLayers->stackLayer()->cancelInterruptOffer(ActionStack::DONT_INTERRUPT, false); + } + if (JGE_BTN_SEC == key || JGE_BTN_PREV == key || JGE_BTN_NEXT == key || JGE_BTN_MENU == key) + { + if (tc && (tc->targetMin == false || tc->maxtargets == TargetChooser::UNLITMITED_TARGETS)) + { + tc->done = true; + tc->forceTargetListReadyByPlayer = 1; + if (!abilitySecond && !tc->getNbTargets() && tc->source) + { + tc->source->getObserver()->cardClick(tc->source, 0, false); + if (abilityFirst)///some abilities resolve themselves and remove faster than you can removethem from the game. + { + abilityFirst->removeFromGame(); + game->mLayers->stackLayer()->Remove(abilityFirst); + } + game->Update(0); + if (zone->cards.size() && abilityFirst->testDestroy()) + { + initDisplay(revealTopAmount); + abilitySecond = contructAbility(abilityTwo); + game->addObserver(abilitySecond); + } + } + else if (tc->source) + { + tc->source->getObserver()->cardClick(tc->source, 0, false); + } + } + else if (!tc && !abilitySecond) + { + CheckUserInput(JGE_BTN_OK); + } + return false; + } + if (JGE_BTN_OK == key) + { + if (!tc && !abilitySecond) + { + if (abilityFirst) + { + abilityFirst->removeFromGame(); + game->mLayers->stackLayer()->Remove(abilityFirst); + } + game->Update(1); + + if (zone->cards.size() || revealDisplay && !zone->cards.size()) + { + initDisplay(revealTopAmount); + abilitySecond = contructAbility(abilityTwo); + game->addObserver(abilitySecond); + } + + } + if (!tc && abilitySecond && abilitySecond->testDestroy()) + { + do + { + if (!RevealZone->cards.size()) + break; + MTGCardInstance * toMove = RevealZone->cards[0]; + if (toMove) + { + source->revealedLast = toMove; + MTGAbility * a = NEW AAMover(game, getMaxId(), source, toMove,"library", "Place on top"); + a->oneShot = true; + a->resolve(); + SAFE_DELETE(a); + } + } while (RevealZone->cards.size()); + + if (delayed) + { + MTGAbility * delayedA = contructAbility(delayedAbilityString); + if (delayedA->oneShot) + { + delayedA->resolve(); + SAFE_DELETE(delayedA); + } + else + delayedA->addToGame(); + + } + } + } + if (revealDisplay) + return revealDisplay->CheckUserInput(key); + return false; +} + +MTGScryCards * MTGScryCards::clone() const +{ + return NEW MTGScryCards(*this); +} + +MTGScryCards::~MTGScryCards() +{ + for (vector::iterator it = trashDisplays.begin(); it != trashDisplays.end(); ++it) + SAFE_DELETE(*it); + for (vector::iterator it = cards.begin(); it != cards.end(); ++it) + SAFE_DELETE(*it); +} + +int MTGScryCards::receiveEvent(WEvent* e) +{ + + if (WEventZoneChange* event = dynamic_cast(e)) + { + if (event->from == zone) + { + CardView* t; + if (event->card->view) + t = NEW CardView(CardView::nullZone, event->card, *(event->card->view)); + //we store copies of the card view since moving to and from card displays also deletes the guis stored in cards. + //GuiLayer::resetObjects() is the main reason we need to back them up. card views are set to NULL maybe more often than + //they should be, possibly someone being to over cautious. + t->actX = SCREEN_WIDTH; + t->actY = SCREEN_HEIGHT * -2; + //correct cards x and y, last known location was the reveal display. + cards.push_back(t); + } + } + return 0; +} + +//scry wrapper +GenericScryAbility::GenericScryAbility(GameObserver* observer, int id, MTGCardInstance * source, + Targetable * target, string _howMany) : + ActivatedAbility(observer, id, source, NULL), howMany(_howMany) +{ + this->GetId(); +} + +int GenericScryAbility::resolve() +{ + MTGAbility * ability = NEW MTGScryCards(game, this->GetId(), source, howMany); + ability->addToGame(); + return 1; +} + +const string GenericScryAbility::getMenuText() +{ + return "Scry Cards"; +} + +GenericScryAbility * GenericScryAbility::clone() const +{ + GenericScryAbility * a = NEW GenericScryAbility(*this); + return a; +} + +GenericScryAbility::~GenericScryAbility() +{ + //SAFE_DELETE(ability); +} + //////////////////////// //Activated Abilities @@ -1342,6 +2174,9 @@ int GenericPaidAbility::resolve() } else { + //dangerous code below, parse a string line that might not exist. baseAbilityStrSplit[0] + //you either have a string and do stuff, or dont and leave the ability + //not fixing this since its been heavily modified from the orginal implementation. nomenu = true; baseAbility = Af.parseMagicLine(baseAbilityStrSplit[0], this->GetId(), NULL, source); baseAbility->target = target; @@ -1884,10 +2719,11 @@ AADrawer * AADrawer::clone() const } // AAFrozen: Prevent a card from untapping during next untap phase -AAFrozen::AAFrozen(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost) : +AAFrozen::AAFrozen(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, bool tap, ManaCost * _cost) : ActivatedAbility(observer, id, card, _cost, 0) { target = _target; + freeze = tap; } int AAFrozen::resolve() @@ -1897,6 +2733,10 @@ int AAFrozen::resolve() { while (_target->next) _target = _target->next; //This is for cards such as rampant growth + if (freeze) + { + _target->tap();//easier to manage for cards that allow you to tap and also freeze. + } _target->frozen += 1; } return 1; @@ -3037,7 +3877,16 @@ int AAMover::resolve() delete spell; return 1; } + if (destZone == game->players[i]->game->graveyard && fromZone == game->players[i]->game->hand) + { + //movers that take a card from hand and place them in graveyard are always discards. we send an event for it here. + + WEvent * e = NEW WEventCardDiscard(_target); + game->receiveEvent(e); + } + } + if(_target->hasSubtype(Subtypes::TYPE_AURA) && (destZone == game->players[0]->game->inPlay || destZone == game->players[1]->game->inPlay)) {//put into play aura if there is no valid targets then it will be in its current zone MTGAbility *a = NEW AACastCard(game, game->mLayers->actionLayer()->getMaxId(), _target, _target,false,false,false,"","Put in play",false,true); @@ -3440,6 +4289,40 @@ AARemoveMana::~AARemoveMana() SAFE_DELETE(mManaDesc); } +//Bestow +ABestow::ABestow(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost) : + ActivatedAbility(observer, id, card, _cost, 0) +{ + target = _target; + aType = MTGAbility::TAPPER; + _card = card; +} + +int ABestow::resolve() +{ + if (target) + { + if (_card->hasType("creature")) + { + _card->removeType("creature"); + _card->addType("aura"); + } + _card->target = (MTGCardInstance*)target; + _card->isBestowed = true; + } + return 1; +} + +const string ABestow::getMenuText() +{ + return "Bestow"; +} + +ABestow * ABestow::clone() const +{ + return NEW ABestow(*this); +} + //Tapper AATapper::AATapper(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost) : ActivatedAbility(observer, id, card, _cost, 0) @@ -3520,6 +4403,30 @@ AAWhatsMax * AAWhatsMax::clone() const { return NEW AAWhatsMax(*this); } +//count objects on field before doing an effect +AACountObject::AACountObject(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance *, ManaCost * _cost, string value) : + ActivatedAbility(observer, id, card, _cost, 0), value(value) +{ +} + +int AACountObject::resolve() +{ + + if (source) + { + int amount = 0; + WParsedInt * use = NEW WParsedInt(value, NULL, source); + amount = use->getValue(); + source->CountedObjects = amount; + SAFE_DELETE(use); + } + return 1; +} + +AACountObject * AACountObject::clone() const +{ + return NEW AACountObject(*this); +} // Win Game AAWinGame::AAWinGame(GameObserver* observer, int _id, MTGCardInstance * card, Targetable * _target, ManaCost * _cost, int who) : @@ -3812,6 +4719,17 @@ void MenuAbility::Update(float dt) { if(game->mExtraPayment->isPaymentSet() && game->mExtraPayment->canPay() ) { + if (game->mExtraPayment->costs.size()) + { + if (game->mExtraPayment->costs[0]->costToPay) + { + ManaCost * diff = game->mExtraPayment->costs[0]->costToPay; + ManaCost * c = source->controller()->getManaPool()->Diff(diff); + source->X = c->getCost(Constants::NB_Colors); + delete c; + } + } + game->mExtraPayment->doPay(); game->mLayers->actionLayer()->reactToClick(game->mExtraPayment->action, game->mExtraPayment->source); game->mExtraPayment = NULL; @@ -4064,6 +4982,7 @@ MultiAbility * MultiAbility::clone() const a->abilities.clear(); for (size_t i = 0; i < abilities.size(); ++i) { + if(abilities[i]) a->abilities.push_back(abilities[i]->clone()); } return a; @@ -5993,8 +6912,8 @@ AEquip * AEquip::clone() const } // 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,bool putinplay) : - MTGAbility(observer, _id, _source),restricted(_restricted),asCopy(_copied),normal(asNormal),cardNamed(_namedCard),nameThis(_name),noEvent(_noEvent),putinplay(putinplay) +AACastCard::AACastCard(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target,bool _restricted,bool _copied,bool asNormal,string _namedCard,string _name,bool _noEvent,bool putinplay,bool madness) : + MTGAbility(observer, _id, _source),restricted(_restricted),asCopy(_copied),normal(asNormal),cardNamed(_namedCard),nameThis(_name),noEvent(_noEvent),putinplay(putinplay), asNormalMadness(madness) { target = _target; andAbility = NULL; @@ -6145,13 +7064,15 @@ int AACastCard::resolveSpell() { Spell * spell = NULL; MTGCardInstance * copy = NULL; - if (normal ||(!_target->hasType(Subtypes::TYPE_INSTANT) && !_target->hasType(Subtypes::TYPE_SORCERY))) + if ((normal || asNormalMadness)||(!_target->hasType(Subtypes::TYPE_INSTANT) && !_target->hasType(Subtypes::TYPE_SORCERY))) { if (putinplay && (_target->hasType(Subtypes::TYPE_ARTIFACT)||_target->hasType(Subtypes::TYPE_CREATURE)||_target->hasType(Subtypes::TYPE_ENCHANTMENT)||_target->hasType(Subtypes::TYPE_PLANESWALKER))) copy =_target->controller()->game->putInZone(_target, _target->currentZone, source->controller()->game->battlefield,noEvent); else copy =_target->controller()->game->putInZone(_target, _target->currentZone, source->controller()->game->stack,noEvent); copy->changeController(source->controller(),true); + if(asNormalMadness) + copy->MadnessPlay = true; } else { diff --git a/projects/mtg/src/ExtraCost.cpp b/projects/mtg/src/ExtraCost.cpp index e6940f0d7..a0c5ca1b8 100644 --- a/projects/mtg/src/ExtraCost.cpp +++ b/projects/mtg/src/ExtraCost.cpp @@ -756,9 +756,10 @@ int TapTargetCost::isPaymentSet() int TapTargetCost::doPay() { MTGCardInstance * _target = (MTGCardInstance *) target; - source->storedCard = target->createSnapShot(); + if (target) { + source->storedCard = target->createSnapShot(); _target->tap(); target = NULL; if (tc) @@ -799,9 +800,10 @@ int UnTapTargetCost::isPaymentSet() int UnTapTargetCost::doPay() { MTGCardInstance * _target = (MTGCardInstance *) target; - source->storedCard = target->createSnapShot(); + if (target) { + source->storedCard = target->createSnapShot(); _target->untap(); target = NULL; if (tc) diff --git a/projects/mtg/src/GameObserver.cpp b/projects/mtg/src/GameObserver.cpp index 435b97688..908ce9606 100644 --- a/projects/mtg/src/GameObserver.cpp +++ b/projects/mtg/src/GameObserver.cpp @@ -1,2202 +1,2251 @@ -#include "PrecompiledHeader.h" - -#include "GameObserver.h" -#include "CardGui.h" -#include "Damage.h" -#include "Rules.h" -#include "ExtraCost.h" -#include "Subtypes.h" -#include -#include -#include "MTGGamePhase.h" -#include "GuiPhaseBar.h" -#include "AIPlayerBaka.h" -#include "MTGRules.h" -#include "Trash.h" -#include "DeckManager.h" -#include "GuiCombat.h" -#include -#ifdef TESTSUITE -#include "TestSuiteAI.h" -#endif -#ifdef NETWORK_SUPPORT -#include "NetworkPlayer.h" -#endif - - -void GameObserver::cleanup() -{ - SAFE_DELETE(targetChooser); - SAFE_DELETE(mLayers); - SAFE_DELETE(phaseRing); - SAFE_DELETE(replacementEffects); - for (size_t i = 0; i < players.size(); ++i) - { - SAFE_DELETE(players[i]); - } - players.clear(); - - currentPlayer = NULL; - currentActionPlayer = NULL; - isInterrupting = NULL; - currentPlayerId = 0; - mCurrentGamePhase = MTG_PHASE_INVALID; - targetChooser = NULL; - cardWaitingForTargets = NULL; - mExtraPayment = NULL; - gameOver = NULL; - phaseRing = NULL; - replacementEffects = NEW ReplacementEffects(); - combatStep = BLOCKERS; - connectRule = false; - actionsList.clear(); - gameTurn.clear(); -} - -GameObserver::~GameObserver() -{ - LOG("==Destroying GameObserver=="); - - for (size_t i = 0; i < players.size(); ++i) - { - players[i]->End(); - } - SAFE_DELETE(targetChooser); - SAFE_DELETE(mLayers); - SAFE_DELETE(phaseRing); - SAFE_DELETE(replacementEffects); - for (size_t i = 0; i < players.size(); ++i) - { - SAFE_DELETE(players[i]); - } - players.clear(); - delete[] ExtraRules; - ExtraRules = 0; - LOG("==GameObserver Destroyed=="); - SAFE_DELETE(mTrash); - SAFE_DELETE(mDeckManager); - -} - -GameObserver::GameObserver(WResourceManager *output, JGE* input) - : mSeed((unsigned int)time(0)), randomGenerator(mSeed, true), mResourceManager(output), mJGE(input) - -{ - ExtraRules = new MTGCardInstance[2](); - - mGameType = GAME_TYPE_CLASSIC; - currentPlayer = NULL; - currentActionPlayer = NULL; - isInterrupting = NULL; - currentPlayerId = 0; - mCurrentGamePhase = MTG_PHASE_INVALID; - targetChooser = NULL; - cardWaitingForTargets = NULL; - mExtraPayment = NULL; - gameOver = NULL; - phaseRing = NULL; - replacementEffects = NEW ReplacementEffects(); - combatStep = BLOCKERS; - mRules = NULL; - connectRule = false; - mLoading = false; - mLayers = NULL; - mTrash = new Trash(); - mDeckManager = new DeckManager(); -} - -GamePhase GameObserver::getCurrentGamePhase() -{ - return mCurrentGamePhase; -} - -const string& GameObserver::getCurrentGamePhaseName() -{ - return phaseRing->phaseName(mCurrentGamePhase); -} - -const string& GameObserver::getNextGamePhaseName() -{ - return phaseRing->phaseName((mCurrentGamePhase + 1) % MTG_PHASE_CLEANUP); -} - -Player * GameObserver::opponent() -{ - int index = (currentPlayerId + 1) % players.size(); - return players[index]; -} - -Player * GameObserver::nextTurnsPlayer() -{ - int nextTurnsId = 0; - if(!players[currentPlayerId]->extraTurn) - nextTurnsId = (currentPlayerId + 1) % players.size(); - else - { - nextTurnsId = currentPlayerId; - } - if(players[currentPlayerId]->skippingTurn) - { - nextTurnsId = (currentPlayerId + 1) % players.size(); - } - return players[nextTurnsId]; -} - -void GameObserver::nextPlayer() -{ - turn++; - if(!players[currentPlayerId]->extraTurn) - currentPlayerId = (currentPlayerId + 1) % players.size(); - else - { - players[currentPlayerId]->extraTurn--; - } - if(players[currentPlayerId]->skippingTurn) - { - players[currentPlayerId]->skippingTurn--; - currentPlayerId = (currentPlayerId + 1) % players.size(); - } - currentPlayer = players[currentPlayerId]; - currentActionPlayer = currentPlayer; - combatStep = BLOCKERS; -} - -void GameObserver::nextGamePhase() -{ - Phase * cPhaseOld = phaseRing->getCurrentPhase(); - if (cPhaseOld->id == MTG_PHASE_COMBATDAMAGE) - if ((FIRST_STRIKE == combatStep) || (END_FIRST_STRIKE == combatStep) || (DAMAGE == combatStep)) - { - nextCombatStep(); - return; - } - - if (cPhaseOld->id == MTG_PHASE_COMBATBLOCKERS) - if (BLOCKERS == combatStep || TRIGGERS == combatStep) - { - nextCombatStep(); - return; - } - - phaseRing->forward(); - - //Go directly to end of combat if no attackers - if (cPhaseOld->id == MTG_PHASE_COMBATATTACKERS && !(currentPlayer->game->inPlay->getNextAttacker(NULL))) - { - phaseRing->forward(); - phaseRing->forward(); - } - - Phase * cPhase = phaseRing->getCurrentPhase(); - mCurrentGamePhase = cPhase->id; - - if (MTG_PHASE_COMBATDAMAGE == mCurrentGamePhase) - nextCombatStep(); - if (MTG_PHASE_COMBATEND == mCurrentGamePhase) - combatStep = BLOCKERS; - - //if (currentPlayer != cPhase->player) - // nextPlayer();//depreciated; we call this at EOT step now. unsure what the purpose of this was originally.fix for a bug? - - //init begin of turn - if (mCurrentGamePhase == MTG_PHASE_BEFORE_BEGIN) - { - cleanupPhase(); - currentPlayer->damageCount = 0; - currentPlayer->drawCounter = 0; - currentPlayer->raidcount = 0; - currentPlayer->opponent()->raidcount = 0; - currentPlayer->prowledTypes.clear(); - currentPlayer->opponent()->damageCount = 0; //added to clear odcount - currentPlayer->preventable = 0; - mLayers->actionLayer()->cleanGarbage(); //clean abilities history for this turn; - mLayers->stackLayer()->garbageCollect(); //clean stack history for this turn; - mLayers->actionLayer()->Update(0); - currentPlayer->game->library->miracle = false; - currentPlayer->opponent()->game->library->miracle = false; - for (int i = 0; i < 2; i++) - { - //Cleanup of each player's gamezones - players[i]->game->beforeBeginPhase(); - } - combatStep = BLOCKERS; - return nextGamePhase(); - } - - if (mCurrentGamePhase == MTG_PHASE_AFTER_EOT) - { - int handmodified = 0; - handmodified = currentPlayer->handsize+currentPlayer->handmodifier; - //Auto Hand cleaning, in case the player didn't do it himself - if(handmodified < 0) - handmodified = 0; - while (currentPlayer->game->hand->nb_cards > handmodified && currentPlayer->nomaxhandsize == false) - { - WEvent * e = NEW WEventCardDiscard(currentPlayer->game->hand->cards[0]); - receiveEvent(e); - currentPlayer->game->putInGraveyard(currentPlayer->game->hand->cards[0]); - } - mLayers->actionLayer()->Update(0); - currentPlayer->drawCounter = 0; - currentPlayer->prowledTypes.clear(); - currentPlayer->lifeLostThisTurn = 0; - currentPlayer->opponent()->lifeLostThisTurn = 0; - currentPlayer->doesntEmpty->remove(currentPlayer->doesntEmpty); - currentPlayer->opponent()->doesntEmpty->remove(currentPlayer->opponent()->doesntEmpty); - nextPlayer(); - return nextGamePhase(); - } - - //Phase Specific actions - switch (mCurrentGamePhase) - { - case MTG_PHASE_UNTAP: - DebugTrace("Untap Phase ------------- Turn " << turn ); - untapPhase(); - break; - case MTG_PHASE_COMBATBLOCKERS: - receiveEvent(NEW WEventAttackersChosen()); - break; - default: - break; - } -} - -int GameObserver::cancelCurrentAction() -{ - SAFE_DELETE(targetChooser); - return mLayers->actionLayer()->cancelCurrentAction(); -} - -void GameObserver::nextCombatStep() -{ - switch (combatStep) - { - case BLOCKERS: - receiveEvent(NEW WEventBlockersChosen()); - receiveEvent(NEW WEventCombatStepChange(combatStep = TRIGGERS)); - return; - - case TRIGGERS: - receiveEvent(NEW WEventCombatStepChange(combatStep = ORDER)); - return; - case ORDER: - receiveEvent(NEW WEventCombatStepChange(combatStep = FIRST_STRIKE)); - return; - case FIRST_STRIKE: - receiveEvent(NEW WEventCombatStepChange(combatStep = END_FIRST_STRIKE)); - return; - case END_FIRST_STRIKE: - receiveEvent(NEW WEventCombatStepChange(combatStep = DAMAGE)); - return; - case DAMAGE: - receiveEvent(NEW WEventCombatStepChange(combatStep = END_DAMAGE)); - return; - case END_DAMAGE: - ; // Nothing : go to next phase - } -} - -void GameObserver::userRequestNextGamePhase(bool allowInterrupt, bool log) -{ - if(log) { - stringstream stream; - stream << "next " << allowInterrupt << " " <maxtargets == 1000) - { - getCurrentTargetChooser()->done = true; - if(getCurrentTargetChooser()->source) - cardClick(getCurrentTargetChooser()->source, 0, false); - } - if (allowInterrupt && mLayers->stackLayer()->getNext(NULL, 0, NOT_RESOLVED)) - return; - if (getCurrentTargetChooser()) - return; - //if (mLayers->actionLayer()->isWaitingForAnswer()) - // return; - // Wil 12/5/10: additional check, not quite understanding why TargetChooser doesn't seem active at this point. - // If we deem that an extra cost payment needs to be made, don't allow the next game phase to proceed. - // Here's what I find weird - if the extra cost is something like a sacrifice, doesn't that imply a TargetChooser? - if (WaitForExtraPayment(NULL)) - return; - - Phase * cPhaseOld = phaseRing->getCurrentPhase(); - if (allowInterrupt && ((cPhaseOld->id == MTG_PHASE_COMBATBLOCKERS && combatStep == ORDER) - || (cPhaseOld->id == MTG_PHASE_COMBATBLOCKERS && combatStep == TRIGGERS) - || (cPhaseOld->id == MTG_PHASE_COMBATDAMAGE) - || opponent()->isAI() - || options[Options::optionInterrupt(mCurrentGamePhase)].number - || currentPlayer->offerInterruptOnPhase - 1 == mCurrentGamePhase - )) - { - mLayers->stackLayer()->AddNextGamePhase(); - } - else - { - nextGamePhase(); - } -} - -void GameObserver::shuffleLibrary(Player* p) -{ - if(!p) - { - DebugTrace("FATAL: No Player To Shuffle"); - return; - } - logAction(p, "shufflelib"); - MTGLibrary * library = p->game->library; - if(!library) - { - DebugTrace("FATAL: Player has no zones"); - return; - } - library->shuffle(); - - for(unsigned int k = 0;k < library->placeOnTop.size();k++) - { - MTGCardInstance * toMove = library->placeOnTop[k]; - assert(toMove); - p->game->putInZone(toMove, p->game->temp, library); - } - library->placeOnTop.clear(); - -} - - -int GameObserver::forceShuffleLibraries() -{ - int result = 0; - for (int i = 0; i < 2 ; ++i) - { - if (players[i]->game->library->needShuffle) - { - shuffleLibrary(players[i]); - players[i]->game->library->needShuffle = false; - ++result; - } - } - - return result; -} - -void GameObserver::resetStartupGame() -{ - stringstream stream; - startupGameSerialized = ""; - stream << *this; - startupGameSerialized = stream.str(); -// DebugTrace("startGame\n"); -// DebugTrace(startupGameSerialized); -} - -void GameObserver::startGame(GameType gtype, Rules * rules) -{ - mGameType = gtype; - turn = 0; - mRules = rules; - if (rules) - rules->initPlayers(this); - - options.automaticStyle(players[0], players[1]); - - mLayers = NEW DuelLayers(this); - - currentPlayerId = 0; - currentPlayer = players[currentPlayerId]; - currentActionPlayer = currentPlayer; - phaseRing = NEW PhaseRing(this); - - resetStartupGame(); - - if (rules) - rules->initGame(this); - - //Preload images from hand - if (!players[0]->isAI()) - { - for (int i = 0; i < players[0]->game->hand->nb_cards; i++) - { - WResourceManager::Instance()->RetrieveCard(players[0]->game->hand->cards[i], CACHE_THUMB); - WResourceManager::Instance()->RetrieveCard(players[0]->game->hand->cards[i]); - } - } - - startedAt = time(0); - - //Difficult mode special stuff - if (!players[0]->isAI() && players[1]->isAI()) - { - int difficulty = options[Options::DIFFICULTY].number; - if (options[Options::DIFFICULTY_MODE_UNLOCKED].number && difficulty) - { - Player * p = players[1]; - for (int level = 0; level < difficulty; level++) - { - MTGCardInstance * card = NULL; - MTGGameZone * z = p->game->library; - for (int j = 0; j < z->nb_cards; j++) - { - MTGCardInstance * _card = z->cards[j]; - if (_card->isLand()) - { - card = _card; - j = z->nb_cards; - } - } - if (card) - { - MTGCardInstance * copy = p->game->putInZone(card, p->game->library, p->game->stack); - Spell * spell = NEW Spell(this, copy); - spell->resolve(); - delete spell; - } - } - } - } - - switch(gtype) { - case GAME_TYPE_MOMIR: - { - addObserver(NEW MTGMomirRule(this, -1, MTGCollection())); - break; - } - case GAME_TYPE_STONEHEWER: - { - addObserver(NEW MTGStoneHewerRule(this, -1,MTGCollection())); - break; - } - case GAME_TYPE_HERMIT: - { - addObserver(NEW MTGHermitRule(this, -1)); - break; - } - default: - break; - } -} - -void GameObserver::addObserver(MTGAbility * observer) -{ - mLayers->actionLayer()->Add(observer); -} - -//Returns true if the Ability was correctly removed from the game, false otherwise -//Main (valid) reason of returning false is an attempt at removing an Ability that has already been removed -bool GameObserver::removeObserver(ActionElement * observer) -{ - if (!observer) - return false; - return mLayers->actionLayer()->moveToGarbage(observer); - -} - -bool GameObserver::operator==(const GameObserver& aGame) -{ - int error = 0; - - if (aGame.mCurrentGamePhase != mCurrentGamePhase) - { - error++; - } - for (int i = 0; i < 2; i++) - { - Player * p = aGame.players[i]; - - if (p->life != players[i]->life) - { - error++; - } - if (p->poisonCount != players[i]->poisonCount) - { - error++; - } - if (!p->getManaPool()->canAfford(players[i]->getManaPool())) - { - error++; - } - if (!players[i]->getManaPool()->canAfford(p->getManaPool())) - { - error++; - } - MTGGameZone * aZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay, p->game->exile }; - MTGGameZone * thisZones[] = { players[i]->game->graveyard, - players[i]->game->library, - players[i]->game->hand, - players[i]->game->inPlay, - players[i]->game->exile }; - for (int j = 0; j < 5; j++) - { - MTGGameZone * zone = aZones[j]; - if (zone->nb_cards != thisZones[j]->nb_cards) - { - error++; - } - for (size_t k = 0; k < (size_t)thisZones[j]->nb_cards; k++) - { - MTGCardInstance* cardToCheck = (kcards.size())?thisZones[j]->cards[k]:0; - MTGCardInstance* card = (kcards.size())?aZones[j]->cards[k]:0; - if(!card || !cardToCheck || cardToCheck->getId() != card->getId()) - { - error++; - } - } - } - } - - return (error == 0); -} - -void GameObserver::dumpAssert(bool val) -{ - if(!val) - { - cerr << *this << endl; - assert(0); - } -} - - -void GameObserver::Update(float dt) -{ - Player * player = currentPlayer; - if (MTG_PHASE_COMBATBLOCKERS == mCurrentGamePhase && BLOCKERS == combatStep) - { - player = player->opponent(); - } - if(getCurrentTargetChooser() && getCurrentTargetChooser()->Owner && player != getCurrentTargetChooser()->Owner) - { - if(getCurrentTargetChooser()->Owner != currentlyActing()) - { - player = getCurrentTargetChooser()->Owner; - isInterrupting = player; - } - } - currentActionPlayer = player; - if (isInterrupting) - player = isInterrupting; - if(mLayers) - { - mLayers->Update(dt, player); - while (mLayers->actionLayer()->stuffHappened) - { - mLayers->actionLayer()->Update(0); - } - gameStateBasedEffects(); - } - oldGamePhase = mCurrentGamePhase; -} - -//applies damage to creatures after updates -//Players life test -//Handles game state based effects -void GameObserver::gameStateBasedEffects() -{ - if(getCurrentTargetChooser() && int(getCurrentTargetChooser()->getNbTargets()) == getCurrentTargetChooser()->maxtargets) - getCurrentTargetChooser()->done = true; - ///////////////////////////////////// - for (int d = 0; d < 2; d++) - { - ////check snow count - if (players[d]->snowManaC > players[d]->getManaPool()->getCost(0) + players[d]->getManaPool()->getCost(6)) - players[d]->snowManaC = players[d]->getManaPool()->getCost(0) + players[d]->getManaPool()->getCost(6); - if (players[d]->snowManaC < 0) - players[d]->snowManaC = 0; - if (players[d]->snowManaG > players[d]->getManaPool()->getCost(1)) - players[d]->snowManaG = players[d]->getManaPool()->getCost(1); - if (players[d]->snowManaG < 0) - players[d]->snowManaG = 0; - if (players[d]->snowManaU > players[d]->getManaPool()->getCost(2)) - players[d]->snowManaU = players[d]->getManaPool()->getCost(2); - if (players[d]->snowManaU < 0) - players[d]->snowManaU = 0; - if (players[d]->snowManaR > players[d]->getManaPool()->getCost(3)) - players[d]->snowManaR = players[d]->getManaPool()->getCost(3); - if (players[d]->snowManaR < 0) - players[d]->snowManaR = 0; - if (players[d]->snowManaB > players[d]->getManaPool()->getCost(4)) - players[d]->snowManaB = players[d]->getManaPool()->getCost(4); - if (players[d]->snowManaB < 0) - players[d]->snowManaB = 0; - if (players[d]->snowManaW > players[d]->getManaPool()->getCost(5)) - players[d]->snowManaW = players[d]->getManaPool()->getCost(5); - if (players[d]->snowManaW < 0) - players[d]->snowManaW = 0; - - MTGGameZone * dzones[] = { players[d]->game->inPlay, players[d]->game->graveyard, players[d]->game->hand, players[d]->game->library, players[d]->game->exile }; - for (int k = 0; k < 5; k++) - { - MTGGameZone * zone = dzones[k]; - if (mLayers->stackLayer()->count(0, NOT_RESOLVED) == 0) - { - for (int c = zone->nb_cards - 1; c >= 0; c--) - { - zone->cards[c]->cardistargetted = 0; - zone->cards[c]->cardistargetter = 0; - } - } - - ///while checking all these zones, lets also strip devoid cards of thier colors - for (int w = 0; w < zone->nb_cards; w++) - { - MTGCardInstance * card = zone->cards[w]; - for (int i = Constants::MTG_COLOR_GREEN; i <= Constants::MTG_COLOR_WHITE; ++i) - { - if (card->has(Constants::DEVOID)) - { - card->removeColor(i); - } - } - } - - - }//check for losers if its GAMEOVER clear the stack to allow gamestateeffects to continue - players[d]->DeadLifeState(); - } - //////////////////////////////////// - - if (mLayers->stackLayer()->count(0, NOT_RESOLVED) != 0) - return; - if (mLayers->actionLayer()->menuObject) - return; - if (getCurrentTargetChooser() || mLayers->actionLayer()->isWaitingForAnswer()) - return; - //////////////////////// - //---apply damage-----// - //after combat effects// - //////////////////////// - for (int i = 0; i < 2; i++) - { - MTGGameZone * zone = players[i]->game->inPlay; - players[i]->curses.clear(); - for (int j = zone->nb_cards - 1; j >= 0; j--) - { - MTGCardInstance * card = zone->cards[j]; - card->LKIpower = card->power; - card->LKItoughness = card->toughness; - card->LKIbasicAbilities = card->basicAbilities; - card->afterDamage(); - 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; - } - ///clear imprints - if(isInPlay(card) && card->imprintedCards.size()) - { - for(size_t ic = 0; ic < card->imprintedCards.size(); ic++) - { - if(!isInExile(card->imprintedCards[ic])) - { - card->imprintG = 0; - card->imprintU = 0; - card->imprintR = 0; - card->imprintB = 0; - card->imprintW = 0; - card->currentimprintName = ""; - card->imprintedNames.clear(); - card->imprintedCards.erase(card->imprintedCards.begin() + ic); - } - } - } - card->bypassTC = false; //turn off bypass - //////////////////////////////////////////////////// - //Unattach Equipments that dont have valid targets// - //////////////////////////////////////////////////// - if ((card->target) && card->hasType(Subtypes::TYPE_EQUIPMENT)) - { - if(card->target && isInPlay(card->target) && (card->target)->protectedAgainst(card))//protection from quality - { - for (size_t i = 1; i < mLayers->actionLayer()->mObjects.size(); i++) - { - MTGAbility * a = ((MTGAbility *) mLayers->actionLayer()->mObjects[i]); - AEquip * eq = dynamic_cast (a); - if (eq && eq->source == card) - { - ((AEquip*)a)->unequip(); - } - } - } - } - /////////////////////////////////////////////////////// - //Remove auras that don't have a valid target anymore// - /////////////////////////////////////////////////////// - if ((card->target||card->playerTarget) && !card->hasType(Subtypes::TYPE_EQUIPMENT)) - { - if(card->target && !isInPlay(card->target)) - players[i]->game->putInGraveyard(card); - if(card->target && isInPlay(card->target)) - { - if(card->spellTargetType.find("creature") != string::npos && !card->target->hasType("creature")) - players[i]->game->putInGraveyard(card); - if(card->spellTargetType.find("artifact") != string::npos && !card->target->hasType("artifact")) - players[i]->game->putInGraveyard(card); - if(card->spellTargetType.find("enchantment") != string::npos && !card->target->hasType("enchantment")) - players[i]->game->putInGraveyard(card); - if(card->spellTargetType.find("land") != string::npos && !card->target->hasType("land")) - players[i]->game->putInGraveyard(card); - if(card->spellTargetType.find("planeswalker") != string::npos && !card->target->hasType("planeswalker")) - players[i]->game->putInGraveyard(card); - } - if(card->target && isInPlay(card->target) && (card->target)->protectedAgainst(card) && !card->has(Constants::AURAWARD))//protection from quality except aura cards like flickering ward - players[i]->game->putInGraveyard(card); - } - card->enchanted = false; - if (card->target && isInPlay(card->target) && !card->hasType(Subtypes::TYPE_EQUIPMENT) && card->hasSubtype(Subtypes::TYPE_AURA)) - { - card->target->enchanted = true; - } - if (card->playerTarget && card->hasType("curse")) - { - card->playerTarget->curses.push_back(card); - } - /////////////////////////// - //reset extracost shadows// - /////////////////////////// - card->isExtraCostTarget = false; - if(mExtraPayment != NULL) - { - for(unsigned int ec = 0;ec < mExtraPayment->costs.size();ec++) - { - if( mExtraPayment->costs[ec]->target) - mExtraPayment->costs[ec]->target->isExtraCostTarget = true; - } - } - ////////////////////// - //reset morph hiding// - ////////////////////// - if((card->previous && card->previous->morphed && !card->turningOver) || (card->morphed && !card->turningOver)) - { - card->morphed = true; - card->isMorphed = true; - } - else - { - card->isMorphed = false; - card->morphed = false; - } - ////////////////////////// - //handles phasing events// - ////////////////////////// - if(card->has(Constants::PHASING)&& mCurrentGamePhase == MTG_PHASE_UNTAP && currentPlayer == card->controller() && card->phasedTurn != turn && !card->isPhased) - { - card->isPhased = true; - card->phasedTurn = turn; - if(card->view) - card->view->alpha = 50; - card->initAttackersDefensers(); - } - else if((card->has(Constants::PHASING) || card->isPhased)&& mCurrentGamePhase == MTG_PHASE_UNTAP && currentPlayer == card->controller() && card->phasedTurn != turn) - { - card->isPhased = false; - card->phasedTurn = turn; - if(card->view) - card->view->alpha = 255; - } - if (card->target && isInPlay(card->target) && (card->hasSubtype(Subtypes::TYPE_EQUIPMENT) || card->hasSubtype(Subtypes::TYPE_AURA))) - { - card->isPhased = card->target->isPhased; - card->phasedTurn = card->target->phasedTurn; - if(card->view && card->target->view) - card->view->alpha = card->target->view->alpha; - } - ////////////////////////// - //forceDestroy over ride// - ////////////////////////// - if(card->isInPlay(this)) - { - card->graveEffects = false; - card->exileEffects = false; - } - - if(card->childrenCards.size()) - { - MTGCardInstance * check = NULL; - MTGCardInstance * matched = NULL; - sort(card->childrenCards.begin(),card->childrenCards.end()); - for(size_t wC = 0; wC < card->childrenCards.size();wC++) - { - check = card->childrenCards[wC]; - for(size_t wCC = 0; wCC < card->childrenCards.size();wCC++) - { - if(check->isInPlay(this)) - { - if(check->getName() == card->childrenCards[wCC]->getName() && check != card->childrenCards[wCC]) - { - card->isDualWielding = true; - matched = card->childrenCards[wCC]; - } - } - } - if(matched) - wC = card->childrenCards.size(); - } - if(!matched) - card->isDualWielding = false; - } - } - } - //------------------------------------- - - for (int i = 0; i < 2; i++) - { - /////////////////////////////////////////////////////////// - //life checks/poison checks also checks cant win or lose.// - /////////////////////////////////////////////////////////// - players[i]->DeadLifeState(true);//refactored - } - ////////////////////////////////////////////////////// - //-------------card based states effects------------// - ////////////////////////////////////////////////////// - //ie:cantcast; extra land; extra turn;no max hand;--// - ////////////////////////////////////////////////////// - - for (int i = 0; i < 2; i++) - { - //checks if a player has a card which has the stated ability in play. - Player * p = players[i]; - MTGGameZone * z = players[i]->game->inPlay; - int nbcards = z->nb_cards; - //------------------------------ - p->nomaxhandsize = (z->hasAbility(Constants::NOMAXHAND)); - - ///////////////////////////////////////////////// - //handle end of turn effects while we're at it.// - ///////////////////////////////////////////////// - if (mCurrentGamePhase == MTG_PHASE_ENDOFTURN+1) - { - for (int j = 0; j < nbcards; ++j) - { - MTGCardInstance * c = z->cards[j]; - - if(!c)break; - while (c->flanked) - { - ///////////////////////////////// - //undoes the flanking on a card// - ///////////////////////////////// - c->power += 1; - c->addToToughness(1); - c->flanked -= 1; - } - c->fresh = 0; - if(c->wasDealtDamage && c->isInPlay(this)) - c->wasDealtDamage = false; - c->damageToController = false; - c->damageToOpponent = false; - c->damageToCreature = false; - c->isAttacking = NULL; - } - for (int t = 0; t < nbcards; t++) - { - MTGCardInstance * c = z->cards[t]; - - if(!c->isPhased) - { - if (c->has(Constants::TREASON)) - { - MTGCardInstance * beforeCard = c; - p->game->putInGraveyard(c); - WEvent * e = NEW WEventCardSacrifice(beforeCard,c); - receiveEvent(e); - } - if (c->has(Constants::UNEARTH)) - { - p->game->putInExile(c); - - } - } - if(c->modifiedbAbi > 0) - { - c->modifiedbAbi = 0; - c->basicAbilities = c->origbasicAbilities; - } - if(nbcards > z->nb_cards) - { - t = 0; - nbcards = z->nb_cards; - } - } - - MTGGameZone * f = p->game->graveyard; - for (int k = 0; k < f->nb_cards; k++) - { - MTGCardInstance * card = f->cards[k]; - card->fresh = 0; - } - } - if (z->nb_cards == 0) - { - p->nomaxhandsize = false; - } - ////////////////////////// - // Check auras on a card// - ////////////////////////// - enchantmentStatus(); - ///////////////////////////// - // Check affinity on a card// - // plus modify costs // - ///////////////////////////// - Affinity(); - ///////////////////////////////////// - // Check colored statuses on cards // - ///////////////////////////////////// - for(int w = 0;w < z->nb_cards;w++) - { - int colored = 0; - for (int i = Constants::MTG_COLOR_GREEN; i <= Constants::MTG_COLOR_WHITE; ++i) - { - if (z->cards[w]->hasColor(i)) - ++colored; - } - z->cards[w]->isMultiColored = (colored > 1) ? 1 : 0; - } - } - /////////////////////////////////// - //phase based state effects------// - /////////////////////////////////// - if (combatStep == TRIGGERS) - { - if (!mLayers->stackLayer()->getNext(NULL, 0, NOT_RESOLVED) && !targetChooser - && !mLayers->actionLayer()->isWaitingForAnswer()) - mLayers->stackLayer()->AddNextCombatStep(); - } - - //Auto skip Phases - int skipLevel = (currentPlayer->playMode == Player::MODE_TEST_SUITE || mLoading) ? Constants::ASKIP_NONE - : options[Options::ASPHASES].number; - - if (skipLevel == Constants::ASKIP_SAFE || skipLevel == Constants::ASKIP_FULL) - { - if ((opponent()->isAI() && !(isInterrupting)) && ((mCurrentGamePhase == MTG_PHASE_UNTAP) - || (mCurrentGamePhase == MTG_PHASE_DRAW) || (mCurrentGamePhase == MTG_PHASE_COMBATBEGIN) - || ((mCurrentGamePhase == MTG_PHASE_COMBATATTACKERS) && (currentPlayer->noPossibleAttackers())) - || mCurrentGamePhase == MTG_PHASE_COMBATEND || mCurrentGamePhase == MTG_PHASE_ENDOFTURN - || ((mCurrentGamePhase == MTG_PHASE_CLEANUP) && (currentPlayer->game->hand->nb_cards < 8)))) - userRequestNextGamePhase(); - } - if (skipLevel == Constants::ASKIP_FULL) - { - if ((opponent()->isAI() && !(isInterrupting)) && (mCurrentGamePhase == MTG_PHASE_UPKEEP - || mCurrentGamePhase == MTG_PHASE_COMBATDAMAGE)) - userRequestNextGamePhase(); - } -} - -void GameObserver::enchantmentStatus() -{ - for (int i = 0; i < 2; i++) - { - MTGGameZone * zone = players[i]->game->inPlay; - for (int k = zone->nb_cards - 1; k >= 0; k--) - { - MTGCardInstance * card = zone->cards[k]; - if (card && !card->hasType(Subtypes::TYPE_EQUIPMENT) && !card->hasSubtype(Subtypes::TYPE_AURA)) - { - card->enchanted = false; - card->auras = 0; - } - } - for (int j = zone->nb_cards - 1; j >= 0; j--) - { - MTGCardInstance * card = zone->cards[j]; - if (card->target && isInPlay(card->target) && !card->hasType(Subtypes::TYPE_EQUIPMENT) && card->hasSubtype(Subtypes::TYPE_AURA)) - { - card->target->enchanted = true; - card->target->auras += 1; - } - } - } -} - -void GameObserver::Affinity() -{ - for (int dd = 0; dd < 2; dd++) - { - MTGGameZone * dzones[] = { players[dd]->game->graveyard, players[dd]->game->hand, players[dd]->game->library, players[dd]->game->exile }; - for (int kk = 0; kk < 4; kk++) - { - MTGGameZone * zone = dzones[kk]; - for (int cc = zone->nb_cards - 1; cc >= 0; cc--) - {//start - MTGCardInstance * card = zone->cards[cc]; - if (!card) - continue; - - int color = 0; - string type = ""; - //only do any of the following if a card with the stated ability is in your hand. - ManaCost * original = NEW ManaCost(); - original->copy(card->model->data->getManaCost()); - if(card->getIncreasedManaCost()->getConvertedCost()||card->getReducedManaCost()->getConvertedCost()) - {//start1 - if(card->getIncreasedManaCost()->getConvertedCost()) - original->add(card->getIncreasedManaCost()); - if(card->getReducedManaCost()->getConvertedCost()) - original->remove(card->getReducedManaCost()); - if(card->getManaCost()) - card->getManaCost()->copy(original); - if(card->getManaCost()->extraCosts) - { - for(unsigned int i = 0; i < card->getManaCost()->extraCosts->costs.size();i++) - { - card->getManaCost()->extraCosts->costs[i]->setSource(card); - } - } - }//end1 - int reducem = 0; - bool resetCost = false; - for(unsigned int na = 0; na < card->cardsAbilities.size();na++) - {//start2 - ANewAffinity * newAff = dynamic_cast(card->cardsAbilities[na]); - if(newAff) - { - if(!resetCost) - { - resetCost = true; - card->getManaCost()->copy(original); - if(card->getManaCost()->extraCosts) - { - for(unsigned int i = 0; i < card->getManaCost()->extraCosts->costs.size();i++) - { - card->getManaCost()->extraCosts->costs[i]->setSource(card); - } - } - } - TargetChooserFactory tf(this); - TargetChooser * tcn = tf.createTargetChooser(newAff->tcString,card,NULL); - - for (int w = 0; w < 2; ++w) - { - Player *p = this->players[w]; - MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->stack, p->game->exile }; - for (int k = 0; k < 6; k++) - { - MTGGameZone * z = zones[k]; - if (tcn->targetsZone(z)) - { - reducem += z->countByCanTarget(tcn); - } - } - } - SAFE_DELETE(tcn); - ManaCost * removingCost = ManaCost::parseManaCost(newAff->manaString); - for(int j = 0; j < reducem; j++) - card->getManaCost()->remove(removingCost); - SAFE_DELETE(removingCost); - } - }//end2 - if(card->has(Constants::AFFINITYARTIFACTS)|| - card->has(Constants::AFFINITYFOREST)|| - card->has(Constants::AFFINITYGREENCREATURES)|| - card->has(Constants::AFFINITYISLAND)|| - card->has(Constants::AFFINITYMOUNTAIN)|| - card->has(Constants::AFFINITYPLAINS)|| - card->has(Constants::AFFINITYSWAMP)) - {//start3 - if (card->has(Constants::AFFINITYARTIFACTS)) - { - type = "artifact"; - } - else if (card->has(Constants::AFFINITYSWAMP)) - { - type = "swamp"; - } - else if (card->has(Constants::AFFINITYMOUNTAIN)) - { - type = "mountain"; - } - else if (card->has(Constants::AFFINITYPLAINS)) - { - type = "plains"; - } - else if (card->has(Constants::AFFINITYISLAND)) - { - type = "island"; - } - else if (card->has(Constants::AFFINITYFOREST)) - { - type = "forest"; - } - else if (card->has(Constants::AFFINITYGREENCREATURES)) - { - color = 1; - type = "creature"; - } - card->getManaCost()->copy(original); - if(card->getManaCost()->extraCosts) - { - for(unsigned int i = 0; i < card->getManaCost()->extraCosts->costs.size();i++) - { - card->getManaCost()->extraCosts->costs[i]->setSource(card); - } - } - int reduce = 0; - if(card->has(Constants::AFFINITYGREENCREATURES)) - { - TargetChooserFactory tf(this); - TargetChooser * tc = tf.createTargetChooser("creature[green]",NULL); - reduce = card->controller()->game->battlefield->countByCanTarget(tc); - SAFE_DELETE(tc); - } - else - { - reduce = card->controller()->game->battlefield->countByType(type); - } - for(int i = 0; i < reduce;i++) - { - if(card->getManaCost()->getCost(color) > 0) - card->getManaCost()->remove(color,1); - } - }//end3 - //trinisphere... now how to implement kicker recomputation - - if(card->has(Constants::TRINISPHERE)) - { - for(int jj = card->getManaCost()->getConvertedCost(); jj < 3; jj++) - { - card->getManaCost()->add(Constants::MTG_COLOR_ARTIFACT, 1); - card->countTrini++; - } - } - else - { - if(card->countTrini) - { - card->getManaCost()->remove(Constants::MTG_COLOR_ARTIFACT, card->countTrini); - card->countTrini=0; - } - } - - SAFE_DELETE(original); - }//end - } - } -} - -void GameObserver::Render() -{ - if(mLayers) - mLayers->Render(); - if (targetChooser || (mLayers && mLayers->actionLayer()->isWaitingForAnswer())) - JRenderer::GetInstance()->DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, ARGB(255,255,0,0)); - if (mExtraPayment) - mExtraPayment->Render(); - - for (size_t i = 0; i < players.size(); ++i) - { - players[i]->Render(); - } -} - -void GameObserver::ButtonPressed(PlayGuiObject * target) -{ - DebugTrace("GAMEOBSERVER Click"); - if (CardView* cardview = dynamic_cast(target)) - { - MTGCardInstance * card = cardview->getCard(); - cardClick(card, card); - } - else if (GuiLibrary* library = dynamic_cast(target)) - { - if (library->showCards) - { - library->toggleDisplay(); - forceShuffleLibraries(); - } - else - { - TargetChooser * _tc = this->getCurrentTargetChooser(); - if (_tc && _tc->targetsZone(library->zone)) - { - library->toggleDisplay(); - library->zone->needShuffle = true; - } - } - } - else if (GuiGraveyard* graveyard = dynamic_cast(target)) - graveyard->toggleDisplay(); - else if (GuiExile* exile = dynamic_cast(target)) - exile->toggleDisplay(); - //opponenthand - else if (GuiOpponentHand* opponentHand = dynamic_cast(target)) - if (opponentHand->showCards) - { - opponentHand->toggleDisplay(); - } - else - { - TargetChooser * _tc = this->getCurrentTargetChooser(); - if (_tc && _tc->targetsZone(opponentHand->zone)) - { - opponentHand->toggleDisplay(); - } - } - //end opponenthand - else if (GuiAvatar* avatar = dynamic_cast(target)) - { - cardClick(NULL, avatar->player); - } - else if (dynamic_cast(target)) - { - mLayers->getPhaseHandler()->NextGamePhase(); - } -} - -void GameObserver::stackObjectClicked(Interruptible * action) -{ - stringstream stream; - stream << "stack[" << mLayers->stackLayer()->getIndexOf(action) << "]"; - logAction(currentlyActing(), stream.str()); - - if (targetChooser != NULL) - { - int result = targetChooser->toggleTarget(action); - if (result == TARGET_OK_FULL) - { - cardClick(cardWaitingForTargets, 0, false); - } - else - { - return; - } - } - else - { - int reaction = mLayers->actionLayer()->isReactingToTargetClick(action); - if (reaction == -1) - mLayers->actionLayer()->reactToTargetClick(action); - } -} - -bool GameObserver::WaitForExtraPayment(MTGCardInstance * card) -{ - bool result = false; - if (mExtraPayment) - { - if (card) - { - mExtraPayment->tryToSetPayment(card); - } - if (mExtraPayment->isPaymentSet()) - { - mLayers->actionLayer()->reactToClick(mExtraPayment->action, mExtraPayment->source); - mExtraPayment = NULL; - } - result = true; - } - - return result; -} - -int GameObserver::cardClick(MTGCardInstance * card, MTGAbility *ability) -{ - MTGGameZone* zone = card->currentZone; - size_t index = 0; - if(zone) - index = zone->getIndex(card); - int choice; - bool logChoice = mLayers->actionLayer()->getMenuIdFromCardAbility(card, ability, choice); - int result = ability->reactToClick(card); - logAction(card, zone, index, result); - - if(logChoice) { - stringstream stream; - stream << "choice " << choice; - logAction(currentActionPlayer, stream.str()); - } - - return result; -} - -int GameObserver::cardClick(MTGCardInstance * card, int abilityType) -{ - int result = 0; - MTGAbility * a = mLayers->actionLayer()->getAbility(abilityType); - - if(a) - { - result = cardClick(card, a); - } - - return result; -} - -int GameObserver::cardClickLog(bool log, Player* clickedPlayer, MTGGameZone* zone, MTGCardInstance*backup, size_t index, int toReturn) -{ - if(log) - { - if (clickedPlayer) { - this->logAction(clickedPlayer); - } else if(zone) { - this->logAction(backup, zone, index, toReturn); - } - } - return toReturn; -} - -int GameObserver::cardClick(MTGCardInstance * card, Targetable * object, bool log) -{ - Player * clickedPlayer = NULL; - int toReturn = 0; - int handmodified = 0; - MTGGameZone* zone = NULL; - size_t index = 0; - MTGCardInstance* backup = NULL; - - if (!card) { - clickedPlayer = ((Player *) object); - } else { - backup = card; - zone = card->currentZone; - if(zone) - { - index = zone->getIndex(card); - } - } - - do { - if (targetChooser) - { - int result; - if (card) - { - if (card == cardWaitingForTargets) - { - int _result = targetChooser->ForceTargetListReady(); - if(targetChooser->targetMin && int(targetChooser->getNbTargets()) < targetChooser->maxtargets) - _result = 0; - if (_result) - { - result = TARGET_OK_FULL; - } - else - { - result = targetChooser->targetsReadyCheck(); - } - } - else - { - result = targetChooser->toggleTarget(card); - WEvent * e = NEW WEventTarget(card,cardWaitingForTargets); - receiveEvent(e); - } - } - else - { - result = targetChooser->toggleTarget(clickedPlayer); - if(card) - card->playerTarget = clickedPlayer; - else - targetChooser->source->playerTarget = clickedPlayer; - } - if (result == TARGET_OK_FULL) - card = cardWaitingForTargets; - else { - toReturn = 1; - break; - } - } - ExtraManaCost * costType = NULL; - if( mExtraPayment && mExtraPayment->costs.size()) - costType = dynamic_cast(mExtraPayment->costs[0]); - - if (WaitForExtraPayment(card) && !costType) - { - toReturn = 1; - break; - } - - int reaction = 0; - - if (ORDER == combatStep) - { - //TODO it is possible at this point that card is NULL. if so, what do we return since card->defenser would result in a crash? - card->defenser->raiseBlockerRankOrder(card); - toReturn = 1; - break; - } - - if (card) - { - //card played as normal, alternative cost, buyback, flashback, retrace. - - //the variable "paymenttype = int" only serves one purpose, to tell this bug fix what menu item you clicked on... - // all alternative cost or play methods suffered from the fix because if the card contained "target=" - // it would automatically force the play method to putinplayrule...even charge you the original mana cost. - - /* Fix for Issue http://code.google.com/p/wagic/issues/detail?id=270 - put into play is hopefully the only ability causing that kind of trouble - If the same kind of issue occurs with other abilities, let's think of a cleaner solution - */ - if (targetChooser) - { - MTGAbility * a = mLayers->actionLayer()->getAbility(card->paymenttype); - toReturn = a->reactToClick(card); - return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); - } - - reaction = mLayers->actionLayer()->isReactingToClick(card); - if (reaction == -1) { - toReturn = mLayers->actionLayer()->reactToClick(card); - return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); - } - } - else - {//this handles abilities on a menu...not just when card is being played - reaction = mLayers->actionLayer()->isReactingToTargetClick(object); - if (reaction == -1) { - toReturn = mLayers->actionLayer()->reactToTargetClick(object); - return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); - } - } - - if (!card) { - toReturn = 0; - return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); - } - - //Current player's hand - handmodified = currentPlayer->handsize+currentPlayer->handmodifier; - if(handmodified < 0) - handmodified = 0; - if (currentPlayer->game->hand->hasCard(card) && mCurrentGamePhase == MTG_PHASE_CLEANUP - && currentPlayer->game->hand->nb_cards > handmodified && currentPlayer->nomaxhandsize == false) - { - WEvent * e = NEW WEventCardDiscard(currentPlayer->game->hand->cards[0]); - receiveEvent(e); - currentPlayer->game->putInGraveyard(card); - } - else if (reaction) - { - if (reaction == 1) - { - toReturn = mLayers->actionLayer()->reactToClick(card); - return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); - } - else - { - mLayers->actionLayer()->setMenuObject(object); - toReturn = 1; - return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); - } - } - else if (card->isTapped() && card->controller() == currentPlayer) - { - toReturn = untap(card); - return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); - } - } while(0); - - - return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); -} - -int GameObserver::untap(MTGCardInstance * card) -{ - if (!card->isUntapping()) - { - return 0; - } - if (card->has(Constants::DOESNOTUNTAP)) - return 0; - if (card->frozen > 0) - return 0; - card->attemptUntap(); - return 1; -} - -TargetChooser * GameObserver::getCurrentTargetChooser() -{ - if(mLayers) - { - TargetChooser * _tc = mLayers->actionLayer()->getCurrentTargetChooser(); - if (_tc) - return _tc; - } - return targetChooser; -} - -/* Returns true if the card is in one of the player's play zone */ -int GameObserver::isInPlay(MTGCardInstance * card) -{ - for (int i = 0; i < 2; i++) - { - if (players[i]->game->isInPlay(card)) - return 1; - } - return 0; -} -int GameObserver::isInGrave(MTGCardInstance * card) -{ - - for (int i = 0; i < 2; i++) - { - MTGGameZone * graveyard = players[i]->game->graveyard; - if (players[i]->game->isInZone(card,graveyard)) - return 1; - } - return 0; -} -int GameObserver::isInExile(MTGCardInstance * card) -{ - - for (int i = 0; i < 2; i++) - { - MTGGameZone * exile = players[i]->game->exile; - if (players[i]->game->isInZone(card,exile)) - return 1; - } - return 0; -} - -void GameObserver::cleanupPhase() -{ - currentPlayer->cleanupPhase(); - opponent()->cleanupPhase(); -} - -void GameObserver::untapPhase() -{ - currentPlayer->inPlay()->untapAll(); -} - -int GameObserver::receiveEvent(WEvent * e) -{ - if (!e) - return 0; - eventsQueue.push(e); - if (eventsQueue.size() > 1) - return -1; //resolving events can generate more events - int result = 0; - while (eventsQueue.size()) - { - WEvent * ev = eventsQueue.front(); - result += mLayers->receiveEvent(ev); - for (int i = 0; i < 2; ++i) - { - result += players[i]->receiveEvent(ev); - } - SAFE_DELETE(ev); - eventsQueue.pop(); - } - return result; -} - -Player * GameObserver::currentlyActing() -{ - if (isInterrupting) - return isInterrupting; - return currentActionPlayer; -} - -//TODO CORRECT THIS MESS -int GameObserver::targetListIsSet(MTGCardInstance * card) -{ - if (targetChooser == NULL) - { - TargetChooserFactory tcf(this); - targetChooser = tcf.createTargetChooser(card); - if (targetChooser == NULL) - { - return 1; - } - } - if(targetChooser && targetChooser->validTargetsExist()) - { - cardWaitingForTargets = card; - return (targetChooser->targetListSet()); - } - else - SAFE_DELETE(targetChooser); - return 0; - -} - -ostream& operator<<(ostream& out, const GameObserver& g) -{ - if(g.startupGameSerialized == "") - { - out << "[init]" << endl; - out << "player=" << g.currentPlayerId + 1 << endl; - if(g.mCurrentGamePhase != MTG_PHASE_INVALID) - out << "phase=" << g.phaseRing->phaseName(g.mCurrentGamePhase) << endl; - out << "[player1]" << endl; - out << *(g.players[0]) << endl; - out << "[player2]" << endl; - out << *(g.players[1]) << endl; - return out; - } - else - { - out << "seed:"; - out << g.mSeed; - out << endl; - out << "rvalues:"; - g.randomGenerator.saveUsedRandValues(out); - out << endl; - out << g.startupGameSerialized; - } - - out << "[do]" << endl; - list::const_iterator it; - - for(it = (g.actionsList.begin()); it != (g.actionsList.end()); it++) - { - out << (*it) << endl; - } - - out << "[end]" << endl; - return out; -} - -bool GameObserver::parseLine(const string& s) -{ - size_t limiter = s.find("="); - if (limiter == string::npos) limiter = s.find(":"); - string areaS; - if (limiter != string::npos) - { - areaS = s.substr(0, limiter); - if (areaS.compare("player") == 0) - { - currentPlayerId = atoi(s.substr(limiter + 1).c_str()) - 1; - return true; - } - else if (areaS.compare("phase") == 0) - { - mCurrentGamePhase = PhaseRing::phaseStrToInt(s.substr(limiter + 1).c_str()); - return true; - } - } - return false; -} - -bool GameObserver::load(const string& ss, bool undo, int controlledPlayerIndex -#ifdef TESTSUITE - , TestSuiteGame* testgame -#endif - ) -{ - bool currentPlayerSet = false; - int state = -1; - string s; - stringstream stream(ss); - - DebugTrace("Loading " + ss); - randomGenerator.loadRandValues(""); - - cleanup(); - - while (std::getline(stream, s)) - { - if (!s.size()) continue; - if (s[s.size() - 1] == '\r') s.erase(s.size() - 1); //Handle DOS files - if (!s.size()) continue; - if (s[0] == '#') continue; - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - if (s.find("seed ") == 0) - { - mSeed = atoi(s.substr(5).c_str()); - randomGenerator.setSeed(mSeed); - continue; - } - if (s.find("rvalues:") == 0) - { - randomGenerator.loadRandValues(s.substr(8).c_str()); - continue; - } - switch (state) - { - case -1: - if (s.compare("[init]") == 0) - state++; - break; - case 0: - if (s.compare("[player1]") == 0) - { - state++; - } - else - { - currentPlayerSet = parseLine(s); - } - break; - case 1: - if (s.compare("[player2]") == 0) - { - state++; - } - else - { - if(players.size() == 0 || !players[0]) - { - if (s.find("mode=") == 0) - { - createPlayer(s.substr(5) - #ifdef TESTSUITE - , testgame - #endif //TESTSUITE - ); - } - } - players[0]->parseLine(s); - } - break; - case 2: - if (s.compare("[do]") == 0) - { - state++; - } - else - { - if(players.size() == 1 || !players[1]) - { - if (s.find("mode=") == 0) - { - createPlayer(s.substr(5) -#ifdef TESTSUITE - , testgame -#endif //TESTSUITE - ); - } - } - players[1]->parseLine(s); - } - break; - case 3: - if (s.compare("[end]") == 0) - { - turn = 0; - mLayers = NEW DuelLayers(this, controlledPlayerIndex); - currentPlayer = players[currentPlayerId]; - phaseRing = NEW PhaseRing(this); - startedAt = time(0); - - // take a snapshot before processing the actions - resetStartupGame(); - - if(mRules) mRules->initGame(this, currentPlayerSet); - phaseRing->goToPhase(0, currentPlayer, false); - phaseRing->goToPhase(mCurrentGamePhase, currentPlayer); - -#ifdef TESTSUITE - if(testgame) - testgame->initGame(); -#endif //TESTSUITE - - processActions(undo - #ifdef TESTSUITE - , testgame - #endif //TESTSUITE - ); - } - else - { - logAction(s); - } - break; - } - } - - return true; -} - -bool GameObserver::processAction(const string& s) -{ - Player* p = players[1]; - if (s.find("p1") != string::npos) - p = players[0]; - - MTGGameZone* zone = NULL; - if(s.find(string(p->game->hand->getName())+"[") != string::npos) - zone = p->game->hand; - else if(s.find(string(p->game->battlefield->getName())+"[") != string::npos) - zone = p->game->battlefield; - else if(s.find(string(p->game->graveyard->getName())+"[") != string::npos) - zone = p->game->graveyard; - else if(s.find(string(p->game->library->getName())+"[") != string::npos) - zone = p->game->library; - - if(zone) { - size_t begin = s.find("[")+1; - size_t size = s.find("]")-begin; - size_t index = atoi(s.substr(begin, size).c_str()); - dumpAssert(index < zone->cards.size()); - cardClick(zone->cards[index], zone->cards[index]); - } else if (s.find("stack") != string::npos) { - size_t begin = s.find("[")+1; - size_t size = s.find("]")-begin; - size_t index = atoi(s.substr(begin, size).c_str()); - stackObjectClicked((Interruptible*)mLayers->stackLayer()->getByIndex(index)); - } else if (s.find("yes") != string::npos) { - mLayers->stackLayer()->setIsInterrupting(p); - } else if (s.find("no") != string::npos) { - mLayers->stackLayer()->cancelInterruptOffer(); - } else if (s.find("endinterruption") != string::npos) { - mLayers->stackLayer()->endOfInterruption(); - } else if (s.find("next") != string::npos) { - userRequestNextGamePhase(); - } else if (s.find("combatok") != string::npos) { - mLayers->combatLayer()->clickOK(); - } else if (s == "p1" || s == "p2") { - cardClick(NULL, p); - } else if (s.find("choice") != string::npos) { - int choice = atoi(s.substr(s.find("choice ") + 7).c_str()); - mLayers->actionLayer()->doReactTo(choice); - } else if (s == "p1" || s == "p2") { - cardClick(NULL, p); - } else if(s.find("mulligan") != string::npos) { - Mulligan(p); - } else if(s.find("shufflelib") != string::npos) { - // This should probably be differently and be automatically part of the ability triggered - // that would allow the AI to use it as well. - shuffleLibrary(p); - } else { - DebugTrace("no clue about: " + s); - } - - return true; -} - -bool GameObserver::processActions(bool undo - #ifdef TESTSUITE - , TestSuiteGame* testgame - #endif - ) -{ - bool result = false; - size_t cmdIndex = 0; - - loadingList = actionsList; - actionsList.clear(); - - mLoading = true; - float counter = 0.0f; - - // To handle undo, we'll remove the last P1 action and all P2 actions after. - if(undo && loadingList.size()) { - while(loadingList.back().find("p2") != string::npos) - loadingList.pop_back(); - // we do not undo "next phase" action to avoid abuse by users - if(loadingList.back().find("next") == string::npos) - loadingList.pop_back(); - } - - // We fake here cause the initialization before caused mana pool reset events to be triggered - // So, we need them flushed to be able to set the manapool to whatever we need - GameObserver::Update(counter); - counter += 1.000f; - -#ifdef TESTSUITE - if(testgame) - { - testgame->ResetManapools(); - } -#endif - - for(loadingite = loadingList.begin(); loadingite != loadingList.end(); loadingite++, cmdIndex++) - { - processAction(*loadingite); - - size_t nb = actionsList.size(); - - for (int i = 0; i<6; i++) - { - // let's fake an update - GameObserver::Update(counter); - counter += 1.000f; - } - dumpAssert(actionsList.back() == *loadingite); - dumpAssert(nb == actionsList.size()); - dumpAssert(cmdIndex == (actionsList.size()-1)); - } - - mLoading = false; - return result; -} - -void GameObserver::logAction(Player* player, const string& s) { - if(player == players[0]) - if(s != "") - logAction("p1." + s); - else - logAction("p1"); - else - if(s != "") - logAction("p2." + s); - else - logAction("p2"); -} - -void GameObserver::logAction(MTGCardInstance* card, MTGGameZone* zone, size_t index, int result) { - stringstream stream; - if(zone == NULL) zone = card->currentZone; - stream << "p" << ((card->controller()==players[0])?"1.":"2.") - << zone->getName()<< "[" << index << "] " - << result << card->getLCName(); - logAction(stream.str()); -} - -void GameObserver::logAction(const string& s) -{ - if(mLoading) - { - string toCheck = *loadingite; - dumpAssert(toCheck == s); - } - actionsList.push_back(s); -}; - -bool GameObserver::undo() -{ - stringstream stream; - stream << *this; - DebugTrace(stream.str()); - return load(stream.str(), true); -} - -void GameObserver::Mulligan(Player* player) -{ - if(!player) player = currentPlayer; - logAction(player, "mulligan"); - player->takeMulligan(); -} - -void GameObserver::serumMulligan(Player* player) -{ - if(!player) player = currentPlayer; - logAction(player, "mulligan serum powder"); - player->serumMulligan(); -} - -Player* GameObserver::createPlayer(const string& playerMode - #ifdef TESTSUITE - , TestSuiteGame* testgame - #endif //TESTSUITE - ) -{ - Player::Mode aMode = (Player::Mode)atoi(playerMode.c_str()); - Player* pPlayer = 0; - - switch(aMode) - { - case Player::MODE_AI: - AIPlayerFactory playerCreator; - if(players.size()) - pPlayer = playerCreator.createAIPlayer(this, MTGCollection(), players[0]); - else - pPlayer = playerCreator.createAIPlayer(this, MTGCollection(), 0); - break; - case Player::MODE_HUMAN: - pPlayer = new HumanPlayer(this, "", ""); - break; - case Player::MODE_TEST_SUITE: -#ifdef TESTSUITE - if(players.size()) - pPlayer = new TestSuiteAI(testgame, 1); - else - pPlayer = new TestSuiteAI(testgame, 0); -#endif //TESTSUITE - break; - } - - if(pPlayer) - { - players.push_back(pPlayer); - } - - return pPlayer; -} - -#ifdef TESTSUITE -void GameObserver::loadTestSuitePlayer(int playerId, TestSuiteGame* testSuite) -{ - loadPlayer(playerId, new TestSuiteAI(testSuite, playerId)); -} -#endif //TESTSUITE - -void GameObserver::loadPlayer(int playerId, Player* player) -{ - //Because we're using a vector instead of an array (why?), - // we have to prepare the vector in order to be the right size to accomodate the playerId variable - // see http://code.google.com/p/wagic/issues/detail?id=772 - if (players.size() > (size_t) playerId) { - SAFE_DELETE(players[playerId]); - players[playerId] = NULL; - } else { - while (players.size() <= (size_t) playerId) - { - players.push_back(NULL); - } - } - - players[playerId] = player; -} - -void GameObserver::loadPlayer(int playerId, PlayerType playerType, int decknb, bool premadeDeck) -{ - if (decknb) - { - if (playerType == PLAYER_TYPE_HUMAN) - { //Human Player - if(playerId == 0) - { - char deckFile[255]; - if (premadeDeck) - sprintf(deckFile, "player/premade/deck%i.txt", decknb); - else - sprintf(deckFile, "%s/deck%i.txt", options.profileFile().c_str(), decknb); - char deckFileSmall[255]; - sprintf(deckFileSmall, "player_deck%i", decknb); - - loadPlayer(playerId, NEW HumanPlayer(this, deckFile, deckFileSmall, premadeDeck)); - } - } - else - { //AI Player, chooses deck - AIPlayerFactory playerCreator; - Player * opponent = NULL; - if (playerId == 1) opponent = players[0]; - - loadPlayer(playerId, playerCreator.createAIPlayer(this, MTGCollection(), opponent, decknb)); - } - } - else - { - //Random deck - AIPlayerFactory playerCreator; - Player * opponent = NULL; - - // Reset the random logging. - randomGenerator.loadRandValues(""); - - if (playerId == 1) opponent = players[0]; -#ifdef AI_CHANGE_TESTING - if (playerType == PLAYER_TYPE_CPU_TEST) - loadPlayer(playerId, playerCreator.createAIPlayerTest(this, MTGCollection(), opponent, playerId == 0 ? "ai/bakaA/" : "ai/bakaB/")); - else -#endif - { - loadPlayer(playerId, playerCreator.createAIPlayer(this, MTGCollection(), opponent)); - } - - if (playerType == PLAYER_TYPE_CPU_TEST) - ((AIPlayer *) players[playerId])->setFastTimerMode(); - } -} - -#ifdef NETWORK_SUPPORT -NetworkGameObserver::NetworkGameObserver(JNetwork* pNetwork, WResourceManager* output, JGE* input) - : GameObserver(output, input), mpNetworkSession(pNetwork), mSynchronized(false) -{ - mpNetworkSession->registerCommand("loadPlayer", this, loadPlayer, ignoreResponse); - mpNetworkSession->registerCommand("synchronize", this, synchronize, checkSynchro); - mpNetworkSession->registerCommand("sendAction", this, sendAction, checkSynchro); - mpNetworkSession->registerCommand("disconnect", this, disconnect, ignoreResponse); -} - -NetworkGameObserver::~NetworkGameObserver() -{ - mpNetworkSession->sendCommand("disconnect", ""); -} - -void NetworkGameObserver::disconnect(void*pxThis, stringstream&, stringstream&) -{ - NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; - pThis->setLoser(pThis->getView()->getRenderedPlayerOpponent()); -} - -void NetworkGameObserver::Update(float dt) -{ - mpNetworkSession->Update(); - ::GameObserver::Update(dt); -} - -void NetworkGameObserver::loadPlayer(int playerId, Player* player) -{ - GameObserver::loadPlayer(playerId, player); - stringstream out; - out << *player; - mpNetworkSession->sendCommand("loadPlayer", out.str()); -} - -void NetworkGameObserver::loadPlayer(void*pxThis, stringstream& in, stringstream&) -{ - NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; - Player* pPlayer = 0; - string s; - - while(std::getline(in, s)) - { - if (s.find("mode=") == 0) - { - pPlayer = pThis->createPlayer(s.substr(5) - #ifdef TESTSUITE - , 0 - #endif //TESTSUITE - ); - } - - if(pPlayer && (!pPlayer->parseLine(s))) - { - break; - } - } -} - -void NetworkGameObserver::synchronize() -{ - if(!mSynchronized && mpNetworkSession->isServer()) - { - stringstream out; - out << *this; - mpNetworkSession->sendCommand("synchronize", out.str()); - mSynchronized = true; - } -} - -void NetworkGameObserver::synchronize(void*pxThis, stringstream& in, stringstream& out) -{ - NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; - // now, we need to load the game from player 2's perspective - pThis->load(in.str(), false, 1); - out << *pThis; -} - - -void NetworkGameObserver::checkSynchro(void*pxThis, stringstream& in, stringstream&) -{ - NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; - - GameObserver aGame; - aGame.mRules = pThis->mRules; - aGame.load(in.str()); - - assert(aGame == *pThis); -} - -void NetworkGameObserver::sendAction(void*pxThis, stringstream& in, stringstream&) -{ - NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; - - pThis->mForwardAction = false; - pThis->processAction(in.str()); - pThis->mForwardAction = true; - //out << *pThis; -} - -void NetworkGameObserver::logAction(const string& s) -{ - GameObserver::logAction(s); - if(mForwardAction) - mpNetworkSession->sendCommand("sendAction", s); -} - -#endif +#include "PrecompiledHeader.h" + +#include "GameObserver.h" +#include "CardGui.h" +#include "Damage.h" +#include "Rules.h" +#include "ExtraCost.h" +#include "Subtypes.h" +#include +#include +#include "MTGGamePhase.h" +#include "GuiPhaseBar.h" +#include "AIPlayerBaka.h" +#include "MTGRules.h" +#include "Trash.h" +#include "DeckManager.h" +#include "GuiCombat.h" +#include +#ifdef TESTSUITE +#include "TestSuiteAI.h" +#endif +#ifdef NETWORK_SUPPORT +#include "NetworkPlayer.h" +#endif + + +void GameObserver::cleanup() +{ + SAFE_DELETE(targetChooser); + SAFE_DELETE(mLayers); + SAFE_DELETE(phaseRing); + SAFE_DELETE(replacementEffects); + for (size_t i = 0; i < players.size(); ++i) + { + SAFE_DELETE(players[i]); + } + players.clear(); + + currentPlayer = NULL; + currentActionPlayer = NULL; + isInterrupting = NULL; + currentPlayerId = 0; + mCurrentGamePhase = MTG_PHASE_INVALID; + targetChooser = NULL; + cardWaitingForTargets = NULL; + mExtraPayment = NULL; + gameOver = NULL; + phaseRing = NULL; + replacementEffects = NEW ReplacementEffects(); + combatStep = BLOCKERS; + connectRule = false; + actionsList.clear(); + gameTurn.clear(); + OpenedDisplay = NULL; +} + +GameObserver::~GameObserver() +{ + LOG("==Destroying GameObserver=="); + + for (size_t i = 0; i < players.size(); ++i) + { + players[i]->End(); + } + SAFE_DELETE(targetChooser); + SAFE_DELETE(mLayers); + SAFE_DELETE(phaseRing); + SAFE_DELETE(replacementEffects); + for (size_t i = 0; i < players.size(); ++i) + { + SAFE_DELETE(players[i]); + } + players.clear(); + delete[] ExtraRules; + ExtraRules = 0; + LOG("==GameObserver Destroyed=="); + SAFE_DELETE(mTrash); + SAFE_DELETE(mDeckManager); + +} + +GameObserver::GameObserver(WResourceManager *output, JGE* input) + : mSeed((unsigned int)time(0)), randomGenerator(mSeed, true), mResourceManager(output), mJGE(input) + +{ + ExtraRules = new MTGCardInstance[2](); + + mGameType = GAME_TYPE_CLASSIC; + currentPlayer = NULL; + currentActionPlayer = NULL; + isInterrupting = NULL; + currentPlayerId = 0; + mCurrentGamePhase = MTG_PHASE_INVALID; + targetChooser = NULL; + cardWaitingForTargets = NULL; + mExtraPayment = NULL; + OpenedDisplay = NULL; + gameOver = NULL; + phaseRing = NULL; + replacementEffects = NEW ReplacementEffects(); + combatStep = BLOCKERS; + mRules = NULL; + connectRule = false; + mLoading = false; + mLayers = NULL; + mTrash = new Trash(); + mDeckManager = new DeckManager(); +} + +GamePhase GameObserver::getCurrentGamePhase() +{ + return mCurrentGamePhase; +} + +const string& GameObserver::getCurrentGamePhaseName() +{ + return phaseRing->phaseName(mCurrentGamePhase); +} + +const string& GameObserver::getNextGamePhaseName() +{ + return phaseRing->phaseName((mCurrentGamePhase + 1) % MTG_PHASE_CLEANUP); +} + +Player * GameObserver::opponent() +{ + int index = (currentPlayerId + 1) % players.size(); + return players[index]; +} + +Player * GameObserver::nextTurnsPlayer() +{ + int nextTurnsId = 0; + if(!players[currentPlayerId]->extraTurn) + nextTurnsId = (currentPlayerId + 1) % players.size(); + else + { + nextTurnsId = currentPlayerId; + } + if(players[currentPlayerId]->skippingTurn) + { + nextTurnsId = (currentPlayerId + 1) % players.size(); + } + return players[nextTurnsId]; +} + +void GameObserver::nextPlayer() +{ + turn++; + if(!players[currentPlayerId]->extraTurn) + currentPlayerId = (currentPlayerId + 1) % players.size(); + else + { + players[currentPlayerId]->extraTurn--; + } + if(players[currentPlayerId]->skippingTurn) + { + players[currentPlayerId]->skippingTurn--; + currentPlayerId = (currentPlayerId + 1) % players.size(); + } + currentPlayer = players[currentPlayerId]; + currentActionPlayer = currentPlayer; + combatStep = BLOCKERS; +} + +void GameObserver::nextGamePhase() +{ + Phase * cPhaseOld = phaseRing->getCurrentPhase(); + if (cPhaseOld->id == MTG_PHASE_COMBATDAMAGE) + if ((FIRST_STRIKE == combatStep) || (END_FIRST_STRIKE == combatStep) || (DAMAGE == combatStep)) + { + nextCombatStep(); + return; + } + + if (cPhaseOld->id == MTG_PHASE_COMBATBLOCKERS) + if (BLOCKERS == combatStep || TRIGGERS == combatStep) + { + nextCombatStep(); + return; + } + + phaseRing->forward(); + + //Go directly to end of combat if no attackers + if (cPhaseOld->id == MTG_PHASE_COMBATATTACKERS && !(currentPlayer->game->inPlay->getNextAttacker(NULL))) + { + phaseRing->forward(); + phaseRing->forward(); + } + + Phase * cPhase = phaseRing->getCurrentPhase(); + mCurrentGamePhase = cPhase->id; + + if (MTG_PHASE_COMBATDAMAGE == mCurrentGamePhase) + nextCombatStep(); + if (MTG_PHASE_COMBATEND == mCurrentGamePhase) + combatStep = BLOCKERS; + + //if (currentPlayer != cPhase->player) + // nextPlayer();//depreciated; we call this at EOT step now. unsure what the purpose of this was originally.fix for a bug? + + //init begin of turn + if (mCurrentGamePhase == MTG_PHASE_BEFORE_BEGIN) + { + cleanupPhase(); + currentPlayer->damageCount = 0; + currentPlayer->drawCounter = 0; + currentPlayer->raidcount = 0; + currentPlayer->opponent()->raidcount = 0; + currentPlayer->prowledTypes.clear(); + currentPlayer->opponent()->damageCount = 0; //added to clear odcount + currentPlayer->preventable = 0; + mLayers->actionLayer()->cleanGarbage(); //clean abilities history for this turn; + mLayers->stackLayer()->garbageCollect(); //clean stack history for this turn; + mLayers->actionLayer()->Update(0); + currentPlayer->game->library->miracle = false; + currentPlayer->opponent()->game->library->miracle = false; + for (int i = 0; i < 2; i++) + { + //Cleanup of each player's gamezones + players[i]->game->beforeBeginPhase(); + } + combatStep = BLOCKERS; + return nextGamePhase(); + } + + if (mCurrentGamePhase == MTG_PHASE_AFTER_EOT) + { + int handmodified = 0; + handmodified = currentPlayer->handsize+currentPlayer->handmodifier; + //Auto Hand cleaning, in case the player didn't do it himself + if(handmodified < 0) + handmodified = 0; + while (currentPlayer->game->hand->nb_cards > handmodified && currentPlayer->nomaxhandsize == false) + { + WEvent * e = NEW WEventCardDiscard(currentPlayer->game->hand->cards[0]); + receiveEvent(e); + currentPlayer->game->putInGraveyard(currentPlayer->game->hand->cards[0]); + } + mLayers->actionLayer()->Update(0); + currentPlayer->drawCounter = 0; + currentPlayer->prowledTypes.clear(); + currentPlayer->lifeLostThisTurn = 0; + currentPlayer->opponent()->lifeLostThisTurn = 0; + currentPlayer->doesntEmpty->remove(currentPlayer->doesntEmpty); + currentPlayer->opponent()->doesntEmpty->remove(currentPlayer->opponent()->doesntEmpty); + nextPlayer(); + return nextGamePhase(); + } + + //Phase Specific actions + switch (mCurrentGamePhase) + { + case MTG_PHASE_UNTAP: + DebugTrace("Untap Phase ------------- Turn " << turn ); + untapPhase(); + break; + case MTG_PHASE_COMBATBLOCKERS: + receiveEvent(NEW WEventAttackersChosen()); + break; + default: + break; + } +} + +int GameObserver::cancelCurrentAction() +{ + SAFE_DELETE(targetChooser); + return mLayers->actionLayer()->cancelCurrentAction(); +} + +void GameObserver::nextCombatStep() +{ + switch (combatStep) + { + case BLOCKERS: + receiveEvent(NEW WEventBlockersChosen()); + receiveEvent(NEW WEventCombatStepChange(combatStep = TRIGGERS)); + return; + + case TRIGGERS: + receiveEvent(NEW WEventCombatStepChange(combatStep = ORDER)); + return; + case ORDER: + receiveEvent(NEW WEventCombatStepChange(combatStep = FIRST_STRIKE)); + return; + case FIRST_STRIKE: + receiveEvent(NEW WEventCombatStepChange(combatStep = END_FIRST_STRIKE)); + return; + case END_FIRST_STRIKE: + receiveEvent(NEW WEventCombatStepChange(combatStep = DAMAGE)); + return; + case DAMAGE: + receiveEvent(NEW WEventCombatStepChange(combatStep = END_DAMAGE)); + return; + case END_DAMAGE: + ; // Nothing : go to next phase + } +} + +void GameObserver::userRequestNextGamePhase(bool allowInterrupt, bool log) +{ + if(log) { + stringstream stream; + stream << "next " << allowInterrupt << " " <maxtargets == 1000) + { + getCurrentTargetChooser()->done = true; + if(getCurrentTargetChooser()->source) + cardClick(getCurrentTargetChooser()->source, 0, false); + } + if (allowInterrupt && mLayers->stackLayer()->getNext(NULL, 0, NOT_RESOLVED)) + return; + if (getCurrentTargetChooser()) + return; + //if (mLayers->actionLayer()->isWaitingForAnswer()) + // return; + // Wil 12/5/10: additional check, not quite understanding why TargetChooser doesn't seem active at this point. + // If we deem that an extra cost payment needs to be made, don't allow the next game phase to proceed. + // Here's what I find weird - if the extra cost is something like a sacrifice, doesn't that imply a TargetChooser? + if (WaitForExtraPayment(NULL)) + return; + /*if (OpenedDisplay)//dont let us fly through all the phases with grave and library box still open. + { + return;//I want this here, but it locks up on opponents turn, we need to come up with a clever way to close opened + //displays, it makes no sense that you travel through 4 or 5 phases with library or grave still open. + }*/ + Phase * cPhaseOld = phaseRing->getCurrentPhase(); + if (allowInterrupt && ((cPhaseOld->id == MTG_PHASE_COMBATBLOCKERS && combatStep == ORDER) + || (cPhaseOld->id == MTG_PHASE_COMBATBLOCKERS && combatStep == TRIGGERS) + || (cPhaseOld->id == MTG_PHASE_COMBATDAMAGE) + || opponent()->isAI() + || options[Options::optionInterrupt(mCurrentGamePhase)].number + || currentPlayer->offerInterruptOnPhase - 1 == mCurrentGamePhase + )) + { + mLayers->stackLayer()->AddNextGamePhase(); + } + else + { + nextGamePhase(); + } +} + +void GameObserver::shuffleLibrary(Player* p) +{ + if(!p) + { + DebugTrace("FATAL: No Player To Shuffle"); + return; + } + logAction(p, "shufflelib"); + MTGLibrary * library = p->game->library; + if(!library) + { + DebugTrace("FATAL: Player has no zones"); + return; + } + library->shuffle(); + + for(unsigned int k = 0;k < library->placeOnTop.size();k++) + { + MTGCardInstance * toMove = library->placeOnTop[k]; + assert(toMove); + p->game->putInZone(toMove, p->game->temp, library); + } + library->placeOnTop.clear(); + +} + + +int GameObserver::forceShuffleLibraries() +{ + int result = 0; + for (int i = 0; i < 2 ; ++i) + { + if (players[i]->game->library->needShuffle) + { + shuffleLibrary(players[i]); + players[i]->game->library->needShuffle = false; + ++result; + } + } + + return result; +} + +void GameObserver::resetStartupGame() +{ + stringstream stream; + startupGameSerialized = ""; + stream << *this; + startupGameSerialized = stream.str(); +// DebugTrace("startGame\n"); +// DebugTrace(startupGameSerialized); +} + +void GameObserver::startGame(GameType gtype, Rules * rules) +{ + mGameType = gtype; + turn = 0; + mRules = rules; + if (rules) + rules->initPlayers(this); + + options.automaticStyle(players[0], players[1]); + + mLayers = NEW DuelLayers(this); + + currentPlayerId = 0; + currentPlayer = players[currentPlayerId]; + currentActionPlayer = currentPlayer; + phaseRing = NEW PhaseRing(this); + + resetStartupGame(); + + if (rules) + rules->initGame(this); + + //Preload images from hand + if (!players[0]->isAI()) + { + for (int i = 0; i < players[0]->game->hand->nb_cards; i++) + { + WResourceManager::Instance()->RetrieveCard(players[0]->game->hand->cards[i], CACHE_THUMB); + WResourceManager::Instance()->RetrieveCard(players[0]->game->hand->cards[i]); + } + } + + startedAt = time(0); + + //Difficult mode special stuff + if (!players[0]->isAI() && players[1]->isAI()) + { + int difficulty = options[Options::DIFFICULTY].number; + if (options[Options::DIFFICULTY_MODE_UNLOCKED].number && difficulty) + { + Player * p = players[1]; + for (int level = 0; level < difficulty; level++) + { + MTGCardInstance * card = NULL; + MTGGameZone * z = p->game->library; + for (int j = 0; j < z->nb_cards; j++) + { + MTGCardInstance * _card = z->cards[j]; + if (_card->isLand()) + { + card = _card; + j = z->nb_cards; + } + } + if (card) + { + MTGCardInstance * copy = p->game->putInZone(card, p->game->library, p->game->stack); + Spell * spell = NEW Spell(this, copy); + spell->resolve(); + delete spell; + } + } + } + } + + switch(gtype) { + case GAME_TYPE_MOMIR: + { + addObserver(NEW MTGMomirRule(this, -1, MTGCollection())); + break; + } + case GAME_TYPE_STONEHEWER: + { + addObserver(NEW MTGStoneHewerRule(this, -1,MTGCollection())); + break; + } + case GAME_TYPE_HERMIT: + { + addObserver(NEW MTGHermitRule(this, -1)); + break; + } + default: + break; + } +} + +void GameObserver::addObserver(MTGAbility * observer) +{ + mLayers->actionLayer()->Add(observer); +} + +//Returns true if the Ability was correctly removed from the game, false otherwise +//Main (valid) reason of returning false is an attempt at removing an Ability that has already been removed +bool GameObserver::removeObserver(ActionElement * observer) +{ + if (!observer) + return false; + return mLayers->actionLayer()->moveToGarbage(observer); + +} + +bool GameObserver::operator==(const GameObserver& aGame) +{ + int error = 0; + + if (aGame.mCurrentGamePhase != mCurrentGamePhase) + { + error++; + } + for (int i = 0; i < 2; i++) + { + Player * p = aGame.players[i]; + + if (p->life != players[i]->life) + { + error++; + } + if (p->poisonCount != players[i]->poisonCount) + { + error++; + } + if (!p->getManaPool()->canAfford(players[i]->getManaPool())) + { + error++; + } + if (!players[i]->getManaPool()->canAfford(p->getManaPool())) + { + error++; + } + MTGGameZone * aZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay, p->game->exile }; + MTGGameZone * thisZones[] = { players[i]->game->graveyard, + players[i]->game->library, + players[i]->game->hand, + players[i]->game->inPlay, + players[i]->game->exile }; + for (int j = 0; j < 5; j++) + { + MTGGameZone * zone = aZones[j]; + if (zone->nb_cards != thisZones[j]->nb_cards) + { + error++; + } + for (size_t k = 0; k < (size_t)thisZones[j]->nb_cards; k++) + { + MTGCardInstance* cardToCheck = (kcards.size())?thisZones[j]->cards[k]:0; + MTGCardInstance* card = (kcards.size())?aZones[j]->cards[k]:0; + if(!card || !cardToCheck || cardToCheck->getId() != card->getId()) + { + error++; + } + } + } + } + + return (error == 0); +} + +void GameObserver::dumpAssert(bool val) +{ + if(!val) + { + cerr << *this << endl; + assert(0); + } +} + + +void GameObserver::Update(float dt) +{ + Player * player = currentPlayer; + if (MTG_PHASE_COMBATBLOCKERS == mCurrentGamePhase && BLOCKERS == combatStep) + { + player = player->opponent(); + } + if(getCurrentTargetChooser() && getCurrentTargetChooser()->Owner && player != getCurrentTargetChooser()->Owner) + { + if(getCurrentTargetChooser()->Owner != currentlyActing()) + { + player = getCurrentTargetChooser()->Owner; + isInterrupting = player; + } + } + currentActionPlayer = player; + if (isInterrupting) + player = isInterrupting; + if(mLayers) + { + mLayers->Update(dt, player); + while (mLayers->actionLayer()->stuffHappened) + { + mLayers->actionLayer()->Update(0); + } + gameStateBasedEffects(); + } + oldGamePhase = mCurrentGamePhase; +} + +//applies damage to creatures after updates +//Players life test +//Handles game state based effects +void GameObserver::gameStateBasedEffects() +{ + if(getCurrentTargetChooser() && int(getCurrentTargetChooser()->getNbTargets()) == getCurrentTargetChooser()->maxtargets) + getCurrentTargetChooser()->done = true; + ///////////////////////////////////// + for (int d = 0; d < 2; d++) + { + ////check snow count + if (players[d]->snowManaC > players[d]->getManaPool()->getCost(0) + players[d]->getManaPool()->getCost(6)) + players[d]->snowManaC = players[d]->getManaPool()->getCost(0) + players[d]->getManaPool()->getCost(6); + if (players[d]->snowManaC < 0) + players[d]->snowManaC = 0; + if (players[d]->snowManaG > players[d]->getManaPool()->getCost(1)) + players[d]->snowManaG = players[d]->getManaPool()->getCost(1); + if (players[d]->snowManaG < 0) + players[d]->snowManaG = 0; + if (players[d]->snowManaU > players[d]->getManaPool()->getCost(2)) + players[d]->snowManaU = players[d]->getManaPool()->getCost(2); + if (players[d]->snowManaU < 0) + players[d]->snowManaU = 0; + if (players[d]->snowManaR > players[d]->getManaPool()->getCost(3)) + players[d]->snowManaR = players[d]->getManaPool()->getCost(3); + if (players[d]->snowManaR < 0) + players[d]->snowManaR = 0; + if (players[d]->snowManaB > players[d]->getManaPool()->getCost(4)) + players[d]->snowManaB = players[d]->getManaPool()->getCost(4); + if (players[d]->snowManaB < 0) + players[d]->snowManaB = 0; + if (players[d]->snowManaW > players[d]->getManaPool()->getCost(5)) + players[d]->snowManaW = players[d]->getManaPool()->getCost(5); + if (players[d]->snowManaW < 0) + players[d]->snowManaW = 0; + + MTGGameZone * dzones[] = { players[d]->game->inPlay, players[d]->game->graveyard, players[d]->game->hand, players[d]->game->library, players[d]->game->exile }; + for (int k = 0; k < 5; k++) + { + MTGGameZone * zone = dzones[k]; + if (mLayers->stackLayer()->count(0, NOT_RESOLVED) == 0) + { + for (int c = zone->nb_cards - 1; c >= 0; c--) + { + zone->cards[c]->cardistargetted = 0; + zone->cards[c]->cardistargetter = 0; + } + } + + ///while checking all these zones, lets also strip devoid cards of thier colors + for (int w = 0; w < zone->nb_cards; w++) + { + MTGCardInstance * card = zone->cards[w]; + for (int i = Constants::MTG_COLOR_GREEN; i <= Constants::MTG_COLOR_WHITE; ++i) + { + if (card->has(Constants::DEVOID)) + { + card->removeColor(i); + } + } + } + + + }//check for losers if its GAMEOVER clear the stack to allow gamestateeffects to continue + players[d]->DeadLifeState(); + } + //////////////////////////////////// + + if (mLayers->stackLayer()->count(0, NOT_RESOLVED) != 0) + return; + if (mLayers->actionLayer()->menuObject) + return; + if (getCurrentTargetChooser() || mLayers->actionLayer()->isWaitingForAnswer()) + return; + //////////////////////// + //---apply damage-----// + //after combat effects// + //////////////////////// + for (int i = 0; i < 2; i++) + { + MTGGameZone * zone = players[i]->game->inPlay; + players[i]->curses.clear(); + for (int j = zone->nb_cards - 1; j >= 0; j--) + { + MTGCardInstance * card = zone->cards[j]; + card->LKIpower = card->power; + card->LKItoughness = card->toughness; + card->LKIbasicAbilities = card->basicAbilities; + card->afterDamage(); + 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; + } + ///clear imprints + if(isInPlay(card) && card->imprintedCards.size()) + { + for(size_t ic = 0; ic < card->imprintedCards.size(); ic++) + { + if(!isInExile(card->imprintedCards[ic])) + { + card->imprintG = 0; + card->imprintU = 0; + card->imprintR = 0; + card->imprintB = 0; + card->imprintW = 0; + card->currentimprintName = ""; + card->imprintedNames.clear(); + card->imprintedCards.erase(card->imprintedCards.begin() + ic); + } + } + } + card->bypassTC = false; //turn off bypass + //////////////////////////////////////////////////// + //Unattach Equipments that dont have valid targets// + //////////////////////////////////////////////////// + if ((card->target) && card->hasType(Subtypes::TYPE_EQUIPMENT)) + { + if(card->target && isInPlay(card->target) && (card->target)->protectedAgainst(card))//protection from quality + { + for (size_t i = 1; i < mLayers->actionLayer()->mObjects.size(); i++) + { + MTGAbility * a = ((MTGAbility *) mLayers->actionLayer()->mObjects[i]); + AEquip * eq = dynamic_cast (a); + if (eq && eq->source == card) + { + ((AEquip*)a)->unequip(); + } + } + } + } + /////////////////////////////////////////////////////// + //Remove auras that don't have a valid target anymore// + /////////////////////////////////////////////////////// + if (card->target && !isInPlay(card->target) && card->isBestowed && card->hasType("aura")) + { + card->removeType("aura"); + card->addType("creature"); + card->target = NULL; + card->isBestowed = false; + } + + if ((card->target||card->playerTarget) && !card->hasType(Subtypes::TYPE_EQUIPMENT)) + { + if(card->target && !isInPlay(card->target)) + players[i]->game->putInGraveyard(card); + if(card->target && isInPlay(card->target)) + {//what exactly does this section do? + if(card->spellTargetType.find("creature") != string::npos && !card->target->hasType("creature")) + players[i]->game->putInGraveyard(card); + if(card->spellTargetType.find("artifact") != string::npos && !card->target->hasType("artifact")) + players[i]->game->putInGraveyard(card); + if(card->spellTargetType.find("enchantment") != string::npos && !card->target->hasType("enchantment")) + players[i]->game->putInGraveyard(card); + if(card->spellTargetType.find("land") != string::npos && !card->target->hasType("land")) + players[i]->game->putInGraveyard(card); + if(card->spellTargetType.find("planeswalker") != string::npos && !card->target->hasType("planeswalker")) + players[i]->game->putInGraveyard(card); + } + if(card->target && isInPlay(card->target) && (card->target)->protectedAgainst(card) && !card->has(Constants::AURAWARD))//protection from quality except aura cards like flickering ward + players[i]->game->putInGraveyard(card); + } + card->enchanted = false; + if (card->target && isInPlay(card->target) && !card->hasType(Subtypes::TYPE_EQUIPMENT) && card->hasSubtype(Subtypes::TYPE_AURA)) + { + card->target->enchanted = true; + } + if (card->playerTarget && card->hasType("curse")) + { + card->playerTarget->curses.push_back(card); + } + /////////////////////////// + //reset extracost shadows// + /////////////////////////// + card->isExtraCostTarget = false; + if(mExtraPayment != NULL) + { + for(unsigned int ec = 0;ec < mExtraPayment->costs.size();ec++) + { + if( mExtraPayment->costs[ec]->target) + mExtraPayment->costs[ec]->target->isExtraCostTarget = true; + } + } + ////////////////////// + //reset morph hiding// + ////////////////////// + if((card->previous && card->previous->morphed && !card->turningOver) || (card->morphed && !card->turningOver)) + { + card->morphed = true; + card->isMorphed = true; + } + else + { + card->isMorphed = false; + card->morphed = false; + } + ////////////////////////// + //handles phasing events// + ////////////////////////// + if(card->has(Constants::PHASING)&& mCurrentGamePhase == MTG_PHASE_UNTAP && currentPlayer == card->controller() && card->phasedTurn != turn && !card->isPhased) + { + card->isPhased = true; + card->phasedTurn = turn; + if(card->view) + card->view->alpha = 50; + card->initAttackersDefensers(); + } + else if((card->has(Constants::PHASING) || card->isPhased)&& mCurrentGamePhase == MTG_PHASE_UNTAP && currentPlayer == card->controller() && card->phasedTurn != turn) + { + card->isPhased = false; + card->phasedTurn = turn; + if(card->view) + card->view->alpha = 255; + } + if (card->target && isInPlay(card->target) && (card->hasSubtype(Subtypes::TYPE_EQUIPMENT) || card->hasSubtype(Subtypes::TYPE_AURA))) + { + card->isPhased = card->target->isPhased; + card->phasedTurn = card->target->phasedTurn; + if(card->view && card->target->view) + card->view->alpha = card->target->view->alpha; + } + ////////////////////////// + //forceDestroy over ride// + ////////////////////////// + if(card->isInPlay(this)) + { + card->graveEffects = false; + card->exileEffects = false; + } + + if(card->childrenCards.size()) + { + MTGCardInstance * check = NULL; + MTGCardInstance * matched = NULL; + sort(card->childrenCards.begin(),card->childrenCards.end()); + for(size_t wC = 0; wC < card->childrenCards.size();wC++) + { + check = card->childrenCards[wC]; + for(size_t wCC = 0; wCC < card->childrenCards.size();wCC++) + { + if(check->isInPlay(this)) + { + if(check->getName() == card->childrenCards[wCC]->getName() && check != card->childrenCards[wCC]) + { + card->isDualWielding = true; + matched = card->childrenCards[wCC]; + } + } + } + if(matched) + wC = card->childrenCards.size(); + } + if(!matched) + card->isDualWielding = false; + } + } + } + //------------------------------------- + + for (int i = 0; i < 2; i++) + { + /////////////////////////////////////////////////////////// + //life checks/poison checks also checks cant win or lose.// + /////////////////////////////////////////////////////////// + players[i]->DeadLifeState(true);//refactored + } + ////////////////////////////////////////////////////// + //-------------card based states effects------------// + ////////////////////////////////////////////////////// + //ie:cantcast; extra land; extra turn;no max hand;--// + ////////////////////////////////////////////////////// + + for (int i = 0; i < 2; i++) + { + //checks if a player has a card which has the stated ability in play. + Player * p = players[i]; + MTGGameZone * z = players[i]->game->inPlay; + int nbcards = z->nb_cards; + //------------------------------ + p->nomaxhandsize = (z->hasAbility(Constants::NOMAXHAND)); + + ///////////////////////////////////////////////// + //handle end of turn effects while we're at it.// + ///////////////////////////////////////////////// + if (mCurrentGamePhase == MTG_PHASE_ENDOFTURN+1) + { + for (int j = 0; j < nbcards; ++j) + { + MTGCardInstance * c = z->cards[j]; + + if(!c)break; + while (c->flanked) + { + ///////////////////////////////// + //undoes the flanking on a card// + ///////////////////////////////// + c->power += 1; + c->addToToughness(1); + c->flanked -= 1; + } + c->fresh = 0; + if(c->wasDealtDamage && c->isInPlay(this)) + c->wasDealtDamage = false; + c->damageToController = false; + c->damageToOpponent = false; + c->damageToCreature = false; + c->isAttacking = NULL; + } + for (int t = 0; t < nbcards; t++) + { + MTGCardInstance * c = z->cards[t]; + + if(!c->isPhased) + { + if (c->has(Constants::TREASON)) + { + MTGCardInstance * beforeCard = c; + p->game->putInGraveyard(c); + WEvent * e = NEW WEventCardSacrifice(beforeCard,c); + receiveEvent(e); + } + if (c->has(Constants::UNEARTH)) + { + p->game->putInExile(c); + + } + } + if(c->modifiedbAbi > 0) + { + c->modifiedbAbi = 0; + c->basicAbilities = c->origbasicAbilities; + } + if(nbcards > z->nb_cards) + { + t = 0; + nbcards = z->nb_cards; + } + } + + MTGGameZone * f = p->game->graveyard; + for (int k = 0; k < f->nb_cards; k++) + { + MTGCardInstance * card = f->cards[k]; + card->fresh = 0; + } + } + if (z->nb_cards == 0) + { + p->nomaxhandsize = false; + } + ////////////////////////// + // Check auras on a card// + ////////////////////////// + enchantmentStatus(); + ///////////////////////////// + // Check affinity on a card// + // plus modify costs // + ///////////////////////////// + Affinity(); + ///////////////////////////////////// + // Check colored statuses on cards // + ///////////////////////////////////// + for(int w = 0;w < z->nb_cards;w++) + { + int colored = 0; + for (int i = Constants::MTG_COLOR_GREEN; i <= Constants::MTG_COLOR_WHITE; ++i) + { + if (z->cards[w]->hasColor(i)) + ++colored; + } + z->cards[w]->isMultiColored = (colored > 1) ? 1 : 0; + } + } + /////////////////////////////////// + //phase based state effects------// + /////////////////////////////////// + if (combatStep == TRIGGERS) + { + if (!mLayers->stackLayer()->getNext(NULL, 0, NOT_RESOLVED) && !targetChooser + && !mLayers->actionLayer()->isWaitingForAnswer()) + mLayers->stackLayer()->AddNextCombatStep(); + } + + //Auto skip Phases + int skipLevel = (currentPlayer->playMode == Player::MODE_TEST_SUITE || mLoading) ? Constants::ASKIP_NONE + : options[Options::ASPHASES].number; + + if (skipLevel == Constants::ASKIP_SAFE || skipLevel == Constants::ASKIP_FULL) + { + if ((opponent()->isAI() && !(isInterrupting)) && ((mCurrentGamePhase == MTG_PHASE_UNTAP) + || (mCurrentGamePhase == MTG_PHASE_DRAW) || (mCurrentGamePhase == MTG_PHASE_COMBATBEGIN) + || ((mCurrentGamePhase == MTG_PHASE_COMBATATTACKERS) && (currentPlayer->noPossibleAttackers())) + || mCurrentGamePhase == MTG_PHASE_COMBATEND || mCurrentGamePhase == MTG_PHASE_ENDOFTURN + || ((mCurrentGamePhase == MTG_PHASE_CLEANUP) && (currentPlayer->game->hand->nb_cards < 8)))) + userRequestNextGamePhase(); + } + if (skipLevel == Constants::ASKIP_FULL) + { + if ((opponent()->isAI() && !(isInterrupting)) && (mCurrentGamePhase == MTG_PHASE_UPKEEP + || mCurrentGamePhase == MTG_PHASE_COMBATDAMAGE)) + userRequestNextGamePhase(); + } +} + +void GameObserver::enchantmentStatus() +{ + for (int i = 0; i < 2; i++) + { + MTGGameZone * zone = players[i]->game->inPlay; + for (int k = zone->nb_cards - 1; k >= 0; k--) + { + MTGCardInstance * card = zone->cards[k]; + if (card && !card->hasType(Subtypes::TYPE_EQUIPMENT) && !card->hasSubtype(Subtypes::TYPE_AURA)) + { + card->enchanted = false; + card->auras = 0; + } + } + for (int j = zone->nb_cards - 1; j >= 0; j--) + { + MTGCardInstance * card = zone->cards[j]; + if (card->target && isInPlay(card->target) && !card->hasType(Subtypes::TYPE_EQUIPMENT) && card->hasSubtype(Subtypes::TYPE_AURA)) + { + card->target->enchanted = true; + card->target->auras += 1; + } + } + } +} + +void GameObserver::Affinity() +{ + for (int dd = 0; dd < 2; dd++) + { + MTGGameZone * dzones[] = { players[dd]->game->graveyard, players[dd]->game->hand, players[dd]->game->library, players[dd]->game->exile }; + for (int kk = 0; kk < 4; kk++) + { + MTGGameZone * zone = dzones[kk]; + for (int cc = zone->nb_cards - 1; cc >= 0; cc--) + {//start + MTGCardInstance * card = zone->cards[cc]; + if (!card) + continue; + + bool NewAffinityFound = false; + for (unsigned int na = 0; na < card->cardsAbilities.size(); na++) + { + if (!card->cardsAbilities[na]) + break; + ANewAffinity * newAff = dynamic_cast(card->cardsAbilities[na]); + if (newAff) + { + NewAffinityFound = true; + } + } + bool DoReduceIncrease = false; + if (card->has(Constants::AFFINITYARTIFACTS) || + card->has(Constants::AFFINITYFOREST) || + card->has(Constants::AFFINITYGREENCREATURES) || + card->has(Constants::AFFINITYISLAND) || + card->has(Constants::AFFINITYMOUNTAIN) || + card->has(Constants::AFFINITYPLAINS) || + card->has(Constants::AFFINITYSWAMP) || + card->has(Constants::TRINISPHERE) || + card->getIncreasedManaCost()->getConvertedCost() || + card->getReducedManaCost()->getConvertedCost() || + NewAffinityFound) + DoReduceIncrease = true; + if (!DoReduceIncrease) + continue; + //above we check if there are even any cards that effect cards manacost + //if there are none, leave this function. manacost->copy( is a very expensive funtion + //1mb a sec to run at all time even when no known reducers or increasers are in play. + //memory snapshot shots pointed to this as such a heavy load that games with many cards inplay + //would slow to a crawl. + //only do any of the following if a card with the stated ability is in your hand. + int color = 0; + string type = ""; + + ManaCost * original = NEW ManaCost(); + original->copy(card->model->data->getManaCost()); + if(card->getIncreasedManaCost()->getConvertedCost()||card->getReducedManaCost()->getConvertedCost()) + {//start1 + if(card->getIncreasedManaCost()->getConvertedCost()) + original->add(card->getIncreasedManaCost()); + if(card->getReducedManaCost()->getConvertedCost()) + original->remove(card->getReducedManaCost()); + if(card->getManaCost()) + card->getManaCost()->copy(original); + if(card->getManaCost()->extraCosts) + { + for(unsigned int i = 0; i < card->getManaCost()->extraCosts->costs.size();i++) + { + card->getManaCost()->extraCosts->costs[i]->setSource(card); + } + } + }//end1 + int reducem = 0; + bool resetCost = false; + for(unsigned int na = 0; na < card->cardsAbilities.size();na++) + {//start2 + if (!card->cardsAbilities[na]) + break; + ANewAffinity * newAff = dynamic_cast(card->cardsAbilities[na]); + if(newAff) + { + if(!resetCost) + { + resetCost = true; + card->getManaCost()->copy(original); + if(card->getManaCost()->extraCosts) + { + for(unsigned int i = 0; i < card->getManaCost()->extraCosts->costs.size();i++) + { + card->getManaCost()->extraCosts->costs[i]->setSource(card); + } + } + } + TargetChooserFactory tf(this); + TargetChooser * tcn = tf.createTargetChooser(newAff->tcString,card,NULL); + + for (int w = 0; w < 2; ++w) + { + Player *p = this->players[w]; + MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->stack, p->game->exile }; + for (int k = 0; k < 6; k++) + { + MTGGameZone * z = zones[k]; + if (tcn->targetsZone(z)) + { + reducem += z->countByCanTarget(tcn); + } + } + } + SAFE_DELETE(tcn); + ManaCost * removingCost = ManaCost::parseManaCost(newAff->manaString); + for(int j = 0; j < reducem; j++) + card->getManaCost()->remove(removingCost); + SAFE_DELETE(removingCost); + } + }//end2 + if(card->has(Constants::AFFINITYARTIFACTS)|| + card->has(Constants::AFFINITYFOREST)|| + card->has(Constants::AFFINITYGREENCREATURES)|| + card->has(Constants::AFFINITYISLAND)|| + card->has(Constants::AFFINITYMOUNTAIN)|| + card->has(Constants::AFFINITYPLAINS)|| + card->has(Constants::AFFINITYSWAMP)) + {//start3 + if (card->has(Constants::AFFINITYARTIFACTS)) + { + type = "artifact"; + } + else if (card->has(Constants::AFFINITYSWAMP)) + { + type = "swamp"; + } + else if (card->has(Constants::AFFINITYMOUNTAIN)) + { + type = "mountain"; + } + else if (card->has(Constants::AFFINITYPLAINS)) + { + type = "plains"; + } + else if (card->has(Constants::AFFINITYISLAND)) + { + type = "island"; + } + else if (card->has(Constants::AFFINITYFOREST)) + { + type = "forest"; + } + else if (card->has(Constants::AFFINITYGREENCREATURES)) + { + color = 1; + type = "creature"; + } + card->getManaCost()->copy(original); + if(card->getManaCost()->extraCosts) + { + for(unsigned int i = 0; i < card->getManaCost()->extraCosts->costs.size();i++) + { + card->getManaCost()->extraCosts->costs[i]->setSource(card); + } + } + int reduce = 0; + if(card->has(Constants::AFFINITYGREENCREATURES)) + { + TargetChooserFactory tf(this); + TargetChooser * tc = tf.createTargetChooser("creature[green]",NULL); + reduce = card->controller()->game->battlefield->countByCanTarget(tc); + SAFE_DELETE(tc); + } + else + { + reduce = card->controller()->game->battlefield->countByType(type); + } + for(int i = 0; i < reduce;i++) + { + if(card->getManaCost()->getCost(color) > 0) + card->getManaCost()->remove(color,1); + } + }//end3 + //trinisphere... now how to implement kicker recomputation + + if(card->has(Constants::TRINISPHERE)) + { + for(int jj = card->getManaCost()->getConvertedCost(); jj < 3; jj++) + { + card->getManaCost()->add(Constants::MTG_COLOR_ARTIFACT, 1); + card->countTrini++; + } + } + else + { + if(card->countTrini) + { + card->getManaCost()->remove(Constants::MTG_COLOR_ARTIFACT, card->countTrini); + card->countTrini=0; + } + } + + SAFE_DELETE(original); + }//end + } + } +} + +void GameObserver::Render() +{ + if(mLayers) + mLayers->Render(); + if (targetChooser || (mLayers && mLayers->actionLayer()->isWaitingForAnswer())) + JRenderer::GetInstance()->DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, ARGB(255,255,0,0)); + if (mExtraPayment) + mExtraPayment->Render(); + + for (size_t i = 0; i < players.size(); ++i) + { + players[i]->Render(); + } +} + +void GameObserver::ButtonPressed(PlayGuiObject * target) +{ + DebugTrace("GAMEOBSERVER Click"); + if (CardView* cardview = dynamic_cast(target)) + { + MTGCardInstance * card = cardview->getCard(); + cardClick(card, card); + } + else if (GuiLibrary* library = dynamic_cast(target)) + { + if (library->showCards) + { + library->toggleDisplay(); + forceShuffleLibraries(); + } + else + { + TargetChooser * _tc = this->getCurrentTargetChooser(); + if (_tc && _tc->targetsZone(library->zone)) + { + library->toggleDisplay(); + library->zone->needShuffle = true; + } + } + } + else if (GuiGraveyard* graveyard = dynamic_cast(target)) + graveyard->toggleDisplay(); + else if (GuiExile* exile = dynamic_cast(target)) + exile->toggleDisplay(); + //opponenthand + else if (GuiOpponentHand* opponentHand = dynamic_cast(target)) + if (opponentHand->showCards) + { + opponentHand->toggleDisplay(); + } + else + { + TargetChooser * _tc = this->getCurrentTargetChooser(); + if (_tc && _tc->targetsZone(opponentHand->zone)) + { + opponentHand->toggleDisplay(); + } + } + //end opponenthand + else if (GuiAvatar* avatar = dynamic_cast(target)) + { + cardClick(NULL, avatar->player); + } + else if (dynamic_cast(target)) + { + mLayers->getPhaseHandler()->NextGamePhase(); + } +} + +void GameObserver::stackObjectClicked(Interruptible * action) +{ + stringstream stream; + stream << "stack[" << mLayers->stackLayer()->getIndexOf(action) << "]"; + logAction(currentlyActing(), stream.str()); + + if (targetChooser != NULL) + { + int result = targetChooser->toggleTarget(action); + if (result == TARGET_OK_FULL) + { + cardClick(cardWaitingForTargets, 0, false); + } + else + { + return; + } + } + else + { + int reaction = mLayers->actionLayer()->isReactingToTargetClick(action); + if (reaction == -1) + mLayers->actionLayer()->reactToTargetClick(action); + } +} + +bool GameObserver::WaitForExtraPayment(MTGCardInstance * card) +{ + bool result = false; + if (mExtraPayment) + { + if (card) + { + mExtraPayment->tryToSetPayment(card); + } + if (mExtraPayment->isPaymentSet()) + { + mLayers->actionLayer()->reactToClick(mExtraPayment->action, mExtraPayment->source); + mExtraPayment = NULL; + } + result = true; + } + + return result; +} + +int GameObserver::cardClick(MTGCardInstance * card, MTGAbility *ability) +{ + MTGGameZone* zone = card->currentZone; + size_t index = 0; + if(zone) + index = zone->getIndex(card); + int choice; + bool logChoice = mLayers->actionLayer()->getMenuIdFromCardAbility(card, ability, choice); + int result = ability->reactToClick(card); + logAction(card, zone, index, result); + + if(logChoice) { + stringstream stream; + stream << "choice " << choice; + logAction(currentActionPlayer, stream.str()); + } + + return result; +} + +int GameObserver::cardClick(MTGCardInstance * card, int abilityType) +{ + int result = 0; + MTGAbility * a = mLayers->actionLayer()->getAbility(abilityType); + + if(a) + { + result = cardClick(card, a); + } + + return result; +} + +int GameObserver::cardClickLog(bool log, Player* clickedPlayer, MTGGameZone* zone, MTGCardInstance*backup, size_t index, int toReturn) +{ + if(log) + { + if (clickedPlayer) { + this->logAction(clickedPlayer); + } else if(zone) { + this->logAction(backup, zone, index, toReturn); + } + } + return toReturn; +} + +int GameObserver::cardClick(MTGCardInstance * card, Targetable * object, bool log) +{ + Player * clickedPlayer = NULL; + int toReturn = 0; + int handmodified = 0; + MTGGameZone* zone = NULL; + size_t index = 0; + MTGCardInstance* backup = NULL; + + if (!card) { + clickedPlayer = ((Player *) object); + } else { + backup = card; + zone = card->currentZone; + if(zone) + { + index = zone->getIndex(card); + } + } + + do { + if (targetChooser) + { + int result; + if (card) + { + if (card == cardWaitingForTargets) + { + int _result = targetChooser->ForceTargetListReady(); + if(targetChooser->targetMin && int(targetChooser->getNbTargets()) < targetChooser->maxtargets) + _result = 0; + + if (_result) + { + result = TARGET_OK_FULL; + } + else + { + result = targetChooser->targetsReadyCheck(); + } + } + else + { + result = targetChooser->toggleTarget(card); + WEvent * e = NEW WEventTarget(card,cardWaitingForTargets); + receiveEvent(e); + } + } + else + { + result = targetChooser->toggleTarget(clickedPlayer); + if(card) + card->playerTarget = clickedPlayer; + else + targetChooser->source->playerTarget = clickedPlayer; + } + if (result == TARGET_OK_FULL) + card = cardWaitingForTargets; + else { + toReturn = 1; + break; + } + } + ExtraManaCost * costType = NULL; + if( mExtraPayment && mExtraPayment->costs.size()) + costType = dynamic_cast(mExtraPayment->costs[0]); + + if (WaitForExtraPayment(card) && !costType) + { + toReturn = 1; + break; + } + + int reaction = 0; + + if (ORDER == combatStep) + { + //TODO it is possible at this point that card is NULL. if so, what do we return since card->defenser would result in a crash? + card->defenser->raiseBlockerRankOrder(card); + toReturn = 1; + break; + } + + if (card) + { + //card played as normal, alternative cost, buyback, flashback, retrace. + + //the variable "paymenttype = int" only serves one purpose, to tell this bug fix what menu item you clicked on... + // all alternative cost or play methods suffered from the fix because if the card contained "target=" + // it would automatically force the play method to putinplayrule...even charge you the original mana cost. + + /* Fix for Issue http://code.google.com/p/wagic/issues/detail?id=270 + put into play is hopefully the only ability causing that kind of trouble + If the same kind of issue occurs with other abilities, let's think of a cleaner solution + */ + if (targetChooser) + { + MTGAbility * a = mLayers->actionLayer()->getAbility(card->paymenttype); + toReturn = a->reactToClick(card); + return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); + } + + reaction = mLayers->actionLayer()->isReactingToClick(card); + if (reaction == -1) { + toReturn = mLayers->actionLayer()->reactToClick(card); + return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); + } + } + else + {//this handles abilities on a menu...not just when card is being played + reaction = mLayers->actionLayer()->isReactingToTargetClick(object); + if (reaction == -1) { + toReturn = mLayers->actionLayer()->reactToTargetClick(object); + return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); + } + } + + if (!card) { + toReturn = 0; + return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); + } + + //Current player's hand + handmodified = currentPlayer->handsize+currentPlayer->handmodifier; + if(handmodified < 0) + handmodified = 0; + if (currentPlayer->game->hand->hasCard(card) && mCurrentGamePhase == MTG_PHASE_CLEANUP + && currentPlayer->game->hand->nb_cards > handmodified && currentPlayer->nomaxhandsize == false) + { + WEvent * e = NEW WEventCardDiscard(currentPlayer->game->hand->cards[0]); + receiveEvent(e); + currentPlayer->game->putInGraveyard(card); + } + else if (reaction) + { + if (reaction == 1) + { + toReturn = mLayers->actionLayer()->reactToClick(card); + return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); + } + else + { + mLayers->actionLayer()->setMenuObject(object); + toReturn = 1; + return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); + } + } + else if (card->isTapped() && card->controller() == currentPlayer) + { + toReturn = untap(card); + return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); + } + } while(0); + + + return cardClickLog(log, clickedPlayer, zone, backup, index, toReturn); +} + +int GameObserver::untap(MTGCardInstance * card) +{ + if (!card->isUntapping()) + { + return 0; + } + if (card->has(Constants::DOESNOTUNTAP)) + return 0; + if (card->frozen > 0) + return 0; + card->attemptUntap(); + return 1; +} + +TargetChooser * GameObserver::getCurrentTargetChooser() +{ + if(mLayers) + { + TargetChooser * _tc = mLayers->actionLayer()->getCurrentTargetChooser(); + if (_tc) + return _tc; + } + return targetChooser; +} + +/* Returns true if the card is in one of the player's play zone */ +int GameObserver::isInPlay(MTGCardInstance * card) +{ + for (int i = 0; i < 2; i++) + { + if (players[i]->game->isInPlay(card)) + return 1; + } + return 0; +} +int GameObserver::isInGrave(MTGCardInstance * card) +{ + + for (int i = 0; i < 2; i++) + { + MTGGameZone * graveyard = players[i]->game->graveyard; + if (players[i]->game->isInZone(card,graveyard)) + return 1; + } + return 0; +} +int GameObserver::isInExile(MTGCardInstance * card) +{ + + for (int i = 0; i < 2; i++) + { + MTGGameZone * exile = players[i]->game->exile; + if (players[i]->game->isInZone(card,exile)) + return 1; + } + return 0; +} + +void GameObserver::cleanupPhase() +{ + currentPlayer->cleanupPhase(); + opponent()->cleanupPhase(); +} + +void GameObserver::untapPhase() +{ + currentPlayer->inPlay()->untapAll(); +} + +int GameObserver::receiveEvent(WEvent * e) +{ + if (!e) + return 0; + eventsQueue.push(e); + if (eventsQueue.size() > 1) + return -1; //resolving events can generate more events + int result = 0; + while (eventsQueue.size()) + { + WEvent * ev = eventsQueue.front(); + result += mLayers->receiveEvent(ev); + for (int i = 0; i < 2; ++i) + { + result += players[i]->receiveEvent(ev); + } + SAFE_DELETE(ev); + eventsQueue.pop(); + } + return result; +} + +Player * GameObserver::currentlyActing() +{ + if (isInterrupting) + return isInterrupting; + return currentActionPlayer; +} + +//TODO CORRECT THIS MESS +int GameObserver::targetListIsSet(MTGCardInstance * card) +{ + if (targetChooser == NULL) + { + TargetChooserFactory tcf(this); + targetChooser = tcf.createTargetChooser(card); + if (targetChooser == NULL) + { + return 1; + } + } + if(targetChooser && targetChooser->validTargetsExist()) + { + cardWaitingForTargets = card; + return (targetChooser->targetListSet()); + } + else + SAFE_DELETE(targetChooser); + return 0; + +} + +ostream& operator<<(ostream& out, const GameObserver& g) +{ + if(g.startupGameSerialized == "") + { + out << "[init]" << endl; + out << "player=" << g.currentPlayerId + 1 << endl; + if(g.mCurrentGamePhase != MTG_PHASE_INVALID) + out << "phase=" << g.phaseRing->phaseName(g.mCurrentGamePhase) << endl; + out << "[player1]" << endl; + out << *(g.players[0]) << endl; + out << "[player2]" << endl; + out << *(g.players[1]) << endl; + return out; + } + else + { + out << "seed:"; + out << g.mSeed; + out << endl; + out << "rvalues:"; + g.randomGenerator.saveUsedRandValues(out); + out << endl; + out << g.startupGameSerialized; + } + + out << "[do]" << endl; + list::const_iterator it; + + for(it = (g.actionsList.begin()); it != (g.actionsList.end()); it++) + { + out << (*it) << endl; + } + + out << "[end]" << endl; + return out; +} + +bool GameObserver::parseLine(const string& s) +{ + size_t limiter = s.find("="); + if (limiter == string::npos) limiter = s.find(":"); + string areaS; + if (limiter != string::npos) + { + areaS = s.substr(0, limiter); + if (areaS.compare("player") == 0) + { + currentPlayerId = atoi(s.substr(limiter + 1).c_str()) - 1; + return true; + } + else if (areaS.compare("phase") == 0) + { + mCurrentGamePhase = PhaseRing::phaseStrToInt(s.substr(limiter + 1).c_str()); + return true; + } + } + return false; +} + +bool GameObserver::load(const string& ss, bool undo, int controlledPlayerIndex +#ifdef TESTSUITE + , TestSuiteGame* testgame +#endif + ) +{ + bool currentPlayerSet = false; + int state = -1; + string s; + stringstream stream(ss); + + DebugTrace("Loading " + ss); + randomGenerator.loadRandValues(""); + + cleanup(); + + while (std::getline(stream, s)) + { + if (!s.size()) continue; + if (s[s.size() - 1] == '\r') s.erase(s.size() - 1); //Handle DOS files + if (!s.size()) continue; + if (s[0] == '#') continue; + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + if (s.find("seed ") == 0) + { + mSeed = atoi(s.substr(5).c_str()); + randomGenerator.setSeed(mSeed); + continue; + } + if (s.find("rvalues:") == 0) + { + randomGenerator.loadRandValues(s.substr(8).c_str()); + continue; + } + switch (state) + { + case -1: + if (s.compare("[init]") == 0) + state++; + break; + case 0: + if (s.compare("[player1]") == 0) + { + state++; + } + else + { + currentPlayerSet = parseLine(s); + } + break; + case 1: + if (s.compare("[player2]") == 0) + { + state++; + } + else + { + if(players.size() == 0 || !players[0]) + { + if (s.find("mode=") == 0) + { + createPlayer(s.substr(5) + #ifdef TESTSUITE + , testgame + #endif //TESTSUITE + ); + } + } + players[0]->parseLine(s); + } + break; + case 2: + if (s.compare("[do]") == 0) + { + state++; + } + else + { + if(players.size() == 1 || !players[1]) + { + if (s.find("mode=") == 0) + { + createPlayer(s.substr(5) +#ifdef TESTSUITE + , testgame +#endif //TESTSUITE + ); + } + } + players[1]->parseLine(s); + } + break; + case 3: + if (s.compare("[end]") == 0) + { + turn = 0; + mLayers = NEW DuelLayers(this, controlledPlayerIndex); + currentPlayer = players[currentPlayerId]; + phaseRing = NEW PhaseRing(this); + startedAt = time(0); + + // take a snapshot before processing the actions + resetStartupGame(); + + if(mRules) mRules->initGame(this, currentPlayerSet); + phaseRing->goToPhase(0, currentPlayer, false); + phaseRing->goToPhase(mCurrentGamePhase, currentPlayer); + +#ifdef TESTSUITE + if(testgame) + testgame->initGame(); +#endif //TESTSUITE + + processActions(undo + #ifdef TESTSUITE + , testgame + #endif //TESTSUITE + ); + } + else + { + logAction(s); + } + break; + } + } + + return true; +} + +bool GameObserver::processAction(const string& s) +{ + Player* p = players[1]; + if (s.find("p1") != string::npos) + p = players[0]; + + MTGGameZone* zone = NULL; + if(s.find(string(p->game->hand->getName())+"[") != string::npos) + zone = p->game->hand; + else if(s.find(string(p->game->battlefield->getName())+"[") != string::npos) + zone = p->game->battlefield; + else if(s.find(string(p->game->graveyard->getName())+"[") != string::npos) + zone = p->game->graveyard; + else if(s.find(string(p->game->library->getName())+"[") != string::npos) + zone = p->game->library; + + if(zone) { + size_t begin = s.find("[")+1; + size_t size = s.find("]")-begin; + size_t index = atoi(s.substr(begin, size).c_str()); + dumpAssert(index < zone->cards.size()); + cardClick(zone->cards[index], zone->cards[index]); + } else if (s.find("stack") != string::npos) { + size_t begin = s.find("[")+1; + size_t size = s.find("]")-begin; + size_t index = atoi(s.substr(begin, size).c_str()); + stackObjectClicked((Interruptible*)mLayers->stackLayer()->getByIndex(index)); + } else if (s.find("yes") != string::npos) { + mLayers->stackLayer()->setIsInterrupting(p); + } else if (s.find("no") != string::npos) { + mLayers->stackLayer()->cancelInterruptOffer(); + } else if (s.find("endinterruption") != string::npos) { + mLayers->stackLayer()->endOfInterruption(); + } else if (s.find("next") != string::npos) { + userRequestNextGamePhase(); + } else if (s.find("combatok") != string::npos) { + mLayers->combatLayer()->clickOK(); + } else if (s == "p1" || s == "p2") { + cardClick(NULL, p); + } else if (s.find("choice") != string::npos) { + int choice = atoi(s.substr(s.find("choice ") + 7).c_str()); + mLayers->actionLayer()->doReactTo(choice); + } else if (s == "p1" || s == "p2") { + cardClick(NULL, p); + } else if(s.find("mulligan") != string::npos) { + Mulligan(p); + } else if(s.find("shufflelib") != string::npos) { + // This should probably be differently and be automatically part of the ability triggered + // that would allow the AI to use it as well. + shuffleLibrary(p); + } else { + DebugTrace("no clue about: " + s); + } + + return true; +} + +bool GameObserver::processActions(bool undo + #ifdef TESTSUITE + , TestSuiteGame* testgame + #endif + ) +{ + bool result = false; + size_t cmdIndex = 0; + + loadingList = actionsList; + actionsList.clear(); + + mLoading = true; + float counter = 0.0f; + + // To handle undo, we'll remove the last P1 action and all P2 actions after. + if(undo && loadingList.size()) { + while(loadingList.back().find("p2") != string::npos) + loadingList.pop_back(); + // we do not undo "next phase" action to avoid abuse by users + if(loadingList.back().find("next") == string::npos) + loadingList.pop_back(); + } + + // We fake here cause the initialization before caused mana pool reset events to be triggered + // So, we need them flushed to be able to set the manapool to whatever we need + GameObserver::Update(counter); + counter += 1.000f; + +#ifdef TESTSUITE + if(testgame) + { + testgame->ResetManapools(); + } +#endif + + for(loadingite = loadingList.begin(); loadingite != loadingList.end(); loadingite++, cmdIndex++) + { + processAction(*loadingite); + + size_t nb = actionsList.size(); + + for (int i = 0; i<6; i++) + { + // let's fake an update + GameObserver::Update(counter); + counter += 1.000f; + } + dumpAssert(actionsList.back() == *loadingite); + dumpAssert(nb == actionsList.size()); + dumpAssert(cmdIndex == (actionsList.size()-1)); + } + + mLoading = false; + return result; +} + +void GameObserver::logAction(Player* player, const string& s) { + if(player == players[0]) + if(s != "") + logAction("p1." + s); + else + logAction("p1"); + else + if(s != "") + logAction("p2." + s); + else + logAction("p2"); +} + +void GameObserver::logAction(MTGCardInstance* card, MTGGameZone* zone, size_t index, int result) { + stringstream stream; + if(zone == NULL) zone = card->currentZone; + stream << "p" << ((card->controller()==players[0])?"1.":"2.") + << zone->getName()<< "[" << index << "] " + << result << card->getLCName(); + logAction(stream.str()); +} + +void GameObserver::logAction(const string& s) +{ + if(mLoading) + { + string toCheck = *loadingite; + dumpAssert(toCheck == s); + } + actionsList.push_back(s); +}; + +bool GameObserver::undo() +{ + stringstream stream; + stream << *this; + DebugTrace(stream.str()); + return load(stream.str(), true); +} + +void GameObserver::Mulligan(Player* player) +{ + if(!player) player = currentPlayer; + logAction(player, "mulligan"); + player->takeMulligan(); +} + +void GameObserver::serumMulligan(Player* player) +{ + if(!player) player = currentPlayer; + logAction(player, "mulligan serum powder"); + player->serumMulligan(); +} + +Player* GameObserver::createPlayer(const string& playerMode + #ifdef TESTSUITE + , TestSuiteGame* testgame + #endif //TESTSUITE + ) +{ + Player::Mode aMode = (Player::Mode)atoi(playerMode.c_str()); + Player* pPlayer = 0; + + switch(aMode) + { + case Player::MODE_AI: + AIPlayerFactory playerCreator; + if(players.size()) + pPlayer = playerCreator.createAIPlayer(this, MTGCollection(), players[0]); + else + pPlayer = playerCreator.createAIPlayer(this, MTGCollection(), 0); + break; + case Player::MODE_HUMAN: + pPlayer = new HumanPlayer(this, "", ""); + break; + case Player::MODE_TEST_SUITE: +#ifdef TESTSUITE + if(players.size()) + pPlayer = new TestSuiteAI(testgame, 1); + else + pPlayer = new TestSuiteAI(testgame, 0); +#endif //TESTSUITE + break; + } + + if(pPlayer) + { + players.push_back(pPlayer); + } + + return pPlayer; +} + +#ifdef TESTSUITE +void GameObserver::loadTestSuitePlayer(int playerId, TestSuiteGame* testSuite) +{ + loadPlayer(playerId, new TestSuiteAI(testSuite, playerId)); +} +#endif //TESTSUITE + +void GameObserver::loadPlayer(int playerId, Player* player) +{ + //Because we're using a vector instead of an array (why?), + // we have to prepare the vector in order to be the right size to accomodate the playerId variable + // see http://code.google.com/p/wagic/issues/detail?id=772 + if (players.size() > (size_t) playerId) { + SAFE_DELETE(players[playerId]); + players[playerId] = NULL; + } else { + while (players.size() <= (size_t) playerId) + { + players.push_back(NULL); + } + } + + players[playerId] = player; +} + +void GameObserver::loadPlayer(int playerId, PlayerType playerType, int decknb, bool premadeDeck) +{ + if (decknb) + { + if (playerType == PLAYER_TYPE_HUMAN) + { //Human Player + if(playerId == 0) + { + char deckFile[255]; + if (premadeDeck) + sprintf(deckFile, "player/premade/deck%i.txt", decknb); + else + sprintf(deckFile, "%s/deck%i.txt", options.profileFile().c_str(), decknb); + char deckFileSmall[255]; + sprintf(deckFileSmall, "player_deck%i", decknb); + + loadPlayer(playerId, NEW HumanPlayer(this, deckFile, deckFileSmall, premadeDeck)); + } + } + else + { //AI Player, chooses deck + AIPlayerFactory playerCreator; + Player * opponent = NULL; + if (playerId == 1) opponent = players[0]; + + loadPlayer(playerId, playerCreator.createAIPlayer(this, MTGCollection(), opponent, decknb)); + } + } + else + { + //Random deck + AIPlayerFactory playerCreator; + Player * opponent = NULL; + + // Reset the random logging. + randomGenerator.loadRandValues(""); + + if (playerId == 1) opponent = players[0]; +#ifdef AI_CHANGE_TESTING + if (playerType == PLAYER_TYPE_CPU_TEST) + loadPlayer(playerId, playerCreator.createAIPlayerTest(this, MTGCollection(), opponent, playerId == 0 ? "ai/bakaA/" : "ai/bakaB/")); + else +#endif + { + loadPlayer(playerId, playerCreator.createAIPlayer(this, MTGCollection(), opponent)); + } + + if (playerType == PLAYER_TYPE_CPU_TEST) + ((AIPlayer *) players[playerId])->setFastTimerMode(); + } +} + +#ifdef NETWORK_SUPPORT +NetworkGameObserver::NetworkGameObserver(JNetwork* pNetwork, WResourceManager* output, JGE* input) + : GameObserver(output, input), mpNetworkSession(pNetwork), mSynchronized(false) +{ + mpNetworkSession->registerCommand("loadPlayer", this, loadPlayer, ignoreResponse); + mpNetworkSession->registerCommand("synchronize", this, synchronize, checkSynchro); + mpNetworkSession->registerCommand("sendAction", this, sendAction, checkSynchro); + mpNetworkSession->registerCommand("disconnect", this, disconnect, ignoreResponse); +} + +NetworkGameObserver::~NetworkGameObserver() +{ + mpNetworkSession->sendCommand("disconnect", ""); +} + +void NetworkGameObserver::disconnect(void*pxThis, stringstream&, stringstream&) +{ + NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; + pThis->setLoser(pThis->getView()->getRenderedPlayerOpponent()); +} + +void NetworkGameObserver::Update(float dt) +{ + mpNetworkSession->Update(); + ::GameObserver::Update(dt); +} + +void NetworkGameObserver::loadPlayer(int playerId, Player* player) +{ + GameObserver::loadPlayer(playerId, player); + stringstream out; + out << *player; + mpNetworkSession->sendCommand("loadPlayer", out.str()); +} + +void NetworkGameObserver::loadPlayer(void*pxThis, stringstream& in, stringstream&) +{ + NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; + Player* pPlayer = 0; + string s; + + while(std::getline(in, s)) + { + if (s.find("mode=") == 0) + { + pPlayer = pThis->createPlayer(s.substr(5) + #ifdef TESTSUITE + , 0 + #endif //TESTSUITE + ); + } + + if(pPlayer && (!pPlayer->parseLine(s))) + { + break; + } + } +} + +void NetworkGameObserver::synchronize() +{ + if(!mSynchronized && mpNetworkSession->isServer()) + { + stringstream out; + out << *this; + mpNetworkSession->sendCommand("synchronize", out.str()); + mSynchronized = true; + } +} + +void NetworkGameObserver::synchronize(void*pxThis, stringstream& in, stringstream& out) +{ + NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; + // now, we need to load the game from player 2's perspective + pThis->load(in.str(), false, 1); + out << *pThis; +} + + +void NetworkGameObserver::checkSynchro(void*pxThis, stringstream& in, stringstream&) +{ + NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; + + GameObserver aGame; + aGame.mRules = pThis->mRules; + aGame.load(in.str()); + + assert(aGame == *pThis); +} + +void NetworkGameObserver::sendAction(void*pxThis, stringstream& in, stringstream&) +{ + NetworkGameObserver* pThis = (NetworkGameObserver*)pxThis; + + pThis->mForwardAction = false; + pThis->processAction(in.str()); + pThis->mForwardAction = true; + //out << *pThis; +} + +void NetworkGameObserver::logAction(const string& s) +{ + GameObserver::logAction(s); + if(mForwardAction) + mpNetworkSession->sendCommand("sendAction", s); +} + +#endif diff --git a/projects/mtg/src/GameStateOptions.cpp b/projects/mtg/src/GameStateOptions.cpp index 57d983833..9562aba27 100644 --- a/projects/mtg/src/GameStateOptions.cpp +++ b/projects/mtg/src/GameStateOptions.cpp @@ -48,7 +48,8 @@ void GameStateOptions::Start() } optionsList->Add(NEW OptionInteger(Options::INTERRUPT_SECONDS, "Seconds to pause for an Interrupt", 20, 1)); optionsList->Add(NEW OptionInteger(Options::INTERRUPTMYSPELLS, "Interrupt my spells")); - optionsList->Add(NEW OptionInteger(Options::INTERRUPTMYABILITIES, "Interrupt my abilities")); + // optionsList->Add(NEW OptionInteger(Options::INTERRUPTMYABILITIES, "Interrupt my abilities")); + //this is a dev option, not meant for standard play. uncomment if you need to see abilities you own hitting the stack. optionsList->Add(NEW OptionInteger(Options::INTERRUPT_SECONDMAIN, "Interrupt opponent's end of turn")); optionsTabs = NEW WGuiTabMenu(); optionsTabs->Add(optionsList); diff --git a/projects/mtg/src/GuiStatic.cpp b/projects/mtg/src/GuiStatic.cpp index 2b7c6c93d..1ec667c15 100644 --- a/projects/mtg/src/GuiStatic.cpp +++ b/projects/mtg/src/GuiStatic.cpp @@ -149,12 +149,16 @@ ostream& GuiAvatar::toString(ostream& out) const void GuiGameZone::toggleDisplay() { - if (showCards) - showCards = 0; - else + if (showCards) + { + showCards = 0; + cd->zone->owner->getObserver()->OpenedDisplay = NULL; + } + else if(!cd->zone->owner->getObserver()->OpenedDisplay)//one display at a time please. { showCards = 1; cd->init(zone); + cd->zone->owner->getObserver()->OpenedDisplay = cd; } } diff --git a/projects/mtg/src/MTGAbility.cpp b/projects/mtg/src/MTGAbility.cpp index 58abcadce..089921769 100644 --- a/projects/mtg/src/MTGAbility.cpp +++ b/projects/mtg/src/MTGAbility.cpp @@ -19,8 +19,8 @@ const string kLordKeywords[] = { "lord(", "foreach(", "aslongas(", "teach(", "all(" }; const size_t kLordKeywordsCount = 5; -const string kThisKeywords[] = { "this(", "thisforeach(" }; -const size_t kThisKeywordsCount = 2; +const string kThisKeywords[] = { "this(", "thisforeach(","while(", }; +const size_t kThisKeywordsCount = 3; // Used for the maxCast/maxPlay ability parsing @@ -353,6 +353,24 @@ int AbilityFactory::parseCastRestrictions(MTGCardInstance * card, Player * playe return 0; } + check = restriction[i].find("delirium"); + if (check != string::npos) + { + Player * checkCurrent = card->controller(); + MTGGameZone * grave = checkCurrent->game->graveyard; + + int checkTypesAmount = 0; + if(grave->hasType("creature")) checkTypesAmount++; + if (grave->hasType("enchantment")) checkTypesAmount++; + if (grave->hasType("sorcery")) checkTypesAmount++; + if (grave->hasType("instant")) checkTypesAmount++; + if (grave->hasType("land")) checkTypesAmount++; + if (grave->hasType("artifact")) checkTypesAmount++; + if (grave->hasType("planeswalker")) checkTypesAmount++; + if (checkTypesAmount < 4) + return 0; + } + check = restriction[i].find("miracle"); if(check != string::npos) { @@ -362,6 +380,13 @@ int AbilityFactory::parseCastRestrictions(MTGCardInstance * card, Player * playe return 0; } + check = restriction[i].find("madnessplayed"); + if (check != string::npos) + { + if (card->previous && !card->previous->MadnessPlay) + return 0; + } + check = restriction[i].find("prowl"); if(check != string::npos) { @@ -437,6 +462,13 @@ int AbilityFactory::parseCastRestrictions(MTGCardInstance * card, Player * playe return 0; } + check = restriction[i].find("geared"); + if (check != string::npos) + { + if (card->equipment < 1) + return 0; + } + check = restriction[i].find("raid"); if(check != string::npos) { @@ -1139,6 +1171,13 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG observer->addObserver(NEW MTGFlashBackRule(observer, -1)); return NULL; } + //alternative cost type flashback + found = s.find("bestowrule"); + if (found != string::npos) + { + observer->addObserver(NEW MTGBestowRule(observer, -1)); + return NULL; + } //alternative cost type retrace found = s.find("retracerule"); if(found != string::npos) @@ -1306,19 +1345,46 @@ 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(")"); - } - } + + //Reveal:x remove the core so we dont build them prematurely + vectortransPayfound = parseBetween(s, "newability[pay(", " "); + vectortransfound = parseBetween(s,"newability[reveal:"," ");//if we are using reveal inside a newability, let transforms remove the string instead. + vectorabilfound = parseBetween(s, "ability$!name(reveal) reveal:", " "); + if(!abilfound.size()) + abilfound = parseBetween(s, "ability$!reveal:", " ");//see above. this allows us to nest reveals inside these 2 other master classes. while also allowing us to nest them inside reveals. + + found = s.find("pay("); + if (found != string::npos && storedPayString.empty() && !transPayfound.size()) + { + 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(")"); + } + } + + vector splitRevealx = parseBetween(s, "reveal:", " revealend", false); + if (!abilfound.size() && !transfound.size() && splitRevealx.size() && storedAbilityString.empty()) + { + storedAbilityString = splitRevealx[1]; + s = splitRevealx[0]; + s.append("reveal: "); + s.append(splitRevealx[2]); + } + + vector splitScryx = parseBetween(s, "scry:", " scryend", false); + if (splitScryx.size() && storedAbilityString.empty()) + { + storedAbilityString = splitScryx[1]; + s = splitScryx[0]; + s.append("scry: "); + s.append(splitScryx[2]); + } + found = s.find("transforms(("); if (found != string::npos && storedString.empty()) { @@ -1750,15 +1816,25 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } if (end != string::npos) { + ThisDescriptor * td = NULL; string thisDescriptorString = s.substr(found + header, end - found - header); - ThisDescriptorFactory tdf; - ThisDescriptor * td = tdf.createThisDescriptor(observer, thisDescriptorString); + vector splitRest = parseBetween(s, "restriction{", "}"); + if (splitRest.size()) + { - if (!td) - { - DebugTrace("MTGABILITY: Parsing Error:" << s); - return NULL; - } + + } + else + { + ThisDescriptorFactory tdf; + td = tdf.createThisDescriptor(observer, thisDescriptorString); + + if (!td) + { + DebugTrace("MTGABILITY: Parsing Error:" << s); + return NULL; + } + } MTGAbility * a = parseMagicLine(s1, id, spell, card, 0, activated); if (!a) @@ -1795,6 +1871,9 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG case 1: result = NEW AThisForEach(observer, id, card, _target, td, a); break; + case 2: + result = NEW AThis(observer, id, card, _target, NULL, a, thisDescriptorString); + break; default: result = NULL; } @@ -1986,7 +2065,6 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return NEW APaired(observer,id, card,card->myPair,a); return NULL; } - //mana of the listed type doesnt get emptied from the pools. vectorcolorType = parseBetween(s,"poolsave(",")",false); if (colorType.size()) @@ -2269,9 +2347,21 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG tok->oneShot = 1; return tok; } - + string tokenDesc = splitToken[1]; vector tokenParameters = split(tokenDesc, ','); + //lets try finding a token by card name. + if (splitToken[1].size() && tokenParameters.size() ==1) + { + string cardName = splitToken[1]; + MTGCard * safetycard = MTGCollection()->getCardByName(cardName); + if (safetycard) //lets try constructing it then,we didnt find it by name + { + ATokenCreator * tok = NEW ATokenCreator(observer, id, card, target, NULL, cardName, starfound, multiplier, who); + tok->oneShot = 1; + return tok; + } + } if (tokenParameters.size() < 3) { DebugTrace("incorrect Parameters for Token" << tokenDesc); @@ -2474,6 +2564,7 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG 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 asNormalMadness = splitCastCard[1].find("madness") != string::npos; bool sendNoEvent = splitCastCard[1].find("noevent") != string::npos; bool putinplay = splitCastCard[1].find("putinplay") != string::npos; string nameCard = ""; @@ -2485,7 +2576,7 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG nameCard = splitCastName[1]; } } - MTGAbility *a = NEW AACastCard(observer, id, card, target,withRestrictions,asCopy,asNormal,nameCard,newName,sendNoEvent,putinplay); + MTGAbility *a = NEW AACastCard(observer, id, card, target,withRestrictions,asCopy,asNormal,nameCard,newName,sendNoEvent,putinplay, asNormalMadness); a->oneShot = false; if(splitCastCard[1].find("trigger[to]") != string::npos) { @@ -2933,6 +3024,16 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return a; } + //bestow + found = s.find("bstw"); + if (found != string::npos) + { + MTGAbility * a = NEW ABestow(observer, id, card, target); + a->oneShot = 1; + return a; + + } + //no counters on target of optional type vector splitCounterShroud = parseBetween(s, "countershroud(", ")"); if (splitCounterShroud.size()) @@ -3122,6 +3223,30 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG } + //Reveal:x (activate aility) + vector splitReveal = parseBetween(s, "reveal:", "revealend", false); + if (splitReveal.size()) + { + string backup = storedAbilityString; + storedAbilityString = "";//we clear the string here for cards that contain more than 1 reveal. + GenericRevealAbility * a = NEW GenericRevealAbility(observer, id, card, target, backup); + a->oneShot = 1; + a->canBeInterrupted = false; + return a; + } + + //scry:x (activate aility) + vector splitScry = parseBetween(s, "scry:", "scryend", false); + if (splitScry.size()) + { + string backup = storedAbilityString; + storedAbilityString = "";//we clear the string here for cards that contain more than 1 reveal. + GenericScryAbility * a = NEW GenericScryAbility(observer, id, card, target, backup); + a->oneShot = 1; + a->canBeInterrupted = false; + return a; + } + //flip vector splitFlipStat = parseBetween(s, "flip(", ")", true); if(splitFlipStat.size()) @@ -3291,11 +3416,20 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG found = s.find("frozen"); if (found != string::npos) { - MTGAbility * a = NEW AAFrozen(observer, id, card, target); + MTGAbility * a = NEW AAFrozen(observer, id, card, target,false); a->oneShot = 1; return a; } + //frozen, next untap this does not untap. + found = s.find("freeze"); + if (found != string::npos) + { + MTGAbility * a = NEW AAFrozen(observer, id, card, target,true); + a->oneShot = 1; + return a; + } + //get a new target - retarget and newtarget makes the card refreshed - from exile to play... if ((s.find("retarget") != string::npos) || s.find("newtarget") != string::npos) { @@ -3330,6 +3464,14 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return a; } + vector splitCountObject = parseBetween(s, "count(", ")", false); + if (splitCountObject.size()) + { + MTGAbility * a = NEW AACountObject(observer, id, card, card, NULL, splitCountObject[1]); + a->oneShot = 1; + return a; + } + //switch targest power with toughness found = s.find("swap"); if (found != string::npos) @@ -4625,6 +4767,7 @@ MTGAbility::MTGAbility(const MTGAbility& a): ActionElement(a) BuyBack = a.BuyBack; //? NEW ManaCost(a.BuyBack) : NULL; FlashBack = a.FlashBack; // ? NEW ManaCost(a.FlashBack) : NULL; Retrace = a.Retrace;// ? NEW ManaCost(a.Retrace) : NULL; + Bestow = a.Bestow; morph = a.morph; //? NEW ManaCost(a.morph) : NULL; suspend = a.suspend;// ? NEW ManaCost(a.suspend) : NULL; @@ -5101,7 +5244,7 @@ int TargetAbility::reactToClick(MTGCardInstance * card) } else { - if (tc->toggleTarget(card) == TARGET_OK_FULL) + if (tc->toggleTarget(card) == TARGET_OK_FULL && tc->targetsReadyCheck() == TARGET_OK_FULL) { int result = ActivatedAbility::reactToClick(source); if (result) @@ -5360,8 +5503,8 @@ void ListMaintainerAbility::updateTargets() for (int i = 0; i < 2; i++) { Player * p = game->players[i]; - MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->stack, p->game->exile }; - for (int k = 0; k < 6; k++) + MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->stack, p->game->exile ,p->game->reveal }; + for (int k = 0; k < 7; k++) { MTGGameZone * zone = zones[k]; if (canTarget(zone)) @@ -5432,8 +5575,8 @@ void ListMaintainerAbility::checkTargets() for (int i = 0; i < 2; i++) { Player * p = game->players[i]; - MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->stack, p->game->exile }; - for (int k = 0; k < 6; k++) + MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->stack, p->game->exile, p->game->reveal }; + for (int k = 0; k < 7; k++) { MTGGameZone * zone = zones[k]; if (canTarget(zone)) @@ -5772,7 +5915,7 @@ int AManaProducer::resolve() Player * player = getPlayerFromTarget(_target); if (!player) return 0; - + player->getManaPool()->add(output, source); if(DoesntEmpty) player->doesntEmpty->add(output); diff --git a/projects/mtg/src/MTGCardInstance.cpp b/projects/mtg/src/MTGCardInstance.cpp index 70e8b32cf..6b63ec740 100644 --- a/projects/mtg/src/MTGCardInstance.cpp +++ b/projects/mtg/src/MTGCardInstance.cpp @@ -69,6 +69,8 @@ MTGCardInstance::MTGCardInstance(MTGCard * card, MTGPlayerCards * arg_belongs_to cardistargetted = 0; cardistargetter = 0; myconvertedcost = getManaCost()->getConvertedCost(); + revealedLast = NULL; + MadnessPlay = false; } MTGCardInstance * MTGCardInstance::createSnapShot() @@ -194,6 +196,7 @@ void MTGCardInstance::initMTGCI() wasDealtDamage = false; isDualWielding = false; suspended = false; + isBestowed = false; castMethod = Constants::NOT_CAST; mPropertiesChangedSinceLastUpdate = false; stillNeeded = true; @@ -222,6 +225,7 @@ void MTGCardInstance::initMTGCI() imprintW = 0; currentimprintName = ""; imprintedNames.clear(); + CountedObjects = 0; for (int i = 0; i < ManaCost::MANA_PAID_WITH_SUSPEND +1; i++) alternateCostPaid[i] = 0; @@ -870,7 +874,7 @@ int MTGCardInstance::canBlock(MTGCardInstance * opponent) return 0; if (opponent->basicAbilities[(int)Constants::ONEBLOCKER] && opponent->blocked) return 0; - if(opponent->basicAbilities[(int)Constants::EVADEBIGGER] && power > opponent->power) + if((opponent->basicAbilities[(int)Constants::EVADEBIGGER]|| opponent->basicAbilities[(int)Constants::SKULK]) && power > opponent->power) return 0; if(opponent->basicAbilities[(int)Constants::STRONG] && power < opponent->power) return 0; @@ -1266,7 +1270,7 @@ int MTGCardInstance::setDefenser(MTGCardInstance * opponent) if (defenser) { if (observer->players[0]->game->battlefield->hasCard(defenser) || observer->players[1]->game->battlefield->hasCard(defenser)) - { + {//remove blocker "this" from the attackers list of blockers. defenser->removeBlocker(this); } } diff --git a/projects/mtg/src/MTGDeck.cpp b/projects/mtg/src/MTGDeck.cpp index 0189b2208..f0beba462 100644 --- a/projects/mtg/src/MTGDeck.cpp +++ b/projects/mtg/src/MTGDeck.cpp @@ -105,8 +105,19 @@ int MTGAllCards::processConfLine(string &s, MTGCard *card, CardPrimitive * primi } break; - case 'b': //buyback + case 'b': //buyback/Bestow if (!primitive) primitive = NEW CardPrimitive(); + if (key[1] == 'e' && key[2] == 's') + { //bestow + if (!primitive) primitive = NEW CardPrimitive(); + if (ManaCost * cost = primitive->getManaCost()) + { + string value = val; + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + cost->setBestow(ManaCost::parseManaCost(value)); + } + } + else//buyback if (ManaCost * cost = primitive->getManaCost()) { string value = val; diff --git a/projects/mtg/src/MTGDefinitions.cpp b/projects/mtg/src/MTGDefinitions.cpp index 543493675..d2c76c05f 100644 --- a/projects/mtg/src/MTGDefinitions.cpp +++ b/projects/mtg/src/MTGDefinitions.cpp @@ -26,6 +26,7 @@ const string Constants::kFlashBackKeyword = "flashback"; const string Constants::kRetraceKeyword = "retrace"; const string Constants::kKickerKeyword = "kicker"; const string Constants::kMorphKeyword = "facedown"; +const string Constants::kBestowKeyword = "bestow"; int Constants::NB_Colors = 0; //Store the Max number of colors. @@ -157,7 +158,11 @@ const char* Constants::MTGBasicAbilities[] = { "cantchangelife", "combattoughness", "cantpaylife", - "cantbesacrified" + "cantbesacrified", + "skulk", + "menace", + "nosolo", + "mustblock" }; map Constants::MTGBasicAbilitiesMap; diff --git a/projects/mtg/src/MTGGamePhase.cpp b/projects/mtg/src/MTGGamePhase.cpp index e88ae621d..7bc8c5c51 100644 --- a/projects/mtg/src/MTGGamePhase.cpp +++ b/projects/mtg/src/MTGGamePhase.cpp @@ -9,6 +9,7 @@ MTGGamePhase::MTGGamePhase(GameObserver* g, int id) : animation = 0; currentState = -1; mFont = WResourceManager::Instance()->GetWFont(Fonts::MAIN_FONT); + if(mFont) mFont->SetBase(0); // using 2nd font } diff --git a/projects/mtg/src/MTGGameZones.cpp b/projects/mtg/src/MTGGameZones.cpp index 3a274b192..ab34c5f57 100644 --- a/projects/mtg/src/MTGGameZones.cpp +++ b/projects/mtg/src/MTGGameZones.cpp @@ -74,6 +74,7 @@ MTGPlayerCards::~MTGPlayerCards() SAFE_DELETE(stack); SAFE_DELETE(removedFromGame); SAFE_DELETE(garbage); + SAFE_DELETE(reveal); SAFE_DELETE(temp); SAFE_DELETE(playRestrictions); } @@ -91,6 +92,7 @@ void MTGPlayerCards::beforeBeginPhase() stack->beforeBeginPhase(); removedFromGame->beforeBeginPhase(); garbage->beforeBeginPhase(); + reveal->beforeBeginPhase(); temp->beforeBeginPhase(); } @@ -105,6 +107,7 @@ void MTGPlayerCards::setOwner(Player * player) stack->setOwner(player); garbage->setOwner(player); garbageLastTurn->setOwner(player); + reveal->setOwner(player); temp->setOwner(player); } @@ -272,6 +275,7 @@ void MTGPlayerCards::init() exile = removedFromGame; garbage = NEW MTGGameZone(); garbageLastTurn = garbage; + reveal = NEW MTGGameZone(); temp = NEW MTGGameZone(); playRestrictions = NEW PlayRestrictions(); @@ -360,6 +364,12 @@ MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone to = g->players[i]->game->exile; } } + //all cards that go from the hand to the graveyard is ALWAYS a discard. + if ((to == g->players[0]->game->graveyard || to == g->players[1]->game->graveyard) && (from == g->players[0]->game->hand || from + == g->players[1]->game->hand)) + { + card->discarded = true; + } //When a card is moved from inPlay to inPlay (controller change, for example), it is still the same object if ((to == g->players[0]->game->inPlay || to == g->players[1]->game->inPlay) && (from == g->players[0]->game->inPlay || from == g->players[1]->game->inPlay)) @@ -371,6 +381,7 @@ MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone if (!(copy = from->removeCard(card, doCopy))) return NULL; //ERROR + if (card->miracle) { copy->miracle = true; @@ -435,11 +446,12 @@ MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone previous->next = NULL; SAFE_DELETE(previous); } + } if(!asCopy) { if(shufflelibrary) - copy->owner->game->library->shuffle(); + copy->owner->game->library->shuffle();//shouldnt we only ever do this if you clicked close on your library gui?????? WEvent * e = NEW WEventZoneChange(copy, from, to); g->receiveEvent(e); @@ -995,6 +1007,14 @@ MTGGameZone * MTGGameZone::intToZone(int zoneId, Player * p, Player * p2) return p->opponent()->game->stack; case STACK: return p->game->stack; + + case MY_REVEAL: + return p->game->reveal; + case OPPONENT_REVEAL: + return p->opponent()->game->reveal; + case REVEAL: + return p->game->reveal; + } if (!p2) return NULL; switch (zoneId) @@ -1017,6 +1037,9 @@ MTGGameZone * MTGGameZone::intToZone(int zoneId, Player * p, Player * p2) case TARGET_CONTROLLER_STACK: return p2->game->stack; + case TARGET_CONTROLLER_REVEAL: + return p2->game->reveal; + default: return NULL; } @@ -1114,6 +1137,18 @@ MTGGameZone * MTGGameZone::intToZone(GameObserver *g, int zoneId, MTGCardInstanc if(source->playerTarget) return source->playerTarget->game->stack; else return source->controller()->game->stack; + + case TARGET_OWNER_REVEAL: + return target->owner->game->reveal; + case REVEAL: + return target->owner->game->reveal; + case OWNER_REVEAL: + return target->owner->game->reveal; + case TARGETED_PLAYER_REVEAL: + if (source->playerTarget) + return source->playerTarget->game->reveal; + else return source->controller()->game->reveal; + default: return NULL; } @@ -1141,6 +1176,8 @@ int MTGGameZone::zoneStringToId(string zoneName) "mystack", "opponentstack", "targetownerstack", "targetcontrollerstack", "ownerstack", "stack","targetedpersonsstack", + "myreveal", "opponentreveal", "targetownerreveal", "targetcontrollerreveal", "ownerreveal", "reveal","targetedpersonsreveal", + }; int values[] = { MY_GRAVEYARD, OPPONENT_GRAVEYARD, TARGET_OWNER_GRAVEYARD, TARGET_CONTROLLER_GRAVEYARD, OWNER_GRAVEYARD, @@ -1160,7 +1197,9 @@ int MTGGameZone::zoneStringToId(string zoneName) MY_EXILE, OPPONENT_EXILE, TARGET_OWNER_EXILE, TARGET_CONTROLLER_EXILE, OWNER_EXILE, EXILE,TARGETED_PLAYER_EXILE, - MY_STACK, OPPONENT_STACK, TARGET_OWNER_STACK, TARGET_CONTROLLER_STACK, OWNER_STACK, STACK,TARGETED_PLAYER_STACK }; + MY_STACK, OPPONENT_STACK, TARGET_OWNER_STACK, TARGET_CONTROLLER_STACK, OWNER_STACK, STACK,TARGETED_PLAYER_STACK, + + MY_REVEAL, OPPONENT_REVEAL, TARGET_OWNER_REVEAL, TARGET_CONTROLLER_REVEAL, OWNER_REVEAL, REVEAL,TARGETED_PLAYER_REVEAL }; int max = sizeof(values) / sizeof *(values); diff --git a/projects/mtg/src/MTGRules.cpp b/projects/mtg/src/MTGRules.cpp index d2ead3181..d9a40173f 100644 --- a/projects/mtg/src/MTGRules.cpp +++ b/projects/mtg/src/MTGRules.cpp @@ -367,9 +367,7 @@ int MTGPutInPlayRule::reactToClick(MTGCardInstance * card) return 0; Player * player = game->currentlyActing(); ManaCost * cost = card->getManaCost(); - //this handles extra cost payments at the moment a card is played. - if (cost->isExtraPaymentSet()) { if (!game->targetListIsSet(card)) @@ -383,7 +381,7 @@ int MTGPutInPlayRule::reactToClick(MTGCardInstance * card) game->mExtraPayment = cost->extraCosts; return 0; } - + ManaCost * previousManaPool = NEW ManaCost(player->getManaPool()); int payResult = player->getManaPool()->pay(card->getManaCost()); if (card->getManaCost()->getKicker() && (OptionKicker::KICKER_ALWAYS == options[Options::KICKERPAYMENT].number || card->controller()->isAI())) @@ -497,6 +495,9 @@ int MTGKickerRule::isReactingToClick(MTGCardInstance * card, ManaCost *) ManaCost * withKickerCost= NEW ManaCost(card->getManaCost()); withKickerCost->add(card->getManaCost()->getKicker()); //cost reduction/recalculation must be here or outside somehow... + //no recalculations beyound this point, reactToClick is the function that + //happens only with the assumption that you could actually pay for it, any calculations after will + //have negitive effects. this function is basically "can i play this card?" #ifdef WIN32 withKickerCost->Dump(); #endif @@ -532,7 +533,7 @@ int MTGKickerRule::reactToClick(MTGCardInstance * card) ManaCost * previousManaPool = NEW ManaCost(player->getManaPool()); int payResult = player->getManaPool()->pay(card->getManaCost()); if (card->getManaCost()->getKicker()) - { //cost reduction/recalculation must be here or outside somehow... + { ManaCost * withKickerCost= NEW ManaCost(card->getManaCost()); withKickerCost->add(withKickerCost->getKicker()); if (card->getManaCost()->getKicker()->isMulti) @@ -1345,7 +1346,68 @@ MTGOverloadRule * MTGOverloadRule::clone() const { return NEW MTGOverloadRule(*this); } +/////////////////////////////////////////////////////////////////////////////////////////////////// +//bestow +MTGBestowRule::MTGBestowRule(GameObserver* observer, int _id) : + MTGAlternativeCostRule(observer, _id) +{ + aType = MTGAbility::BESTOW_COST; +} +int MTGBestowRule::isReactingToClick(MTGCardInstance * card, ManaCost * mana) +{ + if (!card->model) + return 0; + Player * player = game->currentlyActing(); + if (!card->model->data->getManaCost()->getBestow()) + return 0; + if (card->isInPlay(game)) + return 0; + ManaCost * cost = NEW ManaCost(card->model->data->getManaCost()->getBestow()); + ManaCost * newCost = card->computeNewCost(card, cost, cost); + if (newCost->extraCosts) + for (unsigned int i = 0; i < newCost->extraCosts->costs.size(); i++) + { + newCost->extraCosts->costs[i]->setSource(card); + } + SAFE_DELETE(cost); + if (card->isLand()) + return 0; + if (!card->controller()->inPlay()->hasType("creature") && !card->controller()->opponent()->inPlay()->hasType("creature")) + return 0; + return MTGAlternativeCostRule::isReactingToClick(card, mana, newCost); +} + +int MTGBestowRule::reactToClick(MTGCardInstance * card) +{ + if (!isReactingToClick(card)) + return 0; + //this new method below in all alternative cost type causes a memleak, however, you cant safedelete the cost here as it cause a crash + //TODO::::we need to get to the source of this leak and fix it. + ManaCost * cost = NEW ManaCost(card->model->data->getManaCost()->getBestow()); + ManaCost * newCost = card->computeNewCost(card, cost, cost); + + if (newCost->extraCosts) + for (unsigned int i = 0; i < newCost->extraCosts->costs.size(); i++) + { + newCost->extraCosts->costs[i]->setSource(card); + } + + card->paymenttype = MTGAbility::BESTOW_COST; + card->spellTargetType = "creature|battlefield"; + return MTGAlternativeCostRule::reactToClick(card, newCost, ManaCost::MANA_PAID_WITH_BESTOW, false); +} + +ostream& MTGBestowRule::toString(ostream& out) const +{ + out << "MTGBestowRule ::: ("; + return MTGAbility::toString(out) << ")"; +} + +MTGBestowRule * MTGBestowRule::clone() const +{ + return NEW MTGBestowRule(*this); +} /////////////////////////////////////////////////////////////////////////////////////////////////// //ATTACK COST @@ -1425,7 +1487,6 @@ MTGBlockCostRule::MTGBlockCostRule(GameObserver* observer, int _id) : aType = MTGAbility::BLOCK_COST; scost = "Pay to block"; } - int MTGBlockCostRule::isReactingToClick(MTGCardInstance * card, ManaCost *) { if (currentPhase == MTG_PHASE_COMBATBLOCKERS && !game->isInterrupting @@ -1536,6 +1597,14 @@ int MTGAttackRule::receiveEvent(WEvent *e) for (int i = 0; i < z->nb_cards; i++) { MTGCardInstance * card = z->cards[i]; + if (card->isAttacker() && card->has(Constants::NOSOLO)) + { + TargetChooserFactory tf(game); + TargetChooser * tc = tf.createTargetChooser("creature[attacking]", NULL); + int Check = card->controller()->game->battlefield->countByCanTarget(tc); + if (Check <2) + card->initAttackersDefensers(); + } if (!card->isAttacker() && !event->from->isExtra && card->has(Constants::MUSTATTACK))//cards are only required to attack in the real attack phase of a turn. reactToClick(card); if (!card->isAttacker() && card->has(Constants::TREASON) && p->isAI()) @@ -1848,6 +1917,50 @@ PermanentAbility(observer, _id) int MTGBlockRule::receiveEvent(WEvent *e) { + + if (dynamic_cast(e)) + {//do not refactor, these are keep seperate for readability. + Player * p = game->currentPlayer; + + vector Attacker; + MTGGameZone * k = p->game->inPlay; + for (int i = 0; i < k->nb_cards; i++) + { + MTGCardInstance * card = k->cards[i]; + if (card->isAttacker()) + { + Attacker.push_back(card); + } + } + //force cards that must block, to block whatever is first found. players have a chance to set thier own + //but if ignored we do it for them. + if (Attacker.size()) + { + MTGGameZone * tf = p->opponent()->game->inPlay; + for (size_t i = 0; i < tf->cards.size(); i++) + { + MTGCardInstance * card = tf->cards[i]; + if (card->has(Constants::MUSTBLOCK) && !card->defenser && card->canBlock()) + {//force mustblockers to block the first thing theyre allowed to block if player leaves blockers with them + //unassigned as a block. + for (size_t i = 0; i < Attacker.size(); i++) + { + if (card->canBlock(Attacker[i]) && !card->defenser) + { + blocker = NEW AABlock(card->getObserver(), -1, card, NULL); + blocker->oneShot = true; + blocker->forceDestroy = 1; + blocker->canBeInterrupted = false; + blocker->target = Attacker[i]; + blocker->resolve(); + SAFE_DELETE(blocker); + } + } + + } + } + + } if (dynamic_cast(e)) { @@ -1866,6 +1979,25 @@ int MTGBlockRule::receiveEvent(WEvent *e) //but this action can not be ignored. } } + + //if a card with menace is not blocked by 2 or more, remove any known blockers and attacking as normal. + MTGGameZone * z = p->game->inPlay; + for (int i = 0; i < z->nb_cards; i++) + { + MTGCardInstance * card = z->cards[i]; + if (card->has(Constants::MENACE) && card->blockers.size() < 2) + { + while (card->blockers.size()) + { + MTGCardInstance * blockingCard = card->blockers.front(); + blockingCard->toggleDefenser(NULL); + + } + } + } + + } + return 1; } diff --git a/projects/mtg/src/ManaCost.cpp b/projects/mtg/src/ManaCost.cpp index ffac01ac3..e9af2bd43 100644 --- a/projects/mtg/src/ManaCost.cpp +++ b/projects/mtg/src/ManaCost.cpp @@ -152,7 +152,8 @@ ManaCost * ManaCost::parseManaCost(string s, ManaCost * _manaCost, MTGCardInstan manaCost->addExtraCost(NEW SacrificeCost(tc)); } break; - case 'e': //Exile + case 'e': + //Exile manaCost->addExtraCost(NEW ExileTargetCost(tc)); break; case 'h': //bounce (move to Hand) @@ -387,6 +388,7 @@ ManaCost::ManaCost(ManaCost * manaCost) FlashBack = NEW ManaCost( manaCost->FlashBack ); morph = NEW ManaCost( manaCost->morph ); suspend = NEW ManaCost( manaCost->suspend ); + Bestow = NEW ManaCost(manaCost->Bestow); extraCosts = manaCost->extraCosts ? manaCost->extraCosts->clone() : NULL; manaUsedToCast = NULL; @@ -415,7 +417,8 @@ ManaCost::ManaCost(const ManaCost& manaCost) FlashBack = NEW ManaCost( manaCost.FlashBack ); morph = NEW ManaCost( manaCost.morph ); suspend = NEW ManaCost( manaCost.suspend ); - + Bestow = NEW ManaCost(manaCost.Bestow); + extraCosts = manaCost.extraCosts ? manaCost.extraCosts->clone() : NULL; manaUsedToCast = NULL; xColor = manaCost.xColor; @@ -438,6 +441,7 @@ ManaCost & ManaCost::operator= (const ManaCost & manaCost) FlashBack = manaCost.FlashBack; morph = manaCost.morph; suspend = manaCost.suspend; + Bestow = manaCost.Bestow; manaUsedToCast = manaCost.manaUsedToCast; xColor = manaCost.xColor; } @@ -454,6 +458,7 @@ ManaCost::~ManaCost() SAFE_DELETE(Retrace); SAFE_DELETE(morph); SAFE_DELETE(suspend); + SAFE_DELETE(Bestow); SAFE_DELETE(manaUsedToCast); cost.erase(cost.begin() ,cost.end()); @@ -539,6 +544,7 @@ void ManaCost::init() Retrace = NULL; morph = NULL; suspend = NULL; + Bestow = NULL; manaUsedToCast = NULL; isMulti = false; xColor = -1; @@ -563,6 +569,7 @@ void ManaCost::resetCosts() SAFE_DELETE(Retrace); SAFE_DELETE(morph); SAFE_DELETE(suspend); + SAFE_DELETE(Bestow); } void ManaCost::copy(ManaCost * _manaCost) @@ -628,6 +635,12 @@ void ManaCost::copy(ManaCost * _manaCost) suspend = NEW ManaCost(); suspend->copy(_manaCost->suspend); } + SAFE_DELETE(Bestow); + if (_manaCost->Bestow) + { + Bestow = NEW ManaCost(); + Bestow->copy(_manaCost->Bestow); + } xColor = _manaCost->xColor; } @@ -1092,6 +1105,7 @@ void ManaPool::Empty() SAFE_DELETE(Retrace); SAFE_DELETE(morph); SAFE_DELETE(suspend); + SAFE_DELETE(Bestow); SAFE_DELETE(manaUsedToCast); init(); WEvent * e = NEW WEventEmptyManaPool(this); diff --git a/projects/mtg/src/Rules.cpp b/projects/mtg/src/Rules.cpp index 80bf52ab2..55cf0b6b6 100644 --- a/projects/mtg/src/Rules.cpp +++ b/projects/mtg/src/Rules.cpp @@ -413,12 +413,13 @@ void Rules::initGame(GameObserver *g, bool currentPlayerSet) { p->mAvatarName = initState.playerData[i].player->mAvatarName; } - MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay, p->game->exile }; + MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay, p->game->exile , p->game->reveal }; MTGGameZone * loadedPlayerZones[] = { initState.playerData[i].player->game->graveyard, initState.playerData[i].player->game->library, initState.playerData[i].player->game->hand, initState.playerData[i].player->game->inPlay, - initState.playerData[i].player->game->exile }; + initState.playerData[i].player->game->exile, + initState.playerData[i].player->game->reveal }; for (int j = 0; j < 5; j++) { MTGGameZone * zone = playerZones[j]; diff --git a/projects/mtg/src/TargetChooser.cpp b/projects/mtg/src/TargetChooser.cpp index 687834fdf..59a464dbb 100644 --- a/projects/mtg/src/TargetChooser.cpp +++ b/projects/mtg/src/TargetChooser.cpp @@ -156,6 +156,11 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta { zones[nbzones++] = MTGGameZone::ALL_ZONES; } + else if (zoneName.compare("reveal") == 0) + { + zones[nbzones++] = MTGGameZone::MY_REVEAL; + zones[nbzones++] = MTGGameZone::OPPONENT_REVEAL; + } else if (zoneName.compare("graveyard") == 0) { zones[nbzones++] = MTGGameZone::MY_GRAVEYARD; @@ -637,15 +642,15 @@ 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 = CardDescriptor::CD_OR; - } + if (attribute.find("colorless") != string::npos) + { + attributefound = 1; + for (int cid = 1; cid < Constants::NB_Colors; cid++) + { + cd->SetExclusionColor(cid); + } + cd->mode = CardDescriptor::CD_OR; + } if (attribute.find("chosencolor") != string::npos) { @@ -817,6 +822,7 @@ TargetChooser::TargetChooser(GameObserver *observer, MTGCardInstance * card, int TargetsList(), observer(observer) { forceTargetListReady = 0; + forceTargetListReadyByPlayer = 0; source = card; targetter = card; maxtargets = _maxtargets; @@ -899,6 +905,10 @@ int TargetChooser::ForceTargetListReady() int TargetChooser::targetsReadyCheck() { + if (targetMin == false && !targets.size() && forceTargetListReadyByPlayer) + { + return TARGET_OK_FULL;//we have no min amount for targets and 0 targets is a valid amount player called for a forced finish. + } if (!targets.size()) { return TARGET_NOK; @@ -931,8 +941,8 @@ bool TargetChooser::validTargetsExist(int maxTargets) int maxAmount = 0; Player *p = observer->players[i]; if (canTarget(p)) return true; - MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->exile, p->game->stack }; - for (int k = 0; k < 6; k++) + MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->exile, p->game->stack, p->game->reveal }; + for (int k = 0; k < 7; k++) { MTGGameZone * z = zones[k]; if (targetsZone(z)) @@ -965,8 +975,8 @@ int TargetChooser::countValidTargets(bool withoutProtections) Player *p = observer->players[i]; if(canTarget(p)) result++; - MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->exile, p->game->stack }; - for (int k = 0; k < 6; k++) + MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->exile, p->game->stack, p->game->reveal }; + for (int k = 0; k < 7; k++) { MTGGameZone * z = zones[k]; if (targetsZone(z)) diff --git a/projects/mtg/src/WEvent.cpp b/projects/mtg/src/WEvent.cpp index ccd04afb3..1273e77f8 100644 --- a/projects/mtg/src/WEvent.cpp +++ b/projects/mtg/src/WEvent.cpp @@ -123,6 +123,7 @@ WEventTarget::WEventTarget(MTGCardInstance * card,MTGCardInstance * source) : WEventCardUpdate(card),card(card),source(source) { card->cardistargetted = 1; + if(source) source->cardistargetter = 1; }