• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!

SmartAI v1.1

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: Imadori and Ofel
SmartAI v1.1

Introduction

Code

Tutorials and Instructions

Credits

Not Supported

Changelogs

Bugs


Since the AI editor has a limited functionality for searching to pick and buy items. This system allows you to fully customize everything.

Featured:
- Allows AI to pick up nearby items
- Optionally searches allied shops to buy items
- Customize shopping time
- Customize maximum item that can be picked/bought
- Configurable picks only 1 item type
- Many more inside the code

JASS:
library SmartAI /* v1.1 by mckill2009
************************************************************************************
  
    - Allows AI to pick up nearby items
    - Optionally searches allied shops to buy items

************************************************************************************

    */ uses /*
        */ Table            /* http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
        */ GetClosestWidget /* http://www.hiveworkshop.com/forums/jass-resources-412/snippet-getclosestwidget-204217/
        */ IsUnitChanneling /* http://www.hiveworkshop.com/forums/jass-resources-412/snippet-isunitchanneling-211254/

************************************************************************************

    Installation:
        - Just copy this script and the required libraries to your map

************************************************************************************

    API:
        static method add takes unit picker, boolean buyItems returns nothing
            - Picker should be a hero
            - Optionally buys items
            
        static method remove takes unit picker returns nothing       
            - Removes the hero from the system OFC XD
            
        static method addUnpickableItem takes integer itemID returns nothing
            - If the hero reaches it's maximum level it will ignore any registered item types                

        static method registerShopItem takes unit shop, integer itemID returns nothing
            - Shops and itemID must be registered before the hero can buy any item      
*/

globals
    /**************************************************************
    *   Chance that the AI will search for allied shops to buy items
    *   based on percentage, 30 means 30%
    ***************************************************************/
    private constant integer CHANCE_TO_SEARCH_FOR_SHOPS = 30
    
    
    /**************************************************************
    *   AI will pick or buy items based on this setting
    *   default is 5 maximum is 6
    ***************************************************************/
    private constant integer MAX_ITEM_PICK = 5
    
    
    /**************************************************************
    *   This should be matched with the Hero Maximum Level in
    *   Gameplay Constants, coz the hero may pick an unpickable
    *   item such as an experience powerup when he's already at max level
    ***************************************************************/    
    private constant integer HERO_MAX_LEVEL_IN_MAP = 10
    
    
    /**************************************************************
    *   Checks how many gold the player has, if it's below this number
    *   the AI will not buy items
    ***************************************************************/  
    private constant integer MINIMUM_GOLD_AMOUNT_TO_GO_SHOPPING = 1000
    
    
    /**************************************************************
    *   Checks if the hero has already an item type, else it will ignore it
    ***************************************************************/  
    private constant boolean PICK_UP_ONLY_ONE_ITEM_TYPE = true 
    
    
    /**************************************************************
    *   Checks the surrounding if there's an item to pick
    ***************************************************************/
    private constant real ITEM_RANGE_PICK = 500
    
    
    /**************************************************************
    *   This is the delay in which the hero will search for a shop  
    *   to buy items but depends on the CHANCE_TO_SEARCH_FOR_SHOPS
    *   and MINIMUM_GOLD_AMOUNT_TO_GO_SHOPPING
    ***************************************************************/
    private constant real SEARCH_FOR_SHOP_DELAY = 10 //300
    
    
    /**************************************************************
    *   This is the time duration to buy items   
    ***************************************************************/
    private constant real SHOPPING_TIME = 5    
    
    
    /**************************************************************
    *   SHOPPING_TIME starts when hero is NEAR_SHOP  
    ***************************************************************/
    private constant real NEAR_SHOP = 40000 //SquareRoot 200
    
    /**************************************************************
    *   Searches closest shop in range of hero
    ***************************************************************/
    private constant real SHOP_IN_RANGE = 5000
    
/********************************************************************************************************
*   Non- configurable globals, functions and codes, this means DO NOT TOUCH ALL CODES BELOW THIS LINE!
*********************************************************************************************************/
    private constant integer ATTACK = 851983
    private constant integer MOVE = 851986    
    private constant integer SMART = 851971 //pick item command
    private constant integer SHOP_SELECT_ORDER = 852566
    private constant integer SHOP_ABILITY_ID = 'Apit'
    private constant real INTERVAL = 1.0
    private boolean array IsBuying
    private item itm
    private rect Rct
    private integer array itemIds
    private integer itemIdCount = 2
    private Table dl
    private Table thisInd
    private Table sInd
    private TableArray si
endglobals

private function UnitAlive takes unit u returns boolean
    return not IsUnitType(u, UNIT_TYPE_DEAD)
endfunction

