- Joined
- Mar 18, 2012
- Messages
- 1,716
I was looking for names without meaning beyond being awesome. IAmBot is the new library name. Special thanks to WaterKnight.
The attached map is an old one and most part of the code had a radical change.
I'll update the code step by step each time one block is finished.
Limitations:
The amount of active units during the same time is directly limited by the maximum amount of allocations of the linked list struct.
I guess that in the end one unit consumes ~1.3 instances.
In conclusion it safely freezes your screen without creating an overflow.
The attached map is an old one and most part of the code had a radical change.
I'll update the code step by step each time one block is finished.
Limitations:
The amount of active units during the same time is directly limited by the maximum amount of allocations of the linked list struct.
I guess that in the end one unit consumes ~1.3 instances.
In conclusion it safely freezes your screen without creating an overflow.
JASS:
/* With special thanks to WaterKnight. */
library IAmBot /* v1.0.0
**************************************************************************************
*
* */uses/*
*
* */ List /* - [url]https://github.com/nestharus[/url]
* */ Table /* - [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/[/url]
* */ TimerUtils /* - [url]http://www.wc3c.net/showthread.php?t=101322[/url]
* */ RegisterPlayerUnitEvent /* hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*
* Requires one of these UnitIndexers:
*________________________________
* */ optional UnitIndexer /* - [url]https://github.com/nestharus[/url]
* */ optional UnitDex /* - [url]http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/[/url]
*¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* */ optional IAmBotDebug /*
*
* Credits to Bribe, Nestharus, Magtheridon96, TriggerHappy and Vexorian
*
**************************************************************************************
*/
native UnitAlive takes unit id returns boolean
/*
**************************************************************************************
*
* 1. Import instruction
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the IAmBot script and the required libraries into your map.
*
* 2. API
* ¯¯¯¯¯¯
* First and foremost all struct members.
* -> Access is granted via IAmBot instance which equals UnitUserData.
*
* Fields are (all readonly)
* - who, whoX, whoY, id (UnitTypeId)
* - owner
* - aim, aimX, aimY
* - angle (towards aim)
* - archer (is of type archer)
* - maxRange
*
* Spam protection members
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - paused (paused > 0 skips all operations of the periodic loop)
* --> decreases by 1 each loop.
* - cd (cd > 0 skips some operations of the periodic loop, i.e. casting spells)
* --> decreases by 1 each loop.
*
* Method operators:
*
* static method operator [] takes unit whichUnit returns IAmBot
* --> returns UnitUserData
*
* method operator isBot takes nothing returns boolean
*
* method operator pause= takes integer value returns nothing
* method operator cooldown= takes integer value returns nothing
* method operator target= takes unit whichUnit returns nothing
*
* Methods:
*
* method smart takes nothing returns boolean
* method attack takes nothing returns boolean
* method move takes real x, real y returns boolean
*
* method isUnitInGroup takes thistype whichGroup returns boolean
* method countGroupAlliesInRangeXY takes real x, real y, real range returns integer
* method countAlliesInRangeXY takes real x, real y, real range returns integer
*
* 3. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
/* General IAmBot library config
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Debug only: Print out every bot descicion through texttags and textmessages.
*/
private constant boolean PRINT_BOT_DESCISION = true
/*
* At which size a trigger will be rebuilt. A value of 80 units per trigger is recommended.
* - Information: A larger trigger size will save perfomance, but also creates more triggers.
*
* Which event is the core event of the IAmBot struct. EVENT_UNIT_ACQUIRED_TARGET is reommended.
*/
private constant integer TRIGGER_SIZE = 80
private constant unitevent TRIGGER_EVENT = EVENT_UNIT_ACQUIRED_TARGET
/*
* IAmBot struct config
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* How long is one unit controlled by IAmBot. Refreshes on unit interaction.
*
* How much time elapse between two loops. Recommended margin lies between 0.8 - 3.0 seconds.
* - Information: Lower values create more overhead, but can improve unit behaviour.
* A reasonable timeout is recommended ¯¯¯
*
*/
private constant real DEFAULT_DURATION = 18.
private constant real CLOCK_TIMEOUT = 2.5
/*
* Maximum size of one unit group. Units in one group share the same group behaviour.
* - Information: A smaller group size will decrease the loop duration, but create more timers.
*/
private constant integer GROUP_SIZE = 6
/*
* Archer type units will retreat for DEFAULT_RETREAT_DISTANCE if their aim is of type meele and within MININUM_ARCHER_RANGE.
*/
private constant real MININUM_ARCHER_RANGE = 160.
private constant real DEFAULT_RETREAT_DISTANCE = 260.
/*
* Meele type units will consider their aim as WITHIN_MEELE_RANGE.
* - Information: Implemented as range buffer. Aim priority decreases outside this boundary.
*/
private constant real WITHIN_MEELE_RANGE = 270.
/*
* Aims outside DEFAULT_MAXIMUM_RANGE are not considered as valid targets.
* - Information: You can customize this range per UnitTypeId by using library IAmBotExUnitInfo.
*/
private constant real DEFAULT_MAXIMUM_RANGE = 1200.
/*
* Units within CONSIDER_GROUP_RANGE can act like group units.
* Fresh activated units will look for incomplete groups within this range.
*
* A fresh activated unit can activate nearby inactive allies within TEAM_UP_RANGE.
*/
private constant real CONSIDER_GROUP_RANGE = 1500.
private constant real TEAM_UP_RANGE = 500.
/*
* The stun buff your map is using. You may have a custom stun system in your map.
* - Information: Stunned units skip large parts of the periodic loop.
*/
private constant integer STUN_BUFF = 'BSTN'
/*
* Player neutral passive is never considered by IAmBot.
*
* IAmBot distinguishs between active and computer players. The latter gets injected by the IAmBot controlling system :).
*/
private constant player NEUTRAL_PASSIVE_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
private boolean array PLAYING_PLAYER
endglobals
/*
* IAmBot function config
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
private function IsUnitNotStunned takes unit wichUnit returns boolean
return (0 == GetUnitAbilityLevel(wichUnit, STUN_BUFF))
endfunction
/*
* Units sharing the same compatibility index are drift compatible and interact as allies.
* - Information: This function runs once on index event and eventually on unit change owner event.
*/
private function AssignCompatibilityIndex takes unit whichUnit returns integer
return GetPlayerId(GetOwningPlayer(whichUnit))
endfunction
/*
* Units passing this filter are taken over by IAmBot.
* - Information: Ensure to filter out playing player units and dummy units.
* Each unit gets its own trigger, hence this filter must be very neat.
*/
private function onFilter takes unit indexed returns boolean
return not PLAYING_PLAYER[GetPlayerId(GetOwningPlayer(indexed))] and (GetOwningPlayer(indexed) != NEUTRAL_PASSIVE_PLAYER)
endfunction
/* End of configuration
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯*/
//IAmBot is not using group < agent < handle. IAmBot groups are linked lists.
struct BotList extends array
implement List
readonly integer index//Connect node with UnitUserData.
static timer array clock//Connect collection with a timer.
method link takes integer i returns nothing
set index = i
endmethod
endstruct
private keyword IAmBotInit
private keyword requestHelp
private keyword registerHealer
private keyword releaseHealer
private keyword IAmBotExUnitStateClear
private keyword IAmBotExUnitStateUpdate
private keyword IAmBotExUnitStateInit
private keyword IAmBotExUnitStateAPI
private keyword IAmBotExUnitStateResponsePrimary
private keyword IAmBotExUnitStateResponseSecondary
//! runtextmacro optional I_AM_BOT_EXPANSION_UNITSTATE_CODE()
struct IAmBot extends array
readonly player owner
readonly unit who
readonly real whoX
readonly real whoY
readonly unit aim
readonly real aimX
readonly real aimY
readonly real angle
readonly integer id
readonly boolean archer
readonly real maxRange
//Spam protection
readonly integer paused
readonly integer cd
readonly static IAmBot current //Required to fire custom events. --> current = this --> TriggerEvaluate(trigger)
readonly static IAmBot secondary//Required for unit unit interaction.
readonly integer comp//Drift compatibility. YAY! We can drive the Gipsy Danger.
readonly boolean active//Unit status active.
private real time//== 0 will set the unit to inactive. Refreshes onAquire and damage input.
private BotList collection//All unit groups are data structures of type list.
private static integer array nodeCounter//Each collection also has a node counter.
static boolean array has//Controll taken over by IAmBot.
private static group enu = CreateGroup()//For various group enumerations within the IAmBot library.
static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
private IAmBotTexttag text
endif
//----------------------Default API functions----------------------
method move takes real x, real y returns boolean
return IssuePointOrderById(who, 851986, x, y)
endmethod
//Directly attack aim.
method smart takes nothing returns boolean
return IssueTargetOrderById(who, 851971, aim)
endmethod
//Attack the x/y of aim.
method attack takes nothing returns boolean
return IssuePointOrderById(who, 851983, aimX, aimY)
endmethod
static method operator [] takes unit u returns IAmBot
return thistype(GetUnitUserData(u))
endmethod
method operator isBot takes nothing returns boolean
return has[this]
endmethod
method operator cooldown= takes integer value returns nothing
set cd = value
endmethod
method operator pause= takes integer value returns nothing
set paused = value
endmethod
method operator target= takes unit whichUnit returns nothing
set aim = whichUnit
endmethod
method isUnitInGroup takes thistype node returns boolean
static if DEBUG_MODE then
if not has[this] then//Probably never true.
call BJDebugMsg("ERROR: library IAmBot, method isUnitInGroup, passed in unit is not a bot.")
endif
if (0 == node.collection) then
call BJDebugMsg("ERROR: library IAmBot, method isUnitInGroup, passed in invalid collection")
endif
endif
return (collection == node.collection) and (0 != node.collection)
endmethod
method countGroupAlliesInRangeXY takes real x, real y, real range returns integer
local integer c = 0
local BotList node = collection.first
loop
exitwhen 0 == node
if IsUnitInRangeXY(IAmBot(node.index).who, x, y, range) and (node.index != this) then
set c = c + 1
endif
set node = node.next
endloop
return c
endmethod
method countAlliesInRangeXY takes real x, real y, real range returns integer
local integer c = 0
local thistype node
local unit u
call GroupEnumUnitsInRange(thistype.enu, x, y, range, null)
loop
set u = FirstOfGroup(thistype.enu)
exitwhen u == null
call GroupRemoveUnit(thistype.enu, u)
set node = GetUnitUserData(u)
if (has[node]) and (node.comp == this.comp) and (node != this) then
set c = c + 1
endif
endloop
return c
endmethod
//-------------------- End of API functions------------------------------
//-------------------- private functions --------------------------------
//Does not exceed the maximum group size.
private method findCloseCollection takes nothing returns BotList
local unit u
local thistype node
call GroupEnumUnitsInRange(enu, whoX, whoY, CONSIDER_GROUP_RANGE, null)
loop
set u = FirstOfGroup(enu)
exitwhen u == null
call GroupRemoveUnit(enu, u)
set node = GetUnitUserData(u)
//Unit has to be active
//Alive
//node != this
//Drift compatible
if (node.active) and (UnitAlive(u)) and (node != this) and (node.comp == this.comp) then
if (nodeCounter[node.collection] < GROUP_SIZE) then
static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
call IAmBotTexttag.display("I'll join group " + I2S(node.collection), who)
endif
set u = null
return node.collection
endif
endif
endloop
return 0
endmethod
//Method enforce may exceed the maximum group size.
private method enforce takes unit target, thistype same returns nothing
local BotList node
call setup(target)
//Using the same list. Group size is ignored.
set collection = same.collection
set node = collection.enqueue()
call node.link(this)
set nodeCounter[collection] = nodeCounter[collection] + 1
//Unit status active.
set active = true
static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
set text = IAmBotTexttag.create(who, collection)
endif
//Issue attack on the same spot as the unit, which enforced this unit to status active.
call attack()
endmethod
//----------------------------------------------------
//-------------------- Cleanup --------------------------------
private method clear takes nothing returns nothing
static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
call text.destroy()
endif
static if DEBUG_MODE and PRINT_BOT_DESCISION then
debug call BJDebugMsg("Clear IAmBot for |cff20b2aa" + GetUnitName(who))
endif
implement optional IAmBotExUnitStateClear
set who = null
set aim = null
set owner = null
set active = false
set nodeCounter[collection] = nodeCounter[collection] - 1
static if DEBUG_MODE and PRINT_BOT_DESCISION then
debug call BJDebugMsg(I2S(nodeCounter[collection]) + " remaining units in collection " + I2S(collection))
endif
if (0 == collection.first) then
call destroyCollection()
endif
//Required for method findCloseCollection.
set collection = 0
endmethod
private method destroyCollection takes nothing returns nothing
debug if BotList.clock[collection] == null then
debug call BJDebugMsg("ERROR, library IAmBot, method destroyCollection, ATTEMPT TO RELEASE A NULL TIMER")
debug endif
call ReleaseTimer(BotList.clock[collection])
static if DEBUG_MODE and PRINT_BOT_DESCISION then
debug call BJDebugMsg("Collection " + I2S(collection) + " is empty and gets destroyed. Timer is stopped.")
endif
call collection.destroy()
set BotList.clock[collection] = null
set nodeCounter[collection] = 0
endmethod
//-------------------- End of Cleanup functions--------------------
//Runs for each BotList every CLOCK_TIMEOUT seconds.
private static method onPeriodic takes nothing returns nothing
//----------------- local instances -----------------
local BotList temp = GetTimerData(GetExpiredTimer())//temp is the current collection. TimerUtils API
local BotList node = temp.first
local thistype this
local thistype data
//---------------------------------------------------
//-------------- other local variables ---------------
local boolean going = true//Allows to skip various operations of the loop if set to false.
local boolean hasAim
local integer order//current order, still not sure if it's worth to be a struct member, future will show.
local integer count//in case something has to be counted.
//---------------------------------------------------
local unit u//group enumerations.
//Step 1: Merge groups
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//if the group size equals 1, this block will try to release that group.
if (node == temp.last) then//first == last
set this = node.index
set temp = findCloseCollection()//Can't return the same instance.
if (0 != temp) then
set u = aim//Store the current target, may be null.
//clear() is not able to destroy lists if they are not empty.
call node.remove()//Hence remove the last node.
call clear() //Cleanup
//Each unit has a reference to his list. Get the new from new list first unit.
set node = temp.first
set data = node.index
call enforce(u, data)//Same target, new list.
set u = null
return//Not entering the loop. This unit may have a additional timeout in range 0.0 to CLOCK_TIMEOUT
endif
endif
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
loop
//Step 2: Linked List usage
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
set this = node.index//Each BotList node is linked with a IAmBot instance.
set temp = node.next //Linked list double free protection.
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
if (UnitAlive(who)) then
//Optional Pluggin: UnitState requires library IAmBotExUnitState
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//
implement optional IAmBotExUnitStateUpdate
implement optional IAmBotExUnitStateResponsePrimary
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//Step 3: Spam protection
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
set cd = cd - 1//Must be cd < 0 to fire engage events.
set time = time - CLOCK_TIMEOUT//time < 0 sets unit back to status inactive. Complete cleanup.
if (0 < paused) then//Skips all actions beside those of the UniState pluggin.
set paused = paused - 1
elseif IsUnitNotStunned(who) then
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//Step 4: Gather information
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
set hasAim = UnitAlive(aim) and IsUnitInRange(who, aim, maxRange)
if hasAim then
set time = DEFAULT_DURATION
set aimX = GetUnitX(aim)
set aimY = GetUnitY(aim)
set angle = Atan2(aimY - whoY ,aimX - whoX)
set order = GetUnitCurrentOrder(who)
implement optional IAmBotExUnitStateResponseSecondary
//------------------- Yet flawed basic order -------------------
//Copied this condition from ZTS. May change while testing further AI behaviour.
if (851983 == order) or (0 == order) or (851971 == order) then
call smart()//May become a condition, because of invisible units. Needs a test. --> if false vs invis --> attack ground/move by 0 units --> new aquire event
endif
//Here will be the aim priority calculation: Status 0% :(
//---------------------------------------------------
else
set hasAim = false
endif
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//Step 5: Unique action
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
if going then
//Optional Pluggin: IAmBotExPosition Status 75% finished. Imports additional API
//Features:
// Additional new IAmBot members: none. (Table POSITION)
// --> method surround: meele units may surround the target. sets going to false
// --> method retreat: range units may retreat if their aim is meele and too close.
// --> may fire custom position event (not sure, as mentioned 75%)
//implement optional IAmBotExUnitPosition
endif
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//Step 6: Enage event
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//Optional Pluggin: IAmBotExEnage Status 100% finished. Imports additional API
//Features:
// Additional new IAmBot members: Table ENAGE
// --> fires engage event based on UnitTypeId if cd < 0
//implement optional IAmBotExUnitEnage
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
endif
//Step 7: Cleanup
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
if (0 > time) and (0 > cd) then//Has to wait until no cooldown is left
call node.remove()
call clear()
endif
else//UnitAlive condition.
call node.remove()
call clear()
endif
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//Step 8: Next node
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
set node = temp
exitwhen 0 == node
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
endloop
endmethod
//Gather basic information
method setup takes unit target returns nothing
//Not AIDS compatible, because of scope IAmBotRefresh
set who = GetUnitById(this)
set whoX = GetUnitX(who)
set whoY = GetUnitY(who)
set owner = GetOwningPlayer(who)
set id = GetUnitTypeId(who)
set archer = IsUnitType(who, UNIT_TYPE_RANGED_ATTACKER)
set maxRange = DEFAULT_MAXIMUM_RANGE//Some ideas about an optional UnitInfo add-on
set aim = target
set aimX = GetUnitX(target)
set aimY = GetUnitY(target)
implement optional IAmBotExUnitStateUpdate
implement optional IAmBotExUnitStateSetup
set paused = 0
set cd = 0
set time = DEFAULT_DURATION
endmethod
//Does not exceed the maximum group size.
private method teamUp takes nothing returns nothing
local thistype node
local unit u
call GroupEnumUnitsInRange(enu, whoX, whoY, TEAM_UP_RANGE, null)
loop
set u = FirstOfGroup(enu)
exitwhen u == null
call GroupRemoveUnit(enu, u)
set node = GetUnitUserData(u)
//IAmBot has node
//not active
//Alive
//Is drift compatible.
if (has[node]) and (not node.active) and (UnitAlive(u)) and (this.comp == node.comp) then
//Maximum group size is not reached.
if (nodeCounter[collection] < GROUP_SIZE) then
static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
call IAmBotTexttag.display("I just teamed up with " + GetUnitName(who), u)
endif
//Same aim and group.
call node.enforce(aim, this)
else
set u = null
call node.setup(aim)//May takes null argument. Doesn't matter the operation is still safe.
call node.add()//Allows to create a new group or find an incomplete one.
call node.attack()//Do not order smart, instead find the closest target.
return//Stop this operation if maximum size is reached.
endif
endif
endloop
endmethod
method add takes nothing returns nothing
local BotList node
//Try to find a close incomplete unit group.
set collection = findCloseCollection()
//if 0 no group has been found or all nearby groups are full.
if (0 == collection) then
//Create a new list. Each list has an individual timer. TimerUtils API.
set collection = BotList.create()
set BotList.clock[collection] = NewTimerEx(collection)
static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
call IAmBotTexttag.display("Couldn't find a valid group. I start my own group.", who)
endif
call TimerStart(BotList.clock[collection], CLOCK_TIMEOUT, true, function thistype.onPeriodic)
endif
//Enqueue the list. Link the node with IAmBot instance. Increase the node counter.
set node = collection.enqueue()
call node.link(this)
set nodeCounter[collection] = nodeCounter[collection] + 1
//Unit status active.
set active = true
static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
set text = IAmBotTexttag.create(who, collection)
endif
//Activate nearby allies.
call teamUp()
endmethod
static method onAquireEvent takes nothing returns boolean
local thistype this = GetUnitUserData(GetTriggerUnit())
if (active) then
set aim = null
set aim = GetEventTargetUnit()
set time = DEFAULT_DURATION
//Do not order smart here, because nothing is known about the current unit state.
else
static if DEBUG_MODE and PRINT_BOT_DESCISION and LIBRARY_IAmBotDebug then
call IAmBotTexttag.display("AQUIRE EVENT. Inactive. I'm now a bot", GetUnitById(this))
endif
//Gather basic info and add the unit to the IAmBot system.
call setup(GetEventTargetUnit())
call add()
endif
return false
endmethod
implement optional IAmBotExUnitStateAPI
static method deactivate takes integer index returns nothing
local thistype this = thistype(index)
local BotList temp = this.collection
local BotList node = temp.first
if active then
set temp = collection
set node = collection.first
loop
exitwhen 0 == node
if (node.index == this) then
call node.remove()
call clear()
return
endif
set node = node.next
endloop
endif
endmethod
static method compatibility takes thistype this returns nothing
set this.comp = AssignCompatibilityIndex(GetUnitById(this))
endmethod
private static method onInit takes nothing returns nothing
implement optional IAmBotInit
implement optional IAmBotExUnitStateInit
endmethod
endstruct
private module IAmBotInit
local integer i = 16
loop
set i = i - 1
set PLAYING_PLAYER[i] = false
if (GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING) and (GetPlayerController(Player(i)) == MAP_CONTROL_USER) then
set PLAYING_PLAYER[i] = true
endif
exitwhen i == 0
endloop
endmodule
//Of course IAmBot has a DDS Pluggin. Can use any DDS.
//! runtextmacro optional IAmBot_DAMAGE_RESPONSE_PLUGGIN()
//Special thanks to Bribe
function RegisterBotEvent takes Table eventTable, integer unitTypeId, boolexpr booleanexpression returns nothing
if not eventTable.handle.has(unitTypeId) then
set eventTable.trigger[unitTypeId] = CreateTrigger()
endif
call TriggerAddCondition(eventTable.trigger[unitTypeId], booleanexpression)
endfunction
//Nestharus TriggerRefresh with some required modifications
//
// Modifications:
// - added onFilter for index/deindex event
// - Some name changed to prevent them from interfering with other structs named Trigger.
//Special thanks to Nestharus
scope IAmBotRefresh
globals
private boolexpr condition
endglobals
struct IAmBotTrigger extends array
private static integer instanceCount = 0
private thistype first
private thistype next
private thistype prev
readonly thistype parent
private integer inactiveUnits
readonly integer activeUnits
readonly trigger trigger
static if LIBRARY_UnitDex then
private method registerUnit takes UnitDex whichUnit returns boolean
if (activeUnits < TRIGGER_SIZE) then
call TriggerRegisterUnitEvent(trigger, GetUnitById(whichUnit), TRIGGER_EVENT)
set activeUnits = activeUnits + 1
return true
endif
return false
endmethod
else
private method registerUnit takes UnitIndex whichUnit returns boolean
if (activeUnits < TRIGGER_SIZE) then
call TriggerRegisterUnitEvent(trigger, GetUnitById(whichUnit), TRIGGER_EVENT)
set activeUnits = activeUnits + 1
return true
endif
return false
endmethod
endif
private method unregisterUnit takes nothing returns nothing
set inactiveUnits = inactiveUnits + 1
set activeUnits = activeUnits - 1
endmethod
private method createTrigger takes nothing returns nothing
set trigger = CreateTrigger()
call TriggerAddCondition(trigger, condition)
endmethod
private method remakeTrigger takes nothing returns nothing
call DestroyTrigger(trigger)
call createTrigger()
endmethod
private method rebuildTrigger takes nothing returns nothing
local thistype current = first
call remakeTrigger()
/*
* Iterate over all units registered to the trigger and reregister them
*/
set current.prev.next = 0
loop
exitwhen 0 == current
call TriggerRegisterUnitEvent(trigger, GetUnitById(current), TRIGGER_EVENT)
set current = current.next
endloop
set first.prev.next = current
endmethod
private method remake takes nothing returns nothing
if (inactiveUnits == TRIGGER_SIZE) then
set inactiveUnits = 0
call rebuildTrigger()
endif
endmethod
private method addToList takes thistype whichUnit returns nothing
set whichUnit.parent = this
if (0 == first) then
set first = whichUnit
set whichUnit.next = whichUnit
set whichUnit.prev = whichUnit
else
set this = first
set whichUnit.prev = prev
set whichUnit.next = this
set prev.next = whichUnit
set prev = whichUnit
endif
endmethod
method add takes thistype whichUnit returns boolean
if (0 == this) then
return false
endif
if (registerUnit(whichUnit)) then
call addToList(whichUnit)
return true
endif
return false
endmethod
private method removeFromList takes thistype whichUnit returns nothing
set whichUnit.parent = 0
set whichUnit.prev.next = whichUnit.next
set whichUnit.next.prev = whichUnit.prev
if (first == whichUnit) then
set first = whichUnit.next
if (first == whichUnit) then
set first = 0
endif
endif
endmethod
static method remove takes thistype whichUnit returns nothing
local thistype this = whichUnit.parent
call removeFromList(whichUnit)
call unregisterUnit()
call remake()
endmethod
private static method allocate takes nothing returns thistype
set instanceCount = instanceCount + 1
return instanceCount
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
call createTrigger()
return this
endmethod
endstruct
private struct TriggerHeapInner extends array
readonly static integer size = 0
readonly thistype node
readonly thistype heap
public method bubbleUp takes nothing returns nothing
local integer activeUnits = IAmBotTrigger(this).activeUnits
local thistype heapPosition = heap
local thistype parent
/*
* Bubble node up
*/
loop
set parent = heapPosition/2
if (integer(parent) != 0 and activeUnits < IAmBotTrigger(parent.node).activeUnits) then
set heapPosition.node = parent.node
set heapPosition.node.heap = heapPosition
else
exitwhen true
endif
set heapPosition = parent
endloop
/*
* Update pointers
*/
set heapPosition.node = this
set heap = heapPosition
endmethod
public method bubbleDown takes nothing returns nothing
local integer activeUnits = IAmBotTrigger(this).activeUnits
local thistype heapPosition = heap
local thistype left
local thistype right
/*
* Bubble node down
*/
loop
set left = heapPosition*2
set right = left + 1
if (IAmBotTrigger(left.node).activeUnits < activeUnits and IAmBotTrigger(left.node).activeUnits < IAmBotTrigger(right.node).activeUnits) then
/*
* Go left
*/
set heapPosition.node = left.node
set heapPosition.node.heap = heapPosition
set heapPosition = left
elseif (IAmBotTrigger(right.node).activeUnits < activeUnits) then
/*
* Go right
*/
set heapPosition.node = right.node
set heapPosition.node.heap = heapPosition
set heapPosition = right
else
exitwhen true
endif
endloop
/*
* Update pointers
*/
set heapPosition.node = this
set heap = heapPosition
endmethod
static method insert takes thistype this returns nothing
/*
* Increase heap size
*/
set size = size + 1
/*
* Store node in last heap position
*/
set thistype(size).node = this
set heap = size
/*
* Bubble node into correct position
*/
call bubbleUp()
endmethod
endstruct
private struct TriggerHeap extends array
static if LIBRARY_UnitDex then
static method add takes UnitDex whichUnit returns nothing
local IAmBotTrigger trig = TriggerHeapInner(1).node
if (not trig.add(whichUnit)) then
set trig = IAmBotTrigger.create()
call trig .add(whichUnit)
call TriggerHeapInner.insert(trig)
else
call TriggerHeapInner(trig).bubbleDown()
endif
endmethod
static method remove takes UnitDex whichUnit returns nothing
local IAmBotTrigger trig = IAmBotTrigger(whichUnit).parent
call IAmBotTrigger.remove(whichUnit)
call TriggerHeapInner(trig).bubbleUp()
endmethod
else
static method add takes UnitIndex whichUnit returns nothing
local IAmBotTrigger trig = TriggerHeapInner(1).node
if (not trig.add(whichUnit)) then
set trig = IAmBotTrigger.create()
call trig .add(whichUnit)
call TriggerHeapInner.insert(trig)
else
call TriggerHeapInner(trig).bubbleDown()
endif
endmethod
static method remove takes UnitIndex whichUnit returns nothing
local IAmBotTrigger trig = IAmBotTrigger(whichUnit).parent
call IAmBotTrigger.remove(whichUnit)
call TriggerHeapInner(trig).bubbleUp()
endmethod
endif
endstruct
private module IAmBotTriggerRefreshInitModule
private static method onInit takes nothing returns nothing
call init(function IAmBot.onAquireEvent)
endmethod
endmodule
private struct TriggerRefreshInit extends array
private static method onIndex takes nothing returns boolean
if onFilter(GetIndexedUnit()) then
call TriggerHeap.add(GetIndexedUnitId())
set IAmBot.has[GetIndexedUnitId()] = true
call IAmBot.compatibility(GetIndexedUnitId())
endif
return false
endmethod
private static method onDeindex takes nothing returns boolean
if IAmBot.has[GetIndexedUnitId()] then
call TriggerHeap.remove(GetIndexedUnitId())
set IAmBot.has[GetIndexedUnitId()] = false
endif
return false
endmethod
private static method onChangeOwnerEvent takes nothing returns boolean
local integer index = GetUnitUserData(GetChangingUnit())
debug call BJDebugMsg("Change owner event is runnin")
if (0 != index) then
if onFilter(GetUnitById(index)) then
if not IAmBot.has[index] then
call TriggerHeap.add(index)
set IAmBot.has[index] = true
call IAmBot.compatibility(index)
endif
elseif IAmBot.has[index] then
set IAmBot.has[index] = false
call TriggerHeap.remove(index)
call IAmBot.deactivate(index)
endif
endif
return false
endmethod
private static method init takes code c returns nothing
set condition = Condition(c)
static if LIBRARY_UnitDex then
call RegisterUnitIndexEvent(Condition(function thistype.onIndex), 0)
call RegisterUnitIndexEvent(Condition(function thistype.onDeindex), 1)
else
call RegisterUnitIndexEvent(Condition(function thistype.onIndex), UnitIndexer.INDEX)
call RegisterUnitIndexEvent(Condition(function thistype.onDeindex), UnitIndexer.DEINDEX)
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_CHANGE_OWNER, function thistype.onChangeOwnerEvent)
endmethod
implement IAmBotTriggerRefreshInitModule
endstruct
endscope
endlibrary
JASS:
library IAmBotExIndicator uses IAmBot/* v1.0
*************************************************************************************
*
* Indicator Pluggin for IAmBot.
* Assigns a unique effect to an active IAmBot unit.
*
*************************************************************************************
*
* SETTINGS
*/
globals
constant string BOT_UNIQUE_INDICATOR = "UI\\Feedback\\SelectionCircleEnemy\\SelectionCircleEnemy.mdx"
constant string BOT_ATTACH_POINT = "origin"
endglobals
/*
*************************************************************************************
*
* API
* - no additional API, everything works automatic.
*
*************************************************************************************/
//! textmacro I_AM_BOT_EXPANSION_INDICATOR_CODE
scope IAmBotExIndicator
module IAmBotExIndicatorAPI
readonly effect sfx
method display takes boolean flag returns nothing
if (sfx != null) then
call DestroyEffect(sfx)
set sfx = null
endif
if flag then
set sfx = AddSpecialEffectTarget(BOT_UNIQUE_INDICATOR, who, BOT_ATTACH_POINT)
endif
endmethod
endmodule
module IAmBotExIndicatorAdd
call this.display(true)
endmodule
module IAmBotExIndicatorClear
call this.display(false)
endmodule
endscope
//! endtextmacro
endlibrary
JASS:
library IAmBotExUnitState uses IAmBot /* v1.0
*************************************************************************************
*
* Unit State Pluggin for IAmBot.
* Adds additional API to the IAmBot struct.
*
*************************************************************************************
*
* SETTINGS
*/
globals
constant real BOT_MAXIMUM_HELP_RANGE = 700.
constant real BOT_ASK_FOR_HELP_LIFE = 0.35
constant real BOT_CRITICAL_LIFE = 0.15
constant real BOT_RETREAT_DISTANCE = 230.
endglobals
/*
*************************************************************************************
*
* API
*
* readonly real life
* -> how much life the unit has
* readonly real maxLife
* -> how much max life the unit has
* readonly real lifeP
* -> current life percent
* readonly real speed
* -> current movementspeed
* readonly Table HEALER
* -> Additional Table for custom heal events.
*
* Adds the following features by default:
*
* 1. IAmBot units retreat for BOT_RETREAT_DISTANCE if lifeP is below critical life.
*
* 2. IAmBot units may request help:
* -> units with less lifeP than THREAT_ASK_FOR_HELP_LIFE will request help from allied units within THREAT_MAXIMUM_HELP_RANGE
* -> Only units types registered with the HEALER Table can offer help.
* -> Does work group-across, based on drift compatibility index.
*
* (Possible future extra: Units may request passive aggressive help like a tactical storbolt)
* -> Depends much on how IAmBot priority is going to work out.
*
*************************************************************************************/
//! textmacro I_AM_BOT_EXPANSION_UNITSTATE_CODE
scope IAmBotExUnitState
module IAmBotExUnitStateAPI
readonly real life
readonly real maxLife
readonly real lifeP
readonly real speed
readonly static Table HEALER = 0
readonly static BotList healstack = 0
readonly static integer array healR
method requestHeal takes nothing returns boolean
local BotList node = healstack.first
local IAmBot temp
loop
exitwhen 0 == node
set temp = node.index
if (0 > temp.cd) and (IsUnitInRange(who, temp.who, BOT_MAXIMUM_HELP_RANGE)) and (comp == temp.comp) then
if (UnitAlive(temp.who)) then
set IAmBot.current = temp
set IAmBot.secondary = this
return TriggerEvaluate(IAmBot.HEALER.trigger[temp.id])
endif
endif
set node = node.next
endloop
return false
endmethod
method registerHealer takes nothing returns nothing
local BotList node = healstack.enqueue()
call node.link(this)
set healR[this] = node
endmethod
method releaseHealer takes nothing returns nothing
local BotList node = healR[this]
call node.remove()
set healR[this] = 0
endmethod
endmodule
module IAmBotExUnitStateInit
set HEALER = Table.create()
set healstack = BotList.create()
endmodule
module IAmBotExUnitStateUpdate
set life = GetWidgetLife(who)
set maxLife = GetUnitState(who, UNIT_STATE_MAX_LIFE)
set lifeP = life/maxLife
set speed = GetUnitMoveSpeed(who)
endmodule
module IAmBotExUnitStateSetup
if HEALER.handle.has(id) then
call this.registerHealer()
endif
endmodule
module IAmBotExUnitStateResponsePrimary
if (lifeP < BOT_ASK_FOR_HELP_LIFE) then
if this.requestHeal() then
implement IAmBotExUnitStateUpdate
endif
endif
endmodule
module IAmBotExUnitStateResponseSecondary
debug call BJDebugMsg(R2S(lifeP)+ "/" + R2S(BOT_CRITICAL_LIFE))
if (lifeP < BOT_CRITICAL_LIFE) then
set paused = 1
call move(whoX - BOT_RETREAT_DISTANCE*Cos(angle), whoY - BOT_RETREAT_DISTANCE*Sin(angle))
endif
endmodule
module IAmBotExUnitStateClear
if (0 != healR[this]) then
call this.releaseHealer()
endif
endmodule
endscope
//! endtextmacro
endlibrary
JASS:
library IAmBotExEange uses IAmBot /* v1.0
*************************************************************************************
*
* Engage Pluggin for IAmBot.
* Adds additional API to the IAmBot struct.
*
*************************************************************************************
*
* SETTINGS
*/
globals
constant real BOT_DEFAULT_RESPONSE_RANGE = 950.
endglobals
/*
*************************************************************************************
*
* API
*
* readonly static Table ENGAGE
* --> fire custom engage events from this Table.
*
* Adds the following features by default:
*
* 1. IAmBot units now have unique engage behaviour based on their UnitTypeId.
*
*************************************************************************************/
//! textmacro I_AM_BOT_EXPANSION_ENGAGE_CODE
scope IAmBotExEnage
module IAmBotExEnageAPI
readonly static Table ENGAGE = 0
endmodule
module IAmBotExEnageInit
set ENGAGE = Table.create()
endmodule
module IAmBotExEngageResponse
if (0 == paused) and (0 > cd) and ENGAGE.handle.has(id) and (hasAim) then
if IsUnitInRange(who, aim, BOT_DEFAULT_RESPONSE_RANGE) then
set IAmBot.current = this
call TriggerEvaluate(ENGAGE.trigger[id])
set going = false
else
set aim = null
endif
endif
endmodule
endscope
//! endtextmacro
endlibrary
JASS:
scope Example initializer init
private function Paladin takes nothing returns boolean
local IAmBot healer = IAmBot.current
local IAmBot needsHelp = IAmBot.secondary
//i.e. Don't consider healing units with less than 100 max life.
if (needsHelp.maxLife > 100) and IssueTargetOrderById(healer.who, 852092, needsHelp.who) then
set healer.cooldown = 1//Add a spell cooldown of 1 clock timeouts
set healer.pause = 1//And a total pause of 1
endif
return false
endfunction
private function init takes nothing returns nothing
call RegisterBotEvent(IAmBot.HEALER, 'Hpal', Condition(function Paladin))
endfunction
endscope
Attachments
Last edited: