hankerspace
New Member
- Joined
- Apr 4, 2014
- Messages
- 164
- Reaction score
- 3
API released
Here is a little guide to explain how to make you custom deck.
First of all, current API is considered as a "custom deck". User custom deck will be used only once INGAME and during player turn.
In other terms, the entry point of a custom deck (SelectCard) is called once our turn.
You custom deck will be developped in c#. I advise you to use Visual Studio IDE or Mono IDE to help you during developpement. Custom decks sourcefiles must be added in "CustomDecks" folder (near hearthbuddy.exe). Create a subfolder for each different custom deck.
You custom class must inherit from ICustomDeck interface. You must implement "IEnumerator SelectCard()" function.
I strongly advise you to script a custom deck for only 30 card. Describe behaviors for these 30 cards and provide cards needed for your custom deck.
You can, ofcourse, split you custom deck between multiple .cs files. Since your files are in the same namespace, you can access to your namespace members. I Strongly advise you to split your helpers function, custom card behavior, and main function is different files.
Cards definitions (Ids) : View attachment CardDefs.txt
Errors
Scripts compilation is done during Bot startup. Compilation errors are displayed in log window. If your custom deck is avaible, it will be avaible in 'custom deck" list. Else you deck contains errors. Check log window.
Custom deck called "." is the actual deck of Hearthbuddy's team.
If your custom deck throw an exception (trying to access to a null member, dividing by 0, etc...), a log message will be displayed begining with "Exception occured in coroutine:" statement, followed by exception description and line(s) involved in exception.
Documentation
First of all : never cache members! Never store a card in a variable for X loops. Always retrieve cards from TritonHS functions in real-time. If you want to store a card, store its CardID and find a Card matching ID to retrieve it.
Basically, in a first time you should retrieve cards definitions (in your hand, on your battlefield...) and act : should i drop this card? Should i attack with this one?
We are awaiting for an Hearthbuddy wiki. Meanwhile i'll post the API documentation in this thread :
Triton.Game.TritonHS members
	
	
	
		
Triton.Game.HSCard members
	
	
	
		
Triton.Game.Mapping.TAG_RACE enum
	
	
	
		
Triton.Game.Mapping.TAG_RARITY enum
	
	
	
		
Triton.Game.TAG_CLASS enum
	
	
	
		
Triton.Game.CardZone enum
	
	
	
		
Samples :
Endturn :
Here is a simple sample :
This custom deck is implemented in file "MyHearthbuddyPath/CustomDecks/EndturnDeck/EndturnDeck.cs"
	
	
	
		
This custom deck will end turn on each turn (pretty useless but thats an example).
OnlyMinions :
Second sample : basic bot designed to drop minions without battlecries ASAP.
Comments are quite exhaustive, i think you can get the workflow by yourself
This custom deck is implemented in file "MyHearthbuddyPath/CustomDecks/OnlyMinions/OnlyMinionsDeck.cs"
	
	
	
		
Good luck everyone
				
			Here is a little guide to explain how to make you custom deck.
