From 6399917d25a1540f4e9f1cad6ef60bf4c10d783d Mon Sep 17 00:00:00 2001 From: "omegablast2002@yahoo.com" Date: Thu, 1 Sep 2011 20:03:26 +0000 Subject: [PATCH] changes: added abilities: proliferate ProliferateChooser:new targetchooser for cards with counter and poison counters "proliferation". MenuAbility:new internal ability to create custom menus of abilities which can be activated in sequence one after another. multikicker, syntax kicker=multi{b} works with variable word "kicked", the amount of times it was kicked. target=tc,target=tc,target=tc,target(tc),target(tc),target(tc); multitarget is now supported with the exception of "devided any way you choose" which can not be supported becuase we allow detoggling of targeted cards with a "second" click....so you can not click the same card 2 times to add it to the targets list twice for example. this is minor, as the bulk of multitarget is not "devided" removed 's' parsing for multitarget, added a limit of 1000 to "unlimited" for easier handling; we currently can't handle activation of an ability on a 1000 cards very well on any platform(infact i don't suggest it) Countershroud(counterstring), this MTGAbility allows you to denote that a card can not have counters of the type "counterstring" put on it. "any" is for no counters allowed at all. this is a replacement effect. cards state that they can still be the targets of counter effects, however on resolve nothing is placed on them instead. @counteradded(counterstring) from(target):,@counterremoved(counterstring) from(target):: these are triggers for cards which state "whenever you add a counter of "counterstring" to "target"; added counterEvents struct; other changes: added support for ai handling of multitargeted spells. changed a few of delete( into SAFE_DELETE(, safed up a couple areas where they did not seem safe to me; added better handling of menus presented to ai, it will try to select the best based on eff returns. added varible lastactioncontroller for ai use, it keeps it truely from ever tripping over itself and brings ai more inline with MTG rules. converted TC into a protected member. added "abilitybelongsto" string to tc, and set "owner" of the tc. a tc should never belong to "no one" it should always have a owner. abilitybelongs to string is solely for easier debugging, i found it was a pain to never know what ability created a tc while i coded multitarget. the owner of the tc is the only one that should be using it, if an ability needs to declare the opponent as the owner (choose discard which is currently unsupported for example) this will allow us to better handle that situation by setting the tc owner in the ability which called it. rewrote the logic of "checkonly" in ai choose targets, the only time it is "checkonly" is when it is trying to see if it had a target for a spell before it cast it, i now set this in the actual function call instead, the old method was far to error prone. wrote logic for ai checking of menu objects presented to it, ai will now make better choices when a menu is presented to it based on what it already knows. this changes it from it's old method of "just click the first option". taught ai how to use multi-mana producers such as birds and duel lands by adding a method for it to find it's mana for a payment. it can effectively use cards like birds of paradise and sol ring(without locking up). It's primary method of pMana searching was maintain for performance(no need to deep search if we have it in pMana). added a vector to actionlayer to store mana abilities for pMana. this provides us with a dramatic improvement when mana lords are present by reducing the amount of objects that need checking when ai checks pMana. with 80 mana objects and a ton of lords one instance i checked went from 8000ish checks down to 80<===big difference. added "tapped" green coloring(sorry i missed that!)...added red coloring to current actionLayers current action card (usually the source). changed "type(" restrictions second amount from atoi into wparsedint for more flexiable coding. add "&" parsing to CD targetchooser, removed "iscolorandcolor" variables and functions becuase they were a hack the real fix was this. cretaure[dragon&black&blue] a creature that is a dragon, and black and also blue. changed some of the ai computeactions and removed unneeded gaurds in ai chooseblockers, they did more harm then good. --- projects/mtg/include/AIPlayer.h | 31 +- projects/mtg/include/ActionElement.h | 8 +- projects/mtg/include/ActionLayer.h | 5 + projects/mtg/include/ActionStack.h | 2 +- projects/mtg/include/AllAbilities.h | 100 +- projects/mtg/include/CardDescriptor.h | 6 - projects/mtg/include/GameObserver.h | 1 + projects/mtg/include/GuiLayers.h | 1 + projects/mtg/include/MTGAbility.h | 3 +- projects/mtg/include/MTGCardInstance.h | 6 +- projects/mtg/include/MTGDefinitions.h | 6 +- projects/mtg/include/ManaCost.h | 1 + projects/mtg/include/ReplacementEffects.h | 14 + projects/mtg/include/SimpleMenu.h | 2 + projects/mtg/include/TargetChooser.h | 69 +- projects/mtg/include/WEvent.h | 14 + projects/mtg/src/AIMomirPlayer.cpp | 2 +- projects/mtg/src/AIPlayer.cpp | 1312 ++++++++++++++++----- projects/mtg/src/ActionElement.cpp | 1 - projects/mtg/src/ActionLayer.cpp | 119 +- projects/mtg/src/ActionStack.cpp | 15 +- projects/mtg/src/AllAbilities.cpp | 292 ++++- projects/mtg/src/CardDescriptor.cpp | 41 - projects/mtg/src/CardGui.cpp | 20 +- projects/mtg/src/Counters.cpp | 56 +- projects/mtg/src/Damage.cpp | 11 +- projects/mtg/src/DuelLayers.cpp | 2 +- projects/mtg/src/ExtraCost.cpp | 9 + projects/mtg/src/GameObserver.cpp | 54 +- projects/mtg/src/GuiLayers.cpp | 12 + projects/mtg/src/GuiPhaseBar.cpp | 2 +- projects/mtg/src/MTGAbility.cpp | 223 +++- projects/mtg/src/MTGCardInstance.cpp | 17 +- projects/mtg/src/MTGDeck.cpp | 11 +- projects/mtg/src/MTGDefinitions.cpp | 3 +- projects/mtg/src/MTGGameZones.cpp | 4 +- projects/mtg/src/MTGRules.cpp | 44 +- projects/mtg/src/ManaCost.cpp | 13 +- projects/mtg/src/ReplacementEffects.cpp | 28 + projects/mtg/src/SimpleMenu.cpp | 1 + projects/mtg/src/Subtypes.cpp | 2 + projects/mtg/src/TargetChooser.cpp | 199 ++-- projects/mtg/src/TargetsList.cpp | 22 +- projects/mtg/src/WEvent.cpp | 10 + 44 files changed, 2176 insertions(+), 618 deletions(-) diff --git a/projects/mtg/include/AIPlayer.h b/projects/mtg/include/AIPlayer.h index 885ebf83a..9dcadefd4 100644 --- a/projects/mtg/include/AIPlayer.h +++ b/projects/mtg/include/AIPlayer.h @@ -34,22 +34,37 @@ public: int id; MTGCardInstance * click; MTGCardInstance * target; // TODO Improve + vectormAbilityTargets; + Targetable * playerAbilityTarget; + //player targeting through abilities is handled completely seperate from spell targeting. AIAction(MTGAbility * a, MTGCardInstance * c, MTGCardInstance * t = NULL) - : efficiency(-1), ability(a), player(NULL), click(c), target(t) + : efficiency(-1), ability(a), player(NULL), click(c), target(t),playerAbilityTarget(NULL) { id = currentId++; }; AIAction(MTGCardInstance * c, MTGCardInstance * t = NULL); - AIAction(Player * p) - : efficiency(-1), ability(NULL), player(p), click(NULL), target(NULL) + AIAction(Player * p)//player targeting through spells + : efficiency(-1), ability(NULL), player(p), click(NULL), target(NULL),playerAbilityTarget(NULL) { }; + AIAction(MTGAbility * a, MTGCardInstance * c, vectortargetCards) + : efficiency(-1), ability(a), player(NULL), click(c), mAbilityTargets(targetCards),playerAbilityTarget(NULL) + { + id = currentId++; + }; + + AIAction(MTGAbility * a, Player * p, MTGCardInstance * c)//player targeting through abilities. + : efficiency(-1), ability(a), click(c),target(NULL), playerAbilityTarget(p) + { + id = currentId++; + }; int getEfficiency(); int Act(); + int clickMultiAct(vector&actionTargets); }; // compares Abilities efficiency @@ -75,7 +90,7 @@ protected: MTGCardInstance * nextCardToPlay; AIHints * hints; queue clickstream; - bool tapLandsForMana(ManaCost * cost, MTGCardInstance * card = NULL); + bool payTheManaCost(ManaCost * cost, MTGCardInstance * card = NULL,vector gotPayment = vector()); int orderBlockers(); int combatDamages(); int interruptIfICan(); @@ -101,10 +116,15 @@ public: int receiveEvent(WEvent * event); void Render(); ManaCost * getPotentialMana(MTGCardInstance * card = NULL); + vector canPayMana(MTGCardInstance * card = NULL,ManaCost * mCost = NULL); + vector canPaySunBurst(ManaCost * mCost = NULL); AIPlayer(string deckFile, string deckFileSmall, MTGDeck * deck = NULL); virtual ~AIPlayer(); virtual MTGCardInstance * chooseCard(TargetChooser * tc, MTGCardInstance * source, int random = 0); - virtual int chooseTarget(TargetChooser * tc = NULL, Player * forceTarget =NULL,MTGCardInstance * Choosencard = NULL); + virtual int selectMenuOption(); + virtual int chooseTarget(TargetChooser * tc = NULL, Player * forceTarget =NULL,MTGCardInstance * Choosencard = NULL,bool checkonly = false); + virtual int clickMultiTarget(TargetChooser * tc,vector&potentialTargets); + virtual int clickSingleTarget(TargetChooser * tc,vector&potentialTargets,int nbtargets = 0,MTGCardInstance * Choosencard = NULL); virtual int Act(float dt); virtual int affectCombatDamages(CombatStep); int isAI(){return 1;}; @@ -123,6 +143,7 @@ class AIPlayerBaka: public AIPlayer{ float timer; MTGCardInstance * FindCardToPlay(ManaCost * potentialMana, const char * type); public: + vectorgotPayments; int deckId; AIPlayerBaka(string deckFile, string deckfileSmall, string avatarFile, MTGDeck * deck = NULL); virtual int Act(float dt); diff --git a/projects/mtg/include/ActionElement.h b/projects/mtg/include/ActionElement.h index 5e5b09511..5d8cf566a 100644 --- a/projects/mtg/include/ActionElement.h +++ b/projects/mtg/include/ActionElement.h @@ -22,8 +22,8 @@ class ActionElement: public JGuiObject { protected: int activeState; -public: TargetChooser * tc; +public: int currentPhase; int newPhase; int modal; @@ -48,9 +48,15 @@ public: ; ActionElement(int id); ActionElement(const ActionElement& copyFromMe); + TargetChooser * getActionTc(){return tc;} + virtual void setActionTC(TargetChooser * newTc = NULL){this->tc = newTc;} virtual ~ActionElement(); virtual int isReactingToTargetClick(Targetable * card); virtual int reactToTargetClick(Targetable * card); + virtual int reactToChoiceClick(Targetable * card,int choice = 0,int controlid = 0) + { + return 0; + } virtual int isReactingToClick(MTGCardInstance * card, ManaCost * man = NULL) { return 0; diff --git a/projects/mtg/include/ActionLayer.h b/projects/mtg/include/ActionLayer.h index 84d9907b0..7af89a0a5 100644 --- a/projects/mtg/include/ActionLayer.h +++ b/projects/mtg/include/ActionLayer.h @@ -24,6 +24,7 @@ public: vector garbage; Targetable * menuObject; SimpleMenu * abilitiesMenu; + MTGCardInstance * currentActionCard; int stuffHappened; virtual void Render(); virtual void Update(float dt); @@ -41,11 +42,15 @@ public: int reactToTargetClick(ActionElement * ability, Targetable * card); int stillInUse(MTGCardInstance * card); void setMenuObject(Targetable * object, bool must = false); + void setCustomMenuObject(Targetable * object, bool must = false,vectorabilities = vector()); void ButtonPressed(int controllerid, int controlid); + void doMultipleChoice(int choice = -1); + void ButtonPressedOnMultipleChoice(int choice = -1); void doReactTo(int menuIndex); TargetChooser * getCurrentTargetChooser(); void setCurrentWaitingAction(ActionElement * ae); MTGAbility * getAbility(int type); + int checkCantCancel(){return cantCancel;}; //Removes from game but does not move the element to garbage. The caller must take care of deleting the element. int removeFromGame(ActionElement * e); diff --git a/projects/mtg/include/ActionStack.h b/projects/mtg/include/ActionStack.h index 3dd144cf8..461cb6af7 100644 --- a/projects/mtg/include/ActionStack.h +++ b/projects/mtg/include/ActionStack.h @@ -207,7 +207,7 @@ public: DONT_INTERRUPT = 1, DONT_INTERRUPT_ALL = 2, }; - + Player * lastActionController; int setIsInterrupting(Player * player); int count( int type = 0 , int state = 0 , int display = -1); int getActionElementFromCard(MTGCardInstance * card); diff --git a/projects/mtg/include/AllAbilities.h b/projects/mtg/include/AllAbilities.h index 7de22bc9a..cd1c0147a 100644 --- a/projects/mtg/include/AllAbilities.h +++ b/projects/mtg/include/AllAbilities.h @@ -44,6 +44,8 @@ public: WParsedInt(string s, Spell * spell, MTGCardInstance * card) { + if(!card) + return; MTGCardInstance * target = card->target; intValue = 0; bool halfup = false; @@ -185,6 +187,10 @@ public: else if (s == "t" || s == "toughness") { intValue = target->getToughness(); + } + else if (s == "kicked") + { + intValue = target->kicked; } else if (s == "handsize") { @@ -698,6 +704,40 @@ public: } }; +//targetted trigger +class TrCounter: public Trigger +{ +public: + Counter * counter; + int type; + TrCounter(int id, MTGCardInstance * source, Counter * counter, TargetChooser * tc, int type = 0,bool once = false) : + Trigger(id, source, once, tc),counter(counter), type(type) + { + } + + int triggerOnEventImpl(WEvent * event) + { + WEventCounters * e = dynamic_cast (event); + if (!e) return 0; + if (type == 0 && !e->removed) return 0; + if (type == 1 && !e->added) return 0; + if (!(e->power == counter->power && e->toughness == counter->toughness && e->name == counter->name)) return 0; + if (tc && !tc->canTarget(e->targetCard)) return 0; + return 1; + } + + ~TrCounter() + { + SAFE_DELETE(counter); + } + + TrCounter * clone() const + { + TrCounter * mClone = NEW TrCounter(*this); + mClone->counter = NEW Counter(*this->counter); + return mClone; + } +}; //Tutorial Messaging class ATutorialMessage: public MTGAbility, public IconButtonsController @@ -796,13 +836,14 @@ public: class IfThenAbility: public MTGAbility { public: - string delayAbility; + MTGAbility * delayedAbility; int type; string Cond; - IfThenAbility(int _id,string delayAbility = "", MTGCardInstance * _source=NULL, int type = 1,string Cond = ""); + IfThenAbility(int _id,MTGAbility * delayedAbility = NULL, MTGCardInstance * _source=NULL, int type = 1,string Cond = ""); int resolve(); const char * getMenuText(); IfThenAbility * clone() const; + ~IfThenAbility(); }; //MayAbility: May do ... @@ -830,6 +871,39 @@ public: }; +//MayAbility with custom menues. +class MenuAbility: public MayAbility +{ +public: + int triggered; + bool removeMenu; + bool must; + MTGAbility * mClone; + vectorabilities; + Player * who; + MenuAbility(int _id, Targetable * target, MTGCardInstance * _source, bool must = false, vectorabilities = vector(),Player * who = NULL); + void Update(float dt); + int resolve(); + const char * getMenuText(); + int testDestroy(); + int isReactingToTargetClick(Targetable * card); + int reactToTargetClick(Targetable * object); + int reactToChoiceClick(Targetable * object,int choice,int control); + MenuAbility * clone() const; + ~MenuAbility(); + +}; + +class AAProliferate: public ActivatedAbility +{ +public: + AAProliferate(int id, MTGCardInstance * source, Targetable * target,ManaCost * cost = NULL); + int resolve(); + const char* getMenuText(); + AAProliferate * clone() const; + ~AAProliferate(); +}; + //MultiAbility : triggers several actions for a cost class MultiAbility: public ActivatedAbility { @@ -2531,7 +2605,7 @@ public: void livingWeaponToken(MTGCardInstance * card) { - GameObserver * g = g->GetInstance(); + GameObserver * g = GameObserver::GetInstance(); for (size_t i = 1; i < g->mLayers->actionLayer()->mObjects.size(); i++) { MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->mObjects[i]); @@ -2660,6 +2734,13 @@ public: return 1; } + int checkActivation() + { + checkCards.clear(); + checkTargets(); + return checkCards.size(); + } + ~AForeach() { SAFE_DELETE(ability); @@ -3424,7 +3505,18 @@ public: APreventDamageTypes * clone() const; ~APreventDamageTypes(); }; - +//prevent counters +class ACounterShroud: public MTGAbility +{ +public: + Counter * counter; + RECountersPrevention * re; + ACounterShroud(int id, MTGCardInstance * source, MTGCardInstance * target, Counter * counter = NULL); + int addToGame(); + int destroy(); + ACounterShroud * clone() const; + ~ACounterShroud(); +}; //Remove all abilities from target class ALoseAbilities: public MTGAbility { diff --git a/projects/mtg/include/CardDescriptor.h b/projects/mtg/include/CardDescriptor.h index cc770016b..32cbbb11c 100644 --- a/projects/mtg/include/CardDescriptor.h +++ b/projects/mtg/include/CardDescriptor.h @@ -43,12 +43,6 @@ class CardDescriptor: public MTGCardInstance void unsecureSetTapped(int i); void unsecuresetfresh(int k); void setisMultiColored(int w); - void setisBlackAndWhite(int w); - void setisRedAndBlue(int w); - void setisBlackAndGreen(int w); - void setisBlueAndGreen(int w); - void setisRedAndWhite(int w); - void setNegativeSubtype( string value); int counterPower; int counterToughness; diff --git a/projects/mtg/include/GameObserver.h b/projects/mtg/include/GameObserver.h index 0972fb18d..9552718e9 100644 --- a/projects/mtg/include/GameObserver.h +++ b/projects/mtg/include/GameObserver.h @@ -79,6 +79,7 @@ class GameObserver{ bool removeObserver(ActionElement * observer); void startGame(Rules * rules); void untapPhase(); + MTGCardInstance * isCardWaiting(){ return cardWaitingForTargets; } int isInPlay(MTGCardInstance * card); int isInGrave(MTGCardInstance * card); int isInExile(MTGCardInstance * card); diff --git a/projects/mtg/include/GuiLayers.h b/projects/mtg/include/GuiLayers.h index a563e4e3a..666af4d10 100644 --- a/projects/mtg/include/GuiLayers.h +++ b/projects/mtg/include/GuiLayers.h @@ -19,6 +19,7 @@ protected: public: int mCurr; vector mObjects; + vector manaObjects; void Add(JGuiObject * object); int Remove(JGuiObject * object); int modal; diff --git a/projects/mtg/include/MTGAbility.h b/projects/mtg/include/MTGAbility.h index cddef111b..dd74506f3 100644 --- a/projects/mtg/include/MTGAbility.h +++ b/projects/mtg/include/MTGAbility.h @@ -394,6 +394,7 @@ class ListMaintainerAbility:public MTGAbility { public: map cards; + map checkCards; map players; ListMaintainerAbility(int _id) : MTGAbility(_id, NULL) @@ -412,6 +413,7 @@ public: virtual void Update(float dt); void updateTargets(); + void checkTargets(); virtual bool canTarget(MTGGameZone * zone); virtual int canBeInList(MTGCardInstance * card) = 0; virtual int added(MTGCardInstance * card) = 0; @@ -506,7 +508,6 @@ public: int parsePowerToughness(string s, int *power, int *toughness); int getAbilities(vector * v, Spell * spell, MTGCardInstance * card = NULL, int id = 0, MTGGameZone * dest = NULL); MTGAbility* parseMagicLine(string s, int id, Spell * spell, MTGCardInstance *card, bool activated = false, bool forceUEOT = false, MTGGameZone * dest = NULL); - int abilityEfficiency(MTGAbility * a, Player * p, int mode = MODE_ABILITY, TargetChooser * tc = NULL,Targetable * target = NULL); int magicText(int id, Spell * spell, MTGCardInstance * card = NULL, int mode = MODE_PUTINTOPLAY, TargetChooser * tc = NULL, MTGGameZone * dest = NULL); static int computeX(Spell * spell, MTGCardInstance * card); diff --git a/projects/mtg/include/MTGCardInstance.h b/projects/mtg/include/MTGCardInstance.h index 286b6cbe2..5a7cbf979 100644 --- a/projects/mtg/include/MTGCardInstance.h +++ b/projects/mtg/include/MTGCardInstance.h @@ -66,11 +66,6 @@ public: int origpower; int origtoughness; int isMultiColored; - int isBlackAndWhite; - int isRedAndBlue; - int isBlackAndGreen; - int isBlueAndGreen; - int isRedAndWhite; int isLeveler; bool enchanted; int CDenchanted; @@ -92,6 +87,7 @@ public: int notblocked; int fresh; int MaxLevelUp; + int kicked; Player * lastController; MTGGameZone * getCurrentZone(); MTGGameZone * previousZone; diff --git a/projects/mtg/include/MTGDefinitions.h b/projects/mtg/include/MTGDefinitions.h index 65de5eb69..71e39a870 100644 --- a/projects/mtg/include/MTGDefinitions.h +++ b/projects/mtg/include/MTGDefinitions.h @@ -177,11 +177,9 @@ class Constants SNOWISLANDWALK = 89, SNOWSWAMPWALK = 90, CANATTACK = 91, + HYDRA = 92, - - - - NB_BASIC_ABILITIES = 92, + NB_BASIC_ABILITIES = 93, RARITY_S = 'S', //Special Rarity diff --git a/projects/mtg/include/ManaCost.h b/projects/mtg/include/ManaCost.h index dab5b59a8..7f8af7e0e 100644 --- a/projects/mtg/include/ManaCost.h +++ b/projects/mtg/include/ManaCost.h @@ -50,6 +50,7 @@ public: ManaCost * morph; ManaCost * suspend; string alternativeName; + bool isMulti; static ManaCost * parseManaCost(string value, ManaCost * _manacost = NULL, MTGCardInstance * c = NULL); virtual void init(); virtual void reinit(); diff --git a/projects/mtg/include/ReplacementEffects.h b/projects/mtg/include/ReplacementEffects.h index be06ed1de..a1986fa3c 100644 --- a/projects/mtg/include/ReplacementEffects.h +++ b/projects/mtg/include/ReplacementEffects.h @@ -5,6 +5,7 @@ using namespace std; #include "Damage.h" #include "WEvent.h" +#include "Counters.h" class TargetChooser; class MTGAbility; @@ -35,6 +36,19 @@ public: ~REDamagePrevention(); }; +class RECountersPrevention: public ReplacementEffect +{ +protected: + MTGAbility * source; + MTGCardInstance * cardSource; + MTGCardInstance * cardTarget; + Counter * counter; +public: + RECountersPrevention(MTGAbility * _source,MTGCardInstance * cardSource = NULL,MTGCardInstance * cardTarget = NULL,Counter * counter = NULL); + WEvent * replace(WEvent *e); + ~RECountersPrevention(); +}; + class ReplacementEffects { protected: diff --git a/projects/mtg/include/SimpleMenu.h b/projects/mtg/include/SimpleMenu.h index 2bca69d7b..9593eaa90 100644 --- a/projects/mtg/include/SimpleMenu.h +++ b/projects/mtg/include/SimpleMenu.h @@ -37,11 +37,13 @@ private: public: bool autoTranslate; + bool isMultipleChoice; SimpleMenu(int id, JGuiListener* listener, int fontId, float x, float y, const char * _title = "", int _maxItems = 7, bool centerHorizontal = true, bool centerVertical = true); virtual ~SimpleMenu(); void Render(); void Update(float dt); void Add(int id, const char * Text, string desc = "", bool forceFocus = false); + int getmCurr(){return mCurr;} void Close(); void RecenterMenu(); diff --git a/projects/mtg/include/TargetChooser.h b/projects/mtg/include/TargetChooser.h index 4283e0a00..0752b1c69 100644 --- a/projects/mtg/include/TargetChooser.h +++ b/projects/mtg/include/TargetChooser.h @@ -24,8 +24,8 @@ class TargetChooser: public TargetsList { protected: int forceTargetListReady; - public: + const static int UNLITMITED_TARGETS = 1000; enum { UNSET = 0, @@ -36,13 +36,16 @@ public: }; bool other; bool withoutProtections; - TargetChooser(MTGCardInstance * card = NULL, int _maxtargets = -1, bool other = false); - + TargetChooser(MTGCardInstance * card = NULL, int _maxtargets = UNLITMITED_TARGETS, bool other = false, bool targetMin = false); + Player * Owner; MTGCardInstance * source; MTGCardInstance * targetter; //Optional, usually equals source, used for protection from... - - int maxtargets; //Set to -1 for "unlimited" - bool validTargetsExist(); + int maxtargets; + bool done; + bool targetMin; + bool validTargetsExist(int maxTarget = 1); + int attemptsToFill; + string belongsToAbility; int countValidTargets(); virtual int setAllZones() { @@ -68,7 +71,7 @@ public: virtual int full() { - if (maxtargets != -1 && ((int) (targets.size())) >= maxtargets) + if ( (maxtargets != UNLITMITED_TARGETS && (int(targets.size())) >= maxtargets) || done) { return 1; } @@ -107,8 +110,8 @@ public: bool targetsZone(MTGGameZone * z); bool targetsZone(MTGGameZone * z,MTGCardInstance * mSource); bool withoutProtections; - TargetZoneChooser(MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false); - TargetZoneChooser(int * _zones, int _nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false); + TargetZoneChooser(MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false, bool targetMin = false); + TargetZoneChooser(int * _zones, int _nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false, bool targetMin = false); virtual bool canTarget(Targetable * _card,bool withoutProtections = false); int setAllZones(); virtual TargetZoneChooser * clone() const; @@ -133,8 +136,8 @@ public: int nbtypes; int types[10]; bool withoutProtections; - TypeTargetChooser(const char * _type, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false); - TypeTargetChooser(const char * _type, int * _zones, int nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false); + TypeTargetChooser(const char * _type, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false, bool targetMin = false); + TypeTargetChooser(const char * _type, int * _zones, int nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false, bool targetMin = false); void addType(int type); void addType(const char * type); virtual bool canTarget(Targetable * target,bool withoutProtections = false); @@ -146,13 +149,13 @@ class DamageableTargetChooser: public TypeTargetChooser { public: bool withoutProtections; - DamageableTargetChooser(int * _zones, int _nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false) : - TypeTargetChooser("creature",_zones, _nbzones, card, _maxtargets, other) + DamageableTargetChooser(int * _zones, int _nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false, bool targetMin = false) : + TypeTargetChooser("creature",_zones, _nbzones, card, _maxtargets, other, targetMin) { } ; - DamageableTargetChooser(MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false) : - TypeTargetChooser("creature", card, _maxtargets, other) + DamageableTargetChooser(MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false, bool targetMin = false) : + TypeTargetChooser("creature", card, _maxtargets, other, targetMin) { } ; @@ -168,7 +171,7 @@ protected: public: bool withoutProtections; PlayerTargetChooser(MTGCardInstance * card = NULL, int _maxtargets = 1, Player *_p = NULL); - virtual bool canTarget(Targetable * target,bool withoutProtections = false); + virtual bool canTarget(Targetable * target, bool withoutProtections = false); virtual PlayerTargetChooser * clone() const; virtual bool equals(TargetChooser * tc); }; @@ -178,8 +181,8 @@ class DescriptorTargetChooser: public TargetZoneChooser public: CardDescriptor * cd; bool withoutProtections; - DescriptorTargetChooser(CardDescriptor * _cd, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false); - DescriptorTargetChooser(CardDescriptor * _cd, int * _zones, int nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false); + DescriptorTargetChooser(CardDescriptor * _cd, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false,bool targetMin = false); + DescriptorTargetChooser(CardDescriptor * _cd, int * _zones, int nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false,bool targetMin = false); virtual bool canTarget(Targetable * target,bool withoutProtections = false); ~DescriptorTargetChooser(); virtual DescriptorTargetChooser * clone() const; @@ -191,8 +194,8 @@ class SpellTargetChooser: public TargetChooser public: int color; bool withoutProtections; - SpellTargetChooser(MTGCardInstance * card = NULL, int _color = -1, int _maxtargets = 1, bool other = false); - virtual bool canTarget(Targetable * target,bool withoutProtections = false); + SpellTargetChooser(MTGCardInstance * card = NULL, int _color = -1, int _maxtargets = 1, bool other = false, bool targetMin = false); + virtual bool canTarget(Targetable * target, bool withoutProtections = false); virtual SpellTargetChooser * clone() const; virtual bool equals(TargetChooser * tc); }; @@ -202,8 +205,8 @@ class SpellOrPermanentTargetChooser: public TargetZoneChooser public: int color; bool withoutProtections; - SpellOrPermanentTargetChooser(MTGCardInstance * card = NULL, int _color = -1, int _maxtargets = 1, bool other = false); - virtual bool canTarget(Targetable * target,bool withoutProtections = false); + SpellOrPermanentTargetChooser(MTGCardInstance * card = NULL, int _color = -1, int _maxtargets = 1, bool other = false, bool targetMin = false); + virtual bool canTarget(Targetable * target, bool withoutProtections = false); virtual SpellOrPermanentTargetChooser * clone() const; virtual bool equals(TargetChooser * tc); }; @@ -215,7 +218,7 @@ public: int state; bool withoutProtections; DamageTargetChooser(MTGCardInstance * card = NULL, int _color = -1, int _maxtargets = 1, int state = NOT_RESOLVED); - virtual bool canTarget(Targetable * target,bool withoutProtections = false); + virtual bool canTarget(Targetable * target, bool withoutProtections = false); virtual DamageTargetChooser * clone() const; virtual bool equals(TargetChooser * tc); }; @@ -229,9 +232,27 @@ public: bool withoutProtections; TriggerTargetChooser(int _triggerTarget); virtual bool targetsZone(MTGGameZone * z); - virtual bool canTarget(Targetable * _target,bool withoutProtections = false); + virtual bool canTarget(Targetable * _target, bool withoutProtections = false); virtual TriggerTargetChooser * clone() const; virtual bool equals(TargetChooser * tc); }; +class ProliferateChooser: public TypeTargetChooser +{ +public: + bool withoutProtections; + ProliferateChooser(int * _zones, int _nbzones, MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false, bool targetMin = false) : + TypeTargetChooser("*",_zones, _nbzones, card, _maxtargets, other, targetMin) + { + } + ; + ProliferateChooser(MTGCardInstance * card = NULL, int _maxtargets = 1, bool other = false,bool targetMin = false) : + TypeTargetChooser("*", card, _maxtargets, other,targetMin) + { + } + ; + virtual bool canTarget(Targetable * target, bool withoutProtections = false); + virtual ProliferateChooser * clone() const; + virtual bool equals(TargetChooser * tc); +}; #endif diff --git a/projects/mtg/include/WEvent.h b/projects/mtg/include/WEvent.h index e5e0f0034..c33a6f8ad 100644 --- a/projects/mtg/include/WEvent.h +++ b/projects/mtg/include/WEvent.h @@ -10,6 +10,8 @@ class Damage; class Phase; class Targetable; class ManaPool; +class AACounter; +class Counters; class WEvent { public: @@ -51,6 +53,18 @@ struct WEventDamage : public WEvent { virtual Targetable * getTarget(int target); }; +struct WEventCounters : public WEvent { + MTGCardInstance * targetCard; + Counters * counter; + string name; + int power; + int toughness; + bool added; + bool removed; + WEventCounters(Counters *counter,string name,int power, int toughness,bool added = false, bool removed = false); + virtual Targetable * getTarget(); +}; + struct WEventLife : public WEvent { Player * player; int amount; diff --git a/projects/mtg/src/AIMomirPlayer.cpp b/projects/mtg/src/AIMomirPlayer.cpp index 4716f7436..15238c5ad 100644 --- a/projects/mtg/src/AIMomirPlayer.cpp +++ b/projects/mtg/src/AIMomirPlayer.cpp @@ -60,7 +60,7 @@ int AIMomirPlayer::momir() MTGCardInstance * card = game->hand->cards[0]; if (ability->isReactingToClick(card, cost)) { - tapLandsForMana(cost); + payTheManaCost(cost); AIAction * a = NEW AIAction(ability, card); clickstream.push(a); result = 1; diff --git a/projects/mtg/src/AIPlayer.cpp b/projects/mtg/src/AIPlayer.cpp index 40b200762..19cb35c42 100644 --- a/projects/mtg/src/AIPlayer.cpp +++ b/projects/mtg/src/AIPlayer.cpp @@ -10,6 +10,7 @@ #include "GameStateDuel.h" #include "DeckManager.h" #include "AIHints.h" +#include "ManaCostHybrid.h" const char * const MTG_LAND_TEXTS[] = { "artifact", "forest", "island", "mountain", "swamp", "plains", "other lands" }; @@ -42,7 +43,7 @@ AIAction::AIAction(MTGCardInstance * c, MTGCardInstance * t) int AIAction::Act() { GameObserver * g = GameObserver::GetInstance(); - if (player) + if (player && !playerAbilityTarget) { g->cardClick(NULL, player); return 1; @@ -50,9 +51,24 @@ int AIAction::Act() if (ability) { g->mLayers->actionLayer()->reactToClick(ability, click); - if (target) + if (target && !mAbilityTargets.size()) + { g->cardClick(target); - return 1; + return 1; + } + else if(playerAbilityTarget && !mAbilityTargets.size()) + { + g->cardClick(NULL,(Player*)playerAbilityTarget); + return 1; + } + if(mAbilityTargets.size()) + { + return clickMultiAct(mAbilityTargets); + } + } + else if(mAbilityTargets.size()) + { + return clickMultiAct(mAbilityTargets); } else if (click) { //Shouldn't be used, really... @@ -64,6 +80,46 @@ int AIAction::Act() return 0; } +int AIAction::clickMultiAct(vector& actionTargets) +{ + GameObserver * g = GameObserver::GetInstance(); + TargetChooser * tc = g->getCurrentTargetChooser(); + if(!tc) return 0; + bool sourceIncluded = false; + for(size_t f = 0;f < actionTargets.size();f++) + { + MTGCardInstance * card = ((MTGCardInstance *) actionTargets[f]); + if(card == (MTGCardInstance*)tc->source)//click source first. + { + g->cardClick(card); + actionTargets.erase(actionTargets.begin() + f); + sourceIncluded = true; + } + } + std::random_shuffle(actionTargets.begin(), actionTargets.end()); + //shuffle to make it less predictable, otherwise ai will always seem to target from right to left. making it very obvious. + for(int k = 0;k < int(actionTargets.size());k++) + { + int type = actionTargets[k]->typeAsTarget(); + switch (type) + { + case TARGET_CARD: + { + if(k < tc->maxtargets) + { + MTGCardInstance * card = ((MTGCardInstance *) actionTargets[k]); + if(k+1 == int(actionTargets.size())) + tc->done = true; + g->cardClick(card); + } + } + break; + } + } + tc->attemptsToFill++; + return 1; +} + AIPlayer::AIPlayer(string file, string fileSmall, MTGDeck * deck) : Player(file, fileSmall, deck) { @@ -119,25 +175,84 @@ int AIPlayer::Act(float dt) return 1; } -bool AIPlayer::tapLandsForMana(ManaCost * cost, MTGCardInstance * target) +bool AIPlayer::payTheManaCost(ManaCost * cost, MTGCardInstance * target,vectorgotPayments) { - DebugTrace(" AI attempting to tap land for mana." << endl + DebugTrace(" AI attempting to pay a mana cost." << endl << "- Target: " << (target ? target->name : "None" ) << endl << "- Cost: " << (cost ? cost->toString() : "NULL") ); if(cost && !cost->getConvertedCost()) { - DebugTrace("Card has free to play. "); + DebugTrace("Card or Ability was free to play. "); + if(!cost->hasX())//don't return true if it contains {x} but no cost, locks ai in a loop. ie oorchi hatchery cost {x}{x} to play. return true; - //return true becuase we don't need to do anything with a cost of 0; - //special case for 0 cost, which is valid + //return true if cost does not contain "x" becuase we don't need to do anything with a cost of 0; } if (!cost) { DebugTrace("Mana cost is NULL. "); return false; } - ManaCost * pMana = target ? getPotentialMana(target) : getPotentialMana(); - + ManaCost * pMana = NULL; + if(!gotPayments.size()) + { + pMana = target ? getPotentialMana(target) : getPotentialMana(); + pMana->add(this->getManaPool()); + } + if(cost && pMana && !cost->getConvertedCost() && cost->hasX()) + { + cost = pMana;//{x}:effect, set x to max. + } + if(gotPayments.size()) + { + DebugTrace(" Ai had a payment in mind."); + ManaCost * paid = NEW ManaCost(); + paid->init(); + GameObserver * go = GameObserver::GetInstance(); + vectorclicks = vector(); + for(int k = 0;k < int(gotPayments.size());k++) + { + AManaProducer * amp = dynamic_cast (gotPayments[k]); + GenericActivatedAbility * gmp = dynamic_cast(gotPayments[k]); + if (amp) + { + AIAction * action = NEW AIAction(amp,amp->source); + clicks.push_back(action); + paid->add(amp->output); + } + else if(gmp) + { + AIAction * action = NEW AIAction(gmp,gmp->source); + clicks.push_back(action); + AForeach * fmp = dynamic_cast(gmp->ability); + if(fmp) + { + amp = dynamic_cast (fmp->ability); + int outPut = fmp->checkActivation(); + for(int k = 0;k < outPut;k++) + paid->add(amp->output); + } + } + paid->add(this->getManaPool());//incase some of our payments were mana already in the pool/. + if(paid->canAfford(cost) && !cost->hasX()) + { + SAFE_DELETE(paid); + for(int clicking = 0; clicking < int(clicks.size()); clicking++) + clickstream.push(clicks[clicking]); + return true; + } + if(cost->hasX() && k == int(gotPayments.size()) && paid->canAfford(cost)) + { + SAFE_DELETE(paid); + for(int clicking = 0; clicking < int(clicks.size()); clicking++) + clickstream.push(clicks[clicking]); + return true; + } + } + SAFE_DELETE(paid); + return false; + } + //pMana is our main payment form, it is far faster then direct search. + DebugTrace(" the Mana was already in the manapool or could be Paid with potential mana, using potential Mana now."); if(!pMana->canAfford(cost)) { delete pMana; @@ -190,29 +305,320 @@ ManaCost * AIPlayer::getPotentialMana(MTGCardInstance * target) ManaCost * result = NEW ManaCost(); GameObserver * g = GameObserver::GetInstance(); map used; - for (size_t i = 1; i < g->mLayers->actionLayer()->mObjects.size(); i++) - { //0 is not a mtgability...hackish + for (size_t i = 0; i < g->mLayers->actionLayer()->manaObjects.size(); i++) + { //Make sure we can use the ability - MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->mObjects[i]); + MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->manaObjects[i]); AManaProducer * amp = dynamic_cast (a); + GenericActivatedAbility * gmp = dynamic_cast(a); + if(gmp && canHandleCost(gmp)) + { + //skip for each mana producers. + AForeach * fmp = dynamic_cast(gmp->ability); + if(fmp) + { + amp = dynamic_cast (fmp->ability); + if(amp) + { + used[fmp->source] = true; + continue; + } + } + } if (amp && canHandleCost(amp)) { MTGCardInstance * card = amp->source; if (card == target) used[card] = true; //http://code.google.com/p/wagic/issues/detail?id=76 - if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost() >= 1) - { + if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost() == 1) + {//ai can't use cards which produce more then 1 converted while using the old pMana method. result->add(amp->output); used[card] = true; } } } + return result; +} + +vector AIPlayer::canPayMana(MTGCardInstance * target,ManaCost * cost) +{ + if(!cost || (cost && !cost->getConvertedCost())) + return vector(); + ManaCost * result = NEW ManaCost(); + GameObserver * g = GameObserver::GetInstance(); + map used; + vectorpayments = vector(); if (this->getManaPool()->getConvertedCost()) { - //adding the current manapool if any, to the potential mana Ai can use. + //adding the current manapool if any. result->add(this->getManaPool()); } - return result; + int needColorConverted = cost->getConvertedCost()-int(cost->getCost(0)+cost->getCost(7)); + int fullColor = 0; + for (size_t i = 0; i < g->mLayers->actionLayer()->manaObjects.size(); i++) + { + MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->manaObjects[i]); + AManaProducer * amp = dynamic_cast (a); + if(amp && (amp->getCost() && amp->getCost()->extraCosts && !amp->getCost()->extraCosts->canPay())) + continue; + if(fullColor == needColorConverted) + { + if(cost->hasColor(0) && amp)//find colorless after color mana. + { + if(result->canAfford(cost)) + continue; + if (canHandleCost(amp)) + { + MTGCardInstance * card = amp->source; + if (card == target) + used[card] = true; //http://code.google.com/p/wagic/issues/detail?id=76 + if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost() >= 1) + { + if(!(result->canAfford(cost)))//if we got to this point we should be filling colorless mana requirements. + { + payments.push_back(amp); + result->add(amp->output); + used[card] = true; + } + } + } + } + i = g->mLayers->actionLayer()->manaObjects.size(); + break; + } + GenericActivatedAbility * gmp = dynamic_cast(a); + if(gmp && canHandleCost(gmp)) + { + //for each mana producers. + AForeach * fmp = dynamic_cast(gmp->ability); + if(fmp) + { + amp = dynamic_cast (fmp->ability); + if(amp) + { + MTGCardInstance * fecard = gmp->source; + if (fecard == target) + used[fecard] = true; //http://code.google.com/p/wagic/issues/detail?id=76 + if(gmp->getCost() && gmp->getCost()->getConvertedCost() > 0) + {//ai stil can't use cabal coffers and mana abilities which require mana payments effectively; + used[fecard]; + continue; + } + if (!used[fecard] && gmp->isReactingToClick(fecard) && amp->output->getConvertedCost() >= 1 && (cost->getConvertedCost() > 1 || cost->hasX()))//wasteful to tap a potential big mana source for a single mana. + { + int outPut = fmp->checkActivation(); + for(int k = 0;k < outPut;k++) + result->add(amp->output); + payments.push_back(gmp); + used[fecard] = true; + } + } + } + } + else if (amp && canHandleCost(amp) && amp->isReactingToClick(amp->source,amp->getCost())) + { + for (int k = Constants::MTG_NB_COLORS-1; k > 0 ; k--)//go backwards. + { + if (cost->hasColor(k) && amp->output->hasColor(k) && result->getCost(k) < cost->getCost(k)) + { + MTGCardInstance * card = amp->source; + if (card == target) + used[card] = true; //http://code.google.com/p/wagic/issues/detail?id=76 + if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost() >= 1) + { + ManaCost * check = NEW ManaCost(); + check->init(); + check->add(k,cost->getCost(k)); + ManaCost * checkResult = NEW ManaCost(); + checkResult->init(); + checkResult->add(k,result->getCost(k)); + if(!(checkResult->canAfford(check))) + { + payments.push_back(amp); + result->add(k,amp->output->getCost(k)); + used[card] = true; + fullColor++; + } + SAFE_DELETE(check); + SAFE_DELETE(checkResult); + } + } + } + } + } + ManaCostHybrid * hybridCost; + int hyb; + hyb = 0; + hybridCost = cost->getHybridCost(0); + if(hybridCost) + { + while ((hybridCost = cost->getHybridCost(hyb)) != NULL) + { + //here we try to find one of the colors in the hybrid cost, it is done 1 at a time unfortunately + //{rw}{ub} would be 2 runs of this.90% of the time ai finds it's hybrid in pMana check. + bool foundColor1 = false; + bool foundColor2 = false; + for (size_t i = 0; i < g->mLayers->actionLayer()->manaObjects.size(); i++) + { + MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->manaObjects[i]); + AManaProducer * amp = dynamic_cast (a); + if (amp && canHandleCost(amp)) + { + foundColor1 = amp->output->hasColor(hybridCost->color1)?true:false; + foundColor2 = amp->output->hasColor(hybridCost->color2)?true:false; + if ((foundColor1 && result->getCost(hybridCost->color1) < hybridCost->value1)|| + (foundColor2 && result->getCost(hybridCost->color2) < hybridCost->value2)) + { + MTGCardInstance * card = amp->source; + if (card == target) + used[card] = true; //http://code.google.com/p/wagic/issues/detail?id=76 + if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost() >= 1) + { + ManaCost * check = NEW ManaCost(); + check->init(); + check->add(foundColor1?hybridCost->color1:hybridCost->color2,foundColor1?hybridCost->value1:hybridCost->value2); + ManaCost * checkResult = NEW ManaCost(); + checkResult->init(); + checkResult->add(foundColor1?hybridCost->color1:hybridCost->color2,result->getCost(foundColor1?hybridCost->color1:hybridCost->color2)); + if(((foundColor1 && !foundColor2)||(!foundColor1 && foundColor2)) &&!(checkResult->canAfford(check))) + { + payments.push_back(amp); + result->add(foundColor1?hybridCost->color1:hybridCost->color2,amp->output->getCost(foundColor1?hybridCost->color1:hybridCost->color2)); + used[card] = true; + fullColor++; + } + SAFE_DELETE(check); + SAFE_DELETE(checkResult); + } + } + } + } + hyb++; + } + } + else if(!hybridCost && result->getConvertedCost()) + { + ManaCost * check = NEW ManaCost(); + ManaCost * checkResult = NEW ManaCost(); + check->init(); + checkResult->init(); + for (int k = 1; k < Constants::MTG_NB_COLORS; k++) + { + check->add(k,cost->getCost(k)); + checkResult->add(k,result->getCost(k)); + if(!(checkResult->canAfford(check))) + { + SAFE_DELETE(check); + SAFE_DELETE(checkResult); + SAFE_DELETE(result); + payments.clear(); + return payments;//we didn't meet one of the color cost requirements. + } + } + SAFE_DELETE(check); + SAFE_DELETE(checkResult); + } + if(cost->hasX()) + { + //if we decided to play an "x" ability/card, lets go all out, these effects tend to be game winners. + //add the rest of the mana. + for (size_t i = 0; i < g->mLayers->actionLayer()->manaObjects.size(); i++) + { + MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->manaObjects[i]); + AManaProducer * amp = dynamic_cast (a); + if (amp && canHandleCost(amp)) + { + if (!used[amp->source] && amp->isReactingToClick(amp->source) && amp->output->getConvertedCost() >= 1) + { + payments.push_back(amp); + } + } + } + } + if(!result->canAfford(cost)) + payments.clear(); + SAFE_DELETE(result); + return payments; +} + +vector AIPlayer::canPaySunBurst(ManaCost * cost) +{ + //in canPaySunburst we try to fill the cost with one of each color we can produce, + //note it is still possible to use lotus petal for it's first mana ability and not later for a final color + //a search of true sunburst would cause the game to come to a crawl, trust me, this is the "fast" method for sunburst :) + ManaCost * result = NEW ManaCost(); + GameObserver * g = GameObserver::GetInstance(); + map used; + vectorpayments = vector(); + int needColorConverted = 6; + int fullColor = 0; + result->add(this->getManaPool()); + for (size_t i = 0; i < g->mLayers->actionLayer()->manaObjects.size(); i++) + { + //Make sure we can use the ability + if(fullColor == needColorConverted || fullColor == cost->getConvertedCost()) + { + i = g->mLayers->actionLayer()->manaObjects.size(); + break; + } + MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->manaObjects[i]); + AManaProducer * amp = dynamic_cast (a); + if(amp && amp->getCost() && amp->getCost()->extraCosts && !amp->getCost()->extraCosts->canPay()) + continue;//pentid prism, has no cost but contains a counter cost, without this check ai will think it can still use this mana. + if (amp && canHandleCost(amp) && amp->isReactingToClick(amp->source,amp->getCost())) + { + for (int k = Constants::MTG_NB_COLORS-1; k > 0 ; k--) + { + if (amp->output->hasColor(k) && result->getCost(k) < 1 && result->getConvertedCost() < cost->getConvertedCost()) + { + MTGCardInstance * card = amp->source; + if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost() >= 1) + { + ManaCost * check = NEW ManaCost(); + check->init(); + check->add(k,1); + ManaCost * checkResult = NEW ManaCost(); + checkResult->init(); + checkResult->add(k,result->getCost(k)); + if(!(checkResult->canAfford(check))) + { + payments.push_back(amp); + result->add(k,amp->output->getCost(k)); + used[card] = true; + fullColor++; + } + SAFE_DELETE(check); + SAFE_DELETE(checkResult); + } + } + } + } + } + for(int i = fullColor;i < cost->getConvertedCost();i++) + { + for (size_t i = 0; i < g->mLayers->actionLayer()->manaObjects.size(); i++) + { + MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->manaObjects[i]); + AManaProducer * amp = dynamic_cast (a); + if (amp && canHandleCost(amp)) + { + MTGCardInstance * card = amp->source; + if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost() >= 1) + { + if(!(result->canAfford(cost)))//if we got to this point we should be filling colorless mana requirements. + { + payments.push_back(amp); + result->add(amp->output); + used[card] = true; + } + } + } + } + } + if(!result->canAfford(cost)) + payments.clear(); + SAFE_DELETE(result); + return payments; } int AIPlayer::getEfficiency(AIAction * action) @@ -258,7 +664,6 @@ int AIAction::getEfficiency() Player * p = g->currentlyActing(); if (s->has(ability)) return 0; - MTGAbility * a = AbilityFactory::getCoreAbility(ability); if (!a) @@ -274,26 +679,38 @@ int AIAction::getEfficiency() case MTGAbility::DAMAGER: { AADamager * aad = (AADamager *) a; - if (!target) + MTGCardInstance * dTarget = (MTGCardInstance*)target; + if(!target && !playerAbilityTarget) { - Targetable * _t = aad->getTarget(); - if (_t == p->opponent()) - efficiency = 90; + dTarget = (MTGCardInstance*)aad->getTarget(); + if(dTarget)//no action target, but damage has a target...this is most likely a card like pestilence. + { + efficiency = int(p->opponent()->game->battlefield->countByType("creature") - p->game->battlefield->countByType("creature")) * 25 % 100; + break; + } + } + if(playerAbilityTarget || (target && target->typeAsTarget() == TARGET_PLAYER)) + { + TargetChooser * checkT = g->getCurrentTargetChooser(); + int otherTargets = 0; + if(checkT) otherTargets = checkT->countValidTargets(); + if (playerAbilityTarget == p->opponent()||(target && (Player*)target == p->opponent())) + efficiency = 90 - otherTargets; else efficiency = 0; break; } - if (p == target->controller()) + if(p == target->controller()) { efficiency = 0; } - else if (aad->getDamage() >= target->toughness) + else if (aad->getDamage() >= dTarget->toughness) { efficiency = 100; } - else if (target->toughness) + else if (dTarget->toughness) { - efficiency = (50 * aad->getDamage()) / target->toughness; + efficiency = (50 * aad->getDamage()) / dTarget->toughness; } else { @@ -337,7 +754,7 @@ int AIAction::getEfficiency() NeedPreventing = false; if (g->getCurrentGamePhase() == Constants::MTG_PHASE_COMBATBLOCKERS) { - if(!target->getNextOpponent()->typeAsTarget() == TARGET_CARD) + if(target->getNextOpponent() && !target->getNextOpponent()->typeAsTarget() == TARGET_CARD) break; if ((target->defenser || target->blockers.size()) && target->preventable < target->getNextOpponent()->power) NeedPreventing = true; @@ -457,6 +874,8 @@ int AIAction::getEfficiency() } if(cc->maxNb && _target->counters && _target->counters->hasCounter(cc->power,cc->toughness)->nb >= cc->maxNb) efficiency = 0; + if(a->target == a->source && a->getCost() && a->getCost()->hasX()) + efficiency -= 10 * int(p->game->hand->cards.size()); } } break; @@ -541,19 +960,21 @@ int AIAction::getEfficiency() efficiency = 0; //trying to encourage Ai to use his foreach manaproducers in first main - if (a->naType == MTGAbility::MANA_PRODUCER && (g->getCurrentGamePhase() == Constants::MTG_PHASE_FIRSTMAIN + if (a->getCost() && a->getCost()->getConvertedCost() && a->aType == MTGAbility::MANA_PRODUCER && (g->getCurrentGamePhase() == Constants::MTG_PHASE_FIRSTMAIN || g->getCurrentGamePhase() == Constants::MTG_PHASE_SECONDMAIN) && _target->controller()->game->hand->nb_cards > 0) { + efficiency = 0; for (int i = Constants::MTG_NB_COLORS - 1; i > 0; i--) { if ((p->game->hand->hasColor(i) || p->game->hand->hasColor(0)) - && (dynamic_cast ((dynamic_cast (a)->ability))->output->hasColor(i))) + && (dynamic_cast (a)->output->hasColor(i))) { + efficiency = 100; } } - if (p->game->hand->hasX()) + if (a->getCost() && a->getCost()->getConvertedCost() && p->game->hand->hasX()) efficiency = 100; } @@ -633,25 +1054,20 @@ int AIAction::getEfficiency() case MTGAbility::UNTAPPER: //untap things that Ai owns and are tapped. - { - efficiency = 0; - if (!target && !dynamic_cast (a)) - break; + { + efficiency = 0; + if (!target && !dynamic_cast (a)) + break; if(dynamic_cast (a) && !target) { - target = a->source; + target = a->source; } - - if (target->isTapped() && target->controller() == p &&!target->isCreature()) + if (target->isTapped() && target->controller() == p) { - efficiency = 100; + target->isCreature()?efficiency = (20 * target->DangerRanking()):efficiency = 100; } - if (target->isTapped() && target->controller() == p && target->isCreature()) - { - efficiency = (20 * target->DangerRanking()); - } - break; - } + break; + } case MTGAbility::TAPPER: //tap things the player owns and that are untapped. @@ -682,7 +1098,7 @@ int AIAction::getEfficiency() AbilityFactory af; int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); - if ((suggestion == BAKA_EFFECT_BAD && _t == p) || (suggestion == BAKA_EFFECT_GOOD && _t == p)) + if ((suggestion == BAKA_EFFECT_BAD && _t == p) || (suggestion == BAKA_EFFECT_GOOD && _t != p)) { efficiency = 0; } @@ -711,28 +1127,58 @@ int AIAction::getEfficiency() case MTGAbility::CLONING: { efficiency = 0; - if (p == target->controller()) + if(!target) + efficiency = 100;//a clone ability with no target is an "clone all(" + else if (p == target->controller()) { efficiency = 20 * target->DangerRanking(); } break; } - case MTGAbility::STANDARD_FIZZLER: - { - Interruptible * action = g->mLayers->stackLayer()->getAt(-1); - Spell * spell = (Spell *) action; - Player * lastStackActionController = NULL; - if(spell && spell->type == ACTION_SPELL) - lastStackActionController = spell->source->controller(); - if(p != target->controller() && lastStackActionController && lastStackActionController != p) - efficiency = 60;//we want ai to fizzle at higher than "unknown" ability %. - break; - } + case MTGAbility::STANDARD_FIZZLER: + { + if(target) + { + Interruptible * action = g->mLayers->stackLayer()->getAt(-1); + Spell * spell = (Spell *) action; + Player * lastStackActionController = NULL; + if(spell && spell->type == ACTION_SPELL) + lastStackActionController = spell->source->controller(); + if(p != target->controller() && lastStackActionController && lastStackActionController != p) + efficiency = 60;//we want ai to fizzle at higher than "unknown" ability %. + } + break; + } default: if (target) { AbilityFactory af; int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY,NULL,target); + if (AADynamic * ady = dynamic_cast(a)) + { + if(ady) + { + //not going into massive detail with this ability, its far to complex, just going to give it a general idea. + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_COUNTERSONEONE) + suggestion = BAKA_EFFECT_GOOD; + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_DEPLETE) + suggestion = BAKA_EFFECT_BAD; + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_DRAW) + suggestion = BAKA_EFFECT_GOOD; + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_LIFEGAIN) + suggestion = BAKA_EFFECT_GOOD; + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_LIFELOSS) + suggestion = BAKA_EFFECT_BAD; + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_PUMPBOTH) + suggestion = BAKA_EFFECT_GOOD; + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_PUMPTOUGHNESS) + suggestion = BAKA_EFFECT_GOOD; + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_PUMPPOWER) + suggestion = BAKA_EFFECT_GOOD; + if(ady->effect == ady->DYNAMIC_ABILITY_EFFECT_STRIKE) + suggestion = BAKA_EFFECT_BAD; + } + } if ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) || (suggestion == BAKA_EFFECT_GOOD && p != target->controller())) { @@ -741,16 +1187,72 @@ int AIAction::getEfficiency() else { //without a base to start with Wrand % 5 almost always returns 0. - efficiency = 10 + WRand() % 5; //Small percentage of chance for unknown abilities + efficiency = 10 + (WRand() % 20); //Small percentage of chance for unknown abilities } } else { - efficiency = 10 + WRand() % 10; + efficiency = 10 + (WRand() % 30); } break; } - + if (AAMover * aam = dynamic_cast(a)) + { + MTGGameZone * z = aam->destinationZone(target); + if (target) + { + if (target->currentZone == p->game->library|| target->currentZone == p->opponent()->game->inPlay||target->currentZone == p->game->hand) + { + if (z == p->game->hand || z == p->game->inPlay || z == target->controller()->game->hand) + efficiency = 100; + } + else if( target->currentZone == p->game->inPlay && (MTGCardInstance*)target == a->source) + { + if (z == p->game->hand) + efficiency = 10 + (WRand() % 10);//random chance to bounce their own card; + } + else + { + efficiency = 10 + (WRand() % 5); + } + } + } + if (AAProliferate * aap = dynamic_cast(a)) + { + if (aap && target && target->typeAsTarget() == TARGET_PLAYER && (Player*)target != p) + { + efficiency = 60;//ai determines if the counters are good or bad on menu check. + } + else if (aap) + efficiency = 90; + } + if (AAAlterPoison * aaap = dynamic_cast(a)) + { + if (aaap && target && target->typeAsTarget() == TARGET_PLAYER && (Player*)target != p) + { + efficiency = 90; + } + } + if (ATokenCreator * atc = dynamic_cast(a)) + { + efficiency = 80; + if(atc && atc->name.length() && atc->sabilities.length() && atc->types.size() && p->game->inPlay->findByName(atc->name)) + { + list::iterator it; + for (it = atc->types.begin(); it != atc->types.end(); it++) + { + if(*it == Subtypes::TYPE_LEGENDARY)//ai please stop killing voja!!! :P + efficiency = 0; + } + } + } + MayAbility * may = dynamic_cast(ability); + if (!efficiency && may) + { + AIPlayer * chk = (AIPlayer*)p; + if(may->ability->getActionTc() && chk->chooseTarget(may->ability->getActionTc(),NULL,NULL,true)) + efficiency = 50 + (WRand() % 50); + } if (p->game->hand->nb_cards == 0) efficiency = (int) ((float) efficiency * 1.3); //increase chance of using ability if hand is empty ManaCost * cost = ability->getCost(); @@ -775,32 +1277,87 @@ int AIAction::getEfficiency() int AIPlayer::createAbilityTargets(MTGAbility * a, MTGCardInstance * c, RankingContainer& ranking) { - if (!a->tc) + if (!a->getActionTc()) { AIAction aiAction(a, c, NULL); ranking[aiAction] = 1; return 1; } + vectorpotentialTargets; GameObserver * g = GameObserver::GetInstance(); for (int i = 0; i < 2; i++) { Player * p = g->players[i]; - MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay,p->game->stack }; + MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay,p->game->stack }; + if(a->getActionTc()->canTarget((Targetable*)p)) + { + if(a->getActionTc()->maxtargets == 1) + { + AIAction aiAction(a, p, c); + ranking[aiAction] = 1; + } + else + potentialTargets.push_back(p); + } for (int j = 0; j < 5; j++) { MTGGameZone * zone = playerZones[j]; for (int k = 0; k < zone->nb_cards; k++) { MTGCardInstance * t = zone->cards[k]; - if (a->tc->canTarget(t)) + if (a->getActionTc()->canTarget(t)) { - - AIAction aiAction(a, c, t); - ranking[aiAction] = 1; + if(a->getActionTc()->maxtargets == 1) + { + AIAction aiAction(a, c, t); + ranking[aiAction] = 1; + } + else + { + potentialTargets.push_back(t); + } } } } } + vectorrealTargets; + if(a->getActionTc()->maxtargets != 1) + { + if(a->getActionTc()->targets.size() && a->getActionTc()->attemptsToFill > 4) + { + a->getActionTc()->done = true; + return 0; + } + int targetThis = 0; + while(potentialTargets.size()) + { + AIAction * check = NULL; + + MTGCardInstance * targeting = dynamic_cast(potentialTargets[0]); + if(targeting && targeting->typeAsTarget() == TARGET_CARD) + check = NEW AIAction(a,c,targeting); + + Player * ptargeting = dynamic_cast(potentialTargets[0]); + if(ptargeting && ptargeting->typeAsTarget() == TARGET_PLAYER) + check = NEW AIAction(a,ptargeting,c); + + targetThis = getEfficiency(check); + if(targetThis && ptargeting && ptargeting->typeAsTarget() == TARGET_PLAYER) + { + AIAction aiAction(a,ptargeting,c); + ranking[aiAction] = 1; + } + if(targetThis) + realTargets.push_back(potentialTargets[0]); + potentialTargets.erase(potentialTargets.begin()); + SAFE_DELETE(check); + } + if(!realTargets.size() || (int(realTargets.size()) < a->getActionTc()->maxtargets && a->getActionTc()->targetMin)) + return 0; + AIAction aiAction(a, c,realTargets); + aiAction.target = (MTGCardInstance*)realTargets[0]; + ranking[aiAction] = 1; + } return 1; } @@ -810,14 +1367,14 @@ int AIPlayer::selectHintAbility() return 0; ManaCost * totalPotentialMana = getPotentialMana(); - + totalPotentialMana->add(this->getManaPool()); AIAction * action = hints->suggestAbility(totalPotentialMana); if (action && ((WRand() % 100) < 95)) //95% chance { if (!clickstream.size()) { DebugTrace("AIPlayer:Using Activated ability"); - if (tapLandsForMana(action->ability->getCost(), action->click)) + if (payTheManaCost(action->ability->getCost(), action->click)) { clickstream.push(action); SAFE_DELETE(totalPotentialMana); @@ -852,12 +1409,22 @@ int AIPlayer::selectAbility() findingAbility = false;//ok to start looking again. return 1; } - + GameObserver * go = GameObserver::GetInstance(); + if(go->mLayers->stackLayer()->lastActionController == this) + { + //this is here for 2 reasons, MTG rules state that priority is passed with each action. + //without this ai is able to chain cast {t}:damage:1 target(creature) from everything it can all at once. + //this not only is illegal but cause ai to waste abilities ei:all damage:1 on a single 1/1 creature. + findingAbility = false; + return 1; + } RankingContainer ranking; list::iterator it; GameObserver * g = GameObserver::GetInstance(); + vectorabilityPayment = vector(); //This loop is extrmely inefficient. TODO: optimize! ManaCost * totalPotentialMana = getPotentialMana(); + totalPotentialMana->add(this->getManaPool()); for (size_t i = 1; i < g->mLayers->actionLayer()->mObjects.size(); i++) { //0 is not a mtgability...hackish MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->mObjects[i]); @@ -868,17 +1435,43 @@ int AIPlayer::selectAbility() for (int j = 0; j < game->inPlay->nb_cards; j++) { MTGCardInstance * card = game->inPlay->cards[j]; - if (a->isReactingToClick(card, totalPotentialMana)) + if(a->getCost() && !a->isReactingToClick(card, totalPotentialMana))//for preformence reason only look for specific mana if the payment couldnt be made with potential. + { + abilityPayment = vector(); + abilityPayment = canPayMana(card,a->getCost()); + } + if (a->isReactingToClick(card, totalPotentialMana) || abilityPayment.size()) { //This test is to avoid the huge call to getPotentialManaCost after that - ManaCost * pMana = getPotentialMana(card); - if (a->isReactingToClick(card, pMana)) - createAbilityTargets(a, card, ranking); - delete (pMana); + if(a->getCost() && a->getCost()->hasX() && totalPotentialMana->getConvertedCost() < a->getCost()->getConvertedCost()+1) + continue; + //don't even bother to play an ability with {x} if you can't even afford x=1. + ManaCost * fullPayment = NULL; + if (abilityPayment.size()) + { + fullPayment = NEW ManaCost(); + fullPayment->init(); + for(int ch = 0; ch < int(abilityPayment.size());ch++) + { + AManaProducer * ampp = dynamic_cast (abilityPayment[ch]); + if(ampp) + fullPayment->add(ampp->output); + } + if (fullPayment && a->isReactingToClick(card, fullPayment)) + createAbilityTargets(a, card, ranking); + delete fullPayment; + } + else + { + ManaCost * pMana = getPotentialMana(card); + pMana->add(this->getManaPool()); + if (a->isReactingToClick(card, pMana)) + createAbilityTargets(a, card, ranking); + delete (pMana); + } } } } delete totalPotentialMana; - if (ranking.size()) { AIAction action = ranking.begin()->first; @@ -886,18 +1479,25 @@ int AIPlayer::selectAbility() if (!forceBestAbilityUse) chance = 1 + WRand() % 100; int actionScore = action.getEfficiency(); + if(action.ability->getCost() && action.ability->getCost()->hasX() && this->game->hand->cards.size()) + actionScore = actionScore/int(this->game->hand->cards.size());//reduce chance for "x" abilities if cards are in hand. if (actionScore >= chance) { if (!clickstream.size()) { + if (abilityPayment.size()) + { + DebugTrace(" Ai knows exactly what mana to use for this ability."); + } DebugTrace("AIPlayer:Using Activated ability"); - if (tapLandsForMana(action.ability->getCost(), action.click)) + if (payTheManaCost(action.ability->getCost(), action.click,abilityPayment)) clickstream.push(NEW AIAction(action)); } } } findingAbility = false;//ok to start looking again. + abilityPayment.clear(); return 1; } @@ -925,126 +1525,183 @@ int AIPlayer::effectBadOrGood(MTGCardInstance * card, int mode, TargetChooser * return BAKA_EFFECT_DONTKNOW; } -int AIPlayer::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCardInstance * Choosencard) +int AIPlayer::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCardInstance * Choosencard,bool checkOnly) { vector potentialTargets; TargetChooser * tc = _tc; int nbtargets = 0; GameObserver * gameObs = GameObserver::GetInstance(); - int checkOnly = 0; - if (tc) - { - if(!Choosencard) - checkOnly = 1; - } - else + if (!(gameObs->currentlyActing() == this)) + return 0; + if (!tc) { tc = gameObs->getCurrentTargetChooser(); } - if (!tc) + if (!tc || !tc->source || tc->maxtargets < 1) return 0; - - //Make sure we own the decision to choose the targets - assert(tc->source->controller() == this); - if (tc->source->controller() != this) - { - gameObs->currentActionPlayer = tc->source->controller(); + assert(tc); + if(!checkOnly && tc->maxtargets > 1) + { + tc->initTargets();//just incase.... + potentialTargets.clear(); + } + //Make sure we own the decision to choose the targets + assert(tc->Owner == gameObs->currentlyActing()); + if (tc && tc->Owner != gameObs->currentlyActing()) + { + gameObs->currentActionPlayer = tc->Owner; //this is a hack, but if we hit this condition we are locked in a infinate loop //so lets give the tc to its owner //todo:find the root cause of this. DebugTrace("AIPLAYER: Error, was asked to chose targets but I don't own the source of the targetController\n"); return 0; } - - tc->initTargets(); //cleanup the targetchooser just in case. - if (!(gameObs->currentlyActing() == this)) - return 0; Player * target = forceTarget; - + int playerTargetedZone = 1; if (!target) { target = this; int cardEffect = effectBadOrGood(tc->source, MODE_TARGET, tc); + if(tc->belongsToAbility.size()) + { + AbilityFactory af; + MTGAbility * withoutGuessing = af.parseMagicLine(tc->belongsToAbility,NULL,NULL,tc->source); + cardEffect = af.abilityEfficiency(withoutGuessing,this,MODE_TARGET,tc,NULL); + delete withoutGuessing; + } if (cardEffect != BAKA_EFFECT_GOOD) { target = this->opponent(); } + if(dynamic_cast (tc)) + playerTargetedZone = 2; } + while(playerTargetedZone) + { + if (!tc->alreadyHasTarget(target) && tc->canTarget(target) && nbtargets < 50) + { + for (int i = 0; i < 3; i++) + { //Increase probability to target a player when this is possible + potentialTargets.push_back(target); + nbtargets++; + } + } + MTGPlayerCards * playerZones = target->game; + MTGGameZone * zones[] = { playerZones->hand, playerZones->library, playerZones->inPlay, playerZones->graveyard,playerZones->stack }; + for (int j = 0; j < 5; j++) + { + MTGGameZone * zone = zones[j]; + for (int k = 0; k < zone->nb_cards; k++) + { + MTGCardInstance * card = zone->cards[k]; + if (!tc->alreadyHasTarget(card) && tc->canTarget(card) && nbtargets < 50) + { + int multiplier = 1; + if (getStats() && getStats()->isInTop(card, 10)) + { + multiplier++; + if (getStats()->isInTop(card, 5)) + { + multiplier++; + if (getStats()->isInTop(card, 3)) + { + multiplier++; + } + } + } + for (int l = 0; l < multiplier; l++) + { + if(tc->maxtargets != 1 && tc->belongsToAbility.size()) + { + AbilityFactory af; + MTGAbility * withoutGuessing = af.parseMagicLine(tc->belongsToAbility,NULL,NULL,tc->source); + AIAction * effCheck = NEW AIAction(withoutGuessing,(MTGCardInstance*)tc->source,card); + if(effCheck->getEfficiency()) + { + potentialTargets.push_back(card); + nbtargets++; + } + SAFE_DELETE(effCheck); + SAFE_DELETE(withoutGuessing); + } + else + { + potentialTargets.push_back(card); + nbtargets++; + } + } + } + } + } + if(playerTargetedZone > 1) + target = target->opponent(); + playerTargetedZone--; + } + if (nbtargets||potentialTargets.size()) + { + if((!forceTarget && checkOnly)||(tc->maxtargets != 1)) + { + sort(potentialTargets.begin(), potentialTargets.end()); + potentialTargets.erase(std::unique(potentialTargets.begin(), potentialTargets.end()), potentialTargets.end()); + //checking actual amount of unique targets. + //multitargeting can not function the same as single target, + //a second click on the same target causes it to detoggle target, which can lead to ai lockdowns. + if(!checkOnly) + return clickMultiTarget(tc,potentialTargets); + return int(potentialTargets.size());//return the actual amount of targets ai will atempt to select. + } + return clickSingleTarget(tc,potentialTargets,nbtargets,Choosencard); + //click single target contains nbtargets to keep it as it was previously designed + //shoving 100 targets into potential, then selecting one of them at random. + } + if(checkOnly)return 0;//it wasn't an error if we couldn't find a target while checkonly + //Couldn't find any valid target, + //usually that's because we played a card that has bad side effects (ex: when X comes into play, return target land you own to your hand) + //so we try again to choose a target in the other player's field... + int cancel = gameObs->cancelCurrentAction(); + if (!cancel && !forceTarget) + return chooseTarget(tc, target->opponent(),NULL,checkOnly); + //ERROR!!! + DebugTrace("AIPLAYER: ERROR! AI needs to choose a target but can't decide!!!"); + return 1; +} - if (!tc->alreadyHasTarget(target) && tc->canTarget(target) && nbtargets < 50) +int AIPlayer::clickMultiTarget(TargetChooser * tc,vector& potentialTargets) +{ + bool sourceIncluded = false; + for(int f = 0;f < int(potentialTargets.size());f++) { - for (int i = 0; i < 3; i++) - { //Increase probability to target a player when this is possible - potentialTargets.push_back(target); - nbtargets++; - } - if (checkOnly) - return 1; - } - MTGPlayerCards * playerZones = target->game; - MTGGameZone * zones[] = { playerZones->hand, playerZones->library, playerZones->inPlay, playerZones->graveyard }; - for (int j = 0; j < 4; j++) - { - MTGGameZone * zone = zones[j]; - for (int k = 0; k < zone->nb_cards; k++) + MTGCardInstance * card = ((MTGCardInstance *) potentialTargets[f]); + Player * pTarget = (Player*)potentialTargets[f]; + if(card && card == (MTGCardInstance*)tc->source)//if the source is part of the targetting deal with it first. second click is "confirming click". { - MTGCardInstance * card = zone->cards[k]; - if (!tc->alreadyHasTarget(card) && tc->canTarget(card) && nbtargets < 50) - { - if (checkOnly) - return 1; - int multiplier = 1; - if (getStats() && getStats()->isInTop(card, 10)) - { - multiplier++; - if (getStats()->isInTop(card, 5)) - { - multiplier++; - if (getStats()->isInTop(card, 3)) - { - multiplier++; - } - } - } - for (int l = 0; l < multiplier; l++) - { - potentialTargets.push_back(card); - nbtargets++; - } - } + clickstream.push(NEW AIAction(card)); + DebugTrace("Ai clicked source as a target: " << (card ? card->name : "None" ) << endl ); + potentialTargets.erase(potentialTargets.begin() + f); + sourceIncluded = true; } - //targetting the stack - zone = playerZones->stack; - for (int k = 0; k < zone->nb_cards; k++) + if(pTarget && pTarget->typeAsTarget() == TARGET_PLAYER) { - MTGCardInstance* card = zone->cards[k]; - if (!tc->alreadyHasTarget(card) && tc->canTarget(card) && nbtargets < 50) - { - if (checkOnly) - return 1; - int multiplier = 1; - if (getStats() && getStats()->isInTop(card, 10)) - { - multiplier++; - if (getStats()->isInTop(card, 5)) - { - multiplier++; - if (getStats()->isInTop(card, 3)) - { - multiplier++; - } - } - } - for (int l = 0; l < multiplier; l++) - { - potentialTargets.push_back(card); - nbtargets++; - } - } + clickstream.push(NEW AIAction(pTarget)); + DebugTrace("Ai clicked Player as a target"); + potentialTargets.erase(potentialTargets.begin() + f); } } - if (nbtargets) + std::random_shuffle(potentialTargets.begin(), potentialTargets.end()); + if(potentialTargets.size()) + clickstream.push(NEW AIAction(NULL,tc->source,potentialTargets)); + while(clickstream.size()) { + AIAction * action = clickstream.front(); + action->Act(); + SAFE_DELETE(action); + clickstream.pop(); + } + return 1; +} + +int AIPlayer::clickSingleTarget(TargetChooser * tc,vector& potentialTargets ,int nbtargets/*zeth:legacy, not my design*/,MTGCardInstance * Choosencard) +{ int i = WRand() % nbtargets; int type = potentialTargets[i]->typeAsTarget(); switch (type) @@ -1057,34 +1714,16 @@ int AIPlayer::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCardInst clickstream.push(NEW AIAction(card)); Choosencard = card; } - //can't be 100% positive that this wont have an adverse side-effect - //hoping this fills a edge case where ai will keep trying to choose a target for a card which it already has a target for. - return 1; break; } case TARGET_PLAYER: { Player * player = ((Player *) potentialTargets[i]); clickstream.push(NEW AIAction(player)); - return 1; break; } } - } - //Couldn't find any valid target, - //usually that's because we played a card that has bad side effects (ex: when X comes into play, return target land you own to your hand) - //so we try again to choose a target in the other player's field... - if (checkOnly) - { - return 0; - } - int cancel = gameObs->cancelCurrentAction(); - if (!cancel && !forceTarget) - return chooseTarget(_tc, target->opponent()); - - //ERROR!!! - DebugTrace("AIPLAYER: ERROR! AI needs to choose a target but can't decide!!!"); - return 0; + return 1; } int AIPlayer::getCreaturesInfo(Player * player, int neededInfo, int untapMode, int canAttack) @@ -1166,17 +1805,6 @@ int AIPlayer::chooseBlockers() //Should not block during my own turn... if (g->currentPlayer == this) return 0; - //Should not run this if I'm not the player with priority - if (g->currentActionPlayer != this) - return 0; - //I am interrupting, why would I be choosing blockers now? - if(g->isInterrupting == this) - return 0; - //ai should not be allowed to run this if it is not legally allowed to do so - //this fixes a bug where ai would try to use this as an interupt - //when ai is given priority to select blockers it is allowed to run this as normal. - //but as soon as its selected it blockers and proirity switch back to other player - //kick the ai out of this function. map opponentsToughness; int opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER); @@ -1276,6 +1904,7 @@ int AIPlayer::chooseBlockers() } } } + selectAbility(); return 1; } @@ -1393,6 +2022,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty cd.init(); cd.setType(type); card = NULL; + gotPayments = vector(); while ((card = cd.nextmatch(game->hand, card))) { if (!CanHandleCost(card->getManaCost())) @@ -1414,19 +2044,47 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty int currentCost = card->getManaCost()->getConvertedCost(); int hasX = card->getManaCost()->hasX(); - if ((currentCost > maxCost || hasX) && pMana->canAfford(card->getManaCost())) + gotPayments.clear(); + if(!pMana->canAfford(card->getManaCost())) + gotPayments = canPayMana(card,card->getManaCost()); + //for preformence reason we only look for specific mana if the payment couldn't be made with pmana. + if ((currentCost > maxCost || hasX) && (gotPayments.size() || pMana->canAfford(card->getManaCost()))) { TargetChooserFactory tcf; TargetChooser * tc = tcf.createTargetChooser(card); int shouldPlayPercentage = 10; if (tc) { - int hasTarget = (chooseTarget(tc)); - if(tc) - delete tc; - if (!hasTarget) + int hasTarget = chooseTarget(tc,NULL,NULL,true); + if( + (tc->maxtargets > hasTarget && tc->maxtargets > 1 && !tc->targetMin && tc->maxtargets != 1000) ||//target=<3>creature + (tc->maxtargets == TargetChooser::UNLITMITED_TARGETS && hasTarget < 1)//target=creatures + ) + hasTarget = 0; + if (!hasTarget)//single target covered here. + { + SAFE_DELETE(tc); continue; + } shouldPlayPercentage = 90; + if(tc->targetMin && hasTarget < tc->maxtargets) + shouldPlayPercentage = 0; + if(tc->maxtargets > 1 && tc->maxtargets != TargetChooser::UNLITMITED_TARGETS && hasTarget <= tc->maxtargets) + { + int maxA = hasTarget-tc->maxtargets; + shouldPlayPercentage += (10*maxA);//reduce the chances of playing multitarget if we are not above max targets. + } + if(tc->maxtargets == TargetChooser::UNLITMITED_TARGETS) + { + shouldPlayPercentage = 40 + (10*hasTarget); + int totalCost = pMana->getConvertedCost()-currentCost; + int totalTargets = hasTarget+hasTarget; + if(hasX && totalCost <= totalTargets)// {x} spell with unlimited targeting tend to divide damage, we want atleast 1 damage per target before casting. + { + shouldPlayPercentage = 0; + } + } + SAFE_DELETE(tc); } else { @@ -1448,7 +2106,20 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty xDiff = 0; shouldPlayPercentage = shouldPlayPercentage - static_cast ((shouldPlayPercentage * 1.9f) / (1 + xDiff)); } + if(card->getManaCost() && card->getManaCost()->kicker && card->getManaCost()->kicker->isMulti) + { + ManaCost * withKickerCost= NEW ManaCost(card->getManaCost()); + withKickerCost->add(withKickerCost->kicker); + int canKick = 0; + while(pMana->canAfford(withKickerCost)) + { + withKickerCost->add(withKickerCost->kicker); + canKick += 1; + } + SAFE_DELETE(withKickerCost); + shouldPlayPercentage = 10*canKick; + } if (WRand() % 100 > shouldPlayPercentage) continue; nextCardToPlay = card; @@ -1457,6 +2128,11 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty maxCost = pMana->getConvertedCost(); } } + if(nextCardToPlay) + { + DebugTrace(" AI wants to play card." << endl + << "- Next card to play: " << (nextCardToPlay ? nextCardToPlay->name : "None" ) << endl ); + } return nextCardToPlay; } @@ -1487,18 +2163,40 @@ void AIPlayerBaka::initTimer() } int AIPlayerBaka::computeActions() { + /*Zeth fox:TODO:rewrite this entire function, It's a mess. + I made it far to complicated for what it does and is prone to error and inefficiency. + Ai run's certain part's when it doesn't need to and run's certain actions when it shouldn't, + and it is far to easy to cripple the ai even with what appears to be a minor change to this function; + reasoning:I split this from 2 to 3 else statements, leaving chooseblockers in the 3rd else, + the 2nd else is run about 90% of the time over the third, this was causing ai to miss the chance to chooseblockers() + when it could have blocked almost 90% of the time.*/ GameObserver * g = GameObserver::GetInstance(); - Player * p = g->currentPlayer; + Player * p = (Player*)this; Player * currentP = g->currentlyActing(); - if (!(g->currentlyActing() == this)) + if (!(g->currentlyActing() == p)) return 0; - if (g->mLayers->actionLayer()->menuObject) + ActionLayer * object = g->mLayers->actionLayer(); + if (object->menuObject) { - g->mLayers->actionLayer()->doReactTo(0); + int doThis = selectMenuOption(); + if(doThis >= 0) + { + if(object->abilitiesMenu->isMultipleChoice) + g->mLayers->actionLayer()->doMultipleChoice(doThis); + else + g->mLayers->actionLayer()->doReactTo(doThis); + } + else if(doThis < 0 || object->checkCantCancel()) + g->mLayers->actionLayer()->doReactTo(object->abilitiesMenu->mObjects.size()-1); return 1; } - if (chooseTarget()) - return 1; + TargetChooser * currentTc = g->getCurrentTargetChooser(); + if(currentTc) + { + int targetResult = currentTc->Owner == this? chooseTarget():0; + if (targetResult) + return 1; + } int currentGamePhase = g->getCurrentGamePhase(); static bool findingCard = false; //this guard is put in place to prevent Ai from @@ -1515,47 +2213,43 @@ int AIPlayerBaka::computeActions() if(spell && spell->type == ACTION_SPELL) lastStackActionController = spell->source->controller(); if ((interruptIfICan() || g->isInterrupting == this) - //i can interupt or am interupting - && p != this - //and its not my turn && this == currentP //and i am the currentlyActivePlayer && ((lastStackActionController && lastStackActionController != this) || (g->mLayers->stackLayer()->count(0, NOT_RESOLVED) == 0))) //am im not interupting my own spell, or the stack contains nothing. { - findingCard = true; - ManaCost * icurrentMana = getPotentialMana(); bool ipotential = false; - if (icurrentMana->getConvertedCost()) + if(p->game->hand->hasType("instant") || p->game->hand->hasAbility(Constants::FLASH)) { - //if theres mana i can use there then potential is true. - ipotential = true; + findingCard = true; + ManaCost * icurrentMana = getPotentialMana(); + icurrentMana->add(this->getManaPool()); + if (icurrentMana->getConvertedCost()) + { + //if theres mana i can use there then potential is true. + ipotential = true; + } + if (!nextCardToPlay) + { + nextCardToPlay = FindCardToPlay(icurrentMana, "instant"); + if (game->playRestrictions->canPutIntoZone(nextCardToPlay, game->stack) == PlayRestriction::CANT_PLAY) + nextCardToPlay = NULL; + } + SAFE_DELETE (icurrentMana); } - - - if (!nextCardToPlay) - { - nextCardToPlay = FindCardToPlay(icurrentMana, "instant"); - if (game->playRestrictions->canPutIntoZone(nextCardToPlay, game->stack) == PlayRestriction::CANT_PLAY) - nextCardToPlay = NULL; - } - SAFE_DELETE (icurrentMana); - if (!nextCardToPlay) { selectAbility(); } - - - if (nextCardToPlay) { if (ipotential) { - if(tapLandsForMana(nextCardToPlay->getManaCost(),nextCardToPlay)) + if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay,gotPayments)) { AIAction * a = NEW AIAction(nextCardToPlay); clickstream.push(a); + gotPayments.clear(); } } findingCard = false; @@ -1568,26 +2262,33 @@ int AIPlayerBaka::computeActions() } else if(p == this && g->mLayers->stackLayer()->count(0, NOT_RESOLVED) == 0) { //standard actions - switch (currentGamePhase) + switch (g->getCurrentGamePhase()) { + case Constants::MTG_PHASE_UPKEEP: + selectAbility(); + break; case Constants::MTG_PHASE_FIRSTMAIN: case Constants::MTG_PHASE_SECONDMAIN: { ManaCost * currentMana = getPotentialMana(); bool potential = false; + currentMana->add(this->getManaPool()); if (currentMana->getConvertedCost()) { //if theres mana i can use there then potential is true. potential = true; } nextCardToPlay = FindCardToPlay(currentMana, "land"); - selectAbility(); - //look for the most expensive creature we can afford. If not found, try enchantment, then artifact, etc... const char* types[] = {"creature", "enchantment", "artifact", "sorcery", "instant"}; int count = 0; while (!nextCardToPlay && count < 5) { + if(clickstream.size()) //don't find cards while we have clicking to do. + { + SAFE_DELETE(currentMana); + return 0; + } nextCardToPlay = FindCardToPlay(currentMana, types[count]); if (game->playRestrictions->canPutIntoZone(nextCardToPlay, game->stack) == PlayRestriction::CANT_PLAY) nextCardToPlay = NULL; @@ -1595,45 +2296,29 @@ int AIPlayerBaka::computeActions() } SAFE_DELETE(currentMana); - if (nextCardToPlay) { - if (potential) + if(nextCardToPlay->has(Constants::SUNBURST)) { - ///////////////////////// //had to force this on Ai other wise it would pay nothing but 1 color for a sunburst card. - //this does not teach it to use manaproducer more effectively, it simply allow it to use the manaproducers it does understand better on sunburst by force. - if (nextCardToPlay->has(Constants::SUNBURST)) + //this does not teach it to use manaproducer more effectively, it simply allow it to + //use the manaproducers it does understand better on sunburst by force. + vectorchecking = canPaySunBurst(nextCardToPlay->getManaCost()); + if(payTheManaCost(nextCardToPlay->getManaCost(),NULL,checking)) { - ManaCost * SunCheck = manaPool; - SunCheck = getPotentialMana(); - for (int i = Constants::MTG_NB_COLORS - 1; i > 0; i--) - { - //sunburst for Ai - if (SunCheck->hasColor(i)) - { - if (nextCardToPlay->getManaCost()->hasColor(i) > 0) - {//do nothing if the card already has this color. - } - else - { - if (nextCardToPlay->sunburst < nextCardToPlay->getManaCost()->getConvertedCost() || nextCardToPlay->getManaCost()->hasX()) - { - nextCardToPlay->getManaCost()->add(i, 1); - nextCardToPlay->getManaCost()->remove(0, 1); - nextCardToPlay->sunburst += 1; - } - } - } - } - delete (SunCheck); + AIAction * a = NEW AIAction(nextCardToPlay); + clickstream.push(a); + return 1; } - ///////////////////////// + nextCardToPlay = NULL; + gotPayments.clear();//if any. + return 1; } - if(tapLandsForMana(nextCardToPlay->getManaCost(),nextCardToPlay)) + if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay,gotPayments)) { AIAction * a = NEW AIAction(nextCardToPlay); clickstream.push(a); + gotPayments.clear(); } return 1; } @@ -1644,28 +2329,39 @@ int AIPlayerBaka::computeActions() break; } case Constants::MTG_PHASE_COMBATATTACKERS: - chooseAttackers(); - break; + { + if(g->currentPlayer == this)//only on my turns. + chooseAttackers(); + break; + } + case Constants::MTG_PHASE_COMBATBLOCKERS: + { + if(g->currentPlayer != this)//only on my opponents turns. + chooseBlockers(); + break; + } case Constants::MTG_PHASE_ENDOFTURN: + selectAbility(); break; default: - selectAbility(); break; } } else { cout << "my turn" << endl; - switch (currentGamePhase) + switch (g->getCurrentGamePhase()) { + case Constants::MTG_PHASE_UPKEEP: + case Constants::MTG_PHASE_FIRSTMAIN: + case Constants::MTG_PHASE_COMBATATTACKERS: case Constants::MTG_PHASE_COMBATBLOCKERS: + case Constants::MTG_PHASE_SECONDMAIN: { - chooseBlockers(); selectAbility(); break; } default: - selectAbility(); break; } return 1; @@ -1674,6 +2370,90 @@ int AIPlayerBaka::computeActions() } ; +int AIPlayer::selectMenuOption() +{ + GameObserver * g = GameObserver::GetInstance(); + ActionLayer * object = g->mLayers->actionLayer(); + int doThis = -1; + if (object->menuObject) + { + int checkedLast = 0; + int checked = 0; + MenuAbility * currentMenu = NULL; + if(object->abilitiesMenu->isMultipleChoice && object->currentActionCard) + { + for(size_t m = object->mObjects.size()-1;m > 0;m--) + { + MenuAbility * ability = dynamic_cast(object->mObjects[m]); + if(ability && ability->triggered) + { + currentMenu = (MenuAbility *)object->mObjects[m]; + break; + } + } + if(currentMenu) + for(unsigned int mk = 0;mk < currentMenu->abilities.size();mk++) + { + MTGAbility * checkEff = NULL; + AIAction * check = NULL; + checkEff = currentMenu->abilities[mk]; + if(checkEff) + { + if(checkEff->target && checkEff->target->typeAsTarget() == TARGET_CARD) + check = NEW AIAction(checkEff,checkEff->source,(MTGCardInstance*)checkEff->target); + else if(checkEff->target && checkEff->target->typeAsTarget() == TARGET_PLAYER) + check = NEW AIAction(checkEff,(Player*)checkEff->target,checkEff->source); + else + check = NEW AIAction(checkEff,checkEff->source); + } + if(check) + { + checked = getEfficiency(check); + SAFE_DELETE(check); + } + if(checked > 60 && checked > checkedLast) + { + doThis = mk; + checkedLast = checked; + } + checked = 0; + } + } + else + { + for(unsigned int k = 0;k < object->abilitiesMenu->mObjects.size();k++) + { + MTGAbility * checkEff = NULL; + AIAction * check = NULL; + if(object->abilitiesMenu->mObjects[k]->GetId() >= 0) + checkEff = (MTGAbility *)object->mObjects[object->abilitiesMenu->mObjects[k]->GetId()]; + if(checkEff) + { + Targetable * checkTarget = checkEff->target; + if(checkTarget && checkTarget->typeAsTarget() == TARGET_CARD) + check = NEW AIAction(checkEff,checkEff->source,(MTGCardInstance*)checkTarget); + else if(checkTarget && checkTarget->typeAsTarget() == TARGET_PLAYER) + check = NEW AIAction(checkEff,(Player*)checkTarget,checkEff->source); + else + check = NEW AIAction(checkEff,checkEff->source); + } + if(check) + { + checked = getEfficiency(check); + SAFE_DELETE(check); + } + if(checked > 60 && checked > checkedLast) + { + doThis = k; + checkedLast = checked; + } + checked = 0; + } + } + } + return doThis; +} + int AIPlayer::receiveEvent(WEvent * event) { if (getStats()) @@ -1728,15 +2508,19 @@ int AIPlayerBaka::Act(float dt) } else { + if (g->currentActionPlayer == this)//if im not the action player why would i requestnextphase? g->userRequestNextGamePhase(); } } else { + while(clickstream.size()) + { AIAction * action = clickstream.front(); action->Act(); SAFE_DELETE(action); clickstream.pop(); + } } return 1; } diff --git a/projects/mtg/src/ActionElement.cpp b/projects/mtg/src/ActionElement.cpp index 39c5f3785..57a9ee6e2 100644 --- a/projects/mtg/src/ActionElement.cpp +++ b/projects/mtg/src/ActionElement.cpp @@ -29,7 +29,6 @@ ActionElement::ActionElement(const ActionElement& a): JGuiObject(a) ActionElement::~ActionElement() { SAFE_DELETE(tc); - } int ActionElement::getActivity() diff --git a/projects/mtg/src/ActionLayer.cpp b/projects/mtg/src/ActionLayer.cpp index 6a54dc08e..4d8dd2ccb 100644 --- a/projects/mtg/src/ActionLayer.cpp +++ b/projects/mtg/src/ActionLayer.cpp @@ -4,6 +4,7 @@ #include "GameObserver.h" #include "Targetable.h" #include "WEvent.h" +#include "AllAbilities.h" MTGAbility* ActionLayer::getAbility(int type) { @@ -27,12 +28,21 @@ int ActionLayer::removeFromGame(ActionElement * e) if (isWaitingForAnswer() == e) setCurrentWaitingAction(NULL); + assert(e); e->destroy(); i = getIndexOf(e); //the destroy event might have changed the contents of mObjects, so we get the index again if (i == -1) return 0; //Should not happen, it means we deleted thesame object twice? - + AManaProducer * manaObject = dynamic_cast(e); + if(manaObject) + { + for (size_t i = 0; i < manaObjects.size(); i++) + if (manaObjects[i] == e) + { + manaObjects.erase(manaObjects.begin() + i); + } + } mObjects.erase(mObjects.begin() + i); return 1; @@ -140,10 +150,32 @@ void ActionLayer::Update(float dt) if (cantCancel) { ActionElement * ae = isWaitingForAnswer(); - if (ae && !ae->tc->validTargetsExist()) + int countTargets = 0; + int maxTargets = 0; + if(ae && ae->getActionTc()) { - cantCancel = 0; - cancelCurrentAction(); + if (!ae->getActionTc()->validTargetsExist()) + { + cantCancel = 0; + cancelCurrentAction(); + return; + } + countTargets = ae->getActionTc()->countValidTargets(); + maxTargets = ae->getActionTc()->maxtargets; + if (countTargets < maxTargets) + { + /* + @movedto(this|mygraveyard) from(mybattlefield):moveto(mybattlefield) + target(<2>creature[elf]|opponentgraveyard) + and there were 3 in the grave, you have the valid amount needed, this function should not trigger + ...however if you had only 1 in the grave, then the max targets is reset to the maximum you CAN + use this effect on...in line with "up to" wording found on the cards with such abilities. + 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()->targets.size()) == countTargets-1) + ae->getActionTc()->done = true; + } } } } @@ -155,6 +187,7 @@ void ActionLayer::Render() abilitiesMenu->Render(); return; } + currentActionCard = NULL; for (size_t i = 0; i < mObjects.size(); i++) { if (mObjects[i] != NULL) @@ -176,7 +209,7 @@ void ActionLayer::setCurrentWaitingAction(ActionElement * ae) TargetChooser * ActionLayer::getCurrentTargetChooser() { if (currentWaitingAction && currentWaitingAction->waitingForAnswer) - return currentWaitingAction->tc; + return currentWaitingAction->getActionTc(); return NULL; } @@ -185,7 +218,7 @@ int ActionLayer::cancelCurrentAction() ActionElement * ae = isWaitingForAnswer(); if (!ae) return 0; - if (cantCancel && ae->tc->validTargetsExist()) + if (cantCancel && ae->getActionTc()->validTargetsExist()) return 0; ae->waitingForAnswer = 0; //TODO MOVE THIS IN ActionElement setCurrentWaitingAction(NULL); @@ -312,7 +345,7 @@ void ActionLayer::setMenuObject(Targetable * object, bool must) SAFE_DELETE(abilitiesMenu); abilitiesMenu = NEW SimpleMenu(10, this, Fonts::MAIN_FONT, 100, 100, object->getDisplayName().c_str()); - + currentActionCard = NULL; for (size_t i = 0; i < mObjects.size(); i++) { ActionElement * currentAction = (ActionElement *) mObjects[i]; @@ -328,6 +361,36 @@ void ActionLayer::setMenuObject(Targetable * object, bool must) modal = 1; } +void ActionLayer::setCustomMenuObject(Targetable * object, bool must,vectorabilities) +{ + if (!object) + { + DebugTrace("FATAL: ActionLayer::setCustomMenuObject"); + return; + } + menuObject = object; + SAFE_DELETE(abilitiesMenu); + abilitiesMenu = NEW SimpleMenu(10, this, Fonts::MAIN_FONT, 100, 100, object->getDisplayName().c_str()); + currentActionCard = NULL; + abilitiesMenu->isMultipleChoice = false; + if(abilities.size()) + { + abilitiesMenu->isMultipleChoice = true; + ActionElement * currentAction = NULL; + for(int w = 0; w < int(abilities.size());w++) + { + currentAction = (ActionElement*)abilities[w]; + currentActionCard = (MTGCardInstance*)abilities[0]->target; + abilitiesMenu->Add(mObjects.size()-1, currentAction->getMenuText(),"",false); + } + } + if (!must) + abilitiesMenu->Add(kCancelMenuID, "Cancel"); + else + cantCancel = 1; + modal = 1; +} + void ActionLayer::doReactTo(int menuIndex) { @@ -339,8 +402,21 @@ void ActionLayer::doReactTo(int menuIndex) } } +void ActionLayer::doMultipleChoice(int choice) +{ + if (menuObject) + { + DebugTrace("ActionLayer::doReactToChoice " << choice); + ButtonPressedOnMultipleChoice(choice); + } +} + void ActionLayer::ButtonPressed(int controllerid, int controlid) { + if(this->abilitiesMenu && this->abilitiesMenu->isMultipleChoice) + { + return ButtonPressedOnMultipleChoice(); + } if (controlid >= 0 && controlid < static_cast(mObjects.size())) { ActionElement * currentAction = (ActionElement *) mObjects[controlid]; @@ -359,6 +435,35 @@ void ActionLayer::ButtonPressed(int controllerid, int controlid) } } +void ActionLayer::ButtonPressedOnMultipleChoice(int choice) +{ + int currentMenuObject = -1; + for(int i = int(mObjects.size()-1);i > 0;i--) + { + //the currently displayed menu is not always the currently listenning action object + //find the menu which is displayed. + MenuAbility * ma = dynamic_cast(mObjects[i]);//find the active menu + if(ma && ma->triggered) + { + currentMenuObject = i; + break; + } + } + if (currentMenuObject >= 0 && currentMenuObject < static_cast(mObjects.size())) + { + ActionElement * currentAction = (ActionElement *) mObjects[currentMenuObject]; + currentAction->reactToChoiceClick(menuObject,choice > -1?choice:this->abilitiesMenu->getmCurr(),currentMenuObject); + MenuAbility * ma = dynamic_cast(mObjects[currentMenuObject]); + if(ma) + ma->removeMenu = true;//we clicked something, close menu now. + } + else if (currentMenuObject == kCancelMenuID) + { + GameObserver::GetInstance()->mLayers->stackLayer()->endOfInterruption(); + } + menuObject = 0; +} + ActionLayer::ActionLayer() { menuObject = NULL; diff --git a/projects/mtg/src/ActionStack.cpp b/projects/mtg/src/ActionStack.cpp index ccebf9ffa..206ae2f86 100644 --- a/projects/mtg/src/ActionStack.cpp +++ b/projects/mtg/src/ActionStack.cpp @@ -146,9 +146,9 @@ void StackAbility::Render() string alt1 = source->getName(); Targetable * _target = ability->target; - if (ability->tc) + if (ability->getActionTc()) { - Targetable * t = ability->tc->getNextTarget(); + Targetable * t = ability->getActionTc()->getNextTarget(); if (t) _target = t; } @@ -243,7 +243,8 @@ Interruptible(id), tc(tc), cost(_cost), payResult(payResult) int Spell::computeX(MTGCardInstance * card) { - ManaCost * c = cost->Diff(card->getManaCost()); + ManaCost * c = NULL; + cost? c = cost->Diff(card->getManaCost()) : c = card->controller()->getManaPool()->Diff(card->getManaCost()); int x = c->getCost(Constants::MTG_NB_COLORS); delete c; return x; @@ -598,6 +599,7 @@ int ActionStack::addAction(Interruptible * action) interruptDecision[i] = 0; } Add(action); + lastActionController = game->currentlyActing(); DebugTrace("Action added to stack: " << action->getDisplayName()); return 1; @@ -705,7 +707,7 @@ int ActionStack::resolve() interruptDecision[i] = 0; } } - + lastActionController = NULL; return 1; } @@ -1061,6 +1063,11 @@ int ActionStack::garbageCollect() Interruptible * current = ((Interruptible *) *iter); if (current->state != NOT_RESOLVED) { + AManaProducer * amp = dynamic_cast(current); + if(amp) + { + manaObjects.erase(iter); + } iter = mObjects.erase(iter); SAFE_DELETE(current); } diff --git a/projects/mtg/src/AllAbilities.cpp b/projects/mtg/src/AllAbilities.cpp index aeaf43785..9cd26fd5b 100644 --- a/projects/mtg/src/AllAbilities.cpp +++ b/projects/mtg/src/AllAbilities.cpp @@ -254,7 +254,7 @@ AAPhaseOut::AAPhaseOut(int _id, MTGCardInstance * _source, MTGCardInstance * _ta } int AAPhaseOut::resolve() -{GameObserver * g = g->GetInstance(); +{GameObserver * g = GameObserver::GetInstance(); MTGCardInstance * _target = (MTGCardInstance *) target; if (_target) { @@ -299,9 +299,12 @@ AACounter::AACounter(int id, MTGCardInstance * source, MTGCardInstance * target, { MTGCardInstance * _target = (MTGCardInstance *) target; AbilityFactory af; + if(counterstring.size()) + { Counter * checkcounter = af.parseCounter(counterstring, source, NULL); nb = checkcounter->nb; delete checkcounter; + } if (nb > 0) { for (int i = 0; i < nb; i++) @@ -317,7 +320,9 @@ AACounter::AACounter(int id, MTGCardInstance * source, MTGCardInstance * target, currentAmount = targetCounter->nb; } if(!maxNb || (maxNb && currentAmount < maxNb)) + { _target->counters->addCounter(name.c_str(), power, toughness); + } } } else @@ -373,7 +378,45 @@ AACounter * AACounter::clone() const return NEW AACounter(*this); } -//Counters +//sheild a card from a certain type of counter. +ACounterShroud::ACounterShroud(int id, MTGCardInstance * source, MTGCardInstance * target, Counter * counter) : +MTGAbility(id, source),counter(counter),re(NULL) +{ +} + +int ACounterShroud::addToGame() +{ + SAFE_DELETE(re); + re = NEW RECountersPrevention(this,source,(MTGCardInstance*)target,counter); + if (re) + { + game->replacementEffects->add(re); + return MTGAbility::addToGame(); + } + return 0; +} + +int ACounterShroud::destroy() +{ + game->replacementEffects->remove(re); + SAFE_DELETE(re); + return 1; +} + +ACounterShroud * ACounterShroud::clone() const +{ + ACounterShroud * a = NEW ACounterShroud(*this); + a->re = NULL; + return a; +} + +ACounterShroud::~ACounterShroud() +{ + SAFE_DELETE(re); + SAFE_DELETE(counter); +} + +//removeall counters of a certain type or all. AARemoveAllCounter::AARemoveAllCounter(int id, MTGCardInstance * source, MTGCardInstance * target, const char * _name, int power, int toughness, int nb,bool all, ManaCost * cost) : ActivatedAbility(id, source, cost, 0), nb(nb), power(power), toughness(toughness), name(_name),all(all) @@ -453,6 +496,62 @@ AARemoveAllCounter * AARemoveAllCounter::clone() const return NEW AARemoveAllCounter(*this); } +//proliferate a target +AAProliferate::AAProliferate(int id, MTGCardInstance * source, Targetable * target,ManaCost * cost) : +ActivatedAbility(id, source, cost, 0) +{ +} + +int AAProliferate::resolve() +{ + if (target) + { + vectorpcounters = vector(); + if(target->typeAsTarget() == TARGET_PLAYER && ((Player*)target)->poisonCount && target != source->controller()) + { + MTGAbility * a = NEW AAAlterPoison(game->mLayers->actionLayer()->getMaxId(), source,(Player*)target,1,NULL); + a->target = (Player*)target; + a->oneShot = true; + pcounters.push_back(a); + } + else if (target->typeAsTarget() == TARGET_CARD && ((MTGCardInstance*)target)->counters) + { + Counter * targetCounter = NULL; + for(unsigned int i = 0; i < ((MTGCardInstance*)target)->counters->counters.size();i++) + { + MTGAbility * a = NEW AACounter(game->mLayers->actionLayer()->getMaxId(), source, (MTGCardInstance*)target,"", ((MTGCardInstance*)target)->counters->counters[i]->name.c_str(), ((MTGCardInstance*)target)->counters->counters[i]->power, ((MTGCardInstance*)target)->counters->counters[i]->toughness, 1,0); + a->target = (MTGCardInstance*)target; + a->oneShot = true; + pcounters.push_back(a); + } + } + if(pcounters.size()) + { + MTGAbility * a = NEW MenuAbility(/*int(game->mLayers->actionLayer()->getMaxId())*/this->GetId(),(MTGCardInstance*)target, source,false,pcounters); + a->target = (MTGCardInstance*)target; + a->resolve(); + } + return 1; + } + return 0; +} + +const char* AAProliferate::getMenuText() +{ + return "Proliferate"; +} + +AAProliferate * AAProliferate::clone() const +{ + return NEW AAProliferate(*this); +} + +AAProliferate::~AAProliferate() +{ +} + +// + //Reset Damage on creatures AAResetDamage::AAResetDamage(int id, MTGCardInstance * source, MTGCardInstance * _target, ManaCost * cost): ActivatedAbility(id, source, cost, 0) @@ -750,7 +849,7 @@ int AANewTarget::resolve() if(_target->hasSubtype(Subtypes::TYPE_EQUIPMENT)) { reUp->resolve(); - GameObserver * g = g->GetInstance(); + GameObserver * g = GameObserver::GetInstance(); for (size_t i = 1; i < g->mLayers->actionLayer()->mObjects.size(); i++) { MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->mObjects[i]); @@ -814,7 +913,7 @@ int AAMorph::resolve() if( a && dynamic_cast (a)) { a->removeFromGame(); - GameObserver * g = g->GetInstance(); + GameObserver * g = GameObserver::GetInstance(); g->removeObserver(a); } if (a) @@ -843,7 +942,7 @@ int AAMorph::testDestroy() { if(_target->turningOver && !_target->isMorphed && !_target->morphed) { - GameObserver * g = g->GetInstance(); + GameObserver * g = GameObserver::GetInstance(); g->removeObserver(this); return 1; } @@ -2012,8 +2111,8 @@ AAWinGame * AAWinGame::clone() const //IfThenEffect -IfThenAbility::IfThenAbility(int _id, string delayAbility, MTGCardInstance * _source, int type,string Cond) : -MTGAbility(_id, _source),delayAbility(delayAbility), type(type),Cond(Cond) +IfThenAbility::IfThenAbility(int _id, MTGAbility * delayedAbility, MTGCardInstance * _source, int type,string Cond) : +MTGAbility(_id, _source),delayedAbility(delayedAbility), type(type),Cond(Cond) { } @@ -2022,11 +2121,30 @@ int IfThenAbility::resolve() MTGCardInstance * card = (MTGCardInstance*)source; AbilityFactory af; int checkCond = af.parseCastRestrictions(card,card->controller(),Cond); + if(Cond.find("cantargetcard(") != string::npos) + { + TargetChooser * condTc = NULL; + vectorsplitTarget = parseBetween(Cond, "card(", ")"); + if (splitTarget.size()) + { + TargetChooserFactory tcf; + condTc = tcf.createTargetChooser(splitTarget[1], source); + condTc->targetter = NULL; + if(source->target) + checkCond = condTc->canTarget(source->target); + SAFE_DELETE(condTc); + } + + } if((checkCond && type == 1)||(!checkCond && type == 2)) { - MTGAbility * a1 = af.parseMagicLine(delayAbility, this->GetId(), NULL, card); + MTGAbility * a1 = delayedAbility->clone(); if (!a1) return 0; + if(target) + a1->target = target; + if(!a1->target) + a1->target = source->target; if(a1->oneShot) { a1->resolve(); @@ -2046,7 +2164,14 @@ const char * IfThenAbility::getMenuText() IfThenAbility * IfThenAbility::clone() const { - return NEW IfThenAbility(*this); + IfThenAbility * a = NEW IfThenAbility(*this); + a->delayedAbility = delayedAbility->clone(); + return a; +} + +IfThenAbility::~IfThenAbility() +{ + SAFE_DELETE(delayedAbility); } // @@ -2060,13 +2185,14 @@ MayAbility::MayAbility(int _id, MTGAbility * _ability, MTGCardInstance * _source void MayAbility::Update(float dt) { + GameObserver * gameObs = GameObserver::GetInstance(); MTGAbility::Update(dt); - if (!triggered) + if (!triggered && !gameObs->getCurrentTargetChooser()) { triggered = 1; if (TargetAbility * ta = dynamic_cast(ability)) { - if (!ta->tc->validTargetsExist()) + if (!ta->getActionTc()->validTargetsExist()) return; } game->mLayers->actionLayer()->setMenuObject(source, must); @@ -2122,6 +2248,130 @@ MayAbility::~MayAbility() SAFE_DELETE(ability); } +//Menu building ability Abilities +//this will eventaully handle choosen discards/sacrifices. +MenuAbility::MenuAbility(int _id, Targetable * mtarget, MTGCardInstance * _source, bool must,vectorabilities,Player * who) : +MayAbility(_id,NULL,_source,must), must(must),abilities(abilities),who(who) +{ + triggered = 0; + mClone = NULL; + this->target = mtarget; + removeMenu = false; +} + +void MenuAbility::Update(float dt) +{ + MTGAbility::Update(dt); + ActionLayer * object = game->mLayers->actionLayer(); + if (!triggered && !object->menuObject) + { + + triggered = 1; + if (TargetAbility * ta = dynamic_cast(ability)) + { + if (!ta->getActionTc()->validTargetsExist()) + return; + } + } + if(object->currentActionCard && this->target != object->currentActionCard) + { + triggered = 0; + } + if(triggered) + { + game->mLayers->actionLayer()->setCustomMenuObject(source, must,abilities); + previousInterrupter = game->isInterrupting; + game->mLayers->stackLayer()->setIsInterrupting(source->controller()); + } +} + +int MenuAbility::resolve() +{ + this->triggered = 1; + MTGAbility * a = this; + a->target = this->target; + return a->addToGame(); +} + +const char * MenuAbility::getMenuText() +{ + if(abilities.size()) + return "choose one"; + return ability->getMenuText(); +} + +int MenuAbility::testDestroy() +{ + if (!removeMenu) + return 0; + if (game->mLayers->actionLayer()->menuObject) + return 0; + if (game->mLayers->actionLayer()->getIndexOf(mClone) != -1) + return 0; + return 1; +} + +int MenuAbility::isReactingToTargetClick(Targetable * card){return MayAbility::isReactingToTargetClick(card);} +int MenuAbility::reactToTargetClick(Targetable * object){return 1;} + +int MenuAbility::reactToChoiceClick(Targetable * object,int choice,int control) +{ + ActionElement * currentAction = (ActionElement *) game->mLayers->actionLayer()->mObjects[control]; + if(currentAction != (ActionElement*)this) + return 0; + if(!abilities.size()||!triggered) + return 0; + for(int i = 0;i < int(abilities.size());i++) + { + if(choice == i) + mClone = abilities[choice]->clone(); + else + abilities[i]->clone();//all get cloned for clean up purposes. + } + if(!mClone) + return 0; + mClone->target = abilities[choice]->target; + mClone->oneShot = true; + mClone->forceDestroy = 1; + mClone->resolve(); + SAFE_DELETE(mClone); + if (source->controller() == game->isInterrupting) + game->mLayers->stackLayer()->cancelInterruptOffer(); + this->forceDestroy = 1; + removeMenu = true; + return reactToTargetClick(object); +} + +MenuAbility * MenuAbility::clone() const +{ + MenuAbility * a = NEW MenuAbility(*this); + a->canBeInterrupted = false; + if(abilities.size()) + { + for(int i = 0;i < int(abilities.size());i++) + { + a->abilities.push_back(abilities[i]->clone()); + a->abilities[i]->target = abilities[i]->target; + } + } + else + a->ability = ability->clone(); + return a; +} + +MenuAbility::~MenuAbility() +{ + if(abilities.size()) + { + for(int i = 0;i < int(abilities.size());i++) + { + SAFE_DELETE(abilities[i]); + } + } + else + SAFE_DELETE(ability); +} +/// //MultiAbility : triggers several actions for a cost MultiAbility::MultiAbility(int _id, MTGCardInstance * card, Targetable * _target, ManaCost * _cost) : ActivatedAbility(_id, card, _cost, 0) @@ -3381,8 +3631,8 @@ void ABlink::Update(float dt) if(!tc->validTargetsExist()) { spell->source->owner->game->putInExile(spell->source); - delete spell; - delete tc; + SAFE_DELETE(spell); + SAFE_DELETE(tc); this->forceDestroy = 1; return; } @@ -3397,8 +3647,8 @@ void ABlink::Update(float dt) spell->getNextCardTarget(); spell->resolve(); - delete spell; - delete tc; + SAFE_DELETE(spell); + SAFE_DELETE(tc); this->forceDestroy = 1; return; } @@ -3480,8 +3730,8 @@ void ABlink::resolveBlink() if(!tc->validTargetsExist()) { spell->source->owner->game->putInExile(spell->source); - delete spell; - delete tc; + SAFE_DELETE(spell); + SAFE_DELETE(tc); this->forceDestroy = 1; return; } @@ -3495,8 +3745,8 @@ void ABlink::resolveBlink() spell->source->target = inplay->cards[i]; spell->getNextCardTarget(); spell->resolve(); - delete spell; - delete tc; + SAFE_DELETE(spell); + SAFE_DELETE(tc); this->forceDestroy = 1; return; } @@ -3519,8 +3769,8 @@ void ABlink::resolveBlink() clonedStored->addToGame(); } } - delete tc; - delete spell; + SAFE_DELETE(spell); + SAFE_DELETE(tc); this->forceDestroy = 1; if(stored) delete(stored); diff --git a/projects/mtg/src/CardDescriptor.cpp b/projects/mtg/src/CardDescriptor.cpp index a82d34ed0..cd071f163 100644 --- a/projects/mtg/src/CardDescriptor.cpp +++ b/projects/mtg/src/CardDescriptor.cpp @@ -52,27 +52,6 @@ void CardDescriptor::setisMultiColored(int w) { isMultiColored = w; } - -void CardDescriptor::setisBlackAndWhite(int w) -{ - isBlackAndWhite = w; -} -void CardDescriptor::setisRedAndBlue(int w) -{ - isRedAndBlue = w; -} -void CardDescriptor::setisBlackAndGreen(int w) -{ - isBlackAndGreen = w; -} -void CardDescriptor::setisBlueAndGreen(int w) -{ - isBlueAndGreen = w; -} -void CardDescriptor::setisRedAndWhite(int w) -{ - isRedAndWhite = w; -} void CardDescriptor::setNegativeSubtype(string value) { @@ -234,26 +213,6 @@ MTGCardInstance * CardDescriptor::match(MTGCardInstance * card) { match = NULL; } - if ((isBlackAndWhite == -1 && card->isBlackAndWhite) || (isBlackAndWhite == 1 && !card->isBlackAndWhite)) - { - match = NULL; - } - if ((isRedAndBlue == -1 && card->isRedAndBlue) || (isRedAndBlue == 1 && !card->isRedAndBlue)) - { - match = NULL; - } - if ((isBlackAndGreen == -1 && card->isBlackAndGreen) || (isBlackAndGreen == 1 && !card->isBlackAndGreen)) - { - match = NULL; - } - if ((isBlueAndGreen == -1 && card->isBlueAndGreen) || (isBlueAndGreen == 1 && !card->isBlueAndGreen)) - { - match = NULL; - } - if ((isRedAndWhite == -1 && card->isRedAndWhite) || (isRedAndWhite == 1 && !card->isRedAndWhite)) - { - match = NULL; - } if ((isLeveler == -1 && card->isLeveler) || (isLeveler == 1 && !card->isLeveler)) { match = NULL; diff --git a/projects/mtg/src/CardGui.cpp b/projects/mtg/src/CardGui.cpp index ad9c54f69..86ce79d3f 100644 --- a/projects/mtg/src/CardGui.cpp +++ b/projects/mtg/src/CardGui.cpp @@ -253,7 +253,7 @@ void CardGui::Render() if (mask && quad) JRenderer::GetInstance()->FillRect(actX - (scale * quad->mWidth / 2),actY - (scale * quad->mHeight / 2), scale * quad->mWidth, scale* quad->mHeight, mask); - if (tc && tc->alreadyHasTarget(card))//paint targets red. + if ((tc && tc->alreadyHasTarget(card)) || card == game->mLayers->actionLayer()->currentActionCard)//paint targets red. { if (card->isTapped()) { @@ -264,8 +264,17 @@ void CardGui::Render() renderer->FillRect(actX - (scale * quad->mWidth / 2),actY - (scale * quad->mHeight / 2), scale * quad->mWidth, scale* quad->mHeight, ARGB(128,255,0,0)); } } - if(tc && tc->source && tc->source->view->actZ >= 1.3)//paint the source green while infocus. - renderer->FillRect(tc->source->view->actX - (scale * quad->mWidth / 2),tc->source->view->actY - (scale * quad->mHeight / 2), scale*quad->mWidth, scale*quad->mHeight, ARGB(128,0,255,0)); + if(tc && tc->source && tc->source->view && tc->source->view->actZ >= 1.3 && card == tc->source)//paint the source green while infocus. + { + if (tc->source->isTapped()) + { + renderer->FillRect(actX - (scale * quad->mWidth / 2)-7,actY - (scale * quad->mHeight / 2)+7,scale* quad->mHeight,scale * quad->mWidth, ARGB(128,0,255,0)); + } + else + { + renderer->FillRect(tc->source->view->actX - (scale * quad->mWidth / 2),tc->source->view->actY - (scale * quad->mHeight / 2), scale*quad->mWidth, scale*quad->mHeight, ARGB(128,0,255,0)); + } + } PlayGuiObject::Render(); } @@ -779,7 +788,10 @@ void CardGui::TinyCropRender(MTGCard * card, const Pos& pos, JQuad * quad) void CardGui::RenderBig(MTGCard* card, const Pos& pos) { JRenderer * renderer = JRenderer::GetInstance(); - + //GameObserver * game = GameObserver::GetInstance(); + //if((MTGCard*)game->mLayers->actionLayer()->currentActionCard != NULL) + // card = (MTGCard*)game->mLayers->actionLayer()->currentActionCard; + //i want this but ai targets cards so quickly that it can crash the game. float x = pos.actX; JQuadPtr quad = WResourceManager::Instance()->RetrieveCard(card); diff --git a/projects/mtg/src/Counters.cpp b/projects/mtg/src/Counters.cpp index 41a752c40..ce75efb06 100644 --- a/projects/mtg/src/Counters.cpp +++ b/projects/mtg/src/Counters.cpp @@ -72,29 +72,47 @@ Counters::~Counters() int Counters::addCounter(const char * _name, int _power, int _toughness) { -/*420.5n If a permanent has both a +1/+1 counter and a -1/-1 counter on it, N +1/+1 and N -1/-1 counters are removed from it, where N is the smaller of the number of +1/+1 and -1/-1 counters on it.*/ - for (int i = 0; i < mCount; i++) + /*420.5n If a permanent has both a +1/+1 counter and a -1/-1 counter on it, N +1/+1 and N -1/-1 counters are removed from it, where N is the smaller of the number of +1/+1 and -1/-1 counters on it.*/ + GameObserver *g = GameObserver::GetInstance(); + WEvent * e = NEW WEventCounters(this,_name,_power,_toughness); + dynamic_cast(e)->targetCard = this->target; + if (e == g->replacementEffects->replace(e)) { - if (counters[i]->cancels(_power, _toughness) && !counters[i]->name.size() && counters[i]->nb > 0) + for (int i = 0; i < mCount; i++) { - counters[i]->removed(); - counters[i]->nb--; - return mCount; + if (counters[i]->cancels(_power, _toughness) && !counters[i]->name.size() && counters[i]->nb > 0) + { + counters[i]->removed(); + counters[i]->nb--; + WEvent * t = NEW WEventCounters(this,_name,_power*-1,_toughness*-1,false,true); + dynamic_cast(t)->targetCard = this->target; + g->receiveEvent(t); + delete(e); + return mCount; + } } - } - for (int i = 0; i < mCount; i++) - { - if (counters[i]->sameAs(_name, _power, _toughness)) + for (int i = 0; i < mCount; i++) { - counters[i]->added(); - counters[i]->nb++; - return mCount; + if (counters[i]->sameAs(_name, _power, _toughness)) + { + counters[i]->added(); + counters[i]->nb++; + WEvent * j = NEW WEventCounters(this,_name,_power,_toughness,true,false); + dynamic_cast(j)->targetCard = this->target; + g->receiveEvent(j); + delete(e); + return mCount; + } } + Counter * counter = NEW Counter(target, _name, _power, _toughness); + counters.push_back(counter); + counter->added(); + WEvent * w = NEW WEventCounters(this,_name,_power,_toughness,true,false); + dynamic_cast(w)->targetCard = this->target; + g->receiveEvent(w); + mCount++; } - Counter * counter = NEW Counter(target, _name, _power, _toughness); - counters.push_back(counter); - counter->added(); - mCount++; + delete(e); return mCount; } @@ -126,6 +144,10 @@ int Counters::removeCounter(const char * _name, int _power, int _toughness) return 0; counters[i]->removed(); counters[i]->nb--; + GameObserver *g = GameObserver::GetInstance(); + WEvent * e = NEW WEventCounters(this,_name,_power,_toughness,false,true); + dynamic_cast(e)->targetCard = this->target; + g->receiveEvent(e); //special case:if a card is suspended and no longer has a time counter when the last is removed, the card is cast. if (target->suspended && !target->counters->hasCounter("time",0,0)) { diff --git a/projects/mtg/src/Damage.cpp b/projects/mtg/src/Damage.cpp index 0d6f6924b..4bb9a778c 100644 --- a/projects/mtg/src/Damage.cpp +++ b/projects/mtg/src/Damage.cpp @@ -114,6 +114,14 @@ int Damage::resolve() } damage = 0; } + if ((_target)->has(Constants::HYDRA)) + { + for (int j = damage; j > 0; j--) + { + (_target)->counters->removeCounter(1, 1); + } + damage = 0; + } if (!damage) { state = RESOLVED_NOK; @@ -136,14 +144,13 @@ int Damage::resolve() } if(_target->toughness <= 0 && _target->has(Constants::INDESTRUCTIBLE)) _target->controller()->game->putInGraveyard(_target); - } else if (target->type_as_damageable == DAMAGEABLE_PLAYER && source->has(Constants::INFECT)) { // Poison on player Player * _target = (Player *) target; _target->poisonCount += damage;//this will be changed to poison counters. - target->damageCount += damage; + _target->damageCount += damage; } else if (target->type_as_damageable == DAMAGEABLE_PLAYER && (source->has(Constants::POISONTOXIC) || source->has(Constants::POISONTWOTOXIC) || source->has(Constants::POISONTHREETOXIC))) diff --git a/projects/mtg/src/DuelLayers.cpp b/projects/mtg/src/DuelLayers.cpp index 974117cd6..92bb67239 100644 --- a/projects/mtg/src/DuelLayers.cpp +++ b/projects/mtg/src/DuelLayers.cpp @@ -142,7 +142,7 @@ DuelLayers::~DuelLayers() { if (objects[i] != mCardSelector) { - delete objects[i]; + SAFE_DELETE(objects[i]); objects[i] = NULL; } } diff --git a/projects/mtg/src/ExtraCost.cpp b/projects/mtg/src/ExtraCost.cpp index 8f51205af..4af17779b 100644 --- a/projects/mtg/src/ExtraCost.cpp +++ b/projects/mtg/src/ExtraCost.cpp @@ -643,6 +643,9 @@ int CounterCost::doPay() { target->counters->addCounter(counter->name.c_str(), counter->power, counter->toughness); } + if (tc) + tc->initTargets(); + target = NULL; return 1; } @@ -654,8 +657,14 @@ int CounterCost::doPay() target->counters->removeCounter(counter->name.c_str(), counter->power, counter->toughness); } hasCounters = 0; + if (tc) + tc->initTargets(); + target = NULL; return 1; } + if (tc) + tc->initTargets(); + target = NULL; return 0; } diff --git a/projects/mtg/src/GameObserver.cpp b/projects/mtg/src/GameObserver.cpp index 2b6c52255..c7871a9a0 100644 --- a/projects/mtg/src/GameObserver.cpp +++ b/projects/mtg/src/GameObserver.cpp @@ -192,6 +192,12 @@ void GameObserver::nextCombatStep() void GameObserver::userRequestNextGamePhase() { + if(getCurrentTargetChooser() && getCurrentTargetChooser()->maxtargets == 1000) + { + getCurrentTargetChooser()->done = true; + if(getCurrentTargetChooser()->source) + cardClick(getCurrentTargetChooser()->source); + } if (mLayers->stackLayer()->getNext(NULL, 0, NOT_RESOLVED)) return; if (getCurrentTargetChooser()) @@ -312,7 +318,6 @@ bool GameObserver::removeObserver(ActionElement * observer) { if (!observer) return false; - return mLayers->actionLayer()->moveToGarbage(observer); } @@ -334,16 +339,22 @@ GameObserver::~GameObserver() void GameObserver::Update(float dt) { Player * player = currentPlayer; - - if (Constants::MTG_PHASE_COMBATBLOCKERS == currentGamePhase && BLOCKERS == combatStep) - player = player->opponent(); - + if (Constants::MTG_PHASE_COMBATBLOCKERS == currentGamePhase && BLOCKERS == combatStep) + { + player = player->opponent(); + } + if(getCurrentTargetChooser() && getCurrentTargetChooser()->Owner && player != getCurrentTargetChooser()->Owner) + { + if(getCurrentTargetChooser()->Owner != currentlyActing()) + { + player = getCurrentTargetChooser()->Owner; + } + } currentActionPlayer = player; if (isInterrupting) player = isInterrupting; mLayers->Update(dt, player); - - while (mLayers->actionLayer()->stuffHappened) + while (mLayers->actionLayer()->stuffHappened) { mLayers->actionLayer()->Update(0); } @@ -357,15 +368,15 @@ void GameObserver::Update(float dt) //Handles game state based effects void GameObserver::gameStateBasedEffects() { - //check land playability at start; as we want this effect to happen reguardless of unresolved - //effects or menus actions + if(getCurrentTargetChooser() && int(getCurrentTargetChooser()->targets.size()) == getCurrentTargetChooser()->maxtargets) + getCurrentTargetChooser()->done = true; for (int i = 0; i < 2; i++) players[i]->isPoisoned = (players[i]->poisonCount > 0); if (mLayers->stackLayer()->count(0, NOT_RESOLVED) != 0) return; if (mLayers->actionLayer()->menuObject) return; - if (targetChooser || mLayers->actionLayer()->isWaitingForAnswer()) + if (getCurrentTargetChooser() || mLayers->actionLayer()->isWaitingForAnswer()) return; //////////////////////// @@ -585,27 +596,6 @@ void GameObserver::gameStateBasedEffects() ++colored; } z->cards[w]->isMultiColored = (colored > 1) ? 1 : 0; - - if(z->cards[w]->hasColor(Constants::MTG_COLOR_WHITE) && z->cards[w]->hasColor(Constants::MTG_COLOR_BLACK)) - z->cards[w]->isBlackAndWhite = 1; - else - z->cards[w]->isBlackAndWhite = 0; - if(z->cards[w]->hasColor(Constants::MTG_COLOR_RED) && z->cards[w]->hasColor(Constants::MTG_COLOR_BLUE)) - z->cards[w]->isRedAndBlue = 1; - else - z->cards[w]->isRedAndBlue = 0; - if(z->cards[w]->hasColor(Constants::MTG_COLOR_GREEN) && z->cards[w]->hasColor(Constants::MTG_COLOR_BLACK)) - z->cards[w]->isBlackAndGreen = 1; - else - z->cards[w]->isBlackAndGreen = 0; - if(z->cards[w]->hasColor(Constants::MTG_COLOR_BLUE) && z->cards[w]->hasColor(Constants::MTG_COLOR_GREEN)) - z->cards[w]->isBlueAndGreen = 1; - else - z->cards[w]->isBlueAndGreen = 0; - if(z->cards[w]->hasColor(Constants::MTG_COLOR_RED) && z->cards[w]->hasColor(Constants::MTG_COLOR_WHITE)) - z->cards[w]->isRedAndWhite = 1; - else - z->cards[w]->isRedAndWhite = 0; } } /////////////////////////////////// @@ -879,6 +869,8 @@ int GameObserver::cardClick(MTGCardInstance * card, Targetable * object) if (card == cardWaitingForTargets) { int _result = targetChooser->ForceTargetListReady(); + if(targetChooser->targetMin && int(targetChooser->targets.size()) < targetChooser->maxtargets) + _result = 0; if (_result) { result = TARGET_OK_FULL; diff --git a/projects/mtg/src/GuiLayers.cpp b/projects/mtg/src/GuiLayers.cpp index b91039245..9fc4892d5 100644 --- a/projects/mtg/src/GuiLayers.cpp +++ b/projects/mtg/src/GuiLayers.cpp @@ -19,10 +19,22 @@ GuiLayer::~GuiLayer() void GuiLayer::Add(JGuiObject *object) { mObjects.push_back(object); + AManaProducer * manaObject = dynamic_cast(object); + if(manaObject) + manaObjects.push_back(object); } int GuiLayer::Remove(JGuiObject *object) { + AManaProducer * manaObject = dynamic_cast(object); + if(manaObject) + { + for (size_t i = 0; i < manaObjects.size(); i++) + if (manaObjects[i] == object) + { + manaObjects.erase(manaObjects.begin() + i); + } + } for (size_t i = 0; i < mObjects.size(); i++) if (mObjects[i] == object) { diff --git a/projects/mtg/src/GuiPhaseBar.cpp b/projects/mtg/src/GuiPhaseBar.cpp index 8b36c7426..d2df8513b 100644 --- a/projects/mtg/src/GuiPhaseBar.cpp +++ b/projects/mtg/src/GuiPhaseBar.cpp @@ -138,7 +138,7 @@ void GuiPhaseBar::Render() currentP = _("opponent's turn"); } font->SetColor(ARGB(255, 255, 255, 255)); - if (g->currentlyActing()->isAI()) + if (g->currentlyActing() && g->currentlyActing()->isAI()) { font->SetColor(ARGB(255, 128, 128, 128)); } diff --git a/projects/mtg/src/MTGAbility.cpp b/projects/mtg/src/MTGAbility.cpp index f93b2ff31..eb56f86a5 100644 --- a/projects/mtg/src/MTGAbility.cpp +++ b/projects/mtg/src/MTGAbility.cpp @@ -166,7 +166,11 @@ int AbilityFactory::parseCastRestrictions(MTGCardInstance * card,Player * player } } else if (i == 2) - secondAmount = atoi(comparasion[2].c_str()); + { + WParsedInt * secondA = NEW WParsedInt(comparasion[2].c_str(),(Spell*)card,card); + secondAmount = secondA->getValue(); + SAFE_DELETE(secondA); + } } if(firstAmount < secondAmount && !less && !more && !equal) return 0; @@ -599,7 +603,23 @@ TriggeredAbility * AbilityFactory::parseTrigger(string s, string magicText, int TargetChooser *fromTc = parseSimpleTC(s, "from", card); return NEW TrTargeted(id, card, tc, fromTc, 0,once); } - + + if (s.find("counteradded(") != string::npos) + { + vectorsplitCounter = parseBetween(s,"counteradded(",")"); + Counter * counter = parseCounter(splitCounter[1],card,NULL); + TargetChooser * tc = parseSimpleTC(s, "from", card); + return NEW TrCounter(id, card, counter, tc, 1,once); + } + + if (s.find("counterremoved(") != string::npos) + { + vectorsplitCounter = parseBetween(s,"counterremoved(",")"); + Counter * counter = parseCounter(splitCounter[1],card,NULL); + TargetChooser * tc = parseSimpleTC(s, "from", card); + return NEW TrCounter(id, card, counter, tc, 0,once); + } + int who = 0; if (s.find("my") != string::npos) who = 1; @@ -699,7 +719,8 @@ MTGAbility * AbilityFactory::getCoreAbility(MTGAbility * a) //only atempt to return a nestedability if it contains a valid ability. example where this causes a bug otherwise. AEquip is considered nested, but contains no ability. return getCoreAbility(na->ability); } - + if (MenuAbility * ma = dynamic_cast(a)) + return ma->abilities[0]; return a; } @@ -878,11 +899,14 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG TargetChooserFactory tcf; tc = tcf.createTargetChooser("creature|mybattlefield", card); } - ae->tc = tc; + ae->setActionTC(tc); return ae; } if (tc) + { + tc->belongsToAbility = sWithoutTc; return NEW GenericTargetAbility(newName,id, card, tc, a, cost, limit,sideEffect,usesBeforeSideEffect, restrictions, dest); + } return NEW GenericActivatedAbility(newName,id, card, a, cost, limit,sideEffect,usesBeforeSideEffect,restrictions, dest); } SAFE_DELETE(cost); @@ -915,7 +939,8 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG { string cond = sWithoutTc.substr(ifKeywords[i].length(),ifKeywords[i].length() + sWithoutTc.find(" then ")-6); string s1 = s.substr(s.find(" then ")+6); - MTGAbility * a = NEW IfThenAbility(id, s1, card,checkIf[i],cond); + MTGAbility * a1 = parseMagicLine(s1, id, spell, card); + MTGAbility * a = NEW IfThenAbility(id, a1, card,checkIf[i],cond); a->canBeInterrupted = false; a->oneShot = true; if(tc) @@ -1977,7 +2002,30 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG a->oneShot = 1; return a; } - + + //no counters on target of optional type + vector splitCounterShroud = parseBetween(s, "countershroud(", ")"); + if (splitCounterShroud.size()) + { + string counterShroudString = splitCounterShroud[1]; + Counter * counter = NULL; + if(splitCounterShroud[1] == "any") + { + counter = NULL; + } + else + { + counter = parseCounter(counterShroudString, target, spell); + if (!counter) + { + DebugTrace("MTGAbility: can't parse counter:" << s); + return NULL; + } + } + MTGAbility * a = NEW ACounterShroud(id, card, target,counter); + return a; + } + //removes all counters of the specifified type. vector splitRemoveCounter = parseBetween(s, "removeallcounters(", ")"); if (splitRemoveCounter.size()) @@ -2226,6 +2274,16 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return NULL; //TODO } + //proliferate + found = s.find("proliferate"); + if (found != string::npos) + { + MTGAbility * a = NEW AAProliferate(id, card, target); + a->oneShot = 1; + a->canBeInterrupted = false; + return a; + } + //frozen, next untap this does not untap. found = s.find("frozen"); if (found != string::npos) @@ -2340,7 +2398,7 @@ int AbilityFactory::abilityEfficiency(MTGAbility * a, Player * p, int mode, Targ { if (mode == MODE_PUTINTOPLAY) return BAKA_EFFECT_GOOD; - return abilityEfficiency(abi->ability, p, mode, abi->tc); + return abilityEfficiency(abi->ability, p, mode, abi->getActionTc()); } if (GenericActivatedAbility * abi = dynamic_cast(a)) { @@ -2354,8 +2412,8 @@ int AbilityFactory::abilityEfficiency(MTGAbility * a, Player * p, int mode, Targ return abilityEfficiency(abi->ability, p, mode, tc); if (ALord * abi = dynamic_cast(a)) { - int myCards = countCards(abi->tc, p); - int theirCards = countCards(abi->tc, p->opponent()); + int myCards = countCards(abi->getActionTc(), p); + int theirCards = countCards(abi->getActionTc(), p->opponent()); int efficiency = abilityEfficiency(abi->ability, p, mode, tc); if ( ((myCards < theirCards) && efficiency == BAKA_EFFECT_GOOD) || ((myCards > theirCards) && efficiency == BAKA_EFFECT_BAD) ) return efficiency; @@ -2379,6 +2437,8 @@ int AbilityFactory::abilityEfficiency(MTGAbility * a, Player * p, int mode, Targ return BAKA_EFFECT_GOOD; if (dynamic_cast (a)) return BAKA_EFFECT_BAD; + if (dynamic_cast (a)) + return BAKA_EFFECT_GOOD; if (AACounter * ac = dynamic_cast(a)) { bool negative_effect = ac->power < 0 || ac->toughness < 0; @@ -2634,6 +2694,12 @@ int AbilityFactory::magicText(int id, Spell * spell, MTGCardInstance * card, int for (size_t i = 0; i < v.size(); ++i) { MTGAbility * a = v[i]; + if (!a) + { + DebugTrace("ABILITYFACTORY ERROR: Parser returned NULL"); + return 0; + } + if (dryMode) { result = abilityEfficiency(a, card->controller(), mode, tc); @@ -2641,8 +2707,44 @@ int AbilityFactory::magicText(int id, Spell * spell, MTGCardInstance * card, int SAFE_DELETE(v[i]); return result; } - - if (a) + if(spell && spell->tc && spell->tc->targets.size() > 1 && spell->getNextTarget()) + a->target = spell->getNextTarget(); + if(a && a->target && spell && spell->tc && spell->tc->targets.size() > 1) + { + while(a && a->target) + { + if(a->oneShot) + { + a->resolve(); + } + else if(!dynamic_cast(a)) + { + MTGAbility * mClone = a->clone(); + mClone->target = a->target; + MTGAbility * core = getCoreAbility(mClone); + if (dynamic_cast (core)) + mClone->canBeInterrupted = false; + mClone->addToGame(); + } + else if(dynamic_cast(a) && a->target == spell->tc->targets[0]) + { + //only add may/choice/target( menu ability for the first card, + //no point in adding "discard" 3 times to a menu, as you can only choose the effect once + MTGAbility * mClone = a->clone(); + mClone->target = a->target; + MTGAbility * core = getCoreAbility(mClone); + if (dynamic_cast (core)) + mClone->canBeInterrupted = false; + mClone->addToGame(); + } + a->target = spell->getNextTarget(a->target); + if(!a->target) + { + SAFE_DELETE(a); + } + } + } + else { if (a->oneShot) { @@ -2651,18 +2753,9 @@ int AbilityFactory::magicText(int id, Spell * spell, MTGCardInstance * card, int } else { - // Anything involving Mana Producing abilities cannot be interrupted - MTGAbility * core = getCoreAbility(a); - if (dynamic_cast (core)) - a->canBeInterrupted = false; - a->addToGame(); } } - else - { - DebugTrace("ABILITYFACTORY ERROR: Parser returned NULL"); - } } return result; @@ -3045,13 +3138,6 @@ void AbilityFactory::addAbilities(int _id, Spell * spell) } break; } - case 1480: //Energy Tap - { - card->target->tap(); - int mana = card->target->getManaCost()->getConvertedCost(); - game->currentlyActing()->getManaPool()->add(Constants::MTG_COLOR_ARTIFACT, mana); - } - //Addons ICE-AGE Cards case 2474: //Minion of Leshrac @@ -3771,9 +3857,28 @@ int TargetAbility::resolve() if (t->typeAsTarget() == TARGET_CARD && ((MTGCardInstance*)t)->isPhased) return 0; if (ability->oneShot) - return ability->resolve(); - MTGAbility * a = ability->clone(); - return a->addToGame(); + { + while(t) + { + ability->resolve(); + t = tc->getNextTarget(t); + ability->target = t; + } + tc->targets.clear(); + return 1; + } + else + { + while(t) + { + MTGAbility * a = ability->clone(); + a->addToGame(); + t = tc->getNextTarget(t); + ability->target = t; + } + tc->targets.clear(); + return 1; + } } return 0; } @@ -3984,6 +4089,58 @@ void ListMaintainerAbility::updateTargets() } +void ListMaintainerAbility::checkTargets() +{ + //remove invalid ones + map tempCheck; + for (map::iterator it = checkCards.begin(); it != checkCards.end(); ++it) + { + MTGCardInstance * card = (*it).first; + if (!canBeInList(card) || card->mPropertiesChangedSinceLastUpdate) + { + tempCheck[card] = true; + } + } + + for (map::iterator it = tempCheck.begin(); it != tempCheck.end(); ++it) + { + MTGCardInstance * card = (*it).first; + checkCards.erase(card); + } + + tempCheck.clear(); + + //add New valid ones + 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 }; + for (int k = 0; k < 4; k++) + { + MTGGameZone * zone = zones[k]; + if (canTarget(zone)) + { + for (int j = 0; j < zone->nb_cards; j++) + { + MTGCardInstance * card = zone->cards[j]; + if (canBeInList(card)) + { + if (checkCards.find(card) == checkCards.end()) + { + tempCheck[card] = true; + } + } + } + } + } + } + for (map::iterator it = tempCheck.begin(); it != tempCheck.end(); ++it) + { + MTGCardInstance * card = (*it).first; + checkCards[card] = true; + } +} + void ListMaintainerAbility::Update(float dt) { updateTargets(); @@ -4142,7 +4299,7 @@ int GenericTriggeredAbility::triggerOnEvent(WEvent * e) Targetable * GenericTriggeredAbility::getTriggerTarget(WEvent * e, MTGAbility * a) { - TriggerTargetChooser * ttc = dynamic_cast (a->tc); + TriggerTargetChooser * ttc = dynamic_cast (a->getActionTc()); if (ttc) return e->getTarget(ttc->triggerTarget); @@ -4164,7 +4321,7 @@ Targetable * GenericTriggeredAbility::getTriggerTarget(WEvent * e, MTGAbility * void GenericTriggeredAbility::setTriggerTargets(Targetable * ta, MTGAbility * a) { - TriggerTargetChooser * ttc = dynamic_cast (a->tc); + TriggerTargetChooser * ttc = dynamic_cast (a->getActionTc()); if (ttc) { a->target = ta; @@ -4266,7 +4423,7 @@ int AManaProducer::isReactingToClick(MTGCardInstance * _card, ManaCost * mana) && (source->hasType(Subtypes::TYPE_LAND) || !tap || !source->hasSummoningSickness()) && !source->isPhased) { ManaCost * cost = getCost(); - if (!cost || mana->canAfford(cost)) + if (!cost || (mana->canAfford(cost) && (!cost->extraCosts || cost->extraCosts->canPay())))/*counter cost bypass react to click*/ { result = 1; } diff --git a/projects/mtg/src/MTGCardInstance.cpp b/projects/mtg/src/MTGCardInstance.cpp index af710f828..0de21f2c7 100644 --- a/projects/mtg/src/MTGCardInstance.cpp +++ b/projects/mtg/src/MTGCardInstance.cpp @@ -118,11 +118,6 @@ void MTGCardInstance::initMTGCI() frozen = 0; fresh = 0; isMultiColored = 0; - isBlackAndWhite = 0; - isRedAndBlue = 0; - isBlackAndGreen = 0; - isBlueAndGreen = 0; - isRedAndWhite = 0; isLeveler = 0; enchanted = false; CDenchanted = 0; @@ -146,6 +141,7 @@ void MTGCardInstance::initMTGCI() suspended = false; castMethod = Constants::NOT_CAST; mPropertiesChangedSinceLastUpdate = false; + kicked = 0; for (int i = 0; i < ManaCost::MANA_PAID_WITH_RETRACE +1; i++) @@ -194,17 +190,6 @@ void MTGCardInstance::initMTGCI() if (colored > 1) { isMultiColored = 1; - - if(this->hasColor(Constants::MTG_COLOR_WHITE) && this->hasColor(Constants::MTG_COLOR_BLACK)) - isBlackAndWhite = 1; - if(this->hasColor(Constants::MTG_COLOR_RED) && this->hasColor(Constants::MTG_COLOR_BLUE)) - isRedAndBlue = 1; - if(this->hasColor(Constants::MTG_COLOR_GREEN) && this->hasColor(Constants::MTG_COLOR_BLACK)) - isBlackAndGreen = 1; - if(this->hasColor(Constants::MTG_COLOR_BLUE) && this->hasColor(Constants::MTG_COLOR_GREEN)) - isBlueAndGreen = 1; - if(this->hasColor(Constants::MTG_COLOR_RED) && this->hasColor(Constants::MTG_COLOR_WHITE)) - isRedAndWhite = 1; } if(previous && previous->morphed && !turningOver) diff --git a/projects/mtg/src/MTGDeck.cpp b/projects/mtg/src/MTGDeck.cpp index 3235c94b7..edf8549eb 100644 --- a/projects/mtg/src/MTGDeck.cpp +++ b/projects/mtg/src/MTGDeck.cpp @@ -155,7 +155,16 @@ int MTGAllCards::processConfLine(string &s, MTGCard *card, CardPrimitive * primi { string value = val; std::transform(value.begin(), value.end(), value.begin(), ::tolower); - cost->kicker = ManaCost::parseManaCost(value); + size_t multikick = value.find("multi"); + bool isMultikicker = false; + if(multikick != string::npos) + { + size_t endK = value.find("{",multikick); + value.erase(multikick, endK - multikick); + isMultikicker = true; + } + cost->kicker = ManaCost::parseManaCost(value); + cost->kicker->isMulti = isMultikicker; } break; diff --git a/projects/mtg/src/MTGDefinitions.cpp b/projects/mtg/src/MTGDefinitions.cpp index 3850063ff..564a91168 100644 --- a/projects/mtg/src/MTGDefinitions.cpp +++ b/projects/mtg/src/MTGDefinitions.cpp @@ -118,7 +118,8 @@ const char* Constants::MTGBasicAbilities[] = { "snowmountainlandwalk", "snowislandlandwalk", "snowswamplandwalk", - "canattack" + "canattack", + "hydra" }; map Constants::MTGBasicAbilitiesMap; diff --git a/projects/mtg/src/MTGGameZones.cpp b/projects/mtg/src/MTGGameZones.cpp index f3a68cbc5..28591936b 100644 --- a/projects/mtg/src/MTGGameZones.cpp +++ b/projects/mtg/src/MTGGameZones.cpp @@ -10,7 +10,6 @@ #if defined (WIN32) || defined (LINUX) #include #endif - //------------------------------ //Players Game //------------------------------ @@ -467,6 +466,7 @@ MTGCardInstance * MTGGameZone::removeCard(MTGCardInstance * card, int createCopy copy->view = card->view; copy->isToken = card->isToken; copy->X = card->X; + copy->kicked = card->kicked; //stupid bug with tokens... if (card->model == card) @@ -524,7 +524,7 @@ MTGCardInstance * MTGGameZone::findByName(string name) { for (int i = 0; i < (nb_cards); i++) { - if (cards[i]->name == name) + if (cards[i]->name == name || cards[i]->getLCName()/*tokens*/ == name) { return cards[i]; } diff --git a/projects/mtg/src/MTGRules.cpp b/projects/mtg/src/MTGRules.cpp index cd346de86..0d337e55e 100644 --- a/projects/mtg/src/MTGRules.cpp +++ b/projects/mtg/src/MTGRules.cpp @@ -240,11 +240,6 @@ int MTGEventBonus::receiveEvent(WEvent * event) void MTGEventBonus::grantAward(string awardName,int amount) { - JSample * sample = WResourceManager::Instance()->RetrieveSample("bonus.wav"); - if (sample) - { - JSoundSystem::GetInstance()->PlaySample(sample); - } text = awardName; textAlpha = 255; Credits::addCreditBonus(amount); @@ -397,11 +392,22 @@ int MTGPutInPlayRule::reactToClick(MTGCardInstance * card) ManaCost * previousManaPool = NEW ManaCost(player->getManaPool()); int payResult = player->getManaPool()->pay(card->getManaCost()); - if (card->getManaCost()->kicker && OptionKicker::KICKER_ALWAYS == options[Options::KICKERPAYMENT].number) + if (card->getManaCost()->kicker && (OptionKicker::KICKER_ALWAYS == options[Options::KICKERPAYMENT].number || card->controller()->isAI())) { ManaCost * withKickerCost= NEW ManaCost(card->getManaCost()); withKickerCost->add(withKickerCost->kicker); - if (previousManaPool->canAfford(withKickerCost)) + if (card->getManaCost()->kicker->isMulti) + { + while(previousManaPool->canAfford(withKickerCost)) + { + withKickerCost->add(withKickerCost->kicker); + card->kicked += 1; + } + for(int i = 0;i < card->kicked;i++) + player->getManaPool()->pay(card->getManaCost()->kicker); + payResult = ManaCost::MANA_PAID_WITH_KICKER; + } + else if (previousManaPool->canAfford(withKickerCost)) { player->getManaPool()->pay(card->getManaCost()->kicker); payResult = ManaCost::MANA_PAID_WITH_KICKER; @@ -511,8 +517,23 @@ int MTGKickerRule::reactToClick(MTGCardInstance * card) Player * player = game->currentlyActing(); ManaCost * withKickerCost= NEW ManaCost(card->getManaCost());//using pointers here alters the real cost of the card. - withKickerCost->add(withKickerCost->kicker); - card->paymenttype = MTGAbility::PUT_INTO_PLAY_WITH_KICKER; + if (card->getManaCost()->kicker->isMulti) + { + while(player->getManaPool()->canAfford(withKickerCost)) + { + withKickerCost->add(withKickerCost->kicker); + card->kicked += 1; + } + card->kicked -= 1; + //for(int i = 0;i < card->kicked;i++) + //player->getManaPool()->pay(card->getManaCost()->kicker); + card->paymenttype = MTGAbility::PUT_INTO_PLAY_WITH_KICKER; + } + else + { + withKickerCost->add(withKickerCost->kicker); + card->paymenttype = MTGAbility::PUT_INTO_PLAY_WITH_KICKER; + } if (withKickerCost->isExtraPaymentSet()) { if (!game->targetListIsSet(card)) @@ -1163,7 +1184,7 @@ MTGAbility(_id, NULL) int MTGAttackRule::isReactingToClick(MTGCardInstance * card, ManaCost * mana) { - if (currentPhase == Constants::MTG_PHASE_COMBATATTACKERS && card->controller() == game->currentPlayer) + if (currentPhase == Constants::MTG_PHASE_COMBATATTACKERS && card->controller() == game->currentPlayer && card->controller() == game->currentlyActing())//on my turn and when I am the acting player. { if(card->isPhased) return 0; @@ -1205,7 +1226,6 @@ int MTGAttackRule::reactToClick(MTGCardInstance * card) { if (!isReactingToClick(card)) return 0; - //Graphically select the next card that can attack if (!card->isAttacker()) { @@ -1399,7 +1419,7 @@ MTGAbility(_id, NULL) int MTGBlockRule::isReactingToClick(MTGCardInstance * card, ManaCost * mana) { if (currentPhase == Constants::MTG_PHASE_COMBATBLOCKERS && !game->isInterrupting - && card->controller() == game->currentlyActing() + && card->controller() != game->currentPlayer ) { if (card->canBlock() && !card->isPhased) diff --git a/projects/mtg/src/ManaCost.cpp b/projects/mtg/src/ManaCost.cpp index a04e4d19b..adc0bd7db 100644 --- a/projects/mtg/src/ManaCost.cpp +++ b/projects/mtg/src/ManaCost.cpp @@ -172,15 +172,15 @@ ManaCost * ManaCost::parseManaCost(string s, ManaCost * _manaCost, MTGCardInstan size_t separator2 = string::npos; if (separator != string::npos) { - separator2 = value.find(",", separator + 1); + separator2 = value.find(",", counter_end + 1); } SAFE_DELETE(tc); size_t target_start = string::npos; if (separator2 != string::npos) { - target_start = value.find(",", separator2 + 1); + target_start = value.find(",", counter_end + 1); } - size_t target_end = counter_end; + size_t target_end = value.length(); if (target_start != string::npos && target_end != string::npos) { string target = value.substr(target_start + 1, target_end - 1 - target_start); @@ -267,6 +267,8 @@ ManaCost::ManaCost(ManaCost * manaCost) hybrids = manaCost->hybrids; kicker = NEW ManaCost( manaCost->kicker ); + if(kicker) + kicker->isMulti = manaCost->isMulti; Retrace = NEW ManaCost( manaCost->Retrace ); BuyBack = NEW ManaCost( manaCost->BuyBack ); alternative = NEW ManaCost( manaCost->alternative ); @@ -361,6 +363,7 @@ void ManaCost::init() Retrace = NULL; morph = NULL; suspend = NULL; + isMulti = false; } void ManaCost::reinit() @@ -402,6 +405,7 @@ void ManaCost::copy(ManaCost * _manaCost) { kicker = NEW ManaCost(); kicker->copy(_manaCost->kicker); + kicker->isMulti = _manaCost->kicker->isMulti; } SAFE_DELETE(alternative); if (_manaCost->alternative) @@ -753,9 +757,12 @@ string ManaCost::toString() #ifdef WIN32 void ManaCost::Dump() { + //if(this->getConvertedCost())//uncomment when this is far too loud and clutters your output making other traces pointless. + //{ DebugTrace( "\n===ManaCost===" ); DebugTrace( this->toString() ); DebugTrace( "\n=============" ); + //} } #endif diff --git a/projects/mtg/src/ReplacementEffects.cpp b/projects/mtg/src/ReplacementEffects.cpp index 107714215..d99b424d6 100644 --- a/projects/mtg/src/ReplacementEffects.cpp +++ b/projects/mtg/src/ReplacementEffects.cpp @@ -3,6 +3,7 @@ #include "ReplacementEffects.h" #include "MTGCardInstance.h" #include "TargetChooser.h" +#include "AllAbilities.h" REDamagePrevention::REDamagePrevention(MTGAbility * source, TargetChooser *tcSource, TargetChooser *tcTarget, int damage, bool oneShot, int typeOfDamage) : @@ -46,8 +47,35 @@ REDamagePrevention::~REDamagePrevention() { SAFE_DELETE(tcSource); SAFE_DELETE(tcTarget); +} +//counters replacement effect/////////////////// +RECountersPrevention::RECountersPrevention(MTGAbility * source,MTGCardInstance * cardSource,MTGCardInstance * cardTarget,Counter * counter) : + source(source),cardSource(cardSource),cardTarget(cardTarget),counter(counter) +{ } + WEvent * RECountersPrevention::replace(WEvent *event) + { + if (!event) return event; + WEventCounters * e = dynamic_cast (event); + if (!e) return event; + if((MTGCardInstance*)e->targetCard) + { + if((MTGCardInstance*)e->targetCard == cardSource && counter) + { + if(e->power == counter->power && e->toughness == counter->toughness && e->name == counter->name) + return event = NULL; + } + else if((MTGCardInstance*)e->targetCard == cardSource) + return event = NULL; + } + return event; + } +RECountersPrevention::~RECountersPrevention() +{ + +} +////////////////////////////////////////////// ReplacementEffects::ReplacementEffects() { } diff --git a/projects/mtg/src/SimpleMenu.cpp b/projects/mtg/src/SimpleMenu.cpp index 952d6475d..536de4da0 100644 --- a/projects/mtg/src/SimpleMenu.cpp +++ b/projects/mtg/src/SimpleMenu.cpp @@ -34,6 +34,7 @@ SimpleMenu::SimpleMenu(int id, JGuiListener* listener, int fontId, float x, floa : JGuiController(id, listener), fontId(fontId), mCenterHorizontal(centerHorizontal), mCenterVertical(centerVertical) { autoTranslate = true; + isMultipleChoice = false; mHeight = 2 * kVerticalMargin; mWidth = 0; mX = x; diff --git a/projects/mtg/src/Subtypes.cpp b/projects/mtg/src/Subtypes.cpp index e1d155293..05eb2858f 100644 --- a/projects/mtg/src/Subtypes.cpp +++ b/projects/mtg/src/Subtypes.cpp @@ -65,6 +65,8 @@ string Subtypes::find(unsigned int id) bool Subtypes::isSubtypeOfType(unsigned int subtype, unsigned int type) { + if(subtype >= size_t(subtypesToType.size())) + return false; return (subtypesToType[subtype] == type); } diff --git a/projects/mtg/src/TargetChooser.cpp b/projects/mtg/src/TargetChooser.cpp index 423b172ec..ea1d5549b 100644 --- a/projects/mtg/src/TargetChooser.cpp +++ b/projects/mtg/src/TargetChooser.cpp @@ -43,10 +43,10 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta }; found = s.find("other "); - if (found == 0) + if (found != string::npos) { other = true; - s = s.substr(6); + s = s.erase(found,found+6-found); } found = s.find("trigger"); @@ -65,13 +65,20 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta if (found != string::npos) { int maxtargets = 1; - size_t several = s.find_first_of('s', 5); - if (several != string::npos) maxtargets = -1; + size_t several = s.find(""); + if (several != string::npos) maxtargets = TargetChooser::UNLITMITED_TARGETS; found = s.find("creature"); if (found != string::npos) return NEW DamageableTargetChooser(card, maxtargets, other); //Any Damageable target (player, creature) return NEW PlayerTargetChooser(card, maxtargets); //Any player } + found = s.find("proliferation"); + if (found != string::npos) + { + int maxtargets = TargetChooser::UNLITMITED_TARGETS; + return NEW ProliferateChooser(card, maxtargets); //Any player + } + string s1; found = s.find("|"); if (found != string::npos) @@ -152,6 +159,7 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta TargetChooser * tc = NULL; int maxtargets = 1; + bool targetMin = false; CardDescriptor * cd = NULL; //max targets allowed size_t limit = s1.find('<'); @@ -162,8 +170,18 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta if (end != string::npos) { howmany = s1.substr(limit + 1, end - limit - 1); - WParsedInt * howmuch = NEW WParsedInt(howmany, NULL, card); - maxtargets = howmuch->getValue(); + size_t uptoamount= howmany.find("upto:"); + + if(uptoamount != string::npos) + { + howmany = s1.substr(uptoamount + 6, end - uptoamount - 6); + } + else + { + targetMin = true;//if upto: is not found, then we need to have a minimum of the amount.... + } + WParsedInt * howmuch = NEW WParsedInt(howmany, (Spell*)card, card); + howmany.find("anyamount") != string::npos?maxtargets = TargetChooser::UNLITMITED_TARGETS:maxtargets = howmuch->getValue(); delete howmuch; s1 = s1.substr(end + 1); } @@ -195,6 +213,7 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta while (attributes.size()) { size_t found2 = attributes.find(";"); + size_t foundAnd = attributes.find("&"); string attribute; if (found2 != string::npos) { @@ -202,6 +221,12 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta attribute = attributes.substr(0, found2); attributes = attributes.substr(found2 + 1); } + else if (foundAnd != string::npos) + { + cd->mode = CD_AND; + attribute = attributes.substr(0, foundAnd); + attributes = attributes.substr(foundAnd + 1); + } else { attribute = attributes; @@ -396,71 +421,6 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta cd->setisMultiColored(1); } - } - else if (attribute.find("blackandgreen") != string::npos) - { - //card is both colors? - if (minus) - { - cd->setisBlackAndGreen(-1); - } - else - { - cd->setisBlackAndGreen(1); - } - - } - else if (attribute.find("blackandwhite") != string::npos) - { - //card is both colors? - if (minus) - { - cd->setisBlackAndWhite(-1); - } - else - { - cd->setisBlackAndWhite(1); - } - - } - else if (attribute.find("redandblue") != string::npos) - { - //card is both colors? - if (minus) - { - cd->setisRedAndBlue(-1); - } - else - { - cd->setisRedAndBlue(1); - } - - } - else if (attribute.find("blueandgreen") != string::npos) - { - //card is both colors? - if (minus) - { - cd->setisBlueAndGreen(-1); - } - else - { - cd->setisBlueAndGreen(1); - } - - } - else if (attribute.find("redandwhite") != string::npos) - { - //card is both colors? - if (minus) - { - cd->setisRedAndWhite(-1); - } - else - { - cd->setisRedAndWhite(1); - } - } else if (attribute.find("power") != string::npos) { @@ -615,20 +575,13 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta cd->mode = CD_AND; typeName = typeName.substr(0, found); } - //X targets allowed ? - if (typeName.at(typeName.length() - 1) == 's' && !Subtypes::subtypesList->find(typeName, false) && typeName.compare("this") - != 0) - { - typeName = typeName.substr(0, typeName.length() - 1); - maxtargets = -1; - } if (cd) { if (!tc) { if (typeName.compare("*") != 0) cd->setSubtype(typeName); - tc = NEW DescriptorTargetChooser(cd, zones, nbzones, card, maxtargets, other); + tc = NEW DescriptorTargetChooser(cd, zones, nbzones, card, maxtargets, other, targetMin); } else { @@ -642,7 +595,7 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta { if (typeName.compare("*") == 0) { - return NEW TargetZoneChooser(zones, nbzones, card, maxtargets, other); + return NEW TargetZoneChooser(zones, nbzones, card, maxtargets, other, targetMin); } else if (typeName.compare("this") == 0) { @@ -650,7 +603,7 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta } else { - tc = NEW TypeTargetChooser(typeName.c_str(), zones, nbzones, card, maxtargets, other); + tc = NEW TypeTargetChooser(typeName.c_str(), zones, nbzones, card, maxtargets, other, targetMin); } } else @@ -718,7 +671,7 @@ TargetChooser * TargetChooserFactory::createTargetChooser(MTGCardInstance * card } } -TargetChooser::TargetChooser(MTGCardInstance * card, int _maxtargets, bool _other) : +TargetChooser::TargetChooser(MTGCardInstance * card, int _maxtargets, bool _other,bool _targetMin) : TargetsList() { forceTargetListReady = 0; @@ -726,6 +679,11 @@ TargetChooser::TargetChooser(MTGCardInstance * card, int _maxtargets, bool _othe targetter = card; maxtargets = _maxtargets; other = _other; + targetMin = _targetMin; + done = false; + attemptsToFill = 0; + if(source) + Owner = source->controller(); } //Default targetter : every card can be targetted, unless it is protected from the targetter card @@ -810,10 +768,11 @@ int TargetChooser::targetListSet() return 0; } -bool TargetChooser::validTargetsExist() +bool TargetChooser::validTargetsExist(int maxTargets) { for (int i = 0; i < 2; ++i) { + int maxAmount = 0; Player *p = GameObserver::GetInstance()->players[i]; if (canTarget(p)) return true; MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->exile }; @@ -824,10 +783,12 @@ bool TargetChooser::validTargetsExist() { for (int j = 0; j < z->nb_cards; j++) { - if (canTarget(z->cards[j])) return true; + if (canTarget(z->cards[j])) maxAmount++; } } } + if(maxAmount >= maxTargets) + return true; } return false; } @@ -838,6 +799,8 @@ int TargetChooser::countValidTargets() for (int i = 0; i < 2; ++i) { Player *p = GameObserver::GetInstance()->players[i]; + if(canTarget(p)) + result++; MTGGameZone * zones[] = { p->game->inPlay, p->game->graveyard, p->game->hand, p->game->library, p->game->exile }; for (int k = 0; k < 5; k++) { @@ -909,8 +872,8 @@ bool CardTargetChooser::equals(TargetChooser * tc) /** Choose anything that has a given list of types **/ -TypeTargetChooser::TypeTargetChooser(const char * _type, MTGCardInstance * card, int _maxtargets, bool other) : - TargetZoneChooser(card, _maxtargets, other) +TypeTargetChooser::TypeTargetChooser(const char * _type, MTGCardInstance * card, int _maxtargets, bool other,bool targetMin) : + TargetZoneChooser(card, _maxtargets, other,targetMin) { int id = Subtypes::subtypesList->find(_type); nbtypes = 0; @@ -920,8 +883,8 @@ TypeTargetChooser::TypeTargetChooser(const char * _type, MTGCardInstance * card, } TypeTargetChooser::TypeTargetChooser(const char * _type, int * _zones, int nbzones, MTGCardInstance * card, int _maxtargets, - bool other) : - TargetZoneChooser(card, _maxtargets, other) + bool other,bool targetMin) : + TargetZoneChooser(card, _maxtargets, other,targetMin) { int id = Subtypes::subtypesList->find(_type); nbtypes = 0; @@ -1021,8 +984,8 @@ bool TypeTargetChooser ::equals(TargetChooser * tc) /** A Target Chooser associated to a Card Descriptor object, for fine tuning of targets description **/ -DescriptorTargetChooser::DescriptorTargetChooser(CardDescriptor * _cd, MTGCardInstance * card, int _maxtargets, bool other) : - TargetZoneChooser(card, _maxtargets, other) +DescriptorTargetChooser::DescriptorTargetChooser(CardDescriptor * _cd, MTGCardInstance * card, int _maxtargets, bool other,bool targetMin) : + TargetZoneChooser(card, _maxtargets, other,targetMin) { int default_zones[] = { MTGGameZone::MY_BATTLEFIELD, MTGGameZone::OPPONENT_BATTLEFIELD }; init(default_zones, 2); @@ -1030,8 +993,8 @@ DescriptorTargetChooser::DescriptorTargetChooser(CardDescriptor * _cd, MTGCardIn } DescriptorTargetChooser::DescriptorTargetChooser(CardDescriptor * _cd, int * _zones, int nbzones, MTGCardInstance * card, - int _maxtargets, bool other) : - TargetZoneChooser(card, _maxtargets, other) + int _maxtargets, bool other,bool targetMin) : + TargetZoneChooser(card, _maxtargets, other,targetMin) { if (nbzones == 0) { @@ -1092,14 +1055,14 @@ bool DescriptorTargetChooser::equals(TargetChooser * tc) } /* TargetzoneChooser targets everything in a given zone */ -TargetZoneChooser::TargetZoneChooser(MTGCardInstance * card, int _maxtargets, bool other) : - TargetChooser(card, _maxtargets, other) +TargetZoneChooser::TargetZoneChooser(MTGCardInstance * card, int _maxtargets, bool other,bool targetMin) : + TargetChooser(card, _maxtargets, other,targetMin) { init(NULL, 0); } -TargetZoneChooser::TargetZoneChooser(int * _zones, int _nbzones, MTGCardInstance * card, int _maxtargets, bool other) : - TargetChooser(card, _maxtargets, other) +TargetZoneChooser::TargetZoneChooser(int * _zones, int _nbzones, MTGCardInstance * card, int _maxtargets, bool other,bool targetMin) : + TargetChooser(card, _maxtargets, other,targetMin) { init(_zones, _nbzones); } @@ -1275,8 +1238,8 @@ bool DamageableTargetChooser::equals(TargetChooser * tc) /*Spell */ -SpellTargetChooser::SpellTargetChooser(MTGCardInstance * card, int _color, int _maxtargets, bool other) : - TargetChooser(card, _maxtargets, other) +SpellTargetChooser::SpellTargetChooser(MTGCardInstance * card, int _color, int _maxtargets, bool other,bool targetMin) : + TargetChooser(card, _maxtargets, other,targetMin) { color = _color; } @@ -1318,8 +1281,8 @@ bool SpellTargetChooser::equals(TargetChooser * tc) } /*Spell or Permanent */ -SpellOrPermanentTargetChooser::SpellOrPermanentTargetChooser(MTGCardInstance * card, int _color, int _maxtargets, bool other) : - TargetZoneChooser(card, _maxtargets, other) +SpellOrPermanentTargetChooser::SpellOrPermanentTargetChooser(MTGCardInstance * card, int _color, int _maxtargets, bool other,bool targetMin) : + TargetZoneChooser(card, _maxtargets, other,targetMin) { int default_zones[] = { MTGGameZone::MY_BATTLEFIELD, MTGGameZone::OPPONENT_BATTLEFIELD }; init(default_zones, 2); @@ -1443,4 +1406,40 @@ bool TriggerTargetChooser::equals(TargetChooser * tc) return false; return TargetChooser::equals(tc); +} + +/*Proliferate Target */ +bool ProliferateChooser::canTarget(Targetable * target,bool withoutProtections) +{ + if (target->typeAsTarget() == TARGET_CARD) + { + MTGCardInstance * card = (MTGCardInstance*)target; + if(card->counters && card->counters->counters.empty()) + return false; + return true; + } + if (target->typeAsTarget() == TARGET_PLAYER) + { + Player * p = (Player*)target; + if(!p->poisonCount) + return false; + return true; + } + return TypeTargetChooser::canTarget(target,withoutProtections); +} + +ProliferateChooser* ProliferateChooser::clone() const +{ + ProliferateChooser * a = NEW ProliferateChooser(*this); + return a; +} + +bool ProliferateChooser::equals(TargetChooser * tc) +{ + + ProliferateChooser * dtc = dynamic_cast (tc); + if (!dtc) + return false; + + return TypeTargetChooser::equals(tc); } \ No newline at end of file diff --git a/projects/mtg/src/TargetsList.cpp b/projects/mtg/src/TargetsList.cpp index 2c3f5457a..da3ce8636 100644 --- a/projects/mtg/src/TargetsList.cpp +++ b/projects/mtg/src/TargetsList.cpp @@ -20,8 +20,23 @@ int TargetsList::addTarget(Targetable * target) { if (!alreadyHasTarget(target)) { - targets.push_back(target); - return 1; + GameObserver * state = state->GetInstance(); + TargetChooser * tc = state->getCurrentTargetChooser(); + if(!tc || (tc && tc->maxtargets == 1)) + { + //because this was originally coded with targets as an array + //we have to add this condiational to insure that cards with single target effects + //and abilities that seek the nextcardtarget still work correctly. + targets.clear(); + targets.push_back(target); + return 1; + + } + else + { + targets.push_back(target); + return 1; + } } return 0; @@ -46,7 +61,6 @@ int TargetsList::removeTarget(Targetable * target) return 1; } } - return 0; } @@ -72,7 +86,7 @@ Targetable * TargetsList::getNextTarget(Targetable * previous, int type) { if (found && (type == -1 || targets[i]->typeAsTarget() == type)) { - return (targets[i]); + return targets[i]; } if (targets[i] == previous) found = 1; } diff --git a/projects/mtg/src/WEvent.cpp b/projects/mtg/src/WEvent.cpp index 8a4e74451..54fd793db 100644 --- a/projects/mtg/src/WEvent.cpp +++ b/projects/mtg/src/WEvent.cpp @@ -37,6 +37,11 @@ WEventCardUpdate::WEventCardUpdate(MTGCardInstance * card) : } ; +WEventCounters::WEventCounters(Counters *counter,string name,int power,int toughness,bool added,bool removed) : +WEvent(),counter(counter),name(name),power(power),toughness(toughness),added(added),removed(removed) +{ +} + WEventPhaseChange::WEventPhaseChange(Phase * from, Phase * to) : WEvent(CHANGE_PHASE), from(from), to(to) { @@ -168,6 +173,11 @@ Targetable * WEventLife::getTarget(int target) return NULL; } +Targetable * WEventCounters::getTarget() +{ + return targetCard; +} + Targetable * WEventVampire::getTarget(int target) { switch (target)