private function GetDistance takes real x1, real y1, real x2, real y2 returns real
    return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)
endfunction

private function FilterShop takes unit shop returns boolean
    return UnitAlive(shop) and GetUnitAbilityLevel(shop, SHOP_ABILITY_ID) > 0
endfunction

private function IsItemPickable takes item it returns boolean
    local integer i = 0
    loop
        if (GetItemTypeId(it)==itemIds[i]) then
            return true
        endif
        set i = i+1
        exitwhen i==itemIdCount
    endloop
    return false
endfunction

private function CountItemsInSlot takes unit u returns integer
    local integer count = 0
    local integer slot = 0
    loop
        if (UnitItemInSlot(u, slot) != null) then
            set count = count + 1
        endif
        set slot = slot + 1
        exitwhen slot==6
    endloop
    return count
endfunction

private function UnitHasItemType takes unit u, integer itemId returns boolean
    local integer i = 0
    loop
        set itm = UnitItemInSlot(u, i)
        if GetItemTypeId(itm)==itemId then
            return true
        endif     
        set i = i+1
        exitwhen i==6        
    endloop
    return false
endfunction

private module init
    private static method onInit takes nothing returns nothing
        set Rct = Rect(-ITEM_RANGE_PICK, -ITEM_RANGE_PICK, ITEM_RANGE_PICK, ITEM_RANGE_PICK)    
        set itemIds[0] = 'texp' //tome of experience
        set itemIds[1] = 'tkno' //tome of power
        set thisInd = Table.create()
        set sInd = Table.create()
        set dl = Table.create()
        set si = TableArray[0x3000]
    endmethod  
endmodule

//! textmacro DEALLOCATE
    call .deallocate()
    set instanceAR[i] = instanceAR[instance]
    set instanceAR[instance] = this
    set instance = instance - 1 
    set i = i-1
    if instance==0 then
        call PauseTimer(t)
        call DestroyTimer(t)
    endif
//! endtextmacro

private struct Buy
    private unit buyer
    private unit shop
    private real dur
    private integer hID
    private boolean buying
    
    private static integer instance = 0
    private static integer array instanceAR
    private static timer t
    
    private static method heroIsBuying takes nothing returns nothing
        local thistype this
        //local SmartAI sm
        local integer i = 0        
        local integer id
        loop
            set i = i + 1
            set this = instanceAR [i]
            if UnitAlive(.buyer) and UnitAlive(.shop) and .buying then
                if .dur > 0 then
                    if (GetDistance(GetUnitX(.buyer), GetUnitY(.buyer), GetUnitX(.shop), GetUnitY(.shop)) < NEAR_SHOP) then
                        set .dur = .dur - INTERVAL
                    endif           
                else
                    set .buying = false
                endif
            else
                set dl.real[.hID] = 0
                set IsBuying[thisInd[.hID]] = false
                set .buyer = null
                set .shop = null
                //! runtextmacro DEALLOCATE()
            endif        
            exitwhen i==instance
        endloop
    endmethod
    
    static method run takes unit buyer, unit shop returns nothing
        local thistype this = allocate()
        set .buyer = buyer
        set .shop = shop
        set .dur = SHOPPING_TIME
        set .buying = true
        set .hID = GetHandleId(buyer)
        if instance==0 then
            set t = CreateTimer()
            call TimerStart(t, INTERVAL, true, function thistype.heroIsBuying)
        endif            
        set instance = instance + 1
        set instanceAR[instance] = this 
    endmethod
endstruct

struct SmartAI
    private unit hero
    private unit shop
    private boolean pick
    private boolean buyItems
    private player pl
    private integer hID
    //boolean isBuying
    
    private static thistype DATA
    private static integer instance = 0
    private static integer array instanceAR
    private static timer t
    
    implement init
    
    private static method closestAllyShop takes nothing returns boolean
        local thistype this = DATA
        return FilterShop(GetFilterUnit()) and not IsUnitEnemy(GetFilterUnit(), .pl)
    endmethod
    
    private static method onPickItem takes nothing returns nothing
        local thistype this = DATA
        if not IsUnitChanneling(.hero) then
            if PICK_UP_ONLY_ONE_ITEM_TYPE then
                if not UnitHasItemType(.hero, GetItemTypeId(GetEnumItem())) then
                    call IssueTargetOrderById(.hero, SMART, GetEnumItem())
                endif
            else
                call IssueTargetOrderById(.hero, SMART, GetEnumItem())
            endif  
        endif
    endmethod 
    
    //This has a condition MAX_ITEM_PICK 
    private static method filterItems takes nothing returns boolean
        local thistype this = DATA
        if GetHeroLevel(.hero)==HERO_MAX_LEVEL_IN_MAP then
            return not IsItemPickable(GetFilterItem())
        endif
        return GetItemType(GetFilterItem())!=ITEM_TYPE_POWERUP
    endmethod
    
    //Ensures to pick all powerup items exept the blocked ones
    private static method filterPowerupItems takes nothing returns boolean
        local thistype this = DATA
        if GetHeroLevel(.hero)==HERO_MAX_LEVEL_IN_MAP then
            return not IsItemPickable(GetFilterItem())
        endif  
        return GetItemType(GetFilterItem())==ITEM_TYPE_POWERUP or GetItemTypeId(GetFilterItem())=='tkno'
    endmethod
    
    private static method lookForItems takes nothing returns nothing
        local thistype this
        local integer i = 0
        local integer sID
        local real distance
        local real x
        local real y
        loop
            set i = i + 1
            set this = instanceAR [i]
            if .pick then   
                if UnitAlive(.hero) then
                    set x = GetUnitX(.hero)
                    set y = GetUnitY(.hero)
                    set DATA = this
                    
                    if CountItemsInSlot(.hero) < MAX_ITEM_PICK then
                        /***************************************************
                        *   Unit will search for allied shops to buy items
                        ****************************************************/
                        if .buyItems then 
                            if GetPlayerState(.pl, PLAYER_STATE_RESOURCE_GOLD) > MINIMUM_GOLD_AMOUNT_TO_GO_SHOPPING then
                                set dl.real[.hID] = dl.real[.hID] + INTERVAL
                                if (dl.real[.hID] > SEARCH_FOR_SHOP_DELAY) then
                                    if IsBuying[this] then
                                        if UnitAlive(.shop) and not IsUnitChanneling(.hero) then
                                            if GetDistance(x, y, GetUnitX(.shop), GetUnitY(.shop)) > NEAR_SHOP then
                                                call IssuePointOrderById(.hero, MOVE, GetUnitX(.shop), GetUnitY(.shop))
                                            else
                                                call IssueTargetOrderById(.shop, SHOP_SELECT_ORDER, .hero)
                                                set sID = GetHandleId(.shop)
                                                call IssueImmediateOrderById(.shop, si[GetRandomInt(1, sInd[sID])][sID])
                                            endif
                                        endif                
                                    else
                                        if GetRandomInt(0, 100) < CHANCE_TO_SEARCH_FOR_SHOPS then
                                            set .shop = GetClosestUnitInRange(x, y, SHOP_IN_RANGE, Filter(function thistype.closestAllyShop))
                                            if .shop!=null then
                                                set IsBuying[this] = true
                                                call Buy.run(.hero, .shop)
                                            endif
                                        endif                    
                                    endif 
                                endif
                            endif
                        endif
                        
                        /***************************************************
                        *   Unit will search for nearby items
                        ****************************************************/                        
                        call MoveRectTo(Rct, x, y)
                        if GetRandomInt(0,2)==2 then
                            call EnumItemsInRect(Rct, Filter(function thistype.filterItems), function thistype.onPickItem)
                        else
                            call EnumItemsInRect(Rct, Filter(function thistype.filterPowerupItems), function thistype.onPickItem)
                        endif
                    endif
                endif
            else
                set .hero = null
                set .shop = null
                set .pl = null
                //! runtextmacro DEALLOCATE()
            endif
            exitwhen i==instance
        endloop
    endmethod
    
    /*************************************************************
    *   API
    **************************************************************/
    static method add takes unit picker, boolean buyItems returns nothing
        local thistype this
        if thisInd.has(GetHandleId(picker)) then
            call BJDebugMsg("[thistype][add] ERROR: "+GetUnitName(picker)+" cannot be added twice!")
        else
            set this = thistype.allocate()
            set .hero = picker
            set .pl = GetOwningPlayer(.hero)
            set .pick = true
            set .shop = null
            set IsBuying[this] = false
            set .buyItems = buyItems
            set .hID = GetHandleId(.hero)
            set thisInd[.hID] = this
            set dl.real[.hID] = 0
            if instance==0 then
                set t = CreateTimer()
                call TimerStart(t, INTERVAL, true, function thistype.lookForItems)
            endif            
            set instance = instance + 1
            set instanceAR[instance] = this 
        endif
    endmethod
    
    static method remove takes unit picker returns nothing
        local thistype this = thisInd[GetHandleId(picker)]
        set .pick = false
    endmethod
    
    static method addUnpickableItem takes integer itemID returns nothing
        set itemIds[itemIdCount] = itemID
        set itemIdCount = itemIdCount + 1        
    endmethod
    
    static method registerShopItem takes unit shop, integer itemID returns nothing
        local integer id = GetHandleId(shop)
        set sInd[id] = sInd[id] + 1
        set si[sInd[id]][id] = itemID
    endmethod