First of all, current API is considered as a "custom deck". User custom deck will be used only once INGAME and during player turn.
In other terms, the entry point of a custom deck (SelectCard) is called once our turn.
You custom deck will be developped in c#. I advise you to use Visual Studio IDE or Mono IDE to help you during developpement. Custom decks sourcefiles must be added in "CustomDecks" folder (near hearthbuddy.exe). Create a subfolder for each different custom deck.
You custom class must inherit from ICustomDeck interface. You must implement "IEnumerator SelectCard()" function.
I strongly advise you to script a custom deck for only 30 card. Describe behaviors for these 30 cards and provide cards needed for your custom deck.
You can, ofcourse, split you custom deck between multiple .cs files. Since your files are in the same namespace, you can access to your namespace members. I Strongly advise you to split your helpers function, custom card behavior, and main function is different files.
Cards definitions (Ids) : View attachment CardDefs.txt
Errors
Scripts compilation is done during Bot startup. Compilation errors are displayed in log window. If your custom deck is avaible, it will be avaible in 'custom deck" list. Else you deck contains errors. Check log window.
Custom deck called "." is the actual deck of Hearthbuddy's team.
If your custom deck throw an exception (trying to access to a null member, dividing by 0, etc...), a log message will be displayed begining with "Exception occured in coroutine:" statement, followed by exception description and line(s) involved in exception.
Documentation
First of all : never cache members! Never store a card in a variable for X loops. Always retrieve cards from TritonHS functions in real-time. If you want to store a card, store its CardID and find a Card matching ID to retrieve it.
Basically, in a first time you should retrieve cards definitions (in your hand, on your battlefield...) and act : should i drop this card? Should i attack with this one?
We are awaiting for an Hearthbuddy wiki. Meanwhile i'll post the API documentation in this thread :
Triton.Game.TritonHS members
		Code:
	
	        /// <summary>
        /// Retrieves the card in the specified zone, and player.
        /// </summary>
        /// <param name="zone">Zone to retrieve cards from</param>
        /// <param name="localPlayer">Retrieve our cards?</param>
        /// <returns>List of cards</returns>
        public static List<HSCard> GetCards(CardZone zone, bool localPlayer = true)
        /// <summary>
        /// End current turn
        /// </summary>
        public static void EndTurn()
        /// <summary>
        /// Is our turn?
        /// </summary>
        /// <returns></returns>
        public static bool IsOurTurn()
        public static bool IsInGame()
        /// <summary>
        /// Is the game currently in target mode? => Should we have to choose a target?
        /// </summary>
        /// <returns></returns>
        public static bool IsInTargetMode()
        /// <summary>
        /// Cancel current targeting mode
        /// </summary>
        public static void CancelTargetingMode()
        /// <summary>
        /// Concede
        /// </summary>
        public static void Concede()
        /// <summary>
        /// Retrieve current mana crystals avaible
        /// </summary>
        public static int CurrentMana
        public static int Fatigue
        public static int NumCardsPlayedThisTurn
        public static int NumMinionsPlayedThisTurn
        public static int Resources
        public static int RecallOwed 
        #region Heros
        /// <summary>
        /// Retrieve our hero total health (health + armor)
        /// </summary>
        public static int OurHeroHealthAndArmor
        /// <summary>
        /// Retrieve our hero armor
        /// </summary>
        public static int OurHeroArmor
        /// <summary>
        /// Retrieve enemy hero total health (health + armor) 
        /// </summary>
        public static int EnemyHeroHealthAndArmor
        /// <summary>
        /// Retrieve enemy hero amor
        /// </summary>
        public static int EnemyHeroArmor
        /// <summary>
        /// Retrieve our hero attack dmgs
        /// </summary>
        public static int OurHeroAttack
        /// <summary>
        /// Retrieve enemy hero attack dmgs
        /// </summary>
        public static int EnemyHeroAttack
        /// <summary>
        /// Retrieve our hero power card (hero spell)
        /// </summary>
        public static HSCard OurHeroPowerCard
        /// <summary>
        /// Retrieve our hero card
        /// </summary>
        public static HSCard OurHero
        /// <summary>
        /// Retrieve enemy hero card
        /// </summary>
        public static HSCard EnemyHero
        /// <summary>
        /// Do we have a weapon?
        /// </summary>
        public static bool DoWeHaveWeapon
        /// <summary>
        /// Does enemy hhas weapon?
        /// </summary>
        public static bool DoesEnemyHasWeapon
        /// <summary>
        /// Retrieve our hero card (retrieve null if no weapon)
        /// </summary>
        public static HSCard OurWeaponCard
        /// <summary>
        /// Retrieve enemy hero card (retrieve null if no weapon)
        /// </summary>
        public static HSCard EnemyWeaponCard
        /// <summary>
        /// Retrieve current spell power (additional spell power)
        /// </summary>
        public static int CurrentSpellPower
        #endregion
        #region Choice mode
        /// <summary>
        /// Is the game in multi-choice mode? (drood cards)
        /// </summary>
        /// <returns></returns>
        public static bool IsInChoiceMode()
        /// <summary>
        /// If we are in multiple cards choice, choose left card
        /// </summary>
        public static void ChooseOneClickLeft()
        /// <summary>
        /// If we are in multiple cards choice, choose right card
        /// </summary>
        public static void ChooseOneClickRight()Triton.Game.HSCard members
		Code:
	
	        public int Health
        public int Attack
        public int Cost
        public string Id
        public TAG_RACE Race
        public string Name
        public TAG_RARITY Rarity
        public int Durability
        public TAG_CLASS Class
        // Has, Is
        public bool HasTaunt
        public bool HasCharge
        public bool HasBattlecry
        public bool CanBeTargetedByAbilities
        public bool CanBeTargetedByHeroPowers
        public bool IsImmune
        public bool IsPoisonous
        public bool IsEnraged
        public bool IsFreeze
        public bool IsFrozen
        public bool IsAsleep
        public bool IsStealthed 
        public bool HasDivineShield 
        public bool IsHero 
        public bool IsHeroPower
        public bool IsMinion
        public bool IsSpell
        public bool IsAbility)
        public bool IsWeapon
        public bool IsElite
        public bool IsEnchantment
        public bool IsExhausted
        public bool IsAttached
        public bool IsRecentlyArrived
        public bool IsSecret
        public bool CanAttack
        public bool CanBeAttacked
        /// <summary>
        /// Can this card be targetted?
        /// </summary>
        /// <returns></returns>
        public bool CanBeTargeted
        /// <summary>
        /// Can this card be used?
        /// </summary>
        /// <returns></returns>
        public bool CanBeUsed
        public bool CanBeTargetedByOpponents
        public bool IsMagnet
        public bool HasSpellPower
        public bool IsAffectedBySpellPower
        public bool IsDamaged
        public bool HasWindfury
        public bool HasCombo
        public bool HasRecall
        public bool HasDeathrattle
        public bool IsSilenced
        public bool CanBeDamaged
        public int EntityId
        public int ControllerId
        public int CreatorId
        public int NumAttackThisTurn
        public int ZonePosition
        // Actions
        /// <summary>
        /// Grab this card (from hand)
        /// </summary>
        public void DoGrab()
        /// <summary>
        /// Drop this card (once grabbed)
        /// </summary>
        public void DoDrop()
        /// <summary>
        /// Select a target for this card (ensure we are in target mode before)
        /// </summary>
        /// <param name="target"></param>
        public void DoTarget(HSCard target)
        /// <summary>
        /// Cancel current targeting mode
        /// </summary>
        public void CancelTarget()
        /// <summary>
        /// Do attack with this card
        /// </summary>
        /// <param name="attackee">target card</param>
        public void DoAttack(HSCard attackee)
        /// <summary>
        /// Toggle this card (if we are during mulligan)
        /// </summary>Triton.Game.Mapping.TAG_RACE enum
		Code:
	
	    INVALID = 0,
    BLOODELF = 1,
    DRAENEI = 2,
    DWARF = 3,
    GNOME = 4,
    GOBLIN = 5,
    HUMAN = 6,
    NIGHTELF = 7,
    ORC = 8,
    TAUREN = 9,
    TROLL = 10,
    UNDEAD = 11,
    WORGEN = 12,
    GOBLIN2 = 13,
    MURLOC = 14,
    DEMON = 15,
    SCOURGE = 16,
    MECHANICAL = 17,
    ELEMENTAL = 18,
    OGRE = 19,
    PET = 20,
    TOTEM = 21,
    NERUBIAN = 22,
    PIRATE = 23,
    DRAGON = 24,Triton.Game.Mapping.TAG_RARITY enum
		Code:
	
	    INVALID = 0,
    COMMON = 1,
    FREE = 2,
    RARE = 3,
    EPIC = 4,
    LEGENDARY = 5,Triton.Game.TAG_CLASS enum
		Code:
	
	    INVALID = 0,
    DEATHKNIGHT = 1,
    DRUID = 2,
    HUNTER = 3,
    MAGE = 4,
    PALADIN = 5,
    PRIEST = 6,
    ROGUE = 7,
    SHAMAN = 8,
    WARLOCK = 9,
    WARRIOR = 10,
    DREAM = 11,Triton.Game.CardZone enum
		Code:
	
	        None,
        Hand,
        Battlefield,
        GraveyardSamples :
Endturn :
Here is a simple sample :
This custom deck is implemented in file "MyHearthbuddyPath/CustomDecks/EndturnDeck/EndturnDeck.cs"
		Code:
	
	using System.Collections;
using Triton.Bot;
using Triton.Common;
using Triton.Game;
namespace Hearthbuddy.Samples
{
    public class EndTurnDeck : ICustomDeck
    {
        public IEnumerator SelectCard()
        {
            TritonHS.EndTurn();
            yield break;
        }
    }
}This custom deck will end turn on each turn (pretty useless but thats an example).
OnlyMinions :
Second sample : basic bot designed to drop minions without battlecries ASAP.
Comments are quite exhaustive, i think you can get the workflow by yourself

This custom deck is implemented in file "MyHearthbuddyPath/CustomDecks/OnlyMinions/OnlyMinionsDeck.cs"
		Code:
	
	using System.Collections;
using System.Linq;
using Triton.Bot;
using Triton.Common;
using Triton.Game;
using Triton.Game.Mapping;
namespace Hearthbuddy.Samples
{
    class OnlyMinions : ICustomDeck
    {
        private int _loopCount = 1;
        public IEnumerator SelectCard()
        {
            // Some verbose
            Logging.Write("------- Turn " + TritonHS.CurrentTurn + " Loop " + _loopCount + " -------");
            _loopCount++;
            // ----- First : drops
            // Try to play coin :
            yield return TryToPlayCoin();
            // Retrieve our cards in hand which can be used 
            // Can be used do a lot of check for us : enough mana, battlefield is not full to drop a minion, this spell can be used, etc...
            // Use this function to determine if a card can be played from you hand
            foreach (HSCard card in TritonHS.GetCards(CardZone.Hand).Where(s =>
                s.CanBeUsed &&
                s.IsMinion && // Manage only minions (s.IsSecret, s.IsWeapon, etc... to manage other kind of cards)
                !s.HasBattlecry //without battlecries in this custom deck
                ))
            {
                Logging.Write("Lets play " + card.Name);
                // This card is a minion : drop it
                card.DoGrab(); // First : grab card
                yield return Coroutine.Sleep(500);
                    // Then : little sleep (ensure card is considered "grabbed" by the game
                card.DoDrop(); // Finally : drop
                yield return Coroutine.Sleep(500);
                // Check if we are in target mode (how to manage a battlecry).
                if (TritonHS.IsInTargetMode())
                {
                    // We are in target mode : this minion has a battlecry
                    // card.DoTarget(TritonHS.EnemyHero); // Sample : our minion battlecry do X dmg => focus enemy hero
                    // We dont manage Battlecries currently : cancel the target mode -> retrieve this card in our hands
                    card.CancelTarget(); // TritonHS.CancelTargetingMode() works too
                }
                yield break;
                    // Get out of this loop => return at the start of the function => check if we can use new cards (maybe we have drawn a card?)
            }
            // ----- Second : attacks
            // Retrive cards on our battlefield which can be used
            // CanbeUsed function is checking if we can attack with this minion (it is not frozen, not exhausted, has atk > 0, etc...)
            foreach (HSCard card in TritonHS.GetCards(CardZone.Battlefield).Where(s => s.CanBeUsed))
            {
                // Check if enemy has a taunter
                if (DoTheEnemyHasATaunter())
                {
                    // Do our attack on enemy taunter
                    Logging.Write("Do attack : " + card.Name + " -> " + RetrieveEnemyTaunter().Name);
                    card.DoAttack(RetrieveEnemyTaunter());
                    yield return Coroutine.Sleep(1000); // Little sleep after an attack
                    yield break;
                        // Get out of this loop => return at the start of the function => check if we can use new cards (maybe we have drawn a card?)
                }
                // Enemy has NO taunter and we can target him => go for the face
                if (TritonHS.EnemyHero.CanBeTargetedByOpponents)
                {
                    // Do our attack
                    Logging.Write("Do attack : " + card.Name + " -> " + TritonHS.EnemyHero.Name);
                    card.DoAttack(TritonHS.EnemyHero);
                    yield return Coroutine.Sleep(1000); // Little sleep after an attack
                    yield break;
                        // Get out of this loop => return at the start of the function => check if we can use new cards (maybe we have drawn a card?)
                }
            }
            // ----- Third : check hero power
            if (TritonHS.OurHeroPowerCard.CanBeUsed)
            {
                // We can use our hero power
                if (TritonHS.OurHero.Class == TAG_CLASS.WARLOCK && TritonHS.OurHeroHealthAndArmor < 10)
                    // Be careful with warlock power
                {
                    Logging.Write("We are a warlock and we are low life : ignore our hero power.");
                }
                else
                {
                    Logging.Write("Lets use our hero power.");
                    // Grab card : only a grab is needed for spells
                    TritonHS.OurHeroPowerCard.DoGrab();
                    yield return Coroutine.Sleep(1000); // Game need to load some stuff
                    // Is a target needed for hero power?
                    // Check if target retrieved is not null (maybe we did a misstake in our RetrieveTargetForHeroPower function)
                    if (TritonHS.IsInTargetMode() && RetrieveTargetForHeroPower() != null)
                    {
                        // So, find a target
                        TritonHS.OurHeroPowerCard.DoTarget(RetrieveTargetForHeroPower());
                        yield return Coroutine.Sleep(1000); // Game need to load some stuff
                    }
                    yield break;
                        // Get out of this loop => return at the start of the function => check if we can use new cards (maybe we have drawn a card?)
                }
            }
            // ------ Fourth : use our Hero 
            // Our hero can be used : our hero ATK is > 0 && we are not frozen
            // Dont focus taunters with our hero -> Focus enemy hero
            // Enemy hero can be attacked
            if (TritonHS.OurHero.CanBeUsed && !DoTheEnemyHasATaunter() &&
                TritonHS.EnemyHero.CanBeTargetedByOpponents)
            {
                TritonHS.OurHero.DoAttack(TritonHS.EnemyHero);
            }
            // ----- Finally : end our turn
            // If we reach this point : all our hand cards avaible are dropped & all our usable cards on battlefield are exhausted.
            // Ensure we are not in target mode (maybe we failed somewhere)
            // If game is still in target mode, endturn will fail
            if (TritonHS.IsInTargetMode())
            {
                TritonHS.CancelTargetingMode();
            }
            // End our turn
            _loopCount = 1;
            Logging.Write("End turn.");
            TritonHS.EndTurn();
        }
        /// <summary>
        ///     check if we have coin in hand, and play it if needed
        /// </summary>
        private IEnumerator TryToPlayCoin()
        {
            // Coin card ID
            const string coinId = "GAME_005";
            // Should we play coin?
            if (TritonHS.GetCards(CardZone.Hand).Any(s => s.Id == coinId) // Do we have coin in hand?
                &&
                !TritonHS.GetCards(CardZone.Hand)
                    .Any(s => s.CanBeUsed // Ensure we cant play cards with current mana avaible
                              &&
                              TritonHS.GetCards(CardZone.Hand)
                                  .Any(c => c.Cost == TritonHS.CurrentMana + 1 && c.IsMinion && !c.HasBattlecry)))
                // A card is awaible with currentmana + 1 (more checks are needed!!)
            {
                HSCard coin = TritonHS.GetCards(CardZone.Hand).FirstOrDefault(s => s.Id == coinId);
                if (coin != null)
                {
                    Logging.Write("Lets play coin!");
                    coin.DoGrab();
                    yield return Coroutine.Sleep(500);
                    coin.DoDrop();
                    yield return Coroutine.Sleep(500);
                }
            }
        }
        /// <summary>
        ///     Determine if enemy player has a taunter on the field
        /// </summary>
        /// <returns></returns>
        private bool DoTheEnemyHasATaunter()
        {
            return TritonHS.GetCards(CardZone.Battlefield, false).Any(s => s.HasTaunt);
        }
        /// <summary>
        ///     Retrieve first enemy taunter card which can be attacked
        /// </summary>
        /// <returns></returns>
        private HSCard RetrieveEnemyTaunter()
        {
            return
                TritonHS.GetCards(CardZone.Battlefield, false)
                    .FirstOrDefault(s => s.HasTaunt && s.CanBeTargetedByOpponents);
        }
        /// <summary>
        ///     Retrieve a target for hero power (depending on hero class)
        /// </summary>
        /// <returns>null if no target</returns>
        private HSCard RetrieveTargetForHeroPower()
        {
            switch (TritonHS.OurHero.Class)
            {
                // These 6 class dont need a target => return null
                case TAG_CLASS.DRUID:
                case TAG_CLASS.HUNTER:
                case TAG_CLASS.PALADIN:
                case TAG_CLASS.ROGUE:
                case TAG_CLASS.SHAMAN:
                case TAG_CLASS.WARRIOR:
                case TAG_CLASS.WARLOCK:
                    return null;
                case TAG_CLASS.MAGE:
                    // Target enemy hero
                    return TritonHS.EnemyHero;
                case TAG_CLASS.PRIEST:
                    // Target our hero
                    return TritonHS.OurHero;
                default:
                    return null;
            }
        }
    }
}Good luck everyone

			
				Last edited: 
			
		
	
								
								
									
	
								
							
							






 
 
		 
 
		
 
 
		 
 
		
