• 🏆 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!

[Solved] Periodic Repeating Functions

Status
Not open for further replies.
UPDATE: Working code. Requires this in a separate trigger to work:
JASS:
//! textmacro NewIndexGenerator takes NAME
struct $NAME$
    public static method create takes nothing returns VolleyInstance
        return .allocate()
    endmethod
    public method destroy takes nothing returns nothing
        call this.deallocate()
    endmethod
endstruct
//! endtextmacro

JASS:
scope VolleyAttackScope initializer InitTrig_VolleyAttacks

    globals
    
        private trigger VolleyAttacks = CreateTrigger()
        private group grp = CreateGroup()
        private unit array MissileTarget
        private real array MissileScale
        
        private unit array TimerSource
        private unit array TimerTarget
        private real array TimerDamage
        private real array TimerDistance
        private integer array TimerCount
        
    endglobals

    
    //========================================================================
    // Filtering and AoE Damage Functions
    
    
    // Filter Alive, Enemies, Vulnerable Targets, and Visible Targets
    private function ViableTargets takes integer missile, unit target, player owner returns boolean
        return UnitAlive(target) and (IsUnitEnemy(target, owner) or (IsUnitAlly(target, owner) and target == MissileTarget[missile]) ) and /* [Target is Alive and an Enemy]
               [Vulnerable] */GetUnitAbilityLevel(target, 'Bvul') <= 0 and GetUnitAbilityLevel(target, 'Avul') <= 0 and /*
               [Visible]    */not IsUnitInvisible(target, owner)
    endfunction
    
    // if target is a ground unit or a structure
    private function IsGround takes unit u returns boolean
        return IsUnitType(u, UNIT_TYPE_GROUND) or IsUnitType(u, UNIT_TYPE_STRUCTURE)// and not IsUnitType(u, UNIT_TYPE_FLYING))
    endfunction
    
    // if target is a flying unit
    private function IsFlying takes unit u returns boolean
        return IsUnitType(u, UNIT_TYPE_FLYING) // assuming this include flying buildings?
    endfunction
    
    // Damage Ground Units AOE
    private function GroundDamage takes unit s, integer m, real AoE, real d, real x, real y, player o, attacktype AttackType returns nothing
        local unit FoG=null
        call GroupEnumUnitsInRange(grp,x,y,AoE,null)
        loop
            set FoG=FirstOfGroup(grp)
            exitwhen FoG==null
            if IsUnitType(FoG, UNIT_TYPE_GROUND) and ViableTargets(m,FoG,o) then
                set udg_NextDamageType = udg_DamageTypeCode
                call UnitDamageTarget( s, FoG, d, true, true, AttackType, DAMAGE_TYPE_UNIVERSAL, null )
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            call GroupRemoveUnit(grp,FoG)
        endloop
    endfunction
    
    // Damage Flying Units AOE
    private function FlyingDamage takes unit s, integer m, real AoE, real d, real x, real y, player o, attacktype AttackType returns nothing
        local unit FoG=null
        call GroupEnumUnitsInRange(grp,x,y,AoE,null)
        loop
            set FoG=FirstOfGroup(grp)
            exitwhen FoG==null
            if IsUnitType(FoG, UNIT_TYPE_FLYING) and ViableTargets(m,FoG,o) then
                set udg_NextDamageType = udg_DamageTypeCode
                call UnitDamageTarget( s, FoG, d, true, true, AttackType, DAMAGE_TYPE_UNIVERSAL, null )
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            call GroupRemoveUnit(grp,FoG)
        endloop
    endfunction
    
    // Damage Ground and Flying simultaneously
    private function AllDamage takes unit s, integer m, real AoE, real d, real x, real y, player o, attacktype AttackType returns nothing
        local unit FoG=null
        call GroupEnumUnitsInRange(grp,x,y,AoE,null)
        loop
            set FoG=FirstOfGroup(grp)
            exitwhen FoG==null
            if ViableTargets(m,FoG,o) then
                set udg_NextDamageType = udg_DamageTypeCode
                call UnitDamageTarget( s, FoG, d, true, true, AttackType, DAMAGE_TYPE_UNIVERSAL, null )
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            call GroupRemoveUnit(grp,FoG)
        endloop
    endfunction
    
    
    
    //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    /* VOLLEY MISSILES */
    //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    private struct VolleyAttack extends array
        
        
        //========================================================================
        // onDestructable
        static method onDestructable takes Missile this, destructable hit returns boolean
            
            local integer id = GetUnitUserData(this.source)
            
            if udg_VA_Config_AllowDESTRUCTIBLE[id] then
                call UnitDamageTarget(this.source, hit, this.damage, true, true, udg_VA_Config_AttackType[id], DAMAGE_TYPE_UNIVERSAL, null)
                if udg_VA_Config_DeathOnCollision[id] then
                    return true
                else
                    return false
                endif
            endif
            return false
        endmethod
        
        
        //========================================================================
        // onCollide
        private static method onCollide takes Missile this, unit hit returns boolean
            
            local integer id = GetUnitUserData(this.source)
            
            if udg_VA_Config_MissileCollisionSize[id] > 0.00 then
                if ViableTargets(this, hit, this.owner) then

                    // if missile can only hit ground units or structures
                    if udg_VA_AllowGROUND[this] and not udg_VA_AllowFLYING[this] and IsGround(hit) then
                        if udg_VA_Config_MissileImpactAOE[id] > 0.00 then
                            call GroundDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner, udg_VA_Config_AttackType[id])
                        else
                            set udg_NextDamageType = udg_DamageTypeCode
                            call UnitDamageTarget(this.source, hit, this.damage, true, true, udg_VA_Config_AttackType[id], DAMAGE_TYPE_UNIVERSAL, null)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                        if udg_VA_Config_DeathOnCollision[id] then
                            return true
                        else
                            return false
                        endif
                        
                    // if missile can only hit flying units
                    elseif not udg_VA_AllowGROUND[this] and udg_VA_AllowFLYING[this] and IsFlying(hit) then
                        if udg_VA_Config_MissileImpactAOE[id] > 0.00 then
                            call FlyingDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner, udg_VA_Config_AttackType[id])
                        else
                            set udg_NextDamageType = udg_DamageTypeCode
                            call UnitDamageTarget(this.source, hit, this.damage, true, true, udg_VA_Config_AttackType[id], DAMAGE_TYPE_UNIVERSAL, null)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                        if udg_VA_Config_DeathOnCollision[id] then
                            return true
                        else
                            return false
                        endif
                        
                    // if missile can hit both ground units (and structures) and flying units. This is in the event a unit is both ground and air.
                    elseif udg_VA_AllowGROUND[this] and udg_VA_AllowFLYING[this] and IsFlying(hit) or IsGround(hit) then
                        if udg_VA_Config_MissileImpactAOE[id] > 0.00 then
                            call AllDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner, udg_VA_Config_AttackType[id])
                        else
                            set udg_NextDamageType = udg_DamageTypeCode
                            call UnitDamageTarget(this.source, hit, this.damage, true, true, udg_VA_Config_AttackType[id], DAMAGE_TYPE_UNIVERSAL, null)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                        if udg_VA_Config_DeathOnCollision[id] then
                            return true
                        else
                            return false
                        endif
                    endif
                    return false
     
                endif
            endif
        return false 
        endmethod
        
        
        //========================================================================
        // onPeriod
        private static method onPeriod takes Missile this returns boolean
            local integer id = GetUnitUserData(this.source)
            
            if udg_VA_Config_MissileScale_INC[id] != 0.00 then
                set MissileScale[this] = MissileScale[this] + udg_VA_Config_MissileScale_INC[id]
                call SetUnitScale(this.dummy, MissileScale[this], MissileScale[this], MissileScale[this])
            endif
            
            if udg_VA_Config_MissileCollision_INC[id] != 0.00 then
                set this.collision = this.collision + udg_VA_Config_MissileCollision_INC[id]
            endif
            return false
        endmethod
        
        
        //========================================================================
        // onRemove
        private static method onRemove takes Missile this returns boolean
            local integer id = GetUnitUserData(this.source)
            local effect fx = null
            
            //call SetUnitScale(this.dummy, 1.00, 1.00, 1.00)
            
            if udg_VA_Config_MissileDeath_String[id] != null then
                // if the death art is
                if udg_VA_Config_DeathArtOnGround[id] then
                    set fx = AddSpecialEffect(udg_VA_Config_MissileDeath_String[id], GetUnitX(this.dummy), GetUnitY(this.dummy))
                else
                    set fx = AddSpecialEffectTarget(udg_VA_Config_MissileDeath_String[id], this.dummy, "origin")
                endif
                call DestroyEffect(fx)
            endif
            
            return false
        endmethod
        
        
        //========================================================================
        // onFinish
        private static method onFinish takes Missile this returns boolean
            
            local integer id = GetUnitUserData(this.source)
            
            if udg_VA_Config_MissileImpactAOE[id] > 0.00 then
                if udg_VA_AllowGROUND[this] and not udg_VA_AllowFLYING[this] then
                    call GroundDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner, udg_VA_Config_AttackType[id])
                elseif not udg_VA_AllowGROUND[this] and udg_VA_AllowFLYING[this] then
                    call FlyingDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner, udg_VA_Config_AttackType[id])
                elseif udg_VA_AllowGROUND[this] and udg_VA_AllowFLYING[this] then
                    call AllDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner, udg_VA_Config_AttackType[id])
                endif
            else
                set udg_NextDamageType = udg_DamageTypeCode
                call UnitDamageTarget( this.source, this.target, this.damage, true, true, udg_VA_Config_AttackType[id], DAMAGE_TYPE_UNIVERSAL, null )
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            
            return true
        endmethod

        implement MissileStruct
        
    endstruct
    //======================================================================================================================================
    //======================================================================================================================================
    
    
    
    function VolleyCaster takes nothing returns nothing
    
        local timer t = GetExpiredTimer()
        local integer tID = GetTimerData(t)
        
        local integer id = GetUnitUserData(TimerSource[tID])
        
        local real SOURCE_X = GetUnitX(TimerSource[tID])
        local real SOURCE_Y = GetUnitY(TimerSource[tID])
        local real TARGET_X = GetUnitX(TimerTarget[tID])
        local real TARGET_Y = GetUnitY(TimerTarget[tID])
        
        local real Facing = Atan2(TARGET_Y - SOURCE_Y, TARGET_X - SOURCE_X)
        local real AngleCorrection = Facing + udg_VA_Config_LaunchAngle[id] * bj_DEGTORAD
        local real SpreadAngle = 0.00
        
        local real LAUNCH_X = 0.00
        local real LAUNCH_Y = 0.00
        local real IMPACT_X = 0.00
        local real IMPACT_Y = 0.00
        
        local real MissileAngle = 0.00
        local real MinDist = udg_VA_Config_MissileDistance[id] - udg_VA_Config_MissileDistanceVary[id]
        local real MaxDist = udg_VA_Config_MissileDistance[id] + udg_VA_Config_MissileDistanceVary[id]
        local real MinHeight = udg_VA_Config_MissileEndHeight[id] - udg_VA_Config_MissileEndHeightVary[id] + GetUnitFlyHeight(TimerTarget[tID])
        local real MaxHeight = udg_VA_Config_MissileEndHeight[id] + udg_VA_Config_MissileEndHeightVary[id] + GetUnitFlyHeight(TimerTarget[tID])
        
        local real dx = 0.00
        local real dy = 0.00
        local real MissileDistance = 0.00

        local integer MissileCount = 0
        
        local VolleyInstance volley = tID
        local Missile m
        
        set TimerDistance[tID] = TimerDistance[tID] + udg_VA_Config_MissileDistance_INC[id]
        
        loop //Missiles Per Volley Loop
        
            set MissileCount = MissileCount + 1
            exitwhen MissileCount > udg_VA_Config_MissilesPerVolley[id]
            
            set SpreadAngle = Deg2Rad(Facing + GetRandomReal(-90, 90))

            if udg_VA_Config_IgnoreTargetDistance[id] then
                set TARGET_X = SOURCE_X + Cos(Facing) * (GetRandomReal(MinDist, MaxDist) + TimerDistance[tID])
                set TARGET_Y = SOURCE_Y + Sin(Facing) * (GetRandomReal(MinDist, MaxDist) + TimerDistance[tID])
            else
                set TARGET_X = TARGET_X + Cos(Facing) * TimerDistance[tID]
                set TARGET_Y = TARGET_Y + Sin(Facing) * TimerDistance[tID]
            endif
            
            set LAUNCH_X = SOURCE_X + Cos(AngleCorrection) * udg_VA_Config_LaunchOffset[id]
            set LAUNCH_Y = SOURCE_Y + Sin(AngleCorrection) * udg_VA_Config_LaunchOffset[id]
            set IMPACT_X = TARGET_X + Cos(SpreadAngle) * GetRandomReal(0, udg_VA_Config_MissileSpread[id])
            set IMPACT_Y = TARGET_Y + Sin(SpreadAngle) * GetRandomReal(0, udg_VA_Config_MissileSpread[id])

            set dx = LAUNCH_X - TARGET_X
            set dx = LAUNCH_Y - TARGET_Y
            set MissileDistance = SquareRoot(dy*dy+dx*dx)
            
            set MissileAngle = Atan2(TARGET_Y - LAUNCH_Y, TARGET_X - LAUNCH_X)
            
            if udg_VA_Config_IsMissileGrounded[id] then
                set MinHeight = 0.00
                set MaxHeight = 0.00
            endif
        
            if udg_VA_Config_IsMissileHoming[id] then
                set m           = Missile.create(LAUNCH_X, LAUNCH_Y, udg_VA_Config_LaunchHeight[id], MissileAngle, MissileDistance,/*
                                  [Height]   */ GetRandomReal(MinHeight, MaxHeight) )
                set m.target    = TimerTarget[tID]
            else
                set m           = Missile.createXYZ(LAUNCH_X, LAUNCH_Y, udg_VA_Config_LaunchHeight[id] + GetUnitFlyHeight(TimerSource[tID]), IMPACT_X, IMPACT_Y,/*
                                  [Height]   */ GetRandomReal(MinHeight, MaxHeight) )
            endif
            
            /*call SetUnitPathing(m.dummy, false)
            call SetUnitX(m.dummy, LAUNCH_X)
            call SetUnitY(m.dummy, LAUNCH_Y)*/
            
            set m.speed         = GetRandomReal(udg_VA_Config_MissileSpeed_MIN[id], udg_VA_Config_MissileSpeed_MAX[id])
            set m.acceleration  = udg_VA_Config_MissileAcceleration[id]
            set m.scale         = GetRandomReal(udg_VA_Config_MissileScale_MIN[id], udg_VA_Config_MissileScale_MAX[id])
            set m.collision     = udg_VA_Config_MissileCollisionSize[id]
            set m.damage        = TimerDamage[tID]
            set m.model         = udg_VA_Config_MissileArtString[id]
            set m.arc           = GetRandomReal(udg_VA_Config_MissileArc_MIN[id], udg_VA_Config_MissileArc_MAX[id]) * bj_DEGTORAD
            set m.curve         = GetRandomReal(udg_VA_Config_MissileCurve_MIN[id], udg_VA_Config_MissileCurve_MAX[id]) * bj_DEGTORAD
            set m.source        = TimerSource[tID]
            set m.owner         = GetOwningPlayer(TimerSource[tID])
            set MissileScale[m] = m.scale
            set MissileTarget[m] = TimerTarget[tID]
            if udg_VA_Config_HitAllTargets[id] then
                set udg_VA_AllowFLYING[m] = true
                set udg_VA_AllowGROUND[m] = true
            else
                if IsGround(TimerTarget[tID]) then
                    set udg_VA_AllowGROUND[m] = true
                else
                    set udg_VA_AllowFLYING[m] = true
                endif
            endif
            
            call VolleyAttack.launch(m)
            
        endloop
        
        set TimerCount[tID] = TimerCount[tID] - 1
        if TimerCount[tID] <= 0 or not UnitAlive(TimerSource[tID]) then
            call ReleaseTimer(t)
            call volley.destroy()
        endif
        
    endfunction
    
    
    
    //CONDITIONS
    function Trig_VolleyAttacks_Conditions takes nothing returns boolean
        if udg_VA_UsesVolleyAttack[GetUnitUserData(udg_DamageEventSource)] and udg_DamageEventType == 0 and not udg_IsDamageSpell then
            return true
        endif
        return false
    endfunction

    
    
    //ACTIONS
    function Trig_VolleyAttacks_Actions takes nothing returns nothing 
        
        // Because volleys are on a timer, the first volley would be delayed, so the first volley launches in the actions.
        // This also removed the need to have a timer for units that launch just 1 volley, like the Rifleman
        local timer t = GetExpiredTimer()
        local integer tID = VolleyInstance.create()
        
        local integer id = GetUnitUserData(udg_DamageEventSource)
        
        local real SOURCE_X = GetUnitX(udg_DamageEventSource)
        local real SOURCE_Y = GetUnitY(udg_DamageEventSource)
        local real TARGET_X = GetUnitX(udg_DamageEventTarget)
        local real TARGET_Y = GetUnitY(udg_DamageEventTarget)
        
        local real Facing = Atan2(TARGET_Y - SOURCE_Y, TARGET_X - SOURCE_X)
        local real AngleCorrection = Facing + Deg2Rad(udg_VA_Config_LaunchAngle[id])
        local real SpreadAngle = 0.00
        
        local real LAUNCH_X = 0.00
        local real LAUNCH_Y = 0.00
        local real IMPACT_X = 0.00
        local real IMPACT_Y = 0.00
        
        local real MissileAngle = 0.00
        local real MinDist = udg_VA_Config_MissileDistance[id] - udg_VA_Config_MissileDistanceVary[id]
        local real MaxDist = udg_VA_Config_MissileDistance[id] + udg_VA_Config_MissileDistanceVary[id]
        local real MinHeight = udg_VA_Config_MissileEndHeight[id] - udg_VA_Config_MissileEndHeightVary[id] + GetUnitFlyHeight(udg_DamageEventTarget)
        local real MaxHeight = udg_VA_Config_MissileEndHeight[id] + udg_VA_Config_MissileEndHeightVary[id] + GetUnitFlyHeight(udg_DamageEventTarget)
        
        local real dx = 0.00
        local real dy = 0.00
        local real MissileDistance = 0.00

        local integer MissileCount = 0

        local Missile m
        
        set TimerDistance[tID] = udg_VA_Config_MissileDistance_INC[id]
        
        //call BJDebugMsg(R2S(GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget))))
        //call BJDebugMsg(R2S(GetReducedDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget))))
        if udg_VA_Config_SplitDmgBwMissiles[id] and not udg_VA_Config_SplitDmgBwVolleys[id] then
            set TimerDamage[tID] = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget)) / udg_VA_Config_MissilesPerVolley[id]
        elseif not udg_VA_Config_SplitDmgBwMissiles[id] and udg_VA_Config_SplitDmgBwVolleys[id] then
            set TimerDamage[tID] = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget)) / udg_VA_Config_NumberOfVolleys[id]
        elseif udg_VA_Config_SplitDmgBwMissiles[id] and udg_VA_Config_SplitDmgBwVolleys[id] then
            set TimerDamage[tID] = (GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget)) / udg_VA_Config_MissilesPerVolley[id]) / udg_VA_Config_NumberOfVolleys[id]
        elseif not udg_VA_Config_SplitDmgBwMissiles[id] and not udg_VA_Config_SplitDmgBwVolleys[id] then
            set TimerDamage[tID] = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget))
        endif
        
        loop //Missiles Per Volley Loop
        
            set MissileCount = MissileCount + 1
            exitwhen MissileCount > udg_VA_Config_MissilesPerVolley[id]
            
            set SpreadAngle = Deg2Rad(Facing + GetRandomReal(-90, 90))

            if udg_VA_Config_IgnoreTargetDistance[id] then
                set TARGET_X = SOURCE_X + Cos(Facing) * (GetRandomReal(MinDist, MaxDist) + TimerDistance[tID])
                set TARGET_Y = SOURCE_Y + Sin(Facing) * (GetRandomReal(MinDist, MaxDist) + TimerDistance[tID])
            else
                set TARGET_X = TARGET_X + Cos(Facing) * TimerDistance[tID]
                set TARGET_Y = TARGET_Y + Sin(Facing) * TimerDistance[tID]
            endif
            
            set LAUNCH_X = SOURCE_X + Cos(AngleCorrection) * udg_VA_Config_LaunchOffset[id]
            set LAUNCH_Y = SOURCE_Y + Sin(AngleCorrection) * udg_VA_Config_LaunchOffset[id]
            set IMPACT_X = TARGET_X + Cos(SpreadAngle) * GetRandomReal(0, udg_VA_Config_MissileSpread[id])
            set IMPACT_Y = TARGET_Y + Sin(SpreadAngle) * GetRandomReal(0, udg_VA_Config_MissileSpread[id])

            set dx = LAUNCH_X - TARGET_X
            set dx = LAUNCH_Y - TARGET_Y
            set MissileDistance = SquareRoot(dy*dy+dx*dx)
            
            set MissileAngle = Atan2(TARGET_Y - LAUNCH_Y, TARGET_X - LAUNCH_X)
            
            if udg_VA_Config_IsMissileGrounded[id] then
                set MinHeight = 0.00
                set MaxHeight = 0.00
            endif
        
            if udg_VA_Config_IsMissileHoming[id] then
                set m           = Missile.create(LAUNCH_X, LAUNCH_Y, udg_VA_Config_LaunchHeight[id], MissileAngle, MissileDistance,/*
                                  [Height]   */ GetRandomReal(MinHeight, MaxHeight) )
                set m.target    = udg_DamageEventTarget
            else
                set m           = Missile.createXYZ(LAUNCH_X, LAUNCH_Y, udg_VA_Config_LaunchHeight[id] + GetUnitFlyHeight(udg_DamageEventSource), IMPACT_X, IMPACT_Y,/*
                                  [Height]   */ GetRandomReal(MinHeight, MaxHeight) )
            endif
            
            /*call SetUnitPathing(m.dummy, false)
            call SetUnitX(m.dummy, LAUNCH_X)
            call SetUnitY(m.dummy, LAUNCH_Y)*/
            
            set m.speed         = GetRandomReal(udg_VA_Config_MissileSpeed_MIN[id], udg_VA_Config_MissileSpeed_MAX[id])
            set m.acceleration  = udg_VA_Config_MissileAcceleration[id]
            set m.scale         = GetRandomReal(udg_VA_Config_MissileScale_MIN[id], udg_VA_Config_MissileScale_MAX[id])
            set m.collision     = udg_VA_Config_MissileCollisionSize[id]
            set m.damage        = TimerDamage[tID]
            set m.model         = udg_VA_Config_MissileArtString[id]
            set m.arc           = GetRandomReal(udg_VA_Config_MissileArc_MIN[id], udg_VA_Config_MissileArc_MAX[id]) * bj_DEGTORAD
            set m.curve         = GetRandomReal(udg_VA_Config_MissileCurve_MIN[id], udg_VA_Config_MissileCurve_MAX[id]) * bj_DEGTORAD
            set m.source        = udg_DamageEventSource
            set m.owner         = GetOwningPlayer(udg_DamageEventSource)
            set MissileScale[m] = m.scale
            set MissileTarget[m] = udg_DamageEventTarget
            if udg_VA_Config_HitAllTargets[id] then
                set udg_VA_AllowFLYING[m] = true
                set udg_VA_AllowGROUND[m] = true
            else
                if IsGround(udg_DamageEventTarget) then
                    set udg_VA_AllowGROUND[m] = true
                else
                    set udg_VA_AllowFLYING[m] = true
                endif
            endif
            
            call VolleyAttack.launch(m)
            
        endloop
        
        set udg_DamageEventAmount = 0.00

        if udg_VA_Config_NumberOfVolleys[id] > 1 then
            set t = NewTimerEx(tID)
            set TimerSource[tID] = udg_DamageEventSource
            set TimerTarget[tID] = udg_DamageEventTarget
            set TimerCount[tID] = udg_VA_Config_NumberOfVolleys[id] - 1
            call TimerStart(t, udg_VA_Config_TimeBetweenVolleys[id], true, function VolleyCaster)
            set t = null
        endif
        
    endfunction

    
    
    //Events
    function InitTrig_VolleyAttacks takes nothing returns nothing
        call TriggerRegisterVariableEvent( VolleyAttacks, "udg_DamageModifierEvent", EQUAL, 1.00 )
        call TriggerAddCondition( VolleyAttacks, Condition(function Trig_VolleyAttacks_Conditions) )
        call TriggerAddAction( VolleyAttacks, function Trig_VolleyAttacks_Actions )
    endfunction

