From 6ee00c138ce374d54cb3ee034575ce440288ca0e Mon Sep 17 00:00:00 2001 From: zethfoxster Date: Tue, 28 Jun 2016 18:40:55 -0400 Subject: [PATCH] Pretty huge patch here(sorry old habits never die :( ) lots of changes, many bug fixes, first added auto=count(targetchooser) and countedamount wparsed int they work together for cards where it is difficult to get working without knowing in advance how many we had ie: exile blah creatures, for each creature you exiled do effect. auto=count(creature|mybattlefield) auto=moveto(exile) auto=draw:countedamount it takes into account token creatures, which our old methods did not. second, added "freeze" which is a "frozen" that automatically taps your target for you, for use when nesting or whenever needed where it was difficult to nest the ability with tap included. added devotion for "iroas" added reveal:x and scry x reveal contains optionone/optiononeend ; optiontwo/optiontwoend ; repeat; afterrevealed/afterrevealed end. this ability has heavy use of targetListIsSet() and upto:amount, you MUST be certain that all cards being revealed have an action that removes them from reveal either in the first, second, or 3rd ability. there are over 300 examples in the new card code, the ability is VERY easy to understand. scry contains automatic put on top, put on bottom, then scrycore/scrycoreend which is an ability to fire. it also contains keywords, dontshow which is nested in scrycore, scry reveals, puts on top or bottom, then reveal AGAIN, and does an effect, dontshow eliminates the 2nd revealing. is also contains "delayed" keyword, which delays the ability until AFTER the core fires. added bestow. update rules mtg.txt!!!! examples are in primitives, every bestow card was supported. added a new lord based on varibles and restrictions while(restriction{morbid}) while(varible:blah) this simplifies and expands on this(, allowing you to even use while(cantarget together and check if a card is targetable by the variable. examples are in primitives added token(by card name) auto=token(Eldrazi Scion) will search primitives and card dats for this card and give it to you as a token. valid card dat info is still required. added variable delirium added restriction madnessplayed to allow checking if the card was played with madness. added restriction "geared" for checking if a card has equipment on it. added abilities words skulk menace <--cant be blocked except by 2 or more, if you dont block it with 2 or more we automatically unassign the single blocker and the creature is considered not blocked. nosolo <--cant attack alone mustblock <---if you dont assign as a blocker, we assign automatically the first thing it can block legally. changed iscolorless back to "colorless" enjoy, cards coming soon, theyre coded but im debating on not alpha sorting, cards being added this patch 965 uniques. there is a section of the commit which was just VS2016 normalizing line ends, sorry if it makes it a cluster mess. --- projects/mtg/bin/Res/rules/mtg.txt | 1 + projects/mtg/include/AllAbilities.h | 195 +- projects/mtg/include/GameObserver.h | 1 + projects/mtg/include/MTGAbility.h | 1 + projects/mtg/include/MTGCardInstance.h | 4 + projects/mtg/include/MTGDefinitions.h | 9 +- projects/mtg/include/MTGGameZones.h | 9 + projects/mtg/include/MTGRules.h | 15 + projects/mtg/include/ManaCost.h | 4 + projects/mtg/include/TargetChooser.h | 1 + projects/mtg/src/AIPlayerBaka.cpp | 8 +- projects/mtg/src/ActionLayer.cpp | 9 +- projects/mtg/src/ActionStack.cpp | 4 + projects/mtg/src/AllAbilities.cpp | 929 ++++- projects/mtg/src/ExtraCost.cpp | 6 +- projects/mtg/src/GameObserver.cpp | 4453 ++++++++++++------------ projects/mtg/src/GameStateOptions.cpp | 3 +- projects/mtg/src/GuiStatic.cpp | 10 +- projects/mtg/src/MTGAbility.cpp | 207 +- projects/mtg/src/MTGCardInstance.cpp | 8 +- projects/mtg/src/MTGDeck.cpp | 13 +- projects/mtg/src/MTGDefinitions.cpp | 7 +- projects/mtg/src/MTGGamePhase.cpp | 1 + projects/mtg/src/MTGGameZones.cpp | 43 +- projects/mtg/src/MTGRules.cpp | 142 +- projects/mtg/src/ManaCost.cpp | 18 +- projects/mtg/src/Rules.cpp | 5 +- projects/mtg/src/TargetChooser.cpp | 36 +- projects/mtg/src/WEvent.cpp | 1 + 29 files changed, 3854 insertions(+), 2289 deletions(-) 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; }