endstruct

endlibrary

First

Import the following libraries to your map;
Table
GetClosestWidget
IsUnitChanneling
Second

  • DEMO
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • -------- Add unpickable items when hero reaches it's maximum level, the item will be ignored --------
      • -------- This is optional but recommended --------
      • Custom script: call SmartAI.addUnpickableItem('ckng') //Crown of Kings
      • Custom script: call SmartAI.addUnpickableItem('modt') //Mask of Death
      • Unit Group - Pick every unit in (Units in (Playable map area) matching (((Matching unit) is A Hero) Equal to True)) and do (Actions)
        • Loop - Actions
          • -------- Registers all Heros in the map, with an option to buy items or not --------
          • Custom script: call SmartAI.add(GetEnumUnit(), true)
      • -------- Every item in every shop must be registered --------
      • Unit Group - Pick every unit in (Units of type Arcane Vault) and do (Actions)
        • Loop - Actions
          • Custom script: call SmartAI.registerShopItem(GetEnumUnit(), 'sreg') //scroll of regeneration
          • Custom script: call SmartAI.registerShopItem(GetEnumUnit(), 'plcl') //lesser clarity potion
          • Custom script: call SmartAI.registerShopItem(GetEnumUnit(), 'mcri') //mechanical critter

Table by Bribe
GetClosestWidget by Spinnaker
IsUnitChanneling by Magtheridon96

- Selling of items
- Giving items to other heroes
- Use items
- Drop useless items

v1.0
- IsUnitChanneling imported and added global item variable for filtering purposes



Keywords:
mckill2009, dota, buy, sell, item, items, pick, use, power, tome, charge, ai, shop
Contents

SmartAI (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 18:54, 25th Sep 2013 PurgeandFire: Review: http://www.hiveworkshop.com/forums/spells-569/smartai-v1-1-a-239735/#post2422041

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.

18:54, 25th Sep 2013
PurgeandFire: Review:
http://www.hiveworkshop.com/forums/spells-569/smartai-v1-1-a-239735/#post2422041
 
It looks ugly with "."
I get irritated :D

Well boohoo princess

Our standard here doesn't define anything for struct member access, but a common convention is this.member (explicit this with the dot resolution operator)
Another common one is implicit this without the dot resolution operator: member
I only see .member coming out of Asian modders ;|
 
I dont think so, as I've tested it already in many of my systems, works just as expected, but oh well, no harm in doing that...

JASS:
private function UnitNotChanneling takes unit u returns boolean
    local integer orderID = GetUnitCurrentOrder(u)
    return (orderID != SMART) and (orderID==MOVE or orderID==ATTACK or orderID==0)
endfunction

I see how your function works. It has incorrect behavior as a free standing function however. If I call it from inside a spell event, it will report incorrect information.
(This is no _real_ problem here)
 
Last edited:
Review:
  • JASS:
    private function UnitAlive takes unit u returns boolean
        return not IsUnitType(u, UNIT_TYPE_DEAD)
    endfunction
    Should be:
    JASS:
    private function UnitAlive takes unit u returns boolean
        return not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0
    endfunction
    Otherwise it will return false for removed units.
  • In IsItemPickable, you should store the item type id into a local so that the function won't be evaluated each time the loop iterates.
  • (optional) remove DestroyTimer() and just initialize t to CreateTimer(). I like using one handle throughout, but if you want to destroy/allocate when you need it that is completely fine.
  • (optional)
    JASS:
        static method remove takes unit picker returns nothing
            local thistype this = thisInd[GetHandleId(picker)]
            set .pick = false
        endmethod
    Can be:
    JASS:
        static method remove takes unit picker returns nothing 
            set thistype(thisInd[GetHandleId(picker)]).pick = false
        endmethod
    It is ugly but that would allow it to inline afaik.
  • (optional)
    JASS:
        static method registerShopItem takes unit shop, integer itemID returns nothing
            local integer id = GetHandleId(shop)
            set sInd[id] = sInd[id] + 1
            set si[sInd[id]][id] = itemID
        endmethod
    Could be:
    JASS:
        static method registerShopItem takes unit shop, integer itemID returns nothing
            local integer id  = GetHandleId(shop)
            local integer val = sInd[id] + 1
            set sInd[id]    = val
            set si[val][id] = itemID
        endmethod
    It saves one hashtable read. It is minor though, so it is optional for you to implement.

Other than that, I think it is a nice system. I don't really like the name though. SmartAI seems like it encompasses a lot more. I would choose something like HeroItemAI or something. But it is up to you.
 
Top