endscope

//! runtextmacro NewIndexGenerator("VolleyInstance")

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

I want to make a function repeat X times with Y seconds interval time. More specifically, I want to repeat the Actions (function Trig_VolleyAttacks_Actions takes nothing returns nothing):

JASS:
scope VolleyAttackScope initializer InitTrig_VolleyAttacks

    globals
        public trigger VolleyAttacks = CreateTrigger()
        public group grp = CreateGroup()
        public unit array MissileTarget
        public real array MissileScale
    endglobals

    
    //========================================================================
    // Filtering and AoE Damage Functions
    
    
    // Filter Alive, Enemies, Vulnerable Targets, and Visible Targets
    private function ViableTargets takes integer missile, unit target, player owner returns boolean
        return UnitAlive(target) and (IsUnitEnemy(target, owner) or (IsUnitAlly(target, owner) and target == MissileTarget[missile]) ) and /* [Target is Alive and an Enemy]
               [Vulnerable] */GetUnitAbilityLevel(target, 'Bvul') <= 0 and GetUnitAbilityLevel(target, 'Avul') <= 0 and /*
               [Visible]    */not IsUnitInvisible(target, owner)
    endfunction
    
    // if target is a ground unit or a structure
    private function IsGround takes unit u returns boolean
        return IsUnitType(u, UNIT_TYPE_GROUND) or IsUnitType(u, UNIT_TYPE_STRUCTURE)// and not IsUnitType(u, UNIT_TYPE_FLYING))
    endfunction
    
    // if target is a flying unit
    private function IsFlying takes unit u returns boolean
        return IsUnitType(u, UNIT_TYPE_FLYING) // assuming this include flying buildings?
    endfunction
    
    // Damage Ground Units AOE
    private function GroundDamage takes unit s, integer m, real AoE, real d, real x, real y, player o returns nothing
        local unit FoG=null
        call GroupEnumUnitsInRange(grp,x,y,AoE,null)
        loop
            set FoG=FirstOfGroup(grp)
            exitwhen FoG==null
            if IsUnitType(FoG, UNIT_TYPE_GROUND) and ViableTargets(m,FoG,o) then
                set udg_NextDamageType = udg_DamageTypeCode
                call UnitDamageTarget( s, FoG, d, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null )
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            call GroupRemoveUnit(grp,FoG)
        endloop
    endfunction
    
    // Damage Flying Units AOE
    private function FlyingDamage takes unit s, integer m, real AoE, real d, real x, real y, player o returns nothing
        local unit FoG=null
        call GroupEnumUnitsInRange(grp,x,y,AoE,null)
        loop
            set FoG=FirstOfGroup(grp)
            exitwhen FoG==null
            if IsUnitType(FoG, UNIT_TYPE_FLYING) and ViableTargets(m,FoG,o) then
                set udg_NextDamageType = udg_DamageTypeCode
                call UnitDamageTarget( s, FoG, d, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null )
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            call GroupRemoveUnit(grp,FoG)
        endloop
    endfunction
    
    // Damage Ground and Flying simultaneously
    private function AllDamage takes unit s, integer m, real AoE, real d, real x, real y, player o returns nothing
        local unit FoG=null
        call GroupEnumUnitsInRange(grp,x,y,AoE,null)
        loop
            set FoG=FirstOfGroup(grp)
            exitwhen FoG==null
            if ViableTargets(m,FoG,o) then
                set udg_NextDamageType = udg_DamageTypeCode
                call UnitDamageTarget( s, FoG, d, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null )
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            call GroupRemoveUnit(grp,FoG)
        endloop
    endfunction
    
    
    
    //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    /* VOLLEY MISSILES */
    //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    private struct VolleyAttack extends array
        
        
        //========================================================================
        // onDestructable
        static method onDestructable takes Missile this, destructable hit returns boolean
            
            local integer id = GetUnitUserData(this.source)
            
            if udg_VA_Config_AllowDESTRUCTIBLE[id] then
                call UnitDamageTarget(this.source, hit, this.damage, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                if udg_VA_Config_DeathOnCollision[id] then
                    return true
                else
                    return false
                endif
            endif
            return false
        endmethod
        
        
        //========================================================================
        // onCollide
        private static method onCollide takes Missile this, unit hit returns boolean
            
            local integer id = GetUnitUserData(this.source)
            
            if udg_VA_Config_MissileCollisionSize[id] > 0.00 then
                if ViableTargets(this, hit, this.owner) then

                    // if missile can only hit ground units or structures
                    if udg_VA_AllowGROUND[this] and not udg_VA_AllowFLYING[this] and IsGround(hit) then
                        if udg_VA_Config_MissileImpactAOE[id] > 0.00 then
                            call GroundDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner)
                        else
                            set udg_NextDamageType = udg_DamageTypeCode
                            call UnitDamageTarget(this.source, hit, this.damage, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                        if udg_VA_Config_DeathOnCollision[id] then
                            return true
                        else
                            return false
                        endif
                        
                    // if missile can only hit flying units
                    elseif not udg_VA_AllowGROUND[this] and udg_VA_AllowFLYING[this] and IsFlying(hit) then
                        if udg_VA_Config_MissileImpactAOE[id] > 0.00 then
                            call FlyingDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner)
                        else
                            set udg_NextDamageType = udg_DamageTypeCode
                            call UnitDamageTarget(this.source, hit, this.damage, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                        if udg_VA_Config_DeathOnCollision[id] then
                            return true
                        else
                            return false
                        endif
                        
                    // if missile can hit both ground units (and structures) and flying units. This is in the event a unit is both ground and air.
                    elseif udg_VA_AllowGROUND[this] and udg_VA_AllowFLYING[this] and IsFlying(hit) or IsGround(hit) then
                        if udg_VA_Config_MissileImpactAOE[id] > 0.00 then
                            call AllDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner)
                        else
                            set udg_NextDamageType = udg_DamageTypeCode
                            call UnitDamageTarget(this.source, hit, this.damage, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                        if udg_VA_Config_DeathOnCollision[id] then
                            return true
                        else
                            return false
                        endif
                    endif
                    return false
     
                endif
            endif
        return false 
        endmethod
        
        
        //========================================================================
        // onPeriod
        private static method onPeriod takes Missile this returns boolean
            local integer id = GetUnitUserData(this.source)
            
            if udg_VA_Config_MissileScale_INC[id] != 0.00 then
                set MissileScale[this] = MissileScale[this] + udg_VA_Config_MissileScale_INC[id]
                call SetUnitScale(this.dummy, MissileScale[this], MissileScale[this], MissileScale[this])
            endif
            
            if udg_VA_Config_MissileCollision_INC[id] != 0.00 then
                set this.collision = this.collision + udg_VA_Config_MissileCollision_INC[id]
            endif
            return false
        endmethod
        
        
        //========================================================================
        // onRemove
        private static method onRemove takes Missile this returns boolean
            local integer id = GetUnitUserData(this.source)
            local effect fx = null
            
            //call SetUnitScale(this.dummy, 1.00, 1.00, 1.00)
            
            if udg_VA_Config_MissileDeath_String[id] != null then
                // if the death art is
                if udg_VA_Config_DeathArtOnGround[id] then
                    set fx = AddSpecialEffect(udg_VA_Config_MissileDeath_String[id], GetUnitX(this.dummy), GetUnitY(this.dummy))
                else
                    set fx = AddSpecialEffectTarget(udg_VA_Config_MissileDeath_String[id], this.dummy, "origin")
                endif
                call DestroyEffect(fx)
            endif
            
            return false
        endmethod
        
        
        //========================================================================
        // onFinish
        private static method onFinish takes Missile this returns boolean
            
            local integer id = GetUnitUserData(this.source)
            
            if udg_VA_Config_MissileImpactAOE[id] > 0.00 then
                if udg_VA_AllowGROUND[this] and not udg_VA_AllowFLYING[this] then
                    call GroundDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner)
                elseif not udg_VA_AllowGROUND[this] and udg_VA_AllowFLYING[this] then
                    call FlyingDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner)
                elseif udg_VA_AllowGROUND[this] and udg_VA_AllowFLYING[this] then
                    call AllDamage(this.source, this, udg_VA_Config_MissileImpactAOE[id], this.damage, this.x, this.y, this.owner)
                endif
            else
                set udg_NextDamageType = udg_DamageTypeCode
                call UnitDamageTarget( this.source, this.target, this.damage, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null )
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            
            return true
        endmethod

        implement MissileStruct
        
    endstruct
    //======================================================================================================================================
    //======================================================================================================================================
    

    //I've disabled this part because I don't know if it's necessary
    //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    /* VOLLEY CAST */
    //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    /*private struct VolleyCast extends array 
    
        static method VolleyCast takes unit source, unit target, real damage, timer TIMER returns nothing
        endmethod
        
    endstruct*/
    //======================================================================================================================================
    //======================================================================================================================================
    
    /*function VolleyCaster takes unit source, unit target, real damage, integer id, integer VolleyCount, timer TIMER returns nothing
        
    endfunction*/
    
    
    
    //CONDITIONS
    function Trig_VolleyAttacks_Conditions takes nothing returns boolean
        if udg_VA_UsesVolleyAttack[GetUnitUserData(udg_DamageEventSource)] and udg_DamageEventType == 0 and not udg_IsDamageSpell then
            return true
        endif
        return false
    endfunction

    
    
    //ACTIONS
    function Trig_VolleyAttacks_Actions takes nothing returns nothing 
        
        //call VolleyCast(udg_DamageEventSource, udg_DamageEventTarget, udg_DamageEventPrevAmt, GetUnitUserData(udg_DamageEventSource)
        
        local integer id = GetUnitUserData(udg_DamageEventSource)
        local timer t = CreateTimer()
        
        local real SOURCE_X = GetUnitX(udg_DamageEventSource)
        local real SOURCE_Y = GetUnitY(udg_DamageEventSource)
        local real TARGET_X = GetUnitX(udg_DamageEventTarget)
        local real TARGET_Y = GetUnitY(udg_DamageEventTarget)
        
        local real Facing = Atan2(TARGET_Y - SOURCE_Y, TARGET_X - SOURCE_X)
        local real AngleCorrection = Facing + udg_VA_Config_LaunchAngle[id] * bj_DEGTORAD
        local real SpreadAngle = 0.00
        
        local real LAUNCH_X = 0.00
        local real LAUNCH_Y = 0.00
        local real IMPACT_X = 0.00
        local real IMPACT_Y = 0.00
        
        local real MissileAngle = 0.00
        local real MinDist = udg_VA_Config_MissileDistance[id] - udg_VA_Config_MissileDistanceVary[id]
        local real MaxDist = udg_VA_Config_MissileDistance[id] + udg_VA_Config_MissileDistanceVary[id]
        local real MinHeight = udg_VA_Config_MissileEndHeight[id] - udg_VA_Config_MissileEndHeightVary[id] + GetUnitFlyHeight(udg_DamageEventTarget)
        local real MaxHeight = udg_VA_Config_MissileEndHeight[id] + udg_VA_Config_MissileEndHeightVary[id] + GetUnitFlyHeight(udg_DamageEventTarget)
        
        local real dx = 0.00
        local real dy = 0.00
        local real MissileDistance = 0.00
        
        local integer VolleyCount = 1
        local integer MissileCount = 0
        
        local Missile m
        
        set udg_DamageEventAmount = 0.00
        
        loop //Missiles Per Volley Loop
        
            set MissileCount = MissileCount + 1
            exitwhen MissileCount > udg_VA_Config_MissilesPerVolley[id]
            
            set SpreadAngle = Deg2Rad(Facing + GetRandomReal(-90, 90))

            if udg_VA_Config_IgnoreTargetDistance[id] then
                set TARGET_X = SOURCE_X + Cos(Facing) * GetRandomReal(MinDist, MaxDist)
                set TARGET_Y = SOURCE_Y + Sin(Facing) * GetRandomReal(MinDist, MaxDist)
            endif
            
            set LAUNCH_X = SOURCE_X + Cos(Facing + AngleCorrection) * udg_VA_Config_LaunchOffset[id]
            set LAUNCH_Y = SOURCE_Y + Sin(Facing + AngleCorrection) * udg_VA_Config_LaunchOffset[id]
            set IMPACT_X = TARGET_X + Cos(SpreadAngle) * GetRandomReal(0, udg_VA_Config_MissileSpread[id])
            set IMPACT_Y = TARGET_Y + Sin(SpreadAngle) * GetRandomReal(0, udg_VA_Config_MissileSpread[id])

            set dx = LAUNCH_X - TARGET_X
            set dx = LAUNCH_Y - TARGET_Y
            set MissileDistance = SquareRoot(dy*dy+dx*dx)
            
            set MissileAngle = Atan2(TARGET_Y - LAUNCH_Y, TARGET_X - LAUNCH_X)
            
            if udg_VA_Config_IsMissileGrounded[id] then
                set MinHeight = 0.00
                set MaxHeight = 0.00
            endif
        
            if udg_VA_Config_IsMissileHoming[id] then
                set m           = Missile.create(LAUNCH_X, LAUNCH_Y, udg_VA_Config_LaunchHeight[id], MissileAngle, MissileDistance,/*
                                  [Height]   */ GetRandomReal(MinHeight, MaxHeight) )
                set m.target    = udg_DamageEventTarget
            else
                set m           = Missile.createXYZ(LAUNCH_X, LAUNCH_Y, udg_VA_Config_LaunchHeight[id] + GetUnitFlyHeight(udg_DamageEventSource), IMPACT_X, IMPACT_Y,/*
                                  [Height]   */ GetRandomReal(MinHeight, MaxHeight) )
            endif
                                  
            set m.speed         = GetRandomReal(udg_VA_Config_MissileSpeed_MIN[id], udg_VA_Config_MissileSpeed_MAX[id])
            set m.acceleration  = udg_VA_Config_MissileAcceleration[id]
            set m.scale         = GetRandomReal(udg_VA_Config_MissileScale_MIN[id], udg_VA_Config_MissileScale_MAX[id])
            set m.collision     = udg_VA_Config_MissileCollisionSize[id]
            set m.damage        = udg_DamageEventPrevAmt / udg_VA_Config_MissilesPerVolley[id]
            set m.model         = udg_VA_Config_MissileArtString[id]
            set m.arc           = GetRandomReal(udg_VA_Config_MissileArc_MIN[id], udg_VA_Config_MissileArc_MAX[id]) * bj_DEGTORAD
            set m.curve         = GetRandomReal(udg_VA_Config_MissileCurve_MIN[id], udg_VA_Config_MissileCurve_MAX[id]) * bj_DEGTORAD
            set m.source        = udg_DamageEventSource
            set m.owner         = GetOwningPlayer(udg_DamageEventSource)
            set MissileScale[m] = m.scale
            set MissileTarget[m] = udg_DamageEventTarget
            if udg_VA_Config_HitAllTargets[id] then
                set udg_VA_AllowFLYING[m] = true
                set udg_VA_AllowGROUND[m] = true
            else
                if IsGround(udg_DamageEventTarget) then
                    set udg_VA_AllowGROUND[m] = true
                else//if IsFlying(udg_DamageEventTarget) then
                    set udg_VA_AllowFLYING[m] = true
                endif
            endif
            
            call VolleyAttack.launch(m)
            
        endloop
        
        //check if there is more than 1 volley. If so, start a loop that will fire a timer for every volley.
        if udg_VA_Config_NumberOfVolleys[id] > 1 and t != GetExpiredTimer() then
            loop
            exitwhen VolleyCount > udg_VA_Config_NumberOfVolleys[id]
            set VolleyCount = VolleyCount + 1
            
                set t = NewTimerEx(id)
                call SetTimerData(t, id)
                call TimerStart(t, udg_VA_Config_TimeBetweenVolleys[id]*VolleyCount, false, function Trig_VolleyAttacks_Actions)
                set t = null
                
            endloop
        endif
        
    endfunction

    
    
    //Events
    function InitTrig_VolleyAttacks takes nothing returns nothing
        call TriggerRegisterVariableEvent( VolleyAttacks, "udg_DamageModifierEvent", EQUAL, 1.00 )
        call TriggerAddCondition( VolleyAttacks, Condition(function Trig_VolleyAttacks_Conditions) )
        call TriggerAddAction( VolleyAttacks, function Trig_VolleyAttacks_Actions )
    endfunction

endscope

As you can see, I've tried using TimerUtils to do that but I do understand timers or how to use them, so you can imagine how well that went. If you further up, you will see a struct that was commented out - this was supposed to hold the content of Trig_VolleyAttacks_Actions but I don't know how to do that either.

Basically what needs to happen is that if the variable udg_VA_Config_NumberOfVolleys[id] is higher than 1, then create a timer that repeats as many times as udg_VA_Config_NumberOfVolleys[id], with a time of udg_VA_Config_TimeBetweenVolleys[id]. Every time it expires, it calls Trig_VolleyAttacks_Actions, or the Struct VolleyCast if you think it's better to have the actions in the struct instead. It needs to be MUI.

PS: Since this resource is going to be rather performance-heavy, squeezing the most performance out of this would be most desireable. TimerTools is less performance-heavy than TimerUtils from what I've read, so if anyone can help me use that instead, it would be greatly appareciated.
 
Last edited:
I can't index the counter to the attacking unit because if a new set of volleys begins before the last one is done, the last set will be overwritten and a new one will start. So if a set launches 5 volleys and the unit attacks again before the last volley is done, that current set will only execute 4 volleys.

EDIT: Also I figured it's about time I learned how to use timers :|

Most missile systems use a stack & loop structure for missiles and don't use UnitUserData because of it. You could also use an existing missile system to handle the movement. More advanced systems, like BPower's Missile library, let you assign a function to run when there's a collision event or when the trajectory has finalized.

I don't have a problem with the missile system itself, I just need to make a function repeat X number of times.

So Wietlol and WaterKnight helped me in chat, so I guess this is solved. I'll update the OP with the corrected code once the system is done.

EDIT: OP Updated.
 
Last edited:
Status
Not open for further replies.
Top