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