Thread: Bot Creation Tutorial

Results 1 to 4 of 4
  1. #1
    Offline
    Account Icon
    Chat Symbol
    Join Date
    Jul 2009
    Location
    UK - Brum
    Posts
    8,898

    Bot Creation Tutorial

    Bot Tutorial: Pyro

    Contents

    1. Introduction
    2. File Structure
    3 Extending the bot
    4 Conclusion



    1. Introduction

    So you want to write a bot for HoN? great! but how do you do it?
    The bots themselves are written in Lua to mimic the behavior a player would display. While you are able to write a bot completely from scratch, this tutorial will show you how to extend the base functionality of S2's bots to suit a specifc hero.

    The bot’s bahaviors are:
    • Event-Driven. Server-side events (skill usage, damage taken etc...) can be used to trigger certain logic.
    • Weighting. A neural-network approach to governing precedent exists. Different inputs can have varying influence on the following logic. Neuron Weight Adjustment.
    • Procedural. There is also a collection of functions that are called periodically on the bot's object.


    This tutorial shall cover
    • Quick overview of key files
    • How to tailor a bot’s item build order
    • How to tailor the bot’s skillbuild order
    • How to modify the bot’s harassment
    • Where to go from here



    1.1 Scope

    This document shall provide a general overview into the file structure of the bots as well as introducing SkeletonBot as a basic starting point for all bots.
    It is this SkeletonBot which shall be leveraged to create

    Scorcher the PyromancerBot



    The bots themselves have a rudimentary understanding of how to interact within a game. They will go to a suitable lane, kill creeps, deny creeps, return to the well to heal, use Homecomming Stones, use Blight Stones, harass heroes with autoattacks, and push... However, the ability to use skills and use many items is not part of the default behavior. This is what needs to be added to the bot through the exposed API in Lua.

    A large part of this is done through overloading some of the bot’s built in functions and values to adjust the characterise and playstyle of the bot. Should it be a killstealing bot which saves its ult till the target has 10hp? should it initiate via portalkey & stun to allow the rest of the team to maximise its damage? Custom code decides how a hero deviates from the default behavior.

    There are a few console cvars which can assist while testing bots:
    • host_dynamicResReload true - This will cause the Lua files to be re-loaded when the root file is changed
    • con_notify true - This will provide a mini-console window allowing your BotEcho calls to be seen in game without bringing down the console manually.
    • cg_botDebug true - This will allow the lines drawn with botBrain:DrawDebugLine() calls to show up on your client
    • sv_botDebug true - This will send the debug data from the server
    • lua_warningsAsErrors true - This will crash Lua for that frame when the engine's API errors. This is useful for catching errors when interfacing with the API.


    Here are some important commands:

    • startgame practice 'test' map:caldavar mode:botmatch - This will quickly start a botgame at the lobby
    • AddBot 2 Scorcher - This will add a bot whose definition is named “Scorcher” to team 2 (1=Legion, 2=Hellbourne)
    • ReloadTeamBots - Forcibly reload all teambots
    • ReloadBots - Forcibly reload all bots
    Last edited by Naib; 02-07-2013 at 04:39 PM.

    Forum Moderators are not S2 Games employees. My posts in no way represent the view of S2 Games or any of its staff.

    Please use the report post function to have me review a post that you believe is breaking the Forum Rules.
    Check the Sticky Threads for additional information on this sub-forum and the Announcement Threads for more information about Heroes of Newerth as a whole!

    -----------------------------


  2. #2
    Offline
    Account Icon
    Chat Symbol
    Join Date
    Jul 2009
    Location
    UK - Brum
    Posts
    8,898
    2. File Structure

    The bot files reside within the Heroes of Newerth/game/bots/ folder. If you haven't done so already, you can rename the resources0.s2z into resources0.zip and extract the game data.

    There are 5 Lua files in the root of this directory. These files detail the default functionality of the bots.

    core.lua - this file is used by both the player bots and the teambots. It contains functions and data common to both player botBrains and teamBotBrainss, although each bot will have its own instance of the data.

    botbraincore.lua - contains the onthink() function, which is called every bot frame. It also contains all data and functions specific to being a player bot. This contains behavior assessment, chat message processing, local unit processing, and wrappers for various orders.

    behaviorLib.lua - contains the utility and execution functions for all behaviors as well as the functions and data supporting those behaviors.

    eventsLib.lua - contains processing for combat events (as well as the oncombatevent() function itself) and all the data and functions needed for that.

    metadata.lua - contains functions for loading and processing the exported metadata as well as storing middle, top, and bottom lane paths.

    Each bot requires two files, an xml definition file and an lua script file. The specific location of these files isn’t critical since HoN processes all *.bot files and each bot file references the main lua file it loads.
    For simplicity and organization, each S2 bot resides within its own directory.


    Download and rename the following files
    Heroes of Newerth/game/bots/pyromancer/scorcher.bot => Skeleton.bot
    Heroes of Newerth/game/bots/pyromancer/scorcher_main.lua => SkeletonBot_main.lua



    2.1 scorcher.bot

    The Skeleton.bot file is the xml file which contains information that HoN needs to identify the bot. This xml file has 6 properties:

    • name - your definition's name
    • description - a general overview of your bot
    • heroname - internal name of what HoN hero the bot is associated with
    • rootlua - path to the lua file that must be loaded for your bot
    • defaultplayername - a player name for this bot to use
    • precachepath - the path to the hero's entity folder, so that the UI can parse information about the hero


    The Skeleton.bot file needs to be modified to match the codeblock below and renamed to: scorcher.bot for ease of recognition.

    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <bot
    name="scorcher"
    description="My first HoN bot!"
    heroname="Hero_Pyromancer”
    rootlua="scorcher_main.lua"
    defaultplayername="scorcher”
    precachepath="/heroes/pyromancer"
    />


    2.2 scorcher_main.lua

    The SkeletonBot_main.lua file is where the logic associated with the bot’s functionality is contained.
    The idea of the SkeletonBot is to provide a minimum template which can be expanded upon. This file needs to be renamed to scorcher_main.lua to match the rootlua variable within the .bot definition file.

    Likewise the heroName within the Constant Definition section needs to be updated.

    -- hero_<hero> to reference the internal hon name of a hero, Hero_Yogi ==wildsoul
    object.heroName = 'Hero_<hero>'

    to:

    -- Hero_<hero> to reference the internal HoN name of a hero, Hero_Yogi ==Wildsoul
    object.heroName = 'Hero_Pyromancer'


    Congratulations! You now have a working (if simple) Pyromancer bot! If you wish to try him out, start a practice game with the "botmatch" mode, and use the AddBot command to add your new bot.


    We're not done yet, though. Let's move on to expanding and specializing our bot to act like a real Pyromancer!

    There are three parts to this bot template
    • setup
    • constants
    • function overrides


    The setup section (containing the runfile commands and several local references) shall be left alone as this is common to most bots.
    The “constants” and “function override” section are what shall be modified.
    Last edited by Naib; 09-22-2013 at 02:29 PM.

    Forum Moderators are not S2 Games employees. My posts in no way represent the view of S2 Games or any of its staff.

    Please use the report post function to have me review a post that you believe is breaking the Forum Rules.
    Check the Sticky Threads for additional information on this sub-forum and the Announcement Threads for more information about Heroes of Newerth as a whole!

    -----------------------------


  3. #3
    Offline
    Account Icon
    Chat Symbol
    Join Date
    Jul 2009
    Location
    UK - Brum
    Posts
    8,898
    3 Extending the bot

    What does a player/bot need to do to be able to be contribute to a team? What does this specific hero have to consider? How should this bot behave differently from the rest?

    There are many questions to consider when crafting a bot, but for now, we'll look at:
    • buying items
    • leveling skills
    • using skills
    • using items



    3.1 Items

    Contained within the behaviorLib.lua file are 4 lua tables which a bot will buy, in order.

    * behaviorLib.StartingItems
    * behaviorLib.LaneItems
    * behaviorLib.MidItems
    * behaviorLib.LateItems

    These 4 tables are what the behaviourLib code uses to determine what to buy and when. The variablenames “StartingItems”, “LaneItems” ... are a bit misleading since there is no real hard quantifiable definition of what phase the game is in. Instead these variables are iterated over until all items in the present item variable are bought.

    For pyro, the item list shall be:

    “Runes of the Blight", "Minor Totem", "Minor Totem", "Mark of the Novice
    "Steamboots", "Grave Locket"
    “Kuldra's Sheepstick”, "Grimoire Of Power"

    The four Items tables located in the _main.lua constant section needs to be populated with the item build order you want.

    behaviorLib.StartingItems = { "Item_RunesOfTheBlight", "Item_MinorTotem", "Item_MinorTotem", "Item_MarkOfTheNovice"}
    behaviorLib.LaneItems = {"Item_Marchers","Item_Steamboots","Item_GraveLocket"}
    behaviorLib.MidItems = {"Item_Lightbrand","Item_Morph","Item_GrimoireOfPower"}
    behaviorLib.LateItems = {}


    Feel free to adjust and extend these lists as you see fit. An easy reference guide for entity item names can be found here.

    Beyondsimply buying these items, we want to use them. To be able to use any items the bot has bought, it must first discover and track what items it has in its inventory.


    ----------------------------------
    -- FindItems Override
    ----------------------------------
    local function funcFindItemsOverride(botBrain)
    local bUpdated = object.FindItemsOld(botBrain)

    if core.itemSheepstick ~= nil and not core.itemSheepstick:IsValid() then
    core.itemSheepstick = nil
    end

    if bUpdated then
    --only update if we need to
    if core.itemSheepstick then
    return
    end

    local inventory = core.unitSelf:GetInventory(true)
    for slot = 1, 12, 1 do
    local curItem = inventory[slot]
    if curItem then
    if core.itemSheepstick == nil and curItem:GetName() == "Item_Morph" then
    core.itemSheepstick = core.WrapInTable(curItem)
    end
    end
    end
    end
    end
    object.FindItemsOld = core.FindItems
    core.FindItems = funcFindItemsOverride


    To find items, the bot uses the helper function FindItems(). This will iterate over all inventory slots until it finds the item (referenced via internal name) we require and then store the object reference for future use.
    Our override here replaces the default core.FindItems() function and looks for our specific items.



    3.2 Leveling Skills

    Every bot frame, a method within the bot’s “brain” ( object : onthink() ) is executed. Part of this method there is a call to a function SkillBuild. This is the function which shall be used to determine the skilling order for Scorcher the PyroBot.

    The Lua table tSkill within the constants section needs to be populated with the skill build order you want.


    -- table listing desired skillbuild. 0=Q(PhoenixWave), 1=W(Dragonfire), 2=E(Fervor), 3=R(BlazingStrike), 4=AttributeBoost
    object.tSkills = {
    1, 2, 0, 0, 0,
    3, 0, 1, 1, 1,
    3, 2, 2, 2, 4,
    3, 4, 4, 4, 4,
    4, 4, 4, 4, 4,
    }
    Last edited by Naib; 07-11-2013 at 07:35 PM.

    Forum Moderators are not S2 Games employees. My posts in no way represent the view of S2 Games or any of its staff.

    Please use the report post function to have me review a post that you believe is breaking the Forum Rules.
    Check the Sticky Threads for additional information on this sub-forum and the Announcement Threads for more information about Heroes of Newerth as a whole!

    -----------------------------


  4. #4
    Offline
    Account Icon
    Chat Symbol
    Join Date
    Jul 2009
    Location
    UK - Brum
    Posts
    8,898
    3.3 Hero Harrassment

    With basic game awareness, skill-leveling and item purchasing managed, it is time to specialize the functionality of the bot.

    This is done initially with the three method templates present and some constants.


    • HarassHeroExecuteOverride()
    • object : oncombateventOverride()


    The principle is: CustomHarassUtilityFnOverride(), via a collection of constants, can raise the heroes desire to act depending on, among other things, his ability to cast his spells. The player tends to feel more deadly with their skills are available, so increasing the utiltiy of HarassHero's Utility function will make the bot seem to think the same way.

    object : oncombateventOverride() is used to watch for successful ability use, which we can use to add to a "momentum" value. This will generally increase the overall agressive nature of the bot in light of using his abilities.


    Let's set up some values for how much our abilities affect our aggression. Total aggression will be on a scale of 0-100, taking into account many things such as the bot's and enemy's health pools, attack ranges, dps, and many more. Think of 100 as total well-diving bloodlust.


    -- These are bonus agression points if a skill/item is available for use
    object.nPhoenixUp = 10
    object.nDragonUp = 12
    object.nBlazingUp = 35
    object.nSheepstickUp = 12

    -- These are bonus agression points that are applied to the bot upon successfully using a skill/item
    object.nPhoenixUse = 15
    object.nDragonUse = 18
    object.nBlazingUse = 55
    object.nSheepstickUse = 18


    --These are thresholds of aggression the bot must reach to use these abilities
    object.nPhoenixThreshold = 20
    object.nDragonThreshold = 10
    object.nBlazingThreshold = 60
    object.nSheepstickThreshold = 10


    These variables shall contain harassment weighting modifiers which shall be referenced depending on what skills or abilities are available for use or if they have been used.


    Now let's change how aggressive our bot feels by writing a CustomHarassUtilityFnOverride()

    ------------------------------------------------------
    -- CustomHarassUtility Override --
    -- Change Utility according to usable spells here --
    ------------------------------------------------------
    -- @param: IunitEntity hero
    -- @return: number
    local function CustomHarassUtilityFnOverride(hero)
    local nUtil = 0

    if skills.abilQ:CanActivate() then
    nUtil = nUtil + object.nPhoenixUp
    end

    if skills.abilW:CanActivate() then
    nUtil = nUtil + object.nDragonUp
    end

    if skills.abilR:CanActivate() then
    nUtil = nUtil + object.nStrikeUp
    end

    if object.itemSheepstick and object.itemSheepstick:CanActivate() then
    nUtil = nUtil + object.nSheepstickUp
    end

    return nUtil
    end
    -- assisgn custom Harrass function to the behaviourLib object
    behaviorLib.CustomHarassUtility = CustomHarassUtilityFnOverride

    With this code, the overall harass level of the bot is then modified depending on what skills are available to use; More skills up, the more the bot has the desire to harass.
    We can use the amount of aggression to determine if a hero "wants" to use his abilities.


    One final function which needs an override is the oncombateventOverride() which is called when ingame event triggers are registered. This override shall be sensitive to pyromancer skill usage which shall apply an agressive bonus to the bot based upon our already declared behaviour modifier constants. This will turn ability use into a sort of "aggression momentum".

    ----------------------------------------------
    -- OncombatEvent Override --
    -- Use to check for Infilictors (fe. Buffs) --
    ----------------------------------------------
    -- @param: EventData
    -- @return: none
    function object:oncombateventOverride(EventData)
    self:oncombateventOld(EventData)

    local nAddBonus = 0

    if EventData.Type == "Ability" then
    if EventData.InflictorName == "Ability_Pyromancer2" then
    nAddBonus = nAddBonus + object.nDragonUse
    elseif EventData.InflictorName == "Ability_Pyromancer1" then
    nAddBonus = nAddBonus + object.nPhoenixUse
    elseif EventData.InflictorName == "Ability_Pyromancer4" then
    nAddBonus = nAddBonus + object.nBlazingUse
    end
    elseif EventData.Type == "Item" then
    if core.itemSheepstick ~= nil and EventData.SourceUnit == core.unitSelf:GetUniqueID() and EventData.InflictorName == core.itemSheepstick:GetName() then
    nAddBonus = nAddBonus + self.nSheepstickUse
    end
    end

    if nAddBonus > 0 then
    core.DecayBonus(self)
    core.nHarassBonus = core.nHarassBonus + nAddBonus
    end

    end
    -- override combat event trigger function.
    object.oncombateventOld = object.oncombatevent
    object.oncombatevent = object.oncombateventOverride



    3.4 Using Skills.
    We now have all the pieces in place to start building some logical precedence behind Scorcher's behaviour with regards to skills and items which is influenced via previous actions.

    In its simplest form the use of a particular skill is based upon a logical check with regards to

    • Is the skill available (leveled, mana, cooldown)
    • is the present bot harassment level above the predefined threshold to use a skill/item
    • additional check ( range to target, additional targets, other skills/items available)





    ---code snippit

    -- Phoenix Wave
    if not bActionTaken then
    local abilPhoenix = skills.abilQ
    if abilPhoenix:CanActivate() and nLastHarassUtility > botBrain.nPhoenixThreshold then
    local nRange = abilPhoenix:GetRange()
    if nTargetDistanceSq < (nRange * nRange) then
    bActionTaken = core.OrderAbilityPosition(botBrain, abilPhoenix, vecTargetPosition)
    else
    bActionTaken = core.OrderMoveToUnitClamp(botBrain, unitSelf, unitTarget)
    end
    end
    end



    This code snippit shows the logic which shall determine whether Scorcher shall use his PhoenixWave skill.


    What happens next is the present harass level is compared to the threshold set to use PhoenixWave (as well as if PhoenixWave can be activated).

    If this is then met a range check to the target is performed before a decision is made to either close the distance between self and the target OR cast the spell at the targets location.

    Since PhoenixWave is a TargetPosition type spell the OrderAbilityPosition() method is used. For spells that target entities (heroes/creeps/pets...) ie: Sheepstick, PhoenixWave and BlazingStrike which are TargetEntity type casts, the functions OrderItemEntityClamp() & OrderAbilityEntity() are used.

    The logical flow of Scorchers HarassHeroExecute shall be such:
    1. If the target isn't vulnerable try to use Sheepstick and then DragonFire stun
    2. If the bots aggressive level is high enough use PhoenixWave
    3. If the bots aggressive level is higher still then use BlazingStrike





    --------------------------------------------------------------
    -- Harass Behavior --
    -- All code how to use abilities against enemies goes here --
    --------------------------------------------------------------
    -- @param botBrain: CBotBrain
    -- @return: none
    --
    local function HarassHeroExecuteOverride(botBrain)

    local unitTarget = behaviorLib.heroTarget
    if unitTarget == nil then
    return object.harassExecuteOld(botBrain) --Target is invalid, move on to the next behavior
    end


    local unitSelf = core.unitSelf
    local vecMyPosition = unitSelf:GetPosition()
    local nAttackRange = core.GetAbsoluteAttackRangeToUnit(unitSelf, unitTarget)
    local nMyExtraRange = core.GetExtraRange(unitSelf)

    local vecTargetPosition = unitTarget:GetPosition()
    local nTargetExtraRange = core.GetExtraRange(unitTarget)
    local nTargetDistanceSq = Vector3.Distance2DSq(vecMyPosition, vecTargetPosition)


    local nLastHarassUtility = behaviorLib.lastHarassUtil
    local bCanSee = core.CanSeeUnit(botBrain, unitTarget)
    local bActionTaken = false

    --since we are using an old pointer, ensure we can still see the target for entity targeting
    if core.CanSeeUnit(botBrain, unitTarget) then
    local bTargetVuln = unitTarget:IsStunned() or unitTarget:IsImmobilized() or unitTarget:IsPerplexed()
    local abilDragon = skills.abilW
    local abilBlazing = skills.abilR
    core.FindItems()
    local itemSheepstick = core.itemSheepstick

    -- Dragon Fire or Sheep - on unit.
    if not bActionTaken and not bTargetVuln then
    if itemSheepstick then
    local nRange = itemSheepstick:GetRange()
    if itemSheepstick:CanActivate() and nLastHarassUtility > botBrain.nSheepstickThreshold then
    if nTargetDistanceSq < (nRange*nRange) then
    bActionTaken = core.OrderItemEntityClamp(botBrain, unitSelf, itemSheepstick, unitTarget)
    end
    end
    end

    if abilDragon:CanActivate() and nLastHarassUtility > botBrain.nDragonThreshold then
    local nRange = abilDragon:GetRange()
    if nTargetDistanceSq < (nRange * nRange) then
    bActionTaken = core.OrderAbilityPosition(botBrain, abilDragon, vecTargetPosition)
    end
    end
    end
    end


    -- Phoenix Wave
    if not bActionTaken then
    local abilPhoenix = skills.abilQ
    if abilPhoenix:CanActivate() and nLastHarassUtility > botBrain.nPhoenixThreshold then
    local nRange = abilPhoenix:GetRange()
    if nTargetDistanceSq < (nRange * nRange) then
    bActionTaken = core.OrderAbilityPosition(botBrain, abilPhoenix, vecTargetPosition)
    else
    bActionTaken = core.OrderMoveToUnitClamp(botBrain, unitSelf, unitTarget)
    end
    end
    end

    -- Blazing Strike
    if core.CanSeeUnit(botBrain, unitTarget) then
    local abilBlazing = skills.abilR
    if not bActionTaken then --and bTargetVuln then
    if abilBlazing:CanActivate() and nLastHarassUtility > botBrain.nBlazingThreshold then
    local nRange = abilBlazing:GetRange()
    if nTargetDistanceSq < (nRange * nRange) then
    bActionTaken = core.OrderAbilityEntity(botBrain, abilBlazing, unitTarget)
    else
    bActionTaken = core.OrderMoveToUnitClamp(botBrain, unitSelf, unitTarget)
    end
    end
    end
    end


    if not bActionTaken then
    return object.harassExecuteOld(botBrain)
    end
    end



    -- overload the behaviour stock function with custom
    object.harassExecuteOld = behaviorLib.HarassHeroBehavior["Execute"]
    behaviorLib.HarassHeroBehavior["Execute"] = HarassHeroExecuteOverride






    4 Conclusion

    We now have a completed bot that will interact with with the game and has some, be it basic, custom logic to determine as and when it should utilise its skills.

    The behaviourLib.lua file contains other behavioural methods which can be overridden to further enhance how a bot interacts with the game. Should it be an agressive pusher, should it concentrate on ONLY denying etc.


    Complete scorcher.bot file
    Complete scourcher_main.lua file



    5 What's Next?
    The next step is to start your own work on the bot code! Create a new hero bot, author a new behavior, or fix an issue! After that's done you can submit the change on the Bot Submission Forum to work with the Community Bot Team on integrating it into HoN!
    Last edited by Naib; 07-11-2013 at 07:55 PM.

    Forum Moderators are not S2 Games employees. My posts in no way represent the view of S2 Games or any of its staff.

    Please use the report post function to have me review a post that you believe is breaking the Forum Rules.
    Check the Sticky Threads for additional information on this sub-forum and the Announcement Threads for more information about Heroes of Newerth as a whole!

    -----------------------------


Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •