HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Custom Autocast System - Customizable AI

01-01-2008, 04:10 PM#1
rain9441
Well after some requests I give you the first public version of my Autocast system I've created. It is in rough shape, there is basically no documentation, and it hasn't been thoroughly tested (Although, it has been tested quite a bit). USE AT YOUR OWN RISK. The main purpose of this being released as-is is to give you a sense of what I've been working on, not a full fledged working super system. The second purpose of this being released as-is is for anyone to give feedback or ideas to make this system more than what it could be.

Two demos are included, one to give a sense of how melee abilities would be configured and the other to show off the potential for an RPG-Boss like enemy...

What is the "Custom Autocast System"?
==============================
This is a system designed to be self-sustaining and efficient in hopes to grant you (the maker of a map) the ability to make units use their abilities. Through a fairly simple step by step procedure, the system tries to match conditions and meet thresholds in order to figure out the only two things that matter; Should I cast? What should I cast at?

How does it work?
==============================
Here is the not so simple part. I'd have to give you a fairly large flowchart for you to really understand exactly what is going on, but I can sum it up.

Pregame Setup: Provide the system with specifics on the ability, such as mana cost, order strings, buff codes, targeting types, targets allowed, range, area of effect, etc, etc, etc.

1) Verify all precasting conditions are met: Cooldown is up, mana is sufficient, and any other custom conditions you want are satisfied.
2) The autocasting unit searches through the nearby units and filters out all the units that should not be considered at all for being targets. EG: Don't heal enemies, don't cast abolish magic at units with no buffs that aren't summoned.
3) Apply a threshold function to each unit passed. The threshold allows you to limit casting of abilities to only certain things, or only if certain conditions are met. If the unit doesnt meet the threshold, it is not considered to be casted at. Example: Number of enemy units in vicinity > 5 for thunderclap, unit level > 2 for inner fire, Target Life is less than 50% for heal, target life is less than maximum life - 200 for a 200 pt heal. Anything is possible.
4) Apply a threat function to each unit passed, can be anything you want it to be. Example: Inverse life percentage for 'healing', unit level for fire bolt, number of enemy units in the vicinity compared to number of friendly units in the vicinity for blizzard. Again, anything is possible.
5) The highest threat target is selected and the ability is used.
6) Repeat.

What doesn't it do?
==============================
Well, it doesn't work well with neutral hostile. It doesn't scale very well when VERY large numbers of units are clustered together (100+), especially if you are using AOE threat checks (i.e. check all units in the vicinity of each unit in the vicinity, find the best target for an AOE). Although it has backoff features to allow you to make it be less greedy as it uses more resources, you end up trading efficient autocasting for performance. It doesn't magically work when you just put the scripts in the map, ALL the abilities you want autocasted will need to be registered, with almost all of the variables associated with those abilities (for this I use SLK files).

Requirements
==============================
JassHelper - Yes this is made possible by vJASS
Grimoire
TimeLib and MagicTimers are required, and included in the attachment.

Main features
==============================
Implementation Features
  • A lot of variables that can be tweaked to affect performance of the system as a whole, some of which can be changed mid-game.
  • Custom 'backoff' time functions to tell the system not to check conditions for an ability for some time after it has been successfully or unsuccessfully casted. A variable is passed to the function to give a sense of how many resources have been used (number of units checked in the process).
  • Easy to plug in to ANY system out there that controls unit custom data. It defaults to Get/SetUnitUserData right now but you have 3 functions in the interface you can change. ACST_GetUnitUserData takes a unit returns an integer, ACST_SetUnitUserData takes a unit and an integer and returns a TAG. When the Autocast Information is destroyed from the map, ACST_OnUnitUserDataDestroyed is called with the unit and the TAG (for dereferencing). You can put regular Get/SetUnitUserData calls in the code, gamecache calls, or whatever system you want, and it should work.
  • Orders can be reapllied after a unit autocasts an ability, such as "attack", "stop", "holdposition". After the unit finishes casting the ability it will be reordered to do what it was doing last.
  • Orders can be used to make the unit ignore autocasting. For instance if you only want units to autocast when "attack" or "smart", but not "move", you can. Also the stun order is included.
  • Enable or Disable autocast entirely for certain players.

Autocasting Features
  • Each ability can be set to be 'animation canceled' when cast. Animation canceling just means the previous order will be reissued the moment after the ability starts its effects. This is also called "orb walking", it allows units to move around quickly after a spell has been cast. Units with no spell animation time will seem to "glide around" while casting spells (Obsidian Statue)
  • Each ability can be assigned a buff, so that any unit with that buff is not checked when doing cast conditions. This is in addition to thresholds, threats, and filters.
  • Works with toggle abilities like mana shield and defend! When condiitions are met, activate, when conditions are no longer met, deactivate! Very cool!
  • Multiple dispel types for dispelling abilities: favored (meaning all bad buffs are dispelled from allies and good buffs from enemies) and all (removes friendly buffs as well).
  • Arbitrary number of levels per ability, each with their own range, aoe, cooldown, manacost, and threshold values.
  • Each unit can specify the conditions of each spell being cast. You can have 2 priests. One priest heals only when mana is > 80% and the other heals only when mana > 20%. You can stop a single ability from a single unit from being autocasted, in the middle of the game, then reactivate it. You can change the custom casting conditions (a function interface) in game. Each ability on each unit can have its own filter, threshold, and threat function (One priest heals only heroes, one priest heals everyone else).
  • Autocastable abilities have priorities. If a unit has say 8 abilities being autocasted you can specify which order they are checked. Lowest cooldown last usually works best.
  • Autocast preserved when a unit is morphed (not thoroughly tested).
  • Channeled spells are not interrupted.
  • Uses abilities normally not seen in the command card (yes you can have 15 autocasted abilities if you really wanted to)
  • Autocast not limited to just abilities. You can actually use autocast to issue attack, move, or any other order based on conditions, thresholds, and threats. Units with slow poison for instance can be setup to attackonce units that do not have the slow poison debuff. Attack can be autocasted to simulate threat for controlling AI as well.

Debugging Features
  • Visual debugging aid accessible ingame, type "-debug unit visual" and selected units' autocast information is then displayed in a multiboard for you to view. Very handy in figuring out problems with abilities that may be incorrectly configured
  • Warnings can be displayed to give you information on if you are doing things you shouldn't be doing
  • Traces for autocast timer and filters are available which tell you EXACTLY what is going on, where conditions fail, etc.
  • ONLY active when the debug mode is set in Grimoire
  • Reduces performance greatly when debug mode is activated


Known Issues
==============================
Devoured units are not treated well in autocast, since they are alive, visible, and in the vicinity but are not able to be targeted... The Human Priest's heal ability is a endless channel ability, so it kind of sucks for autocasting (but it still works). Sometimes when a unit is ressurected after it dies the last issued order of a unit was to attack it, so it may freak out (rare). Morphing units sometimes deactivate or reactivate autocast when they morph.

Autocast interferes with orders, so ordering groups to do things will unfortunately be greatly affected when units start to receive autocast orders!

Final Remarks
==============================
Autocast isn't the solution for everything. One main aspect of autocast is that it is designed to be able to be controlled. If someone is motivated enough, they could make another system on top of autocast that controls it, an "AI" for example... This system could modify certain features such as making heroes a high threat to all units, or make certain units treat heroes as a high threat, or anything similar. The system could also modify required mana/health conditions for abilities if you are trying to make the AI more efficient. E.G. If you are making an AI for an AoS type map you could have a system that makes units be "mana efficient" and only cast spells when their mana is high, then disable the mana requirement to unload all of there abilities at once.

Certain abilities don't work well with Autocast alone, because they have EXTREMELY tight conditions to be met. One prime example is Big Bad Voodoo. Obviously these abilities almost need another layer of AI to be used effectively... Other abilities work great with autocast... Healing abilities, Nukes, Stuns, Debuffs, Buffs, etc.

JASS script of sample Interface configuration file:
Collapse JASS:
library AutocastInterface

    globals
        // Max Ability Level, defines the maximum level of any ability to which
        // useful information is stored.  If an ability has 200 levels but shares
        // the same mana cost, cooldown, range, etc, than you really only need
        // one set of data, so MaxAbilityLevel does not need to be set to 200

        // Total autocastable abilities = 8191 / ACST_MaxAbilityLevel
        // If set to 5, you may have 1638 autocastable abilities
        // If set to 10, you may have 819 autocastable abilities
        // This limitation should be beyond reasonable even if set to 15-20
        constant integer    ACST_MaxAbilityLevel                    = 3

        // The maximum spells per unit must be set to the number of spells any 
        // unit may have in the game, a normal size would probably be 4, since heroes
        // generally have 4 abilities, but we'll set it to 8 because we have space.
        // The total number of units with the autocast ability cannot exceed
        // 8191 / ACST_MaxSpellsPerUnit
        // So even with a maximum of 8, we can support 1023 units autocasting...
        // If you have more than 1000 units autocasting your map will probably
        // be at a screeching halt already.
        constant integer    ACST_MaxSpellsPerUnit                   = 8
        constant real       ACST_MaxAutocastRange                   = 1200.0
        constant real       ACST_DefaultChannelTime                 = 60.0
        constant boolean    ACST_InitiallyEnabled                   = TRUE
        constant boolean    ACST_CompleteInitialScan                = TRUE
        constant real       ACST_InsufficientManaBackoffTime        = 0.50
        constant real       ACST_FailedCastConditionsBackoffTime    = 0.50
        constant boolean    ACST_UseUnitInRangeEvents               = FALSE
        constant real       ACST_EnumFrequency                      = 1.00
    endglobals

    function ACST_SuccessfulCastBackoffTime takes integer iCount returns real
        return GetRandomReal(0.80,1.20) * Pow(I2R(iCount), 0.5) / 40.0
    endfunction

    function ACST_FailedCastBackoffTime takes integer iCount returns real
        return GetRandomReal(0.80,1.20) * Pow(I2R(iCount), 0.9) / 10.0
    endfunction

    function ACST_GetUnitUserData takes unit u returns ACST_UnitInfo
        return GetUnitUserData(u)
    endfunction

    function ACST_SetUnitUserData takes unit u, integer i returns integer
        call SetUnitUserData(u, i)
        return 0
    endfunction

    function ACST_OnUnitUserDataDestroyed takes unit u, integer iDestroyTag returns nothing
    endfunction

    function ACST_UnitHasAutocast takes unit u returns boolean
        return GetUnitAbilityLevel(u, ACST_AutocastToggleAbilityId) > 0
    endfunction

    function ACST_InitializeUnitDefaults takes nothing returns nothing
        local ACST_UnitDefaults     ud
        local ACST_AbilityDefaults  ad


        set ud = ACST_UnitDefaults.create('hdhw')
        set ad = ud.GetAbilityDefaults('Aclf')
        set ad = ud.GetAbilityDefaults('Amls')

        set ud = ACST_UnitDefaults.create('hfoo')
        set ad = ud.GetAbilityDefaults('Adef')
    endfunction

    function ACST_InitializeAbilities takes nothing returns nothing
        set ACST_RepeatedOrderIds[0]    = OrderId("attack")
        set ACST_RepeatedOrderIds[1]    = OrderId("stop")
        set ACST_RepeatedOrderIds[2]    = OrderId("holdposition")
        set ACST_RepeatedOrderIds[3]    = 0

        set ACST_NotRepeatedOrderIds[0] = OrderId("move")
        set ACST_NotRepeatedOrderIds[1] = OrderId("smart")
        set ACST_NotRepeatedOrderIds[2] = 0

        set ACST_IgnoredOrderIds[0]     = 851973 // stunned
        set ACST_IgnoredOrderIds[1]     = 0

        set ACST_EnabledForPlayer[0]    = TRUE
    endfunction
endlibrary

library AutocastConfiguration initializer AutocastConfiguration_Initialize requires Autocast

    globals
        constant integer    ACST_GlobalAutocastTechId       = 0
        constant integer    ACST_AutocastToggleAbilityId    = 'A002'
        constant integer    ACST_AutocastToggleOnOrderId    = 852543
        constant integer    ACST_AutocastToggleOffOrderId   = 852544

        trigger             ACST_TrgEventOrderToggleAutocast
        trigger             ACST_TrgEventSpellToggleAutocast 
    endglobals

    // Autocast On/Off features
    function ACST_EventUnitToggleAutocastEffect takes nothing returns nothing
        local unit          uCaster     = GetTriggerUnit()
        local integer       iAbilityId  = GetSpellAbilityId()

        if ( iAbilityId == ACST_AutocastToggleAbilityId ) then
            call ACST_ConfigureAutocastForUnit(uCaster)
        endif

        set uCaster = null
    endfunction

    function ACST_EventUnitToggleAutocastOrder takes nothing returns nothing
        local unit          uCaster     = GetOrderedUnit()
        local integer       iOrderId    = GetIssuedOrderId()

        if ( iOrderId == ACST_AutocastToggleOnOrderId ) then
            call ACST_EnableAutocastForUnit(uCaster)
        elseif ( iOrderId == ACST_AutocastToggleOffOrderId ) then
            call ACST_DisableAutocastForUnit(uCaster)
        endif

        set uCaster = null
    endfunction

    function AutocastConfiguration_Initialize takes nothing returns nothing
        set ACST_TrgEventOrderToggleAutocast = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ACST_TrgEventOrderToggleAutocast, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        call TriggerAddAction(ACST_TrgEventOrderToggleAutocast, function ACST_EventUnitToggleAutocastOrder)

        set ACST_TrgEventSpellToggleAutocast = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ACST_TrgEventSpellToggleAutocast, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction(ACST_TrgEventSpellToggleAutocast, function ACST_EventUnitToggleAutocastEffect)
    endfunction
endlibrary

JASS script of System:

This is not posted because it is too large and makes wc3campaigns error out:
Fatal error: Maximum execution time of 30 seconds exceeded in /home/wc3c/wc3/public_html/includes/plugins/jasstag.php on line 185
See attachment...
Attached Files
File type: rarAutocastAlphaDemo.rar (169.6 KB)
01-01-2008, 04:12 PM#2
rain9441
JASS Code of system (couldn't fit in original post):

Collapse JASS:
library Autocast initializer Autocast_Initialize requires Containers, ETrigger, AutocastInterface, AutocastFilters, AutocastUnitDefaults, AutocastDebug

    function interface ACST_CastCondition takes ACST_UnitInfo ui, ACST_CastInfo ci, ACST_AbilityInfo ai returns boolean

    globals
        constant integer    ACST_ORDERTYPE_UNKNOWN      = 0
        constant integer    ACST_ORDERTYPE_TARGET       = 1
        constant integer    ACST_ORDERTYPE_POINT        = 2
        constant integer    ACST_ORDERTYPE_IMMEDIATE    = 3
        constant integer    ACST_ORDERTYPE_TOGGLE       = 4

        constant integer    ACST_DISPEL_UNKNOWN         = 0
        constant integer    ACST_DISPEL_FAVORED         = 1
        constant integer    ACST_DISPEL_ALL             = 2

        trigger             ACST_TrgUnitEntersWorld
        triggeraction       ACST_TraUnitEntersWorld
        trigger             ACST_TrgUnitStartsChannel
        triggeraction       ACST_TraUnitStartsChannel
        trigger             ACST_TrgUnitEndsCast
        triggeraction       ACST_TraUnitEndsCast
        trigger             ACST_TrgUnitStartsEffect
        triggeraction       ACST_TraUnitStartsEffect
        trigger             ACST_TrgUnitOrderedUnit
        triggeraction       ACST_TraUnitOrderedUnit
        trigger             ACST_TrgUnitOrderedPoint
        triggeraction       ACST_TraUnitOrderedPoint
        trigger             ACST_TrgUnitOrdered
        triggeraction       ACST_TraUnitOrdered

        player              ACST_CleanupPlayer
        unit                ACST_CleanupCaster
        unit                ACST_CleanupEnemy
        real                ACST_CleanupRange
        group               ACST_CleanupGroup
        boolean             ACST_CleanupFriends
        boolean             ACST_CleanupEnemies

        group               ACST_MasterEnumGroup        = CreateGroup()

        integer array       ACST_NotRepeatedOrderIds
        integer array       ACST_RepeatedOrderIds
        integer array       ACST_IgnoredOrderIds
        boolean array       ACST_EnabledForPlayer
    endglobals

    struct ACST_AbilityInfo
        integer                 iAbilityId              = 0
        integer                 iPriority               = 0
        string                  strOrder                = ""
        string                  strUnorder              = ""
        integer                 iOrderId                = 0 // for those without string orders...
        integer                 iUnorderId              = 0
        integer                 iOrderType              = 0
        integer                 iBuffCode               = 0
        integer                 iIgnoreAbility          = 0
        integer                 iIgnoreUnit             = 0
        //real                    rAreaOfEffect           = 0
        boolean                 bAnimationCancel        = FALSE
        boolean                 bPhysicalAttack         = FALSE

        //ACST_FilterAction       filterAction            = 0 // function that takes info and returns bCasted
        //integer                 filterFunc              = 0 // for ForGroup, filters POSSIBLE candidates
        ACST_FilterAction       defFilterAction         = ACST_FilterAction.ACST_DefaultFilterAction
        ACST_Filter             defFilterFunc           = ACST_Filter.ACST_GenericFilter
        ACST_Filter             defFilterAoeFunc        = ACST_Filter.ACST_GenericFilter

        // Optionals
        ACST_ThresholdFunction  defThresholdFunc        = 0 // Limits candidates from filterFunc 
        ACST_ThresholdFunction  defThresholdAoeFunc     = 0
        ACST_ThreatFunction     defThreatFunc           = 0 // Picks the best candidate from all filtered

        // Various filter stuff here per spell, filters targeting constraints
        boolean                 bSelfTarget             = FALSE
        boolean                 bAir                    = FALSE
        boolean                 bGround                 = FALSE
        boolean                 bEnemies                = FALSE
        boolean                 bFriends                = FALSE
        boolean                 bPlayerOwned            = FALSE
        boolean                 bDeadUnits              = FALSE
        boolean                 bLivingUnits            = FALSE
        boolean                 bBuffCheck              = FALSE

        boolean                 bOnlyRangedAttackers    = FALSE
        boolean                 bOnlyStructures         = FALSE
        boolean                 bOnlySummoned           = FALSE
        boolean                 bOnlyUndead             = FALSE
        boolean                 bOnlyTauren             = FALSE
        boolean                 bOnlyMechanical         = FALSE
        boolean                 bOnlyInvisible          = FALSE
        boolean                 bNotVisible             = FALSE
        boolean                 bInvisible              = FALSE
        boolean                 bHeroes                 = FALSE
        boolean                 bMechanical             = FALSE
        boolean                 bMagicImmune            = FALSE
        boolean                 bStructures             = FALSE
        boolean                 bWards                  = FALSE

        // Dispel mode, favored or all
        integer                 iDispel                 = 0

        real array              rThreshold[ACST_MaxAbilityLevel]
        real array              rAoeThreshold[ACST_MaxAbilityLevel]
        real array              rRange[ACST_MaxAbilityLevel]
        real array              rAreaOfEffect[ACST_MaxAbilityLevel]
        real array              rCooldown[ACST_MaxAbilityLevel]
        integer array           iManaCost[ACST_MaxAbilityLevel]

        // SLK variables
        
        // Non string variables are loaded into all levels
        real                    rSLKThreshold
        real                    rSLKAoeThreshold
        real                    rSLKRange
        real                    rSLKAreaOfEffect
        real                    rSLKCooldown
        integer                 iSLKManaCost            
        // Afterwords, string variables are comma delimited to apply to all levels
        // E.g. "60,75,90" would be 60 for level 1, 75 for level 2, and 90 for level 3
        string                  strSLKThreshold         = ""
        string                  strSLKAoeThreshold      = ""
        string                  strSLKRange             = ""
        string                  strSLKAreaOfEffect      = ""
        string                  strSLKCooldown          = ""
        string                  strSLKManaCost          = ""

        string                  strSLKFilter            = ""

        static IntHash                  IH

        static ACST_AbilityInfo array   MasterList
        static integer                  MasterListCount     = 0

        method getAoeThreshold takes integer i returns real
            if ( i < 1 ) then
                set i = 1
            elseif ( i > this.rAoeThreshold.size ) then
                set i = this.rAoeThreshold.size
            endif
            return this.rAoeThreshold[i-1]
        endmethod
        method setAoeThreshold takes real val returns nothing
            local integer i = 0
            loop
                exitwhen i >= this.rAoeThreshold.size
                set this.rAoeThreshold[i] = val
                set i = i + 1
            endloop
        endmethod

        method getThreshold takes integer i returns real
            if ( i < 1 ) then
                set i = 1
            elseif ( i > this.rThreshold.size ) then
                set i = this.rThreshold.size
            endif
            return this.rThreshold[i-1]
        endmethod
        method setThreshold takes real val returns nothing
            local integer i = 0
            loop
                exitwhen i >= this.rThreshold.size
                set this.rThreshold[i] = val
                set i = i + 1
            endloop
        endmethod

        method getAreaOfEffect takes integer i returns real
            if ( i < 1 ) then
                set i = 1
            elseif ( i > this.rAreaOfEffect.size ) then
                set i = this.rAreaOfEffect.size
            endif
            return this.rAreaOfEffect[i-1]
        endmethod
        method setAreaOfEffect takes real val returns nothing
            local integer i = 0
            loop
                exitwhen i >= this.rAreaOfEffect.size
                set this.rAreaOfEffect[i] = val
                set i = i + 1
            endloop
        endmethod

        method getRange takes integer i returns real
            if ( i < 1 ) then
                set i = 1
            elseif ( i > this.rRange.size ) then
                set i = this.rRange.size
            endif
            return this.rRange[i-1]
        endmethod
        method setRange takes real val returns nothing
            local integer i = 0
            loop
                exitwhen i >= this.rRange.size
                set this.rRange[i] = val
                set i = i + 1
            endloop
        endmethod

        method getCooldown takes integer i returns real
            if ( i < 1 ) then
                set i = 1
            elseif ( i > this.rCooldown.size ) then
                set i = this.rCooldown.size
            endif
            return this.rCooldown[i-1]
        endmethod
        method setCooldown takes real val returns nothing
            local integer i = 0
            loop
                exitwhen i >= this.rCooldown.size
                set this.rCooldown[i] = val
                set i = i + 1
            endloop
        endmethod

        method getManaCost takes integer i returns integer
            if ( i < 1 ) then
                set i = 1
            elseif ( i > this.iManaCost.size ) then
                set i = this.iManaCost.size
            endif
            return this.iManaCost[i-1]
        endmethod
        method setManaCost takes integer val returns nothing
            local integer i = 0
            loop
                exitwhen i >= this.iManaCost.size
                set this.iManaCost[i] = val
                set i = i + 1
            endloop
        endmethod

        static method GetNextSLKVariable takes string str returns string
            local integer i = 0
             
            loop
                exitwhen i >= StringLength(str)

                if ( SubString(str, i, i+1) == "," ) then
                    return SubString(str, i+1, 9999)
                endif
                set i = i + 1
            endloop
            return ""
        endmethod

        static method FinalizeSLK takes nothing returns nothing
            local ACST_AbilityInfo  ai
            local integer           iAbility    = 0
            local string            strTemp
            local real              rTemp
            local integer           iTemp
            local integer           iLevel

            loop
                exitwhen iAbility == .MasterListCount

                set ai = .MasterList[iAbility]
                
                if ( ai.rSLKAoeThreshold != 0.0 ) then
                    call ai.setAoeThreshold(ai.rSLKAoeThreshold)
                endif
                if ( ai.rSLKThreshold != 0.0 ) then
                    call ai.setThreshold(ai.rSLKThreshold)
                endif
                if ( ai.rSLKAreaOfEffect > 0.0 ) then
                    call ai.setAreaOfEffect(ai.rSLKAreaOfEffect)
                endif
                if ( ai.rSLKRange > 0.0 ) then
                    call ai.setRange(ai.rSLKRange)
                endif
                if ( ai.rSLKCooldown > 0.0 ) then
                    call ai.setCooldown(ai.rSLKCooldown)
                endif
                if ( ai.iSLKManaCost > 0 ) then
                    call ai.setManaCost(ai.iSLKManaCost)
                endif
                if ( ai.strSLKFilter != "" ) then
                    //set ai.filter = ACST_Filter.getFromKey(ai.strSLKFilter)
                endif

                if ( ai.strSLKAoeThreshold != "" ) then
                    set iLevel = 0
                    set strTemp = ai.strSLKAoeThreshold
                    loop
                        exitwhen strTemp == ""
                        exitwhen iLevel >= ai.rAoeThreshold.size
                        set rTemp = S2R(strTemp)
                        set ai.rAoeThreshold[iLevel] = rTemp
                        set iLevel = iLevel + 1
                        set strTemp = .GetNextSLKVariable(strTemp)
                    endloop
                    loop
                        exitwhen iLevel >= ai.rAoeThreshold.size
                        set ai.rAoeThreshold[iLevel] = rTemp
                        set iLevel = iLevel + 1
                    endloop
                endif
                
                if ( ai.strSLKThreshold != "" ) then
                    set iLevel = 0
                    set strTemp = ai.strSLKThreshold
                    loop
                        exitwhen strTemp == ""
                        exitwhen iLevel >= ai.rThreshold.size
                        set rTemp = S2R(strTemp)
                        set ai.rThreshold[iLevel] = rTemp
                        set iLevel = iLevel + 1
                        set strTemp = .GetNextSLKVariable(strTemp)
                    endloop
                    loop
                        exitwhen iLevel >= ai.rThreshold.size
                        set ai.rThreshold[iLevel] = rTemp
                        set iLevel = iLevel + 1
                    endloop
                endif
                
                if ( ai.strSLKAreaOfEffect != "" ) then
                    set iLevel = 0
                    set strTemp = ai.strSLKAreaOfEffect
                    loop
                        exitwhen strTemp == ""
                        exitwhen iLevel >= ai.rAreaOfEffect.size
                        set rTemp = S2R(strTemp)
                        set ai.rAreaOfEffect[iLevel] = rTemp
                        set iLevel = iLevel + 1
                        set strTemp = .GetNextSLKVariable(strTemp)
                    endloop
                    loop
                        exitwhen iLevel >= ai.rAreaOfEffect.size
                        set ai.rAreaOfEffect[iLevel] = rTemp
                        set iLevel = iLevel + 1
                    endloop
                endif
                
                if ( ai.strSLKRange != "" ) then
                    set iLevel = 0
                    set strTemp = ai.strSLKRange
                    loop
                        exitwhen strTemp == ""
                        exitwhen iLevel >= ai.rRange.size
                        set rTemp = S2R(strTemp)
                        set ai.rRange[iLevel] = rTemp
                        set iLevel = iLevel + 1
                        set strTemp = .GetNextSLKVariable(strTemp)
                    endloop
                    loop
                        exitwhen iLevel >= ai.rRange.size
                        set ai.rRange[iLevel] = rTemp
                        set iLevel = iLevel + 1
                    endloop
                endif
                
                if ( ai.strSLKCooldown != "" ) then
                    set iLevel = 0
                    set strTemp = ai.strSLKCooldown
                    loop
                        exitwhen strTemp == ""
                        exitwhen iLevel >= ai.rCooldown.size
                        set rTemp = S2R(strTemp)
                        set ai.rCooldown[iLevel] = rTemp
                        set iLevel = iLevel + 1
                        set strTemp = .GetNextSLKVariable(strTemp)
                    endloop
                    loop
                        exitwhen iLevel >= ai.rCooldown.size
                        set ai.rCooldown[iLevel] = rTemp
                        set iLevel = iLevel + 1
                    endloop
                endif
                
                if ( ai.strSLKManaCost != "" ) then
                    set iLevel = 0
                    set strTemp = ai.strSLKManaCost
                    loop
                        exitwhen strTemp == ""
                        exitwhen iLevel >= ai.iManaCost.size
                        set iTemp = S2I(strTemp)
                        set ai.iManaCost[iLevel] = iTemp
                        set iLevel = iLevel + 1
                        set strTemp = .GetNextSLKVariable(strTemp)
                    endloop
                    loop
                        exitwhen iLevel >= ai.iManaCost.size
                        set ai.iManaCost[iLevel] = iTemp
                        set iLevel = iLevel + 1
                    endloop
                endif
                
                set iAbility = iAbility + 1
            endloop
        endmethod

        static method getFromKey takes integer i returns ACST_AbilityInfo
            local ACST_AbilityInfo  ai = (.IH.get(i))
            if ( ai == 0 ) then
                set ai = .create(i)
            endif
            return ai
        endmethod

        static method create takes integer iAbility returns ACST_AbilityInfo
            local ACST_AbilityInfo  ai
            if ( (.IH.has(iAbility)) == TRUE ) then
                return (.IH.get(iAbility))
            endif
                
            set ai = ACST_AbilityInfo.allocate()
            set .MasterList[.MasterListCount] = ai
            set .MasterListCount = .MasterListCount + 1

            call .IH.add(iAbility, ai)
            set ai.iAbilityId = iAbility
            return ai
        endmethod

        method onDestroy takes nothing returns nothing
debug       call ACST_DebugMsg(ACST_DebugInvalidOnDestroy, "WARNING: ACST_AbilityInfo::onDestroy not supported")
            // Remove it from the hash anyway
            call .IH.del(this.iAbilityId)
        endmethod
    endstruct

    struct ACST_CastInfo
        ACST_AbilityInfo        ai
        ACST_UnitInfo           ui
        integer                 iPriority

        boolean                 bToggled                = FALSE
        real                    rNextCast               = 0.0
        integer                 iFilterCount            = 0
        real                    rLastCheckTime          = 0.0
debug   string                  strDebugState           = ""

        ACST_FilterAction       filterAction            = 0
        ACST_Filter             filterFunc              = 0
        ACST_Filter             filterAoeFunc           = 0

        // Optionals
        ACST_ThresholdFunction  thresholdFunc           = 0
        ACST_ThresholdFunction  thresholdAoeFunc        = 0
        ACST_ThreatFunction     threatFunc              = 0

        ACST_CastCondition      castConditionFunc       = 0
   
        boolean                 bActive                 = TRUE
        real                    rRequiredMinMana        = 0.0
        real                    rRequiredMaxMana        = 1.0
        real                    rRequiredMinLife        = 0.0
        real                    rRequiredMaxLife        = 1.0

        static method create takes ACST_AbilityInfo ai, ACST_UnitInfo ui returns ACST_CastInfo
            local ACST_CastInfo     ci

            set ci                  = ACST_CastInfo.allocate()
            set ci.ai               = ai
            set ci.ui               = ui
            set ci.iPriority        = ai.iPriority

            set ci.filterAction     = ai.defFilterAction
            set ci.filterFunc       = ai.defFilterFunc
            set ci.filterAoeFunc    = ai.defFilterAoeFunc
            set ci.thresholdFunc    = ai.defThresholdFunc
            set ci.thresholdAoeFunc = ai.defThresholdAoeFunc
            set ci.threatFunc       = ai.defThreatFunc

            return ci
        endmethod

        method onDestroy takes nothing returns nothing
        endmethod
    endstruct

    function ACST_UnitInRangeETA takes ETrigger et returns nothing
        call GroupAddUnit(et.gUnitsInRange, GetTriggerUnit())
    endfunction

    function ACST_AddFilterUnit takes nothing returns boolean
        if ( IsUnitEnemy(GetFilterUnit(), ACST_CleanupPlayer) == FALSE ) then
            if ( ACST_CleanupFriends == TRUE ) then
                call GroupAddUnit(ACST_CleanupGroup, GetFilterUnit())
            endif
        else
            if ( ACST_CleanupEnemies == TRUE ) then
                call GroupAddUnit(ACST_CleanupGroup, GetFilterUnit())
            endif
        endif
//        if ( IsUnitAlly(GetFilterUnit(), ACST_CleanupPlayer) == TRUE ) then
//            if ( ACST_CleanupFriends == TRUE ) then
//                call GroupAddUnit(ACST_CleanupGroup, GetFilterUnit())
//            endif
//        else
//            if ( ACST_CleanupEnemies == TRUE ) then
//                call GroupAddUnit(ACST_CleanupGroup, GetFilterUnit())
//            endif
//        endif
        return FALSE
    endfunction

    function ACST_EnumUnitsInRange takes ACST_UnitInfo ui returns nothing
        local ACST_CastInfo     ci
        local ACST_AbilityInfo  ai
        local integer           iSpell
        local integer           iAbilityLevel
        local real              rRange

        set ACST_CleanupEnemies     = FALSE
        set ACST_CleanupFriends     = FALSE
        set ACST_CleanupGroup       = ui.gUnitsInRange
        set ACST_CleanupPlayer      = GetOwningPlayer(ui.u)
        set rRange                  = 0.0

        set iSpell = 0
        loop
            exitwhen iSpell == ui.ciSpellCount

            set ci = ui.ciSpells[iSpell]
            exitwhen ci == 0

            set ai = ci.ai

            if ( ai.bEnemies == TRUE ) then
                set ACST_CleanupEnemies = TRUE
            endif
            if ( ai.bFriends == TRUE ) then
                set ACST_CleanupFriends = TRUE
            endif

            set iAbilityLevel = GetUnitAbilityLevel(ui.u, ai.iAbilityId)
            if ( ai.getRange(iAbilityLevel) > rRange ) then
                set rRange = ai.getRange(iAbilityLevel)
            endif

            set iSpell = iSpell + 1
        endloop

        if ( rRange > ACST_MaxAutocastRange ) then
            set rRange = ACST_MaxAutocastRange
        endif
        call GroupEnumUnitsInRange(ACST_MasterEnumGroup, GetUnitX(ui.u), GetUnitY(ui.u), rRange, Condition(function ACST_AddFilterUnit))
    endfunction

    function ACST_CleanupUnits takes nothing returns nothing

        set ACST_CleanupEnemy = GetEnumUnit()

        // always keep ourself, never need to check it either.
        if ( ACST_CleanupEnemy == ACST_CleanupCaster ) then 
            return
        endif

        if ( GetUnitTypeId(ACST_CleanupEnemy) == 0 ) then
            call GroupRemoveUnit(ACST_CleanupGroup, ACST_CleanupEnemy)
            return
        endif

        if ( IsUnitInRange(ACST_CleanupCaster, ACST_CleanupEnemy, ACST_CleanupRange) == FALSE ) then
            call GroupRemoveUnit(ACST_CleanupGroup, ACST_CleanupEnemy)
            return
        endif

    endfunction

    function ACST_OrderIdIsIgnored takes integer iOrderId returns boolean
        local integer i = 0

        loop
            exitwhen ACST_IgnoredOrderIds[i] == 0
            if ( ACST_IgnoredOrderIds[i] == iOrderId ) then
                return TRUE
            endif
            set i = i + 1
        endloop
        return FALSE
    endfunction

    struct ACST_UnitInfo extends MagicTimer

        unit                u                   = null
        integer             iDestroyTag         = 0
        group               gUnitsInRange
        ETrigger            etUnitEnters        = 0
        boolean             bEnabled            = ACST_InitiallyEnabled
debug   boolean             bDebugInfo          = FALSE
debug   string              strDebugState       = ""

        ACST_CastInfo array ciSpells[ACST_MaxSpellsPerUnit]
        integer             ciSpellCount        = 0
        real                rChannelTime        = 0.0
        real                rLastEnumTime       = 0.0

        // for morphing, we want to preserve autocast.
        integer             iMorphingUnitId     = 0
        boolean             bMorphAutocastOn    = FALSE
        boolean             bMorphAutocastOff   = FALSE

        integer             iThisOrderId        = 0
        integer             iThisOrderType      = ACST_ORDERTYPE_UNKNOWN
        unit                uThisOrderTarget    = null
        real                rThisOrderPointX    = 0.0
        real                rThisOrderPointY    = 0.0

        integer             iLastOrderId        = 0
        integer             iLastOrderType      = ACST_ORDERTYPE_UNKNOWN
        unit                uLastOrderTarget    = null
        real                rLastOrderPointX    = 0.0
        real                rLastOrderPointY    = 0.0

        static integer      EnumAbility
        static boolean      EnumHasSpell
        static boolean      EnumCasted

        method onExpire takes nothing returns nothing
            local integer           iAbilityLevel
            local integer           iSpell
            local boolean           bCasted
            local real              rTimeNow
            local real              rNextCast
            local real              rCooldown
            local real              rMinTime
            local boolean           bCastCheck
            local real              rMana
            local real              rLife
            local real              rManaPercent
            local real              rLifePercent

            local ACST_CastInfo     ci
            local ACST_AbilityInfo  ai

debug       call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "TimerTrace: Entered " + GetUnitName(this.u) + " of " + GetPlayerName(GetOwningPlayer(this.u)))
            set rTimeNow = GetElapsedGameTime()
debug       set this.strDebugState = "Thinking"

            // Check if the unit has died/decayed and is no longer in the world
            if ( GetUnitTypeId(this.u) == 0 ) then 
debug           set this.strDebugState = "Inactive"
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "UnitTypeId == 0: flushing timer")
                call .destroy()
                return
            endif

            // This unit is probably still active in the world
            //call AddTimerEvent(this, GetRandomReal(0.25, 0.40))
            call .start(GetRandomReal(0.25,0.40), FALSE)

            // Is autocast enabled for this player?
            if ( ACST_EnabledForPlayer[GetPlayerId(GetOwningPlayer(this.u))] == FALSE ) then
debug           set this.strDebugState = "Disabled For Player"
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Autocast disabled for owning player")
                return 
            endif

            // If autocast isn't on, don't do anything
            if ( this.bEnabled == FALSE ) then
debug           set this.strDebugState = "Autocast Disabled"
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Autocast ability disabled")
                return
            endif

            // If our guy isn't really alive, don't try and cast.  Reincarnation issue mainly
            if ( GetUnitState(this.u, UNIT_STATE_LIFE) <= 0.403 ) then
debug           set this.strDebugState = "Incapacitated"
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Caster temporarily incapacitated")
                return
            endif

            // Check to see if Autocast is a tech to research
            if ( ACST_GlobalAutocastTechId != 0 ) then
                // Don't autocast if Tech is required
                if ( GetPlayerTechCount(GetOwningPlayer(this.u), ACST_GlobalAutocastTechId, TRUE) == 0 ) then
debug               set this.strDebugState = "Tech Needed"
debug               call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Research needed")
                    return
                endif
            endif

            // Safeguard, if the unit is channeling (in any sort of casting phase), don't autocast
            if ( this.rChannelTime > rTimeNow ) then
debug           set this.strDebugState = "Channeling"
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Channeling")
                return
            endif

            // Cleanup the groups
            if ( GetRandomInt(0,3) == 4 ) then
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Cleaning up units")
                set ACST_CleanupCaster           = this.u
                set ACST_CleanupGroup            = this.gUnitsInRange
                set ACST_CleanupRange            = ACST_MaxAutocastRange
                call ForGroup(ACST_CleanupGroup, function ACST_CleanupUnits)
            endif

            // Sometimes the unit itself is removed from the group, when it dies and such
            if ( IsUnitInGroup(this.u, this.gUnitsInRange) == FALSE ) then
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Readding self")
                call GroupAddUnit(this.gUnitsInRange, this.u)
            endif

            // Don't autocast anything if the order overrides autocast
            if ( ACST_OrderIdIsIgnored(GetUnitCurrentOrder(this.u)) == TRUE ) then
debug           set this.strDebugState = "Ignored Order"
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "OrderId is ignored: " + I2S(GetUnitCurrentOrder(this.u)) + " '" + OrderId2String(GetUnitCurrentOrder(this.u)) + "'")
                return
            endif

            set bCasted = FALSE
            set rMana = GetUnitState(this.u, UNIT_STATE_MANA)
            set rLife = GetUnitState(this.u, UNIT_STATE_LIFE)
            if ( GetUnitState(this.u, UNIT_STATE_MAX_MANA) > 0 ) then
                set rManaPercent = rMana / GetUnitState(this.u, UNIT_STATE_MAX_MANA)
            else
                set rManaPercent = 0
            endif
            if ( GetUnitState(this.u, UNIT_STATE_MAX_LIFE) > 0 ) then
                set rLifePercent = rLife / GetUnitState(this.u, UNIT_STATE_MAX_LIFE)
            else
                set rLifePercent = 0
            endif
            set iSpell = 0
            loop
                exitwhen iSpell == this.ciSpellCount

                set ci = this.ciSpells[iSpell]
                exitwhen ci == 0

debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Checking spell " + GetObjectName(ci.ai.iAbilityId))

                set ai = ci.ai // just a tidbit faster, lots of .ab is bad
                set iAbilityLevel = GetUnitAbilityLevel(this.u, ai.iAbilityId)
debug           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Ability Level: " + I2S(iAbilityLevel))

                set bCastCheck = TRUE
                if ( ci.rRequiredMinMana >= 0.0 ) then
                    if ( ci.rRequiredMinMana <= 1.0 and rManaPercent < ci.rRequiredMinMana ) then
                        set bCastCheck = FALSE
                    elseif ( ci.rRequiredMinMana > 1.0 and rMana < ci.rRequiredMinMana ) then
                        set bCastCheck = FALSE
                    endif
                endif
                
                if ( ci.rRequiredMaxMana >= 0.0 ) then
                    if ( ci.rRequiredMaxMana <= 1.0 and rManaPercent > ci.rRequiredMaxMana ) then
                        set bCastCheck = FALSE
                    elseif ( ci.rRequiredMaxMana > 1.0 and rMana > ci.rRequiredMaxMana ) then
                        set bCastCheck = FALSE
                    endif
                endif
                
                if ( ci.rRequiredMinMana >= 0.0 ) then
                    if ( ci.rRequiredMinMana <= 1.0 and rManaPercent < ci.rRequiredMinMana ) then
                        set bCastCheck = FALSE
                    elseif ( ci.rRequiredMinMana > 1.0 and rMana < ci.rRequiredMinMana ) then
                        set bCastCheck = FALSE
                    endif
                endif
                
                if ( ci.rRequiredMaxMana >= 0.0 ) then
                    if ( ci.rRequiredMaxMana <= 1.0 and rManaPercent > ci.rRequiredMaxMana ) then
                        set bCastCheck = FALSE
                    elseif ( ci.rRequiredMaxMana > 1.0 and rMana > ci.rRequiredMaxMana ) then
                        set bCastCheck = FALSE
                    endif
                endif
                
                if ( ci.castConditionFunc != 0 ) then
                    if ( ci.castConditionFunc.evaluate(this, ci, ai) == FALSE ) then
                        set bCastCheck = FALSE
                    endif
                endif

                if ( bCastCheck == TRUE ) then

                    if ( rTimeNow >= ci.rNextCast and iAbilityLevel > 0 ) then

                        if ( ai.getManaCost(iAbilityLevel) <= rMana ) then
                            //set bCasted = TRUE

                            // Enum units
                            if ( rTimeNow - this.rLastEnumTime > ACST_EnumFrequency ) then
debug                           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Calling GroupEnum")
                                call ACST_EnumUnitsInRange(this)
                                set this.rLastEnumTime = rTimeNow
                            endif

debug                       call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Evaluating filterAction")
                            set bCasted = ci.filterAction.evaluate(ai, this, ci)
                            set ci.iFilterCount     = ACSTFilter_Count
                            set ci.rLastCheckTime   = rTimeNow

                            if ( bCasted == TRUE ) then
debug                           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Casting")
                                if ( ai.bPhysicalAttack == TRUE ) then
                                    // Physical attacks don't channel, so we assume its instant recharge
                                    set rMinTime    = ACST_SuccessfulCastBackoffTime(ACSTFilter_Count)
                                    set rCooldown   = ai.getCooldown(iAbilityLevel)
                                    if ( rMinTime > rCooldown ) then
                                        set ci.rNextCast = rTimeNow + rMinTime
                                    else
                                        set ci.rNextCast = rTimeNow + rCooldown
                                    endif
                                    set this.rChannelTime = rTimeNow + 0.05
                                else
                                    // NOTE: We add 1.0 to the next cast because we handle
                                    // a trigger which fires on any spell effect begins.
                                    // Ability cooldowns start exactly when the effect starts!
                                    // So the other trigger will update rNextCast if need be.
                                    set ci.rNextCast = rTimeNow + ai.getCooldown(iAbilityLevel) + 1.0
                                    set this.rChannelTime = rTimeNow + 1.00
                                endif
debug                           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Backoff Time: " + R2S(ci.rNextCast - rTimeNow))
                            else
debug                           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Not Casting")
                                // even if we don't cast, lets bump the rNextCast randomly
                                // we do this so that high cpu usage spells dont lag the shit
                                // out of the game by doing groupaddgroup and forgroup on 100
                                // units every 1.15 seconds...
                                // CHANGE: Instead of random, do it based on their resource usage
                                set ci.rNextCast = rTimeNow + ACST_FailedCastBackoffTime(ACSTFilter_Count)
debug                           call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Backoff Time: " + R2S(ci.rNextCast - rTimeNow))
                            endif
                        else
                            set ci.rNextCast = rTimeNow + ACST_InsufficientManaBackoffTime
debug                       set ci.strDebugState = "Insufficient mana"
debug                       call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Not enough mana")
                        endif
                    else
debug                   call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Not ready to be casted")
                    endif
                else
                    set ci.rNextCast = rTimeNow + ACST_FailedCastConditionsBackoffTime
debug               set ci.strDebugState = "Failed Cast Conditions"
debug               call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Failed Cast Conditions")
                endif
                exitwhen bCasted == TRUE

                set iSpell = iSpell + 1
            endloop

debug       set this.strDebugState = "Full Check"
debug       call ACST_DebugMsgIf(ACST_DebugTimerTrace, this.bDebugInfo, "Unable to cast any spells")
        endmethod

        static method create takes unit u returns ACST_UnitInfo
            local ACST_UnitInfo     ui
            local ETrigger          et

            set ui                  = ACST_UnitInfo.allocate()
            set ui.gUnitsInRange    = CreateGroup()
            set ui.u                = u

            call GroupAddUnit(ui.gUnitsInRange, u)

            if ( ACST_UseUnitInRangeEvents == TRUE ) then
                set et                      = ETrigger.create(ETriggerAction.ACST_UnitInRangeETA)
                set et.gUnitsInRange        = ui.gUnitsInRange
                set et.plr                  = GetOwningPlayer(u)
                call TriggerRegisterUnitInRange(et.trg, u, ACST_MaxAutocastRange, null)
                set ui.etUnitEnters         = et
            endif

            //call AddTimerEvent(ui, GetRandomReal(0.25, 1.0))
            call ui.start(GetRandomReal(0.25,1.00), FALSE)
            return ui
        endmethod

        method getSpell takes integer iSpellAbilityId returns ACST_CastInfo
            local ACST_CastInfo     ci
            local integer           iSpell

            set iSpell = 0
            loop
                exitwhen iSpell == this.ciSpellCount

                set ci = this.ciSpells[iSpell]
                exitwhen ci == 0

                if ( ci.ai.iAbilityId == iSpellAbilityId ) then
                    return ci
                endif

                set iSpell = iSpell + 1
            endloop

            return 0
        endmethod

        method addSpell takes integer iSpellAbilityID returns nothing
            local ACST_AbilityInfo  ai      = ACST_AbilityInfo.getFromKey(iSpellAbilityID)
            local ACST_CastInfo     ci
            local integer           iSpell
            local boolean           bMove   = FALSE
            local integer           iSlot   = -1

            if ( ai == 0 ) then
debug           call ACST_DebugMsg(ACST_DebugWarnings, "WARNING: ACST_UnitInfo::addSpell Spell not valid autocastable spell")
                return
            endif

            set iSpell = 0
            loop
                exitwhen iSpell == this.ciSpellCount

                set ci = this.ciSpells[iSpell]
                exitwhen ci == 0

                if ( ci.ai == ai ) then
                    return
                endif

                set iSpell = iSpell + 1
            endloop

            if ( this.ciSpellCount == ACST_MaxSpellsPerUnit ) then
debug           call ACST_DebugMsg(ACST_DebugWarnings, "WARNING: ACST_UnitInfo::addSpell Maximum spells per unit reached")
                return
            endif

            // OK add it but conserve priority
            set iSpell = 0
            loop
                exitwhen iSpell == this.ciSpellCount
                set ci = this.ciSpells[iSpell]
                exitwhen ci == 0

                if ( ai.iPriority > ci.iPriority ) then
                    set iSlot = iSpell
                    exitwhen TRUE
                endif
                set iSpell = iSpell + 1
            endloop

            if ( iSlot == -1 ) then // append to the end
                set this.ciSpells[this.ciSpellCount] = ACST_CastInfo.create(ai, this)
                set this.ciSpellCount = this.ciSpellCount + 1
            else
                // insert at iSlot, but first move everything to the right one
                set iSpell = this.ciSpellCount
                loop
                    exitwhen iSpell == iSlot or iSpell == 0

                    set this.ciSpells[iSpell] = this.ciSpells[iSpell - 1]

                    set iSpell = iSpell - 1
                endloop
                set this.ciSpells[iSlot] = ACST_CastInfo.create(ai, this)
                set this.ciSpellCount = this.ciSpellCount + 1
            endif
        endmethod

        method onDestroy takes nothing returns nothing
            call DestroyGroup(this.gUnitsInRange)
            
            if ( this.u != null ) then
                call ACST_OnUnitUserDataDestroyed(this.u, this.iDestroyTag)
            endif
            set this.u = null

            if ( this.etUnitEnters != 0 ) then
                call this.etUnitEnters.destroy()
            endif

        endmethod
    endstruct

    function ACST_True takes nothing returns boolean
        return TRUE
    endfunction

    function ACST_OrderIdIsNotRepeated takes integer iOrderId returns boolean
        local integer i = 0

        loop
            exitwhen ACST_NotRepeatedOrderIds[i] == 0
            if ( ACST_NotRepeatedOrderIds[i] == iOrderId ) then
                return TRUE
            endif
            set i = i + 1
        endloop
        return FALSE
    endfunction

    function ACST_OrderIdIsRepeated takes integer iOrderId returns boolean
        local integer i = 0

        loop
            exitwhen ACST_RepeatedOrderIds[i] == 0
            if ( ACST_RepeatedOrderIds[i] == iOrderId ) then
                return TRUE
            endif
            set i = i + 1
        endloop
        return FALSE
    endfunction

    function ACST_AddSpellToUnit takes unit u, integer abSpell returns nothing
        local ACST_UnitInfo         ui

        set ui = ACST_GetUnitUserData(u)
        if ( ui == 0 ) then
            set ui              = ACST_UnitInfo.create(u)
            set ui.iDestroyTag  = ACST_SetUnitUserData(u, ui)
        endif
        call ui.addSpell(abSpell)
    endfunction

    function ACST_InitializeUnit takes unit u returns nothing
        local integer               iAbilityInfo
        local ACST_AbilityInfo      ai
        local ACST_UnitDefaults     ud
        local ACST_AbilityDefaults  ad

        if ( ACST_UnitHasAutocast(u) == FALSE ) then
            return
        endif

        set ud = ACST_UnitDefaults.GetUnitDefaults(GetUnitTypeId(u))
        if ( ud == 0 and ACST_CompleteInitialScan == TRUE ) then
            set iAbilityInfo = 0
            loop
                exitwhen iAbilityInfo == ACST_AbilityInfo.MasterListCount
                set ai = ACST_AbilityInfo.MasterList[iAbilityInfo]

                if ( GetUnitAbilityLevel(u, ai.iAbilityId) > 0 ) then
                    // we have 
                    call ACST_AddSpellToUnit(u, ai.iAbilityId)
                endif

                set iAbilityInfo = iAbilityInfo + 1
            endloop
        elseif ( ud != 0 ) then
            set iAbilityInfo = 0
            loop
                exitwhen iAbilityInfo >= ud.iAbilityCount

                set ad = ud.adList[iAbilityInfo]
                call ACST_AddSpellToUnit(u, ad.iAbilityId)

                set iAbilityInfo = iAbilityInfo + 1
            endloop
        endif
    endfunction

    function ACST_InitializeFilterUnit takes nothing returns boolean
        call ACST_InitializeUnit(GetFilterUnit())
        return FALSE
    endfunction

    function ACST_InitializeEnumUnit takes nothing returns nothing
        call ACST_InitializeUnit(GetEnumUnit())
    endfunction

    function ACST_EventUnitEntersWorld takes nothing returns nothing
        call ACST_InitializeUnit(GetEnteringUnit())
    endfunction

    function ACST_EventSpellUnitStartsChannel takes nothing returns nothing
        local unit              uCaster     = GetTriggerUnit()
        local ACST_UnitInfo     ui

        set ui = ACST_GetUnitUserData(uCaster)

        if ( ui != 0 ) then
            set ui.rChannelTime = GetElapsedGameTime() + ACST_DefaultChannelTime
        endif
        set uCaster = null
    endfunction

    function ACST_EventSpellUnitEndsCast takes nothing returns nothing
        local unit              uCaster     = GetTriggerUnit()
        local ACST_UnitInfo     ui

        set ui = ACST_GetUnitUserData(uCaster)

        if ( ui != 0 ) then
            set ui.rChannelTime = 0.0
            if ( ACST_OrderIdIsRepeated(ui.iLastOrderId) == TRUE ) then
                if ( ui.iLastOrderType == ACST_ORDERTYPE_TARGET ) then
                    call IssueTargetOrderById(uCaster, ui.iLastOrderId, ui.uLastOrderTarget)
                elseif ( ui.iLastOrderType == ACST_ORDERTYPE_POINT ) then
                    call IssuePointOrderById(uCaster, ui.iLastOrderId, ui.rLastOrderPointX, ui.rLastOrderPointY)
                elseif ( ui.iLastOrderType == ACST_ORDERTYPE_IMMEDIATE ) then
                    call IssueImmediateOrderById(uCaster, ui.iLastOrderId)
                endif
            endif
            if ( ui.iMorphingUnitId != GetUnitTypeId(uCaster)) then
                set ui.iMorphingUnitId = GetUnitTypeId(uCaster)
                call ACST_InitializeUnit(uCaster)
            endif
        endif
        set uCaster = null
    endfunction

    function ACST_EventSpellUnitAnimationCancel takes integer tag returns nothing
        local ACST_UnitInfo     ui          = ACST_UnitInfo(tag)

        if ( ui != 0 ) then
            set ui.rChannelTime = 0.0
            if ( ACST_OrderIdIsRepeated(ui.iLastOrderId) == TRUE ) then
                if ( ui.iLastOrderType == ACST_ORDERTYPE_TARGET ) then
                    call IssueTargetOrderById(ui.u, ui.iLastOrderId, ui.uLastOrderTarget)
                elseif ( ui.iLastOrderType == ACST_ORDERTYPE_POINT ) then
                    call IssuePointOrderById(ui.u, ui.iLastOrderId, ui.rLastOrderPointX, ui.rLastOrderPointY)
                elseif ( ui.iLastOrderType == ACST_ORDERTYPE_IMMEDIATE ) then
                    call IssueImmediateOrderById(ui.u, ui.iLastOrderId)
                endif
            endif
        endif
    endfunction

    function ACST_EventSpellUnitStartsEffect takes nothing returns nothing
        local unit              uCaster     = GetTriggerUnit()
        local integer           iAbilityId  = GetSpellAbilityId()
        local ACST_UnitInfo     ui
        local ACST_CastInfo     ci

        set ui = ACST_GetUnitUserData(uCaster)

        if ( ui != 0 ) then
            set ci = ui.getSpell(iAbilityId)
            if ( ci != 0 ) then
                set ci.rNextCast = GetElapsedGameTime() + ci.ai.getCooldown(GetUnitAbilityLevel(uCaster, iAbilityId)) + GetRandomReal(0.05,0.10)
                if ( ci.ai.bAnimationCancel == TRUE ) then
                    call DelayedFunctionCall(OnTimeFunction.ACST_EventSpellUnitAnimationCancel, ui, 0.01)
                endif
            endif
        endif
        set uCaster = null
    endfunction

    function ACST_ConfigureAutocastForUnit takes unit uCaster returns nothing
    endfunction

    function ACST_DisableAutocastForUnit takes unit uCaster returns nothing
        local ACST_UnitInfo     ui

        set ui = ACST_GetUnitUserData(uCaster)
        if ( ui == 0 ) then
            return
        endif
        set ui.bEnabled = FALSE
    endfunction

    function ACST_EnableAutocastForUnit takes unit uCaster returns nothing
        local ACST_UnitInfo     ui

        set ui = ACST_GetUnitUserData(uCaster)
        if ( ui == 0 ) then
            return
        endif
        set ui.bEnabled = TRUE
    endfunction

    function ACST_MoveThisOrderToLast takes ACST_UnitInfo ui returns nothing
        if ( ACST_OrderIdIsRepeated(ui.iThisOrderId) == FALSE and ACST_OrderIdIsNotRepeated(ui.iThisOrderId) == FALSE) then
            return
        endif
        set ui.iLastOrderId     = ui.iThisOrderId
        set ui.iLastOrderType   = ui.iThisOrderType
        set ui.uLastOrderTarget = ui.uThisOrderTarget
        set ui.rLastOrderPointX = ui.rThisOrderPointX
        set ui.rLastOrderPointY = ui.rThisOrderPointY
    endfunction

    function ACST_EventUnitOrderedUnit takes nothing returns nothing
        local unit          uOrdered    = GetOrderedUnit()
        local unit          uTarget     = GetOrderTargetUnit()
        local integer       iOrderId    = GetIssuedOrderId()
        local ACST_UnitInfo ui          = ACST_GetUnitUserData(uOrdered)

        if ( ui != 0 ) then
            call ACST_MoveThisOrderToLast(ui)
debug       call ACST_DebugMsgIf(ACST_DebugOrderHistory, ui.bDebugInfo, GetUnitName(uOrdered) + " of " + GetPlayerName(GetOwningPlayer(uOrdered)) + " ordered to " + I2S(iOrderId) + " '" + OrderId2String(iOrderId) + "' " + GetUnitName(uTarget))
            set ui.iThisOrderId     = iOrderId
            set ui.iThisOrderType   = ACST_ORDERTYPE_TARGET
            set ui.uThisOrderTarget = uTarget
            set ui.rThisOrderPointX = 0.0
            set ui.rThisOrderPointY = 0.0
        endif

        set uOrdered    = null
        set uTarget     = null
    endfunction

    function ACST_EventUnitOrderedPoint takes nothing returns nothing
        local unit          uOrdered    = GetOrderedUnit()
        local location      locTarget   = GetOrderPointLoc()
        local integer       iOrderId    = GetIssuedOrderId()
        local ACST_UnitInfo ui          = ACST_GetUnitUserData(uOrdered)

        if ( ui != 0 ) then
            call ACST_MoveThisOrderToLast(ui)
debug       call ACST_DebugMsgIf(ACST_DebugOrderHistory, ui.bDebugInfo, GetUnitName(uOrdered) + " of " + GetPlayerName(GetOwningPlayer(uOrdered)) + " ordered to " + I2S(iOrderId) + " '" + OrderId2String(iOrderId) + "' " + " point (" + R2S(GetLocationX(locTarget)) + ","  + R2S(GetLocationY(locTarget)) + ")")
            set ui.iThisOrderId     = iOrderId
            set ui.iThisOrderType   = ACST_ORDERTYPE_POINT
            set ui.uThisOrderTarget = null
            set ui.rThisOrderPointX = GetLocationX(locTarget)
            set ui.rThisOrderPointY = GetLocationY(locTarget)
        endif

        call RemoveLocation(locTarget)
        set locTarget   = null
        set uOrdered    = null
    endfunction
    

    function ACST_EventUnitOrdered takes nothing returns nothing
        local unit          uOrdered    = GetOrderedUnit()
        local integer       iOrderId    = GetIssuedOrderId()
        local ACST_UnitInfo ui          = ACST_GetUnitUserData(uOrdered)

        if ( ui != 0 ) then
            call ACST_MoveThisOrderToLast(ui)
debug       call ACST_DebugMsgIf(ACST_DebugOrderHistory, ui.bDebugInfo, GetUnitName(uOrdered) + " of " + GetPlayerName(GetOwningPlayer(uOrdered)) + " ordered to " + I2S(iOrderId) + " '" + OrderId2String(iOrderId) + "' " + " immediately")
            set ui.iThisOrderId     = iOrderId
            set ui.iThisOrderType   = ACST_ORDERTYPE_IMMEDIATE
            set ui.uThisOrderTarget = null
            set ui.rThisOrderPointX = 0.0
            set ui.rThisOrderPointY = 0.0
        endif

        set uOrdered    = null
    endfunction

    function ACST_DelayedInitialize takes nothing returns nothing
        local group     gTemp

        call ACST_AbilityInfo.FinalizeSLK()

        set ACST_TrgUnitEntersWorld = CreateTrigger()
        call TriggerRegisterEnterRectSimple(ACST_TrgUnitEntersWorld, GetPlayableMapRect())
        set ACST_TraUnitEntersWorld = TriggerAddAction(ACST_TrgUnitEntersWorld, function ACST_EventUnitEntersWorld)
        
        //set gTemp = CreateGroup()
        //call GroupEnumUnitsInRect(gTemp, GetPlayableMapRect(), Condition(function ACST_True))
        //call ForGroup(gTemp, function ACST_InitializeEnumUnit)
        //call DestroyGroup(gTemp)
        //set gTemp = null
        call GroupEnumUnitsInRect(ACST_MasterEnumGroup, GetPlayableMapRect(), Condition(function ACST_InitializeFilterUnit))
    endfunction

    function Autocast_Initialize takes nothing returns nothing
        
        set ACST_AbilityInfo.IH         = IntHash.create()

        set ACST_EnabledForPlayer[0]    = FALSE
        set ACST_EnabledForPlayer[1]    = FALSE
        set ACST_EnabledForPlayer[2]    = FALSE
        set ACST_EnabledForPlayer[3]    = FALSE
        set ACST_EnabledForPlayer[4]    = FALSE
        set ACST_EnabledForPlayer[5]    = FALSE
        set ACST_EnabledForPlayer[6]    = FALSE
        set ACST_EnabledForPlayer[7]    = FALSE
        set ACST_EnabledForPlayer[8]    = FALSE
        set ACST_EnabledForPlayer[9]    = FALSE
        set ACST_EnabledForPlayer[10]   = FALSE
        set ACST_EnabledForPlayer[11]   = FALSE
        set ACST_EnabledForPlayer[12]   = FALSE
        set ACST_EnabledForPlayer[13]   = FALSE
        set ACST_EnabledForPlayer[14]   = FALSE
        set ACST_EnabledForPlayer[15]   = FALSE

        set ACST_TrgUnitStartsChannel = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ACST_TrgUnitStartsChannel, EVENT_PLAYER_UNIT_SPELL_CHANNEL)
        set ACST_TraUnitStartsChannel = TriggerAddAction(ACST_TrgUnitStartsChannel, function ACST_EventSpellUnitStartsChannel)

        set ACST_TrgUnitEndsCast = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ACST_TrgUnitEndsCast, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
        set ACST_TraUnitEndsCast = TriggerAddAction(ACST_TrgUnitEndsCast, function ACST_EventSpellUnitEndsCast)
        
        set ACST_TrgUnitStartsEffect = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ACST_TrgUnitStartsEffect, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        set ACST_TraUnitStartsEffect = TriggerAddAction(ACST_TrgUnitStartsEffect, function ACST_EventSpellUnitStartsEffect)

        set ACST_TrgUnitOrderedUnit = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ACST_TrgUnitOrderedUnit, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
        set ACST_TraUnitOrderedUnit = TriggerAddAction(ACST_TrgUnitOrderedUnit, function ACST_EventUnitOrderedUnit)

        set ACST_TrgUnitOrderedPoint = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ACST_TrgUnitOrderedPoint, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        set ACST_TraUnitOrderedPoint = TriggerAddAction(ACST_TrgUnitOrderedPoint, function ACST_EventUnitOrderedPoint)

        set ACST_TrgUnitOrdered = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ACST_TrgUnitOrdered, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        set ACST_TraUnitOrdered = TriggerAddAction(ACST_TrgUnitOrdered, function ACST_EventUnitOrdered)

        // hack..
        call ACST_InitializeAbilities()
        call TriggerSleepAction(1.0)
        call ACST_DelayedInitialize()
    endfunction
endlibrary


library AutocastDebug initializer AutocastDebug_Initialize requires AutocastInterface, TimeLib

    globals
debug   constant integer    ACST_DebugUnknown           = -1
debug   constant integer    ACST_DebugInvalidOnDestroy  = 0
debug   constant integer    ACST_DebugWarnings          = 1
debug   constant integer    ACST_DebugTimerTrace        = 2
debug   constant integer    ACST_DebugOrderHistory      = 3
debug   constant integer    ACST_DebugFilterTrace       = 4
debug   constant integer    ACST_DebugFilterFailure     = 5
debug   constant integer    ACST_DebugMax               = 6
debug
debug   boolean array       ACST_DebugInfo
debug   string array        ACST_DebugNames
debug
debug   trigger             ACST_TrgDebugController
debug   triggeraction       ACST_TraDebugController
debug
debug   multiboard          ACST_DebugVisualMultiboard  = null
debug   unit array          ACST_DebugVisualUnits
debug   ACST_UnitInfo array ACST_DebugVisualUnitInfos
debug   integer             ACST_DebugVisualUnitCount   = 0
debug   timer               ACST_DebugVisualTimer
    endglobals

debug   function ACST_DebugMsg takes integer iDebugType, string str returns nothing
debug       if ( iDebugType < 0 or iDebugType >= ACST_DebugMax ) then
debug           call DisplayTimedTextFromPlayer(Player(0), 0, 0, 60, R2S(GetElapsedGameTime()) + " " + "Invalid Debug Type")
debug           return
debug       endif
debug
debug       if ( ACST_DebugInfo[iDebugType] == FALSE ) then
debug           return
debug       endif
debug 
debug       call DisplayTimedTextFromPlayer(Player(0), 0, 0, 60, R2S(GetElapsedGameTime()) + " " + str)
debug   endfunction

debug   function ACST_DebugMsgIf takes integer iDebugType, boolean bIf, string str returns nothing
debug       if ( bIf == FALSE ) then
debug           return
debug       endif
debug 
debug       if ( iDebugType < 0 or iDebugType >= ACST_DebugMax ) then
debug           call DisplayTimedTextFromPlayer(Player(0), 0, 0, 60, R2S(GetElapsedGameTime()) + " " + "Invalid Debug Type")
debug           return
debug       endif
debug
debug       if ( ACST_DebugInfo[iDebugType] == FALSE ) then
debug           return
debug       endif
debug 
debug       call DisplayTimedTextFromPlayer(Player(0), 0, 0, 60, R2S(GetElapsedGameTime()) + " " + str)
debug   endfunction
debug   

debug   function ACST_DebugBooleanString takes boolean b returns string
debug       if ( b == TRUE ) then
debug           return "On"
debug       else
debug           return "Off"
debug       endif
debug   endfunction

debug   function ACST_SetMultiboardItem takes multiboard mb, integer iRow, integer iCol, real rWidth, string strVal returns nothing
debug       local multiboarditem    mbi = MultiboardGetItem(mb, iRow, iCol)
debug       call MultiboardSetItemWidth(mbi, rWidth)
debug       call MultiboardSetItemValue(mbi, strVal)
debug       call MultiboardReleaseItem(mbi)
debug       set mbi = null
debug   endfunction

debug   function ACST_DebugVisualUpdate takes nothing returns nothing
debug       local ACST_CastInfo     ci
debug       local ACST_UnitInfo     ui
debug       local multiboard        mb
debug       local integer           iSpell
debug       local integer           iRow
debug       local real              rTime = GetElapsedGameTime()
debug       local unit              u
debug       local integer           iUnit
debug
debug       if ( ACST_DebugVisualMultiboard == null ) then
debug           return
debug       endif
debug
debug       set mb = ACST_DebugVisualMultiboard
debug
debug       // Find out how many rows we'll need
debug       set iRow = 0
debug       set iUnit = 0
debug       loop
debug           exitwhen iUnit >= ACST_DebugVisualUnitCount
debug
debug           set iRow = iRow + 3 + ACST_DebugVisualUnitInfos[iUnit].ciSpellCount
debug
debug           set iUnit = iUnit + 1
debug       endloop
debug       call MultiboardDisplay(mb, FALSE)
debug       call MultiboardSetRowCount(mb, iRow)
debug       call MultiboardSetColumnCount(mb, 8)
debug       call MultiboardSetItemsStyle(mb, TRUE, FALSE)
debug
debug       set iRow = 0
debug       set iUnit = 0
debug       loop
debug           exitwhen iUnit >= ACST_DebugVisualUnitCount
debug           set u   = ACST_DebugVisualUnits[iUnit]
debug           set ui  = ACST_DebugVisualUnitInfos[iUnit]
debug
debug           call ACST_SetMultiboardItem(mb, iRow, 0, 0.10, GetUnitName(u))
debug           call ACST_SetMultiboardItem(mb, iRow, 1, 0.08, ui.strDebugState)
debug           call ACST_SetMultiboardItem(mb, iRow, 2, 0.05, "ID: " + I2S(ui))
debug           call ACST_SetMultiboardItem(mb, iRow, 3, 0.01, "")
debug           call ACST_SetMultiboardItem(mb, iRow, 4, 0.06, "LastCheck")
debug           call ACST_SetMultiboardItem(mb, iRow, 5, 0.06, "NextCast")
debug           call ACST_SetMultiboardItem(mb, iRow, 6, 0.06, "Active")
debug           call ACST_SetMultiboardItem(mb, iRow, 7, 0.06, "FilterCount")
debug
debug           set iRow = iRow + 1
debug
debug           call ACST_SetMultiboardItem(mb, iRow, 0, 0.03, "")
debug           call ACST_SetMultiboardItem(mb, iRow, 1, 0.11, "Current Order:")
debug           call ACST_SetMultiboardItem(mb, iRow, 2, 0.06, I2S(ui.iThisOrderId))
debug           call ACST_SetMultiboardItem(mb, iRow, 3, 0.10, OrderId2String(ui.iThisOrderId))
debug           if ( ui.iThisOrderType == ACST_ORDERTYPE_TARGET ) then
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.10, GetUnitName(ui.uThisOrderTarget))
debug           elseif ( ui.iThisOrderType == ACST_ORDERTYPE_POINT ) then
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.10, "(" + R2S(ui.rThisOrderPointX) + "," + R2S(ui.rThisOrderPointY) + ")")
debug           elseif ( ui.iThisOrderType == ACST_ORDERTYPE_IMMEDIATE ) then
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.10, "Immediate")
debug           elseif ( ui.iThisOrderType == ACST_ORDERTYPE_TOGGLE ) then
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.10, "Toggle")
debug           endif
debug           call ACST_SetMultiboardItem(mb, iRow, 5, 0.06, "")
debug           call ACST_SetMultiboardItem(mb, iRow, 6, 0.01, "")
debug           call ACST_SetMultiboardItem(mb, iRow, 7, 0.01, "")
debug
debug           set iRow = iRow + 1
debug
debug           call ACST_SetMultiboardItem(mb, iRow, 0, 0.03, "")
debug           call ACST_SetMultiboardItem(mb, iRow, 1, 0.11, "Last Order:")
debug           call ACST_SetMultiboardItem(mb, iRow, 2, 0.06, I2S(ui.iLastOrderId))
debug           call ACST_SetMultiboardItem(mb, iRow, 3, 0.10, OrderId2String(ui.iLastOrderId))
debug           if ( ui.iLastOrderType == ACST_ORDERTYPE_TARGET ) then
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.10, GetUnitName(ui.uLastOrderTarget))
debug           elseif ( ui.iLastOrderType == ACST_ORDERTYPE_POINT ) then
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.10, "(" + R2S(ui.rLastOrderPointX) + "," + R2S(ui.rLastOrderPointY) + ")")
debug           elseif ( ui.iLastOrderType == ACST_ORDERTYPE_IMMEDIATE ) then
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.10, "Immediate")
debug           elseif ( ui.iLastOrderType == ACST_ORDERTYPE_TOGGLE ) then
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.10, "Toggle")
debug           endif
debug           call ACST_SetMultiboardItem(mb, iRow, 5, 0.06, "")
debug           call ACST_SetMultiboardItem(mb, iRow, 6, 0.01, "")
debug           call ACST_SetMultiboardItem(mb, iRow, 7, 0.01, "")
debug
debug           set iRow = iRow + 1
debug           set iSpell = 0
debug           loop
debug               exitwhen iSpell == ui.ciSpellCount
debug               set ci = ui.ciSpells[iSpell]
debug               
debug               call ACST_SetMultiboardItem(mb, iRow, 0, 0.01, "")
debug               call ACST_SetMultiboardItem(mb, iRow, 1, 0.02, I2S(ci.iPriority))
debug               call ACST_SetMultiboardItem(mb, iRow, 2, 0.08, GetObjectName(ci.ai.iAbilityId))
debug               call ACST_SetMultiboardItem(mb, iRow, 3, 0.14, ci.strDebugState)
debug               call ACST_SetMultiboardItem(mb, iRow, 4, 0.06, R2S(ci.rLastCheckTime))
debug               call ACST_SetMultiboardItem(mb, iRow, 5, 0.06, R2S(ci.rNextCast - rTime))
debug               if ( ci.bToggled == TRUE ) then
debug                   call ACST_SetMultiboardItem(mb, iRow, 6, 0.06, "On")
debug               else
debug                   call ACST_SetMultiboardItem(mb, iRow, 6, 0.06, "Off")
debug               endif
debug               call ACST_SetMultiboardItem(mb, iRow, 7, 0.05, I2S(ci.iFilterCount))
debug
debug               set iRow = iRow + 1
debug               set iSpell = iSpell + 1
debug           endloop
debug
debug           set iUnit = iUnit + 1
debug       endloop
debug       call MultiboardDisplay(mb, TRUE)
debug       set mb = null
debug   endfunction

debug   function ACST_DebugVisualRemoveUnit takes unit u, ACST_UnitInfo ui returns nothing
debug       local integer           iUnit
debug       local boolean           bMove = FALSE
debug
debug       set iUnit = 0
debug       loop
debug           exitwhen iUnit >= ACST_DebugVisualUnitCount-1
debug           if ( ACST_DebugVisualUnits[iUnit] == u ) then
debug               set bMove = TRUE
debug           endif
debug
debug           if ( bMove == TRUE ) then
debug               set ACST_DebugVisualUnits[iUnit] = ACST_DebugVisualUnits[iUnit + 1]
debug               set ACST_DebugVisualUnitInfos[iUnit] = ACST_DebugVisualUnitInfos[iUnit + 1]
debug           endif
debug           set iUnit = iUnit + 1
debug       endloop
debug       if ( bMove == TRUE or ACST_DebugVisualUnits[iUnit] == u ) then
debug           set ACST_DebugVisualUnitCount = ACST_DebugVisualUnitCount - 1
debug       endif
debug       if ( ACST_DebugVisualUnitCount == 0 and ACST_DebugVisualMultiboard != null ) then
debug           call DestroyMultiboard(ACST_DebugVisualMultiboard)
debug           set ACST_DebugVisualMultiboard = null
debug       endif
debug   endfunction

debug   function ACST_DebugVisualAddUnit takes unit u, ACST_UnitInfo ui returns nothing
debug       local integer           iUnit
debug
debug       if ( ACST_DebugVisualMultiboard == null ) then
debug           set ACST_DebugVisualMultiboard = CreateMultiboard()
debug           call MultiboardSetTitleText(ACST_DebugVisualMultiboard, "Autocast Visual Debug Info")
debug       endif
debug
debug       set iUnit = 0
debug       loop
debug           exitwhen iUnit >= ACST_DebugVisualUnitCount
debug           if ( ACST_DebugVisualUnits[iUnit] == u ) then
debug               return
debug           endif
debug           set iUnit = iUnit + 1
debug       endloop
debug       set ACST_DebugVisualUnits[ACST_DebugVisualUnitCount] = u
debug       set ACST_DebugVisualUnitInfos[ACST_DebugVisualUnitCount] = ui
debug       set ACST_DebugVisualUnitCount = ACST_DebugVisualUnitCount + 1
debug   endfunction

debug   function ACST_DebugController takes nothing returns nothing
debug       local string        strText         = StringCase(GetEventPlayerChatString(), FALSE)
debug       local string        strDebugType
debug       local string        strDebugVal
debug       local integer       iDebugType
debug       local integer       iStrLen
debug       local group         gUnits
debug       local unit          u
debug       local ACST_UnitInfo ui
debug
debug       set strDebugType = SubString(strText, 7, 999)
debug       if ( SubString(strDebugType, 0, 4) == "unit" ) then
debug           set strDebugVal = SubString(strDebugType, 5, 999)
debug           call SyncSelections()
debug           set gUnits = CreateGroup()
debug           call GroupEnumUnitsSelected(gUnits, GetTriggerPlayer(), null)
debug           loop
debug               set u = FirstOfGroup(gUnits)
debug               exitwhen u == null
debug
debug               call GroupRemoveUnit(gUnits, u)
debug               set ui = ACST_GetUnitUserData(u)
debug               if ( ui == 0 ) then
debug                   call BJDebugMsg("Unable to set debug info for " + GetUnitName(u) + ", not autocaster")
debug               else
debug                   if ( StringLength(strDebugVal) == 0 or strDebugVal == "on" ) then
debug                       call BJDebugMsg("Debug info for " + GetUnitName(u) + " activated")
debug                       set ui.bDebugInfo = TRUE
debug                   elseif ( strDebugVal == "off" ) then
debug                       call BJDebugMsg("Debug info for " + GetUnitName(u) + " deactivated")
debug                       call ACST_DebugVisualRemoveUnit(u, ui)
debug                       set ui.bDebugInfo = FALSE
debug                   elseif ( strDebugVal == "visual" ) then
debug                       call BJDebugMsg("Visual Debug info for " + GetUnitName(u) + " activated")
debug                       call ACST_DebugVisualAddUnit(u, ui)
debug                   endif
debug               endif    
debug           endloop
debug           call DestroyGroup(gUnits)
debug           set gUnits = null
debug           return
debug       endif    
debug       set iDebugType = 0
debug       loop
debug           exitwhen iDebugType == ACST_DebugMax
debug
debug           set iStrLen = StringLength(ACST_DebugNames[iDebugType])
debug           if ( SubString(strDebugType, 0, iStrLen) == StringCase(ACST_DebugNames[iDebugType], FALSE) ) then
debug               set strDebugVal = SubString(strDebugType, iStrLen + 1, 999)
debug               exitwhen TRUE
debug           endif
debug
debug           set iDebugType = iDebugType + 1
debug       endloop
debug
debug       if ( iDebugType >= ACST_DebugMax ) then
debug           // help
debug           call BJDebugMsg("Usage: -debug (<type>|unit) [(on|off|visual)]")
debug           call BJDebugMsg("  Note: Visual only for units, * denotes per unit debug info")
debug           set iDebugType = 0
debug           loop
debug               exitwhen iDebugType == ACST_DebugMax
debug               call BJDebugMsg("  Type '" + ACST_DebugNames[iDebugType] + "' " + ACST_DebugBooleanString(ACST_DebugInfo[iDebugType]))
debug               set iDebugType = iDebugType + 1
debug           endloop
debug           return
debug       endif
debug       if ( StringLength(strDebugVal) == 0 or strDebugVal == "on" ) then
debug           set ACST_DebugInfo[iDebugType] = TRUE
debug           call BJDebugMsg("Debug info for " + ACST_DebugNames[iDebugType] + " activated")
debug       elseif ( strDebugVal == "off" ) then
debug           set ACST_DebugInfo[iDebugType] = FALSE
debug           call BJDebugMsg("Debug info for " + ACST_DebugNames[iDebugType] + " deactivated")
debug       endif
debug   endfunction

        function AutocastDebug_Initialize takes nothing returns nothing
debug       set ACST_DebugInfo[0]                               = TRUE
debug       set ACST_DebugNames[0]                              = "Unknown"
debug       set ACST_DebugInfo[ACST_DebugInvalidOnDestroy]      = FALSE
debug       set ACST_DebugNames[ACST_DebugInvalidOnDestroy]     = "InvalidOnDestroy"
debug       set ACST_DebugInfo[ACST_DebugWarnings]              = FALSE
debug       set ACST_DebugNames[ACST_DebugWarnings]             = "Warnings"
debug       set ACST_DebugInfo[ACST_DebugTimerTrace]            = FALSE
debug       set ACST_DebugNames[ACST_DebugTimerTrace]           = "*TimerTrace"
debug       set ACST_DebugInfo[ACST_DebugOrderHistory]          = FALSE
debug       set ACST_DebugNames[ACST_DebugOrderHistory]         = "*OrderHistory"
debug       set ACST_DebugInfo[ACST_DebugFilterTrace]           = FALSE
debug       set ACST_DebugNames[ACST_DebugFilterTrace]          = "*FilterTrace"
debug       set ACST_DebugInfo[ACST_DebugFilterFailure]         = FALSE
debug       set ACST_DebugNames[ACST_DebugFilterFailure]        = "*FilterFailure"
debug       set ACST_DebugInfo[ACST_DebugMax]                   = FALSE
debug       set ACST_DebugNames[ACST_DebugMax]                  = "Max"

debug       set ACST_TrgDebugController = CreateTrigger()
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(0), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(1), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(2), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(3), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(4), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(5), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(6), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(7), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(8), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(9), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(10), "-debug", FALSE)
debug       call TriggerRegisterPlayerChatEvent(ACST_TrgDebugController, Player(11), "-debug", FALSE)
debug       set ACST_TraDebugController = TriggerAddAction(ACST_TrgDebugController, function ACST_DebugController)
debug
debug       set ACST_DebugVisualTimer = CreateTimer()
debug       call TimerStart(ACST_DebugVisualTimer, 0.10, TRUE, function ACST_DebugVisualUpdate)
        endfunction

endlibrary


