• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Codes

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Spell Codes

Snare Trap

Persecute

Riposte

Turmoil

JASS:
scope SnareTrap

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                    Snare Trap v1.0
//                    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//    
//      I. Description
//          Tosses a snaring net to the target location. Enemy units
//          within 200 range away will be snared by the net and have
//          it's movement speed reduced by 75% and has 303% chance to
//          miss it's attacks.
//
//              Level 1 - Nets last for 6 seconds.
//              Level 2 - Nets last for 8 seconds.
//              Level 3 - Nets last for 10 seconds.
//
//          Tosses  an  invisible trap instead if there is no  enemy
//          units  around  on cast. The trap will  spring  a snaring
//          net 1 second after any enemy unit walks over it.
//
//      II. Requirements
//          • Missile       by BPower       | hiveworkshop.com/threads/missile.265370/
//          • TimerUtils    by Vexorian     | wc3c.net/showthread.php?t=101322
//        
//      III. How to import
//          • Import dummy.mdx from import manager to your map. Other files are optional
//          • Import the following object data to your map:
//              (Unit)
//                  • Snare Trap (Net)
//                  • Snare Trap (Trap)
//                  • Dummy Caster
//                  • Missile Dummy
//              (Ability)
//                  • Snare Trap (Miss)
//                  • Snare Trap (Slow)
//                  • Snare Trap (Main)
//              (Buff)
//                  • Snare Trap (Buff)
//          • Make sure "Snare Trap (Miss)" & "(Slow)" abilities both has "Snare Trap (Buff)" as buff
//          • Import and configure required libraries properly
//          • Import Snare Trap trigger and configure it properly
//        
//      IV. Credits
//          • HappyCockroach : spell concept
//          • BPower         : Missile library
//          • Vexorian       : TimerUtils library, dummy.mdx
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                          Configurations
//                          ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//      A. Static configurations
        globals
//          1. Snare Trap main ability raw code
            private constant integer MAIN_SPELL_ID          = 'A000'
//        
//          2. Snare Trap (Miss) ability raw code
            private constant integer MISS_SPELL_ID          = 'A001'
//        
//          3. Snare Trap (Slow) ability raw code
            private constant integer SLOW_SPELL_ID          = 'A002'
//        
//          4. Snare Trap (Buff) buff raw code
            private constant integer NET_BUFF_ID            = 'B000'
//        
//          5. Dummy Caster unit raw code
            private constant integer DUMMY_CASTER_ID        = 'h000'
//        
//          6. Snare Trap (Net) unit raw code
            private constant integer NET_DUMMY_ID           = 'h001'
//        
//          7. Snare Trap (Trap) unit raw code
            private constant integer TRAP_DUMMY_ID          = 'h002'
//        
//          8. Snare Trap (Miss) ability order id
            private constant integer MISS_ORDER_ID          = 852190 // "curse"
//        
//          9. Snare Trap (Slow) ability order id
            private constant integer SLOW_ORDER_ID          = 852075 // "slow"
//        
//          10. Net missile model filepath
            private constant string  NET_MISSILE_MODEL      = "Abilities\\Spells\\Orc\\Ensnare\\EnsnareMissile.mdl"
//        
//          11. Trap missile model filepath
            private constant string  TRAP_MISSILE_MODEL     = "war3mapImported\\Swashbuckler_SnareTrap_Projectile.MDX"
//        
//          12. Springing trap special effect filepath
            private constant string  SPRUNG_EFFECT_MODEL    = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
//        
//          13. If true, apply expiration timer bar to trap unit
            private constant boolean APPLY_EXP_TIMER        = true
//        
//          14. Missile scale/size
            private constant real    NET_MISSILE_SCALE      = 1.5
//      
//          15. Missile scale/size
            private constant real    TRAP_MISSILE_SCALE     = 1.0
//        
//          16. Missile launching vertical (height) offset
            private constant real    MISSILE_LAUNCH_VOFFSET = 100.0
//        
//          17. Missile launching horizontal (xy) offset
            private constant real    MISSILE_LAUNCH_HOFFSET = 65.0
//        
//          18. Missile move speed
            private constant real    MISSILE_SPEED          = 15.0
//        
//          19. Missile trajectory arc
            private constant real    MISSILE_ARC            = 0.15
//        
//          Better not to modify this
            private constant real    INTERVAL               = 0.03125000
        endglobals
//        
//      B. Dynamic configurations
//        
//          20. Net maximum snaring range (area of effect)
            private constant function NetAoE takes integer level returns real
                return 200.0
            endfunction
//        
//          21. How long the net lasts before expired (in second)
            private constant function NetLifespan takes integer level returns real
                return 4.0 + 2.0*level
            endfunction
//        
//          22. How long the trap lasts before expired (in second)
            private constant function TrapLifespan takes integer level returns real
                return 180.0
            endfunction
//      
//          23. How long the trap needs to spring (in second)
            private constant function TrapSpringDelay takes integer level returns real
                return 1.0
            endfunction
//      
//          24. Target classifications that can be affected by the net
//              • target = target
//              • caster = owner of the net/trap (player)
//              Current targets: enemy, non-structure, ground, amphibious
            private constant function SpellTargets takes unit target, player caster returns boolean
                return IsUnitEnemy(target, caster) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and (IsUnitType(target, UNIT_TYPE_GROUND) or not IsUnitType(target, UNIT_TYPE_FLYING)) // Looks weird but this is how to detect both ground and amphibious units
            endfunction
