From fbfac78b09de0af0f85143e846126e9110167749 Mon Sep 17 00:00:00 2001 From: "wagic.the.homebrew" Date: Tue, 20 Sep 2011 03:06:06 +0000 Subject: [PATCH] - Split AIPlayer and AIPlayerBaka in 2 files. Moved all "AI" specific code into AIPlayerBaka, as much as possible. -- This is a copy/paste and shouldn't have any impact on the logic. I just moved some functions from AIPlayer to AIPlayerBaka - Added back the possibility to select a different Resource folder with file Res.txt - Fix a crash when a token id does not exist --- projects/mtg/Android/jni/Android.mk | 1 + projects/mtg/Makefile | 2 +- projects/mtg/bin/Res.txt | 2 +- projects/mtg/include/AIHints.h | 6 +- projects/mtg/include/AIMomirPlayer.h | 4 +- projects/mtg/include/AIPlayer.h | 106 +- projects/mtg/include/AIPlayerBaka.h | 131 ++ projects/mtg/include/TestSuiteAI.h | 3 +- projects/mtg/src/AIHints.cpp | 4 +- projects/mtg/src/AIMomirPlayer.cpp | 2 +- projects/mtg/src/AIPlayer.cpp | 2268 +----------------------- projects/mtg/src/AIPlayerBaka.cpp | 2298 +++++++++++++++++++++++++ projects/mtg/src/GameApp.cpp | 28 +- projects/mtg/src/MTGAbility.cpp | 2 +- projects/mtg/src/StoryFlow.cpp | 4 + projects/mtg/src/TargetChooser.cpp | 1 - projects/mtg/src/Tasks.cpp | 4 + projects/mtg/src/TestSuiteAI.cpp | 2 +- projects/mtg/template.vcxproj | 4 + projects/mtg/template.vcxproj.filters | 12 + projects/mtg/wagic-SDL.pro | 1 + projects/mtg/wagic-qt.pro | 1 + 22 files changed, 2536 insertions(+), 2350 deletions(-) create mode 100644 projects/mtg/include/AIPlayerBaka.h create mode 100644 projects/mtg/src/AIPlayerBaka.cpp diff --git a/projects/mtg/Android/jni/Android.mk b/projects/mtg/Android/jni/Android.mk index 504992ed4..9c61ec269 100644 --- a/projects/mtg/Android/jni/Android.mk +++ b/projects/mtg/Android/jni/Android.mk @@ -34,6 +34,7 @@ LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.cpp \ $(MTG_PATH)/src/AIHints.cpp \ $(MTG_PATH)/src/AIMomirPlayer.cpp \ $(MTG_PATH)/src/AIPlayer.cpp \ + $(MTG_PATH)/src/AIPlayerBaka.cpp \ $(MTG_PATH)/src/AIStats.cpp \ $(MTG_PATH)/src/AllAbilities.cpp \ $(MTG_PATH)/src/CardDescriptor.cpp \ diff --git a/projects/mtg/Makefile b/projects/mtg/Makefile index 4725bea37..3630fee5b 100644 --- a/projects/mtg/Makefile +++ b/projects/mtg/Makefile @@ -1,4 +1,4 @@ -OBJS = objs/ActionElement.o objs/ActionLayer.o objs/ActionStack.o objs/AIHints.o objs/AIMomirPlayer.o objs/AIPlayer.o objs/AIStats.o objs/AllAbilities.o objs/CardGui.o objs/CardDescriptor.o objs/CardDisplay.o objs/CardEffect.o objs/CardPrimitive.o objs/CardSelector.o objs/CardSelectorSingleton.o objs/Counters.o objs/Credits.o objs/Damage.o objs/DamagerDamaged.o objs/DeckDataWrapper.o objs/DeckEditorMenu.o objs/DeckMenu.o objs/DeckMenuItem.o objs/DeckMetaData.o objs/DeckStats.o objs/DuelLayers.o objs/Effects.o objs/ExtraCost.o objs/GameApp.o objs/GameLauncher.o objs/GameObserver.o objs/GameOptions.o objs/GameState.o objs/GameStateAwards.o objs/GameStateDeckViewer.o objs/GameStateDuel.o objs/DeckManager.o objs/GameStateMenu.o objs/GameStateOptions.o objs/GameStateShop.o objs/GameStateStory.o objs/GameStateTransitions.o objs/GuiAvatars.o objs/GuiBackground.o objs/GuiCardsController.o objs/GuiCombat.o objs/GuiFrame.o objs/GuiHand.o objs/GuiLayers.o objs/GuiMana.o objs/GuiPhaseBar.o objs/GuiPlay.o objs/GuiStatic.o objs/IconButton.o objs/ManaCost.o objs/ManaCostHybrid.o objs/MenuItem.o objs/ModRules.o objs/MTGAbility.o objs/MTGCardInstance.o objs/MTGCard.o objs/MTGDeck.o objs/MTGDefinitions.o objs/MTGGamePhase.o objs/MTGGameZones.o objs/MTGPack.o objs/MTGRules.o objs/Navigator.o objs/ObjectAnalytics.o objs/OptionItem.o objs/PhaseRing.o objs/Player.o objs/PlayerData.o objs/PlayGuiObjectController.o objs/PlayGuiObject.o objs/PlayRestrictions.o objs/Pos.o objs/PrecompiledHeader.o objs/PriceList.o objs/ReplacementEffects.o objs/Rules.o objs/SimpleMenu.o objs/SimpleMenuItem.o objs/SimplePad.o objs/SimplePopup.o objs/StoryFlow.o objs/StyleManager.o objs/Subtypes.o objs/TargetChooser.o objs/TargetsList.o objs/TextScroller.o objs/ThisDescriptor.o objs/Token.o objs/Translate.o objs/TranslateKeys.o objs/Trash.o objs/utils.o objs/WEvent.o objs/WResourceManager.o objs/WCachedResource.o objs/WDataSrc.o objs/WGui.o objs/WFilter.o objs/Tasks.o objs/WFont.o +OBJS = objs/ActionElement.o objs/ActionLayer.o objs/ActionStack.o objs/AIHints.o objs/AIMomirPlayer.o objs/AIPlayer.o objs/AIPlayerBaka.o objs/AIStats.o objs/AllAbilities.o objs/CardGui.o objs/CardDescriptor.o objs/CardDisplay.o objs/CardEffect.o objs/CardPrimitive.o objs/CardSelector.o objs/CardSelectorSingleton.o objs/Counters.o objs/Credits.o objs/Damage.o objs/DamagerDamaged.o objs/DeckDataWrapper.o objs/DeckEditorMenu.o objs/DeckMenu.o objs/DeckMenuItem.o objs/DeckMetaData.o objs/DeckStats.o objs/DuelLayers.o objs/Effects.o objs/ExtraCost.o objs/GameApp.o objs/GameLauncher.o objs/GameObserver.o objs/GameOptions.o objs/GameState.o objs/GameStateAwards.o objs/GameStateDeckViewer.o objs/GameStateDuel.o objs/DeckManager.o objs/GameStateMenu.o objs/GameStateOptions.o objs/GameStateShop.o objs/GameStateStory.o objs/GameStateTransitions.o objs/GuiAvatars.o objs/GuiBackground.o objs/GuiCardsController.o objs/GuiCombat.o objs/GuiFrame.o objs/GuiHand.o objs/GuiLayers.o objs/GuiMana.o objs/GuiPhaseBar.o objs/GuiPlay.o objs/GuiStatic.o objs/IconButton.o objs/ManaCost.o objs/ManaCostHybrid.o objs/MenuItem.o objs/ModRules.o objs/MTGAbility.o objs/MTGCardInstance.o objs/MTGCard.o objs/MTGDeck.o objs/MTGDefinitions.o objs/MTGGamePhase.o objs/MTGGameZones.o objs/MTGPack.o objs/MTGRules.o objs/Navigator.o objs/ObjectAnalytics.o objs/OptionItem.o objs/PhaseRing.o objs/Player.o objs/PlayerData.o objs/PlayGuiObjectController.o objs/PlayGuiObject.o objs/PlayRestrictions.o objs/Pos.o objs/PrecompiledHeader.o objs/PriceList.o objs/ReplacementEffects.o objs/Rules.o objs/SimpleMenu.o objs/SimpleMenuItem.o objs/SimplePad.o objs/SimplePopup.o objs/StoryFlow.o objs/StyleManager.o objs/Subtypes.o objs/TargetChooser.o objs/TargetsList.o objs/TextScroller.o objs/ThisDescriptor.o objs/Token.o objs/Translate.o objs/TranslateKeys.o objs/Trash.o objs/utils.o objs/WEvent.o objs/WResourceManager.o objs/WCachedResource.o objs/WDataSrc.o objs/WGui.o objs/WFilter.o objs/Tasks.o objs/WFont.o DEPS = $(patsubst objs/%.o, deps/%.d, $(OBJS)) RESULT = $(shell psp-config --psp-prefix 2> Makefile.cache) diff --git a/projects/mtg/bin/Res.txt b/projects/mtg/bin/Res.txt index d3f5a12fa..e259ae40a 100644 --- a/projects/mtg/bin/Res.txt +++ b/projects/mtg/bin/Res.txt @@ -1 +1 @@ - +../../wagic_res/ diff --git a/projects/mtg/include/AIHints.h b/projects/mtg/include/AIHints.h index ce1ed428c..4bd4dd788 100644 --- a/projects/mtg/include/AIHints.h +++ b/projects/mtg/include/AIHints.h @@ -6,7 +6,7 @@ using std::string; using std::vector; -#include "AIPlayer.h" +#include "AIPlayerBaka.h" class ManaCost; class MTGAbility; @@ -24,7 +24,7 @@ public: class AIHints { protected: - AIPlayer * mPlayer; + AIPlayerBaka * mPlayer; vector hints; AIHint * getByCondition (string condition); AIAction * findAbilityRecursive(AIHint * hint, ManaCost * potentialMana); @@ -34,7 +34,7 @@ protected: bool findSource(int sourceId); bool abilityMatches(MTGAbility * a, AIHint * hint); public: - AIHints (AIPlayer * player); + AIHints (AIPlayerBaka * player); AIAction * suggestAbility(ManaCost * potentialMana); void add(string line); ~AIHints(); diff --git a/projects/mtg/include/AIMomirPlayer.h b/projects/mtg/include/AIMomirPlayer.h index 603573c68..3693ccc8d 100644 --- a/projects/mtg/include/AIMomirPlayer.h +++ b/projects/mtg/include/AIMomirPlayer.h @@ -1,13 +1,13 @@ #ifndef _AIMOMIRPLAYER_H_ #define _AIMOMIRPLAYER_H_ -#include "AIPlayer.h" +#include "AIPlayerBaka.h" class AIMomirPlayer: public AIPlayerBaka { public: AIMomirPlayer(string file, string fileSmall, string avatarFile, MTGDeck * deck = NULL); - int getEfficiency(AIAction * action); + int getEfficiency(OrderedAIAction * action); int momir(); int computeActions(); static MTGAbility * momirAbility; diff --git a/projects/mtg/include/AIPlayer.h b/projects/mtg/include/AIPlayer.h index 9dcadefd4..2ff0ce3d3 100644 --- a/projects/mtg/include/AIPlayer.h +++ b/projects/mtg/include/AIPlayer.h @@ -2,6 +2,14 @@ * Wagic, The Homebrew ?! is licensed under the BSD license * See LICENSE in the Folder's root * http://wololo.net/wagic/ + + AIPlayer is the interface to represent a CPU Player. + At its core, AIPlayer inherits from Player, and its children need to implement the function "Act" which + pretty much handles all the logic. + A sample implementation can be found in AIPlayerBaka. + + Ideally, mid-term, AIPlayer will handle all the mechanical tasks (clicking on cards, etc...) while its children are just in charge of the logic + */ #ifndef _IAPLAYER_H @@ -12,20 +20,11 @@ #include using std::queue; -#define INFO_NBCREATURES 0 -#define INFO_CREATURESPOWER 1 -#define INFO_CREATURESRANK 2 -#define INFO_CREATURESTOUGHNESS 3 -#define INFO_CREATURESATTACKINGPOWER 4 - - class AIStats; -class AIHints; class AIAction { protected: - int efficiency; static int currentId; public: MTGAbility * ability; @@ -39,7 +38,7 @@ public: //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),playerAbilityTarget(NULL) + : ability(a), player(NULL), click(c), target(t),playerAbilityTarget(NULL) { id = currentId++; }; @@ -47,110 +46,57 @@ public: AIAction(MTGCardInstance * c, MTGCardInstance * t = NULL); AIAction(Player * p)//player targeting through spells - : efficiency(-1), ability(NULL), player(p), click(NULL), target(NULL),playerAbilityTarget(NULL) + : 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) + : 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) + : ability(a), click(c),target(NULL), playerAbilityTarget(p) { id = currentId++; }; - int getEfficiency(); int Act(); int clickMultiAct(vector&actionTargets); }; -// compares Abilities efficiency -class CmpAbilities -{ -public: - bool operator()(const AIAction& a1, const AIAction& a2) const - { - AIAction* a1Ptr = const_cast(&a1); - AIAction* a2Ptr = const_cast(&a2); - int e1 = a1Ptr->getEfficiency(); - int e2 = a2Ptr->getEfficiency(); - if (e1 == e2) return a1Ptr->id < a2Ptr->id; - return (e1 > e2); - } -}; -typedef std::map RankingContainer; class AIPlayer: public Player{ protected: - //Variables used by Test suite - MTGCardInstance * nextCardToPlay; - AIHints * hints; queue clickstream; - bool payTheManaCost(ManaCost * cost, MTGCardInstance * card = NULL,vector gotPayment = vector()); - int orderBlockers(); - int combatDamages(); - int interruptIfICan(); - int chooseAttackers(); - int chooseBlockers(); - int canFirstStrikeKill(MTGCardInstance * card, MTGCardInstance *ennemy); - int effectBadOrGood(MTGCardInstance * card, int mode = MODE_PUTINTOPLAY, TargetChooser * tc = NULL); - int getCreaturesInfo(Player * player, int neededInfo = INFO_NBCREATURES , int untapMode = 0, int canAttack = 0); - AIStats * getStats(); - - // returns 1 if the AI algorithm supports a given cost (ex:simple mana cost), 0 otherwise (ex: cost involves Sacrificing a target) - int CanHandleCost(ManaCost * cost); - - //Tries to play an ability recommended by the deck creator - int selectHintAbility(); + int clickMultiTarget(TargetChooser * tc,vector&potentialTargets); + int clickSingleTarget(TargetChooser * tc,vector&potentialTargets, MTGCardInstance * Choosencard = NULL); public: - AIStats * stats; + + //These variables are used by TestSuite and Rules.cpp... TODO change that? int agressivity; bool forceBestAbilityUse; + void End(){}; virtual int displayStack() {return 0;}; - int receiveEvent(WEvent * event); - void Render(); - ManaCost * getPotentialMana(MTGCardInstance * card = NULL); - vector canPayMana(MTGCardInstance * card = NULL,ManaCost * mCost = NULL); - vector canPaySunBurst(ManaCost * mCost = NULL); + virtual int receiveEvent(WEvent * event); + virtual void Render(); + AIPlayer(string deckFile, string deckFileSmall, MTGDeck * deck = NULL); virtual ~AIPlayer(); - virtual MTGCardInstance * chooseCard(TargetChooser * tc, MTGCardInstance * source, int random = 0); - 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); + + virtual int chooseTarget(TargetChooser * tc = NULL, Player * forceTarget = NULL, MTGCardInstance * Chosencard = NULL, bool checkonly = false) = 0; + virtual int affectCombatDamages(CombatStep) = 0; + virtual int Act(float dt) = 0; + int isAI(){return 1;}; - int canHandleCost(MTGAbility * ability); - int selectAbility(); - int createAbilityTargets(MTGAbility * a, MTGCardInstance * c, RankingContainer& ranking); - int useAbility(); - virtual int getEfficiency(AIAction * action); + }; -class AIPlayerBaka: public AIPlayer{ - protected: - int oldGamePhase; - 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); - void initTimer(); - virtual int computeActions(); -}; - class AIPlayerFactory{ public: AIPlayer * createAIPlayer(MTGAllCards * collection, Player * opponent, int deckid = 0); diff --git a/projects/mtg/include/AIPlayerBaka.h b/projects/mtg/include/AIPlayerBaka.h new file mode 100644 index 000000000..c29d117a7 --- /dev/null +++ b/projects/mtg/include/AIPlayerBaka.h @@ -0,0 +1,131 @@ +#ifndef _AI_PLAYER_BAKA_H_ +#define _AI_PLAYER_BAKA_H_ + +#include "AIPlayer.h" + +class AIStats; +class AIHints; + + +//Would love to define those classes as private nested classes inside of AIPlayerBaka, but they are used by AIHints (which itself should be known only by AIPlayerBaka anyways) +// Any way to clean this up and make all the AIPlayerBaka universe (AIHints, AIPlayerBaka, OrderedAIAction) "closed" ? +class OrderedAIAction: public AIAction +{ +protected: + int efficiency; + +public: + + + OrderedAIAction(MTGAbility * a, MTGCardInstance * c, MTGCardInstance * t = NULL) + : AIAction(a, c, t), efficiency(-1) + { + }; + + OrderedAIAction(MTGCardInstance * c, MTGCardInstance * t = NULL); + + OrderedAIAction(Player * p)//player targeting through spells + : AIAction(p), efficiency(-1) + { + }; + + OrderedAIAction(MTGAbility * a, MTGCardInstance * c, vectortargetCards) + : AIAction(a, c, targetCards), efficiency(-1) + { + }; + + OrderedAIAction(MTGAbility * a, Player * p, MTGCardInstance * c)//player targeting through abilities. + : AIAction(a, p, c), efficiency(-1) + { + }; + int getEfficiency(); +}; + +// compares Abilities efficiency +class CmpAbilities +{ +public: + bool operator()(const OrderedAIAction& a1, const OrderedAIAction& a2) const + { + OrderedAIAction* a1Ptr = const_cast(&a1); + OrderedAIAction* a2Ptr = const_cast(&a2); + int e1 = a1Ptr->getEfficiency(); + int e2 = a2Ptr->getEfficiency(); + if (e1 == e2) return a1Ptr->id < a2Ptr->id; + return (e1 > e2); + } +}; + +typedef std::map RankingContainer; + + +class AIPlayerBaka: public AIPlayer{ +private: + + MTGCardInstance * nextCardToPlay; + AIHints * hints; + AIStats * stats; + + int orderBlockers(); + int combatDamages(); + int interruptIfICan(); + int chooseAttackers(); + int chooseBlockers(); + int canFirstStrikeKill(MTGCardInstance * card, MTGCardInstance *ennemy); + int effectBadOrGood(MTGCardInstance * card, int mode = MODE_PUTINTOPLAY, TargetChooser * tc = NULL); + + + // returns 1 if the AI algorithm supports a given cost (ex:simple mana cost), 0 otherwise (ex: cost involves Sacrificing a target) + int CanHandleCost(ManaCost * cost); + + //Tries to play an ability recommended by the deck creator + int selectHintAbility(); + + vector canPayMana(MTGCardInstance * card = NULL,ManaCost * mCost = NULL); + vector canPaySunBurst(ManaCost * mCost = NULL); + + MTGCardInstance * chooseCard(TargetChooser * tc, MTGCardInstance * source, int random = 0); + int selectMenuOption(); + int useAbility(); + + AIStats * getStats(); + + protected: + int oldGamePhase; + float timer; + MTGCardInstance * FindCardToPlay(ManaCost * potentialMana, const char * type); + + //used by MomirPlayer, hence protected instead of private + virtual int getEfficiency(OrderedAIAction * action); + bool payTheManaCost(ManaCost * cost, MTGCardInstance * card = NULL,vector gotPayment = vector()); + int getCreaturesInfo(Player * player, int neededInfo = INFO_NBCREATURES , int untapMode = 0, int canAttack = 0); + ManaCost * getPotentialMana(MTGCardInstance * card = NULL); + int selectAbility(); + + public: + enum { + INFO_NBCREATURES, + INFO_CREATURESPOWER, + INFO_CREATURESRANK, + INFO_CREATURESTOUGHNESS, + INFO_CREATURESATTACKINGPOWER + }; + + vectorgotPayments; + int deckId; + AIPlayerBaka(string deckFile, string deckfileSmall, string avatarFile, MTGDeck * deck = NULL); + virtual int Act(float dt); + void initTimer(); + virtual int computeActions(); + virtual void Render(); + virtual int receiveEvent(WEvent * event); + virtual ~AIPlayerBaka(); + int affectCombatDamages(CombatStep step); + int canHandleCost(MTGAbility * ability); + int chooseTarget(TargetChooser * tc = NULL, Player * forceTarget =NULL,MTGCardInstance * Choosencard = NULL,bool checkonly = false); + + //used by AIHInts, therefore public instead of private :/ + int createAbilityTargets(MTGAbility * a, MTGCardInstance * c, RankingContainer& ranking); +}; + +#endif \ No newline at end of file diff --git a/projects/mtg/include/TestSuiteAI.h b/projects/mtg/include/TestSuiteAI.h index e42bf2df6..ce21d7a37 100644 --- a/projects/mtg/include/TestSuiteAI.h +++ b/projects/mtg/include/TestSuiteAI.h @@ -6,7 +6,7 @@ #define MAX_TESTSUITE_ACTIONS 100 #define MAX_TESTUITE_CARDS 100 -#include "AIPlayer.h" +#include "AIPlayerBaka.h" class TestSuiteActions { @@ -95,6 +95,7 @@ public: }; +// TODO This should inherit from AIPlayer instead! class TestSuiteAI:public AIPlayerBaka { private: diff --git a/projects/mtg/src/AIHints.cpp b/projects/mtg/src/AIHints.cpp index b3f64f39b..5d2e750a4 100644 --- a/projects/mtg/src/AIHints.cpp +++ b/projects/mtg/src/AIHints.cpp @@ -1,7 +1,7 @@ #include "PrecompiledHeader.h" #include "AIHints.h" -#include "AIPlayer.h" +#include "AIPlayerBaka.h" #include "utils.h" #include "AllAbilities.h" @@ -33,7 +33,7 @@ AIHint::AIHint(string _line) } } -AIHints::AIHints(AIPlayer * player): mPlayer(player) +AIHints::AIHints(AIPlayerBaka * player): mPlayer(player) { } diff --git a/projects/mtg/src/AIMomirPlayer.cpp b/projects/mtg/src/AIMomirPlayer.cpp index 15238c5ad..d11d15930 100644 --- a/projects/mtg/src/AIMomirPlayer.cpp +++ b/projects/mtg/src/AIMomirPlayer.cpp @@ -15,7 +15,7 @@ AIMomirPlayer::AIMomirPlayer(string file, string fileSmall, string avatarFile, M agressivity = 100; } -int AIMomirPlayer::getEfficiency(AIAction * action) +int AIMomirPlayer::getEfficiency(OrderedAIAction * action) { MTGAbility * ability = action->ability; ManaCost * cost = ability->getCost(); diff --git a/projects/mtg/src/AIPlayer.cpp b/projects/mtg/src/AIPlayer.cpp index bd1bdacf0..9314a99da 100644 --- a/projects/mtg/src/AIPlayer.cpp +++ b/projects/mtg/src/AIPlayer.cpp @@ -1,23 +1,20 @@ #include "PrecompiledHeader.h" #include "AIPlayer.h" -#include "CardDescriptor.h" -#include "CardSelectorSingleton.h" -#include "AIStats.h" -#include "AllAbilities.h" -#include "ExtraCost.h" -#include "GuiCombat.h" #include "GameStateDuel.h" #include "DeckManager.h" -#include "AIHints.h" -#include "ManaCostHybrid.h" +#include "CardSelectorSingleton.h" + + +// Instances for Factory +#include "AIPlayerBaka.h" const char * const MTG_LAND_TEXTS[] = { "artifact", "forest", "island", "mountain", "swamp", "plains", "other lands" }; int AIAction::currentId = 0; AIAction::AIAction(MTGCardInstance * c, MTGCardInstance * t) - : efficiency(-1), ability(NULL), player(NULL), click(c), target(t) + : ability(NULL), player(NULL), click(c), target(t) { id = currentId++; @@ -123,50 +120,24 @@ int AIAction::clickMultiAct(vector& actionTargets) AIPlayer::AIPlayer(string file, string fileSmall, MTGDeck * deck) : Player(file, fileSmall, deck) { - nextCardToPlay = NULL; - stats = NULL; agressivity = 50; forceBestAbilityUse = false; playMode = Player::MODE_AI; - //Initialize "AIHints" system - hints = NULL; - if (mDeck && mDeck->meta_AIHints.size()) - { - hints = NEW AIHints(this); - for (size_t i = 0; i < mDeck->meta_AIHints.size(); ++i) - hints->add(mDeck->meta_AIHints[i]); - } } AIPlayer::~AIPlayer() { - if (stats) - { - stats->save(); - SAFE_DELETE(stats); - } while (!clickstream.empty()) { AIAction * action = clickstream.front(); SAFE_DELETE(action); clickstream.pop(); } - SAFE_DELETE(hints); -} -MTGCardInstance * AIPlayer::chooseCard(TargetChooser * tc, MTGCardInstance * source, int random) -{ - for (int i = 0; i < game->hand->nb_cards; i++) - { - MTGCardInstance * card = game->hand->cards[i]; - if (!tc->alreadyHasTarget(card) && tc->canTarget(card)) - { - return card; - } - } - return NULL; + } + int AIPlayer::Act(float dt) { GameObserver * gameObs = GameObserver::GetInstance(); @@ -175,1494 +146,10 @@ int AIPlayer::Act(float dt) return 1; } -bool AIPlayer::payTheManaCost(ManaCost * cost, MTGCardInstance * target,vectorgotPayments) -{ - 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 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 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 = 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(); - vectorclicks = vector(); - - paid->init(); - 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; - return false; - } - ManaCost * diff = pMana->Diff(cost); - delete (pMana); - GameObserver * g = GameObserver::GetInstance(); - - map used; - for (size_t i = 1; i < g->mLayers->actionLayer()->mObjects.size(); i++) - { //0 is not a mtgability...hackish - //Make sure we can use the ability - MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->mObjects[i]); - AManaProducer * amp = dynamic_cast (a); - 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) - { - used[card] = true; - int doUse = 1; - for (int i = Constants::MTG_NB_COLORS - 1; i >= 0; i--) - { - if (diff->getCost(i) && amp->output->getCost(i)) - { - diff->remove(i, 1); - doUse = 0; - break; - } - } - if (doUse) - { - AIAction * action = NEW AIAction(amp, card); - clickstream.push(action); - } - } - } - } - delete (diff); - return true; -} - -ManaCost * AIPlayer::getPotentialMana(MTGCardInstance * target) -{ - ManaCost * result = NEW ManaCost(); - GameObserver * g = GameObserver::GetInstance(); - map used; - 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()->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) - {//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. - result->add(this->getManaPool()); - } - 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) -{ - return action->getEfficiency(); -} - -//Can't yet handle extraCost objects (ex: sacrifice) if they require a target :( -int AIPlayer::CanHandleCost(ManaCost * cost) -{ - if (!cost) - return 1; - - ExtraCosts * ec = cost->extraCosts; - if (!ec) - return 1; - - for (size_t i = 0; i < ec->costs.size(); ++i) - { - if (ec->costs[i]->tc) - { - return 0; - } - } - return 1; -} - -int AIPlayer::canHandleCost(MTGAbility * ability) -{ - return CanHandleCost(ability->getCost()); -} - -// In this function, target represents the target of the currentAIAction object, while _target is the target of the ability of this AIAction object -// I can't remember as I type this in which condition we use one or the other for this function, if you find out please replace this comment -int AIAction::getEfficiency() -{ - if (efficiency > -1) - return efficiency; - if (!ability) - return 0; - GameObserver * g = GameObserver::GetInstance(); - ActionStack * s = g->mLayers->stackLayer(); - Player * p = g->currentlyActing(); - if (s->has(ability)) - return 0; - MTGAbility * a = AbilityFactory::getCoreAbility(ability); - if (!a) - { - DebugTrace("FATAL: Ability is NULL in AIAction::getEfficiency()"); - return 0; - } - - if (!((AIPlayer *) p)->canHandleCost(ability)) - return 0; - switch (a->aType) - { - case MTGAbility::DAMAGER: - { - AADamager * aad = (AADamager *) a; - MTGCardInstance * dTarget = (MTGCardInstance*)target; - if(!target && !playerAbilityTarget) - { - 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()) - { - efficiency = 0; - } - else if (aad->getDamage() >= dTarget->toughness) - { - efficiency = 100; - } - else if (dTarget->toughness) - { - efficiency = (50 * aad->getDamage()) / dTarget->toughness; - } - else - { - efficiency = 0; - } - break; - } - case MTGAbility::STANDARD_REGENERATE: - { - MTGCardInstance * _target = (MTGCardInstance *) (a->target); - efficiency = 0; - if (!_target) - break; - - if (!_target->regenerateTokens && g->getCurrentGamePhase() == Constants::MTG_PHASE_COMBATBLOCKERS - && (_target->defenser || _target->blockers.size()) - ) - { - efficiency = 95; - } - //TODO If the card is the target of a damage spell - break; - } - case MTGAbility::STANDARD_PREVENT: - { - efficiency = 0;//starts out low to avoid spamming it when its not needed. - if (!target && !dynamic_cast (a)) - break; - if(dynamic_cast (a) && !target) - { - //this is a special case for all(this) targetting workaround. - //adding a direct method for targetting the source is planned for - //the coming releases, all(this) workaround prevents eff from being returned - //as its not targetted the same as abilities - //for now this dirty hack will calculate eff on lords as tho the source is - //the target...otherwise these abilities will never be used. - target = a->source; - } - - bool NeedPreventing; - NeedPreventing = false; - if (g->getCurrentGamePhase() == Constants::MTG_PHASE_COMBATBLOCKERS) - { - if(target->getNextOpponent() && !target->getNextOpponent()->typeAsTarget() == TARGET_CARD) - break; - if ((target->defenser || target->blockers.size()) && target->preventable < target->getNextOpponent()->power) - NeedPreventing = true; - if (p == target->controller() && target->controller()->isAI() && NeedPreventing && !(target->getNextOpponent()->has(Constants::DEATHTOUCH) - || target->getNextOpponent()->has(Constants::WITHER))) - { - efficiency = 20 * (target->DangerRanking());//increase this chance to be used in combat if the creature blocking/blocked could kill the creature this chance is taking into consideration how good the creature is, best creature will always be the first "saved".. - if (target->toughness == 1 && target->getNextOpponent()->power == 1) - efficiency += 15; - //small bonus added for the poor 1/1s, if we can save them, we will unless something else took precidence. - //note is the target is being blocked or blocking a creature with wither or deathtouch, it is not even considered for preventing as it is a waste. - //if its combat blockers, it is being blocked or blocking, and has less prevents the the amount of damage it will be taking, the effeincy is increased slightly and totalled by the danger rank multiplier for final result. - int calculateAfterDamage = 0; - int damages = 0; - if((target->defenser || target->blockers.size()) && target->controller() == p) - { - damages = target->getNextOpponent()->power; - calculateAfterDamage = int(target->toughness - damages); - if((calculateAfterDamage + target->preventable) > 0) - { - efficiency = 0; - //this is to avoid wasting prevents on creatures that will already survive. - //this should take into account bushido and flanking as this check is run after every trigger. - } - } - } - } - //TODO If the card is the target of a damage spell - break; - } - case MTGAbility::STANDARD_EQUIP: - { - - efficiency = 0; - if (!target) - break; - - int equips = p->game->battlefield->countByType("Equipment"); - int myArmy = p->game->battlefield->countByType("Creature"); - // when can this ever be negative? - int equilized = myArmy ? abs(equips / myArmy) : 0; - - if (p == target->controller() && target->equipment <= 1 && !a->source->target) - { - efficiency = 20 * (target->DangerRanking()); - if (target->hasColor(Constants::MTG_COLOR_WHITE)) - efficiency += 20;//this is to encourage Ai to equip white creatures in a weenie deck. ultimately it will depend on what had the higher dangerranking. - if (target->power == 1 && target->toughness == 1 && target->isToken == 0) - efficiency += 10; //small bonus to encourage equipping nontoken 1/1 creatures. - } - - if (p == target->controller() && !a->source->target && target->equipment < equilized) - { - efficiency = 15 * (target->DangerRanking()); - efficiency -= 5 * (target->equipment); - } - break; - } - case MTGAbility::STANDARD_LEVELUP: - { - MTGCardInstance * _target = (MTGCardInstance *) (a->target); - efficiency = 0; - Counter * targetCounter = NULL; - int currentlevel = 0; - - if (_target) - { - if (_target->counters && _target->counters->hasCounter("level", 0, 0)) - { - targetCounter = _target->counters->hasCounter("level", 0, 0); - currentlevel = targetCounter->nb; - } - if (currentlevel < _target->MaxLevelUp) - { - efficiency = 85; - //increase the efficeincy of leveling up by a small amount equal to current level. - efficiency += currentlevel; - - if (p->game->hand->nb_cards > 0 && p->isAI()) - { - efficiency -= (10 * p->game->hand->nb_cards);//reduce the eff if by 10 times the amount of cards in Ais hand. - //it should always try playing more cards before deciding - } - - if (g->getCurrentGamePhase() == Constants::MTG_PHASE_SECONDMAIN) - { - efficiency = 100; - //in 2nd main, go all out and try to max stuff. - } - } - } - break; - } - case MTGAbility::COUNTERS: - { - MTGCardInstance * _target = (MTGCardInstance *) target; - if(!_target) - _target = (MTGCardInstance *) (a->target); - efficiency = 0; - if(AACounter * cc = dynamic_cast (a)) - { - if(cc && _target) - { - if(_target->controller() == p && cc->toughness>=0) - { - efficiency = 90; - - } - if(_target->controller() != p && ((_target->toughness + cc->toughness <= 0 && _target->toughness) || (cc->toughness < 0 && cc->power < 0))) - { - efficiency = 90; - - } - if(_target->counters && _target->counters->hasCounter(cc->power,cc->toughness) && _target->counters->hasCounter(cc->power,cc->toughness)->nb > 15) - { - efficiency = _target->counters->hasCounter(cc->power,cc->toughness)->nb; - } - 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; - } - case MTGAbility::STANDARD_PUMP: - { - MTGCardInstance * _target = (MTGCardInstance *) (a->target); - efficiency = 0; - if(!_target) - break; - if(!target && !dynamic_cast (a) && (((MTGCardInstance *)a->source)->hasSubtype(Subtypes::TYPE_AURA) || ((MTGCardInstance *)a->source)->hasSubtype(Subtypes::TYPE_EQUIPMENT))) - { - if(((MTGCardInstance *)a->source)->target) - _target = ((MTGCardInstance *)a->source)->target; - target = (MTGCardInstance *)a->source; - } - if (!target && !dynamic_cast (a)) - break; - if(dynamic_cast (a) && !target) - { - target = a->source; - } - AbilityFactory af; - int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); - //i do not set a starting eff. on this ability, this allows Ai to sometimes randomly do it as it normally does. - int currentPhase = g->getCurrentGamePhase(); - if ((currentPhase == Constants::MTG_PHASE_COMBATBLOCKERS) || (currentPhase == Constants::MTG_PHASE_COMBATATTACKERS)) - { - if (suggestion == BAKA_EFFECT_GOOD && target->controller() == p) - { - if(_target->defenser || _target->blockers.size()) - { - if(!_target->getNextOpponent()->typeAsTarget() == TARGET_CARD) - break; - if (_target->power < _target->getNextOpponent()->toughness ||(_target->getNextOpponent() && _target->toughness < _target->getNextOpponent()->power) || (_target->has(Constants::TRAMPLE))) - { - //this pump is based on a start eff. of 20 multiplied by how good the creature is. - efficiency = 20 * _target->DangerRanking(); - } - } - if (_target->isAttacker() && !_target->blockers.size()) - { - //this means im heading directly for the player, pump this creature as much as possible. - efficiency = 100; - if(_target->power > 50) - efficiency -= _target->power;//we don't need to go overboard. better to not put all your eggs in a single basket. - } - } - } - if (suggestion == BAKA_EFFECT_BAD && target->controller() != p && target->toughness > 0) - { - efficiency = 100; - } - break; - } - case MTGAbility::STANDARD_BECOMES: - { - MTGCardInstance * _target = (MTGCardInstance *) (a->target); - //nothing huge here, just ensuring that Ai makes his noncreature becomers into creatures during first main, so it can actually use them in combat. - if (_target && !_target->isCreature() && g->getCurrentGamePhase() == Constants::MTG_PHASE_FIRSTMAIN) - { - efficiency = 100; - } - break; - } - case MTGAbility::FOREACH: - case MTGAbility::MANA_PRODUCER://only way to hit this condition is nested manaabilities, ai skips manaproducers by defualt when finding an ability to use. - { - MTGCardInstance * _target = (MTGCardInstance *) (a->target); - MTGAbility * a = AbilityFactory::getCoreAbility(ability); - AManaProducer * amp = dynamic_cast(a); - efficiency = 0; - //trying to encourage Ai to use his foreach manaproducers in first main - if (amp && amp->output && amp->output->getConvertedCost() && (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)) - && amp->output->hasColor(i)) - { - - efficiency = 100; - } - } - - if (amp->getCost() && amp->getCost()->getConvertedCost() && p->game->hand->hasX()) - efficiency = 100; - - } - else - { - AbilityFactory af; - int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); - - if (target && a->naType != MTGAbility::MANA_PRODUCER && ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) - || (suggestion == BAKA_EFFECT_GOOD && p != target->controller()))) - { - efficiency = 0; - } - else if (a->naType != MTGAbility::MANA_PRODUCER && (g->getCurrentGamePhase() == Constants::MTG_PHASE_FIRSTMAIN - || g->getCurrentGamePhase() == Constants::MTG_PHASE_SECONDMAIN)) - { - //if its not a manaproducing foreach, and its not targetted, its eff is 90. - //added this basically to cover the unknown foreachs, or untrained ones which were not targetted effects. - efficiency = 90; - } - - } - break; - } - - case MTGAbility::STANDARDABILITYGRANT: - { - efficiency = 0; - MTGCardInstance * _target = (MTGCardInstance*)(a->target); - if(!_target) - break; - if (!target && !dynamic_cast (a)) - break; - if(dynamic_cast (a) && !target) - { - target = a->source; - } - - //ensuring that Ai grants abilities to creatures during first main, so it can actually use them in combat. - //quick note: the eff is multiplied by creatures ranking then divided by the number of cards in hand. - //the reason i do this is to encourage more casting and less waste of mana on abilities. - AbilityFactory af; - int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); - - int efficiencyModifier = (25 * target->DangerRanking()); - if (p->game->hand->nb_cards > 1) - { - efficiencyModifier -= p->game->hand->nb_cards*3; - } - if (suggestion == BAKA_EFFECT_BAD && p != target->controller() && !target->has(a->abilitygranted)) - { - efficiency += efficiencyModifier; - } - - if (!target->has(a->abilitygranted) && g->getCurrentGamePhase() == Constants::MTG_PHASE_COMBATBEGIN - && p == target->controller() - ) - { - efficiency += efficiencyModifier; - } - - if (suggestion == BAKA_EFFECT_GOOD && target->has(a->abilitygranted)) - { - //trying to avoid Ai giving ie:flying creatures ie:flying twice. - efficiency = 0; - } - - if ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) - || (suggestion == BAKA_EFFECT_GOOD && p != target->controller()) - ) - { - efficiency = 0; - //stop giving trample to the players creatures. - } - break; - } - - case MTGAbility::UNTAPPER: - //untap things that Ai owns and are tapped. - { - efficiency = 0; - if (!target && !dynamic_cast (a)) - break; - if(dynamic_cast (a) && !target) - { - target = a->source; - } - if (target->isTapped() && target->controller() == p) - { - target->isCreature()?efficiency = (20 * target->DangerRanking()):efficiency = 100; - } - break; - } - - case MTGAbility::TAPPER: - //tap things the player owns and that are untapped. - { - if (!target && !dynamic_cast (a)) - break; - if(dynamic_cast (a) && !target) - { - target = a->source; - } - - if (target->controller() != p) - efficiency = (20 * target->DangerRanking()); - - if (target->isTapped()) - efficiency = 0; - - break; - } - - case MTGAbility::LIFER: - { - //use life abilities whenever possible. - AALifer * alife = (AALifer *) a; - Targetable * _t = alife->getTarget(); - - efficiency = 100; - AbilityFactory af; - int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); - - if ((suggestion == BAKA_EFFECT_BAD && _t == p) || (suggestion == BAKA_EFFECT_GOOD && _t != p)) - { - efficiency = 0; - } - - break; - } - case MTGAbility::STANDARD_DRAW: - { - AADrawer * drawer = (AADrawer *)a; - //adding this case since i played a few games where Ai litterally decided to mill himself to death. fastest and easiest win ever. - //this should help a little, tho ultimately it will be decided later what the best course of action is. - //eff of drawing ability is calculated by base 20 + the amount of cards in library minus the amount of cards in hand times 7. - //drawing is never going to return a hundred eff because later eff is multiplied by 1.3 if no cards in hand. - efficiency = int(20 + p->game->library->nb_cards) - int(p->game->hand->nb_cards * 7); - if (p->game->hand->nb_cards > 8)//reduce by 50 if cards in hand are over 8, high chance ai cant play them. - { - efficiency -= 70; - } - if ((drawer->getNumCards() >= p->game->library->nb_cards && (Targetable*)p == drawer->getTarget()) || (p->game->hand->nb_cards > 10 && (Targetable*)p == drawer->getTarget())) - { - //if the amount im drawing will mill me to death or i have more then 10 cards in hand, eff is 0; - efficiency = 0; - } - break; - } - case MTGAbility::CLONING: - { - efficiency = 0; - 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: - { - 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())) - { - efficiency = 0; - } - else - { - //without a base to start with Wrand % 5 almost always returns 0. - efficiency = 10 + (WRand() % 20); //Small percentage of chance for unknown abilities - } - } - else - { - efficiency = 10 + (WRand() % 30); - } - break; - } - if(AUpkeep * auk = dynamic_cast(ability)) - { - //hello, Ai pay your upcost please :P, this entices Ai into paying upcost, the conditional isAi() is required strangely ai is able to pay upcost during YOUR upkeep. - if (auk && g->getCurrentGamePhase() == Constants::MTG_PHASE_UPKEEP && g->currentPlayer == p && p == a->source->controller()) - { - efficiency = 100; - } - } - 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(); - if (cost) - { - ExtraCosts * ec = cost->extraCosts; - if (ec) - { - for(unsigned int i = 0; i < ec->costs.size();i++) - { - ExtraCost * tapper = dynamic_cast(ec->costs[i]); - if(tapper) - continue; - else - efficiency = efficiency / 2; - } - //Decrease chance of using ability if there is an extra cost to use the ability, ignore tap - } - } - return efficiency; -} - -int AIPlayer::createAbilityTargets(MTGAbility * a, MTGCardInstance * c, RankingContainer& ranking) -{ - 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 }; - 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->getActionTc()->canTarget(t)) - { - 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; -} - -int AIPlayer::selectHintAbility() -{ - if (!hints) - 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 (payTheManaCost(action->ability->getCost(), action->click)) - { - clickstream.push(action); - SAFE_DELETE(totalPotentialMana); - return 1; - } - } - } - SAFE_DELETE(action); - SAFE_DELETE(totalPotentialMana); - return 0; -} - -int AIPlayer::selectAbility() -{ - static bool findingAbility = false; - //this guard is put in place to prevent Ai from - //ever running selectAbility() function WHILE its already doing so. - - // Break if this happens in debug mode. If this happens, it's actually a bug - assert(!findingAbility); - - if (findingAbility) - {//is already looking kick me out of this function! - return 0; - } - findingAbility = true;//im looking now safely! - // Try Deck hints first - if (selectHintAbility()) - { - 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]); - //Skip mana abilities for performance - if (dynamic_cast (a)) - continue; - //Make sure we can use the ability - for (int j = 0; j < game->inPlay->nb_cards; j++) - { - MTGCardInstance * card = game->inPlay->cards[j]; - 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 - 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; - int chance = 1; - 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 (payTheManaCost(action.ability->getCost(), action.click,abilityPayment)) - clickstream.push(NEW AIAction(action)); - } - } - } - findingAbility = false;//ok to start looking again. - abilityPayment.clear(); - return 1; -} - -int AIPlayer::interruptIfICan() -{ - GameObserver * g = GameObserver::GetInstance(); - - if (g->mLayers->stackLayer()->askIfWishesToInterrupt == this) - { - if (!clickstream.empty()) - g->mLayers->stackLayer()->cancelInterruptOffer(); - else - return g->mLayers->stackLayer()->setIsInterrupting(this); - } - return 0; -} - -int AIPlayer::effectBadOrGood(MTGCardInstance * card, int mode, TargetChooser * tc) -{ - int id = card->getMTGId(); - AbilityFactory af; - int autoGuess = af.magicText(id, NULL, card, mode, tc); - if (autoGuess) - return autoGuess; - return BAKA_EFFECT_DONTKNOW; -} - -int AIPlayer::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCardInstance * Choosencard,bool checkOnly) -{ - vector potentialTargets; - TargetChooser * tc = _tc; - int nbtargets = 0; - GameObserver * gameObs = GameObserver::GetInstance(); - if (!(gameObs->currentlyActing() == this)) - return 0; - if (!tc) - { - tc = gameObs->getCurrentTargetChooser(); - } - if (!tc || !tc->source || tc->maxtargets < 1) - return 0; - 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; - } - 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; -} - -int AIPlayer::clickMultiTarget(TargetChooser * tc,vector& potentialTargets) +int AIPlayer::clickMultiTarget(TargetChooser * tc, vector& potentialTargets) { bool sourceIncluded = false; for(int f = 0;f < int(potentialTargets.size());f++) @@ -1696,19 +183,19 @@ int AIPlayer::clickMultiTarget(TargetChooser * tc,vector& potential return 1; } -int AIPlayer::clickSingleTarget(TargetChooser * tc,vector& potentialTargets ,int nbtargets/*zeth:legacy, not my design*/,MTGCardInstance * Choosencard) +int AIPlayer::clickSingleTarget(TargetChooser * tc, vector& potentialTargets, MTGCardInstance * chosenCard) { - int i = WRand() % nbtargets; + int i = WRand() % potentialTargets.size(); int type = potentialTargets[i]->typeAsTarget(); switch (type) { case TARGET_CARD: { - if(!Choosencard) + if(!chosenCard) { MTGCardInstance * card = ((MTGCardInstance *) potentialTargets[i]); clickstream.push(NEW AIAction(card)); - Choosencard = card; + chosenCard = card; } break; } @@ -1722,236 +209,6 @@ int AIPlayer::clickSingleTarget(TargetChooser * tc,vector& potentia return 1; } -int AIPlayer::getCreaturesInfo(Player * player, int neededInfo, int untapMode, int canAttack) -{ - int result = 0; - CardDescriptor cd; - cd.init(); - cd.setType("Creature"); - cd.unsecureSetTapped(untapMode); - MTGCardInstance * card = NULL; - while ((card = cd.nextmatch(player->game->inPlay, card))) - { - if (!canAttack || card->canAttack()) - { - if (neededInfo == INFO_NBCREATURES) - { - result++; - } - else - { - result += card->power; - } - } - } - return result; -} - -int AIPlayer::chooseAttackers() -{ - //Attack with all creatures - //How much damage can the other player do during his next Attack ? - int opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER); - int opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES); - int myForce = getCreaturesInfo(this, INFO_CREATURESPOWER, -1, 1); - int myCreatures = getCreaturesInfo(this, INFO_NBCREATURES, -1, 1); - bool attack = ((myCreatures > opponentCreatures) || (myForce > opponentForce) || (myForce > 2 * opponent()->life)); - if (agressivity > 80 && !attack && life > opponentForce) - { - opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES, -1); - opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER, -1); - attack = (myCreatures >= opponentCreatures && myForce > opponentForce) - || (myForce > opponentForce) || (myForce > opponent()->life); - } - printf("Choose attackers : %i %i %i %i -> %i\n", opponentForce, opponentCreatures, myForce, myCreatures, attack); - if (attack) - { - CardDescriptor cd; - cd.init(); - cd.setType("creature"); - MTGCardInstance * card = NULL; - GameObserver * g = GameObserver::GetInstance(); - MTGAbility * a = g->mLayers->actionLayer()->getAbility(MTGAbility::MTG_ATTACK_RULE); - while ((card = cd.nextmatch(game->inPlay, card))) - { - g->mLayers->actionLayer()->reactToClick(a, card); - } - } - return 1; -} - -/* Can I first strike my oponent and get away with murder ? */ -int AIPlayer::canFirstStrikeKill(MTGCardInstance * card, MTGCardInstance *ennemy) -{ - if (ennemy->has(Constants::FIRSTSTRIKE) || ennemy->has(Constants::DOUBLESTRIKE)) - return 0; - if (!(card->has(Constants::FIRSTSTRIKE) || card->has(Constants::DOUBLESTRIKE))) - return 0; - if (!(card->power >= ennemy->toughness)) - return 0; - if (!(card->power >= ennemy->toughness + 1) && ennemy->has(Constants::FLANKING)) - return 0; - return 1; -} - -int AIPlayer::chooseBlockers() -{ - GameObserver * g = GameObserver::GetInstance(); - - //Should not block during my own turn... - if (g->currentPlayer == this) - return 0; - map opponentsToughness; - int opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER); - - //Initialize the list of opponent's attacking cards toughness - CardDescriptor cdAttackers; - cdAttackers.init(); - cdAttackers.setType("Creature"); - MTGCardInstance * card = NULL; - while ((card = cdAttackers.nextmatch(opponent()->game->inPlay, card))) - { - if (card->isAttacker()) - opponentsToughness[card] = card->toughness; - } - - //A Descriptor to find untapped creatures in our game - CardDescriptor cd; - cd.init(); - cd.setType("Creature"); - cd.unsecureSetTapped(-1); - card = NULL; - MTGAbility * a = g->mLayers->actionLayer()->getAbility(MTGAbility::MTG_BLOCK_RULE); - - // We first try to block the major threats, those that are marked in the Top 3 of our stats - while ((card = cd.nextmatch(game->inPlay, card))) - { - g->mLayers->actionLayer()->reactToClick(a, card); - int set = 0; - while (!set) - { - if (!card->defenser) - { - set = 1; - } - else - { - MTGCardInstance * attacker = card->defenser; - map::iterator it = opponentsToughness.find(attacker); - if (it == opponentsToughness.end()) - { - opponentsToughness[attacker] = attacker->toughness; - it = opponentsToughness.find(attacker); - } - if (opponentsToughness[attacker] > 0 && getStats() && getStats()->isInTop(attacker, 3, false)) - { - opponentsToughness[attacker] -= card->power; - set = 1; - } - else - { - g->mLayers->actionLayer()->reactToClick(a, card); - } - } - } - } - - //If blocking one of the major threats is not enough to kill it, - // We change strategy, first we unassign its blockers that where assigned above - card = NULL; - while ((card = cd.nextmatch(game->inPlay, card))) - { - if (card->defenser && opponentsToughness[card->defenser] > 0) - { - while (card->defenser) - { - g->mLayers->actionLayer()->reactToClick(a, card); - } - } - } - - //Assign the "free" potential blockers to attacking creatures that are not blocked enough - card = NULL; - while ((card = cd.nextmatch(game->inPlay, card))) - { - if (!card->defenser) - { - g->mLayers->actionLayer()->reactToClick(a, card); - int set = 0; - while (!set) - { - if (!card->defenser) - { - set = 1; - } - else - { - MTGCardInstance * attacker = card->defenser; - if (opponentsToughness[attacker] <= 0 || (card->toughness <= attacker->power && opponentForce * 2 < life - && !canFirstStrikeKill(card, attacker)) || attacker->nbOpponents() > 1) - { - g->mLayers->actionLayer()->reactToClick(a, card); - } - else - { - set = 1; - } - } - } - } - } - selectAbility(); - return 1; -} - -int AIPlayer::orderBlockers() -{ - - GameObserver * g = GameObserver::GetInstance(); - if (ORDER == g->combatStep && g->currentPlayer == this) - { - DebugTrace("AIPLAYER: order blockers"); - g->userRequestNextGamePhase(); //TODO clever rank of blockers - return 1; - } - - return 0; -} - -int AIPlayer::affectCombatDamages(CombatStep step) -{ - GameObserver * g = GameObserver::GetInstance(); - GuiCombat * gc = g->mLayers->combatLayer(); - for (vector::iterator attacker = gc->attackers.begin(); attacker != gc->attackers.end(); ++attacker) - gc->autoaffectDamage(*attacker, step); - return 1; -} - -//TODO: Deprecate combatDamages -int AIPlayer::combatDamages() -{ - int currentGamePhase = GameObserver::GetInstance()->getCurrentGamePhase(); - - if (currentGamePhase == Constants::MTG_PHASE_COMBATBLOCKERS) - return orderBlockers(); - - if (currentGamePhase != Constants::MTG_PHASE_COMBATDAMAGE) - return 0; - - return 0; - -} - -AIStats * AIPlayer::getStats() -{ - if (!stats) - { - char statFile[512]; - sprintf(statFile, "ai/baka/stats/%s.stats", opponent()->deckFileSmall.c_str()); - stats = NEW AIStats(this, statFile); - } - return stats; -} AIPlayer * AIPlayerFactory::createAIPlayer(MTGAllCards * collection, Player * opponent, int deckid) { @@ -2009,451 +266,8 @@ AIPlayer * AIPlayerFactory::createAIPlayer(MTGAllCards * collection, Player * op return baka; } -MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * type) -{ - int maxCost = -1; - MTGCardInstance * nextCardToPlay = NULL; - MTGCardInstance * card = NULL; - CardDescriptor cd; - cd.init(); - cd.setType(type); - card = NULL; - gotPayments = vector(); - while ((card = cd.nextmatch(game->hand, card))) - { - if (!CanHandleCost(card->getManaCost())) - continue; - - if (card->hasType(Subtypes::TYPE_LAND)) - { - if (game->playRestrictions->canPutIntoZone(card, game->inPlay) == PlayRestriction::CANT_PLAY) - continue; - } - else - { - if (game->playRestrictions->canPutIntoZone(card, game->stack) == PlayRestriction::CANT_PLAY) - continue; - } - - if (card->hasType(Subtypes::TYPE_LEGENDARY) && game->inPlay->findByName(card->name)) - continue; - - int currentCost = card->getManaCost()->getConvertedCost(); - int hasX = card->getManaCost()->hasX(); - 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,NULL,NULL,true); - if( - (tc->maxtargets > hasTarget && tc->maxtargets > 1 && !tc->targetMin && tc->maxtargets != TargetChooser::UNLITMITED_TARGETS) ||//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 - { - int shouldPlay = effectBadOrGood(card); - if (shouldPlay == BAKA_EFFECT_GOOD) - { - shouldPlayPercentage = 90; - } - else if (BAKA_EFFECT_DONTKNOW == shouldPlay) - { - shouldPlayPercentage = 80; - } - } - //Reduce the chances of playing a spell with X cost if available mana is low - if (hasX) - { - int xDiff = pMana->getConvertedCost() - currentCost; - if (xDiff < 0) - 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; - maxCost = currentCost; - if (hasX) - maxCost = pMana->getConvertedCost(); - } - } - if(nextCardToPlay) - { - DebugTrace(" AI wants to play card." << endl - << "- Next card to play: " << (nextCardToPlay ? nextCardToPlay->name : "None" ) << endl ); - } - return nextCardToPlay; -} - -AIPlayerBaka::AIPlayerBaka(string file, string fileSmall, string avatarFile, MTGDeck * deck) : - AIPlayer(file, fileSmall, deck) -{ - - mAvatarTex = WResourceManager::Instance()->RetrieveTexture(avatarFile, RETRIEVE_LOCK, TEXTURE_SUB_AVATAR); - if (!mAvatarTex) - { - avatarFile = "baka.jpg"; - mAvatarTex = WResourceManager::Instance()->RetrieveTexture(avatarFile, RETRIEVE_LOCK, TEXTURE_SUB_AVATAR); - } - - if (mAvatarTex) - mAvatar = WResourceManager::Instance()->RetrieveQuad(avatarFile, 0, 0, 35, 50, "bakaAvatar", RETRIEVE_NORMAL, - TEXTURE_SUB_AVATAR); - - if (fileSmall == "ai_baka_eviltwin") - mAvatar->SetHFlip(true); - - initTimer(); -} - -void AIPlayerBaka::initTimer() -{ - timer = 0.1f; -} -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 = (Player*)this; - Player * currentP = g->currentlyActing(); - if (!(g->currentlyActing() == p)) - return 0; - ActionLayer * object = g->mLayers->actionLayer(); - if (object->menuObject) - { - 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; - } - TargetChooser * currentTc = g->getCurrentTargetChooser(); - if(currentTc) - { - int targetResult = currentTc->Owner == this? chooseTarget():0; - if (targetResult) - return 1; - } - - static bool findingCard = false; - //this guard is put in place to prevent Ai from - //ever running computeActions() function WHILE its already doing so. - // Break if this happens in debug mode. If this happens, it's actually a bug - assert(!findingCard); - if (findingCard) - {//is already looking kick me out of this function! - return 0; - } - 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 ((interruptIfICan() || g->isInterrupting == this) - && 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. - { - bool ipotential = false; - if(p->game->hand->hasType("instant") || p->game->hand->hasAbility(Constants::FLASH)) - { - 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) - { - selectAbility(); - } - if (nextCardToPlay) - { - if (ipotential) - { - if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay,gotPayments)) - { - AIAction * a = NEW AIAction(nextCardToPlay); - clickstream.push(a); - gotPayments.clear(); - } - } - findingCard = false; - nextCardToPlay = NULL; - return 1; - } - nextCardToPlay = NULL; - findingCard = false; - return 1; - } - else if(p == this && g->mLayers->stackLayer()->count(0, NOT_RESOLVED) == 0) - { //standard actions - 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"); - //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; - count++; - } - - SAFE_DELETE(currentMana); - if (nextCardToPlay) - { - 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. - vectorchecking = canPaySunBurst(nextCardToPlay->getManaCost()); - if(payTheManaCost(nextCardToPlay->getManaCost(),NULL,checking)) - { - AIAction * a = NEW AIAction(nextCardToPlay); - clickstream.push(a); - return 1; - } - nextCardToPlay = NULL; - gotPayments.clear();//if any. - return 1; - } - if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay,gotPayments)) - { - AIAction * a = NEW AIAction(nextCardToPlay); - clickstream.push(a); - gotPayments.clear(); - } - return 1; - } - else - { - selectAbility(); - } - break; - } - case Constants::MTG_PHASE_COMBATATTACKERS: - { - 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: - break; - } - } - else - { - cout << "my turn" << endl; - 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: - { - selectAbility(); - break; - } - default: - break; - } - return 1; - } - return 1; -} -; - -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()) - return getStats()->receiveEvent(event); return 0; } @@ -2464,61 +278,5 @@ void AIPlayer::Render() #endif } -int AIPlayerBaka::Act(float dt) -{ - GameObserver * g = GameObserver::GetInstance(); - if (!(g->currentlyActing() == this)) - { - return 0; - } - int currentGamePhase = g->getCurrentGamePhase(); - - oldGamePhase = currentGamePhase; - - timer -= dt; - if (timer > 0) - { - return 0; - } - initTimer(); - if (combatDamages()) - { - return 0; - } - interruptIfICan(); - - //computeActions only when i have priority - if (!(g->currentlyActing() == this)) - { - DebugTrace("Cannot interrupt"); - return 0; - } - if (clickstream.empty()) - computeActions(); - if (clickstream.empty()) - { - if (g->isInterrupting == this) - { - g->mLayers->stackLayer()->cancelInterruptOffer(); //endOfInterruption(); - } - 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/AIPlayerBaka.cpp b/projects/mtg/src/AIPlayerBaka.cpp new file mode 100644 index 000000000..6e67d1993 --- /dev/null +++ b/projects/mtg/src/AIPlayerBaka.cpp @@ -0,0 +1,2298 @@ +#include "PrecompiledHeader.h" + +#include "AIPlayerBaka.h" +#include "CardDescriptor.h" +#include "AIStats.h" +#include "AllAbilities.h" +#include "ExtraCost.h" +#include "GuiCombat.h" +#include "AIHints.h" +#include "ManaCostHybrid.h" + + +// +// AIAction +// + +// In this function, target represents the target of the currentAIAction object, while _target is the target of the ability of this AIAction object +// I can't remember as I type this in which condition we use one or the other for this function, if you find out please replace this comment +int OrderedAIAction::getEfficiency() +{ + if (efficiency > -1) + return efficiency; + if (!ability) + return 0; + GameObserver * g = GameObserver::GetInstance(); + ActionStack * s = g->mLayers->stackLayer(); + Player * p = g->currentlyActing(); + if (s->has(ability)) + return 0; + MTGAbility * a = AbilityFactory::getCoreAbility(ability); + if (!a) + { + DebugTrace("FATAL: Ability is NULL in AIAction::getEfficiency()"); + return 0; + } + + if (!((AIPlayerBaka *) p)->canHandleCost(ability)) + return 0; + switch (a->aType) + { + case MTGAbility::DAMAGER: + { + AADamager * aad = (AADamager *) a; + MTGCardInstance * dTarget = (MTGCardInstance*)target; + if(!target && !playerAbilityTarget) + { + 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()) + { + efficiency = 0; + } + else if (aad->getDamage() >= dTarget->toughness) + { + efficiency = 100; + } + else if (dTarget->toughness) + { + efficiency = (50 * aad->getDamage()) / dTarget->toughness; + } + else + { + efficiency = 0; + } + break; + } + case MTGAbility::STANDARD_REGENERATE: + { + MTGCardInstance * _target = (MTGCardInstance *) (a->target); + efficiency = 0; + if (!_target) + break; + + if (!_target->regenerateTokens && g->getCurrentGamePhase() == Constants::MTG_PHASE_COMBATBLOCKERS + && (_target->defenser || _target->blockers.size()) + ) + { + efficiency = 95; + } + //TODO If the card is the target of a damage spell + break; + } + case MTGAbility::STANDARD_PREVENT: + { + efficiency = 0;//starts out low to avoid spamming it when its not needed. + if (!target && !dynamic_cast (a)) + break; + if(dynamic_cast (a) && !target) + { + //this is a special case for all(this) targetting workaround. + //adding a direct method for targetting the source is planned for + //the coming releases, all(this) workaround prevents eff from being returned + //as its not targetted the same as abilities + //for now this dirty hack will calculate eff on lords as tho the source is + //the target...otherwise these abilities will never be used. + target = a->source; + } + + bool NeedPreventing; + NeedPreventing = false; + if (g->getCurrentGamePhase() == Constants::MTG_PHASE_COMBATBLOCKERS) + { + if(target->getNextOpponent() && !target->getNextOpponent()->typeAsTarget() == TARGET_CARD) + break; + if ((target->defenser || target->blockers.size()) && target->preventable < target->getNextOpponent()->power) + NeedPreventing = true; + if (p == target->controller() && target->controller()->isAI() && NeedPreventing && !(target->getNextOpponent()->has(Constants::DEATHTOUCH) + || target->getNextOpponent()->has(Constants::WITHER))) + { + efficiency = 20 * (target->DangerRanking());//increase this chance to be used in combat if the creature blocking/blocked could kill the creature this chance is taking into consideration how good the creature is, best creature will always be the first "saved".. + if (target->toughness == 1 && target->getNextOpponent()->power == 1) + efficiency += 15; + //small bonus added for the poor 1/1s, if we can save them, we will unless something else took precidence. + //note is the target is being blocked or blocking a creature with wither or deathtouch, it is not even considered for preventing as it is a waste. + //if its combat blockers, it is being blocked or blocking, and has less prevents the the amount of damage it will be taking, the effeincy is increased slightly and totalled by the danger rank multiplier for final result. + int calculateAfterDamage = 0; + int damages = 0; + if((target->defenser || target->blockers.size()) && target->controller() == p) + { + damages = target->getNextOpponent()->power; + calculateAfterDamage = int(target->toughness - damages); + if((calculateAfterDamage + target->preventable) > 0) + { + efficiency = 0; + //this is to avoid wasting prevents on creatures that will already survive. + //this should take into account bushido and flanking as this check is run after every trigger. + } + } + } + } + //TODO If the card is the target of a damage spell + break; + } + case MTGAbility::STANDARD_EQUIP: + { + + efficiency = 0; + if (!target) + break; + + int equips = p->game->battlefield->countByType("Equipment"); + int myArmy = p->game->battlefield->countByType("Creature"); + // when can this ever be negative? + int equilized = myArmy ? abs(equips / myArmy) : 0; + + if (p == target->controller() && target->equipment <= 1 && !a->source->target) + { + efficiency = 20 * (target->DangerRanking()); + if (target->hasColor(Constants::MTG_COLOR_WHITE)) + efficiency += 20;//this is to encourage Ai to equip white creatures in a weenie deck. ultimately it will depend on what had the higher dangerranking. + if (target->power == 1 && target->toughness == 1 && target->isToken == 0) + efficiency += 10; //small bonus to encourage equipping nontoken 1/1 creatures. + } + + if (p == target->controller() && !a->source->target && target->equipment < equilized) + { + efficiency = 15 * (target->DangerRanking()); + efficiency -= 5 * (target->equipment); + } + break; + } + case MTGAbility::STANDARD_LEVELUP: + { + MTGCardInstance * _target = (MTGCardInstance *) (a->target); + efficiency = 0; + Counter * targetCounter = NULL; + int currentlevel = 0; + + if (_target) + { + if (_target->counters && _target->counters->hasCounter("level", 0, 0)) + { + targetCounter = _target->counters->hasCounter("level", 0, 0); + currentlevel = targetCounter->nb; + } + if (currentlevel < _target->MaxLevelUp) + { + efficiency = 85; + //increase the efficeincy of leveling up by a small amount equal to current level. + efficiency += currentlevel; + + if (p->game->hand->nb_cards > 0 && p->isAI()) + { + efficiency -= (10 * p->game->hand->nb_cards);//reduce the eff if by 10 times the amount of cards in Ais hand. + //it should always try playing more cards before deciding + } + + if (g->getCurrentGamePhase() == Constants::MTG_PHASE_SECONDMAIN) + { + efficiency = 100; + //in 2nd main, go all out and try to max stuff. + } + } + } + break; + } + case MTGAbility::COUNTERS: + { + MTGCardInstance * _target = (MTGCardInstance *) target; + if(!_target) + _target = (MTGCardInstance *) (a->target); + efficiency = 0; + if(AACounter * cc = dynamic_cast (a)) + { + if(cc && _target) + { + if(_target->controller() == p && cc->toughness>=0) + { + efficiency = 90; + + } + if(_target->controller() != p && ((_target->toughness + cc->toughness <= 0 && _target->toughness) || (cc->toughness < 0 && cc->power < 0))) + { + efficiency = 90; + + } + if(_target->counters && _target->counters->hasCounter(cc->power,cc->toughness) && _target->counters->hasCounter(cc->power,cc->toughness)->nb > 15) + { + efficiency = _target->counters->hasCounter(cc->power,cc->toughness)->nb; + } + 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; + } + case MTGAbility::STANDARD_PUMP: + { + MTGCardInstance * _target = (MTGCardInstance *) (a->target); + efficiency = 0; + if(!_target) + break; + if(!target && !dynamic_cast (a) && (((MTGCardInstance *)a->source)->hasSubtype(Subtypes::TYPE_AURA) || ((MTGCardInstance *)a->source)->hasSubtype(Subtypes::TYPE_EQUIPMENT))) + { + if(((MTGCardInstance *)a->source)->target) + _target = ((MTGCardInstance *)a->source)->target; + target = (MTGCardInstance *)a->source; + } + if (!target && !dynamic_cast (a)) + break; + if(dynamic_cast (a) && !target) + { + target = a->source; + } + AbilityFactory af; + int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); + //i do not set a starting eff. on this ability, this allows Ai to sometimes randomly do it as it normally does. + int currentPhase = g->getCurrentGamePhase(); + if ((currentPhase == Constants::MTG_PHASE_COMBATBLOCKERS) || (currentPhase == Constants::MTG_PHASE_COMBATATTACKERS)) + { + if (suggestion == BAKA_EFFECT_GOOD && target->controller() == p) + { + if(_target->defenser || _target->blockers.size()) + { + if(!_target->getNextOpponent()->typeAsTarget() == TARGET_CARD) + break; + if (_target->power < _target->getNextOpponent()->toughness ||(_target->getNextOpponent() && _target->toughness < _target->getNextOpponent()->power) || (_target->has(Constants::TRAMPLE))) + { + //this pump is based on a start eff. of 20 multiplied by how good the creature is. + efficiency = 20 * _target->DangerRanking(); + } + } + if (_target->isAttacker() && !_target->blockers.size()) + { + //this means im heading directly for the player, pump this creature as much as possible. + efficiency = 100; + if(_target->power > 50) + efficiency -= _target->power;//we don't need to go overboard. better to not put all your eggs in a single basket. + } + } + } + if (suggestion == BAKA_EFFECT_BAD && target->controller() != p && target->toughness > 0) + { + efficiency = 100; + } + break; + } + case MTGAbility::STANDARD_BECOMES: + { + MTGCardInstance * _target = (MTGCardInstance *) (a->target); + //nothing huge here, just ensuring that Ai makes his noncreature becomers into creatures during first main, so it can actually use them in combat. + if (_target && !_target->isCreature() && g->getCurrentGamePhase() == Constants::MTG_PHASE_FIRSTMAIN) + { + efficiency = 100; + } + break; + } + case MTGAbility::FOREACH: + case MTGAbility::MANA_PRODUCER://only way to hit this condition is nested manaabilities, ai skips manaproducers by defualt when finding an ability to use. + { + MTGCardInstance * _target = (MTGCardInstance *) (a->target); + MTGAbility * a = AbilityFactory::getCoreAbility(ability); + AManaProducer * amp = dynamic_cast(a); + efficiency = 0; + //trying to encourage Ai to use his foreach manaproducers in first main + if (amp && amp->output && amp->output->getConvertedCost() && (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)) + && amp->output->hasColor(i)) + { + + efficiency = 100; + } + } + + if (amp->getCost() && amp->getCost()->getConvertedCost() && p->game->hand->hasX()) + efficiency = 100; + + } + else + { + AbilityFactory af; + int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); + + if (target && a->naType != MTGAbility::MANA_PRODUCER && ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) + || (suggestion == BAKA_EFFECT_GOOD && p != target->controller()))) + { + efficiency = 0; + } + else if (a->naType != MTGAbility::MANA_PRODUCER && (g->getCurrentGamePhase() == Constants::MTG_PHASE_FIRSTMAIN + || g->getCurrentGamePhase() == Constants::MTG_PHASE_SECONDMAIN)) + { + //if its not a manaproducing foreach, and its not targetted, its eff is 90. + //added this basically to cover the unknown foreachs, or untrained ones which were not targetted effects. + efficiency = 90; + } + + } + break; + } + + case MTGAbility::STANDARDABILITYGRANT: + { + efficiency = 0; + MTGCardInstance * _target = (MTGCardInstance*)(a->target); + if(!_target) + break; + if (!target && !dynamic_cast (a)) + break; + if(dynamic_cast (a) && !target) + { + target = a->source; + } + + //ensuring that Ai grants abilities to creatures during first main, so it can actually use them in combat. + //quick note: the eff is multiplied by creatures ranking then divided by the number of cards in hand. + //the reason i do this is to encourage more casting and less waste of mana on abilities. + AbilityFactory af; + int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); + + int efficiencyModifier = (25 * target->DangerRanking()); + if (p->game->hand->nb_cards > 1) + { + efficiencyModifier -= p->game->hand->nb_cards*3; + } + if (suggestion == BAKA_EFFECT_BAD && p != target->controller() && !target->has(a->abilitygranted)) + { + efficiency += efficiencyModifier; + } + + if (!target->has(a->abilitygranted) && g->getCurrentGamePhase() == Constants::MTG_PHASE_COMBATBEGIN + && p == target->controller() + ) + { + efficiency += efficiencyModifier; + } + + if (suggestion == BAKA_EFFECT_GOOD && target->has(a->abilitygranted)) + { + //trying to avoid Ai giving ie:flying creatures ie:flying twice. + efficiency = 0; + } + + if ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) + || (suggestion == BAKA_EFFECT_GOOD && p != target->controller()) + ) + { + efficiency = 0; + //stop giving trample to the players creatures. + } + break; + } + + case MTGAbility::UNTAPPER: + //untap things that Ai owns and are tapped. + { + efficiency = 0; + if (!target && !dynamic_cast (a)) + break; + if(dynamic_cast (a) && !target) + { + target = a->source; + } + if (target->isTapped() && target->controller() == p) + { + target->isCreature()?efficiency = (20 * target->DangerRanking()):efficiency = 100; + } + break; + } + + case MTGAbility::TAPPER: + //tap things the player owns and that are untapped. + { + if (!target && !dynamic_cast (a)) + break; + if(dynamic_cast (a) && !target) + { + target = a->source; + } + + if (target->controller() != p) + efficiency = (20 * target->DangerRanking()); + + if (target->isTapped()) + efficiency = 0; + + break; + } + + case MTGAbility::LIFER: + { + //use life abilities whenever possible. + AALifer * alife = (AALifer *) a; + Targetable * _t = alife->getTarget(); + + efficiency = 100; + AbilityFactory af; + int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); + + if ((suggestion == BAKA_EFFECT_BAD && _t == p) || (suggestion == BAKA_EFFECT_GOOD && _t != p)) + { + efficiency = 0; + } + + break; + } + case MTGAbility::STANDARD_DRAW: + { + AADrawer * drawer = (AADrawer *)a; + //adding this case since i played a few games where Ai litterally decided to mill himself to death. fastest and easiest win ever. + //this should help a little, tho ultimately it will be decided later what the best course of action is. + //eff of drawing ability is calculated by base 20 + the amount of cards in library minus the amount of cards in hand times 7. + //drawing is never going to return a hundred eff because later eff is multiplied by 1.3 if no cards in hand. + efficiency = int(20 + p->game->library->nb_cards) - int(p->game->hand->nb_cards * 7); + if (p->game->hand->nb_cards > 8)//reduce by 50 if cards in hand are over 8, high chance ai cant play them. + { + efficiency -= 70; + } + if ((drawer->getNumCards() >= p->game->library->nb_cards && (Targetable*)p == drawer->getTarget()) || (p->game->hand->nb_cards > 10 && (Targetable*)p == drawer->getTarget())) + { + //if the amount im drawing will mill me to death or i have more then 10 cards in hand, eff is 0; + efficiency = 0; + } + break; + } + case MTGAbility::CLONING: + { + efficiency = 0; + 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: + { + 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())) + { + efficiency = 0; + } + else + { + //without a base to start with Wrand % 5 almost always returns 0. + efficiency = 10 + (WRand() % 20); //Small percentage of chance for unknown abilities + } + } + else + { + efficiency = 10 + (WRand() % 30); + } + break; + } + if(AUpkeep * auk = dynamic_cast(ability)) + { + //hello, Ai pay your upcost please :P, this entices Ai into paying upcost, the conditional isAi() is required strangely ai is able to pay upcost during YOUR upkeep. + if (auk && g->getCurrentGamePhase() == Constants::MTG_PHASE_UPKEEP && g->currentPlayer == p && p == a->source->controller()) + { + efficiency = 100; + } + } + 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(); + if (cost) + { + ExtraCosts * ec = cost->extraCosts; + if (ec) + { + for(unsigned int i = 0; i < ec->costs.size();i++) + { + ExtraCost * tapper = dynamic_cast(ec->costs[i]); + if(tapper) + continue; + else + efficiency = efficiency / 2; + } + //Decrease chance of using ability if there is an extra cost to use the ability, ignore tap + } + } + return efficiency; +} + +int AIPlayerBaka::getEfficiency(OrderedAIAction * action) +{ + return action->getEfficiency(); +} + +// +// Abilities/Target Selection +// + + +MTGCardInstance * AIPlayerBaka::chooseCard(TargetChooser * tc, MTGCardInstance * source, int random) +{ + for (int i = 0; i < game->hand->nb_cards; i++) + { + MTGCardInstance * card = game->hand->cards[i]; + if (!tc->alreadyHasTarget(card) && tc->canTarget(card)) + { + return card; + } + } + return NULL; +} + +bool AIPlayerBaka::payTheManaCost(ManaCost * cost, MTGCardInstance * target,vectorgotPayments) +{ + 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 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 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 = 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(); + vectorclicks = vector(); + + paid->init(); + 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; + return false; + } + ManaCost * diff = pMana->Diff(cost); + delete (pMana); + GameObserver * g = GameObserver::GetInstance(); + + map used; + for (size_t i = 1; i < g->mLayers->actionLayer()->mObjects.size(); i++) + { //0 is not a mtgability...hackish + //Make sure we can use the ability + MTGAbility * a = ((MTGAbility *) g->mLayers->actionLayer()->mObjects[i]); + AManaProducer * amp = dynamic_cast (a); + 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) + { + used[card] = true; + int doUse = 1; + for (int i = Constants::MTG_NB_COLORS - 1; i >= 0; i--) + { + if (diff->getCost(i) && amp->output->getCost(i)) + { + diff->remove(i, 1); + doUse = 0; + break; + } + } + if (doUse) + { + AIAction * action = NEW AIAction(amp, card); + clickstream.push(action); + } + } + } + } + delete (diff); + return true; +} + +ManaCost * AIPlayerBaka::getPotentialMana(MTGCardInstance * target) +{ + ManaCost * result = NEW ManaCost(); + GameObserver * g = GameObserver::GetInstance(); + map used; + 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()->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) + {//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 AIPlayerBaka::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. + result->add(this->getManaPool()); + } + 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 AIPlayerBaka::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; +} + + +//Can't yet handle extraCost objects (ex: sacrifice) if they require a target :( +int AIPlayerBaka::CanHandleCost(ManaCost * cost) +{ + if (!cost) + return 1; + + ExtraCosts * ec = cost->extraCosts; + if (!ec) + return 1; + + for (size_t i = 0; i < ec->costs.size(); ++i) + { + if (ec->costs[i]->tc) + { + return 0; + } + } + return 1; +} + +int AIPlayerBaka::canHandleCost(MTGAbility * ability) +{ + return CanHandleCost(ability->getCost()); +} + + + + +int AIPlayerBaka::createAbilityTargets(MTGAbility * a, MTGCardInstance * c, RankingContainer& ranking) +{ + if (!a->getActionTc()) + { + OrderedAIAction 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 }; + if(a->getActionTc()->canTarget((Targetable*)p)) + { + if(a->getActionTc()->maxtargets == 1) + { + OrderedAIAction 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->getActionTc()->canTarget(t)) + { + if(a->getActionTc()->maxtargets == 1) + { + OrderedAIAction 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()) + { + OrderedAIAction * check = NULL; + + MTGCardInstance * targeting = dynamic_cast(potentialTargets[0]); + if(targeting && targeting->typeAsTarget() == TARGET_CARD) + check = NEW OrderedAIAction(a,c,targeting); + + Player * ptargeting = dynamic_cast(potentialTargets[0]); + if(ptargeting && ptargeting->typeAsTarget() == TARGET_PLAYER) + check = NEW OrderedAIAction(a,ptargeting,c); + + targetThis = getEfficiency(check); + if(targetThis && ptargeting && ptargeting->typeAsTarget() == TARGET_PLAYER) + { + OrderedAIAction 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; + OrderedAIAction aiAction(a, c,realTargets); + aiAction.target = (MTGCardInstance*)realTargets[0]; + ranking[aiAction] = 1; + } + return 1; +} + +int AIPlayerBaka::selectHintAbility() +{ + if (!hints) + 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 (payTheManaCost(action->ability->getCost(), action->click)) + { + clickstream.push(action); + SAFE_DELETE(totalPotentialMana); + return 1; + } + } + } + SAFE_DELETE(action); + SAFE_DELETE(totalPotentialMana); + return 0; +} + +int AIPlayerBaka::selectAbility() +{ + static bool findingAbility = false; + //this guard is put in place to prevent Ai from + //ever running selectAbility() function WHILE its already doing so. + + // Break if this happens in debug mode. If this happens, it's actually a bug + assert(!findingAbility); + + if (findingAbility) + {//is already looking kick me out of this function! + return 0; + } + findingAbility = true;//im looking now safely! + + + // Try Deck hints first + if (selectHintAbility()) + { + 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]); + //Skip mana abilities for performance + if (dynamic_cast (a)) + continue; + //Make sure we can use the ability + for (int j = 0; j < game->inPlay->nb_cards; j++) + { + MTGCardInstance * card = game->inPlay->cards[j]; + 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 + 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()) + { + OrderedAIAction action = ranking.begin()->first; + int chance = 1; + 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 (payTheManaCost(action.ability->getCost(), action.click,abilityPayment)) + clickstream.push(NEW AIAction(action)); + } + } + } + + findingAbility = false;//ok to start looking again. + abilityPayment.clear(); + return 1; +} + +int AIPlayerBaka::interruptIfICan() +{ + GameObserver * g = GameObserver::GetInstance(); + + if (g->mLayers->stackLayer()->askIfWishesToInterrupt == this) + { + if (!clickstream.empty()) + g->mLayers->stackLayer()->cancelInterruptOffer(); + else + return g->mLayers->stackLayer()->setIsInterrupting(this); + } + return 0; +} + +int AIPlayerBaka::effectBadOrGood(MTGCardInstance * card, int mode, TargetChooser * tc) +{ + int id = card->getMTGId(); + AbilityFactory af; + int autoGuess = af.magicText(id, NULL, card, mode, tc); + if (autoGuess) + return autoGuess; + return BAKA_EFFECT_DONTKNOW; +} + +int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCardInstance * chosenCard,bool checkOnly) +{ + vector potentialTargets; + TargetChooser * tc = _tc; + GameObserver * gameObs = GameObserver::GetInstance(); + if (!(gameObs->currentlyActing() == this)) + return 0; + if (!tc) + { + tc = gameObs->getCurrentTargetChooser(); + } + if (!tc || !tc->source || tc->maxtargets < 1) + return 0; + 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; + } + 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) && potentialTargets.size() < 50) + { + for (int i = 0; i < 3; i++) + { //Increase probability to target a player when this is possible + potentialTargets.push_back(target); + } + } + 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) && potentialTargets.size() < 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); + OrderedAIAction * effCheck = NEW OrderedAIAction(withoutGuessing,(MTGCardInstance*)tc->source,card); + if(effCheck->getEfficiency()) + { + potentialTargets.push_back(card); + } + SAFE_DELETE(effCheck); + SAFE_DELETE(withoutGuessing); + } + else + { + potentialTargets.push_back(card); + } + } + } + } + } + if(playerTargetedZone > 1) + target = target->opponent(); + playerTargetedZone--; + } + if (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, chosenCard); + //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; +} + +int AIPlayerBaka::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; + OrderedAIAction * check = NULL; + checkEff = currentMenu->abilities[mk]; + if(checkEff) + { + if(checkEff->target && checkEff->target->typeAsTarget() == TARGET_CARD) + check = NEW OrderedAIAction(checkEff,checkEff->source,(MTGCardInstance*)checkEff->target); + else if(checkEff->target && checkEff->target->typeAsTarget() == TARGET_PLAYER) + check = NEW OrderedAIAction(checkEff,(Player*)checkEff->target,checkEff->source); + else + check = NEW OrderedAIAction(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; + OrderedAIAction * 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 OrderedAIAction(checkEff,checkEff->source,(MTGCardInstance*)checkTarget); + else if(checkTarget && checkTarget->typeAsTarget() == TARGET_PLAYER) + check = NEW OrderedAIAction(checkEff,(Player*)checkTarget,checkEff->source); + else + check = NEW OrderedAIAction(checkEff,checkEff->source); + } + if(check) + { + checked = getEfficiency(check); + SAFE_DELETE(check); + } + if(checked > 60 && checked > checkedLast) + { + doThis = k; + checkedLast = checked; + } + checked = 0; + } + } + } + return doThis; +} + +MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * type) +{ + int maxCost = -1; + MTGCardInstance * nextCardToPlay = NULL; + MTGCardInstance * card = NULL; + CardDescriptor cd; + cd.init(); + cd.setType(type); + card = NULL; + gotPayments = vector(); + while ((card = cd.nextmatch(game->hand, card))) + { + if (!CanHandleCost(card->getManaCost())) + continue; + + if (card->hasType(Subtypes::TYPE_LAND)) + { + if (game->playRestrictions->canPutIntoZone(card, game->inPlay) == PlayRestriction::CANT_PLAY) + continue; + } + else + { + if (game->playRestrictions->canPutIntoZone(card, game->stack) == PlayRestriction::CANT_PLAY) + continue; + } + + if (card->hasType(Subtypes::TYPE_LEGENDARY) && game->inPlay->findByName(card->name)) + continue; + + int currentCost = card->getManaCost()->getConvertedCost(); + int hasX = card->getManaCost()->hasX(); + 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,NULL,NULL,true); + if( + (tc->maxtargets > hasTarget && tc->maxtargets > 1 && !tc->targetMin && tc->maxtargets != TargetChooser::UNLITMITED_TARGETS) ||//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 + { + int shouldPlay = effectBadOrGood(card); + if (shouldPlay == BAKA_EFFECT_GOOD) + { + shouldPlayPercentage = 90; + } + else if (BAKA_EFFECT_DONTKNOW == shouldPlay) + { + shouldPlayPercentage = 80; + } + } + //Reduce the chances of playing a spell with X cost if available mana is low + if (hasX) + { + int xDiff = pMana->getConvertedCost() - currentCost; + if (xDiff < 0) + 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; + maxCost = currentCost; + if (hasX) + maxCost = pMana->getConvertedCost(); + } + } + if(nextCardToPlay) + { + DebugTrace(" AI wants to play card." << endl + << "- Next card to play: " << (nextCardToPlay ? nextCardToPlay->name : "None" ) << endl ); + } + return nextCardToPlay; +} + + +void AIPlayerBaka::initTimer() +{ + timer = 0.1f; +} + +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 = (Player*)this; + Player * currentP = g->currentlyActing(); + if (!(g->currentlyActing() == p)) + return 0; + ActionLayer * object = g->mLayers->actionLayer(); + if (object->menuObject) + { + 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; + } + TargetChooser * currentTc = g->getCurrentTargetChooser(); + if(currentTc) + { + int targetResult = currentTc->Owner == this? chooseTarget():0; + if (targetResult) + return 1; + } + + static bool findingCard = false; + //this guard is put in place to prevent Ai from + //ever running computeActions() function WHILE its already doing so. + // Break if this happens in debug mode. If this happens, it's actually a bug + assert(!findingCard); + if (findingCard) + {//is already looking kick me out of this function! + return 0; + } + 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 ((interruptIfICan() || g->isInterrupting == this) + && 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. + { + bool ipotential = false; + if(p->game->hand->hasType("instant") || p->game->hand->hasAbility(Constants::FLASH)) + { + 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) + { + selectAbility(); + } + if (nextCardToPlay) + { + if (ipotential) + { + if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay,gotPayments)) + { + AIAction * a = NEW AIAction(nextCardToPlay); + clickstream.push(a); + gotPayments.clear(); + } + } + findingCard = false; + nextCardToPlay = NULL; + return 1; + } + nextCardToPlay = NULL; + findingCard = false; + return 1; + } + else if(p == this && g->mLayers->stackLayer()->count(0, NOT_RESOLVED) == 0) + { //standard actions + 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"); + //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; + count++; + } + + SAFE_DELETE(currentMana); + if (nextCardToPlay) + { + 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. + vectorchecking = canPaySunBurst(nextCardToPlay->getManaCost()); + if(payTheManaCost(nextCardToPlay->getManaCost(),NULL,checking)) + { + AIAction * a = NEW AIAction(nextCardToPlay); + clickstream.push(a); + return 1; + } + nextCardToPlay = NULL; + gotPayments.clear();//if any. + return 1; + } + if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay,gotPayments)) + { + AIAction * a = NEW AIAction(nextCardToPlay); + clickstream.push(a); + gotPayments.clear(); + } + return 1; + } + else + { + selectAbility(); + } + break; + } + case Constants::MTG_PHASE_COMBATATTACKERS: + { + 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: + break; + } + } + else + { + cout << "my turn" << endl; + 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: + { + selectAbility(); + break; + } + default: + break; + } + return 1; + } + return 1; +}; + + +// +// Combat // +// + +int AIPlayerBaka::getCreaturesInfo(Player * player, int neededInfo, int untapMode, int canAttack) +{ + int result = 0; + CardDescriptor cd; + cd.init(); + cd.setType("Creature"); + cd.unsecureSetTapped(untapMode); + MTGCardInstance * card = NULL; + while ((card = cd.nextmatch(player->game->inPlay, card))) + { + if (!canAttack || card->canAttack()) + { + if (neededInfo == INFO_NBCREATURES) + { + result++; + } + else + { + result += card->power; + } + } + } + return result; +} + +int AIPlayerBaka::chooseAttackers() +{ + //Attack with all creatures + //How much damage can the other player do during his next Attack ? + int opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER); + int opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES); + int myForce = getCreaturesInfo(this, INFO_CREATURESPOWER, -1, 1); + int myCreatures = getCreaturesInfo(this, INFO_NBCREATURES, -1, 1); + bool attack = ((myCreatures > opponentCreatures) || (myForce > opponentForce) || (myForce > 2 * opponent()->life)); + if (agressivity > 80 && !attack && life > opponentForce) + { + opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES, -1); + opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER, -1); + attack = (myCreatures >= opponentCreatures && myForce > opponentForce) + || (myForce > opponentForce) || (myForce > opponent()->life); + } + printf("Choose attackers : %i %i %i %i -> %i\n", opponentForce, opponentCreatures, myForce, myCreatures, attack); + if (attack) + { + CardDescriptor cd; + cd.init(); + cd.setType("creature"); + MTGCardInstance * card = NULL; + GameObserver * g = GameObserver::GetInstance(); + MTGAbility * a = g->mLayers->actionLayer()->getAbility(MTGAbility::MTG_ATTACK_RULE); + while ((card = cd.nextmatch(game->inPlay, card))) + { + g->mLayers->actionLayer()->reactToClick(a, card); + } + } + return 1; +} + +/* Can I first strike my oponent and get away with murder ? */ +int AIPlayerBaka::canFirstStrikeKill(MTGCardInstance * card, MTGCardInstance *ennemy) +{ + if (ennemy->has(Constants::FIRSTSTRIKE) || ennemy->has(Constants::DOUBLESTRIKE)) + return 0; + if (!(card->has(Constants::FIRSTSTRIKE) || card->has(Constants::DOUBLESTRIKE))) + return 0; + if (!(card->power >= ennemy->toughness)) + return 0; + if (!(card->power >= ennemy->toughness + 1) && ennemy->has(Constants::FLANKING)) + return 0; + return 1; +} + +int AIPlayerBaka::chooseBlockers() +{ + GameObserver * g = GameObserver::GetInstance(); + + //Should not block during my own turn... + if (g->currentPlayer == this) + return 0; + map opponentsToughness; + int opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER); + + //Initialize the list of opponent's attacking cards toughness + CardDescriptor cdAttackers; + cdAttackers.init(); + cdAttackers.setType("Creature"); + MTGCardInstance * card = NULL; + while ((card = cdAttackers.nextmatch(opponent()->game->inPlay, card))) + { + if (card->isAttacker()) + opponentsToughness[card] = card->toughness; + } + + //A Descriptor to find untapped creatures in our game + CardDescriptor cd; + cd.init(); + cd.setType("Creature"); + cd.unsecureSetTapped(-1); + card = NULL; + MTGAbility * a = g->mLayers->actionLayer()->getAbility(MTGAbility::MTG_BLOCK_RULE); + + // We first try to block the major threats, those that are marked in the Top 3 of our stats + while ((card = cd.nextmatch(game->inPlay, card))) + { + g->mLayers->actionLayer()->reactToClick(a, card); + int set = 0; + while (!set) + { + if (!card->defenser) + { + set = 1; + } + else + { + MTGCardInstance * attacker = card->defenser; + map::iterator it = opponentsToughness.find(attacker); + if (it == opponentsToughness.end()) + { + opponentsToughness[attacker] = attacker->toughness; + it = opponentsToughness.find(attacker); + } + if (opponentsToughness[attacker] > 0 && getStats() && getStats()->isInTop(attacker, 3, false)) + { + opponentsToughness[attacker] -= card->power; + set = 1; + } + else + { + g->mLayers->actionLayer()->reactToClick(a, card); + } + } + } + } + + //If blocking one of the major threats is not enough to kill it, + // We change strategy, first we unassign its blockers that where assigned above + card = NULL; + while ((card = cd.nextmatch(game->inPlay, card))) + { + if (card->defenser && opponentsToughness[card->defenser] > 0) + { + while (card->defenser) + { + g->mLayers->actionLayer()->reactToClick(a, card); + } + } + } + + //Assign the "free" potential blockers to attacking creatures that are not blocked enough + card = NULL; + while ((card = cd.nextmatch(game->inPlay, card))) + { + if (!card->defenser) + { + g->mLayers->actionLayer()->reactToClick(a, card); + int set = 0; + while (!set) + { + if (!card->defenser) + { + set = 1; + } + else + { + MTGCardInstance * attacker = card->defenser; + if (opponentsToughness[attacker] <= 0 || (card->toughness <= attacker->power && opponentForce * 2 < life + && !canFirstStrikeKill(card, attacker)) || attacker->nbOpponents() > 1) + { + g->mLayers->actionLayer()->reactToClick(a, card); + } + else + { + set = 1; + } + } + } + } + } + selectAbility(); + return 1; +} + +int AIPlayerBaka::orderBlockers() +{ + + GameObserver * g = GameObserver::GetInstance(); + if (ORDER == g->combatStep && g->currentPlayer == this) + { + DebugTrace("AIPLAYER: order blockers"); + g->userRequestNextGamePhase(); //TODO clever rank of blockers + return 1; + } + + return 0; +} + +int AIPlayerBaka::affectCombatDamages(CombatStep step) +{ + GameObserver * g = GameObserver::GetInstance(); + GuiCombat * gc = g->mLayers->combatLayer(); + for (vector::iterator attacker = gc->attackers.begin(); attacker != gc->attackers.end(); ++attacker) + gc->autoaffectDamage(*attacker, step); + return 1; +} + +//TODO: Deprecate combatDamages +int AIPlayerBaka::combatDamages() +{ + int currentGamePhase = GameObserver::GetInstance()->getCurrentGamePhase(); + + if (currentGamePhase == Constants::MTG_PHASE_COMBATBLOCKERS) + return orderBlockers(); + + if (currentGamePhase != Constants::MTG_PHASE_COMBATDAMAGE) + return 0; + + return 0; + +} + + +// +// General +// + +AIStats * AIPlayerBaka::getStats() +{ + if (!stats) + { + char statFile[512]; + sprintf(statFile, "ai/baka/stats/%s.stats", opponent()->deckFileSmall.c_str()); + stats = NEW AIStats(this, statFile); + } + return stats; +} + + +void AIPlayerBaka::Render() +{ +#ifdef RENDER_AI_STATS + if (getStats()) getStats()->Render(); +#endif +} + +int AIPlayerBaka::receiveEvent(WEvent * event) +{ + if (getStats()) + return getStats()->receiveEvent(event); + return 0; +} + + +AIPlayerBaka::AIPlayerBaka(string file, string fileSmall, string avatarFile, MTGDeck * deck) : + AIPlayer(file, fileSmall, deck) +{ + + nextCardToPlay = NULL; + stats = NULL; + + //Initialize "AIHints" system + hints = NULL; + if (mDeck && mDeck->meta_AIHints.size()) + { + hints = NEW AIHints(this); + for (size_t i = 0; i < mDeck->meta_AIHints.size(); ++i) + hints->add(mDeck->meta_AIHints[i]); + } + + + mAvatarTex = WResourceManager::Instance()->RetrieveTexture(avatarFile, RETRIEVE_LOCK, TEXTURE_SUB_AVATAR); + if (!mAvatarTex) + { + avatarFile = "baka.jpg"; + mAvatarTex = WResourceManager::Instance()->RetrieveTexture(avatarFile, RETRIEVE_LOCK, TEXTURE_SUB_AVATAR); + } + + if (mAvatarTex) + mAvatar = WResourceManager::Instance()->RetrieveQuad(avatarFile, 0, 0, 35, 50, "bakaAvatar", RETRIEVE_NORMAL, + TEXTURE_SUB_AVATAR); + + if (fileSmall == "ai_baka_eviltwin") + mAvatar->SetHFlip(true); + + initTimer(); +} + +int AIPlayerBaka::Act(float dt) +{ + GameObserver * g = GameObserver::GetInstance(); + if (!(g->currentlyActing() == this)) + { + return 0; + } + + int currentGamePhase = g->getCurrentGamePhase(); + + oldGamePhase = currentGamePhase; + + timer -= dt; + if (timer > 0) + { + return 0; + } + initTimer(); + if (combatDamages()) + { + return 0; + } + interruptIfICan(); + + //computeActions only when i have priority + if (!(g->currentlyActing() == this)) + { + DebugTrace("Cannot interrupt"); + return 0; + } + if (clickstream.empty()) + computeActions(); + if (clickstream.empty()) + { + if (g->isInterrupting == this) + { + g->mLayers->stackLayer()->cancelInterruptOffer(); //endOfInterruption(); + } + 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; +}; + +AIPlayerBaka::~AIPlayerBaka() { + if (stats) + { + stats->save(); + SAFE_DELETE(stats); + } + SAFE_DELETE(hints); +} diff --git a/projects/mtg/src/GameApp.cpp b/projects/mtg/src/GameApp.cpp index e1842895e..67ad9b331 100644 --- a/projects/mtg/src/GameApp.cpp +++ b/projects/mtg/src/GameApp.cpp @@ -97,7 +97,33 @@ void GameApp::Create() WResourceManager::Instance()->ResetCacheLimits(); - JFileSystem::init("User/", "Res/"); + string systemFolder = "Res/"; + string foldersRoot = ""; + + //Find the Res folder + ifstream mfile("Res.txt"); + string resPath; + if (mfile) + { + bool found = false; + while (!found && std::getline(mfile, resPath)) + { + if (resPath[resPath.size() - 1] == '\r') + resPath.erase(resPath.size() - 1); //Handle DOS files + string testfile = resPath + systemFolder; + testfile.append("graphics/simon.dat"); + ifstream tempfile(testfile.c_str()); + if (tempfile) + { + found = true; + tempfile.close(); + foldersRoot = resPath; + } + } + mfile.close(); + } + + JFileSystem::init(foldersRoot + "User/", foldersRoot + systemFolder); // Create User Folders (for write access) if they don't exist { diff --git a/projects/mtg/src/MTGAbility.cpp b/projects/mtg/src/MTGAbility.cpp index 11ebcadb7..b0beb82c6 100644 --- a/projects/mtg/src/MTGAbility.cpp +++ b/projects/mtg/src/MTGAbility.cpp @@ -1470,7 +1470,7 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG { MTGCard * safetycard = MTGCollection()->getCardById(tokenId); if (!safetycard) //Error, card not foudn in DB - return NEW ATokenCreator(id, card, target, NULL, "ID NOT FOUND", "ERROR ID",0, 0, "",0, NULL,0); + return NEW ATokenCreator(id, card, target, NULL, "ID NOT FOUND", "ERROR ID",0, 0, "","", NULL,0); ATokenCreator * tok = NEW ATokenCreator(id, card,target, NULL, tokenId, starfound, multiplier, who); tok->oneShot = 1; diff --git a/projects/mtg/src/StoryFlow.cpp b/projects/mtg/src/StoryFlow.cpp index 9f567809f..aaacfb49e 100644 --- a/projects/mtg/src/StoryFlow.cpp +++ b/projects/mtg/src/StoryFlow.cpp @@ -4,6 +4,10 @@ #include "MTGDefinitions.h" #include "WResourceManager.h" #include "AIPlayer.h" + +//TODO remove this dependency +#include "AIPlayerBaka.h" + #include "Rules.h" #include "Credits.h" #include "PlayerData.h" diff --git a/projects/mtg/src/TargetChooser.cpp b/projects/mtg/src/TargetChooser.cpp index 18dd2a62d..9ddfd097f 100644 --- a/projects/mtg/src/TargetChooser.cpp +++ b/projects/mtg/src/TargetChooser.cpp @@ -263,7 +263,6 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta string numberCD = attribute.substr(operatorPosition + 1, attribute.size() - operatorPosition - 1); WParsedInt * val = NEW WParsedInt(numberCD,NULL, card); comparisonCriterion = val->getValue(); - /*atoi(attribute.substr(operatorPosition + 1, attribute.size() - operatorPosition - 1).c_str());*/ delete val; switch (attribute[operatorPosition - 1]) { diff --git a/projects/mtg/src/Tasks.cpp b/projects/mtg/src/Tasks.cpp index 2999b5025..d5e0259b0 100644 --- a/projects/mtg/src/Tasks.cpp +++ b/projects/mtg/src/Tasks.cpp @@ -4,6 +4,10 @@ #include "Player.h" #include "Tasks.h" #include "AIPlayer.h" + +// Todo remove this dependency! +#include "AIPlayerBaka.h" + #include "Translate.h" #include "MTGDefinitions.h" #include diff --git a/projects/mtg/src/TestSuiteAI.cpp b/projects/mtg/src/TestSuiteAI.cpp index 49745bffa..772911e2b 100644 --- a/projects/mtg/src/TestSuiteAI.cpp +++ b/projects/mtg/src/TestSuiteAI.cpp @@ -391,7 +391,7 @@ void TestSuite::initGame() g->currentGamePhase = initState.phase; for (int i = 0; i < 2; i++) { - AIPlayer * p = (AIPlayer *) (g->players[i]); + AIPlayerBaka * p = (AIPlayerBaka *) (g->players[i]); p->forceBestAbilityUse = forceAbility; p->life = initState.playerData[i].life; MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay }; diff --git a/projects/mtg/template.vcxproj b/projects/mtg/template.vcxproj index c450c0bbc..fd66ec88f 100644 --- a/projects/mtg/template.vcxproj +++ b/projects/mtg/template.vcxproj @@ -308,6 +308,8 @@ + + @@ -442,6 +444,8 @@ + + diff --git a/projects/mtg/template.vcxproj.filters b/projects/mtg/template.vcxproj.filters index b1c074b11..bd429c388 100644 --- a/projects/mtg/template.vcxproj.filters +++ b/projects/mtg/template.vcxproj.filters @@ -313,6 +313,12 @@ src + + src + + + src + @@ -651,6 +657,12 @@ inc + + inc + + + inc + diff --git a/projects/mtg/wagic-SDL.pro b/projects/mtg/wagic-SDL.pro index f640ab5b2..7d16fd073 100644 --- a/projects/mtg/wagic-SDL.pro +++ b/projects/mtg/wagic-SDL.pro @@ -51,6 +51,7 @@ SOURCES += \ src/AIHints.cpp\ src/AIMomirPlayer.cpp\ src/AIPlayer.cpp\ + src/AIPlayerBaka.cpp\ src/AIStats.cpp\ src/AllAbilities.cpp\ src/CardDescriptor.cpp\ diff --git a/projects/mtg/wagic-qt.pro b/projects/mtg/wagic-qt.pro index bc8067a79..3535c0c73 100644 --- a/projects/mtg/wagic-qt.pro +++ b/projects/mtg/wagic-qt.pro @@ -47,6 +47,7 @@ SOURCES += \ src/AIHints.cpp\ src/AIMomirPlayer.cpp\ src/AIPlayer.cpp\ + src/AIPlayerBaka.cpp\ src/AIStats.cpp\ src/AllAbilities.cpp\ src/CardDescriptor.cpp\