library AutocastFilters initializer AutocastFilters_Initialize requires AutocastDebug

    function interface ACST_FilterAction takes ACST_AbilityInfo ai, ACST_UnitInfo ui, ACST_CastInfo ci returns boolean
    function interface ACST_Filter takes unit uCaster, unit uTarget, real rRange, ACST_AbilityInfo ai returns boolean

    globals
        ACST_Filter         ACST_GenericFilterFunc
        ACST_Filter         ACST_GenericPBAOEFilterFunc

        // ForGroup variables... These are the suck
        ACST_AbilityInfo    ACSTFilter_CurrentAbilityInfo
        ACST_CastInfo       ACSTFilter_CurrentCastInfo
        integer             ACSTFilter_Count
        integer             ACSTFilter_Threat
        integer             ACSTFilter_ThreatSameCount
        unit                ACSTFilter_Target
        unit                ACSTFilter_Enemy
        unit                ACSTFilter_Caster
        ACST_UnitInfo       ACSTFilter_CasterUnitInfo
        group               ACSTFilter_Group
        real                ACSTFilter_Range
        real                ACSTFilter_Threshold
    endglobals

    function ACST_GenericPBAOEFilter takes unit uCaster, unit uTarget, real rRange, ACST_AbilityInfo ai returns boolean
        set ACSTFilter_Count = ACSTFilter_Count + 1

        // check for buffs, we don't buff a unit with the buff in place
        if ( ai.bBuffCheck == TRUE ) then
            if ( GetUnitAbilityLevel(uTarget, ai.iBuffCode) > 0 ) then
                return FALSE
            endif
        endif

        // Check for an ability which makes us ignore it, could be a buff or anything..
        if ( ai.iIgnoreAbility > 0 ) then
            if ( GetUnitAbilityLevel(uTarget, ai.iIgnoreAbility) > 0 ) then
                return FALSE
            endif
        endif

        return uCaster == uTarget
    endfunction

    function ACST_GenericFilter takes unit uCaster, unit uTarget, real rRange, ACST_AbilityInfo ai returns boolean
        local integer               iPositiveBuffCount
        local integer               iNegativeBuffCount
        local integer               iThreat

        // increase the number of units we filtered, used to throttle the
        // speed at which autocasting thinks
        set ACSTFilter_Count = ACSTFilter_Count + 1

        // Check dead units
        if ( ai.bDeadUnits == FALSE ) then
            if ( GetUnitState(uTarget, UNIT_STATE_LIFE) <= 0.405 ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is dead")
                return FALSE
            endif
        endif

        // Check living units
        if ( ai.bLivingUnits == FALSE ) then
            if ( GetUnitState(uTarget, UNIT_STATE_LIFE) > 0.405 ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is alive")
                return FALSE
            endif
        endif

        if ( uTarget == uCaster ) then
            if ( ai.bSelfTarget == FALSE ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is self, cannot target self")
                return FALSE
            endif
        else
            if ( ai.bPlayerOwned == TRUE ) then
                if ( GetOwningPlayer(uCaster) != GetOwningPlayer(uTarget)) then
debug               call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not PlayerOwned")
                    return FALSE
                endif
            elseif ( ai.bFriends == FALSE and IsUnitAlly(uTarget, GetOwningPlayer(uCaster)) == TRUE ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is Friendly")
                return FALSE
            endif
        endif
        if ( ai.bEnemies == FALSE and IsUnitAlly(uTarget, GetOwningPlayer(uCaster)) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is Enemy")
            return FALSE
        endif
        if ( ai.bOnlyInvisible == TRUE and IsUnitInvisible(uTarget, GetOwningPlayer(uCaster)) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not Invisible")
            return FALSE
        endif
        if ( ai.bOnlyRangedAttackers == TRUE and IsUnitType(uTarget, UNIT_TYPE_RANGED_ATTACKER) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not Ranged Attacker")
            return FALSE
        endif
        if ( ai.bOnlyStructures == TRUE and IsUnitType(uTarget, UNIT_TYPE_STRUCTURE) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not Structure")
            return FALSE
        endif
        if ( ai.bOnlyUndead == TRUE and IsUnitType(uTarget, UNIT_TYPE_UNDEAD) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not Undead")
            return FALSE
        endif
        if ( ai.bOnlySummoned == TRUE and IsUnitType(uTarget, UNIT_TYPE_SUMMONED) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not Summoned")
            return FALSE
        endif
        if ( ai.bOnlyTauren == TRUE and IsUnitType(uTarget, UNIT_TYPE_TAUREN) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not Tauren")
            return FALSE
        endif
        if ( ai.bOnlyMechanical == TRUE and IsUnitType(uTarget, UNIT_TYPE_MECHANICAL) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not Mechanical")
            return FALSE
        endif
        if ( ai.bGround == FALSE and IsUnitType(uTarget, UNIT_TYPE_GROUND) == TRUE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is Ground unit")
            return FALSE
        endif
        if ( ai.bAir == FALSE and IsUnitType(uTarget, UNIT_TYPE_FLYING) == TRUE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is Air unit")
            return FALSE
        endif
        if ( ai.bNotVisible == FALSE and IsUnitVisible(uTarget, GetOwningPlayer(uCaster)) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not visible")
            return FALSE
        endif
        if ( ai.bInvisible == FALSE and IsUnitInvisible(uTarget, GetOwningPlayer(uCaster)) == TRUE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is invisible")
            return FALSE
        endif
        if ( ai.bHeroes == FALSE and IsUnitType(uTarget, UNIT_TYPE_HERO) == TRUE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is a Hero")
            return FALSE
        endif
        if ( ai.bMechanical == FALSE and IsUnitType(uTarget, UNIT_TYPE_MECHANICAL) == TRUE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is mechanical")
            return FALSE
        endif
        if ( ai.bMagicImmune == FALSE and IsUnitType(uTarget, UNIT_TYPE_MAGIC_IMMUNE) == TRUE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is Magic Immune")
            return FALSE
        endif
        if ( ai.bStructures == FALSE and IsUnitType(uTarget, UNIT_TYPE_STRUCTURE) == TRUE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is a Structure")
            return FALSE
        endif

        if ( ai.bWards == FALSE and IsUnitType(uTarget, UNIT_TYPE_MELEE_ATTACKER) == FALSE and IsUnitType(uTarget, UNIT_TYPE_RANGED_ATTACKER) == FALSE and IsUnitType(uTarget, UNIT_TYPE_SUMMONED) == TRUE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is a Ward")
            return FALSE
        endif


        if ( ai.iDispel == ACST_DISPEL_FAVORED ) then
            set iPositiveBuffCount =  UnitCountBuffsEx(uTarget, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE)
            set iNegativeBuffCount =  UnitCountBuffsEx(uTarget, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE)
    
            if ( IsPlayerAlly(GetOwningPlayer(uCaster), GetOwningPlayer(uTarget)) == TRUE ) then
                // allies, don't dispel if they got alot of good buffs
                //if ( iPositiveBuffCount > iNegativeBuffCount or iNegativeBuffCount == 0 ) then
                if ( iNegativeBuffCount == 0 ) then
debug               call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not debuffed")
                    return FALSE
                endif
            else
                if ( IsUnitType(uTarget, UNIT_TYPE_SUMMONED) == TRUE ) then
                    // do nothing, summoned units ALWAYS qualify for dispel?
                // unlike allies, don't dispel enemies with lots of negative buffs
                //elseif ( iPositiveBuffCount < iNegativeBuffCount or iPositiveBuffCount == 0 ) then
                elseif ( iPositiveBuffCount == 0 ) then
debug               call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not buffed")
                    return FALSE
                endif
            endif
        elseif ( ai.iDispel == ACST_DISPEL_ALL ) then
            set iPositiveBuffCount =  UnitCountBuffsEx(uTarget, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE)
            set iNegativeBuffCount =  UnitCountBuffsEx(uTarget, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE)
    
            if ( IsPlayerAlly(GetOwningPlayer(uCaster), GetOwningPlayer(uTarget)) == TRUE ) then
                // allies, don't dispel if they got alot of good buffs
                //if ( iPositiveBuffCount > iNegativeBuffCount or iNegativeBuffCount == 0 ) then
                if ( iNegativeBuffCount == 0 or iPositiveBuffCount > 2*iNegativeBuffCount ) then
debug               call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not debuffed or has many buffs")
                    return FALSE
                endif
            else
                if ( IsUnitType(uTarget, UNIT_TYPE_SUMMONED) == TRUE ) then
                    // do nothing, summoned units ALWAYS qualify for dispel?
                // unlike allies, don't dispel enemies with lots of negative buffs
                //elseif ( iPositiveBuffCount < iNegativeBuffCount or iPositiveBuffCount == 0 ) then
                elseif ( iPositiveBuffCount == 0 ) then
debug               call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not buffed")
                    return FALSE
                endif
            endif
        endif

        // recheck range, units move all the time
        if ( IsUnitInRange(uCaster, uTarget, rRange) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is not in range")
            // this is the ONLY case where we want to remove the unit from the group
            call GroupRemoveUnit(ACSTFilter_Group, uTarget)
            return FALSE
        endif

        // check for buffs, we don't buff a unit with the buff in place
        if ( ai.bBuffCheck == TRUE ) then
            if ( GetUnitAbilityLevel(uTarget, ai.iBuffCode) > 0 ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit is already affected by buff")
                return FALSE
            endif
        endif

        // Check for an ability which makes us ignore it, could be a buff or anything..
        if ( ai.iIgnoreAbility > 0 ) then
            if ( GetUnitAbilityLevel(uTarget, ai.iIgnoreAbility) > 0 ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterFailure, ACSTFilter_CasterUnitInfo.bDebugInfo, "Filter Failure: Unit has IgnoreAbility")
                return FALSE
            endif
        endif

        return TRUE
    endfunction

    function ACST_FilterController takes nothing returns nothing
        local ACST_AbilityInfo      ai  = ACSTFilter_CurrentAbilityInfo
        local ACST_CastInfo         ci  = ACSTFilter_CurrentCastInfo
        local integer               iThreat

        if ( ci.threatFunc == 0 ) then
            if ( ACSTFilter_Target != null ) then
                return
            endif
        endif

        set ACSTFilter_Enemy = GetEnumUnit()
        //if ( ACST_GenericFilter(ACSTFilter_Caster, ACSTFilter_Enemy, ACSTFilter_Range, ai) == FALSE ) then
        if ( ci.filterFunc.evaluate(ACSTFilter_Caster, ACSTFilter_Enemy, ACSTFilter_Range, ai) == FALSE ) then
debug       call ACST_DebugMsgIf(ACST_DebugFilterTrace, ACSTFilter_CasterUnitInfo.bDebugInfo, "FilterUnit " + GetUnitName(ACSTFilter_Enemy) + " failed filterFunc evaluation")
            return
        endif

        // Check threshold
        if ( ci.thresholdFunc != 0 ) then
            if ( ci.thresholdFunc.evaluate(ACSTFilter_Caster, ACSTFilter_Enemy, ci, ai, ACSTFilter_Threshold) == FALSE ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ACSTFilter_CasterUnitInfo.bDebugInfo, "FilterUnit " + GetUnitName(ACSTFilter_Enemy) + " failed thresholdFunc evaluation")
                return
            endif
        endif

        if ( ci.threatFunc != 0 ) then
            set iThreat = ci.threatFunc.evaluate(ACSTFilter_Caster, ACSTFilter_Enemy, ci, ai)
            if ( iThreat > ACSTFilter_Threat ) then
                set ACSTFilter_Target = ACSTFilter_Enemy
                set ACSTFilter_Threat = iThreat
                set ACSTFilter_ThreatSameCount = 1
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ACSTFilter_CasterUnitInfo.bDebugInfo, "FilterUnit " + GetUnitName(ACSTFilter_Enemy) + " is new target (threat: " + I2S(iThreat) + ")")
debug       elseif ( iThreat == ACSTFilter_Threat and ACSTFilter_ThreatSameCount > 0 ) then
                set ACSTFilter_ThreatSameCount = ACSTFilter_ThreatSameCount + 1
debug               call ACST_DebugMsgIf(ACST_DebugFilterTrace, ACSTFilter_CasterUnitInfo.bDebugInfo, "FilterUnit " + GetUnitName(ACSTFilter_Enemy) + " has same threat, randomizing (samecount: " + I2S(ACSTFilter_ThreatSameCount) + ") (threat: " + I2S(iThreat) + ")")
                if ( GetRandomInt(1, ACSTFilter_ThreatSameCount) == 1 ) then
                    set ACSTFilter_Target = ACSTFilter_Enemy
debug               call ACST_DebugMsgIf(ACST_DebugFilterTrace, ACSTFilter_CasterUnitInfo.bDebugInfo, "FilterUnit " + GetUnitName(ACSTFilter_Enemy) + " is new target (tie random win) (threat: " + I2S(iThreat) + ")")
                endif
            else
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ACSTFilter_CasterUnitInfo.bDebugInfo, "FilterUnit " + GetUnitName(ACSTFilter_Enemy) + " threat too low (" + I2S(iThreat) + " < " + I2S(ACSTFilter_Threat))
            endif
            
        else
debug       call ACST_DebugMsgIf(ACST_DebugFilterTrace, ACSTFilter_CasterUnitInfo.bDebugInfo, "FilterUnit " + GetUnitName(ACSTFilter_Enemy) + " is new target")
            set ACSTFilter_Target = ACSTFilter_Enemy
        endif
    endfunction

    function ACST_DefaultFilterAction takes ACST_AbilityInfo ai, ACST_UnitInfo ui, ACST_CastInfo ci returns boolean
        local boolean   bCasted         = FALSE
        local integer   iMaxIterations  = 2
        local unit      uTempRemoved    = null

debug   call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "FilterTrace (ACST_DefaultFilterAction): Entered " + GetUnitName(ui.u) + " of " + GetPlayerName(GetOwningPlayer(ui.u)) + " for ability " + GetObjectName(ai.iAbilityId))
        set ACSTFilter_CurrentAbilityInfo   = ai
        set ACSTFilter_CurrentCastInfo      = ci
        set ACSTFilter_Caster               = ui.u
        set ACSTFilter_CasterUnitInfo       = ui
        set ACSTFilter_Group                = ui.gUnitsInRange
        set ACSTFilter_Range                = ai.getRange(GetUnitAbilityLevel(ui.u, ai.iAbilityId)) - 50.0
        set ACSTFilter_Threshold            = ai.getThreshold(GetUnitAbilityLevel(ui.u, ai.iAbilityId))
        set ACSTFilter_Count                = 0

debug   call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Initial filter check")
        // Ok lets check for something to cast on
        loop
            exitwhen iMaxIterations == 0
            set iMaxIterations              = iMaxIterations - 1

            set ACSTFilter_Target           = null
            set ACSTFilter_Threat           = -1 
            set ACSTFilter_ThreatSameCount  = 0
            
debug       call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Filtering units in range (" + I2S(CountUnitsInGroup(ui.gUnitsInRange)) + ")")

            call ForGroup(ui.gUnitsInRange, function ACST_FilterController)

            if ( uTempRemoved != null ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Readding temporary unit '" + GetUnitName(uTempRemoved))
                // readd the temporary removed unit
                call GroupAddUnit(ui.gUnitsInRange, uTempRemoved)
            endif

debug       call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Filtered target: " + GetUnitName(ACSTFilter_Target) + ", Threat: " + I2S(ACSTFilter_Threat))
            if ( ai.iOrderType == ACST_ORDERTYPE_TOGGLE and ci.bToggled == TRUE and ACSTFilter_Target == null ) then
                if ( ai.iUnorderId != 0 ) then
debug               call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Issuing toggle unorder")
                    set bCasted = IssueImmediateOrderById(ui.u, ai.iUnorderId)
                else
debug               call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Issuing toggle order")
                    set bCasted = IssueImmediateOrder(ui.u, ai.strUnorder)
                endif
                if ( bCasted == TRUE ) then
debug               call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Order successful")
                    set ci.bToggled = FALSE
                    exitwhen TRUE
                endif
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Order unsuccessful")
            endif

            // no target? don't do anything
            if ( ACSTFilter_Target == null ) then
debug           set ci.strDebugState = "No Valid Target"
                exitwhen TRUE
            endif
            // we have a target, but the threat is < 0
            if ( ci.threatFunc != 0 and ACSTFilter_Threat < 0 ) then
debug           set ci.strDebugState = "No Threat (" + I2S(ACSTFilter_Threat) + ")"
                exitwhen TRUE
            endif

            if ( ai.iOrderType == ACST_ORDERTYPE_TARGET ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Issuing target order " + GetUnitName(ACSTFilter_Target))
                if ( ai.iOrderId != 0 ) then
                    set bCasted = IssueTargetOrderById(ui.u, ai.iOrderId, ACSTFilter_Target)
                else
                    set bCasted = IssueTargetOrder(ui.u, ai.strOrder, ACSTFilter_Target)
                endif
            elseif ( ai.iOrderType == ACST_ORDERTYPE_POINT ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Issuing point order " + GetUnitName(ACSTFilter_Target))
                if ( ai.iOrderId != 0 ) then
                    set bCasted = IssuePointOrderById(ui.u, ai.iOrderId, GetUnitX(ACSTFilter_Target), GetUnitY(ACSTFilter_Target))
                else
                    set bCasted = IssuePointOrder(ui.u, ai.strOrder, GetUnitX(ACSTFilter_Target), GetUnitY(ACSTFilter_Target))
                endif
            elseif ( ai.iOrderType == ACST_ORDERTYPE_IMMEDIATE ) then
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Issuing immediate order")
                if ( ai.iOrderId != 0 ) then
                    set bCasted = IssueImmediateOrderById(ui.u, ai.iOrderId)
                else
                    set bCasted = IssueImmediateOrder(ui.u, ai.strOrder)
                endif
            elseif ( ai.iOrderType == ACST_ORDERTYPE_TOGGLE ) then
                if ( ci.bToggled == FALSE ) then
debug               call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Issuing toggle order")
                    if ( ai.iOrderId != 0 ) then
                        set bCasted = IssueImmediateOrderById(ui.u, ai.iOrderId)
                    else
                        set bCasted = IssueImmediateOrder(ui.u, ai.strOrder)
                    endif
                    if ( bCasted == TRUE ) then
                        set ci.bToggled = TRUE
                    endif
                else
                    // do nothing here
debug               call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Would issue toggle order but already active")
                    exitwhen TRUE
                endif
            else
debug           call ACST_DebugMsg(ACST_DebugWarnings, "WARNING: ACST_DefaultFilterAction Filter has invalid order type")
            endif

            if ( bCasted == TRUE ) then
debug           set ci.strDebugState = "Cast: " + GetUnitName(ACSTFilter_Target) + " (" + I2S(ACSTFilter_Threat) + ")"
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Order successful")
            else
debug           set ci.strDebugState = "Unsuccessful Cast: " + GetUnitName(ACSTFilter_Target) + " (" + I2S(ACSTFilter_Threat) + ")"
debug           call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Order unsuccessful")
            endif
            exitwhen bCasted == TRUE
            exitwhen uTempRemoved != null

debug       if ( ai.iOrderType == ACST_ORDERTYPE_TARGET or ai.iOrderType == ACST_ORDERTYPE_POINT ) then
debug           call ACST_DebugMsg(ACST_DebugWarnings, "WARNING: ACST_DefaultFilterAction " + GetUnitName(ui.u) + " failed casting " + GetObjectName(ai.iAbilityId) + " at " + GetUnitName(ACSTFilter_Target))
debug       else
debug           call ACST_DebugMsg(ACST_DebugWarnings, "WARNING: ACST_DefaultFilterAction " + GetUnitName(ui.u) + " failed casting " + GetObjectName(ai.iAbilityId))
debug       endif

            // its an invalid target, try again one more time BUT readd it later
            set uTempRemoved = ACSTFilter_Target
            call GroupRemoveUnit(ui.gUnitsInRange, uTempRemoved)
debug       call ACST_DebugMsgIf(ACST_DebugFilterTrace, ui.bDebugInfo, "Invalid target? Try again but without " + GetUnitName(uTempRemoved))
        endloop

        set uTempRemoved = null
        return bCasted
    endfunction

    function AutocastFilters_Initialize takes nothing returns nothing
        set ACST_GenericFilterFunc          = ACST_Filter.ACST_GenericFilter
        set ACST_GenericPBAOEFilterFunc     = ACST_Filter.ACST_GenericPBAOEFilter
    endfunction
endlibrary


library AutocastThreat initializer AutocastThreat_Initialize requires AutocastFilters

    function interface ACST_ThreatFunction takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer

    globals
        // Set a variable to each function so we can load via SLK
        ACST_ThreatFunction     ACST_GenericHarmfulAOEThreatFunc
        ACST_ThreatFunction     ACST_GenericLevelThreatFunc
        ACST_ThreatFunction     ACST_GenericPointValueThreatFunc
        ACST_ThreatFunction     ACST_GenericFeedbackThreatFunc
        ACST_ThreatFunction     ACST_GenericHealThreatFunc
        ACST_ThreatFunction     ACST_GenericDeathCoilThreatFunc
        ACST_ThreatFunction     ACST_GenericDrainFriendlyManaThreatFunc
        ACST_ThreatFunction     ACST_GenericDarkRitualThreatFunc
        
        group                   ACST_ThreatEnumGroup = CreateGroup()
        boolexpr                ACST_ThreatEnumCondition
        integer                 ACST_ThreatEnumCountFriendly
        integer                 ACST_ThreatEnumCountEnemy
        ACST_Filter             ACST_ThreatEnumFilter
        unit                    ACST_ThreatEnumCaster
        unit                    ACST_ThreatEnumTarget
        ACST_AbilityInfo        ACST_ThreatEnumAbilityInfo
    endglobals

    function ACST_ThreatEnumFunction takes nothing returns boolean
        if ( ACST_ThreatEnumFilter.evaluate(ACST_ThreatEnumCaster, GetFilterUnit(), 1500.0, ACST_ThreatEnumAbilityInfo) == TRUE ) then
            if ( IsUnitAlly(ACST_ThreatEnumCaster, GetOwningPlayer(GetFilterUnit())) == TRUE ) then
                set ACST_ThreatEnumCountFriendly = ACST_ThreatEnumCountFriendly + 1
            else
                set ACST_ThreatEnumCountEnemy = ACST_ThreatEnumCountEnemy + 1
            endif
        endif
        return FALSE
    endfunction

    function ACST_GenericHarmfulAOEThreat takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer
        local integer   iThreat
        local unit      uTemp

        set ACST_ThreatEnumCountFriendly    = 0
        set ACST_ThreatEnumCountEnemy       = 0
        set ACST_ThreatEnumFilter           = ci.filterAoeFunc
        set ACST_ThreatEnumCaster           = uCaster
        set ACST_ThreatEnumTarget           = uTarget
        set ACST_ThreatEnumAbilityInfo      = ai

        call GroupClear(ACST_ThreatEnumGroup)
        call GroupEnumUnitsInRange(ACST_ThreatEnumGroup, GetUnitX(uTarget), GetUnitY(uTarget), ai.getAreaOfEffect(GetUnitAbilityLevel(uCaster, ai.iAbilityId)), ACST_ThreatEnumCondition)
           
        set iThreat = -3 - (ACST_ThreatEnumCountFriendly * 3) + (ACST_ThreatEnumCountEnemy * 2)
        return iThreat
    endfunction

    function ACST_GenericLevelThreat takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer
        local integer   iThreat

        // Quite simple really, high level = the most important unit
        set iThreat = GetUnitLevel(uTarget)
        return iThreat
    endfunction

    function ACST_GenericPointValueThreat takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer
        local integer   iThreat

        // Quite simple really, high level = the most important unit
        set iThreat = GetUnitPointValue(uTarget)
        return iThreat
    endfunction

    function ACST_GenericFeedbackThreat takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer
        local integer   iThreat

        set iThreat = 0
        if ( IsUnitType(uTarget, UNIT_TYPE_SUMMONED) ) then 
            set iThreat = iThreat + R2I(GetUnitState(uTarget, UNIT_STATE_LIFE))
        endif
        set iThreat = iThreat + R2I(GetUnitState(uTarget, UNIT_STATE_MANA))

        return iThreat
    endfunction

    // Generic Heal Threat Calculator
    function ACST_GenericHealThreat takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer
        local real      rLifePercentage
        local integer   iThreat

        // Check the life percentage, we want to prioritize units that are almost dead
        set rLifePercentage = GetUnitState(uTarget, UNIT_STATE_LIFE)/GetUnitState(uTarget, UNIT_STATE_MAX_LIFE)

        // Square the life percentage, a unit with 50% life has a 25 base,
        // but a unit with 30% life has a 49 base, scale this with their level.
        // levels usually range from 1-5, so a hero with 50% life will be:
        // 0.5*0.5*100 * 5 = 125

        // And a regular unit, level 3, with 35% life will be
        // 0.65*0.65*100 * 3 = 126

        // So it slightly sets priority to more valuable units.
        set iThreat = R2I(Pow((1.0 - rLifePercentage), 2.0)*100) * GetUnitLevel(uTarget)
        return iThreat
    endfunction

    // Generic Death Coil Threat Calculator // uses heal
    function ACST_GenericDeathCoilThreat takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer

        if ( IsUnitAlly(uTarget, GetOwningPlayer(uCaster)) == TRUE ) then
            return ACST_GenericHealThreat(uCaster, uTarget, ci, ai)
        endif

        return 20*GetUnitLevel(uTarget)
    endfunction

    // Generic Drain Friendly Mana Threat Calculator
    function ACST_GenericDrainFriendlyManaThreat takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer
        local real      rLifePercentage
        local integer   iThreat

        // Similar to heal, but we prioritize lower level units moreso than higher level units
        // Also, slightly prioritize units with a ton of mana.

        set rLifePercentage = GetUnitState(uTarget, UNIT_STATE_LIFE)/GetUnitState(uTarget, UNIT_STATE_MAX_LIFE)

        // Square the life percentage, a unit with 50% life has a 25 base,
        // but a unit with 30% life has a 49 base, scale this with their (10-level).
        // levels usually range from 1-5, so a hero with 50% life will be:
        // 0.5*0.5*100 * (10-5) = 125

        // And a regular unit, level 3, with 58% life will be
        // 0.42*0.42*100 * (10-3) = 123

        // And a level 1 unit (ie shade) with 50% life will be
        // 0.50*0.50*100 * (10-1) = 225

        // So it slightly sets priority to less valuable units.
        set iThreat = R2I(Pow((1.0 - rLifePercentage), 2.0)*100) * (10 - GetUnitLevel(uTarget))

        // Now, scale this in porportion to the unit's mana/100
        set iThreat = iThreat * R2I(GetUnitState(uTarget, UNIT_STATE_MANA)) / 100
        // units with <100 mana will be completly ignored, whereas units with 200 mana will be favored
        return iThreat
    endfunction

    // Generic Dark Ritual Threat Calculator
    function ACST_GenericDarkRitualThreat takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai returns integer
        // Unknown territory, going to go with something really simple for now...
        return R2I(Pow(R2I(5 - GetUnitLevel(uTarget)), 3.0) * GetUnitState(uTarget, UNIT_STATE_LIFE))
    endfunction

    function AutocastThreat_Initialize takes nothing returns nothing
        set ACST_ThreatEnumCondition                = Condition(function ACST_ThreatEnumFunction)

        set ACST_GenericHarmfulAOEThreatFunc        = ACST_ThreatFunction.ACST_GenericHarmfulAOEThreat
        set ACST_GenericLevelThreatFunc             = ACST_ThreatFunction.ACST_GenericLevelThreat
        set ACST_GenericPointValueThreatFunc        = ACST_ThreatFunction.ACST_GenericPointValueThreat
        set ACST_GenericFeedbackThreatFunc          = ACST_ThreatFunction.ACST_GenericFeedbackThreat
        set ACST_GenericHealThreatFunc              = ACST_ThreatFunction.ACST_GenericHealThreat
        set ACST_GenericDeathCoilThreatFunc         = ACST_ThreatFunction.ACST_GenericDeathCoilThreat
        set ACST_GenericDrainFriendlyManaThreatFunc = ACST_ThreatFunction.ACST_GenericDrainFriendlyManaThreat
        set ACST_GenericDarkRitualThreatFunc        = ACST_ThreatFunction.ACST_GenericDarkRitualThreat
    endfunction

endlibrary

library AutocastThreshold initializer AutocastThreshold_Initialize requires AutocastFilters

    function interface ACST_ThresholdFunction takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai, real rThreshold returns boolean

    globals
        // Set a variable to each function so we can load via SLK
        ACST_ThresholdFunction  ACST_GenericPBAOECountThresholdFunc
        ACST_ThresholdFunction  ACST_GenericLevelThresholdFunc
        ACST_ThresholdFunction  ACST_GenericLifeThresholdFunc
        ACST_ThresholdFunction  ACST_GenericAOELifeThresholdFunc
        ACST_ThresholdFunction  ACST_GenericManaThresholdFunc
        
        group                   ACST_ThresholdEnumGroup = CreateGroup()
        boolexpr                ACST_ThresholdEnumCondition
        integer                 ACST_ThresholdEnumCountIgnored
        integer                 ACST_ThresholdEnumCountFriendly
        integer                 ACST_ThresholdEnumCountEnemy
        ACST_Filter             ACST_ThresholdEnumFilter
        unit                    ACST_ThresholdEnumCaster
        unit                    ACST_ThresholdEnumTarget
        ACST_AbilityInfo        ACST_ThresholdEnumAbilityInfo
        ACST_CastInfo           ACST_ThresholdEnumCastInfo
        ACST_ThresholdFunction  ACST_ThresholdEnumAoeThresholdFunc
        real                    ACST_ThresholdEnumAoeThreshold
    endglobals

    function ACST_ThresholdEnumFunction takes nothing returns boolean
        if ( GetUnitTypeId(GetFilterUnit()) == ACST_ThresholdEnumAbilityInfo.iIgnoreUnit ) then
            set ACST_ThresholdEnumCountIgnored = ACST_ThresholdEnumCountIgnored + 1
            return FALSE
        endif

        if ( ACST_ThresholdEnumFilter.evaluate(ACST_ThresholdEnumCaster, GetFilterUnit(), 1500.0, ACST_ThresholdEnumAbilityInfo) == FALSE ) then
            return FALSE
        endif

        // Check threshold
        if ( ACST_ThresholdEnumAoeThresholdFunc != 0 ) then
            if ( ACST_ThresholdEnumAoeThresholdFunc.evaluate(ACST_ThresholdEnumCaster, GetFilterUnit(), ACST_ThresholdEnumCastInfo, ACST_ThresholdEnumAbilityInfo, ACST_ThresholdEnumAoeThreshold) == FALSE ) then
                return FALSE
            endif
        endif

        if ( IsUnitAlly(ACST_ThresholdEnumCaster, GetOwningPlayer(GetFilterUnit())) == TRUE ) then
            set ACST_ThresholdEnumCountFriendly = ACST_ThresholdEnumCountFriendly + 1
        else
            set ACST_ThresholdEnumCountEnemy = ACST_ThresholdEnumCountEnemy + 1
        endif

        return FALSE
    endfunction

    // Generic PBAOE Count Units Threshold Calculator
    function ACST_GenericPBAOECountThreshold takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai, real rThreshold returns boolean
        local unit uTemp
        local integer iCount

        set ACST_ThresholdEnumCountIgnored      = 0
        set ACST_ThresholdEnumCountFriendly     = 0
        set ACST_ThresholdEnumCountEnemy        = 0
        set ACST_ThresholdEnumFilter            = ci.filterAoeFunc
        set ACST_ThresholdEnumCaster            = uCaster
        set ACST_ThresholdEnumTarget            = uTarget
        set ACST_ThresholdEnumAbilityInfo       = ai
        set ACST_ThresholdEnumCastInfo          = ci
        set ACST_ThresholdEnumAoeThreshold      = ai.getAoeThreshold(GetUnitAbilityLevel(uCaster, ai.iAbilityId))
        set ACST_ThresholdEnumAoeThresholdFunc  = ci.thresholdAoeFunc

        call GroupClear(ACST_ThresholdEnumGroup)
        call GroupEnumUnitsInRange(ACST_ThresholdEnumGroup, GetUnitX(uTarget), GetUnitY(uTarget), ai.getAreaOfEffect(GetUnitAbilityLevel(uCaster, ai.iAbilityId)), ACST_ThresholdEnumCondition)
           
        set iCount = ACST_ThresholdEnumCountEnemy + ACST_ThresholdEnumCountFriendly
        return iCount >= R2I(rThreshold)
    endfunction

    // Generic Life AOE Count Units Threshold Calculator
    function ACST_GenericAOELifeThreshold takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai, real rThreshold returns boolean
        local unit uTemp
        local integer iCount

        set ACST_ThresholdEnumCountIgnored      = 0
        set ACST_ThresholdEnumCountFriendly     = 0
        set ACST_ThresholdEnumCountEnemy        = 0
        set ACST_ThresholdEnumFilter            = ci.filterAoeFunc
        set ACST_ThresholdEnumCaster            = uTarget
        set ACST_ThresholdEnumTarget            = uTarget
        set ACST_ThresholdEnumAbilityInfo       = ai
        set ACST_ThresholdEnumCastInfo          = ci
        set ACST_ThresholdEnumAoeThreshold      = ai.getAoeThreshold(GetUnitAbilityLevel(uCaster, ai.iAbilityId))
        set ACST_ThresholdEnumAoeThresholdFunc  = ci.thresholdAoeFunc

        call GroupClear(ACST_ThresholdEnumGroup)
        call GroupEnumUnitsInRange(ACST_ThresholdEnumGroup, GetUnitX(uTarget), GetUnitY(uTarget), ai.getAreaOfEffect(GetUnitAbilityLevel(uCaster, ai.iAbilityId)), ACST_ThresholdEnumCondition)
           
        if ( ACST_ThresholdEnumCountIgnored > 0 ) then
            // ignored unit in the area, bounce
            return FALSE
        endif
        set iCount = ACST_ThresholdEnumCountFriendly
        return iCount >= R2I(rThreshold)
    endfunction

    // Generic Level Threshold Calculator
    function ACST_GenericLevelThreshold takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai, real rThreshold returns boolean
        if ( rThreshold >= 0 ) then
            return GetUnitLevel(uTarget) >= R2I(rThreshold)
        else
            return GetUnitLevel(uTarget) <= R2I(-1.0 * rThreshold)
        endif
    endfunction

    // Generic Mana Threshold Calculator
    function ACST_GenericManaThreshold takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai, real rThreshold returns boolean
        if ( rThreshold < 0 and rThreshold >= -1.0 ) then
            // if threshhold is < 0 and a fraction we check if MANA > MAX_MANA * THRESHOLD
            if ( GetUnitState(uTarget, UNIT_STATE_MANA)/GetUnitState(uTarget, UNIT_STATE_MAX_MANA) < (-1.0 * rThreshold) ) then
                return FALSE
            endif
        elseif ( rThreshold < 0 ) then
            // if threshhold is < 0, we check if MANA < MAX_MANA + THRESHOLD
            if ( GetUnitState(uTarget, UNIT_STATE_MANA) > rThreshold + GetUnitState(uTarget, UNIT_STATE_MAX_MANA) ) then
                return FALSE
            endif
        elseif ( rThreshold > 0 and rThreshold <= 1.0 ) then // percentage
            if ( GetUnitState(uTarget, UNIT_STATE_MANA)/GetUnitState(uTarget, UNIT_STATE_MAX_MANA) > rThreshold ) then
                return FALSE
            endif
        elseif ( rThreshold > 0 ) then // probably some damage, though this shouldnt be used often...
            // if threshhold is > 0, we check if MANA > THRESHOLD
            if ( GetUnitState(uTarget, UNIT_STATE_MANA) < rThreshold ) then
                return FALSE
            endif
        endif
        return TRUE
    endfunction

    function ACST_GenericLifeThreshold takes unit uCaster, unit uTarget, ACST_CastInfo ci, ACST_AbilityInfo ai, real rThreshold returns boolean
        if ( rThreshold < 0 and rThreshold >= -1.0 ) then
            // if threshhold is < 0 and a fraction we check if LIFE > MAX_LIFE * THRESHOLD
            if ( GetUnitState(uTarget, UNIT_STATE_LIFE)/GetUnitState(uTarget, UNIT_STATE_MAX_LIFE) < (-1.0 * rThreshold) ) then
                return FALSE
            endif
        elseif ( rThreshold < 0 ) then
            // if threshhold is < 0, we check if LIFE < MAX_LIFE + THRESHOLD
            if ( GetUnitState(uTarget, UNIT_STATE_LIFE) > rThreshold + GetUnitState(uTarget, UNIT_STATE_MAX_LIFE) ) then
                return FALSE
            endif
        elseif ( rThreshold > 0 and rThreshold <= 1.0 ) then // percentage
            if ( GetUnitState(uTarget, UNIT_STATE_LIFE)/GetUnitState(uTarget, UNIT_STATE_MAX_LIFE) > rThreshold ) then
                return FALSE
            endif
        elseif ( rThreshold > 0 ) then // probably some damage, though this shouldnt be used often...
            // if threshhold is > 0, we check if LIFE > THRESHOLD
            if ( GetUnitState(uTarget, UNIT_STATE_LIFE) < rThreshold ) then
                return FALSE
            endif
        endif
        return TRUE
    endfunction

    function AutocastThreshold_Initialize takes nothing returns nothing
        set ACST_ThresholdEnumCondition         = Condition(function ACST_ThresholdEnumFunction)

        set ACST_GenericPBAOECountThresholdFunc = ACST_ThresholdFunction.ACST_GenericPBAOECountThreshold
        set ACST_GenericLevelThresholdFunc      = ACST_ThresholdFunction.ACST_GenericLevelThreshold
        set ACST_GenericLifeThresholdFunc       = ACST_ThresholdFunction.ACST_GenericLifeThreshold
        set ACST_GenericAOELifeThresholdFunc    = ACST_ThresholdFunction.ACST_GenericAOELifeThreshold
        set ACST_GenericManaThresholdFunc       = ACST_ThresholdFunction.ACST_GenericManaThreshold
    endfunction

endlibrary

library AutocastUnitDefaults initializer AutocastUnitDefaults_Initialize requires Containers, AutocastDebug

    struct ACST_AbilityDefaults
        integer                         iAbilityId

        real                            rMinMana
        real                            rMaxMana
        real                            rMinLife
        real                            rMaxLife

        static method create takes integer iAbilityId returns ACST_AbilityDefaults
            local ACST_AbilityDefaults  ad

            set ad              = ACST_AbilityDefaults.allocate()
            set ad.iAbilityId   = iAbilityId

            return ad
        endmethod

    endstruct

    struct ACST_UnitDefaults
        integer                         iUnitTypeId

        integer                         iAbilityCount   = 0
        ACST_AbilityDefaults array      adList[ACST_MaxSpellsPerUnit]

        static IntHash                  IH

        method GetAbilityDefaults takes integer iAbilityId returns ACST_AbilityDefaults
            local integer               iAbility
            local ACST_AbilityDefaults  ad

            set iAbility = 0
            loop
                exitwhen iAbility >= .iAbilityCount

                if ( iAbilityId == .adList[iAbility] ) then
                    return .adList[iAbility]
                endif

                set iAbility = iAbility + 1
            endloop
        
            if ( .iAbilityCount >= .adList.size ) then
    debug       call ACST_DebugMsg(ACST_DebugWarnings, "WARNING: ACST_UnitDefaults::AddUnitDefault Maximum abilities per unit " + GetObjectName(.iUnitTypeId) + " reached")
                return 0
            endif

            set ad = ACST_AbilityDefaults.create(iAbilityId)
            set .adList[.iAbilityCount] = ad
            set .iAbilityCount = .iAbilityCount + 1
            return ad
        endmethod

        static method GetUnitDefaults takes integer iUnitTypeId returns ACST_UnitDefaults
            local ACST_UnitDefaults   ud
            if ( (.IH.has(iUnitTypeId)) == TRUE ) then
                return (.IH.get(iUnitTypeId))
            endif
                
            return 0
        endmethod

        static method create takes integer iUnitTypeId returns ACST_UnitDefaults
            local ACST_UnitDefaults   ud
            if ( (.IH.has(iUnitTypeId)) == TRUE ) then
                return (.IH.get(iUnitTypeId))
            endif
                
            set ud = ACST_UnitDefaults.allocate()

            call .IH.add(iUnitTypeId, ud)
            set ud.iUnitTypeId = iUnitTypeId
            return ud
        endmethod

        method onDestroy takes nothing returns nothing
debug       call ACST_DebugMsg(ACST_DebugInvalidOnDestroy, "WARNING: ACST_UnitDefaults::onDestroy not supported")
            // Remove it from the hash anyway
            call .IH.del(this.iUnitTypeId)
        endmethod
    endstruct

    function AutocastUnitDefaults_Initialize takes nothing returns nothing
        set ACST_UnitDefaults.IH        = IntHash.create()
        call ACST_InitializeUnitDefaults()
    endfunction

endlibrary


library Containers

    function HandleHash_H2I takes handle h returns integer
        return h
        return 0
    endfunction
        
    // hash a Handle to an INT
    struct HandleHash

        // for now we use gamecache as a hash
        static gamecache        GameCache

        method add takes handle key, integer i returns nothing
            call StoreInteger(.GameCache, I2S(this), I2S(HandleHash_H2I(key)), i)
        endmethod

        method has takes handle key returns boolean
            return HaveStoredInteger(.GameCache, I2S(this), I2S(HandleHash_H2I(key)))
        endmethod

        method del takes handle key returns nothing
            call FlushStoredInteger(.GameCache, I2S(this), I2S(HandleHash_H2I(key)))
        endmethod

        method get takes handle key returns integer
            return GetStoredInteger(.GameCache, I2S(this), I2S(HandleHash_H2I(key)))
        endmethod

        method onDestroy takes nothing returns nothing
            call FlushStoredMission(.GameCache, I2S(this))
        endmethod

        static method onInit takes nothing returns nothing
            call FlushGameCache(InitGameCache("handlehash.w3v"))
            set HandleHash.GameCache = InitGameCache("handlehash.w3v")
        endmethod
    endstruct


    struct IntHash

        // for now we use gamecache as a hash
        static gamecache        GameCache

        method operator [] takes integer key returns integer
            return GetStoredInteger(.GameCache, I2S(this), I2S(key))
        endmethod

        method operator []= takes integer key, integer i returns nothing
            call StoreInteger(.GameCache, I2S(this), I2S(key), i)
        endmethod

        method add takes integer key, integer i returns nothing
            call StoreInteger(.GameCache, I2S(this), I2S(key), i)
        endmethod

        method has takes integer key returns boolean
            return HaveStoredInteger(.GameCache, I2S(this), I2S(key))
        endmethod

        method del takes integer key returns nothing
            call FlushStoredInteger(.GameCache, I2S(this), I2S(key))
        endmethod

        method get takes integer key returns integer
            return GetStoredInteger(.GameCache, I2S(this), I2S(key))
        endmethod

        method onDestroy takes nothing returns nothing
            call FlushStoredMission(.GameCache, I2S(this))
        endmethod

        static method onInit takes nothing returns nothing
            call FlushGameCache(InitGameCache("integerhash.w3v"))
            set IntHash.GameCache = InitGameCache("integerhash.w3v")
        endmethod
    endstruct
endlibrary


library ETrigger initializer ETrigger_Initialize requires Containers

    function interface ETriggerAction takes ETrigger i returns nothing
    function interface ETriggerCondition takes ETrigger i returns boolean

    function ETrigger_Action takes nothing returns nothing
        local ETrigger et = ETrigger.HH.get(GetTriggeringTrigger())
        call et.eta.execute(et)
    endfunction

    function ETrigger_Condition takes nothing returns boolean
        local ETrigger et = ETrigger.HH.get(GetTriggeringTrigger())
        return et.etc.evaluate(et)
    endfunction

    struct ETrigger

        // user variables
        player                  plr
        group                   gUnitsInRange

        // system variables
        trigger                 trg
        triggeraction           tra
        triggercondition        trc
        ETriggerAction          eta
        ETriggerCondition       etc

        static HandleHash       HH

        method setCondition takes ETriggerCondition etc returns nothing
            set this.etc = etc
            if ( this.trc == null ) then
                set this.trc = TriggerAddCondition(this.trg, Condition(function ETrigger_Condition))
            endif
        endmethod

        static method create takes ETriggerAction eta returns ETrigger
            local ETrigger      et

            set et              = ETrigger.allocate()
            set et.trg          = CreateTrigger()
            set et.tra          = TriggerAddAction(et.trg, function ETrigger_Action)
            set et.eta          = eta
            set et.trc          = null // no conditions at first
            set et.etc          = 0
            call .HH.add(et.trg, et)
            return et
        endmethod

        method onDestroy takes nothing returns nothing
            // unit variables
            set this.gUnitsInRange      = null
            set this.plr                = null

            // system variables
            call DisableTrigger(this.trg)
            call .HH.del(this.trg)
            call TriggerRemoveAction(this.trg, this.tra)
            if ( this.trc != null ) then
                call TriggerRemoveCondition(this.trg, this.trc)
            endif
            call DestroyTrigger(this.trg)
            set this.trg = null
            set this.tra = null
            set this.trc = null
        endmethod
    endstruct

    function ETrigger_Initialize takes nothing returns nothing
        set ETrigger.HH = HandleHash.create()
    endfunction

endlibrary
01-01-2008, 09:04 PM#3
cohadar
As far as I know maximum level for abilities in WE is 100
01-01-2008, 10:02 PM#4
moyack
Your system is overwhelming rain9441... I'll put my comments for you on IRC
01-02-2008, 03:03 PM#6
Tide-Arc Ephemera
Quote:
Originally Posted by cohadar
As far as I know maximum level for abilities in WE is 100
I've made one which was much, much higher.
01-03-2008, 11:01 AM#7
Malf
Footies 10000 levels.

Ohh, and the name is pretty misleading. I thought this was like, an On-Attack-While-Ability-Is-Autocasted system, if you don't know what I'm talking about, it means that an attack is detected while a unit has an ability auto-casted.