| 01-01-2008, 04:10 PM | #1 |
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
Autocasting Features
Debugging Features
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: 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... |
| 01-01-2008, 04:12 PM | #2 |
JASS Code of system (couldn't fit in original post): 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 |
As far as I know maximum level for abilities in WE is 100 |
| 01-01-2008, 10:02 PM | #4 |
Your system is overwhelming rain9441... I'll put my comments for you on IRC |
| 01-02-2008, 03:03 PM | #6 | |
Quote:
|
| 01-03-2008, 11:01 AM | #7 |
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. |