//
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    native UnitAlive takes unit id returns boolean

    globals
        private constant integer STOP_ORDER_ID = 851972 // "stop"
    endglobals

    private struct SnareTrap
   
        unit    net
        group   captured
        boolean capture
        boolean sprung  // true means it's not a trap anymore
        integer level
        player  owner
   
        real targetX
        real targetY
        real lifespan   // Lifespan of both net and trap
        real delay      // Delay for the trap to sprung
        real aoe
   
        static unit  DummyCaster
        static timer CheckerTimer  = CreateTimer()
        static group TargetGroup   = CreateGroup() // Group containing units affected by the spells buff
        static group AffectedGroup = CreateGroup() // Group containing any units iterated by the spell
        static group RecycleGroup  = CreateGroup() // For recycling purpose
        static group TempGroup     = CreateGroup()
        static group TempGroup2
   
        static method check takes nothing returns nothing
   
            local unit fog
       
            loop
                set fog = FirstOfGroup(TargetGroup)
                exitwhen (fog == null)
                call GroupRemoveUnit(TargetGroup, fog)
                // Remove buff if a unit is still in target group (has the spell buff)
                // but not iterated by the spell at all. Means the unit is nowhere around
                // any net
                if (not UnitAlive(fog) or not IsUnitInGroup(fog, AffectedGroup)) then
                    // Need to remove the buff twice because the two abilties
                    // are using the same buff
                    call UnitRemoveAbility(fog, NET_BUFF_ID)
                    call UnitRemoveAbility(fog, NET_BUFF_ID)
                else
                    // Return the unit to the new target group if still snared
                    call GroupAddUnit(RecycleGroup, fog)
                endif
            endloop
       
            // Recycle group
            set TempGroup2   = TargetGroup
            set TargetGroup  = RecycleGroup
            set RecycleGroup = TempGroup2
            call GroupClear(AffectedGroup)
            // Reset and pause the checker timer if there's no affected unit's left
            if (FirstOfGroup(TargetGroup) == null) then
                call PauseTimer(CheckerTimer)
            endif
       
        endmethod
   
        static method onPeriodic takes nothing returns nothing
       
            local timer    t    = GetExpiredTimer()
            local thistype this = GetTimerData(t)
            local unit fog
            local real a
            local real x
            local real y
       
            if (.lifespan > INTERVAL) then
                if (.sprung) then
                    if (.delay == 0) then
                        set .lifespan = .lifespan - INTERVAL
                        call GroupEnumUnitsInRange(TempGroup, .targetX, .targetY, .aoe, null)
                        loop
                            set fog = FirstOfGroup(TempGroup)
                            exitwhen (fog == null)
                            call GroupRemoveUnit(TempGroup, fog)
                            // If target is classified
                            if (UnitAlive(fog) and SpellTargets(fog, .owner)) then
                                if (not IsUnitInGroup(fog, TargetGroup)) then
                                    // Apply slow and miss chance buff
                                    call IssueTargetOrderById(DummyCaster, SLOW_ORDER_ID, fog)
                                    call IssueTargetOrderById(DummyCaster, MISS_ORDER_ID, fog)
                                    if (.capture) then
                                        call GroupAddUnit(.captured, fog)
                                    endif
                                    // Make sure the target has the buffs
                                    if (GetUnitAbilityLevel(fog, NET_BUFF_ID) > 0) then
                                        if (FirstOfGroup(TargetGroup) == null) then
                                            // Start the timer to check that affected units are still snared (within AoE)
                                            call TimerStart(CheckerTimer, INTERVAL, true, function thistype.check)
                                        endif
                                        call GroupAddUnit(TargetGroup, fog)
                                    endif
                                endif
                                call GroupAddUnit(AffectedGroup, fog)
                            endif
                        endloop
                   
                        set .capture  = false
                        if (FirstOfGroup(.captured) != null) then
                            loop
                                set fog = FirstOfGroup(.captured)
                                exitwhen (fog == null)
                                call GroupRemoveUnit(.captured, fog)
                                // Prevent unit to be removed when outside the rope area
                                call GroupAddUnit(AffectedGroup, fog)
                                call GroupAddUnit(RecycleGroup, fog)
                                set x = GetUnitX(fog)
                                set y = GetUnitY(fog)
                                // Prevent captured unit from leaving
                                if ((.targetX-x)*(.targetX-x)+(.targetY-y)*(.targetY-y) > .aoe*.aoe) then
                                    call IssueImmediateOrderById(fog, STOP_ORDER_ID)
                                    set a = Atan2(y-.targetY, x-.targetX)
                                    call SetUnitX(fog, .targetX+.aoe*Cos(a))
                                    call SetUnitY(fog, .targetY+.aoe*Sin(a))
                                endif
                            endloop
                            // Recycle group
                            set TempGroup2   = .captured
                            set .captured    = RecycleGroup
                            set RecycleGroup = TempGroup2
                        endif
                    else
                        // Springing trap
                        set .delay = .delay - INTERVAL
                        if (.delay <= 0) then
                            call RemoveUnit(.net)
                            call DestroyEffect(AddSpecialEffect(SPRUNG_EFFECT_MODEL, .targetX, .targetY))
                            set .net = CreateUnit(.owner, NET_DUMMY_ID, .targetX, .targetY, 0)
                            call SetUnitAnimation(.net, "birth")
                            call QueueUnitAnimation(.net, "stand")
                            set .lifespan = NetLifespan(.level)
                            set .delay    = 0
                        endif
                    endif
                else
                    set .lifespan = .lifespan - INTERVAL
                    // Check if there is any target unit passing by the trap
                    call GroupEnumUnitsInRange(TempGroup, .targetX, .targetY, .aoe, null)
                    loop
                        set fog = FirstOfGroup(TempGroup)
                        exitwhen (fog == null)
                        call GroupRemoveUnit(TempGroup, fog)
                        if (UnitAlive(fog) and SpellTargets(fog, .owner)) then
                            set .sprung = true
                            exitwhen true
                        endif
                    endloop
                    if (fog != null) then
                        call GroupClear(TempGroup)
                        set fog = null
                    endif
                endif
            else
                // Dispose the spell instance
                call DestroyGroup(.captured)
                call KillUnit(.net)
                call ReleaseTimer(t)
                call deallocate()
                set .captured = null
                set .net = null
            endif
            set t = null
       
        endmethod
   
        static method onRemove takes Missile missile returns boolean
       
            local thistype this = missile.data
       
            // Hoping the missile library handles world bounds correctly                                                                                                yeah I'm lazy so what?
            set .targetX = missile.x
            set .targetY = missile.y
            // Check whether the missile should be a trap or a net
            if (.sprung) then
                set .net = CreateUnit(.owner, NET_DUMMY_ID,  .targetX, .targetY, 0)
                call SetUnitAnimation(.net, "birth")
                call QueueUnitAnimation(.net, "stand")
            else
                set .net = CreateUnit(.owner, TRAP_DUMMY_ID, .targetX, .targetY, 0)
                static if APPLY_EXP_TIMER then
                    call UnitApplyTimedLife(.net, 'BTLF', .lifespan)
                endif
            endif
            call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.onPeriodic)
       
            return true
        endmethod
   
        implement MissileStruct

        static method onCast takes nothing returns boolean
       
            local Missile  missile
            local thistype this
            local unit fog
            local unit caster
            local real angle
            local real x
            local real y
       
            if (GetSpellAbilityId() == MAIN_SPELL_ID) then
                set this      = allocate()
                set caster    = GetTriggerUnit()
                set .owner    = GetTriggerPlayer()
                set .targetX  = GetSpellTargetX()
                set .targetY  = GetSpellTargetY()
                set .captured = CreateGroup()
                set .capture  = true
                set .sprung   = false
           
                // Initialize the missile
                set x         = GetUnitX(caster)
                set y         = GetUnitY(caster)
                set angle     = Atan2(.targetY-y, .targetX-x)
                set missile   = Missile.createXYZ(x+MISSILE_LAUNCH_HOFFSET*Cos(angle), y+MISSILE_LAUNCH_HOFFSET*Sin(angle), MISSILE_LAUNCH_VOFFSET+GetUnitFlyHeight(caster), .targetX, .targetY, 0)
                set missile.speed = MISSILE_SPEED
                set missile.arc   = MISSILE_ARC
                set missile.data  = this
           
                set .level = GetUnitAbilityLevel(caster, MAIN_SPELL_ID)
                set .aoe   = NetAoE(.level)
                // Check if there's target units around the target area
                call GroupEnumUnitsInRange(TempGroup, .targetX, .targetY, .aoe, null)
                loop
                    set fog = FirstOfGroup(TempGroup)
                    exitwhen (fog == null)
                    call GroupRemoveUnit(TempGroup, fog)
                    if (UnitAlive(fog) and SpellTargets(fog, .owner)) then
                        set .sprung = true
                        exitwhen true
                    endif
                endloop
                if (fog != null) then
                    call GroupClear(TempGroup)
                    set fog = null
                endif
                // If a target is detected, throw a net, else, a trap
                if (.sprung) then
                    set missile.model = NET_MISSILE_MODEL
                    set missile.scale = NET_MISSILE_SCALE
                    set .lifespan = NetLifespan(.level)
                    set .delay    = 0
                else
                    set missile.model = TRAP_MISSILE_MODEL
                    set missile.scale = TRAP_MISSILE_SCALE
                    set .lifespan = TrapLifespan(.level)
                    set .delay    = TrapSpringDelay(.level)
                endif
                call launch(missile)
                set caster = null
            endif
       
            return false
        endmethod
   
        static method onInit takes nothing returns nothing

            local trigger t = CreateTrigger()
       
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.onCast))
       
            // Create dummy caster to apply buffs
            set DummyCaster = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_CASTER_ID, 0, 0, 0)
            call UnitAddAbility(DummyCaster, SLOW_SPELL_ID)
            call UnitAddAbility(DummyCaster, MISS_SPELL_ID)
       
        endmethod
   
    endstruct

endscope
JASS:
scope Persecute

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                    Persecute v1.0
//                    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//    
//      I. Description
//          Throws a paintball at the target. Marking it as a wanted
//          target.  Marked  unit will leave traces as  it  travels.
//          Ally  units  who's targeting it will have  their  attack
//          speed  and  move speed increased. The mark lasts for  15
//          seconds.
//      
//              Level 1 - 15% attack speed bonus. 10% move speed bonus.
//              Level 2 - 25% attack speed bonus. 15% move speed bonus.
//              Level 3 - 35% attack speed bonus. 20% move speed bonus.
//      
//      II. Requirements
//          • Missile       by BPower       | hiveworkshop.com/threads/missile.265370/
//          • UnitIndexer   by TriggerHappy | hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
//          • TimerUtils    by Vexorian     | wc3c.net/showthread.php?t=101322
//        
//      III. How to import
//          • Import dummy.mdx from import manager to your map. Other files are optional
//          • Import the following object data to your map:
//              (Unit)
//                  • Dummy Caster
//                  • Missile Dummy
//              (Ability)
//                  • Persecute (Buff)
//                  • Persecute (Mark)
//                  • Persecute (Main)
//              (Buff)
//                  • Persecute (Buff)
//                  • Persecute (Mark)
//          • Make sure "Persecute (Buff)" ability has "Persecute (Buff)" as buff
//          • Make sure "Persecute (Mark)" ability has "Persecute (Mark)" as buff
//          • Import and configure required libraries properly
//          • Import Persecute trigger and configure it properly
//        
//      IV. Credits
//          • HappyCockroach : spell concept
//          • TriggerHappy : UnitIndexer library
//          • Vexorian     : TimerUtils library
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                          Configurations
//                          ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//      A. Static configurations
        globals
//          1. Persecute main ability raw code
            private constant integer MAIN_SPELL_ID          = 'A00B'
//        
//          2. Persecute (Mark) ability raw code
            private constant integer MARK_SPELL_ID          = 'A00C'
//        
//          3. Persecute (Buff) ability raw code
            private constant integer BONUS_SPELL_ID         = 'A00D'
//        
//          4. Persecute (Buff) buff raw code
            private constant integer MARK_BUFF_ID           = 'B003'
//        
//          5. Persecute (Mark) buff raw code
            private constant integer BONUS_BUFF_ID          = 'B004'
//        
//          6. Dummy Caster unit raw code
            private constant integer DUMMY_CASTER_ID        = 'h000'
//        
//          7. Persecute (Mark) ability order id
            private constant integer MARK_ORDER_ID          = 852570 // "wandofshadowsight"
//        
//          8. Persecute (Buff) ability order id
            private constant integer BONUS_ORDER_ID         = 852101 // "bloodlust"
//        
//          9. Launched missile model file path
            private constant string  MISSILE_MODEL_PATH     = "war3mapImported\\Swashbuckler_Persecute_Projectile.MDX"
//        
//          10. Missile move speed
            private constant real    MISSILE_SPEED          = 25.0
//        
//          11. Missile trajectory arc
            private constant real    MISSILE_ARC            = 0.15
//        
//          12. Missile launching vertical (height) offset
            private constant real    MISSILE_LAUNCH_VOFFSET = 125.0
//        
//          13. Missile launching horizontal (xy) offset
            private constant real    MISSILE_LAUNCH_HOFFSET = 65.0
//        
//          Interval at which the detection trigger will reset
//          (Just don't modify if you don't know what it means)                                                                                                  Congrats! If you are reading this, it means you are certified nerd
            private constant real    TRIGGER_RESET_INTERVAL = 30.0
//        
//          Better not to modify this
            private constant real    INTERVAL               = 0.1
        endglobals
//        
//      B. Dynamic configurations
//        
//          14. Mark duration on normal units
            private constant function MarkDurationNormal takes integer level returns real
                return 20.0
            endfunction
//        
//          15. Mark duration on hero units
            private constant function MarkDurationHero takes integer level returns real
                return 10.0
            endfunction
//        
//          16. Classification of unit that can receive bonus stats when
//             chasing a target with Persecute (Buff - Target) buff
//              • chaser = unit that's targeting the marked unit
//              • caster = owner of the caster (who gives the mark)
//              • target = marked unit
//              Current targets: enemy of target, ally of caster, non-mechanical, non-structure
            private constant function AllowedChaser takes unit chaser, player caster, player target returns boolean
                return IsUnitEnemy(chaser, target) and IsUnitAlly(chaser, caster) and not IsUnitType(chaser, UNIT_TYPE_MECHANICAL) and not IsUnitType(chaser, UNIT_TYPE_STRUCTURE)
            endfunction
//
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    native UnitAlive takes unit id returns boolean

    globals
        private constant integer ATTACK_ORDER_ID = 851983 // "attack"
        private constant integer SMART_ORDER_ID  = 851971 // "smart"
        private constant player  PASSIVE         = Player(PLAYER_NEUTRAL_PASSIVE)
        private unit DummyCaster
    endglobals

    private struct PersecuteMark
   
        unit target
        real duration
   
        static thistype array Index
   
        static method onPeriodic takes nothing returns nothing
       
            local timer    t    = GetExpiredTimer()
            local thistype this = GetTimerData(t)
   
            // If need the buff to be removed
            if (.duration <= INTERVAL or not UnitAlive(.target) or GetUnitAbilityLevel(.target, MARK_BUFF_ID) == 0) then
                set Index[GetUnitUserData(.target)] = 0
                call UnitRemoveAbility(.target, MARK_BUFF_ID)
                call ReleaseTimer(t)
                call deallocate()
                set .target = null
            else
                set .duration = .duration - INTERVAL
            endif
            set t = null
   
        endmethod
   
        static method apply takes Missile missile returns nothing
   
            local thistype this
            local integer  data = GetUnitUserData(missile.target)
       
            // If already registered
            if (Index[data] != 0) then
                set this          = Index[data]
                if (IsUnitType(missile.target, UNIT_TYPE_HERO)) then
                    set .duration = MarkDurationHero(missile.data)
                else
                    set .duration = MarkDurationNormal(missile.data)
                endif
            else
                set this          = allocate()
                set Index[data]   = this
                set .target       = missile.target
                // If target is a hero
                if (IsUnitType(missile.target, UNIT_TYPE_HERO)) then
                    set .duration = MarkDurationHero(missile.data)
                else
                    set .duration = MarkDurationNormal(missile.data)
                endif
                // If not have the buff yet
                if (GetUnitAbilityLevel(missile.target, MARK_BUFF_ID) == 0) then
                    call SetUnitOwner(DummyCaster, missile.owner, false)
                    call IssueTargetOrderById(DummyCaster, MARK_ORDER_ID, missile.target)
                    call SetUnitOwner(DummyCaster, PASSIVE, false)
                endif
                call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.onPeriodic)
            endif
       
        endmethod
   
    endstruct

    private struct Persecute extends array
   
        static group   TempGroup
        static group   BonusGroup    = CreateGroup()  // Group containing units with bonus buff
        static group   DetectGroup   = CreateGroup()  // Group containing units registered in detection trigger
        static group   RecycleGroup  = CreateGroup()  // For recycling purpose
        static timer   ResetTimer    = CreateTimer()
        static timer   CheckTimer    = CreateTimer()
        static trigger DetectTrigger = CreateTrigger()
        static trigger LearnTrigger  = CreateTrigger()
   
        static integer array AbilityLevel
        static player  array CasterOwner
        static unit    array TargetUnit
   
        static method removeBonus takes unit u returns nothing
       
            if (IsUnitInGroup(u, BonusGroup)) then
                call GroupRemoveUnit(BonusGroup, u)
                call UnitRemoveAbility(u, BONUS_BUFF_ID)
                // If there is no unit with the bonus buff anymore
                if (FirstOfGroup(BonusGroup) == null) then
                    call PauseTimer(CheckTimer)
                endif
            endif
       
        endmethod
   
        static method check takes nothing returns nothing
       
            local integer data
            local unit fog
       
            loop
                set fog = FirstOfGroup(DetectGroup)
                exitwhen (fog == null)
                call GroupRemoveUnit(DetectGroup, fog)
                call GroupAddUnit(RecycleGroup, fog)
                if (IsUnitInGroup(fog, BonusGroup)) then
                    set data = GetUnitUserData(fog)
                    // Check if unit's target is still alive and still marked
                    if (not UnitAlive(TargetUnit[data]) or not IsUnitVisible(TargetUnit[data], GetOwningPlayer(fog)) or GetUnitAbilityLevel(TargetUnit[data], MARK_BUFF_ID) == 0) then
                        call removeBonus(fog)
                    endif
                // Add compatibility with spell stealing abilities (no permanent buff)
                elseif (GetUnitAbilityLevel(fog, BONUS_BUFF_ID) > 0) then
                    call UnitRemoveAbility(fog, BONUS_BUFF_ID)
                endif
            endloop
            set TempGroup    = DetectGroup
            set DetectGroup  = RecycleGroup
            set RecycleGroup = TempGroup
       
        endmethod
   
        static method addBonus takes unit u, unit t returns nothing
       
            if (not IsUnitInGroup(u, BonusGroup) and AllowedChaser(u, CasterOwner[GetUnitUserData(t)], GetOwningPlayer(t))) then
                // If the unit is the first one to have bonus buff
                if (FirstOfGroup(BonusGroup) == null) then
                    call TimerStart(CheckTimer, INTERVAL, true, function thistype.check)
                endif
                call GroupAddUnit(BonusGroup, u)
                // Apply move and attack speed bonus based on ability level
                call SetUnitAbilityLevel( DummyCaster, BONUS_SPELL_ID, AbilityLevel[GetUnitUserData(t)])
                call IssueTargetOrderById(DummyCaster, BONUS_ORDER_ID, u)
            endif
       
        endmethod
   
        static method onDetect takes nothing returns boolean

            local unit t = GetEventTargetUnit()
            local unit u = GetTriggerUnit()
       
            set TargetUnit[GetUnitUserData(u)] = t
            // If the target is marked
            if (GetUnitAbilityLevel(t, MARK_BUFF_ID) > 0) then
                call addBonus(u, t)
            else
                call removeBonus(u)
            endif
            set t = null
            set u = null
           
            return false
        endmethod
   
        static method onOrder takes nothing returns boolean
       
            local integer order
            local unit t = GetOrderTargetUnit()
            local unit u = GetTriggerUnit()
       
            if (t != null) then
                set order = GetIssuedOrderId()
                // Make sure the order is "attack"
                if (order == ATTACK_ORDER_ID or (order == SMART_ORDER_ID and IsUnitEnemy(u, GetOwningPlayer(t)))) then
                    // Save the unit's current target
                    set TargetUnit[GetUnitUserData(u)] = t
                    // If the target is marked
                    if (GetUnitAbilityLevel(t, MARK_BUFF_ID) > 0) then
                        call addBonus(u, t)
                    else
                        call removeBonus(u)
                    endif
                else
                    call removeBonus(u)
                endif
            else
                call removeBonus(u)
            endif
            set t = null
            set u = null
       
            return false
        endmethod
   
        static method onRemove takes Missile missile returns boolean
       
            local integer data
            local unit fog
       
            if (UnitAlive(missile.target)) then
                set data               = GetUnitUserData(missile.target)
                set AbilityLevel[data] = GetUnitAbilityLevel(missile.source, MAIN_SPELL_ID)
                set CasterOwner[data]  = missile.owner
                // Every unit can only have one mark
                if (GetUnitAbilityLevel(missile.target, MARK_BUFF_ID) > 0) then
                    call UnitRemoveAbility(missile.target, MARK_BUFF_ID)
                    loop
                        set fog = FirstOfGroup(BonusGroup)
                        exitwhen (fog == null)
                        call GroupRemoveUnit(BonusGroup, fog)
                        if (TargetUnit[GetUnitUserData(fog)] == missile.target) then
                            call removeBonus(fog)
                        else
                            call GroupAddUnit(RecycleGroup, fog)
                        endif
                    endloop
                    set TempGroup    = BonusGroup
                    set BonusGroup   = RecycleGroup
                    set RecycleGroup = TempGroup
                endif
                // Re-apply to reset mark duration
                call PersecuteMark.apply(missile)
           
                call IssueTargetOrderById(DummyCaster, MARK_ORDER_ID, missile.target)
                loop
                    set fog = FirstOfGroup(DetectGroup)
                    exitwhen (fog == null)
                    call GroupRemoveUnit(DetectGroup, fog)
                    call GroupAddUnit(RecycleGroup, fog)
                    // If unit's target is the marked unit
                    if (TargetUnit[GetUnitUserData(fog)] == missile.target) then
                        call addBonus(fog, missile.target)
                    endif
                endloop
                set TempGroup    = DetectGroup
                set DetectGroup  = RecycleGroup
                set RecycleGroup = TempGroup
            endif
       
            return true
        endmethod
   
        implement MissileStruct
   
        static method onCast takes nothing returns boolean
       
            local Missile missile
       
            local unit caster
            local unit target
       
            local real xt
            local real yt
            local real x
            local real y
            local real a
       
            if (GetSpellAbilityId() == MAIN_SPELL_ID) then
                set caster = GetTriggerUnit()
                set target = GetSpellTargetUnit()
                set x      = GetUnitX(caster)
                set y      = GetUnitY(caster)
                set xt     = GetUnitX(target)
                set yt     = GetUnitY(target)
                set a      = Atan2(yt-y, xt-x)
           
                // Launch the paintball
                set missile        = Missile.createXYZ(x+MISSILE_LAUNCH_HOFFSET*Cos(a), y+MISSILE_LAUNCH_HOFFSET*Sin(a), MISSILE_LAUNCH_VOFFSET+GetUnitFlyHeight(caster), xt, yt, MISSILE_LAUNCH_VOFFSET+GetUnitFlyHeight(target))
                set missile.owner  = GetTriggerPlayer()
                set missile.source = caster
                set missile.target = target
                set missile.arc    = MISSILE_ARC
                set missile.speed  = MISSILE_SPEED
                set missile.model  = MISSILE_MODEL_PATH
                set missile.data   = GetUnitAbilityLevel(caster, MAIN_SPELL_ID)
                call launch(missile)
           
                set target = null
                set caster = null
            endif
       
            return false
        endmethod
   
        static method resetTrigger takes nothing returns nothing
       
            local unit fog
       
            // Reset detection trigger
            call DestroyTrigger(DetectTrigger)
            set  DetectTrigger = CreateTrigger()
            call TriggerAddCondition(DetectTrigger, Condition(function thistype.onDetect))
            loop
                set fog = FirstOfGroup(DetectGroup)
                exitwhen (fog == null)
                call GroupRemoveUnit(DetectGroup, fog)
                call GroupAddUnit(RecycleGroup, fog)
                call TriggerRegisterUnitEvent(DetectTrigger, fog, EVENT_UNIT_ACQUIRED_TARGET)
            endloop
            // Recycle group
            set TempGroup    = DetectGroup
            set DetectGroup  = RecycleGroup
            set RecycleGroup = TempGroup
       
        endmethod
   
        static method onDeindex takes nothing returns boolean
       
            local unit u = GetIndexedUnit()
   
            set TargetUnit[GetIndexedUnitId()] = null
            call GroupRemoveUnit(DetectGroup, u)
            if (IsUnitInGroup(u, BonusGroup)) then
                call GroupRemoveUnit(BonusGroup, u)
                // If there is no unit with the bonus buff anymore
                if (FirstOfGroup(BonusGroup) == null) then
                    call PauseTimer(CheckTimer)
                endif
            endif
            set u = null
       
            return false
        endmethod
   
        static method initializeSpell takes nothing returns nothing
   
            local unit    fog
            local group   g  = CreateGroup()
            local trigger t1 = CreateTrigger()
            local trigger t2 = CreateTrigger()
            local integer i  = 0
            local player  p
       
            // Prepare triggers
            loop
                exitwhen (i == bj_MAX_PLAYER_SLOTS)
                set p = Player(i)
                call TriggerRegisterPlayerUnitEvent(t1, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
                call TriggerRegisterPlayerUnitEvent(t2, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
                call TriggerRegisterPlayerUnitEvent(t2, p, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
                call TriggerRegisterPlayerUnitEvent(t2, p, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
                set i = i + 1
            endloop
       
            call EnableTrigger(DetectTrigger)
            call DestroyTrigger(LearnTrigger)
            call TriggerAddCondition(t1, Condition(function thistype.onCast))
            call TriggerAddCondition(t2, Condition(function thistype.onOrder))
            call TriggerAddCondition(DetectTrigger, Condition(function thistype.onDetect))
            call RegisterUnitIndexEvent(Condition(function thistype.onDeindex), EVENT_UNIT_DEINDEX)
       
            // Periodically reset detection trigger to prevent event leaks
            call TimerStart(ResetTimer, TRIGGER_RESET_INTERVAL, true, function thistype.resetTrigger)
            // Create dummy caster to apply buffs
            set DummyCaster = CreateUnit(PASSIVE, DUMMY_CASTER_ID, 0, 0, 0)
            call UnitAddAbility(DummyCaster, BONUS_SPELL_ID)
            call UnitAddAbility(DummyCaster, MARK_SPELL_ID)
       
            call GroupEnumUnitsInRect(g, GetWorldBounds(), null)
            loop
                set fog = FirstOfGroup(g)
                exitwhen (fog == null)
                call GroupRemoveUnit(g, fog)
                if (IsUnitIndexed(fog)) then
                    call TriggerRegisterUnitEvent(DetectTrigger, fog, EVENT_UNIT_ACQUIRED_TARGET)
                    call GroupAddUnit(DetectGroup, fog)
                endif
            endloop
            call DestroyGroup(g)
            set LearnTrigger = null
            set g = null
       
        endmethod
   
        static method onLearn takes nothing returns boolean
       
            if (GetLearnedSkill() == MAIN_SPELL_ID) then
                // The first time the spell appears in game, initialize the spell trigger
                call initializeSpell()
            endif
       
            return false
        endmethod
   
        static method onIndex takes nothing returns boolean
   
            local unit u
       
            if (IsTriggerEnabled(DetectTrigger)) then
                set u = GetIndexedUnit()
                // Add indexed unit to the detection trigger
                call TriggerRegisterUnitEvent(DetectTrigger, u, EVENT_UNIT_ACQUIRED_TARGET)
                call GroupAddUnit(DetectGroup, u)
                set u = null
            elseif (GetUnitAbilityLevel(GetIndexedUnit(), MAIN_SPELL_ID) > 0) then
                // The first time the spell appears in game, initialize the spell trigger
                call initializeSpell()
            endif
       
            return false
        endmethod
   
        static method onInit takes nothing returns nothing
       
            call DisableTrigger(DetectTrigger)
            call TriggerRegisterAnyUnitEventBJ(LearnTrigger, EVENT_PLAYER_HERO_SKILL)
            call TriggerAddCondition(LearnTrigger, Condition(function thistype.onLearn))
            call RegisterUnitIndexEvent(Condition(function thistype.onIndex), EVENT_UNIT_INDEX)
       
        endmethod
   
    endstruct

endscope
JASS:
scope Riposte

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                    Riposte v1.0
//                    ¯¯¯¯¯¯¯¯¯¯¯¯
//    
//      I. Description
//          Gives a chance to counter an incoming melee  attack.
//          Dealing  damage  and  causing  1 second  stun  while
//          ignoring the incoming damage.
//      
//              Level 1 - 20% chance. Deals 80 damage.
//              Level 2 - 30% chance. Deals 90 damage.
//              Level 3 - 40% chance. Deals 120 damage.
//      
//      II. Requirements
//          • RapidSound    by Quilnez      | hiveworkshop.com/threads/snippet-rapidsound.258991/
//          • UnitIndexer   by TriggerHappy | hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
//          • TimerUtils    by Vexorian     | wc3c.net/showthread.php?t=101322
//        
//      III. How to import
//          • Import dummy.mdx from import manager to your map. Other files are optional
//          • Import the following object data to your map:
//              (Unit)
//                  • Dummy Caster
//              (Ability)
//                  • Riposte (Pause)
//                  • Riposte (Main)
//              (Buff)
//                  • Riposte (Buff)
//          • Make sure "Persecute (Pause)" ability has "Persecute (Buff)" as buff
//          • Import and configure required libraries properly
//          • Import Persecute trigger and configure it properly
//        
//      IV. Credits
//          • TriggerHappy   : UnitIndexer library
//          • Vexorian       : TimerUtils library, dummy.mdx
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                          Configurations
//                          ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//      A. Static configurations
        globals
//          1. Riposte main ability raw code
            private constant integer    MAIN_SPELL_ID          = 'A009'
//        
//          2. Riposte (Pause) ability raw code
            private constant integer    PAUSE_SPELL_ID         = 'A00A'
//        
//          3. Riposte (Buff) buff raw code
            private constant integer    PAUSE_BUFF_ID          = 'B002'
//        
//          4. Dummy Caster unit raw code
            private constant integer    DUMMY_CASTER_ID        = 'h000'
//        
//          5. Riposte (Pause) ability order id
            private constant integer    PAUSE_ORDER_ID         = 852127 // "stomp"
//        
//          6. Played animation when the spell is triggering
            private constant string     DEFLECT_ANIMATION      = "defend"
//        
//          7. Played animation when countering the attack
            private constant string     COUNTER_ANIMATION      = "slam"
//        
//          8. Special effect when target's attack is deflected
            private constant string     STUN_SFX_MODEL         = "Abilities\\Spells\\Human\\Thunderclap\\ThunderclapTarget.mdl"
            private constant string     STUN_SFX_MODEL_PT      = "overhead"
//        
//          9. Dealt damage configurations
            private constant attacktype ATTACK_TYPE            = ATTACK_TYPE_HERO
            private constant damagetype DAMAGE_TYPE            = DAMAGE_TYPE_NORMAL
            private constant weapontype WEAPON_TYPE            = WEAPON_TYPE_METAL_MEDIUM_SLICE
//        
//          Interval at which the detection trigger will reset
//          (Just don't modify if you don't know what it means)
            private constant real       TRIGGER_RESET_INTERVAL = 30.0
//        
//          Better not to modify this
            private constant real       INTERVAL               = 0.1
        endglobals
//        
//      B. Dynamic configurations
//        
//          10. Chance of the counter move to be performed
            private constant function TriggerChance takes integer level returns real
                return 10.0 + 10.0*level
            endfunction
//        
//          11. Dealt damage when attack is countered
            private constant function DealtDamage takes integer level returns real
                return 30.0 + 30.0*level
            endfunction
//        
//          12. Stun duration after attack is countered
            private constant function StunDuration takes integer level returns real
                return 1.0
            endfunction
//        
//          13. Delay before the next counter move can be performed again
            private constant function CooldownTime takes integer level returns real
                return 2.0
            endfunction
//        
//          14. Delay before the next counter move can be performed again
            private constant function CounterDelay takes integer level returns real
                return 0.4
            endfunction
//        
//          15. Delay before the damage is dealt after deflecting move is performed
            private constant function DamageDelay takes integer sourceType returns real
                return 0.25
            endfunction
//        
//          16. Classification of unit that its attacks can be countered
//              • target = attacker
//              • caster = owner of attacked unit
//              Current targets: enemy, melee, non-structure, ground, amphibious
            private constant function SpellTargets takes unit target, player caster returns boolean
                return IsUnitEnemy(target, caster) and IsUnitType(target, UNIT_TYPE_MELEE_ATTACKER) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and (IsUnitType(target, UNIT_TYPE_GROUND) or not IsUnitType(target, UNIT_TYPE_FLYING))
            endfunction
//
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    native UnitAlive takes unit id returns boolean

    globals
        private constant integer ATTACK_ORDER_ID = 851983 // "attack"
        private constant integer SMART_ORDER_ID  = 851971 // "smart"
        private unit DummyCaster
    endglobals

    private struct Stun
   
        effect sfx
        unit   target
        real   duration
   
        static thistype array Index
   
        static method onPeriodic takes nothing returns nothing
       
            local timer    t    = GetExpiredTimer()
            local thistype this = GetTimerData(t)
       
            // If need the buff to be removed
            if (.duration <= INTERVAL or not UnitAlive(.target) or GetUnitAbilityLevel(.target, PAUSE_BUFF_ID) == 0) then
                set Index[GetUnitUserData(.target)] = 0
                call UnitRemoveAbility(.target, PAUSE_BUFF_ID)
                if (.sfx != null) then
                    call DestroyEffect(.sfx)
                    set .sfx = null
                endif
                call ReleaseTimer(t)
                call deallocate()
                set .target = null
            else
                set .duration = .duration - INTERVAL
            endif
            set t = null
   
        endmethod
   
        static method apply takes unit t, real d, boolean b returns nothing
   
            local thistype this
            local integer  data = GetUnitUserData(t)
       
            // If already registered
            if (Index[data] != 0) then
                set this        = Index[data]
                set .duration   = d
            else
                set this        = allocate()
                set Index[data] = this
                set .target     = t
                set .duration   = d
                if (b) then
                    set .sfx    = AddSpecialEffectTarget(STUN_SFX_MODEL, t, STUN_SFX_MODEL_PT)
                endif
                // If don't have the buff yet
                if (GetUnitAbilityLevel(t, PAUSE_BUFF_ID) == 0) then
                    call SetUnitX(DummyCaster, GetUnitX(t))
                    call SetUnitY(DummyCaster, GetUnitY(t))
                    call IssueImmediateOrderById(DummyCaster, PAUSE_ORDER_ID)
                endif
                call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.onPeriodic)
            endif
       
        endmethod
   
    endstruct

    private struct Riposte extends array
   
        static group   TempGroup
        static group   DetectGroup   = CreateGroup()    // Group containing units with Riposte ability
        static group   RecycleGroup  = CreateGroup()    // For recycling purpose
        static timer   ResetTimer    = CreateTimer()
        static trigger DetectTrigger = CreateTrigger()
        static trigger OrderTrigger
   
        static boolean array IsTriggering
        static unit    array CurrentTarget
        static unit    array DamageTarget
        static real    array DamageAmount
        static real    array DamageDelay
        static real    array StunDuration
        static timer   array CooldownTimer
        static timer   array TriggerTimer
   
        static method delayedDamage takes nothing returns nothing
       
            local timer   t = GetExpiredTimer()
            local integer i = GetTimerData(t)
            local unit    u = GetUnitById(i)
       
            if (UnitAlive(u) and UnitAlive(DamageTarget[i])) then
                call UnitDamageTarget(u, DamageTarget[i], DamageAmount[i], true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
            endif
            // Re-order caster to attack target so it won't change target afterwards
            call DisableTrigger(OrderTrigger)
            call IssueTargetOrderById(u, ATTACK_ORDER_ID, DamageTarget[i])
            call EnableTrigger(OrderTrigger)
            set IsTriggering[i] = false
            set DamageTarget[i] = null
            set t = null
            set u = null
       
        endmethod
   
        static method counter takes nothing returns nothing
   
            local timer   t = GetExpiredTimer()
            local integer i = GetTimerData(t)
            local unit    u = GetUnitById(i)
       
            if (UnitAlive(u)) then
                if (UnitAlive(DamageTarget[i])) then
                    // Re-apply stun on target but now with sfx
                    call Stun.apply(DamageTarget[i], StunDuration[i], true)
                    call SetUnitAnimation(DamageTarget[i], "stand")
                    call TimerStart(t, DamageDelay[i], false, function thistype.delayedDamage)
                else
                    set DamageTarget[i] = null
                    set IsTriggering[i] = false
                endif
                call SetUnitAnimation(u, COUNTER_ANIMATION)
                call QueueUnitAnimation(u, "ready")
            else
                set DamageTarget[i] = null
                set IsTriggering[i] = false
            endif
            set t = null
            set u = null
       
        endmethod
   
        static method onAttack takes nothing returns boolean
       
            local unit    a     = GetAttacker()
            local unit    t     = GetTriggerUnit()
            local integer tdata = GetUnitUserData(t)
            local integer level
            local integer order
            local real    delay
       
            if (not IsTriggering[tdata] and CurrentTarget[tdata] == a) then
                if (IsUnitInGroup(t, DetectGroup) and SpellTargets(a, GetOwningPlayer(t))) then
                    set order = GetUnitCurrentOrder(t)
                    // If caster is on attack mode
                    if (order == ATTACK_ORDER_ID or (order == 0 and UnitAlive(CurrentTarget[tdata]))) then
                        if (TimerGetRemaining(CooldownTimer[tdata]) == 0) then
                            set level = GetUnitAbilityLevel(t, MAIN_SPELL_ID)
                            if (GetRandomReal(0, 100) <= TriggerChance(level)) then
                                set IsTriggering[tdata] = true
                                set DamageTarget[tdata] = a
                                set DamageDelay[tdata]  = DamageDelay(GetUnitTypeId(t))
                                set DamageAmount[tdata] = DealtDamage(level)
                                set StunDuration[tdata] = StunDuration(level)
                                set delay               = CounterDelay(level)
                                // Pause both units so their normal attacks won't interrupt
                                call Stun.apply(a, delay, false)
                                call Stun.apply(t, delay+DamageDelay[tdata], false)
                                call SetUnitAnimation(a, "attack")
                                call SetUnitAnimation(t, DEFLECT_ANIMATION)
                                call TimerStart(TriggerTimer[tdata], delay, false, function thistype.counter)
                                call TimerStart(CooldownTimer[tdata], CooldownTime(level), false, null)
                            endif
                        endif
                    endif
                endif
            endif
            set a = null
            set t = null
       
            return false
        endmethod
   
        static method onDetect takes nothing returns boolean
       
            local unit u = GetTriggerUnit()
   
            if (IsUnitInGroup(u, DetectGroup)) then
                set CurrentTarget[GetUnitUserData(u)] = GetEventTargetUnit()
            endif
            set u = null
       
            return false
        endmethod
   
        static method onOrder takes nothing returns boolean
       
            local unit u = GetTriggerUnit()
            local unit t
   
            if (IsUnitInGroup(u, DetectGroup)) then
                set t = GetOrderTargetUnit()
                // Convert "smart" order to "attack" order
                if (t != null and GetIssuedOrderId() == SMART_ORDER_ID and IsUnitEnemy(t, GetOwningPlayer(u))) then
                    set CurrentTarget[GetUnitUserData(u)] = t
                    call DisableTrigger(OrderTrigger)
                    call IssueTargetOrderById(u, ATTACK_ORDER_ID, t)
                    call EnableTrigger(OrderTrigger)
                endif
                set t = null
            endif
            set u = null
       
            return false
        endmethod
   
        static method onDeindex takes nothing returns boolean
       
            local integer data
            local unit u = GetIndexedUnit()
       
            if (IsUnitInGroup(u, DetectGroup)) then
                // Remove all data related to the unit
                set data = GetIndexedUnitId()
                call GroupRemoveUnit(DetectGroup, u)
                call ReleaseTimer(CooldownTimer[data])
                call ReleaseTimer(TriggerTimer[data])
                set CurrentTarget[data] = null
                set CooldownTimer[data] = null
                set TriggerTimer[data]  = null
            endif
            set u = null
       
            return false
        endmethod
   
        static method resetTrigger takes nothing returns nothing
       
            local unit fog
       
            // Reset detection trigger
            call DestroyTrigger(DetectTrigger)
            set  DetectTrigger = CreateTrigger()
            call TriggerAddCondition(DetectTrigger, Condition(function thistype.onDetect))
            loop
                set fog = FirstOfGroup(DetectGroup)
                exitwhen (fog == null)
                call GroupRemoveUnit(DetectGroup, fog)
                call GroupAddUnit(RecycleGroup, fog)
                call TriggerRegisterUnitEvent(DetectTrigger, fog, EVENT_UNIT_ACQUIRED_TARGET)
            endloop
            // Recycle group
            set TempGroup    = DetectGroup
            set DetectGroup  = RecycleGroup
            set RecycleGroup = TempGroup
       
        endmethod
   
        static method initializeSpell takes nothing returns nothing
   
            local unit    fog
            local group   g = CreateGroup()
            local trigger t = CreateTrigger()
            local integer i = 0
            local integer data
            local player  p
       
            // Prepare triggers
            set OrderTrigger = CreateTrigger()
            loop
                exitwhen (i == bj_MAX_PLAYER_SLOTS)
                set p = Player(i)
                call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ATTACKED, null)
                call TriggerRegisterPlayerUnitEvent(OrderTrigger, p, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
                set i = i + 1
            endloop
       
            call EnableTrigger(DetectTrigger)
            call TriggerAddCondition(t, Condition(function thistype.onAttack))
            call TriggerAddCondition(OrderTrigger,  Condition(function thistype.onOrder))
            call TriggerAddCondition(DetectTrigger, Condition(function thistype.onDetect))
            call RegisterUnitIndexEvent(Condition(function thistype.onDeindex), EVENT_UNIT_DEINDEX)
       
            // Periodically reset detection trigger to prevent event leaks
            call TimerStart(ResetTimer, TRIGGER_RESET_INTERVAL, true, function thistype.resetTrigger)
            set DummyCaster = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_CASTER_ID, 0, 0, 0)
            call UnitAddAbility(DummyCaster, PAUSE_SPELL_ID)
       
            call GroupEnumUnitsInRect(g, GetWorldBounds(), null)
            loop
                set fog = FirstOfGroup(g)
                exitwhen (fog == null)
                call GroupRemoveUnit(g, fog)
                if (IsUnitIndexed(fog) and GetUnitAbilityLevel(fog, MAIN_SPELL_ID) > 0) then
                    set data = GetUnitUserData(fog)
                    // Add to detection trigger
                    set CooldownTimer[data] = NewTimer()
                    set TriggerTimer[data]  = NewTimerEx(data)
                    // Preserve timers that will be used frequently
                    call TriggerRegisterUnitEvent(DetectTrigger, fog, EVENT_UNIT_ACQUIRED_TARGET)
                    call GroupAddUnit(DetectGroup, fog)
                endif
            endloop
            call DestroyGroup(g)
            set g = null
       
        endmethod
   
        static method onLearn takes nothing returns boolean
       
            local integer data
            local unit u
       
            if (GetLearnedSkill() == MAIN_SPELL_ID) then
                if (IsTriggerEnabled(DetectTrigger)) then
                    set u = GetTriggerUnit()
                    if (GetUnitAbilityLevel(u, MAIN_SPELL_ID) == 1) then
                        set data = GetUnitUserData(u)
                        // Add to detection trigger
                        set CooldownTimer[data] = NewTimer()
                        set TriggerTimer[data]  = NewTimerEx(data)
                        // Preserve timers that will be used frequently
                        call TriggerRegisterUnitEvent(DetectTrigger, u, EVENT_UNIT_ACQUIRED_TARGET)
                        call GroupAddUnit(DetectGroup, u)
                    endif
                    set u = null
                else
                    // The first time the spell appears in game, initialize the spell trigger
                    call initializeSpell()
                endif
            endif
       
            return false
        endmethod
   
        static method onIndex takes nothing returns boolean
       
            local integer data
            local unit u
       
            if (IsTriggerEnabled(DetectTrigger)) then
                set u = GetIndexedUnit()
                if (GetUnitAbilityLevel(u, MAIN_SPELL_ID) > 0) then
                    set data = GetIndexedUnitId()
                    // Preserve timers that will be used frequently
                    set CooldownTimer[data] = NewTimer()
                    set TriggerTimer[data]  = NewTimerEx(data)
                    // Add to detection trigger
                    call TriggerRegisterUnitEvent(DetectTrigger, u, EVENT_UNIT_ACQUIRED_TARGET)
                    call GroupAddUnit(DetectGroup, u)
                endif
                set u = null
            else
                // The first time the spell appears in game, initialize the spell trigger
                call initializeSpell()
            endif
       
            return false
        endmethod
   
        static method onInit takes nothing returns nothing
   
            local trigger t = CreateTrigger()
       
            call DisableTrigger(DetectTrigger)
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
            call TriggerAddCondition(t, Condition(function thistype.onLearn))
            call RegisterUnitIndexEvent(Condition(function thistype.onIndex), EVENT_UNIT_INDEX)
       
        endmethod
   
    endstruct

endscope
JASS:
scope Turmoil

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                    Turmoil v1.0
//                    ¯¯¯¯¯¯¯¯¯¯¯¯
//        
//      I. Description
//          Creates a cloud of chaos with 400 radius around the  hero.
//          All units within the cloud area will be forced to join the
//          turmoil. Involved units are silenced, attack twice faster,
//          have  their armor reduced by 5, and become  uncontrollable
//          by player. They will keep fighting random enemy unit until
//          the  brawl  is  over. Swashbuckler will gain  50%  evasion
//          inside the cloud area. Lasts for 10 seconds.
//          
//      II. Requirements
//          • TimerUtils        by Vexorian     | wc3c.net/showthread.php?t=101322
//          • Missile           by BPower       | hiveworkshop.com/threads/missile.265370/
//          • UnitIndexer       by TriggerHappy | hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
//          • IsTerrainWalkable by Anitarf      | hiveworkshop.com/threads/snippet-isterrainwalkable.251774/
//          • Stack             by Nestharus    | github.com/nestharus/JASS/tree/master/jass/Data%20Structures/Stack
//            
//      III. How to import
//          • Import dummy.mdx from import manager to your map. Other files are optional
//          • Import the following object data to your map:
//              (Unit)
//                  • Dummy Caster
//                  • Turmoil (Cloud)
//              (Ability)
//                  • Turmoil (Armor Penalty)
//                  • Turmoil (Evasion Hider)
//                  • Turmoil (Evasion)
//                  • Turmoil (Silence)
//                  • Turmoil (Main)
//              (Buff)
//                  • Turmoil (Buff)
//          • Make sure "Turmoil (Silence)" ability has "Turmoil (Buff)" as buff
//          • Make sure "Turmoil (Evasion Hider)" ability has "Turmoil (Evasion)" ablity in its "Spell List"
//          • Import and configure required libraries properly
//          • Import Persecute trigger and configure it properly
//            
//      IV. Credits
//          • HappyCockroach : spell concept, SFX_Turmoil.mdx
//          • Nestharus      : Stack library
//          • BPower         : Missile library
//          • TriggerHappy   : UnitIndexer library
//          • Anitarf        : IsTerrainWalkable library
//          • Vexorian       : TimerUtils library, dummy.mdx
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//                          Configurations
//                          ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        globals
//          1. Turmoil main ability raw code
            private constant integer MAIN_SPELL_ID                = 'A004'
//
//          2. Turmoil (Silence) ability raw code
            private constant integer SILENCE_SPELL_ID             = 'A005'
//
//          3. Turmoil (Armor Penalty) ability raw code
            private constant integer ARMOR_PENALTY_SPELL_ID       = 'A006'
//
//          4. Turmoil (Evasion Hider) ability raw code
            private constant integer EVASION_HIDER_SPELL_ID       = 'A008'
//
//          5. Turmoil (Buff) ability raw code
            private constant integer SPELL_BUFF_ID                = 'B001'
//
//          6. Dummy Caster unit raw code
            private constant integer DUMMY_CASTER_ID              = 'h000'
//
//          7. Turmoil (Cloud) unit raw code
            private constant integer CLOUD_DUMMY_ID               = 'h004'
//
//          8. Turmoil (Silence) ability order id
            private constant integer SILENCE_ORDER_ID             = 852668 // "soulburn"
//
//          9. If true, evasion will be given to allies as well
            private constant boolean EVASION_BONUS_TO_ALLY        = true
//
//          10. If true, all units will attack totally random target, no regards to allies
            private constant boolean ALLOW_ATTACK_ALLY            = false
//
//          11. Cloud model size (radius)
            private constant real    CLOUD_RADIUS                 = 101.0
//
//          12. Cloud flying height
            private constant real    CLOUD_HEIGHT                 = 75.0
//
//          13. Unit distribution zone reduction, preventing them to leave cloud area while
//              looking for random spot
            private constant real    DISTRIBUTION_RANGE_REDUCTION = 50.0
//
//          14. Maximum target looking retry count for every unit, preventing lag or even thread crash
            private constant integer DISTRIBUTION_MAX_RETRY_COUNT = 5
//
//          15. Maximum attack unit attack range inside the cloud, no exception for ranged units
            private constant real    MAXIMUM_ATTACK_RANGE         = 128.0
//
//          16. Minimum flying height of unit to be considered as flying unit
            private constant real    MINIMUM_FLYING_UNIT_HEIGHT   = 5.0
//
//          17. After this period, every unit entering the cloud will be a free fighter
//              Means it doesn't need to be paired
            private constant real    DUEL_PAIRING_PERIOD          = 1.0
//
//          18. After this period, every unpaired affected unit will become a free fighter
            private constant real    UNPAIRED_PERIOD_TOLERANCE    = 3.0
//
//          19. Accuracy on looking for walkable spot, preventing units from stucking
//              The lower the more precise, but slower
            private constant real    TERRAIN_DETECTION_ACC        = 15.0*bj_DEGTORAD
//
//          20. Minimum units jumping trajectory arc
            private constant real    MIN_JUMP_ARC                 = 45.0*bj_DEGTORAD
//
//          21. Maximum units jumping trajectory arc
            private constant real    MAX_JUMP_ARC                 = 70.0*bj_DEGTORAD
//
//          22. Units jumping speed
            private constant real    JUMP_SPEED                   = 15.0
//            
//          Better not to modify this
            private constant real    INTERVAL                     = 0.1
        endglobals
//            
//      B. Dynamic configurations
//            
//          23. Radius of the spell
            private constant function CloudAoE takes integer level returns real
                return 400.0
            endfunction
//            
//          24. Duration of the spell
            private constant function CloudDuration takes integer level returns real
                return 10.0
            endfunction
//            
//          25. Classification of unit that can be affected by the cloud
//              • target = target
//              • caster = owner of cloud caster
//              Current targets: non-structure, non-mechanical, ground, amphibious
            private constant function SpellTargets takes unit target, player caster returns boolean
                return not IsUnitType(target, UNIT_TYPE_STRUCTURE) and not IsUnitType(target, UNIT_TYPE_MECHANICAL) and (IsUnitType(target, UNIT_TYPE_GROUND) or not IsUnitType(target, UNIT_TYPE_FLYING))
            endfunction
           
            private function AllowJump takes unit whichUnit returns boolean
                // If not snared and not stunned
                return GetUnitAbilityLevel(whichUnit, 'B000') == 0 and GetUnitAbilityLevel(whichUnit, 'BPSE') == 0
            endfunction
//
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
    native UnitAlive takes unit id returns boolean
   
    globals
        private constant integer ATTACK_ORDER_ID = 851983 // "attack"
        private constant integer MOVE_ORDER_ID   = 851986 // "move"
        private constant integer STOP_ORDER_ID   = 851972 // "stop"
    endglobals
       
    private keyword Turmoil
   
    private struct TurmoilAI
       
        unit    unit
        unit    target
        real    duration
        player  owner
        integer dex
       
        boolean free        // Free unit has freedom to attack whoever it wants
        boolean passive     // Passive unit waits for its challenger to come
        boolean affected
        boolean jumping
        boolean evasion
        boolean isPeon
       
        Turmoil source
       
        static unit    DummyCaster
        static trigger OrderTrigger  = CreateTrigger()
        static integer InstanceCount = 0
       
        static thistype array Instance  // For faster unit randomization
        static thistype array UnitIndex
       
        // Returns random target from affected units without any thread crash possibility
        method getTarget takes nothing returns unit
           
            local integer i = GetRandomInt(1, InstanceCount)
            local integer j = i
           
            loop
                // Check if target is classified
                exitwhen ((.free or (not Instance[i].passive and not UnitAlive(Instance[i].target)))/*
                        */ and Instance[i].unit != .unit and UnitAlive(Instance[i].unit) and/*
                        */ (ALLOW_ATTACK_ALLY or not IsUnitAlly(.unit, Instance[i].owner)) and/*
                        */ Instance[i].source == .source)
                set i = i + 1
                if (i > InstanceCount) then
                    set i = 1
                endif
                if (i == j) then
                    return null
                endif
            endloop
           
            return Instance[i].unit
        endmethod
       
        static method onRemove takes Missile missile returns boolean
       
            local thistype this = missile.data
           
            // Prevent unit from returning back to it's prev location
            call DisableTrigger(OrderTrigger)
            call IssueImmediateOrderById(.unit, STOP_ORDER_ID)
            call EnableTrigger(OrderTrigger)
            set .jumping = false
           
            return true
        endmethod
       
        implement MissileStruct
       
        static method onPeriodic takes nothing returns nothing
           
            local timer    t    = GetExpiredTimer()
            local thistype this = GetTimerData(t)
            local Missile  missile
            local integer  data
           
            local real angle
            local real ax
            local real ay
            local real tx
            local real ty
            local real x
            local real y
            local real a
           
            // If unit is still alive and still affected
            if (UnitAlive(.unit) and .affected) then
                // If don't have any target
                if (not UnitAlive(.target)) then
                    if (.free) then
                        set .target = getTarget()
                    elseif (.target != null) then
                        set .free = true
                    elseif (.passive) then
                        set .target = getTarget()
                        set data = GetUnitUserData(.target)
                        set UnitIndex[data].target  = .unit
                        set UnitIndex[data].passive = false
                    endif
                    if (.duration > INTERVAL) then
                        set .duration = .duration - INTERVAL
                    elseif (not .free) then
                        set .free = true
                    endif
                // If both unit and target is not jumping
                elseif (not .jumping and not UnitIndex[GetUnitUserData(.target)].jumping) then
                    set ax = GetUnitX(.unit)
                    set ay = GetUnitY(.unit)
                    set tx = GetUnitX(.target)
                    set ty = GetUnitY(.target)
                    // Attack target must within MAXIMUM_ATTACK_RANGE no exception for ranged units
                    if ((tx-ax)*(tx-ax)+(ty-ay)*(ty-ay) <= MAXIMUM_ATTACK_RANGE*MAXIMUM_ATTACK_RANGE) then
                        call DisableTrigger(OrderTrigger)
                        call IssueTargetOrderById(.unit, ATTACK_ORDER_ID, .target)
                        call EnableTrigger(OrderTrigger)
                    elseif (.free or not .passive) then
                        set angle = Atan2(ty-ay, tx-ax)
                        call SetUnitFacing(.unit, angle*bj_RADTODEG)
                        // All units with flying height higher than the following value will be
                        // considered as flying unit
                        if (GetUnitFlyHeight(.unit) > MINIMUM_FLYING_UNIT_HEIGHT or not AllowJump(.unit)) then
                            call DisableTrigger(OrderTrigger)
                            call IssueTargetOrderById(.unit, MOVE_ORDER_ID, .target)
                            call EnableTrigger(OrderTrigger)
                        else
                            set a = 0
                            loop
                                set x = tx-MAXIMUM_ATTACK_RANGE*Cos(angle+a)
                                set y = ty-MAXIMUM_ATTACK_RANGE*Sin(angle+a)
                                if (IsTerrainWalkable(x, y)) then
                                    set .jumping      = true
                                    set missile       = Missile.createEx(.unit, x, y, 0)
                                    set missile.arc   = GetRandomReal(MIN_JUMP_ARC, MAX_JUMP_ARC)
                                    set missile.speed = JUMP_SPEED
                                    set missile.data  = this
                                    call launch(missile)
                                    exitwhen true
                                endif
                                set a = a+TERRAIN_DETECTION_ACC
                                exitwhen (a >= bj_PI*2)
                            endloop
                        endif
                    endif
                endif
                set .affected = false
            else
                if (not .isPeon) then
                    call UnitRemoveType(.unit, UNIT_TYPE_PEON)
                endif
                // Reset affected unit
                call SetUnitPathing(.unit, true)
                call UnitRemoveAbility(.unit, SPELL_BUFF_ID)
                call UnitRemoveAbility(.unit, ARMOR_PENALTY_SPELL_ID)
                call DisableTrigger(OrderTrigger)
                call IssueImmediateOrderById(.unit, STOP_ORDER_ID)
                call EnableTrigger(OrderTrigger)
                if (.evasion) then
                    call UnitRemoveAbility(.unit, EVASION_HIDER_SPELL_ID)
                endif
               
                // Dispose this instance
                set UnitIndex[GetUnitUserData(.unit)] = 0
                set Instance[.dex] = Instance[InstanceCount]
                set InstanceCount  = InstanceCount - 1
                call ReleaseTimer(t)
                call deallocate()
                set .target = null
                set .unit   = null
            endif
            set t = null
           
        endmethod
       
        // Method to handle orders given by player upon affected units
        static method onOrder takes nothing returns boolean
           
            local thistype this = UnitIndex[GetUnitUserData(GetTriggerUnit())]
           
            if (this != 0) then
                call DisableTrigger(OrderTrigger)
                if (UnitAlive(.target)) then
                    if (.jumping) then
                        call IssueImmediateOrderById(.unit, STOP_ORDER_ID)
                    elseif (GetUnitFlyHeight(.unit) > MINIMUM_FLYING_UNIT_HEIGHT) then
                        call IssueTargetOrderById(.unit, MOVE_ORDER_ID, .target)
                    else
                        call IssueTargetOrderById(.unit, ATTACK_ORDER_ID, .target)
                    endif
                else
                    // I use this instead because "stop" order does not work here                                                                            damnit, blizz
                    call IssueTargetOrderById(.unit, MOVE_ORDER_ID, .unit)
                endif
                call EnableTrigger(OrderTrigger)
            endif
           
            return false
        endmethod
       
        // Add a unit into the chaos!
        static method add takes unit whichUnit, Turmoil source returns nothing
           
            local thistype this
            local Missile  missile
            local integer  data = GetUnitUserData(whichUnit)
            local integer  c
           
            local real d
            local real a
            local real x
            local real y
           
            // If not affected yet
            if (UnitIndex[data] == 0) then
                set this                    = allocate()
                set InstanceCount           = InstanceCount + 1
                set Instance[InstanceCount] = this
                set UnitIndex[data]         = this
               
                set .dex      = InstanceCount
                set .unit     = whichUnit
                set .source   = source
                set .owner    = GetOwningPlayer(whichUnit)
                set .duration = UNPAIRED_PERIOD_TOLERANCE
                set .free     = (source.pairingDur <= INTERVAL)
                set .passive  = (InstanceCount-(InstanceCount/2)*2 == 0)
                set .affected = true
                static if EVASION_BONUS_TO_ALLY then
                    set .evasion = IsUnitAlly(whichUnit, source.owner)
                else
                    set .evasion = (whichUnit == source.evasion)
                endif
                   
                // Prepare the affected unit for the chaos!
                call SetUnitPathing(whichUnit, false)
                if (.evasion) then
                    call UnitAddAbility(whichUnit, EVASION_HIDER_SPELL_ID)
                endif
                call UnitAddAbility(whichUnit, ARMOR_PENALTY_SPELL_ID)
                call IssueImmediateOrderById(whichUnit, STOP_ORDER_ID)
                call IssueTargetOrderById(DummyCaster, SILENCE_ORDER_ID, whichUnit)
               
                // If a passive or free fighter, distribute to random spot for the duel
                if (.passive and not .free) then
                    set c = 0
                    loop
                        set c = c + 1
                        set a = GetRandomReal(-bj_PI, bj_PI)
                        set d = GetRandomReal(0, source.aoe-DISTRIBUTION_RANGE_REDUCTION)
                        set x = source.targetX-d*Cos(a)
                        set y = source.targetY-d*Sin(a)
                        if (IsTerrainWalkable(x, y)) then
                            if (GetUnitFlyHeight(.unit) > MINIMUM_FLYING_UNIT_HEIGHT or not AllowJump(.unit)) then
                                call DisableTrigger(OrderTrigger)
                                call IssuePointOrderById(.unit, MOVE_ORDER_ID, x, y)
                                call EnableTrigger(OrderTrigger)
                            else
                                set .jumping      = true
                                set missile       = Missile.createEx(.unit, x, y, 0)
                                set missile.arc   = GetRandomReal(MIN_JUMP_ARC, MAX_JUMP_ARC)
                                set missile.speed = JUMP_SPEED
                                set missile.data  = this
                                call launch(missile)
                            endif
                            exitwhen true
                        endif
                        exitwhen (c == DISTRIBUTION_MAX_RETRY_COUNT)
                    endloop
                else
                    set .jumping = false
                endif
               
                // Disable auto-attacking
                set .isPeon = IsUnitType(whichUnit, UNIT_TYPE_PEON)
                if (not .isPeon) then
                    call UnitAddType(whichUnit, UNIT_TYPE_PEON)
                endif
                static if not (LIBRARY_AutoFly) then
                    if (UnitAddAbility(whichUnit, 'Amrf') and UnitRemoveAbility(whichUnit, 'Amrf')) then
                    endif
                endif
                call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.onPeriodic)
            else
                set UnitIndex[data].affected = true
            endif
           
        endmethod
           
        static method onInit takes nothing returns nothing
       
            local integer i = 0
            local player  p

            loop
                exitwhen (i == bj_MAX_PLAYER_SLOTS)
                set p = Player(i)
                call TriggerRegisterPlayerUnitEvent(OrderTrigger, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
                call TriggerRegisterPlayerUnitEvent(OrderTrigger, p, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
                call TriggerRegisterPlayerUnitEvent(OrderTrigger, p, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
                call SetPlayerAbilityAvailable(p, EVASION_HIDER_SPELL_ID, false)
                set i = i + 1
            endloop
            call TriggerAddCondition(OrderTrigger, Condition(function thistype.onOrder))
       
            // Create dummy caster to apply buffs
            set DummyCaster = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_CASTER_ID, 0, 0, 0)
            call UnitAddAbility(DummyCaster, SILENCE_SPELL_ID)
           
        endmethod
       
    endstruct
   
    private struct Clouds extends array
        unit dummy
        implement Stack
    endstruct
   
    private struct TurmoilCloud
       
        Clouds c
       
        static real array WorldBounds
        static constant real TAU = bj_PI*2
       
        method destroy takes nothing returns nothing
       
            local Clouds node = .c.first
           
            loop
                exitwhen node == 0
                call KillUnit(node.dummy)
                set node.dummy = null
                set node = node.next
            endloop
            call deallocate()
           
        endmethod
       
        static method create takes Turmoil source returns thistype
           
            local Clouds   node
            local thistype this = allocate()
            local real d = CLOUD_RADIUS
            local real a
            local real r
            local real x
            local real y
           
            set .c   = Clouds.create()
            // Create the middle cloud
            set node = .c.push()
            set node.dummy = CreateUnit(source.owner, CLOUD_DUMMY_ID, source.targetX, source.targetY, GetRandomInt(0, 3)*90)
            static if not LIBRARY_AutoFly then
                if (UnitAddAbility(node.dummy, 'Amrf') and UnitRemoveAbility(node.dummy, 'Amrf')) then
                endif
            endif
            call SetUnitFlyHeight(node.dummy, CLOUD_HEIGHT, 0)
            call SetUnitAnimation(node.dummy, "birth")
            call QueueUnitAnimation(node.dummy, "stand")
            loop
                exitwhen d > source.aoe
                set a = 0
                // Calculate radial spacing between clouds
                set r = Asin(CLOUD_RADIUS/d)
                loop
                    exitwhen a >= TAU
                    set x = source.targetX+d*Cos(a)
                    set y = source.targetY+d*Sin(a)
                    if (x < WorldBounds[0] and x > WorldBounds[1] and y < WorldBounds[2] and y > WorldBounds[3]) then
                        set node = .c.push()
                        set node.dummy = CreateUnit(source.owner, CLOUD_DUMMY_ID, x, y, GetRandomInt(0, 3)*90)
                        static if (not LIBRARY_AutoFly) then
                            if (UnitAddAbility(node.dummy, 'Amrf') and UnitRemoveAbility(node.dummy, 'Amrf')) then
                            endif
                        endif
                        call SetUnitFlyHeight(node.dummy, CLOUD_HEIGHT, 0)
                        call SetUnitAnimation(node.dummy, "birth")
                        call QueueUnitAnimation(node.dummy, "stand")
                    endif
                    set a = a + r
                endloop
                // Go to next layer
                set d = d + CLOUD_RADIUS
            endloop
           
            return this
        endmethod
       
        static method onInit takes nothing returns nothing
       
            local rect r = GetWorldBounds()
           
            set WorldBounds[0] = GetRectMaxX(r)
            set WorldBounds[1] = GetRectMinX(r)
            set WorldBounds[2] = GetRectMaxY(r)
            set WorldBounds[3] = GetRectMinY(r)
            set r = null
           
        endmethod
       
    endstruct
   
    private struct Turmoil
       
        TurmoilCloud cloud
        player owner
        unit   caster
       
        real aoe
        real targetX
        real targetY
        real duration
        real pairingDur
       
        static group TempGroup = CreateGroup()
       
        static method onPeriodic takes nothing returns nothing
           
            local timer    t    = GetExpiredTimer()
            local thistype this = GetTimerData(t)
            local integer  data
            local unit     fog
           
            if (.duration > INTERVAL) then
                set .duration = .duration - INTERVAL
                if (.pairingDur > INTERVAL) then
                    set .pairingDur = .pairingDur - INTERVAL
                endif
                // Iterate through all units withing the cloud area
                call GroupEnumUnitsInRange(TempGroup, .targetX, .targetY, .aoe, null)
                loop
                    set fog = FirstOfGroup(TempGroup)
                    exitwhen (fog == null)
                    call GroupRemoveUnit(TempGroup, fog)
                    if (UnitAlive(fog) and SpellTargets(fog, .owner)) then
                        call TurmoilAI.add(fog, this)
                    endif
                endloop
            else
                // Dispose this instance
                call UnitRemoveAbility(.caster, EVASION_HIDER_SPELL_ID)
                call .cloud.destroy()
                call ReleaseTimer(t)
                call deallocate()
                set .caster = null
            endif
            set t = null
           
        endmethod
       
        static method onCast takes nothing returns boolean
           
            local thistype this
            local integer  level
           
            if (GetSpellAbilityId() == MAIN_SPELL_ID) then
                set this        = allocate()
                set .caster     = GetTriggerUnit()
                set .owner      = GetTriggerPlayer()
                set .targetX    = GetUnitX(.caster)
                set .targetY    = GetUnitY(.caster)
                set .pairingDur = DUEL_PAIRING_PERIOD
               
                set level       = GetUnitAbilityLevel(.caster, MAIN_SPELL_ID)
                set .aoe        = CloudAoE(level)
                set .duration   = CloudDuration(level)
                set .cloud      = TurmoilCloud.create(this)
                call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.onPeriodic)
            endif
           
            return false
        endmethod
       
        static method onInit takes nothing returns nothing

            local trigger t = CreateTrigger()
           
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t,  Condition(function thistype.onCast))
           
        endmethod
       
    endstruct
   
endscope

Attachments

  • indent right.png
    indent right.png
    2.7 KB · Views: 9,286
Last edited:
Top