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)