Name | Type | is_array | initial_value |
AMA_abilityId | abilcode | No | |
AMA_affectsAllies | boolean | No | |
AMA_affectsEnemies | boolean | No | |
AMA_auraType | integer | No | |
AMA_buffId | buffcode | No | |
AMA_itemSource | item | No | |
AMA_unitSource | unit | No | |
AMB_BERSERK | integer | No | 8 |
AMB_BLOODLUST | integer | No | |
AMB_COLD_ARROWS | integer | No | 11 |
AMB_COMMAND_AURA | integer | No | 1 |
AMB_CRIPPLE | integer | No | 5 |
AMB_ENDURANCE_AURA | integer | No | 2 |
AMB_FROST_ARMOR | integer | No | 10 |
AMB_FROST_NOVA | integer | No | 10 |
AMB_INNER_FIRE | integer | No | 2 |
AMB_POISON_ATTACK | integer | No | 9 |
AMB_ROAR | integer | No | 4 |
AMB_SILENCE | integer | No | 6 |
AMB_SLOW | integer | No | 1 |
AMB_SOULBURN | integer | No | 7 |
AMB_THUNDERCLAP | integer | No | 12 |
AMB_TRUESHOT_AURA | integer | No | |
AMB_UNHOLY_FRENZY | integer | No | 3 |
AMS_abilityId | abilcode | No | |
AMS_buffId | buffcode | No | |
AMS_source | unit | No | |
AMS_spellType | integer | No | |
AMS_target | unit | No | |
BUFF_CHECK_INTERVAL | real | No | 0.20 |
CLEAN_UP_ON_DEATH | boolean | No | true |
ENABLE_CAST_DETECTION | boolean | No | true |
IGNORE_NONHERO_UNITS | boolean | No | |
printoutString | integer | No | |
TIMESTEP_CHECK | real | No | 0.05 |
TOA_attacker | unit | No | |
TOA_callback | trigger | No | |
TOA_isStacking | boolean | No | |
TOA_procChance | real | No | |
TOA_target | unit | No | |
TOA_unit | unit | No | |
UAS_ATTACK_SPEED_PER_AGILITY | real | No | 0.02 |
UAS_attackCooldown | real | No | |
UAS_attackDelay | real | No | |
UAS_attackSpeedBonus | real | No | |
UAS_CHECK_AURAS | boolean | No | true |
UAS_CHECK_BUFFS | boolean | No | true |
UAS_CHECK_ITEMS | boolean | No | true |
UAS_damage | real | No | |
UAS_dps | real | No | |
UAS_itemAbilities | abilcode | Yes | |
UAS_unit | unit | No | |
UAS_weaponIndex | integer | No | 1 |
/**************************************************************************************************
T R I G G E R O N A T T A C K
U N I T A T T A C K S T A T S
&
A T T A C K M O D B U F F S
v1.1
created by Antares
***************************************************************************************************
CHANGE LOG:
v.1.1:
-Added ability to change whether an aura is affecting allies and/or enemies.
-Added Thunderclap to RegisterCast.
-Made a function for the Interrupt condition that can be customized.
These three libraries do different tasks related to units' auto-attacks.
***************************************************************************************************
TriggerOnAttack is designed to accurately determine the point when a ranged hero launches an attack
projectile and easily set up effects that trigger on that event. It requires UnitAttackStats to
calculate the delay between the beginning of the attack to the projectile launch point. It provides
an alternative to using invisible, instantaneous projectiles and using a damage event to trigger
the actual attack.
UnitAttackStats can calculate various stats around a unit's auto-attack, including attack delay,
used by TriggerOnAttack. This library can also be used to simply give players the option to display
their attack cooldown or dps. UnitAttackStats requires AttackModBuffs to give an accurate result if
attack speed and damage modifying buffs and auras are present, but it will function without it.
AttackModBuffs is a library designed to track attack speed and damage modifying buffs and auras. It
requires you to declare every such buff and aura in your map, but makes it maximally convenient to
do so.
***************************************************************************************************
H O W T O G E T S T A R T E D :
Make sure that in your editor under File -> Preferences, the flag "Automatically create unknown
variables when pasting trigger data" is set.
===================================================================================================
TriggerOnAttack
===================================================================================================
Copy the TriggerOnAttack folder into your map. There are two parameters you can customize:
TIMESTEP_CHECK determines the accuracy of the On-Attack-Effect.
If CLEAN_UP_ON_DEATH is enabled, it will clean-up everything upon death of a nonhero unit. If disabled,
you can do so manually with the UnitClearAllAttackActions trigger.
To create an On-Attack-Effect, set the TOA_ variables, then run the AddUnitAttackAction trigger.
The proc chance must be between 0 and 1.
TOA_callback is the trigger that is executed when the unit attacks. Use the variables TOA_attacker
and TOA_target to refer to the attacking/attacked unit in your trigger.
Examples of how to set it up is given in the Test Map.
===================================================================================================
UnitAttackStats
===================================================================================================
Copy the UnitAttackStats folder into your map. Set the ATTACK_SPEED_PER_AGILITY variable to the
value of your map. If your map does not have attack speed and damage modifying buffs, auras, and/or
items, you can disable those checks by setting UAS_CHECK_X to false.
For item checks to work, you need to declare all item abilities based on Item Damage Bonus, Item
Attack Speed Bonus, and Orb Abilities (such as Item Attack Fire Bonus). To do this, list all item
damage abilities in the UAS_itemAbilities array variable, then run the CreateAttackDamageItemAbilities
trigger, then repeat for item attack speed abilities.
Examples of those declarations are given in the Test script.
Note that items that grant auras are not handled by this library, but by AttackModBuffs.
To get the attack damage/cooldown etc. of a unit, set the UAS_unit variable and the UAS_weaponIndex
variable (1 or 2), then run the appropriate trigger.
===================================================================================================
AttackModBuffs
===================================================================================================
Copy the AttackModBuffs folder into your map. There are three parameters to customize:
ENABLE_CAST_DETECTION will allow you to declare spells that apply an attack speed or damage modifying
buff. The library will detect those spells being cast and track the buff automatically. These spells
are declared by setting the AMS_ variables, then running CreateAttackModSpells. For AMS_spellTypes,
use the Spell Type Constants listed, such as AMB_BLOODLUST.
If IGNORE_NONHERO_UNITS is enabled, only buffs on heroes are tracked. Chances are, you don't care
about the exact attack speed of a random footman.
BUFF_CHECK_INTERVAL sets the interval at which the expiration of the buff is checked.
Cast Detection will not work on all spells, but not on Poison attack, Cold Arrows, and Frost Armor.
Note that this library cannot extract the correct attack speed and damage modifiers from buffs
if they weren't detected as they were applied.
Cast Detection will not work on all spells. Poison attack, Cold Arrows, Thunderclap, and Frost Armor
have to be applied manually. This is done by running the AddAttackModifyingBuff trigger. Before you
run it, you have to set AMS_target, AMS_source, AMS_abilityId, AMS_buffId, and AMS_spellType.
The tracking of auras works a bit differently. Auras have to be declared on a per-source-basis
(sources can be either units or items). To do this, set the AMA_ variables, then run the
CreateAttackModAura trigger.
To extract the correct modifyers, this library will search for the friendly source with the highest
level of the aura in range.
Setting up most buffs is super easy, barely an inconvenience. If you are fine with less than 100%
accuracy, you can ignore all other buffs and simply focus on the easy or important ones.
***************************************************************************************************
A P I :
===================================================================================================
Attack Mod Buffs
===================================================================================================
CreateAttackModSpells
CreateAttackModAura
AddAttackModifyingBuff
===================================================================================================
Unit Attack Stats
===================================================================================================
GetUnitAttackDamage
GetUnitAttackSpeedBonus
GetUnitAttackCooldown
GetUnitAttackDelay
GetUnitDPS
CreateAttackSpeedItemAbilities
CreateAttackDamageItemAbilities
===================================================================================================
Trigger On Attack
===================================================================================================
AddUnitAttackAction
RemoveUnitAttackAction
UnitClearAllAttackActions
***************************************************************************************************/
library AttackModBuffs initializer Init
globals
private hashtable buffTable = InitHashtable()
private constant abilityreallevelfield DATA_A = ABILITY_RLF_DEFENSE_BONUS_HAV1
private constant abilityreallevelfield DATA_B = ABILITY_RLF_HIT_POINT_BONUS
private constant abilityreallevelfield DATA_C = ABILITY_RLF_DAMAGE_BONUS_HAV3
private constant abilityreallevelfield DATA_D = ABILITY_RLF_MAGIC_DAMAGE_REDUCTION_HAV4
private constant abilityreallevelfield DATA_E = ABILITY_RLF_MAX_DAMAGE_UCO5
private abilityreallevelfield array attackSpeedWhichField
private abilityreallevelfield array attackDamageWhichField
private integer array dataSign
private unit whichCaster
private attackModSpell whichAms
endglobals
private function ThunderclapFilter takes nothing returns boolean
//=========================================================================================================
//Modify this function to properly account for the targets allowed in your Thunderclap abilities. Reference
//the casting unit with whichCaster and whichAms.abilityId for the abilityId. If you have several Thunderclaps
//with different targets allowed, make different conditions for different abilityIds.
//=========================================================================================================
return IsUnitEnemy( GetFilterUnit() , GetOwningPlayer(whichCaster) )
endfunction
struct attackModSpell
integer abilityId
integer buffId
integer spellType
static attackModSpell array list
static integer listSize = 0
static method create takes integer abilityId, integer buffId, integer spellType returns attackModSpell
local attackModSpell ams
local integer A = 1
loop
exitwhen A > listSize
if list[A].abilityId == abilityId then
return 0
endif
set A = A + 1
endloop
set ams = attackModSpell.allocate()
set ams.abilityId = abilityId
set ams.buffId = buffId
set ams.spellType = spellType
set listSize = listSize + 1
set list[listSize] = ams
return ams
endmethod
endstruct
struct attackModAura
//=========================================================================================================
//All abilities that are the source of auras. Must be created for each source individually. Source can be
//a unit or an item. Set the other argument to null.
//=========================================================================================================
integer abilityId
integer buffId
integer auraType
unit unitSource
item itemSource
boolean affectsAllies
boolean affectsEnemies
integer listIndex
static attackModAura array list
static integer listSize = 0
static method create takes integer abilityId, integer buffId, integer auraType, unit unitSource, item itemSource, boolean affectsAllies, boolean affectsEnemies returns attackModAura
local attackModAura ama
local integer A = 1
local trigger trig
set ama = attackModAura.allocate()
set ama.abilityId = abilityId
set ama.buffId = buffId
set ama.auraType = auraType
set ama.unitSource = unitSource
set ama.itemSource = itemSource
set ama.affectsAllies = affectsAllies
set ama.affectsEnemies = affectsEnemies
call auraBuff.create(buffId,auraType)
set listSize = listSize + 1
set list[listSize] = ama
set ama.listIndex = listSize
if unitSource != null and not IsUnitType(unitSource , UNIT_TYPE_HERO) then
set trig = CreateTrigger()
call TriggerRegisterUnitEvent( trig , unitSource , EVENT_UNIT_DEATH )
call TriggerAddAction( trig , function attackModAura.OnDeath )
call SaveInteger( buffTable , GetHandleId(trig) , 0 , ama )
set trig = null
endif
return ama
endmethod
private static method OnDeath takes nothing returns nothing
local trigger trig = GetTriggeringTrigger()
local attackModAura ama = LoadInteger( buffTable , GetHandleId(trig) , 0 )
call ama.destroy()
call FlushChildHashtable( buffTable , GetHandleId(trig) )
call DestroyTrigger(trig)
set trig = null
endmethod
method onDestroy takes nothing returns nothing
if .listIndex < listSize then
set list[.listIndex] = list[listSize]
set list[.listIndex].listIndex = .listIndex
set list[listSize] = 0
endif
set listSize = listSize - 1
endmethod
endstruct
struct auraBuff
//=========================================================================================================
//All buffs that are granted by auras. Automatically generated by attackModAura.create(). Not part of the API!
//=========================================================================================================
integer buffId
integer auraType
static auraBuff array list
static integer listSize = 0
static method create takes integer buffId, integer auraType returns auraBuff
local auraBuff ab
local integer A = 1
loop
exitwhen A > listSize
if list[A].buffId == buffId then
return 0
endif
set A = A + 1
endloop
set ab = auraBuff.allocate()
set ab.buffId = buffId
set ab.auraType = auraType
set listSize = listSize + 1
set list[listSize] = ab
return ab
endmethod
endstruct
function SetAttackDamagePercentBuffs takes unit whichUnit, real attackDamage returns nothing
call SaveReal( buffTable , GetHandleId(whichUnit) , 2 , attackDamage )
endfunction
function SetAttackSpeedPercentBuffs takes unit whichUnit, real attackSpeed returns nothing
call SaveReal( buffTable , GetHandleId(whichUnit) , 1 , attackSpeed )
endfunction
function GetAttackDamagePercentBuffs takes unit whichUnit returns real
return LoadReal( buffTable , GetHandleId(whichUnit) , 2 )
endfunction
function GetAttackSpeedPercentBuffs takes unit whichUnit returns real
return LoadReal( buffTable , GetHandleId(whichUnit) , 1 )
endfunction
function GetAttackDamagePercentAuras takes unit whichUnit returns real
local integer A = 1
local integer B
local attackModAura ama
local auraBuff ab
local real attackDamageBonus = 0
local real dist
local attackModAura highestAma = 0
local integer highestLevel
local integer level
loop
exitwhen A > auraBuff.listSize
set ab = auraBuff.list[A]
set highestAma = 0
set highestLevel = 0
if GetUnitAbilityLevel( whichUnit , ab.buffId ) > 0 and ab.auraType != udg_AMB_ENDURANCE_AURA then
set B = 1
loop
exitwhen B > attackModAura.listSize
set ama = attackModAura.list[B]
if ama.auraType == ab.auraType then
if ama.unitSource != null and IsUnitAliveBJ(ama.unitSource) and ((IsUnitAlly(ama.unitSource , GetOwningPlayer(whichUnit)) and ama.affectsAllies) or (IsUnitEnemy(ama.unitSource , GetOwningPlayer(whichUnit)) and ama.affectsEnemies)) then
set dist = SquareRoot((GetUnitX(ama.unitSource) - GetUnitX(whichUnit))*(GetUnitX(ama.unitSource) - GetUnitX(whichUnit)) + (GetUnitY(ama.unitSource) - GetUnitY(whichUnit))*(GetUnitY(ama.unitSource) - GetUnitY(whichUnit)))
set level = GetUnitAbilityLevel(ama.unitSource , ama.abilityId)
if dist < BlzGetAbilityRealLevelField( BlzGetUnitAbility( ama.unitSource , ama.abilityId ) , ABILITY_RLF_AREA_OF_EFFECT , level - 1 ) and level > highestLevel then
set highestLevel = level
set highestAma = ama
endif
elseif ama.itemSource != null then
if highestLevel == 0 then
set highestLevel = 1
set highestAma = ama
endif
endif
endif
set B = B + 1
endloop
if highestAma != 0 then
if highestAma.unitSource != null then
set attackDamageBonus = attackDamageBonus + BlzGetAbilityRealLevelField( BlzGetUnitAbility(highestAma.unitSource , highestAma.abilityId) , DATA_A , highestLevel - 1 )
else
set attackDamageBonus = attackDamageBonus + BlzGetAbilityRealLevelField( BlzGetItemAbility(highestAma.itemSource , highestAma.abilityId) , DATA_A , 0 )
endif
endif
endif
set A = A + 1
endloop
return attackDamageBonus
endfunction
function GetAttackSpeedPercentAuras takes unit whichUnit returns real
local integer A = 1
local integer B
local attackModAura ama
local auraBuff ab
local real attackSpeedBonus = 0
local real dist
local attackModAura highestAma = 0
local integer highestLevel
local integer level
loop
exitwhen A > auraBuff.listSize
set ab = auraBuff.list[A]
set highestAma = 0
set highestLevel = 0
if GetUnitAbilityLevel( whichUnit , ab.buffId ) > 0 and ab.auraType == udg_AMB_ENDURANCE_AURA then
set B = 1
loop
exitwhen B > attackModAura.listSize
set ama = attackModAura.list[B]
if ama.auraType == ab.auraType then
if ama.unitSource != null and IsUnitAliveBJ(ama.unitSource) and ((IsUnitAlly(ama.unitSource , GetOwningPlayer(whichUnit)) and ama.affectsAllies) or (IsUnitEnemy(ama.unitSource , GetOwningPlayer(whichUnit)) and ama.affectsEnemies)) then
set dist = SquareRoot((GetUnitX(ama.unitSource) - GetUnitX(whichUnit))*(GetUnitX(ama.unitSource) - GetUnitX(whichUnit)) + (GetUnitY(ama.unitSource) - GetUnitY(whichUnit))*(GetUnitY(ama.unitSource) - GetUnitY(whichUnit)))
set level = GetUnitAbilityLevel(ama.unitSource , ama.abilityId)
if dist < BlzGetAbilityRealLevelField( BlzGetUnitAbility( ama.unitSource , ama.abilityId ) , ABILITY_RLF_AREA_OF_EFFECT , level - 1 ) and level > highestLevel then
set highestLevel = level
set highestAma = ama
endif
elseif ama.itemSource != null then
if highestLevel == 0 then
set highestLevel = 1
set highestAma = ama
endif
exitwhen true
endif
endif
set B = B + 1
endloop
if highestAma != 0 then
if highestAma.unitSource != null then
set attackSpeedBonus = attackSpeedBonus + BlzGetAbilityRealLevelField( BlzGetUnitAbility(highestAma.unitSource , highestAma.abilityId) , DATA_B , highestLevel - 1 )
else
set attackSpeedBonus = attackSpeedBonus + BlzGetAbilityRealLevelField( BlzGetItemAbility(highestAma.itemSource , highestAma.abilityId) , DATA_B , 0 )
endif
endif
endif
set A = A + 1
endloop
return attackSpeedBonus
endfunction
private function AttackModifyingBuffCheck takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer Ht = GetHandleId(t)
local unit whichUnit = LoadUnitHandle( buffTable , Ht , 1 )
local integer buffId
local real attackSpeed
local real attackDamage
if IsUnitAliveBJ(whichUnit) then
set buffId = LoadInteger( buffTable , Ht , 0 )
if GetUnitAbilityLevel( whichUnit , buffId ) == 0 then
set attackSpeed = GetAttackSpeedPercentBuffs(whichUnit) - LoadReal(buffTable, Ht, 2)
set attackDamage = GetAttackDamagePercentBuffs(whichUnit) - LoadReal(buffTable, Ht, 3)
if attackSpeed == 0 and attackDamage == 0 then
call FlushChildHashtable( buffTable , GetHandleId(whichUnit) )
else
call RemoveSavedHandle( buffTable , GetHandleId(whichUnit) , buffId )
call SetAttackSpeedPercentBuffs( whichUnit , attackSpeed )
call SetAttackDamagePercentBuffs( whichUnit , attackDamage )
endif
call FlushChildHashtable( buffTable , Ht )
call DestroyTimer(t)
endif
else
call FlushChildHashtable( buffTable , GetHandleId(whichUnit) )
call FlushChildHashtable( buffTable , Ht )
call DestroyTimer(t)
endif
set t = null
endfunction
function AddAttackModifyingBuff takes unit whichUnit, unit source, integer abilityId, integer buffId, integer spellType returns nothing
local timer t
local integer Ht
local integer Hu = GetHandleId(whichUnit)
local ability abi
local real attackSpeed
local real attackDamage
local real oldAttackSpeed = 0
local real oldAttackDamage = 0
local integer level
if udg_IGNORE_NONHERO_UNITS then
if not IsUnitType(whichUnit , UNIT_TYPE_HERO) then
return
endif
endif
if GetUnitAbilityLevel( whichUnit , buffId ) > 0 and HaveSavedHandle( buffTable , Hu , buffId ) then
set t = LoadTimerHandle( buffTable , Hu , buffId )
set Ht = GetHandleId(t)
set oldAttackSpeed = LoadReal( buffTable , Ht , 2 )
set oldAttackDamage = LoadReal( buffTable , Ht , 3 )
else
set t = CreateTimer()
set Ht = GetHandleId(t)
call TimerStart( t , udg_BUFF_CHECK_INTERVAL , true , function AttackModifyingBuffCheck )
call SaveTimerHandle( buffTable , Hu , buffId , t )
call SaveInteger( buffTable , Ht , 0 , buffId )
call SaveUnitHandle( buffTable , Ht , 1 , whichUnit )
endif
set abi = BlzGetUnitAbility(source,abilityId)
set level = GetUnitAbilityLevel(source,abilityId)
if spellType == udg_AMB_FROST_NOVA then
set attackSpeed = -0.25
set attackDamage = 0
else
if attackSpeedWhichField[spellType] != null then
set attackSpeed = dataSign[spellType] * BlzGetAbilityRealLevelField( abi , attackSpeedWhichField[spellType] , level-1 )
else
set attackSpeed = 0
endif
if attackDamageWhichField[spellType] != null then
set attackDamage = dataSign[spellType] * BlzGetAbilityRealLevelField( abi , attackDamageWhichField[spellType] , level-1 )
else
set attackDamage = 0
endif
endif
if attackSpeed != oldAttackSpeed then
call SetAttackSpeedPercentBuffs(whichUnit , GetAttackSpeedPercentBuffs(whichUnit) + attackSpeed - oldAttackSpeed )
call SaveReal( buffTable , Ht , 2 , attackSpeed )
endif
if attackDamage != oldAttackDamage then
call SetAttackDamagePercentBuffs(whichUnit , GetAttackDamagePercentBuffs(whichUnit) + attackDamage - oldAttackDamage )
call SaveReal( buffTable , Ht , 3 , attackDamage )
endif
set t = null
endfunction
private function ThunderclapCallback takes nothing returns nothing
call AddAttackModifyingBuff( GetEnumUnit() , whichCaster , whichAms.abilityId , whichAms.buffId , whichAms.spellType )
endfunction
private function RegisterCast takes nothing returns nothing
local integer abilityId = GetSpellAbilityId()
local unit caster
local integer A = 1
local attackModSpell ams
local group G
loop
exitwhen A > attackModSpell.listSize
if abilityId == attackModSpell.list[A].abilityId then
set caster = GetSpellAbilityUnit()
set ams = attackModSpell.list[A]
if ams.spellType == udg_AMB_BERSERK then
call AddAttackModifyingBuff(caster, caster, abilityId, ams.buffId, ams.spellType)
elseif ams.spellType == udg_AMB_THUNDERCLAP then
set G = CreateGroup()
set whichCaster = caster
set whichAms = ams
call GroupEnumUnitsInRange(G , GetUnitX(caster) , GetUnitY(caster) , BlzGetAbilityRealLevelField(BlzGetUnitAbility(caster , abilityId) , ABILITY_RLF_AREA_OF_EFFECT , GetUnitAbilityLevel(caster , abilityId) - 1 ) , function ThunderclapFilter)
call ForGroup(G , function ThunderclapCallback )
call DestroyGroup(G)
set G = null
else
call AddAttackModifyingBuff(GetSpellTargetUnit(), caster, abilityId, ams.buffId, ams.spellType)
endif
exitwhen true
endif
set A = A + 1
endloop
set caster = null
endfunction
private function At0s takes nothing returns nothing
local trigger trig
if udg_ENABLE_CAST_DETECTION then
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( trig, function RegisterCast )
set trig = null
endif
call DestroyTimer(GetExpiredTimer())
endfunction
private function Init takes nothing returns nothing
set attackSpeedWhichField[0] = DATA_A
set attackDamageWhichField[0] = null
set dataSign[0] = 1
set attackSpeedWhichField[1] = DATA_B
set attackDamageWhichField[1] = null
set dataSign[1] = -1
set attackSpeedWhichField[2] = null
set attackDamageWhichField[2] = DATA_A
set dataSign[2] = 1
set attackSpeedWhichField[3] = DATA_A
set attackDamageWhichField[3] = null
set dataSign[3] = 1
set attackSpeedWhichField[4] = null
set attackDamageWhichField[4] = DATA_A
set dataSign[4] = 1
set attackSpeedWhichField[5] = DATA_B
set attackDamageWhichField[5] = DATA_C
set dataSign[5] = -1
set attackSpeedWhichField[6] = DATA_D
set attackDamageWhichField[6] = null
set dataSign[6] = -1
set attackSpeedWhichField[7] = DATA_E
set attackDamageWhichField[7] = DATA_C
set dataSign[7] = -1
set attackSpeedWhichField[8] = DATA_B
set attackDamageWhichField[8] = null
set dataSign[8] = 1
set attackSpeedWhichField[9] = DATA_C
set attackDamageWhichField[9] = null
set dataSign[9] = -1
set attackSpeedWhichField[11] = DATA_C
set attackDamageWhichField[11] = null
set dataSign[11] = -1
set attackSpeedWhichField[12] = DATA_D
set attackDamageWhichField[12] = null
set dataSign[12] = -1
call TimerStart(CreateTimer(), 0.0, false, function At0s)
endfunction
endlibrary
library UnitAttackStats requires optional AttackModBuffs
globals
private constant abilityintegerlevelfield DATA_ATTACK_DAMAGE = ABILITY_ILF_NUMBER_OF_WAVES
private constant abilityreallevelfield DATA_ATTACK_SPEED = ABILITY_RLF_DEFENSE_BONUS_HAV1
endglobals
struct attackDamageItemAbility
integer abilityId
static attackDamageItemAbility array list
static integer listSize = 0
static method create takes integer abilityId returns attackDamageItemAbility
local attackDamageItemAbility adia
local integer A = 1
loop
exitwhen A > listSize
if list[A].abilityId == abilityId then
return 0
endif
set A = A + 1
endloop
set adia = attackDamageItemAbility.allocate()
set adia.abilityId = abilityId
set listSize = listSize + 1
set list[listSize] = adia
return adia
endmethod
endstruct
struct attackSpeedItemAbility
integer abilityId
static attackSpeedItemAbility array list
static integer listSize = 0
static method create takes integer abilityId returns attackSpeedItemAbility
local attackSpeedItemAbility asia
local integer A = 1
loop
exitwhen A > listSize
if list[A].abilityId == abilityId then
return 0
endif
set A = A + 1
endloop
set asia = attackSpeedItemAbility.allocate()
set asia.abilityId = abilityId
set listSize = listSize + 1
set list[listSize] = asia
return asia
endmethod
endstruct
function GetUnitAttackDamage takes unit whichUnit, integer weaponIndex returns real
local integer A = 0
local integer B
local integer C
local integer baseDamage = BlzGetUnitBaseDamage(whichUnit,weaponIndex)
local real diceDamage = BlzGetUnitDiceNumber(whichUnit,weaponIndex)*I2R( 1+ BlzGetUnitDiceSides(whichUnit,weaponIndex))/2
local integer flatDmgBonus = 0
local item tempItem
local ability itemAbi
local integer id
local real damageModPercent = 0
static if LIBRARY_AttackModBuffs then
if udg_UAS_CHECK_BUFFS then
set damageModPercent = damageModPercent + GetAttackDamagePercentBuffs(whichUnit)
endif
if udg_UAS_CHECK_AURAS then
set damageModPercent = damageModPercent + GetAttackDamagePercentAuras(whichUnit)
endif
endif
if IsHeroUnitId(GetUnitTypeId(whichUnit)) then
loop
exitwhen A > 5
set tempItem = UnitItemInSlot(whichUnit,A)
if tempItem != null then
set B = 0
loop
exitwhen B > 3
set itemAbi = BlzGetItemAbilityByIndex(tempItem,B)
set id = BlzGetAbilityId(itemAbi)
set C = 1
loop
exitwhen C > attackDamageItemAbility.listSize
if id == attackDamageItemAbility.list[C].abilityId then
set flatDmgBonus = flatDmgBonus + BlzGetAbilityIntegerLevelField( itemAbi , DATA_ATTACK_DAMAGE , 0 )
exitwhen true
endif
set C = C + 1
endloop
set B = B + 1
endloop
endif
set A = A + 1
endloop
endif
//============================================================
//Add additional damage bonus abilities here.
//============================================================
//set flatDmgBonus = flatDmgBonus + <Attack damage bonus abilities based on Item Damage Bonus>.
set baseDamage = R2I(baseDamage + (baseDamage+diceDamage)*damageModPercent + flatDmgBonus + 0.5)
return baseDamage + diceDamage
endfunction
function GetUnitAttackSpeedBonus takes unit whichUnit returns real
local integer A = 0
local integer B
local integer C
local real attackSpeedBonusItems = 0
local real attackSpeedBonusAgility = 0
local real attackSpeedBonusBuffs = 0
local real attackSpeedBonus = 0
local item tempItem
local ability itemAbi
local integer id
static if LIBRARY_AttackModBuffs then
if udg_UAS_CHECK_BUFFS then
set attackSpeedBonusBuffs = attackSpeedBonusBuffs + GetAttackSpeedPercentBuffs(whichUnit)
endif
if udg_UAS_CHECK_AURAS then
set attackSpeedBonusBuffs = attackSpeedBonusBuffs + GetAttackSpeedPercentAuras(whichUnit)
endif
endif
if udg_UAS_CHECK_ITEMS then
if IsHeroUnitId(GetUnitTypeId(whichUnit)) then
set attackSpeedBonusAgility = udg_UAS_ATTACK_SPEED_PER_AGILITY*GetHeroAgi( whichUnit , true )
loop
exitwhen A > 5
set tempItem = UnitItemInSlot(whichUnit,A)
if tempItem != null then
set B = 0
loop
exitwhen B > 3
set itemAbi = BlzGetItemAbilityByIndex(tempItem,B)
if itemAbi != null then
set id = BlzGetAbilityId(itemAbi)
set C = 1
loop
exitwhen C > attackSpeedItemAbility.listSize
if id == attackSpeedItemAbility.list[C].abilityId then
set attackSpeedBonusItems = attackSpeedBonusItems + BlzGetAbilityRealLevelField( itemAbi , DATA_ATTACK_SPEED , 0 )
exitwhen true
endif
set C = C + 1
endloop
endif
set B = B + 1
endloop
endif
set A = A + 1
endloop
endif
endif
set attackSpeedBonus = attackSpeedBonusBuffs + attackSpeedBonusItems + attackSpeedBonusAgility
if attackSpeedBonus < -0.8 then
return -0.8
elseif attackSpeedBonus > 4.0 then
return 4.0
else
return attackSpeedBonus
endif
endfunction
function GetUnitAttackDelay takes unit whichUnit, integer weaponIndex returns real
return BlzGetUnitWeaponRealField(whichUnit , UNIT_WEAPON_RF_ATTACK_DAMAGE_POINT , weaponIndex) / (1 + GetUnitAttackSpeedBonus(whichUnit))
endfunction
function GetUnitAttackCooldown takes unit whichUnit, integer weaponIndex returns real
return BlzGetUnitAttackCooldown(whichUnit,weaponIndex) / (1 + GetUnitAttackSpeedBonus(whichUnit))
endfunction
function GetUnitDPS takes unit whichUnit, integer weaponIndex returns real
return GetUnitAttackDamage(whichUnit,weaponIndex) * (1 + GetUnitAttackSpeedBonus(whichUnit)) / BlzGetUnitAttackCooldown(whichUnit,weaponIndex)
endfunction
endlibrary
library TriggerOnAttack requires optional UnitAttackStats
globals
private hashtable multiTable = InitHashtable()
endglobals
private function interface triggerOnAttack takes unit whichUnit, unit whichTarget returns nothing
private function InterruptCondition takes unit orderedUnit, integer whichOrderId, unit targetUnit, unit attackTarget returns boolean
//=========================================================================================================
//If a command is given that isn't "attack" or "smart" or the target of the command isn't the attacked unit,
//the attack will be interrupted. Customize this condition to better align it to your map.
//=========================================================================================================
return (whichOrderId != 851983 and whichOrderId != 851971) or targetUnit != attackTarget
endfunction
private function OnDeath takes nothing returns nothing
local unit whichUnit = GetTriggerUnit()
local integer Hu = GetHandleId(whichUnit)
local integer numberOfAttackActions = LoadInteger( multiTable , Hu , 0 )
local trigger trig
local integer A = 4
local integer Ht
//=========================================================================================================
//On death, destroy all Attack Actions, Interrupt trigger and OnDeath trigger. Only on nonhero units. If a
//nonherounit is revived, the Attack Actions must be added again.
//=========================================================================================================
loop
exitwhen A > numberOfAttackActions + 3
set trig = LoadTriggerHandle( multiTable , Hu , A )
set Ht = GetHandleId(trig)
call DestroyTimer(LoadTimerHandle( multiTable , Ht , 1 ))
call FlushChildHashtable( multiTable , Ht )
call DestroyTrigger(trig)
set A = A + 1
endloop
call DestroyTrigger(LoadTriggerHandle( multiTable , Hu , 1 ))
call DestroyTrigger(LoadTriggerHandle( multiTable , Hu , 2 ))
call FlushChildHashtable( multiTable , Hu )
set whichUnit = null
set trig = null
endfunction
private function Interrupt takes nothing returns nothing
local unit whichUnit = GetOrderedUnit()
local integer Hu = GetHandleId(whichUnit)
local unit whichTarget = LoadUnitHandle( multiTable , Hu , 3 )
local integer numberOfAttackActions
local integer A = 4
local trigger trig
if InterruptCondition(whichUnit,GetIssuedOrderId(),GetOrderTargetUnit(),whichTarget) then
call DisableTrigger(GetTriggeringTrigger())
set numberOfAttackActions = LoadInteger( multiTable , Hu , 0 )
loop
exitwhen A > numberOfAttackActions + 3
set trig = LoadTriggerHandle( multiTable , Hu , A )
call PauseTimer( LoadTimerHandle( multiTable , GetHandleId(trig) , 1 ) )
set A = A + 1
endloop
endif
set whichUnit = null
set whichTarget = null
set trig = null
endfunction
private function Check takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer H = GetHandleId(t)
local integer counter = LoadInteger( multiTable , H , 0 ) + 1
local integer steps = LoadInteger( multiTable , H , 1 )
local unit whichUnit = LoadUnitHandle( multiTable , H , 2 )
local unit whichTarget
local trigger whichCallback
local real x = LoadReal( multiTable , H , 5 )
local real y = LoadReal( multiTable , H , 6 )
if counter == steps then
//=========================================================================================================
//Execute Attack Action, then disable Interrupt trigger.
//=========================================================================================================
set whichTarget = LoadUnitHandle( multiTable , GetHandleId(whichUnit) , 3 )
set whichCallback = LoadTriggerHandle( multiTable , H , 4 )
set udg_TOA_attacker = whichUnit
set udg_TOA_target = whichTarget
call TriggerExecute(whichCallback)
call FlushChildHashtable( multiTable , H )
call DisableTrigger( LoadTriggerHandle( multiTable , GetHandleId(whichUnit) , 2 ) )
call PauseTimer(t)
elseif x == GetUnitX(whichUnit) and y == GetUnitY(whichUnit) then
//=========================================================================================================
//Check if unit has moved. If so, the attack was interrupted.
//=========================================================================================================
call SaveInteger( multiTable , H , 0 , counter )
else
//=========================================================================================================
//Unit has moved. Interrupt check.
//=========================================================================================================
call FlushChildHashtable( multiTable , H )
call PauseTimer(t)
endif
set whichUnit = null
set whichTarget = null
endfunction
private function OnAttack takes nothing returns nothing
//=========================================================================================================
//Registered beginning of attack. Wait for attack.
//=========================================================================================================
local unit whichUnit = GetTriggerUnit()
local unit whichTarget = GetEventTargetUnit()
local trigger trig = GetTriggeringTrigger()
local integer Ht = GetHandleId(trig)
local timer t = LoadTimerHandle( multiTable , Ht , 1 )
local real procChance = LoadReal( multiTable , Ht , 2 )
local integer steps
local integer H = GetHandleId(t)
local integer Hu
if procChance == 1 or GetRandomReal(0,1) < procChance then
static if LIBRARY_UnitAttackStats then
set steps = R2I(GetUnitAttackDelay(whichUnit,0)/udg_TIMESTEP_CHECK) + 1 //Time until projectile is launched.
else
set steps = R2I(BlzGetUnitWeaponRealField(whichUnit , UNIT_WEAPON_RF_ATTACK_DAMAGE_POINT , 0)/udg_TIMESTEP_CHECK) + 1
endif
set Hu = GetHandleId(whichUnit)
call EnableTrigger(LoadTriggerHandle( multiTable , Hu , 2 )) //Interrupt trigger.
call SaveUnitHandle( multiTable , Hu , 3 , whichTarget )
call TimerStart( t , udg_TIMESTEP_CHECK , true , function Check )
call SaveInteger( multiTable , H , 0 , 0 ) //counter
call SaveInteger( multiTable , H , 1 , steps )
call SaveUnitHandle( multiTable , H , 2 , whichUnit )
call SaveTriggerHandle( multiTable , H , 4 , LoadTriggerHandle( multiTable , Ht , 0 ) ) //callback
call SaveReal( multiTable , H , 5 , GetUnitX(whichUnit) )
call SaveReal( multiTable , H , 6 , GetUnitY(whichUnit) )
endif
set whichUnit = null
set whichTarget = null
set trig = null
endfunction
function AddUnitAttackAction takes unit whichUnit, trigger whichCallback, real procChance, boolean isStacking returns nothing
local trigger trig
local timer t
local integer Ht
local integer Hu = GetHandleId(whichUnit)
local integer numberOfAttackActions = LoadInteger( multiTable , Hu , 0 )
local integer A
local integer L
local trigger thisTriggerCallback
if not isStacking then
set A = 4
set L = numberOfAttackActions + 3
loop
exitwhen A > L
set trig = LoadTriggerHandle( multiTable , Hu , A )
set Ht = GetHandleId(trig)
set thisTriggerCallback = LoadTriggerHandle( multiTable , Ht , 0 )
if thisTriggerCallback == whichCallback then
call SaveInteger( multiTable , Ht , 3 , LoadInteger( multiTable , Ht , 3 ) + 1 )
set trig = null
return
endif
set A = A + 1
endloop
endif
set t = CreateTimer()
//=========================================================================================================
//Trigger for detecting beginning of attack.
//=========================================================================================================
set trig = CreateTrigger()
set Ht = GetHandleId(trig)
set numberOfAttackActions = numberOfAttackActions + 1
call TriggerRegisterUnitEvent( trig , whichUnit , EVENT_UNIT_TARGET_IN_RANGE )
call TriggerAddAction( trig , function OnAttack )
call SaveTriggerHandle( multiTable , Ht , 0 , whichCallback )
call SaveTimerHandle( multiTable , Ht , 1 , t )
call SaveReal( multiTable , Ht , 2 , procChance )
call SaveInteger( multiTable , Ht , 3 , 1 ) //Counter for non-stacking effects.
call SaveInteger( multiTable , Hu , 0 , numberOfAttackActions )
call SaveTriggerHandle( multiTable , Hu , numberOfAttackActions + 3 , trig )
if numberOfAttackActions == 1 then
//=========================================================================================================
//If it's the first Attack Action added to that unit, add an OnDeath trigger for cleanup.
//=========================================================================================================
if udg_CLEAN_UP_ON_DEATH then
if not IsUnitType(whichUnit , UNIT_TYPE_HERO) then
set trig = CreateTrigger()
call TriggerRegisterUnitEvent( trig , whichUnit , EVENT_UNIT_DEATH )
call TriggerAddAction( trig , function OnDeath )
call SaveTriggerHandle( multiTable , Hu , 1 , trig )
endif
endif
//=========================================================================================================
//Trigger for detecting the attack being interrupted. The same for all attack actions.
//=========================================================================================================
set trig = CreateTrigger()
call TriggerRegisterUnitEvent( trig , whichUnit , EVENT_UNIT_ISSUED_ORDER )
call TriggerRegisterUnitEvent( trig , whichUnit , EVENT_UNIT_ISSUED_POINT_ORDER )
call TriggerRegisterUnitEvent( trig , whichUnit , EVENT_UNIT_ISSUED_TARGET_ORDER )
call TriggerAddAction( trig , function Interrupt )
call SaveTriggerHandle( multiTable , Hu , 2 , trig)
call DisableTrigger(trig)
endif
set trig = null
set t = null
endfunction
function RemoveUnitAttackAction takes unit whichUnit, trigger whichCallback returns nothing
local trigger trig
local trigger thisTriggerCallback
local integer Hu = GetHandleId(whichUnit)
local integer Ht
local integer numberOfAttackActions = LoadInteger( multiTable , Hu , 0 )
local integer A = 4
local integer L = numberOfAttackActions + 3
local integer counter
//=========================================================================================================
//Search for attack action that has the effect that should be removed.
//=========================================================================================================
loop
exitwhen A > L
set trig = LoadTriggerHandle( multiTable , Hu , A )
set Ht = GetHandleId(trig)
set counter = LoadInteger( multiTable , Ht , 3)
if counter > 1 then
call SaveInteger( multiTable , Ht , 3 , counter - 1 )
set trig = null
return
endif
set thisTriggerCallback = LoadTriggerHandle( multiTable , Ht , 0 )
if thisTriggerCallback == whichCallback then
set numberOfAttackActions = numberOfAttackActions - 1
call SaveInteger( multiTable , Hu , 0 , numberOfAttackActions )
call DestroyTimer(LoadTimerHandle( multiTable , Ht , 1 ))
if A < L then
call SaveTriggerHandle( multiTable , Hu , A , LoadTriggerHandle( multiTable , Hu , L ) )
call RemoveSavedHandle( multiTable , Hu , L )
endif
call FlushChildHashtable( multiTable , Ht )
call DestroyTrigger(trig)
exitwhen true
endif
set A = A + 1
endloop
if numberOfAttackActions == 0 then
if not IsUnitType(whichUnit , UNIT_TYPE_HERO) then
call DestroyTrigger(LoadTriggerHandle(multiTable , Hu , 1)) //On death trigger.
endif
call DestroyTrigger(LoadTriggerHandle(multiTable , Hu , 2)) //Interrupt trigger.
endif
set trig = null
endfunction
function UnitClearAllAttackActions takes unit whichUnit returns nothing
local integer Hu = GetHandleId(whichUnit)
local integer numberOfAttackActions = LoadInteger( multiTable , Hu , 0 )
local trigger trig
local integer A = 4
local integer Ht
loop
exitwhen A > numberOfAttackActions + 3
set trig = LoadTriggerHandle( multiTable , Hu , A )
set Ht = GetHandleId(trig)
call DestroyTimer(LoadTimerHandle( multiTable , Ht , 1 ))
call FlushChildHashtable( multiTable , Ht )
call DestroyTrigger(trig)
set A = A + 1
endloop
call DestroyTrigger(LoadTriggerHandle( multiTable , Hu , 1 ))
call DestroyTrigger(LoadTriggerHandle( multiTable , Hu , 2 ))
call FlushChildHashtable( multiTable , Hu )
set whichUnit = null
set trig = null
endfunction
endlibrary
scope Test initializer Init
globals
timer checkTimer = CreateTimer()
real checkCooldown = 0
endglobals
private function CheckDamage takes nothing returns nothing
if TimerGetRemaining( checkTimer ) > 0 then
set checkCooldown = 100 - TimerGetRemaining(checkTimer)
endif
call TimerStart( checkTimer , 100 , false , null )
endfunction
function Triplicate takes unit whichSource, unit whichTarget returns nothing
//========================================================================================
//This function is an example of a function used in Trigger On Attack
//========================================================================================
local group G = CreateGroup()
local unit u
local unit dummy
local real x = GetUnitX(whichSource)
local real y = GetUnitY(whichSource)
call GroupEnumUnitsOfType( G , "Peasant" , null )
call GroupRemoveUnit( G , whichTarget )
set u = FirstOfGroup(G)
call GroupRemoveUnit(G , u )
set dummy = CreateUnit( Player(0) , 'ewsp' , x , y , Rad2Deg(Atan2( GetUnitY(u) - y , GetUnitX(u) - x )) )
call UnitAddAbility( dummy , 'A001' )
call BlzSetAbilityRealLevelField( BlzGetUnitAbility( dummy , 'A001' ) , ABILITY_RLF_DEFENSE_BONUS_HAV1 , 0 , 2*R2I(GetUnitAttackDamage(gg_unit_Hamg_0003 , 0)) )
call IssueTargetOrder( dummy , "deathcoil" , u )
call UnitApplyTimedLife( dummy , 0 , 0.35 )
set u = FirstOfGroup(G)
set dummy = CreateUnit( Player(0) , 'ewsp' , x , y , Rad2Deg(Atan2( GetUnitY(u) - y , GetUnitX(u) - x )) )
call UnitAddAbility( dummy , 'A001' )
call BlzSetAbilityRealLevelField( BlzGetUnitAbility( dummy , 'A001' ) , ABILITY_RLF_DEFENSE_BONUS_HAV1 , 0 , 2*R2I(GetUnitAttackDamage(gg_unit_Hamg_0003 , 0)) )
call IssueTargetOrder( dummy , "deathcoil" , u )
call UnitApplyTimedLife( dummy , 0 , 0.35 )
call DestroyGroup(G)
endfunction
private function At0s takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterUnitEvent( trig , gg_unit_Hamg_0003, EVENT_UNIT_TARGET_IN_RANGE )
call TriggerAddAction( trig , function CheckDamage )
call DestroyTimer(GetExpiredTimer())
endfunction
private function Init takes nothing returns nothing
call TimerStart(CreateTimer(), 0.0, false, function At0s)
endfunction
endscope