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

[vJASS] Learning Structs

Status
Not open for further replies.
Trying to fix my Burst Laser system by remaking it with structs. I can't get the lightning effects to destroy properly, so I'm not sure what I'm doing wrong.

I'm using this mostly as a way to pre-set the variables instead of having to add a million things when calling StructName.create()
JASS:
library BL initializer BurstLaser_Setup// requires IdAlloc
 
    /*** If you're getting a duplicate of UnitAlive, you can delete or comment out this one ***/
    native UnitAlive takes unit id returns boolean
 
    globals
        private trigger BurstLaserTrigger = CreateTrigger()
        public boolean array UsesBurstLaser     //Must always be true or the system won't work.
        public boolean array Uninterruptible    //If true, lasers will keep firing even if the target is stunned or dead.
        public boolean array IsLaserGrounded    //If true, then your Lasers will always strike the ground. Invalidates LaserSpreadZ. Can be useful for AoE ground attacks.
        public boolean array LaserImpactLock    //If true, the Laser with latch on to it's impact point relative to it's target. That can cause weird behaviour, use trial and error.
        public integer array NumberOfLasers     //CANNOT BE LESS THAN 1 or you will deal no damage. The number of lasers that will be shot per attack. CANNOT BE LESS THAN 1
        public integer array LaserDuration      //How long an individual laser will last before it starts to fade. 32 == 1 second.
        public integer array LaserFadeTime      //How long it takes for each laser to fade. 32 == 1 second.
        public real array LaserInterval         //Time between laser in seconds.
        public string array LaserLaunchFX       //The string of the special effect that plays on the launch dummy.
        public string array LaserImpactFX       //The string of the special effect that plays on the impact dummy.
        public string array LaserAreaFX         //The string of the special effect that plays on the impact dummy in addition to the ImpactFX. Works better with an AOE laser.
        public string array LaserString         //The string of lightning effect itself.
        public real array LaserAOE              //If greater that 0. will deal damage in an area around the impact.
     
        public real array LaserLaunchOffset     //How far forward with the Launch effect be. Use negative values to move backwards.
        public real array LaserLaunchHeight     //Adjusts the Launch effect along the Z axis. Positive value go up, negative values go down.
        public real array LaserLaunchAngle      //By how many radians will the Launch effect be offset by? Positive values adjust the angle counter-clockwise.
        public real array LaserSpreadXY         //How much your laser will spread out along the X and Y axis.
        public real array LaserSpreadZ          //How much your laser will spread out along the Z axis.
     
        public boolean array LaserDivideDamage  //If true, the total damage will be divide amoung the number of lasers.
        public boolean array FollowTarget    //If false, Lasers in a set will strike the same point instead of updating their impact location to follow a moving target.
        public boolean array LaserRangeLock     //If true will set LaserRangeStart's starting point to the attacker (source of the laser) instead of the taret
        public real array LaserRangeStart       //Adds or subtracts a certain amount of distance from it's starting point.
        public real array LaserRangePerLaser    //Adds or substracts distance per Laser in a set.
        public real array LaserDirectionalTilt  //This pivots the angle of the Laser by a certain amount so that the direction from which the Laser starts is rotated. Uses radians.
                                                //A value of 1.5708 radians (90°) with make a laser start from right to left, for example.
                                                //-0.785398 radians (-45°) will start the laser from left to right, moving diagonally.
                                                //This is only useful if LaserRangePerLaser is > 0.
     
        public attacktype array LaserAttackType //Vanilla attack type like ATTACK_TYPE_HERO, ATTACK_TYPE_PIERCE, etc
        public integer array LaserDamageType    //The damage type from Damage Engine
        public boolean array LaserHitAll        //The laser will harm all units in the LaserAOE. If LaserAOE is <= 0. this does nothing.
        public boolean array LaserFriendlyFire  //AOE damage will damage allies
        public unit array LaserSentry           //This is optional. If something is set as the Sentry, the lasers will shoot from that unit instead.
     
        public real array RED                   //Red tint of the Laser. 1. is 100%, and 0. is 0%.
        public real array BLUE                  //Blue tint of the Laser. 1. is 100%, and 0. is 0%.
        public real array GREEN                 //Green tint of the Laser. 1. is 100%, and 0. is 0%.
        public real array ALPHA                 //Transparency of the Laser. 1. fully visible. 0. is completely transparent.
     
        public integer array LaserIndex         //Binding all values to the custom value of the unit can cause problems if using IndexIndexAllocate() so that's just a safety measure.
     
        //Events
        public real EVENT = 0.
        public unit EVENT_Source = null
        public unit EVENT_Target = null
        public unit EVENT_Sentry = null
        public real EVENT_Launch_x = 0.
        public real EVENT_Launch_y = 0.
        public real EVENT_Launch_z = 0.
        public real EVENT_Impact_x = 0.
        public real EVENT_Impact_y = 0.
        public real EVENT_Impact_z = 0.
    endglobals
 
 
    function BurstLaser_onIndex takes nothing returns boolean
 
        local unit Source = udg_UDexUnits[udg_UDex]
        local integer ID  = udg_UDex
     
        if udg_UnitIndexEvent == 1. then
         
            //Rifleman
            if GetUnitTypeId(Source) == 'hrif' then
                //set LaserIndex[udg_UDex]    = IndexAllocate() //This must not be changed.
                //set ID = LaserIndex[udg_UDex]
                set UsesBurstLaser[ID]      = true      //This must always be true or this unit type will not use this system.
                set LaserString[ID]         = "YLLN"    //This is the lightning effect that will be used. Can be null.
                set RED[ID]                 = 1.        //This is the red tint of your lightning effect. 1.0 is 100% and 0.0 is 0%.
                set BLUE[ID]                = 1.        //This is the blue tint of your lightning effect. 1.0 is 100% and 0.0 is 0%.
                set GREEN[ID]               = 1.        //This is the green tint of your lightning effect. 1.0 is 100% and 0.0 is 0%.
                set ALPHA[ID]               = .85       //This is the opacity of your lightning effect. 1.0 is fully visible and 0.0 is completely invisible.
                set LaserLaunchFX[ID]       = "Abilities\\Weapons\\ProcMissile\\ProcMissile.mdl"                    //The special effect that plays at the launch coordinates.
                set LaserImpactFX[ID]       = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdl"    //The special effect that plays at the impact coordinates.
                set LaserAreaFX[ID]         = null      //The special effect that plays at the imapct coordinates - useful if you want to add an AOE effect on top of an impact effect.
                set NumberOfLasers[ID]      = 3         //Number of lasers that get fired. CANNOT BE LOWER THATN 1.
                set LaserDuration[ID]       = 5         //This is the duration before the lightning effect begins to fade. 32 == 1 second. 5 == .15625 seconds.
                set LaserFadeTime[ID]       = 5         //This is how long it will take for the lightning effect to fade completely.
                set LaserInterval[ID]       = 0.075     //This is the interval, in seconds, between shots.
                set LaserAOE[ID]            = 0.        //If greater that 0., will deal damage in an area.
                set IsLaserGrounded[ID]     = false     //If true, all your lasers will impact the terrain, regardless of what you're attacking
                set LaserLaunchOffset[ID]   = 70.       //This moves your launch coordinate towards or away from your target.
                set LaserLaunchHeight[ID]   = 45.       //This raises or lowers your launch coordinate.
                set LaserLaunchAngle[ID]    = 0.        //This pivots your launch coordinates to align with off-center weapons on your unit model, for example. USES RADIANS.
                set LaserSpreadXY[ID]       = 20.       //This scatters your lasers' impact along the X and Y axis.
                set LaserSpreadZ[ID]        = 20.       //This scatters your lasers' impact along the Z axis.
                set LaserRangeStart[ID]     = 0.        //This offsets the IMPACT of your lasers by a certain amount. Leave at 0. if your lasers are not striking a symmetrical pattern.
                set LaserRangePerLaser[ID]  = 0.        //This updates the IMPACT of consequent lasers in a set so that it can move. See the Gryphon Riders' and Gargoyls' attack.
                set LaserDirectionalTilt[ID]= 0.        //This pivots the beam so that it can travel in a different direction. USES RADIANS.
                set FollowTarget[ID]        = true      //If false, consequent lasers for a set will continue along the same angle the initial beam was fired.
                set LaserImpactLock[ID]     = false     //If true, the laser will maintain a lock on their impact location, moving along with their target.
                set LaserRangeLock[ID]      = false     //If true, the lasers will originate relative to the attacker's location instead of the target's. Works with forwards line attacks.
                set LaserAttackType[ID]     = ATTACK_TYPE_PIERCE    //Your attack type.
                set LaserDamageType[ID]     = udg_DamageTypeCode    //This uses Damage Engine's Damage Type mechanic to allow you to do custom damage types, the effects of which can be triggered.
                set LaserDivideDamage[ID]   = true      //If set to true, then the unit's damage displayed on the UI will be divide between all the lasers in a set.
                set Uninterruptible[ID]     = false     //If false, a laser set will end prematurely if the attacker moves or dies or is ordered to stop.
                set LaserHitAll[ID]         = false     //If true, and LaserAOE[ID] is > 0., then the area damage will apply to both ground and flying units.
                set LaserFriendlyFire[ID]   = false     //If true, and LaserAOE[ID] is > 0., area damage attacks will affect friendly units and allies as well.
             
            //Gryphon Rider
            elseif GetUnitTypeId(Source) == 'hgry' then
                //set LaserIndex[udg_UDex]    = IndexAllocate()
                //set ID = LaserIndex[udg_UDex]
                set UsesBurstLaser[ID]      = true
                set LaserString[ID]         = "SLSB"
                set RED[ID]                 = 1.
                set BLUE[ID]                = 1.
                set GREEN[ID]               = 1.
                set ALPHA[ID]               = 1.
                set LaserLaunchFX[ID]       = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl"
                set LaserImpactFX[ID]       = "Abilities\\Weapons\\GryphonRiderMissile\\GryphonRiderMissile.mdl"
                set LaserAreaFX[ID]         = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl"
                set NumberOfLasers[ID]      = 8
                set LaserDuration[ID]       = 7
                set LaserFadeTime[ID]       = 5
                set LaserInterval[ID]       = .0624
                set LaserAOE[ID]            = 100.
                set IsLaserGrounded[ID]     = true
                set LaserLaunchOffset[ID]   = 80.
                set LaserLaunchHeight[ID]   = 80.
                set LaserLaunchAngle[ID]    = 0.
                set LaserSpreadXY[ID]       = 50.
                set LaserSpreadZ[ID]        = 0.
                set FollowTarget[ID]        = false
                set LaserImpactLock[ID]     = false
                set LaserRangeLock[ID]      = true
                set LaserRangeStart[ID]     = 80.
                set LaserRangePerLaser[ID]  = 80.
                set LaserDirectionalTilt[ID]= 3.14159
                set LaserAttackType[ID]     = ATTACK_TYPE_PIERCE
                set LaserDamageType[ID]     = udg_DamageTypeCode
                set LaserDivideDamage[ID]   = false
                set Uninterruptible[ID]     = false
                set LaserHitAll[ID]         = false
                set LaserFriendlyFire[ID]   = false
         
            //Gargoyle
            elseif GetUnitTypeId(Source) == 'ugar' then
                //set LaserIndex[udg_UDex]    = IndexAllocate()
                //set ID = LaserIndex[udg_UDex]
                set UsesBurstLaser[ID]      = true
                set LaserString[ID]         = "GRLN"
                set RED[ID]                 = 1.
                set BLUE[ID]                = 1.
                set GREEN[ID]               = 1.
                set ALPHA[ID]               = 1.
                set LaserLaunchFX[ID]       = "Abilities\\Weapons\\KeeperGroveMissile\\KeeperGroveMissile.mdl"
                set LaserImpactFX[ID]       = "Abilities\\Weapons\\KeeperGroveMissile\\KeeperGroveMissile.mdl"
                set LaserAreaFX[ID]         = null
                set NumberOfLasers[ID]      = 13
                set LaserDuration[ID]       = 7
                set LaserFadeTime[ID]       = 5
                set LaserInterval[ID]       = 0.
                set LaserAOE[ID]            = 70.
                set IsLaserGrounded[ID]     = false
                set LaserLaunchOffset[ID]   = 40.
                set LaserLaunchHeight[ID]   = 20.
                set LaserLaunchAngle[ID]    = 0.
                set LaserSpreadXY[ID]       = 50.
                set LaserSpreadZ[ID]        = 50.
                set FollowTarget[ID]        = false
                set LaserImpactLock[ID]     = false
                set LaserRangeLock[ID]      = false
                set LaserRangeStart[ID]     = 0.
                set LaserRangePerLaser[ID]  = 35.
                set LaserDirectionalTilt[ID]= 1.5708
                set LaserAttackType[ID]     = ATTACK_TYPE_PIERCE
                set LaserDamageType[ID]     = udg_DamageTypeCode
                set LaserDivideDamage[ID]   = false
                set Uninterruptible[ID]     = false
                set LaserHitAll[ID]         = false
                set LaserFriendlyFire[ID]   = false
         
            //Spellbreaker
            elseif GetUnitTypeId(Source) == 'hspt' then
                //set LaserIndex[udg_UDex]    = IndexAllocate()
                //set ID = LaserIndex[udg_UDex]
                set UsesBurstLaser[ID]      = true
                set LaserString[ID]         = "SLSB"
                set RED[ID]                 = 1.
                set BLUE[ID]                = 1.
                set GREEN[ID]               = 1.
                set ALPHA[ID]               = 1.
                set LaserLaunchFX[ID]       = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"
                set LaserImpactFX[ID]       = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"
                set LaserAreaFX[ID]         = null
                set NumberOfLasers[ID]      = 5
                set LaserDuration[ID]       = 7
                set LaserFadeTime[ID]       = 5
                set LaserInterval[ID]       = 0.1
                set LaserAOE[ID]            = 0.
                set IsLaserGrounded[ID]     = false
                set LaserLaunchOffset[ID]   = 0.
                set LaserLaunchHeight[ID]   = 0.
                set LaserLaunchAngle[ID]    = 0.
                set LaserSpreadXY[ID]       = 30.
                set LaserSpreadZ[ID]        = 30.
                set FollowTarget[ID]        = true
                set LaserImpactLock[ID]     = true
                set LaserRangeLock[ID]      = false
                set LaserRangeStart[ID]     = 0.
                set LaserRangePerLaser[ID]  = 0.
                set LaserDirectionalTilt[ID]= 0.
                set LaserAttackType[ID]     = ATTACK_TYPE_PIERCE
                set LaserDamageType[ID]     = udg_DamageTypeCode
                set LaserDivideDamage[ID]   = true
                set Uninterruptible[ID]     = true
                set LaserHitAll[ID]         = false
                set LaserFriendlyFire[ID]   = false
            endif
         
        else
            //set ID = LaserIndex[udg_UDex]
            set UsesBurstLaser[ID]      = false
            set LaserString[ID]         = null
            set RED[ID]                 = 1.
            set BLUE[ID]                = 1.
            set GREEN[ID]               = 1.
            set ALPHA[ID]               = 1.
            set LaserLaunchFX[ID]       = null
            set LaserImpactFX[ID]       = null
            set LaserAreaFX[ID]         = null
            set NumberOfLasers[ID]      = 1
            set LaserDuration[ID]       = 1
            set LaserFadeTime[ID]       = 1
            set LaserInterval[ID]       = 1
            set LaserAOE[ID]            = 0.
            set IsLaserGrounded[ID]     = false
            set LaserLaunchOffset[ID]   = 0.
            set LaserLaunchHeight[ID]   = 0.
            set LaserLaunchAngle[ID]    = 0.
            set LaserSpreadXY[ID]       = 0.
            set LaserSpreadZ[ID]        = 0.
            set FollowTarget[ID]        = false
            set LaserImpactLock[ID]     = false
            set LaserRangeLock[ID]      = false
            set LaserRangeStart[ID]     = 0.
            set LaserRangePerLaser[ID]  = 0.
            set LaserAttackType[ID]     = null
            set LaserDamageType[ID]     = 0
            set LaserDivideDamage[ID]   = false
            set Uninterruptible[ID]     = false
            set LaserHitAll[ID]         = false
            set LaserFriendlyFire[ID]   = false
            //call IndexDeallocate(LaserIndex[udg_UDex])
            //set LaserIndex[udg_UDex]    = 0
        endif
     
        set Source = null
        return false
    endfunction
 
    function BurstLaser_Setup takes nothing returns nothing
        call TriggerRegisterVariableEvent( BurstLaserTrigger, "udg_UnitIndexEvent", EQUAL, 1.00 )
        call TriggerRegisterVariableEvent( BurstLaserTrigger, "udg_UnitIndexEvent", EQUAL, 2.00 )
        call TriggerAddCondition( BurstLaserTrigger, function BurstLaser_onIndex)
    endfunction
 
 
endlibrary

The main code. I'm not sure if I do have to call deallocate(this), but things seem to be overlapping and getting overwritten and timers are getting screwed up or something. It's worth noting that there are two timers running, BurstLaser_Volley and LaserFade_Timer. If the latter runs, it will always be last so the deallocation will take place then. Otherwise, if it doesn't run, the deallocation takes place the the BurstLaser_Volley method.
JASS:
library BurstLaser requires BL, GetUnitCollision, ZLibrary, TimerUtils, ArmorUtils, SpecialEffectZ

    globals
        private group DamageGroup = CreateGroup()
        private constant real MAX_COLLISION_SIZE = 196.
        private constant real COLLISION_PERCENTAGE = .8
        private constant integer MAX_LASERS = 20 //Maximum number of lasers per set. Feel free to increase.
    endglobals
 
    struct BurstLaser
        unit source
        unit target
        unit sentry
     
        player owner
        integer id
     
        real damage
     
        real red
        real blue
        real green
        real alpha
        real fade_amount
        real fade_time
     
        integer duration
        integer count
        integer fx_interval
        real interval
     
        //boolean locked_to_target
        real lock_angle
        real lock_distance
        real lock_height
     
        lightning laser
        boolean fading
        boolean grounded
        boolean hit_all
        boolean friendly_fire
        boolean uninterruptible
     
        real launch_offset
        real launch_height
        real launch_angle
        real spread_xy
        real spread_z
        //real source_x
        //real source_y
        //real source_z
        real launch_x
        real launch_y
        real launch_z
        real impact_x
        real impact_y
        real impact_z
        real impact_spread_x
        real impact_spread_y
        real impact_spread_z
     
        real target_coll
     
        real laser_angle
        boolean follow_target       //If false, Lasers in a set will strike the same point instead of updating their impact location to follow a moving target.
        boolean impact_lock         //if true then Lasers that have struck a target will follow it around until they have been destroyed.
        boolean range_lock          //If true will set LaserRangeStart's starting point to the attacker (source of the laser) instead of the taret
        real range_start            //Adds or subtracts a certain amount of distance from it's starting point.
        real range_per_laser        //Adds or substracts distance per Laser in a set.
     
        string laser_string
        string launch_string
        string impact_string
        string area_string
     
        real aoe
        attacktype attack_type
        integer damage_type
     
        real initial_x
        real initial_y
     
        timer tmr_volley
        timer tmr_laserfade
     
        boolean failsafe_check
        boolean force_end
     
        integer laser_num
        integer laser_index
         
            //Fade Laser Timer Variables
            //unit array f_source[MAX_LASERS]
            //unit array f_target[MAX_LASERS]
            //unit array f_sentry[MAX_LASERS]
         
            lightning array f_laser[MAX_LASERS]
            //real array f_red[MAX_LASERS]
            //real array f_blue[MAX_LASERS]
            //real array f_green[MAX_LASERS]
            real array f_alpha[MAX_LASERS]
            real array f_fade_amount[MAX_LASERS]
         
            boolean array f_is_laser_dead[MAX_LASERS]
         
            real array f_lock_angle[MAX_LASERS]
            real array f_lock_distance[MAX_LASERS]
            real array f_lock_height[MAX_LASERS]
         
            //real array f_launch_offset[MAX_LASERS]
            //real array f_launch_height[MAX_LASERS]
            //real array f_launch_angle[MAX_LASERS]
         
            real array f_impact_x[MAX_LASERS]
            real array f_impact_y[MAX_LASERS]
            real array f_impact_z[MAX_LASERS]
         
            real array f_duration[MAX_LASERS]
            boolean array f_fading[MAX_LASERS]
            boolean array f_uninterruptible[MAX_LASERS]
     
        static code timer_volley_handler
        static code timer_laserfade_handler
     
     
     
        static method LaserFade_Timer takes nothing returns nothing
            //local timer t = GetExpiredTimer()
            local thistype this = GetTimerData(GetExpiredTimer())
     
            local real source_x = GetUnitX(this.source)
            local real source_y = GetUnitY(this.source)
            local real target_x = GetUnitX(this.target)
            local real target_y = GetUnitY(this.target)
            local real angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
         
            local real launch_x = 0.
            local real launch_y = 0.
            local real launch_z = 0.
         
            local real impact_x = 0.
            local real impact_y = 0.
            local real impact_z = 0.
     
            local integer iLoop = 0
            local boolean set_is_ongoing = false
         
            loop
                set iLoop = iLoop + 1
                if not this.f_is_laser_dead[iLoop] then
                    if this.sentry == null then
                        set launch_x = source_x + Cos(angle) * this.launch_offset
                        set launch_y = source_y + Sin(angle) * this.launch_offset
                        set launch_z = GetUnitZ(this.source) + this.launch_height
                    else
                        set launch_x = GetUnitX(this.sentry) + Cos(angle) * this.launch_offset
                        set launch_y = GetUnitY(this.sentry) + Sin(angle) * this.launch_offset
                        set launch_z = GetUnitZ(this.sentry) + this.launch_height
                    endif
                 
                    if this.impact_lock then
                        set impact_x = target_x + Cos(this.f_lock_angle[iLoop]) * this.f_lock_distance[iLoop]
                        set impact_y = target_y + Sin(this.f_lock_angle[iLoop]) * this.f_lock_distance[iLoop]
                        set impact_z = GetUnitZ(this.target) + this.f_lock_height[iLoop]
                        if this.f_laser[iLoop] != null then //I set this if.then.else here because I want the IMPACTS to be set for the EVENTS below.
                            call MoveLightningEx(this.f_laser[iLoop], true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                        endif
                    else
                        set impact_x = this.f_impact_x[iLoop]
                        set impact_y = this.f_impact_y[iLoop]
                        set impact_z = this.f_impact_z[iLoop]
                        if this.f_laser[iLoop] != null then
                            call MoveLightningEx(this.f_laser[iLoop], true, launch_x, launch_y, launch_z, this.f_impact_x[iLoop], this.f_impact_y[iLoop], this.f_impact_z[iLoop])
                        endif
                    endif
                 
                    if this.f_fading[iLoop] then
                        set this.f_alpha[iLoop] = this.f_alpha[iLoop] - this.fade_amount
                        if this.f_laser[iLoop] != null then
                            call SetLightningColor(this.f_laser[iLoop], this.red, this.blue, this.green, this.f_alpha[iLoop])
                        endif
                        if this.f_alpha[iLoop] <= 0. then
                         
                            //EVENT
                            /*set BL_EVENT_Source = LaserSource[NewTimerID]
                            set BL_EVENT_Target = LaserTarget[NewTimerID]
                            set BL_EVENT_Sentry = LaserSentry[NewTimerID]
                            set BL_EVENT_Launch_x = LAUNCH_X
                            set BL_EVENT_Launch_y = LAUNCH_Y
                            set BL_EVENT_Launch_z = LAUNCH_Z
                            set BL_EVENT_Impact_x = IMPACT_X
                            set BL_EVENT_Impact_y = IMPACT_Y
                            set BL_EVENT_Impact_z = IMPACT_Z
                            set BL_EVENT = 2.5
                            set BL_EVENT_Source = null
                            set BL_EVENT_Target = null
                            set BL_EVENT_Sentry = null
                            set BL_EVENT_Launch_x = 0.
                            set BL_EVENT_Launch_y = 0.
                            set BL_EVENT_Launch_z = 0.
                            set BL_EVENT_Impact_x = 0.
                            set BL_EVENT_Impact_y = 0.
                            set BL_EVENT_Impact_z = 0.
                            set BL_EVENT = 0.*/
                            //END EVENT
                         
                            set this.f_is_laser_dead[iLoop] = true
                            if this.f_laser[iLoop] != null then
                                call DestroyLightning(this.f_laser[iLoop])
                                set this.f_laser[iLoop] = null
                            endif
                         
                            //call IndexDeallocate(NewTimerID)
                        endif
                    else
                        set this.f_duration[iLoop] = this.f_duration[iLoop] - 1
                        if this.f_duration[iLoop] <= 0 then
                            set this.f_fading[iLoop] = true
                        endif
                    endif//if this.f_fading[iLoop] then
                 
                endif//if not this.f_is_laser_dead[iLoop] then
             
                exitwhen iLoop >= this.laser_num
            endloop
         
            set iLoop = 0
            loop
                set iLoop = iLoop + 1
                if not this.f_is_laser_dead[iLoop] then
                    set set_is_ongoing = true
                endif
                exitwhen iLoop >= this.laser_num
            endloop
         
            if not set_is_ongoing then
                call ReleaseTimer(this.tmr_laserfade)
                call BJDebugMsg("fade end")
                call deallocate(this)
                //call this.destroy()
            endif
            //set t = null
        endmethod
     
     
     
        static method BurstLaser_Volley takes nothing returns nothing
            //local timer t = GetExpiredTimer()
            local thistype this = GetTimerData(GetExpiredTimer())
         
            local real source_x = GetUnitX(this.source)
            local real source_y = GetUnitY(this.source)
            local real target_x = GetUnitX(this.target)
            local real target_y = GetUnitY(this.target)
            local real angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
         
            local real launch_x = 0.
            local real launch_y = 0.
            local real launch_z = 0.
         
            local real impact_x = 0.
            local real impact_y = 0.
            local real impact_z = 0.
         
            local boolean b = false
            local unit u = null
         
            if this.sentry == null then
                set launch_x = source_x + Cos(angle + this.launch_angle) * this.launch_offset
                set launch_y = source_y + Sin(angle + this.launch_angle) * this.launch_offset
                set launch_z = GetUnitZ(this.source) + this.launch_height
            else
                set launch_x = GetUnitX(this.sentry) + Cos(angle + this.launch_angle) * this.launch_offset
                set launch_y = GetUnitY(this.sentry) + Sin(angle + this.launch_angle) * this.launch_offset
                set launch_z = GetUnitZ(this.sentry) + this.launch_height
            endif
         
            if this.range_lock then //if true, start from the source's point
                if this.follow_target then
                    set angle = Atan2(target_y - this.initial_y, target_x - this.initial_x)
                    set this.impact_x = this.impact_x + Cos(angle) * this.range_per_laser
                    set this.impact_y = this.impact_y + Sin(angle) * this.range_per_laser
                    set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set this.impact_z = GetUnitZ(this.target)
                        set impact_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * .8
                    endif
                else
                    set this.impact_x = this.impact_x + Cos(this.laser_angle) * this.range_per_laser
                    set this.impact_y = this.impact_y + Sin(this.laser_angle) * this.range_per_laser
                    set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set impact_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * .8
                    endif
                endif
            else
                if this.follow_target then
                    set angle = Atan2(target_y - this.initial_y, target_x - this.initial_x)
                    set this.impact_x = this.impact_x + Cos(angle) * this.range_per_laser
                    set this.impact_y = this.impact_y + Sin(angle) * this.range_per_laser
                    set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set this.impact_z = GetUnitZ(this.target)
                        set impact_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * .8
                    endif
                else
                    set this.impact_x = this.impact_x + Cos(this.laser_angle) * this.range_per_laser
                    set this.impact_y = this.impact_y + Sin(this.laser_angle) * this.range_per_laser
                    set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set impact_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * .8
                    endif
                endif
            endif
         
            //Launch
            if this.launch_string != null then
                set b = true
                call DestroyEffect(AddSpecialEffectZ(this.launch_string, this.launch_x, this.launch_y, this.launch_z))
            endif
            //Impact
            if this.impact_string != null then
                set b = true
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.impact_string, impact_x, impact_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.impact_string, impact_x, impact_y, impact_z))
                endif
            endif
            //Area
            if this.area_string != null then
                set b = true
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.area_string, impact_x, impact_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.area_string, impact_x, impact_y, impact_z))
                endif
            endif
         
            //Laser
            if this.laser_string != null then
                set b = true
             
                set this.f_impact_x[this.laser_index] = impact_x
                set this.f_impact_y[this.laser_index] = impact_y
                set this.f_impact_z[this.laser_index] = impact_z
             
                set this.f_alpha[this.laser_index] = this.alpha
                set this.f_fading[this.laser_index] = false
             
                if this.impact_lock then
                    set this.f_lock_angle[this.laser_index] = Atan2(impact_y - target_y, impact_x - target_x)
                    set this.f_lock_distance[this.laser_index] = SquareRoot( (impact_x-target_x)*(impact_x-target_x) + (impact_y-target_y)*(impact_y-target_y) )
                    set this.f_lock_height[this.laser_index] = impact_z - GetUnitZ(this.target)
                endif
             
                set this.fade_amount = this.alpha / this.fade_time
                set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, this.launch_x, this.launch_y, this.launch_z, this.impact_spread_x, this.impact_spread_y, this.impact_spread_z)
                call SetLightningColor(this.f_laser[this.laser_index], this.red, this.green, this.blue, this.f_alpha[this.laser_index])
                set this.laser_index = this.laser_index + 1
            endif
         
            //Damage
            if this.aoe > 0. then
                call GroupEnumUnitsInRange(DamageGroup, impact_x, impact_y, this.aoe + MAX_COLLISION_SIZE, null)
                loop
                    set u = FirstOfGroup(DamageGroup)
                    call GroupRemoveUnit(DamageGroup, u)
                    exitwhen u == null
                    if IsUnitInRangeXY(u, impact_x, impact_y, this.aoe) and UnitAlive(u) and /*
                    [Friendly Fire] */( (this.friendly_fire and (IsUnitEnemy(u, this.owner) or IsUnitAlly(u, this.owner))) or /*
                    [Enemy]         */  ( (not this.friendly_fire and IsUnitEnemy(u, this.owner)) or  /*
                    [Friendly]      */    (not this.friendly_fire and IsUnitAlly(u, this.owner) and u == this.target) ) ) and /*
                    [Magic Immune]  */( (this.attack_type == ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) or /*
                    [Ethereal]      */  (this.attack_type != ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_ETHEREAL)) ) then
                        if not this.hit_all and /*
                        [Ground]    */ ( (not IsUnitType(u, UNIT_TYPE_FLYING) and not IsUnitType(this.target, UNIT_TYPE_FLYING)) or /*
                        [Flying]    */ (IsUnitType(u, UNIT_TYPE_FLYING) and IsUnitType(this.target, UNIT_TYPE_FLYING)) ) then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        elseif this.hit_all then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                    endif
                endloop
            else
                set udg_NextDamageType = this.damage_type
                call UnitDamageTarget(this.source, this.target, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
         
            //EVENT
            /*set BL_EVENT_Source = LaserSource[tID]
            set BL_EVENT_Target = LaserTarget[tID]
            set BL_EVENT_Sentry = LaserSentry[tID]
            set BL_EVENT_Launch_x = LAUNCH_X
            set BL_EVENT_Launch_y = LAUNCH_Y
            set BL_EVENT_Launch_z = LAUNCH_Z
            set BL_EVENT_Impact_x = IMPACT_X
            set BL_EVENT_Impact_y = IMPACT_Y
            set BL_EVENT_Impact_z = IMPACT_Z
            set BL_EVENT = 2.
            set BL_EVENT_Source = null
            set BL_EVENT_Target = null
            set BL_EVENT_Sentry = null
            set BL_EVENT_Launch_x = 0.
            set BL_EVENT_Launch_y = 0.
            set BL_EVENT_Launch_z = 0.
            set BL_EVENT_Impact_x = 0.
            set BL_EVENT_Impact_y = 0.
            set BL_EVENT_Impact_z = 0.
            set BL_EVENT = 0.*/
            //END EVENT
         
            set this.count = this.count - 1
            if this.count <= 0 or not UnitAlive(this.source) or ( this.uninterruptible and /*
            [Move]      */ (GetUnitCurrentOrder(this.source) == 851986 or /*
            [Stunned]   */  GetUnitCurrentOrder(this.source) == 851973 or /*
            [Stop]      */  GetUnitCurrentOrder(this.source) == 851972) ) then
         
                //EVENT
                /*set BL_EVENT_Source = LaserSource[tID]
                set BL_EVENT_Target = LaserTarget[tID]
                set BL_EVENT_Sentry = LaserSentry[tID]
                set BL_EVENT_Launch_x = LAUNCH_X
                set BL_EVENT_Launch_y = LAUNCH_Y
                set BL_EVENT_Launch_z = LAUNCH_Z
                set BL_EVENT_Impact_x = IMPACT_X
                set BL_EVENT_Impact_y = IMPACT_Y
                set BL_EVENT_Impact_z = IMPACT_Z
                set BL_EVENT = 3.
                set BL_EVENT_Source = null
                set BL_EVENT_Target = null
                set BL_EVENT_Sentry = null
                set BL_EVENT_Launch_x = 0.
                set BL_EVENT_Launch_y = 0.
                set BL_EVENT_Launch_z = 0.
                set BL_EVENT_Impact_x = 0.
                set BL_EVENT_Impact_y = 0.
                set BL_EVENT_Impact_z = 0.
                set BL_EVENT = 0.*/
                //END EVENT
             
                call ReleaseTimer(this.tmr_volley)
                set this.tmr_volley = null
                call BJDebugMsg("volley end")
             
                if not this.failsafe_check then
                    call deallocate(this)
                    //call this.destroy()
                endif
            endif
            //set t = null
        endmethod
     
     
     
        static method create takes unit unit_source, unit unit_target, unit unit_sentry, integer id, real damage returns thistype
     
            local thistype this = allocate()
         
            local real source_x = GetUnitX(unit_source)
            local real source_y = GetUnitY(unit_source)
            local real target_x = GetUnitX(unit_target)
            local real target_y = GetUnitY(unit_target)
            local real sentry_x = 0.
            local real sentry_y = 0.
            local real startangle = Atan2(target_y - source_y, target_x - source_x)
         
            local real range_offset = 0.
            local real impact_spread_x = 0.
            local real impact_spread_y = 0.
            local real impact_spread_z = 0.
         
            local boolean b = false
            local unit u = null
         
            set this.owner = GetOwningPlayer(unit_source)
            set this.source = unit_source
            set this.target = unit_target
            set this.sentry = unit_sentry
            //set this.id = id
         
            set this.damage = damage
            set this.target_coll = GetUnitCollision(unit_target)
         
            set this.laser_string = BL_LaserString[id]
            set this.red = BL_RED[id]
            set this.blue = BL_BLUE[id]
            set this.green = BL_GREEN[id]
            set this.alpha = BL_ALPHA[id]
         
            set this.launch_string = BL_LaserLaunchFX[id]
            set this.impact_string = BL_LaserImpactFX[id]
            set this.area_string = BL_LaserAreaFX[id]
         
            set this.duration = BL_LaserDuration[id]
            set this.fade_time = BL_LaserFadeTime[id]
            set this.interval = BL_LaserInterval[id]
            set this.aoe = BL_LaserAOE[id]
            set this.grounded = BL_IsLaserGrounded[id]
         
            set this.launch_offset = BL_LaserLaunchOffset[id]
            set this.launch_height = BL_LaserLaunchHeight[id]
            set this.launch_angle = BL_LaserLaunchAngle[id]
         
            set this.spread_xy = BL_LaserSpreadXY[id]
            set this.spread_z = BL_LaserSpreadZ[id]
         
            set this.follow_target = BL_FollowTarget[id]
            set this.impact_lock = BL_LaserImpactLock[id]
            set this.range_lock = BL_LaserRangeLock[id]
         
            set this.range_start = BL_LaserRangeStart[id]
            set this.range_per_laser = BL_LaserRangePerLaser[id]
            set this.attack_type = BL_LaserAttackType[id]
            set this.damage_type = BL_LaserDamageType[id]
         
            set this.uninterruptible = BL_Uninterruptible[id]
            set this.hit_all = BL_LaserHitAll[id]
            set this.friendly_fire = BL_LaserFriendlyFire[id]
         
            //there's a hard limit to how many lasers you may have per set. This is because you have to declare the size of arrays of variables in a struct
            //if you want to have more that 20 lasers per set, feel free to change the value of MAX_LASERS at the top.
            if BL_NumberOfLasers[id] > MAX_LASERS then
                set this.laser_num = MAX_LASERS
            else
                set this.laser_num = BL_NumberOfLasers[id]
            endif
         
            if this.sentry == null then
                if this.launch_offset > 0. then
                    set this.launch_x = source_x + Cos(startangle + this.launch_angle) * this.launch_offset
                    set this.launch_y = source_y + Sin(startangle + this.launch_angle) * this.launch_offset
                else
                    set this.launch_x = source_x
                    set this.launch_y = source_y
                endif
                set this.launch_z = GetUnitZ(this.source) + this.launch_height
            else
                set sentry_x = GetUnitX(this.sentry)
                set sentry_y = GetUnitY(this.sentry)
                if this.launch_offset > 0. then
                    set this.launch_x = sentry_x + Cos(startangle + this.launch_angle) * this.launch_offset
                    set this.launch_y = sentry_y + Sin(startangle + this.launch_angle) * this.launch_offset
                else
                    set this.launch_x = sentry_x
                    set this.launch_y = sentry_y
                endif
                set this.launch_z = GetUnitZ(this.sentry) + this.launch_height
            endif
             
            set range_offset = ((this.laser_num-1) * this.range_per_laser + this.range_start) * .5
            if this.range_lock then //if true, start from the source's point
                set this.impact_x = source_x + Cos(startangle) * (range_offset + this.range_start)
                set this.impact_y = source_y + Sin(startangle) * (range_offset + this.range_start)
                set startangle = startangle + BL_LaserDirectionalTilt[id]
                set this.impact_x = this.impact_x + Cos(startangle) * (range_offset)
                set this.impact_y = this.impact_y + Sin(startangle) * (range_offset)
                set this.impact_spread_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                set this.impact_spread_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                if this.grounded then
                    set this.impact_z = 0.
                    set this.impact_spread_z = 0.
                else
                    set this.impact_z = GetUnitZ(this.target)
                    set this.impact_spread_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * COLLISION_PERCENTAGE
                endif
            else
                set startangle = startangle + BL_LaserDirectionalTilt[id] + 3.14159
                if this.range_per_laser != 0. then
                    set this.impact_x = target_x + Cos(startangle) * (range_offset + this.range_start)
                    set this.impact_y = target_y + Sin(startangle) * (range_offset + this.range_start)
                else
                    set this.impact_x = target_x + Cos(startangle) * (this.target_coll * COLLISION_PERCENTAGE)
                    set this.impact_y = target_y + Sin(startangle) * (this.target_coll * COLLISION_PERCENTAGE)
                endif
                set this.impact_spread_x = this.impact_x + GetRandomReal(this.spread_xy * -1, this.spread_xy)
                set this.impact_spread_y = this.impact_y + GetRandomReal(this.spread_xy * -1, this.spread_xy)
                if this.grounded then
                    set this.impact_z = 0.
                    set this.impact_spread_z = 0.
                else
                    set this.impact_z = GetUnitZ(this.target)
                    set this.impact_spread_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * COLLISION_PERCENTAGE
                endif
            endif
         
            //Launch
            if this.launch_string != null then
                set b = true
                call DestroyEffect(AddSpecialEffectZ(this.launch_string, this.launch_x, this.launch_y, this.launch_z))
            endif
            //Impact
            if this.impact_string != null then
                set b = true
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.impact_string, impact_spread_x, impact_spread_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.impact_string, impact_spread_x, impact_spread_y, impact_spread_z))
                endif
            endif
            //Area
            if this.area_string != null then
                set b = true
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.area_string, impact_spread_x, impact_spread_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.area_string, impact_spread_x, impact_spread_y, impact_spread_z))
                endif
            endif
         
            //Laser
            if this.laser_string != null then
                set b = true
             
                //set this.f_source[this.laser_index] = this.source
                //set this.f_target[this.laser_index] = this.arget
                //set this.f_sentry[this.laser_index] = this.entry
             
                //set this.f_launch_offset[this.laser_index] = this.launch_offset
                //set this.f_launch_height[this.laser_index] = this.launch_height
                //set this.f_launch_angle[this.laser_index] = this.launch_angle
             
                //set this.f_impact_lock[this.laser_index] = this.impact_lock
                set this.f_impact_x[this.laser_index] = impact_x
                set this.f_impact_y[this.laser_index] = impact_y
                set this.f_impact_z[this.laser_index] = impact_z
             
                //set this.f_red[this.laser_index] = this.red
                //set this.f_blue[this.laser_index] = this.blue
                //set this.f_green[this.laser_index] = this.green
                set this.f_alpha[this.laser_index] = this.alpha
                set this.f_fading[this.laser_index] = false
             
                if this.impact_lock then
                    set this.f_lock_angle[this.laser_index] = Atan2(impact_y - target_y, impact_x - target_x)
                    set this.f_lock_distance[this.laser_index] = SquareRoot( (impact_x-target_x)*(impact_x-target_x) + (impact_y-target_y)*(impact_y-target_y) )
                    set this.f_lock_height[this.laser_index] = impact_z - GetUnitZ(this.target)
                endif
             
                set this.fade_amount = this.alpha / this.fade_time
                set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, this.launch_x, this.launch_y, this.launch_z, this.impact_spread_x, this.impact_spread_y, this.impact_spread_z)
                call SetLightningColor(this.f_laser[this.laser_index], this.red, this.green, this.blue, this.f_alpha[this.laser_index])
             
                set this.laser_index = this.laser_index + 1
            endif
         
            //Damage
            if this.aoe > 0. then
                call GroupEnumUnitsInRange(DamageGroup, impact_x, impact_y, this.aoe + MAX_COLLISION_SIZE, null)
                loop
                    set u = FirstOfGroup(DamageGroup)
                    call GroupRemoveUnit(DamageGroup, u)
                    exitwhen u == null
                    if IsUnitInRangeXY(u, impact_x, impact_y, this.aoe) and UnitAlive(u) and /*
                    [Friendly Fire] */( (this.friendly_fire and (IsUnitEnemy(u, this.owner) or IsUnitAlly(u, this.owner))) or /*
                    [Enemy]         */  ( (not this.friendly_fire and IsUnitEnemy(u, this.owner)) or  /*
                    [Friendly]      */    (not this.friendly_fire and IsUnitAlly(u, this.owner) and u == this.target) ) ) and /*
                    [Magic Immune]  */( (this.attack_type == ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) or /*
                    [Ethereal]      */  (this.attack_type != ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_ETHEREAL)) ) then
                        if not this.hit_all and /*
                        [Ground]    */ ( (not IsUnitType(u, UNIT_TYPE_FLYING) and not IsUnitType(this.target, UNIT_TYPE_FLYING)) or /*
                        [Flying]    */ (IsUnitType(u, UNIT_TYPE_FLYING) and IsUnitType(this.target, UNIT_TYPE_FLYING)) ) then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        elseif this.hit_all then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                    endif
                endloop
            else
                set udg_NextDamageType = this.damage_type
                call UnitDamageTarget(this.source, this.target, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
         
            //Volley
            if this.laser_num > 1 then
                if b then
                    set this.failsafe_check = true
                endif
                set this.laser_angle = startangle + 3.14159
                set this.initial_x = this.impact_x
                set this.initial_y = this.impact_y
                set this.count = this.laser_num - 1
                set this.tmr_volley = NewTimerEx(this)
                call TimerStart(this.tmr_volley, this.interval, true, timer_volley_handler)
            endif
         
            //Timer
            if b then
                set this.failsafe_check = true
                set this.tmr_laserfade = NewTimerEx(this)
                call TimerStart(this.tmr_laserfade, .03125, true, timer_laserfade_handler)
                set b = false
            endif
     
            return this
        endmethod
     
     
        static method onInit takes nothing returns nothing
            set timer_volley_handler = function thistype.BurstLaser_Volley
            set timer_laserfade_handler = function thistype.LaserFade_Timer
        endmethod
    endstruct
 
endlibrary

The system was initially meant to be a custom attack system and this just calls a .create on the struct. Kinda splits the code in two, but allows for more stuff to be done with the system. I'm not entirely sure I'm doing this properlly, though.
JASS:
library BurstLaserOnAttackModule initializer init requires BurstLaser

    globals
        private BurstLaser burstlaser
        private trigger BL_Module_onAttack = CreateTrigger()
    endglobals

    function BL_Module_onAttack_Actions takes nothing returns boolean
        local real Damage = 0.
        local integer Source_ID = GetUnitUserData(udg_DamageEventSource)
        //local integer Laser_ID = BL_LaserIndex[Source_ID]
        if BL_UsesBurstLaser[Source_ID] and udg_DamageEventType == 0 and not udg_IsDamageSpell then
            set udg_DamageEventAmount = 0.
            if BL_LaserDivideDamage[Source_ID] then
                set Damage = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget)) / BL_NumberOfLasers[Source_ID]
            else
                set Damage = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget))
            endif
         
            //You may add new stuff here, like creating a timed LaserSentry[Source_ID] for a specific unit if you want the laser's launch to remain fixed, for example.
            //Or you may make a unit fire different types of Burst Laser for ground and flying.
         
            set burstlaser = BurstLaser.create(udg_DamageEventSource, udg_DamageEventTarget, BL_LaserSentry[Source_ID], Source_ID, Damage)
        endif
        return false
    endfunction

    //===========================================================================
    function init takes nothing returns nothing
        call TriggerRegisterVariableEvent( BL_Module_onAttack, "udg_DamageModifierEvent", EQUAL, 1.00 )
        call TriggerAddCondition( BL_Module_onAttack, function BL_Module_onAttack_Actions )
    endfunction

endlibrary
 
- Added set this.laser_index = 0 to the create method.

- Does it? I know I did set this.laser_count = this.laser_num - 1 but I didn't touch this.laser_num itself afaik. I did put the set iLoop = iLoop + 1 to the bottom of the LaserFade_Timer however, which seems to have fixed lasers with a 0 second interval. Those that still have > 0. seconds interval still bug out. Adding +1 to exitwhen iLoop >= this.laser_num doesn't seem to do anything.

- I copied over the code because otherwise the initial laser would only fire after the first interval as opposed to immediately. It creates a noticeable delay unless the interval is 0 seconds.

I've uploaded the test map itself in case it might help things.
 

Attachments

  • Burst Laser v2.0.w3x
    167.9 KB · Views: 51
Level 13
Joined
Nov 7, 2014
Messages
571
You are using 0 based arrays but you are not looping over them properly:
JASS:
            set iLoop = 0
            loop
                set iLoop = iLoop + 1
                if not this.f_is_laser_dead[iLoop] then
                    set set_is_ongoing = true
                endif
                exitwhen iLoop >= this.laser_num
            endloop

When you are adding lasers make sure you set their dead flag to false:
JASS:
set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, this.launch_x, this.launch_y, this.launch_z, this.impact_spread_x, this.impact_spread_y, this.impact_spread_z)
set this.f_is_laser_dead[this.laser_index] = false

- I copied over the code because otherwise the initial laser would only fire after the first interval as opposed to immediately. It creates a noticeable delay unless the interval is 0 seconds.

You could add the common code into a function/method and call it two times.
 
Ah, I see. I was initially using 1 based arrays but moved to 0 based arrays after a conversation with a friend and failed to change the position of the iLoop + 1.

Setting the flag to false and changing the line of the iLoop did it, it's finally fixed. You have no idea how long I've been fighting with this code @_@, thank you for the rescue.

Afaik as the repeating code goes, I was trying to minimise the number of function calls to the bare minimum, but if you think 1 more won't do any harm (understand this is meant primarily as a custom attack system, so that trigger will fire a LOT) then I'll move the code to a function call.

PS: am I using the deallocation() right?

EDIT: well that seemed too easy. If I set the interval to be too long and the current laser set is not over before a new one begins, the whole thing dies and lightning effects don't get destroyed.
 
Last edited:
PS: am I using the deallocation() right?
For main code from first post?
JASS:
if not set_is_ongoing then
    call ReleaseTimer(this.tmr_laserfade)
    call BJDebugMsg("fade end")
    call deallocate(this)
    //call this.destroy()
endif

There should be a custom destroy method which will be called when ever an instance is about being destroyed.
The method should handle everything like deallocating, destroying handles, and recylcling references, for example:

JASS:
method destroy takes nothing returns nothing
    call this.deallocate()

    set this.source = null
    set this.target = null
    set this.sentry = null

    call ReleaseTimer(this.tmr_volley)

    // maybe destroy lightnings, I'm not sure

endmethod


In the first script where the system inits values inidividualy for each unit, depending on the unit type, and then you clear it again onDeindex.
You actually only can set up it once per time, and then just load values from hashtable. I mean something like:

JASS:
globals

//  ....
    integer array LaserDuration
    integer array LaserFadeTime
//  ....    

endglobals

library L initializer Init

    globals
        private integer counter = 0
        private hashtable hash = InitHashtable()
    endglobals
   
    function GetAttackIndex takes integer uType returns integer
        return LoadInteger(hash, uType, 0)
    endfunction

    function Init takes nothing returns nothing
        local integer uType
       
    // register hrif
        set uType = 'hrif'
       
        set counter = counter + 1
        call SaveInteger(hash, uType, 0, counter)
       
        set LaserDuration[counter] = 2
        set LaserFadeTime[counter] = 2
       
    // register hfoo
        set uType = 'hfoo'
       
        set counter = counter + 1
        call SaveInteger(hash, uType, 0, counter)
       
        set LaserDuration[counter] = 3
        set LaserFadeTime[counter] = 3
   
    endfunction

endlibrary
 
Okay, I added a destroy method that null all the units and falsed the booleans (do booleans matter? I can't remember). I do still call ReleaseTimer() outside of that method because there are two of them and I don't know how I'm going to differentiate between which ones because destroy can't take any arguments. I'm still running into problems with lightnings not getting destroyed, which leads me to believe that the laserfade_timer is ending prematurely.

Here's the current iteration of the main code:
JASS:
library BurstLaser requires BL, GetUnitCollision, ZLibrary, TimerUtils, ArmorUtils, SpecialEffectZ

    globals
        private group DamageGroup = CreateGroup()
        private constant real MAX_COLLISION_SIZE = 196.
        private constant real COLLISION_PERCENTAGE = .8
        private constant integer MAX_LASERS = 20 //Maximum number of lasers per set. Feel free to increase.
    endglobals
  
    struct BurstLaser
        unit source
        unit target
        unit sentry
      
        player owner
        integer id
      
        real damage
      
        real red
        real blue
        real green
        real alpha
        real fade_amount
        real fade_time
      
        integer duration
        integer count
        real interval
      
        real lock_angle
        real lock_distance
        real lock_height
      
        lightning laser
        boolean fading
        boolean grounded
        boolean hit_all
        boolean friendly_fire
        boolean uninterruptible
      
        real launch_offset
        real launch_height
        real launch_angle
        real spread_xy
        real spread_z
        real launch_x
        real launch_y
        real launch_z
        real impact_x
        real impact_y
        real impact_z
        //real impact_spread_x
        //real impact_spread_y
        //real impact_spread_z
      
        real target_coll
      
        real laser_angle
        boolean follow_target       //If false, Lasers in a set will strike the same point instead of updating their impact location to follow a moving target.
        boolean floating_launch
        boolean impact_lock         //if true then Lasers that have struck a target will follow it around until they have been destroyed.
        boolean range_lock          //If true will set LaserRangeStart's starting point to the attacker (source of the laser) instead of the taret
        real range_start            //Adds or subtracts a certain amount of distance from it's starting point.
        real range_per_laser        //Adds or substracts distance per Laser in a set.
        integer lingering_laser_interval //the laser's sfx and damage will repeat every time this integer reaches 0 during the laserfade_timer. It will keep reseting until the laser ends.
      
        string laser_string
        string launch_string
        string impact_string
        string area_string
      
        real aoe
        attacktype attack_type
        integer damage_type
      
        real initial_x
        real initial_y
      
        timer tmr_volley
        timer tmr_laserfade
      
        boolean failsafe_check
        boolean force_end
      
        integer laser_num
        integer laser_index
          
        //Fade Laser Timer Variables
        lightning array f_laser[MAX_LASERS]
        real array f_alpha[MAX_LASERS]
        real array f_fade_amount[MAX_LASERS]
      
        boolean array f_is_laser_dead[MAX_LASERS]
        integer array f_lingering_laser_interval[MAX_LASERS]
      
        real array f_lock_angle[MAX_LASERS]
        real array f_lock_distance[MAX_LASERS]
        real array f_lock_height[MAX_LASERS]
      
        real array f_impact_x[MAX_LASERS]
        real array f_impact_y[MAX_LASERS]
        real array f_impact_z[MAX_LASERS]
      
        real array f_duration[MAX_LASERS]
        boolean array f_fading[MAX_LASERS]
        boolean array f_uninterruptible[MAX_LASERS]
      
        static code timer_volley_handler
        static code timer_laserfade_handler
      
      
      
        method destroy takes nothing returns nothing
            call this.deallocate()
          
            set this.owner = null
            set this.source = null
            set this.target = null
            set this.sentry = null
          
            set this.laser_string = null
            set this.launch_string = null
            set this.impact_string = null
            set this.area_string = null
          
            set this.grounded = false
          
            set this.follow_target = false
            set this.floating_launch = false
            set this.impact_lock = false
            set this.range_lock = false
          
            set this.attack_type = null
          
            set this.uninterruptible = false
            set this.hit_all = false
            set this.friendly_fire = false
          
            //call ReleaseTimer(t)
        endmethod
      
        method ApplySfxAndDamage takes real launch_x, real launch_y, real launch_z, real impact_x, real impact_y, real impact_z returns boolean
            local unit u = null
            local boolean b = false
            //Launch
            if this.launch_string != null then
                set b = true
                call DestroyEffect(AddSpecialEffectZ(this.launch_string, launch_x, launch_y, launch_z))
            endif
            //Impact
            if this.impact_string != null then
                set b = true
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.impact_string, impact_x, impact_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.impact_string, impact_x, impact_y, impact_z))
                endif
            endif
            //Area
            if this.area_string != null then
                set b = true
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.area_string, impact_x, impact_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.area_string, impact_x, impact_y, impact_z))
                endif
            endif
            //Damage
            if this.aoe > 0. then
                call GroupEnumUnitsInRange(DamageGroup, impact_x, impact_y, this.aoe + MAX_COLLISION_SIZE, null)
                loop
                    set u = FirstOfGroup(DamageGroup)
                    call GroupRemoveUnit(DamageGroup, u)
                    exitwhen u == null
                    if IsUnitInRangeXY(u, impact_x, impact_y, this.aoe) and UnitAlive(u) and /*
                    [Friendly Fire] */( (this.friendly_fire and (IsUnitEnemy(u, this.owner) or IsUnitAlly(u, this.owner))) or /*
                    [Enemy]         */  ( (not this.friendly_fire and IsUnitEnemy(u, this.owner)) or  /*
                    [Friendly]      */    (not this.friendly_fire and IsUnitAlly(u, this.owner) and u == this.target) ) ) and /*
                    [Magic Immune]  */( (this.attack_type == ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) or /*
                    [Ethereal]      */  (this.attack_type != ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_ETHEREAL)) ) then
                        if not this.hit_all and /*
                        [Ground]    */ ( (not IsUnitType(u, UNIT_TYPE_FLYING) and not IsUnitType(this.target, UNIT_TYPE_FLYING)) or /*
                        [Flying]    */ (IsUnitType(u, UNIT_TYPE_FLYING) and IsUnitType(this.target, UNIT_TYPE_FLYING)) ) then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        elseif this.hit_all then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                    endif
                endloop
            else
                set udg_NextDamageType = this.damage_type
                call UnitDamageTarget(this.source, this.target, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            return b
        endmethod
      
      
        static method LaserFade_Timer takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
      
            local real source_x = GetUnitX(this.source)
            local real source_y = GetUnitY(this.source)
            local real target_x = GetUnitX(this.target)
            local real target_y = GetUnitY(this.target)
            local real angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
          
            local real launch_x = 0.
            local real launch_y = 0.
            local real launch_z = 0.
          
            local real impact_x = 0.
            local real impact_y = 0.
            local real impact_z = 0.
      
            local integer iLoop = 0
            local boolean set_is_ongoing = false
            local boolean b = false
          
            loop
                if not this.f_is_laser_dead[iLoop] then
                    set set_is_ongoing = true
                    if this.sentry == null then
                        if not this.floating_launch then
                            set launch_x = source_x + Cos(angle) * this.launch_offset
                            set launch_y = source_y + Sin(angle) * this.launch_offset
                            set launch_z = GetUnitZ(this.source) + this.launch_height
                        else
                            set launch_x = this.launch_x
                            set launch_y = this.launch_y
                            set launch_z = this.launch_z
                        endif
                    else
                        if not this.floating_launch then
                            set launch_x = GetUnitX(this.sentry) + Cos(angle) * this.launch_offset
                            set launch_y = GetUnitY(this.sentry) + Sin(angle) * this.launch_offset
                            set launch_z = GetUnitZ(this.sentry) + this.launch_height
                        else
                            set launch_x = this.launch_x
                            set launch_y = this.launch_y
                            set launch_z = this.launch_z
                        endif
                    endif
                  
                    if this.impact_lock then
                        set impact_x = target_x + Cos(this.f_lock_angle[iLoop]) * this.f_lock_distance[iLoop]
                        set impact_y = target_y + Sin(this.f_lock_angle[iLoop]) * this.f_lock_distance[iLoop]
                        set impact_z = GetUnitZ(this.target) + this.f_lock_height[iLoop]
                    else
                        set impact_x = this.f_impact_x[iLoop]
                        set impact_y = this.f_impact_y[iLoop]
                        set impact_z = this.f_impact_z[iLoop]
                    endif
                  
                    if this.f_laser[iLoop] != null then
                        call MoveLightningEx(this.f_laser[iLoop], true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                    endif
                  
                    set this.f_lingering_laser_interval[iLoop] = this.f_lingering_laser_interval[iLoop] - 1
                    if this.f_lingering_laser_interval[iLoop] <= 0 then
                        set this.f_lingering_laser_interval[iLoop] = this.lingering_laser_interval
                        set b = ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                    endif
                  
                    if this.f_fading[iLoop] then
                        set this.f_alpha[iLoop] = this.f_alpha[iLoop] - this.fade_amount
                        if this.f_laser[iLoop] != null then
                            call SetLightningColor(this.f_laser[iLoop], this.red, this.blue, this.green, this.f_alpha[iLoop])
                        endif
                        if this.f_alpha[iLoop] <= 0. then
                          
                            //EVENT
                            /*set BL_EVENT_Source = this.source
                            set BL_EVENT_Target = this.target
                            set BL_EVENT_Sentry = this.sentry
                            set BL_EVENT_Launch_x = launch_x
                            set BL_EVENT_Launch_y = launch_y
                            set BL_EVENT_Launch_z = launch_z
                            set BL_EVENT_Impact_x = impact_x
                            set BL_EVENT_Impact_y = impact_x
                            set BL_EVENT_Impact_z = impact_z
                            set BL_EVENT = 2.5
                            set BL_EVENT_Source = null
                            set BL_EVENT_Target = null
                            set BL_EVENT_Sentry = null
                            set BL_EVENT_Launch_x = 0.
                            set BL_EVENT_Launch_y = 0.
                            set BL_EVENT_Launch_z = 0.
                            set BL_EVENT_Impact_x = 0.
                            set BL_EVENT_Impact_y = 0.
                            set BL_EVENT_Impact_z = 0.
                            set BL_EVENT = 0.*/
                            //END EVENT
                          
                            set this.f_is_laser_dead[iLoop] = true
                            if this.f_laser[iLoop] != null then
                                call DestroyLightning(this.f_laser[iLoop])
                                set this.f_laser[iLoop] = null
                            endif
                          
                        endif
                    else
                        set this.f_duration[iLoop] = this.f_duration[iLoop] - 1
                        if this.f_duration[iLoop] <= 0 then
                            set this.f_fading[iLoop] = true
                        endif
                    endif//if this.f_fading[iLoop] then
                  
                endif//if not this.f_is_laser_dead[iLoop] then
              
                set iLoop = iLoop + 1
                exitwhen iLoop >= this.laser_num
            endloop
          
            /*set iLoop = 0
            loop
                if not this.f_is_laser_dead[iLoop] then
                    set set_is_ongoing = true
                endif
                set iLoop = iLoop + 1
                exitwhen iLoop >= this.laser_num
            endloop*/
          
            if not set_is_ongoing then
                call ReleaseTimer(this.tmr_laserfade)
                call this.destroy()
                //call deallocate(this)
            endif
        endmethod
      
      
      
        static method BurstLaser_Volley takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
          
            local real source_x = GetUnitX(this.source)
            local real source_y = GetUnitY(this.source)
            local real target_x = GetUnitX(this.target)
            local real target_y = GetUnitY(this.target)
            local real angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
          
            local real launch_x = 0.
            local real launch_y = 0.
            local real launch_z = 0.
          
            local real impact_x = 0.
            local real impact_y = 0.
            local real impact_z = 0.
          
            local boolean b = false
            local unit u = null
          
            if this.sentry == null then
                if not this.floating_launch then
                    set launch_x = source_x + Cos(angle + this.launch_angle) * this.launch_offset
                    set launch_y = source_y + Sin(angle + this.launch_angle) * this.launch_offset
                    set launch_z = GetUnitZ(this.source) + this.launch_height
                else                   
                    set launch_x = this.launch_x
                    set launch_y = this.launch_y
                    set launch_z = this.launch_z
                endif
            else
                if not this.floating_launch then
                    set launch_x = GetUnitX(this.sentry) + Cos(angle + this.launch_angle) * this.launch_offset
                    set launch_y = GetUnitY(this.sentry) + Sin(angle + this.launch_angle) * this.launch_offset
                    set launch_z = GetUnitZ(this.sentry) + this.launch_height
                else                   
                    set launch_x = this.launch_x
                    set launch_y = this.launch_y
                    set launch_z = this.launch_z
                endif
            endif
          
            if this.range_lock then //if true, start from the source's point
                if this.follow_target then
                    if this.range_per_laser != 0. then
                        set angle = Atan2(target_y - this.initial_y, target_x - this.initial_x)
                        set this.impact_x = this.impact_x + Cos(angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set this.impact_z = GetUnitZ(this.target)
                        set impact_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * .8
                    endif
                else
                    if this.range_per_laser != 0. then
                        set this.impact_x = this.impact_x + Cos(this.laser_angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(this.laser_angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set this.impact_z = GetUnitZ(this.target)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set impact_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * .8
                    endif
                endif
            else
                if this.follow_target then
                    if this.range_per_laser != 0. then
                        set angle = Atan2(target_y - this.initial_y, target_x - this.initial_x)
                        set this.impact_x = this.impact_x + Cos(angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set this.impact_z = GetUnitZ(this.target)
                        set impact_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * .8
                    endif
                else
                    if this.range_per_laser != 0. then
                        set this.impact_x = this.impact_x + Cos(this.laser_angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(this.laser_angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set impact_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                        set this.impact_z = GetUnitZ(this.target)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set impact_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * .8
                    endif
                endif
            endif
          
            set b = ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
          
            //Laser
            if this.laser_string != null then
                set b = true
              
                set this.f_impact_x[this.laser_index] = impact_x
                set this.f_impact_y[this.laser_index] = impact_y
                set this.f_impact_z[this.laser_index] = impact_z
              
                set this.f_alpha[this.laser_index] = this.alpha
                set this.f_fading[this.laser_index] = false
              
                if this.impact_lock then
                    set this.f_lock_angle[this.laser_index] = Atan2(impact_y - target_y, impact_x - target_x)
                    set this.f_lock_distance[this.laser_index] = SquareRoot( (impact_x-target_x)*(impact_x-target_x) + (impact_y-target_y)*(impact_y-target_y) )
                    set this.f_lock_height[this.laser_index] = impact_z - GetUnitZ(this.target)
                endif
              
                set this.fade_amount = this.alpha / this.fade_time
                set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                //set this.f_is_laser_dead[this.laser_index] = false
                set this.f_lingering_laser_interval[this.laser_index] = this.lingering_laser_interval
                call SetLightningColor(this.f_laser[this.laser_index], this.red, this.green, this.blue, this.f_alpha[this.laser_index])
                set this.laser_index = this.laser_index + 1
            endif
          
          
            //EVENT
            /*set BL_EVENT_Source = LaserSource[tID]
            set BL_EVENT_Target = LaserTarget[tID]
            set BL_EVENT_Sentry = LaserSentry[tID]
            set BL_EVENT_Launch_x = LAUNCH_X
            set BL_EVENT_Launch_y = LAUNCH_Y
            set BL_EVENT_Launch_z = LAUNCH_Z
            set BL_EVENT_Impact_x = IMPACT_X
            set BL_EVENT_Impact_y = IMPACT_Y
            set BL_EVENT_Impact_z = IMPACT_Z
            set BL_EVENT = 2.
            set BL_EVENT_Source = null
            set BL_EVENT_Target = null
            set BL_EVENT_Sentry = null
            set BL_EVENT_Launch_x = 0.
            set BL_EVENT_Launch_y = 0.
            set BL_EVENT_Launch_z = 0.
            set BL_EVENT_Impact_x = 0.
            set BL_EVENT_Impact_y = 0.
            set BL_EVENT_Impact_z = 0.
            set BL_EVENT = 0.*/
            //END EVENT
          
            set this.count = this.count - 1
            if this.count <= 0 or not UnitAlive(this.source) or ( this.uninterruptible and /*
            [Move]      */ (GetUnitCurrentOrder(this.source) == 851986 or /*
            [Stunned]   */  GetUnitCurrentOrder(this.source) == 851973 or /*
            [Stop]      */  GetUnitCurrentOrder(this.source) == 851972) ) then
          
                //EVENT
                /*set BL_EVENT_Source = LaserSource[tID]
                set BL_EVENT_Target = LaserTarget[tID]
                set BL_EVENT_Sentry = LaserSentry[tID]
                set BL_EVENT_Launch_x = LAUNCH_X
                set BL_EVENT_Launch_y = LAUNCH_Y
                set BL_EVENT_Launch_z = LAUNCH_Z
                set BL_EVENT_Impact_x = IMPACT_X
                set BL_EVENT_Impact_y = IMPACT_Y
                set BL_EVENT_Impact_z = IMPACT_Z
                set BL_EVENT = 3.
                set BL_EVENT_Source = null
                set BL_EVENT_Target = null
                set BL_EVENT_Sentry = null
                set BL_EVENT_Launch_x = 0.
                set BL_EVENT_Launch_y = 0.
                set BL_EVENT_Launch_z = 0.
                set BL_EVENT_Impact_x = 0.
                set BL_EVENT_Impact_y = 0.
                set BL_EVENT_Impact_z = 0.
                set BL_EVENT = 0.*/
                //END EVENT
              
                call ReleaseTimer(this.tmr_volley)
                set this.tmr_volley = null
              
                if not this.failsafe_check then
                    call this.destroy()
                    //call deallocate(this)
                endif
            endif
        endmethod
      
      
      
        static method create takes unit unit_source, unit unit_target, unit unit_sentry, integer id, real damage returns thistype
      
            local thistype this = allocate()
          
            local real source_x = GetUnitX(unit_source)
            local real source_y = GetUnitY(unit_source)
            local real target_x = GetUnitX(unit_target)
            local real target_y = GetUnitY(unit_target)
            local real sentry_x = 0.
            local real sentry_y = 0.
            local real startangle = Atan2(target_y - source_y, target_x - source_x)
          
            local real range_offset = 0.
            local real impact_spread_x = 0.
            local real impact_spread_y = 0.
            local real impact_spread_z = 0.
          
            local integer iLoop = 0
            local boolean b = false
            local unit u = null
          
            set this.owner = GetOwningPlayer(unit_source)
            set this.source = unit_source
            set this.target = unit_target
            set this.sentry = unit_sentry
          
            set this.target_coll = GetUnitCollision(unit_target)
          
            set this.laser_string = BL_LaserString[id]
            set this.red = BL_RED[id]
            set this.blue = BL_BLUE[id]
            set this.green = BL_GREEN[id]
            set this.alpha = BL_ALPHA[id]
          
            set this.launch_string = BL_LaserLaunchFX[id]
            set this.impact_string = BL_LaserImpactFX[id]
            set this.area_string = BL_LaserAreaFX[id]
          
            set this.duration = BL_LaserDuration[id]
            set this.fade_time = BL_LaserFadeTime[id]
            set this.interval = BL_LaserInterval[id]
            set this.lingering_laser_interval = BL_LaserLingerInterval[id]
            set this.aoe = BL_LaserAOE[id]
            set this.grounded = BL_IsLaserGrounded[id]
          
            set this.launch_offset = BL_LaserLaunchOffset[id]
            set this.launch_height = BL_LaserLaunchHeight[id]
            set this.launch_angle = BL_LaserLaunchAngle[id]
          
            set this.spread_xy = BL_LaserSpreadXY[id]
            set this.spread_z = BL_LaserSpreadZ[id]
          
            set this.follow_target = BL_FollowTarget[id]
            set this.floating_launch = BL_FloatingLaunch[id]
            set this.impact_lock = BL_LaserImpactLock[id]
            set this.range_lock = BL_LaserRangeLock[id]
          
            set this.range_start = BL_LaserRangeStart[id]
            set this.range_per_laser = BL_LaserRangePerLaser[id]
            set this.attack_type = BL_LaserAttackType[id]
            set this.damage_type = BL_LaserDamageType[id]
          
            set this.uninterruptible = BL_Uninterruptible[id]
            set this.hit_all = BL_LaserHitAll[id]
            set this.friendly_fire = BL_LaserFriendlyFire[id]
          
            //there's a hard limit to how many lasers you may have per set. This is because you have to declare the size of arrays of variables in a struct
            //if you want to have more that 20 lasers per set, feel free to change the value of MAX_LASERS at the top.
            if BL_NumberOfLasers[id] > MAX_LASERS then
                set this.laser_num = MAX_LASERS
            else
                set this.laser_num = BL_NumberOfLasers[id]
            endif
          
            set this.laser_index = 0
          
            if this.sentry == null then
                if this.launch_offset > 0. then
                    set this.launch_x = source_x + Cos(startangle + this.launch_angle) * this.launch_offset
                    set this.launch_y = source_y + Sin(startangle + this.launch_angle) * this.launch_offset
                else
                    set this.launch_x = source_x
                    set this.launch_y = source_y
                endif
                set this.launch_z = GetUnitZ(this.source) + this.launch_height
            else
                set sentry_x = GetUnitX(this.sentry)
                set sentry_y = GetUnitY(this.sentry)
                if this.launch_offset > 0. then
                    set this.launch_x = sentry_x + Cos(startangle + this.launch_angle) * this.launch_offset
                    set this.launch_y = sentry_y + Sin(startangle + this.launch_angle) * this.launch_offset
                else
                    set this.launch_x = sentry_x
                    set this.launch_y = sentry_y
                endif
                set this.launch_z = GetUnitZ(this.sentry) + this.launch_height
            endif
              
            set range_offset = ((this.laser_num-1) * this.range_per_laser + this.range_start) * .5
            if this.range_lock then //if true, start from the source's point
                set this.impact_x = source_x + Cos(startangle) * (range_offset + this.range_start)
                set this.impact_y = source_y + Sin(startangle) * (range_offset + this.range_start)
                set startangle = startangle + BL_LaserDirectionalTilt[id]
                set this.impact_x = this.impact_x + Cos(startangle) * (range_offset)
                set this.impact_y = this.impact_y + Sin(startangle) * (range_offset)
                set impact_spread_x = this.impact_x + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                set impact_spread_y = this.impact_y + GetRandomReal(this.spread_xy*-1, this.spread_xy)
                if this.grounded then
                    set this.impact_z = 0.
                    set impact_spread_z = 0.
                else
                    set this.impact_z = GetUnitZ(this.target)
                    set impact_spread_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * COLLISION_PERCENTAGE
                endif
            else
                set startangle = startangle + BL_LaserDirectionalTilt[id] + 3.14159
                if this.range_per_laser != 0. then
                    set this.impact_x = target_x + Cos(startangle) * (range_offset + this.range_start)
                    set this.impact_y = target_y + Sin(startangle) * (range_offset + this.range_start)
                else
                    set this.impact_x = target_x + Cos(startangle) * (this.target_coll * COLLISION_PERCENTAGE)
                    set this.impact_y = target_y + Sin(startangle) * (this.target_coll * COLLISION_PERCENTAGE)
                endif
                set impact_spread_x = this.impact_x + GetRandomReal(this.spread_xy * -1, this.spread_xy)
                set impact_spread_y = this.impact_y + GetRandomReal(this.spread_xy * -1, this.spread_xy)
                if this.grounded then
                    set this.impact_z = 0.
                    set impact_spread_z = 0.
                else
                    set this.impact_z = GetUnitZ(this.target)
                    set impact_spread_z = this.impact_z + GetRandomReal(this.spread_z*-1, this.spread_z) + this.target_coll * COLLISION_PERCENTAGE
                endif
            endif
          
            if BL_LaserLingerInterval[id] > 0 then
                set this.damage = damage / ((BL_LaserDuration[id] + BL_LaserFadeTime[id]) / BL_LaserLingerInterval[id])
            else
                set this.damage = damage
            endif
            set b = ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_spread_x, impact_spread_y, impact_spread_z)
          
            //Laser
            if this.laser_string != null then
                set b = true
              
                set this.f_impact_x[this.laser_index] = impact_spread_x
                set this.f_impact_y[this.laser_index] = impact_spread_y
                set this.f_impact_z[this.laser_index] = impact_spread_z
              
                set this.f_alpha[this.laser_index] = this.alpha
                set this.f_fading[this.laser_index] = false
              
                if this.impact_lock then
                    set this.f_lock_angle[this.laser_index] = Atan2(impact_spread_y - target_y, impact_spread_x - target_x)
                    set this.f_lock_distance[this.laser_index] = SquareRoot( (impact_spread_x-target_x)*(impact_spread_x-target_x) + (impact_spread_y-target_y)*(impact_spread_y-target_y) )
                    set this.f_lock_height[this.laser_index] = impact_spread_z - GetUnitZ(this.target)
                endif
              
                set this.fade_amount = this.alpha / this.fade_time
                set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, this.launch_x, this.launch_y, this.launch_z, impact_spread_x, impact_spread_y, impact_spread_z)
                //set this.f_is_laser_dead[this.laser_index] = false
                set this.f_lingering_laser_interval[this.laser_index] = this.lingering_laser_interval
                call SetLightningColor(this.f_laser[this.laser_index], this.red, this.green, this.blue, this.f_alpha[this.laser_index])
              
                set this.laser_index = this.laser_index + 1
            endif
          
            //Volley
            if this.laser_num > 1 then
                if b then
                    set this.failsafe_check = true
                else
                    set this.failsafe_check = false
                endif
                set this.laser_angle = startangle + 3.14159
                set this.initial_x = this.impact_x
                set this.initial_y = this.impact_y
                set this.count = this.laser_num - 1
                set this.tmr_volley = NewTimerEx(this)
                call TimerStart(this.tmr_volley, this.interval, true, timer_volley_handler)
            endif
          
            //Timer
            if b then
                loop
                    set this.f_is_laser_dead[iLoop] = false
                    set iLoop = iLoop + 1
                    exitwhen iLoop > this.laser_num
                endloop
                set this.failsafe_check = true
                set this.tmr_laserfade = NewTimerEx(this)
                call TimerStart(this.tmr_laserfade, .03125, true, timer_laserfade_handler)
                set b = false
            else
                set this.failsafe_check = false
            endif
      
            return this
        endmethod
      
      
        static method onInit takes nothing returns nothing
            set timer_volley_handler = function thistype.BurstLaser_Volley
            set timer_laserfade_handler = function thistype.LaserFade_Timer
        endmethod
    endstruct
  
endlibrary
I've tried setting all possible arrays of this.f_is_laser_dead[] to false before the timer even goes off but even that didn't fix jack.
 
and falsed the booleans (do booleans matter? I can't remember)
Matter for "leaks", no. But maybe you want to ensure some default values, then you still have to reset them.
But I usually like making default values only and right there when a new instance allocates.

I do still call ReleaseTimer() outside of that method because there are two of them and I don't know how I'm going to differentiate between which ones because destroy can't take any arguments.
A simple and good way would maybe just to check for both timers in the destroy method.
If timer != null, then release it.

An instance can have 2 timers so 2 callbacks.
In each callback you potentialy call the destroy function for the instance.
What lightnings are not properly destroyed?
Do you ensure the instance is only destroyed once?
It may help to print messages inside your callbacks when the instance is about being destroyed, so you know which runs properly,
because I'm a bit confused right now how it's planned to work exactly.
 
gonna have to help me out with the coder linguo there, lol. What's a callback? Is that the method the timer runs?

The first timer, BurstLaser_Volley, will create new lightning effects on a set interval. The second timer, LaserFade_Timer, will fade each lightning effect after a set duration. All lightning effects created in the same instance are all part of a set, and the call this.destroy() is called only on the second timer, unless there are no lightning effects or special effects, in which case the boolean this.failsafe_check is set to false. If that boolean is false, call this.destroy() is called in the first timer because the second timer was never created in the first place, so there were no lightning effects / special effects to destroy. If this.failsafe_check is true, the first timer will only be released and call this.destroy() will be called in the second timer instead.

As for destroying lightning effects, I used arrays, and the second timer loops through each number from 0 to the highest number of lasers shot per set, and updates their position accordingly. The issue I'm encountering is that if the interval between lasers is too long, the subsequent lightning effects that are created will fail to disappear. When new a new laser set starts from that same unit, the lightning effects that were hanging around are used by the new instance. Eventually, however, newer lightning effects just bug out entirely.

I've attached the demo map down below. you can test the problem by making the spellbreaker attack something.
 

Attachments

  • Burst Laser v2.0.w3x
    168.9 KB · Views: 44
is this correct:

"create" creates one lightning
"create" fires "volley" and "fade" periodicly

"volley" creates lightings and adds them to an lightning array
"fade" handles the lightnng array and fades them out (starts fading after a certain duration (?))

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

shouldn't you fade only until the current lightning amount in "fade"?:

JASS:
set iLoop = iLoop + 1
exitwhen iLoop >= this.laser_num

-> (?)

JASS:
set iLoop = iLoop + 1
exitwhen iLoop >= this.laser_index

The destroy method should only be called when everything is over. Not for each specific lightning.
All lightnings relate to one instance. So the instance is not allowed to be destroyed before there are all lightnings faded out from the array.

so I'm not sure if this.failsafe_check makes sense, because lightings should be "destroyed" with destroy method, but just cleaned properly from array.

set_is_ongoing defines if there is no lightning left in the array?
 
"create" will start 2 timers, "volley" and "fade". I imagine that's what you meant when you said it fires them periodically. You're correct about it firing one lightning and about "volley" and "fade".

The reason I use this.laser_num instead of this.laser_index is because the latter increases as the "volley" timer repeats, so checking for it's value in "fade" could return a lower number before all the lightnings have been created and end the instance prematurely. this.laser_num from the get-go is assigned the maximum possible number of lightnings for that instance so the loop in "fade" will always cycle through all the lightnings that can be created for this instance. The reason why I say 'possible' is that "volley" can be interrupted if the source is stunned or moves or dies, etc.

I destroy each lightning after they have been made completely transparent and set this.f_is_laser_dead[iLoop] to true to tell the system that the lightning effect has been destroyed. If the anytime during the loop the system detects that that boolean is false, it will set the local boolean set_is_ongoing to true. As long as the latter is true, call this.destroy() cannot be called. All arrays of this.f_is_laser_dead[iLoop] are set to false before the "fade" timer starts in "create".

this.failsafe_check is meant as a safety measure in case the user forgot to set any special effect or lightning strings, in which case the "fade" timer doesn't go off and the instance gets destroyed immediately after "volley" has shot the last 'laser'. It's really only a safety measure the system has.

I hope this clarified my convoluted code a bit >.<
 
Last edited:
checking for it's value in "fade" could return a lower number before all the lightnings have been created and end the instance prematurely
It will be lower than laser_num, yes, and is required to be I think.
You only need to loop through existing lightinings.
And it should not end prematurely-- if it would, some structure is wrong probably.

call destroy function if:

1) all binded lightnings were created and destroyed
You can check that when you are about to destroy a lightning.
When index number of destroyed lightning is "this.laser_num" it means you destroy the last lightning

2) unit gets stunned, interrupted, or anything alike

-----

destroy:

Inside the destroy function you loop through all lightnings and destroy them in case they are not destroyed yet.
And you also destroy/recylce both periodic timers volley/fade.
 
The interruption is only for "volley". All created lightnings must be faded, interruption or not. If the instance is set as being uninterruptible, then all lightnings will get created, otherwise this.laser_index can only potentially reach the same value as this.laser_num, because it's possible for the instance to be interrupted (eg: source dies), which means this.laser_index will no longer increase and call ReleaseTimer(this.tmr_volley) will be called.

There is some sense to what you're saying about not having to loop through all possible lightnings, now that you mention it. I'll try that.

1) hmm, I actually never thought of that. I could do set this.laser_num = this.laser_index in the event of interruption.

2) That's already the case, but there has to be no "fade" timer running.

-----

I'm still not sure why I should recycle the timers inside the destroy function rather that when the last lightning is created/destroyed.
 
Last edited:
Ah, ok, I got you.

instance gets interrupted -> recycle volley timer and not call destroy function

inside volley, if you reach "this.laser_num" -> recycle volley timer and not call destroy function

1) hmm, I actually never thought of that. I could do
set this.[COLOR=color: #666666]laser_num[/COLOR] = this.[COLOR=color: #666666]laser_index[/COLOR]
in the event of interruption.
sounds good, yes

and inside fade, loop from 1 to _index

and if you are about to destroy lightning in "fade" with index "this.laser_num" then you call destroy function ( so destroy is always and only called here, if the last lightning is destroyed in "fade")
 
Progress!

Detecting the last lightning seems to have been the way to go all along, although now I think I may have detected an issue I hadn't seen before - I think the system is leaking something and I managed to crash the thread twice during testing.

Moreover, I can't get the interruption to work properly. I'm trying to create a trigger and add events to it, but the method the trigger runs need the value of this and I don't know how to do that. It worked with timers because I'm using TimerUtils, but for a trigger I have no idea how to pull that off. So I looked at how TimerUtils stores data and it does it with either Hashtables or some weird thing with GetHandleId(timer) - 0x100000. I did the same thing with the trigger and it looks like it works. How, though? Search me.

Attack Module

Main Code

JASS:
library BurstLaserOnAttackModule initializer init requires BurstLaser

    globals
        private BurstLaser burstlaser
        private trigger BL_Module_onAttack = CreateTrigger()
    endglobals

    function BL_Module_onAttack_Actions takes nothing returns boolean
        local real Damage = 0.
        local integer Source_ID = GetUnitUserData(udg_DamageEventSource)
        if BL_UsesBurstLaser[Source_ID] and udg_DamageEventType == 0 and not udg_IsDamageSpell then
            set udg_DamageEventAmount = 0.
            if BL_LaserDivideDamage[Source_ID] then
                set Damage = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget)) / BL_NumberOfLasers[Source_ID]
            else
                set Damage = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget))
            endif
       
            //You may add new stuff here, like creating a timed LaserSentry[Source_ID] for a specific unit if you want some fancier launch effects, for example.
            //Or you may make a unit fire different types of Burst Laser for ground and flying.
       
            set burstlaser = BurstLaser.create(udg_DamageEventSource, udg_DamageEventTarget, BL_LaserSentry[Source_ID], Source_ID, Damage)
        endif
        return false
    endfunction

    //===========================================================================
    function init takes nothing returns nothing
        call TriggerRegisterVariableEvent( BL_Module_onAttack, "udg_DamageModifierEvent", EQUAL, 1.00 )
        call TriggerAddCondition( BL_Module_onAttack, function BL_Module_onAttack_Actions )
    endfunction

endlibrary
JASS:
library BurstLaser requires BL, GetUnitCollision, ZLibrary, TimerUtils, ArmorUtils, SpecialEffectZ

    globals
        private group DamageGroup = CreateGroup()
        private integer array data
        private constant real MAX_COLLISION_SIZE = 196.
        private constant real COLLISION_PERCENTAGE = .8
        private constant integer MAX_LASERS = 20 //Maximum number of lasers per set. Feel free to increase.
        private constant integer OFFSET = 0x100000
    endglobals
  
    struct BurstLaser
        unit source
        unit target
        unit sentry
      
        player owner
        integer id
      
        real damage
      
        real red
        real blue
        real green
        real alpha
        real fade_amount
        real fade_time
      
        integer duration
        integer count
        real interval
      
        real lock_angle
        real lock_distance
        real lock_height
      
        lightning laser
        boolean fading
        boolean grounded
        boolean hit_all
        boolean friendly_fire
        boolean uninterruptible
      
        real launch_offset
        real launch_height
        real launch_angle
        real launch_x
        real launch_y
        real launch_z
        real launch_scatter_x
        real launch_scatter_y
        real launch_scatter_z
        real impact_x
        real impact_y
        real impact_z
        real impact_scatter_x
        real impact_scatter_y
        real impact_scatter_z
      
        real target_coll
      
        real laser_angle
        boolean follow_target       //If false, Lasers in a set will strike the same point instead of updating their impact location to follow a moving target.
        boolean floating_launch
        boolean impact_lock         //if true then Lasers that have struck a target will follow it around until they have been destroyed.
        boolean range_lock          //If true will set LaserRangeStart's starting point to the attacker (source of the laser) instead of the taret
        real range_start            //Adds or subtracts a certain amount of distance from it's starting point.
        real range_per_laser        //Adds or substracts distance per Laser in a set.
        integer lingering_laser_interval //the laser's sfx and damage will repeat every time this integer reaches 0 during the laserfade_timer. It will keep reseting until the laser ends.
      
        string laser_string
        string launch_string
        string impact_string
        string area_string
      
        real aoe
        attacktype attack_type
        integer damage_type
      
        real initial_x
        real initial_y
      
        timer tmr_volley
        timer tmr_laserfade
      
        boolean failsafe_check
        boolean force_end
      
        integer laser_num
        integer laser_index
      
        trigger interrupt_trigger
          
        //Fade Laser Timer Variables
        lightning array f_laser[MAX_LASERS]
        real array f_alpha[MAX_LASERS]
        real array f_fade_amount[MAX_LASERS]
      
        boolean array f_is_laser_dead[MAX_LASERS]
        integer array f_lingering_laser_interval[MAX_LASERS]
      
        real array f_lock_angle[MAX_LASERS]
        real array f_lock_distance[MAX_LASERS]
        real array f_lock_height[MAX_LASERS]
      
        real array f_launch_x[MAX_LASERS]
        real array f_launch_y[MAX_LASERS]
        real array f_launch_z[MAX_LASERS]
      
        real array f_launch_scatter_x[MAX_LASERS]
        real array f_launch_scatter_y[MAX_LASERS]
        real array f_launch_scatter_z[MAX_LASERS]
      
        real array f_impact_x[MAX_LASERS]
        real array f_impact_y[MAX_LASERS]
        real array f_impact_z[MAX_LASERS]
      
        real array f_duration[MAX_LASERS]
        boolean array f_fading[MAX_LASERS]
        boolean array f_uninterruptible[MAX_LASERS]
      
        static code timer_volley_handler
        static code timer_laserfade_handler
        static code interruption_event_handler
      
      
      
        method destroy takes nothing returns nothing
            local integer iLoop = 0
            call this.deallocate()
          
            set this.owner = null
            set this.source = null
            set this.target = null
            set this.sentry = null
          
            set this.laser_string = null
            set this.launch_string = null
            set this.impact_string = null
            set this.area_string = null
          
            set this.grounded = false
          
            set this.follow_target = false
            set this.floating_launch = false
            set this.impact_lock = false
            set this.range_lock = false
          
            set this.attack_type = null
          
            set this.uninterruptible = false
            set this.hit_all = false
            set this.friendly_fire = false
          
            loop
                if this.f_laser[iLoop] != null then
                    call DestroyLightning(this.f_laser[iLoop])
                    set this.f_laser[iLoop] = null
                endif
                set iLoop = iLoop + 1
                exitwhen iLoop >= this.laser_num
            endloop

        endmethod
      
      
      
        method IsSourceInterrupted takes nothing returns boolean
            local boolean b = false
            if this.uninterruptible and /*
            [Move]      */ (GetUnitCurrentOrder(this.source) == 851986 or /*
            [Stunned]   */  GetUnitCurrentOrder(this.source) == 851973 or /*
            [Stop]      */  GetUnitCurrentOrder(this.source) == 851972) then
                set b = true
            endif
            return b
        endmethod
      
      
      
        method ApplySfxAndDamage takes real launch_x, real launch_y, real launch_z, real impact_x, real impact_y, real impact_z returns boolean
            local unit u = null
            local boolean b = false
            //Launch
            if this.launch_string != null then
                set b = true
                call DestroyEffect(AddSpecialEffectZ(this.launch_string, launch_x, launch_y, launch_z))
            endif
            //Impact
            if this.impact_string != null then
                set b = true
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.impact_string, impact_x, impact_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.impact_string, impact_x, impact_y, impact_z))
                endif
            endif
            //Area
            if this.area_string != null then
                set b = true
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.area_string, impact_x, impact_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.area_string, impact_x, impact_y, impact_z))
                endif
            endif
            //Damage
            if this.aoe > 0. then
                call GroupEnumUnitsInRange(DamageGroup, impact_x, impact_y, this.aoe + MAX_COLLISION_SIZE, null)
                loop
                    set u = FirstOfGroup(DamageGroup)
                    call GroupRemoveUnit(DamageGroup, u)
                    exitwhen u == null
                    if IsUnitInRangeXY(u, impact_x, impact_y, this.aoe) and UnitAlive(u) and /*
                    [Friendly Fire] */( (this.friendly_fire and (IsUnitEnemy(u, this.owner) or IsUnitAlly(u, this.owner))) or /*
                    [Enemy]         */  ( (not this.friendly_fire and IsUnitEnemy(u, this.owner)) or  /*
                    [Friendly]      */    (not this.friendly_fire and IsUnitAlly(u, this.owner) and u == this.target) ) ) and /*
                    [Magic Immune]  */( (this.attack_type == ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) or /*
                    [Ethereal]      */  (this.attack_type != ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_ETHEREAL)) ) then
                        if not this.hit_all and /*
                        [Ground]    */ ( (not IsUnitType(u, UNIT_TYPE_FLYING) and not IsUnitType(this.target, UNIT_TYPE_FLYING)) or /*
                        [Flying]    */ (IsUnitType(u, UNIT_TYPE_FLYING) and IsUnitType(this.target, UNIT_TYPE_FLYING)) ) then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        elseif this.hit_all then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                    endif
                endloop
            else
                set udg_NextDamageType = this.damage_type
                call UnitDamageTarget(this.source, this.target, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
            return b
        endmethod
      
      
      
        static method Interruption_Event takes nothing returns nothing
            local thistype this = data[GetHandleId(GetTriggeringTrigger()) - OFFSET]
            if GetOrderTargetUnit() == null then
                if /*
                [Move]      */  GetUnitCurrentOrder(this.source) == 851986 or /*
                [Stunned]   */  GetUnitCurrentOrder(this.source) == 851973 or /*
                [Stop]      */  GetUnitCurrentOrder(this.source) == 851972 or /*
                [Patrol]    */  GetUnitCurrentOrder(this.source) == 851990 or /*
                [Hold Poz]  */  GetUnitCurrentOrder(this.source) == 851993 or /*
                [Attack]    */  GetUnitCurrentOrder(this.source) == 851983 or /*
                [Smart]     */  GetUnitCurrentOrder(this.source) == 851971 /*
                */ then
                    call BJDebugMsg("Interrupted")
                    set this.force_end = true
                endif
            else
                if /*
                [Move]      */  GetUnitCurrentOrder(this.source) == 851986 or /*
                [Patrol]    */  GetUnitCurrentOrder(this.source) == 851990 or /*
                [Attack]    */  GetUnitCurrentOrder(this.source) == 851983 or /*
                [Smart]     */  GetUnitCurrentOrder(this.source) == 851971 /*
                */ then
                    call BJDebugMsg("Interrupted")
                    set this.force_end = true
                endif
            endif
        endmethod
      
      
      
        static method LaserFade_Timer takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
      
            local real source_x = GetUnitX(this.source)
            local real source_y = GetUnitY(this.source)
            local real target_x = GetUnitX(this.target)
            local real target_y = GetUnitY(this.target)
            local real angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
          
            local real launch_x = 0.
            local real launch_y = 0.
            local real launch_z = 0.
          
            local real impact_x = 0.
            local real impact_y = 0.
            local real impact_z = 0.
      
            local integer iLoop = 0
            local boolean set_is_ongoing = false
            local boolean b = false
          
            loop
                if this.f_laser[iLoop] != null then
                    if this.sentry == null then
                        if not this.floating_launch then
                            set launch_x = source_x + Cos(angle) * this.launch_offset
                            set launch_y = source_y + Sin(angle) * this.launch_offset
                            set launch_z = GetUnitZ(this.source) + this.launch_height
                        else
                            set launch_x = this.f_launch_x[iLoop]
                            set launch_y = this.f_launch_y[iLoop]
                            set launch_z = this.f_launch_z[iLoop]
                        endif
                    else
                        if not this.floating_launch then
                            set launch_x = GetUnitX(this.sentry) + Cos(angle) * this.launch_offset
                            set launch_y = GetUnitY(this.sentry) + Sin(angle) * this.launch_offset
                            set launch_z = GetUnitZ(this.sentry) + this.launch_height
                        else
                            set launch_x = this.f_launch_x[iLoop]
                            set launch_y = this.f_launch_y[iLoop]
                            set launch_z = this.f_launch_z[iLoop]
                        endif
                    endif
                    set launch_x = launch_x + this.f_launch_scatter_x[iLoop]
                    set launch_y = launch_y + this.f_launch_scatter_y[iLoop]
                    set launch_z = launch_z + this.f_launch_scatter_z[iLoop]
                  
                    if this.impact_lock then
                        set impact_x = target_x + Cos(this.f_lock_angle[iLoop]) * this.f_lock_distance[iLoop]
                        set impact_y = target_y + Sin(this.f_lock_angle[iLoop]) * this.f_lock_distance[iLoop]
                        set impact_z = GetUnitZ(this.target) + this.f_lock_height[iLoop]
                    else
                        set impact_x = this.f_impact_x[iLoop]
                        set impact_y = this.f_impact_y[iLoop]
                        set impact_z = this.f_impact_z[iLoop]
                    endif
                  
                    if this.f_laser[iLoop] != null then
                        call MoveLightningEx(this.f_laser[iLoop], true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                    endif
                  
                    if this.f_lingering_laser_interval[iLoop] > 0 then
                        set this.f_lingering_laser_interval[iLoop] = this.f_lingering_laser_interval[iLoop] - 1
                        if this.f_lingering_laser_interval[iLoop] <= 0 then
                            set this.f_lingering_laser_interval[iLoop] = this.lingering_laser_interval
                            set b = ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                        endif
                    endif
                  
                    if this.f_fading[iLoop] then
                        set this.f_alpha[iLoop] = this.f_alpha[iLoop] - this.fade_amount
                        if this.f_laser[iLoop] != null then
                            call SetLightningColor(this.f_laser[iLoop], this.red, this.blue, this.green, this.f_alpha[iLoop])
                        endif
                        if this.f_alpha[iLoop] <= 0. then
                          
                            //EVENT
                            /*set BL_EVENT_Source = this.source
                            set BL_EVENT_Target = this.target
                            set BL_EVENT_Sentry = this.sentry
                            set BL_EVENT_Launch_x = launch_x
                            set BL_EVENT_Launch_y = launch_y
                            set BL_EVENT_Launch_z = launch_z
                            set BL_EVENT_Impact_x = impact_x
                            set BL_EVENT_Impact_y = impact_x
                            set BL_EVENT_Impact_z = impact_z
                            set BL_EVENT = 2.5
                            set BL_EVENT_Source = null
                            set BL_EVENT_Target = null
                            set BL_EVENT_Sentry = null
                            set BL_EVENT_Launch_x = 0.
                            set BL_EVENT_Launch_y = 0.
                            set BL_EVENT_Launch_z = 0.
                            set BL_EVENT_Impact_x = 0.
                            set BL_EVENT_Impact_y = 0.
                            set BL_EVENT_Impact_z = 0.
                            set BL_EVENT = 0.*/
                            //END EVENT
                          
                            set this.f_is_laser_dead[iLoop] = true
                            if this.f_laser[iLoop] != null then
                                call DestroyLightning(this.f_laser[iLoop])
                                set this.f_laser[iLoop] = null
                            endif
                          
                        endif
                    else
                        set this.f_duration[iLoop] = this.f_duration[iLoop] - 1
                        if this.f_duration[iLoop] <= 0 then
                            set this.f_fading[iLoop] = true
                        endif
                    endif//if this.f_fading[iLoop] then
                  
                endif//if not this.f_is_laser_dead[iLoop] then
              
                set iLoop = iLoop + 1
                exitwhen iLoop >= this.laser_index
            endloop
          
            if this.f_is_laser_dead[this.laser_num] then
                call ReleaseTimer(this.tmr_laserfade)
                call this.destroy()
            endif
        endmethod
      
      
      
        static method BurstLaser_Volley takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
          
            local real source_x = GetUnitX(this.source)
            local real source_y = GetUnitY(this.source)
            local real target_x = GetUnitX(this.target)
            local real target_y = GetUnitY(this.target)
            local real angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
          
            local real launch_x = 0.
            local real launch_y = 0.
            local real launch_z = 0.
          
            local real launch_scatter_x = 0.
            local real launch_scatter_y = 0.
            local real launch_scatter_z = 0.
          
            local real impact_x = 0.
            local real impact_y = 0.
            local real impact_z = 0.
          
            local boolean b = false
            local unit u = null
          
            if this.sentry == null then
                if not this.floating_launch then
                    set launch_x = source_x + Cos(angle + this.launch_angle) * this.launch_offset
                    set launch_y = source_y + Sin(angle + this.launch_angle) * this.launch_offset
                    set launch_z = GetUnitZ(this.source) + this.launch_height
                else
                    set launch_x = this.launch_x
                    set launch_y = this.launch_y
                    set launch_z = this.launch_z
                endif
            else
                if not this.floating_launch then
                    set launch_x = GetUnitX(this.sentry) + Cos(angle + this.launch_angle) * this.launch_offset
                    set launch_y = GetUnitY(this.sentry) + Sin(angle + this.launch_angle) * this.launch_offset
                    set launch_z = GetUnitZ(this.sentry) + this.launch_height
                else
                    set launch_x = this.launch_x
                    set launch_y = this.launch_y
                    set launch_z = this.launch_z
                endif
            endif
            set launch_scatter_x = GetRandomReal(this.launch_scatter_x*-1, this.launch_scatter_x)
            set launch_scatter_y = GetRandomReal(this.launch_scatter_y*-1, this.launch_scatter_y)
            set launch_scatter_z = GetRandomReal(this.launch_scatter_z*-1, this.launch_scatter_z)
            set launch_x = launch_x + launch_scatter_x
            set launch_y = launch_y + launch_scatter_y
            set launch_z = launch_z + launch_scatter_z
          
            if this.range_lock then //if true, start from the source's point
                if this.follow_target then
                    if this.range_per_laser != 0. then
                        set angle = Atan2(target_y - this.initial_y, target_x - this.initial_x)
                        set this.impact_x = this.impact_x + Cos(angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set this.impact_z = GetUnitZ(this.target)
                        set impact_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                    endif
                else
                    if this.range_per_laser != 0. then
                        set this.impact_x = this.impact_x + Cos(this.laser_angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(this.laser_angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                        set this.impact_z = GetUnitZ(this.target)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set impact_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                    endif
                endif
            else
                if this.follow_target then
                    if this.range_per_laser != 0. then
                        set angle = Atan2(target_y - this.initial_y, target_x - this.initial_x)
                        set this.impact_x = this.impact_x + Cos(angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set this.impact_z = GetUnitZ(this.target)
                        set impact_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                    endif
                else
                    if this.range_per_laser != 0. then
                        set this.impact_x = this.impact_x + Cos(this.laser_angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(this.laser_angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                        set this.impact_z = GetUnitZ(this.target)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set impact_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                    endif
                endif
            endif
          
            set b = ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
          
            //Laser
            if this.laser_string != null then
                set b = true
              
                set this.f_launch_x[this.laser_index] = launch_x
                set this.f_launch_y[this.laser_index] = launch_y
                set this.f_launch_z[this.laser_index] = launch_z
              
                if not this.floating_launch then
                    set this.f_launch_scatter_x[this.laser_index] = launch_scatter_x
                    set this.f_launch_scatter_y[this.laser_index] = launch_scatter_y
                    set this.f_launch_scatter_z[this.laser_index] = launch_scatter_z
                endif
              
                set this.f_impact_x[this.laser_index] = impact_x
                set this.f_impact_y[this.laser_index] = impact_y
                set this.f_impact_z[this.laser_index] = impact_z
              
                if this.impact_lock then
                    set this.f_lock_angle[this.laser_index] = Atan2(impact_y - target_y, impact_x - target_x)
                    set this.f_lock_distance[this.laser_index] = SquareRoot( (impact_x-target_x)*(impact_x-target_x) + (impact_y-target_y)*(impact_y-target_y) )
                    set this.f_lock_height[this.laser_index] = impact_z - GetUnitZ(this.target)
                endif
              
                set this.f_alpha[this.laser_index] = this.alpha
                set this.f_fading[this.laser_index] = false
              
                set this.fade_amount = this.alpha / this.fade_time
                set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                set this.f_lingering_laser_interval[this.laser_index] = this.lingering_laser_interval
                set this.f_duration[this.laser_index] = this.duration
                call SetLightningColor(this.f_laser[this.laser_index], this.red, this.green, this.blue, this.f_alpha[this.laser_index])
                set this.laser_index = this.laser_index + 1
            endif
          
            //EVENT
            /*set BL_EVENT_Source = LaserSource[tID]
            set BL_EVENT_Target = LaserTarget[tID]
            set BL_EVENT_Sentry = LaserSentry[tID]
            set BL_EVENT_Launch_x = LAUNCH_X
            set BL_EVENT_Launch_y = LAUNCH_Y
            set BL_EVENT_Launch_z = LAUNCH_Z
            set BL_EVENT_Impact_x = IMPACT_X
            set BL_EVENT_Impact_y = IMPACT_Y
            set BL_EVENT_Impact_z = IMPACT_Z
            set BL_EVENT = 2.
            set BL_EVENT_Source = null
            set BL_EVENT_Target = null
            set BL_EVENT_Sentry = null
            set BL_EVENT_Launch_x = 0.
            set BL_EVENT_Launch_y = 0.
            set BL_EVENT_Launch_z = 0.
            set BL_EVENT_Impact_x = 0.
            set BL_EVENT_Impact_y = 0.
            set BL_EVENT_Impact_z = 0.
            set BL_EVENT = 0.*/
            //END EVENT
          
            set this.count = this.count - 1
            if this.count <= 0 or not UnitAlive(this.source) or this.force_end then
          
                //EVENT
                /*set BL_EVENT_Source = LaserSource[tID]
                set BL_EVENT_Target = LaserTarget[tID]
                set BL_EVENT_Sentry = LaserSentry[tID]
                set BL_EVENT_Launch_x = LAUNCH_X
                set BL_EVENT_Launch_y = LAUNCH_Y
                set BL_EVENT_Launch_z = LAUNCH_Z
                set BL_EVENT_Impact_x = IMPACT_X
                set BL_EVENT_Impact_y = IMPACT_Y
                set BL_EVENT_Impact_z = IMPACT_Z
                set BL_EVENT = 3.
                set BL_EVENT_Source = null
                set BL_EVENT_Target = null
                set BL_EVENT_Sentry = null
                set BL_EVENT_Launch_x = 0.
                set BL_EVENT_Launch_y = 0.
                set BL_EVENT_Launch_z = 0.
                set BL_EVENT_Impact_x = 0.
                set BL_EVENT_Impact_y = 0.
                set BL_EVENT_Impact_z = 0.
                set BL_EVENT = 0.*/
                //END EVENT
              
                set this.laser_num = this.laser_index //This is set in case of interruption.
                if this.interrupt_trigger != null then
                    call DestroyTrigger(this.interrupt_trigger)
                endif
              
                call ReleaseTimer(this.tmr_volley)
                set this.tmr_volley = null
              
                if not this.failsafe_check then
                    call this.destroy()
                endif
            endif
        endmethod
      
      
      
        static method create takes unit unit_source, unit unit_target, unit unit_sentry, integer id, real damage returns thistype
      
            local thistype this = allocate()
          
            local real source_x = GetUnitX(unit_source)
            local real source_y = GetUnitY(unit_source)
            local real target_x = GetUnitX(unit_target)
            local real target_y = GetUnitY(unit_target)
            local real sentry_x = 0.
            local real sentry_y = 0.
            local real startangle = Atan2(target_y - source_y, target_x - source_x)
          
            local real range_offset = 0.
            local real impact_spread_x = 0.
            local real impact_spread_y = 0.
            local real impact_spread_z = 0.
          
            local integer iLoop = 0
            local boolean b = false
            local unit u = null
          
            set this.owner = GetOwningPlayer(unit_source)
            set this.source = unit_source
            set this.target = unit_target
            set this.sentry = unit_sentry
          
            set this.target_coll = GetUnitCollision(unit_target)
          
            set this.laser_string = BL_LaserString[id]
            set this.red = BL_RED[id]
            set this.blue = BL_BLUE[id]
            set this.green = BL_GREEN[id]
            set this.alpha = BL_ALPHA[id]
          
            set this.launch_string = BL_LaserLaunchFX[id]
            set this.impact_string = BL_LaserImpactFX[id]
            set this.area_string = BL_LaserAreaFX[id]
          
            set this.duration = BL_LaserDuration[id]
            set this.fade_time = BL_LaserFadeTime[id]
            set this.interval = BL_LaserInterval[id]
            set this.lingering_laser_interval = BL_LaserLingerInterval[id]
            set this.aoe = BL_LaserAOE[id]
            set this.grounded = BL_IsLaserGrounded[id]
          
            set this.launch_offset = BL_LaserLaunchOffset[id]
            set this.launch_height = BL_LaserLaunchHeight[id]
            set this.launch_angle = BL_LaserLaunchAngle[id]
          
            set this.launch_scatter_x = BL_LaserLaunchScatterX[id]
            set this.launch_scatter_y = BL_LaserLaunchScatterY[id]
            set this.launch_scatter_z = BL_LaserLaunchScatterZ[id]
          
            set this.impact_scatter_x = BL_LaserImpactScatterX[id]
            set this.impact_scatter_y = BL_LaserImpactScatterY[id]
            set this.impact_scatter_z = BL_LaserImpactScatterZ[id]
          
            set this.follow_target = BL_FollowTarget[id]
            set this.floating_launch = BL_FloatingLaunch[id]
            set this.impact_lock = BL_LaserImpactLock[id]
            set this.range_lock = BL_LaserRangeLock[id]
          
            set this.range_start = BL_LaserRangeStart[id]
            set this.range_per_laser = BL_LaserRangePerLaser[id]
            set this.attack_type = BL_LaserAttackType[id]
            set this.damage_type = BL_LaserDamageType[id]
          
            set this.uninterruptible = BL_Uninterruptible[id]
            set this.hit_all = BL_LaserHitAll[id]
            set this.friendly_fire = BL_LaserFriendlyFire[id]
          
            //there's a hard limit to how many lasers you may have per set. This is because you have to declare the size of arrays of variables in a struct
            //if you want to have more that 20 lasers per set, feel free to change the value of MAX_LASERS at the top.
            if BL_NumberOfLasers[id] > MAX_LASERS then
                set this.laser_num = MAX_LASERS
            else
                set this.laser_num = BL_NumberOfLasers[id]
            endif
          
            set this.laser_index = 0
          
            if this.sentry == null then
                if this.launch_offset > 0. then
                    set this.launch_x = source_x + Cos(startangle + this.launch_angle) * this.launch_offset
                    set this.launch_y = source_y + Sin(startangle + this.launch_angle) * this.launch_offset
                else
                    set this.launch_x = source_x
                    set this.launch_y = source_y
                endif
                set this.launch_z = GetUnitZ(this.source) + this.launch_height
            else
                set sentry_x = GetUnitX(this.sentry)
                set sentry_y = GetUnitY(this.sentry)
                if this.launch_offset > 0. then
                    set this.launch_x = sentry_x + Cos(startangle + this.launch_angle) * this.launch_offset
                    set this.launch_y = sentry_y + Sin(startangle + this.launch_angle) * this.launch_offset
                else
                    set this.launch_x = sentry_x
                    set this.launch_y = sentry_y
                endif
                set this.launch_z = GetUnitZ(this.sentry) + this.launch_height
            endif
            set launch_scatter_x = GetRandomReal(this.launch_scatter_x*-1, this.launch_scatter_x)
            set launch_scatter_y = GetRandomReal(this.launch_scatter_y*-1, this.launch_scatter_y)
            set launch_scatter_z = GetRandomReal(this.launch_scatter_z*-1, this.launch_scatter_z)
            //set this.launch_x = this.launch_x + launch_scatter_x
            //set this.launch_y = this.launch_y + launch_scatter_y
            //set this.launch_z = this.launch_z + launch_scatter_z
              
            set range_offset = ((this.laser_num-1) * this.range_per_laser + this.range_start) * .5
            if this.range_lock then //if true, start from the source's point
                set this.impact_x = source_x + Cos(startangle) * (range_offset + this.range_start)
                set this.impact_y = source_y + Sin(startangle) * (range_offset + this.range_start)
                set startangle = startangle + BL_LaserDirectionalTilt[id]
                set this.impact_x = this.impact_x + Cos(startangle) * (range_offset)
                set this.impact_y = this.impact_y + Sin(startangle) * (range_offset)
                set impact_spread_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                set impact_spread_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                if this.grounded then
                    set this.impact_z = 0.
                    set impact_spread_z = 0.
                else
                    set this.impact_z = GetUnitZ(this.target)
                    set impact_spread_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                endif
            else
                set startangle = startangle + BL_LaserDirectionalTilt[id] + 3.14159
                if this.range_per_laser != 0. then
                    set this.impact_x = target_x + Cos(startangle) * (range_offset + this.range_start)
                    set this.impact_y = target_y + Sin(startangle) * (range_offset + this.range_start)
                else
                    set this.impact_x = target_x + Cos(startangle) * (this.target_coll * COLLISION_PERCENTAGE)
                    set this.impact_y = target_y + Sin(startangle) * (this.target_coll * COLLISION_PERCENTAGE)
                endif
                set impact_spread_x = this.impact_x + GetRandomReal(this.impact_scatter_x * -1, this.impact_scatter_x)
                set impact_spread_y = this.impact_y + GetRandomReal(this.impact_scatter_y * -1, this.impact_scatter_y)
                if this.grounded then
                    set this.impact_z = 0.
                    set impact_spread_z = 0.
                else
                    set this.impact_z = GetUnitZ(this.target)
                    set impact_spread_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                endif
            endif
          
            if BL_LaserLingerInterval[id] > 0 then
                set this.damage = damage / ((BL_LaserDuration[id] + BL_LaserFadeTime[id]) / BL_LaserLingerInterval[id])
            else
                set this.damage = damage
            endif
            set b = ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_spread_x, impact_spread_y, impact_spread_z)
          
            //Laser
            if this.laser_string != null then
                set b = true
              
                set this.f_launch_x[this.laser_index] = this.launch_x + launch_scatter_x
                set this.f_launch_y[this.laser_index] = this.launch_y + launch_scatter_y
                set this.f_launch_z[this.laser_index] = this.launch_z + launch_scatter_z
              
                if not this.floating_launch then
                    set this.f_launch_scatter_x[this.laser_index] = launch_scatter_x
                    set this.f_launch_scatter_y[this.laser_index] = launch_scatter_y
                    set this.f_launch_scatter_z[this.laser_index] = launch_scatter_z
                endif
              
                set this.f_impact_x[this.laser_index] = impact_spread_x
                set this.f_impact_y[this.laser_index] = impact_spread_y
                set this.f_impact_z[this.laser_index] = impact_spread_z
              
                if this.impact_lock then
                    set this.f_lock_angle[this.laser_index] = Atan2(impact_spread_y - target_y, impact_spread_x - target_x)
                    set this.f_lock_distance[this.laser_index] = SquareRoot( (impact_spread_x-target_x)*(impact_spread_x-target_x) + (impact_spread_y-target_y)*(impact_spread_y-target_y) )
                    set this.f_lock_height[this.laser_index] = impact_spread_z - GetUnitZ(this.target)
                endif
              
                set this.f_alpha[this.laser_index] = this.alpha
                set this.f_fading[this.laser_index] = false
              
                set this.fade_amount = this.alpha / this.fade_time
                set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, this.launch_x, this.launch_y, this.launch_z, impact_spread_x, impact_spread_y, impact_spread_z)
                set this.f_lingering_laser_interval[this.laser_index] = this.lingering_laser_interval
                set this.f_duration[this.laser_index] = this.duration
                call SetLightningColor(this.f_laser[this.laser_index], this.red, this.green, this.blue, this.f_alpha[this.laser_index])
              
                set this.laser_index = this.laser_index + 1
            endif
          
            //Volley
            if this.laser_num > 1 then
                if b then
                    set this.failsafe_check = true
                else
                    set this.failsafe_check = false
                endif
                if not this.uninterruptible and this.interval > 0. then
                    set this.interrupt_trigger = CreateTrigger()
                    call TriggerRegisterUnitEvent(this.interrupt_trigger, this.source, EVENT_UNIT_ISSUED_TARGET_ORDER)
                    call TriggerRegisterUnitEvent(this.interrupt_trigger, this.source, EVENT_UNIT_ISSUED_POINT_ORDER)
                    call TriggerRegisterUnitEvent(this.interrupt_trigger, this.source, EVENT_UNIT_ISSUED_ORDER)
                    call TriggerAddAction(this.interrupt_trigger, interruption_event_handler)
                    set data[GetHandleId(this.interrupt_trigger) - OFFSET] = this
                endif
                set this.laser_angle = startangle + 3.14159
                set this.initial_x = this.impact_x
                set this.initial_y = this.impact_y
                set this.count = this.laser_num - 1
                set this.tmr_volley = NewTimerEx(this)
                call TimerStart(this.tmr_volley, this.interval, true, timer_volley_handler)
            endif
          
            //Timer
            if b then
                loop
                    set this.f_is_laser_dead[iLoop] = false
                    set iLoop = iLoop + 1
                    exitwhen iLoop > this.laser_num
                endloop
                set this.failsafe_check = true
                set this.tmr_laserfade = NewTimerEx(this)
                call TimerStart(this.tmr_laserfade, .03125, true, timer_laserfade_handler)
                set b = false
            else
                set this.failsafe_check = false
            endif
      
            return this
        endmethod
      
      
        static method onInit takes nothing returns nothing
            set timer_volley_handler = function thistype.BurstLaser_Volley
            set timer_laserfade_handler = function thistype.LaserFade_Timer
            set interruption_event_handler = function thistype.Interruption_Event
        endmethod
    endstruct
  
endlibrary
 
Last edited:
One unit can have multiple instances, or?
So you can not really track a specific instance by only having the unit.

The unit does need a reference to a list or array of all it's instances. For example it's handleId of unitindex may be used as reference.
And then at interruption, you take the unit reference, and destroy the linked list/array (which includes all instanced).

Simple example:

unit u = ...
counter = 0 (unit specific instance counter for only active instances)

NewInstance:

this = allocate()
counter = counter + 1
SaveInteger(hash, GetHandleId(u), counter, this)

Interruption:

loop from (1 to counter)
--- call LoadInteger(hash, GetHandleId(u), loopInt).destroy()stopVolley()

Always when you stop volley:

counter = counter - 1


====

Can you specifiy the crash? Like in which function is occurs. Iirc trying to destroy "null" lightnings will crash.

edit: ah yes, #10 [Crash] - List of WarCraft III Crashes


edit2:

oops, I forgot our last conversation. Of course not call destroy() on interruption, but just stop the volley thing.
 
Last edited:
It's not the game that crashes, the timer just stops working altogether. Kinda like what happens when you divide by zero, I found out recently. I'm convinced something must be leaking because when I test the framerate with fraps, the fps steadily drops are more and more lightnings are created. When the lightning are not being created/faded the fps goes back to sixty, but any subsequent beams after that will lag the game harder and harder until the whole thing dies. I attached a screenshot for reference.

PS: the 'Interrupted' message tells me that the timer is somehow failing to end since those units fire lasers so fast you would need the timer to go on indefinitely to even see that debug message.
 

Attachments

  • thread_crash.jpg
    thread_crash.jpg
    567.1 KB · Views: 63
Sure thing. Make the two gargoyles and gryphon riders attack the mass of riflemen to the east. The thread-crashing should happen when most riflemen are dead. You can also monitor the framerate and you'll notice how each new lightning becomes progressively more taxing.
 

Attachments

  • Burst Laser v2.0.w3x
    175.6 KB · Views: 47
Some things were mixed up. The fail safe things and such were not needed.
And destroy is only called inside fade out. Have a look (shoot with 1 rifleman towards something)

I disabled the interruption trigger for demo, it was not very right I think, but you can change it later maybe.
There can be one global interruption trigger maybe (never destroy it), and if a unit fires it that is currently in your custom attack (can be checked via a unit group for example)
you only do something like "call PauseTimer(this.tmr_volley)". Rest will be fine, but the volley timer isnt allowed to run one more time, because it could create a new lightning.

(bottom of volley, bottom of create, a bit in destroy, and a bit in fade function I changed)
 

Attachments

  • Burst Laser v2.0 (1).w3x
    176.8 KB · Views: 43
Huh, well that seems to have fixed the crashing issue. Hopefully, all the bugs have been ironed out!

How would I make the global interruption trigger detect which instance it's supposed to be halting, thought? Especially if there are multiple instances running on the same unit?
 
Last edited:
All instances should halt or. For example:

JASS:
globals
    hashtable hash = InitHashtable()
    constant integer COUNTER_KEY = -1
endglobals

function create takes unit u // attacker
   
    local thistype this = allocate()
   
    local integer id = GetHandleId(u)
    local integer counter = LoadInteger(hash, id, COUNTER_KEY) // get instance counter
   
    set counter = counter + 1
   
    call SaveInteger(hash, id, COUNTER_KEY, counter) // increase instance counter
   
    call SaveInteger(hash, id, counter, this) // bind instance to unit id
   
    set this.hashIndex = counter // so the instance knows where it's stored
   
   
   
function destroy

    local integer id = GetHandleId(.u)
    call RemoveSavedInteger(hash, id, this.hashIndex) // remove instance from hashtable
   
   
   
   
function interrupt // trigger function

    local integer id = GetHandleId(GetTriggerUnit())
    local integer counter  = LoadInteger(hash, id, COUNTER_KEY)
    local thistype this
   
    loop
        exitwhen counter < 1
       
        set this = LoadInteger(hash, id, counter)
        call PauseTimer(this.volleyTimer) // blindly stop all volleys
       
        set counter = counter - 1
    endloop
   
    call SaveInteger(hash, id, COUNTER_KEY, 0) // reset counter
   
   
function volley

    if this.laser_index == this.laser_num or unitIsDead .... then
   
        call PauseTimer(this.volleyTimer) // stop volley

        call SaveInteger(hash, id, COUNTER_KEY, LoadInteger(hash, id, counter) - 1) // decrease counter
   
    endif
 
Oh, man, hashtable confuse the hell out of me.

function destroy
what's .u ? is it this.source in my code?

function volley
what is the value of counter supposed to be? Same as with interrupt and create? (local integer counter = LoadInteger(hash, id, COUNTER_KEY))

I did as you instructed, but it doesn't seem like it's working. I'm probably missing something here or there:
I'll also upload the map as well just in case.

PS: what's the purpose of the return right after you pause the timer in volley?

JASS:
library BurstLaser requires BL, GetUnitCollision, ZLibrary, TimerUtils, ArmorUtils, SpecialEffectZ

    globals
        private hashtable hash = InitHashtable()
        private trigger interrupt_trigger = CreateTrigger()
        private group DamageGroup = CreateGroup()
    
        private integer array data
    
        private constant real MAX_COLLISION_SIZE = 196.
        private constant real COLLISION_PERCENTAGE = .8
        private constant integer MAX_LASERS = 20 //Maximum number of lasers per set. Feel free to increase.
        private constant integer OFFSET = 0x100000
        private constant integer COUNTER_KEY = -1
    endglobals
 
    struct BurstLaser
        unit source
        unit target
        unit sentry
    
        player owner
        integer id
    
        real damage
    
        real red
        real blue
        real green
        real alpha
        real fade_amount
        real fade_time
    
        integer duration
        integer count
        real interval
    
        real lock_angle
        real lock_distance
        real lock_height
    
        lightning laser
        boolean fading
        boolean grounded
        boolean hit_all
        boolean friendly_fire
        boolean uninterruptible
    
        real launch_offset
        real launch_height
        real launch_angle
        real launch_x
        real launch_y
        real launch_z
        real launch_scatter_x
        real launch_scatter_y
        real launch_scatter_z
        real impact_x
        real impact_y
        real impact_z
        real impact_scatter_x
        real impact_scatter_y
        real impact_scatter_z
    
        real target_coll
    
        real laser_angle
        boolean follow_target       //If false, Lasers in a set will strike the same point instead of updating their impact location to follow a moving target.
        boolean floating_launch
        boolean impact_lock         //if true then Lasers that have struck a target will follow it around until they have been destroyed.
        boolean range_lock          //If true will set LaserRangeStart's starting point to the attacker (source of the laser) instead of the taret
        real range_start            //Adds or subtracts a certain amount of distance from it's starting point.
        real range_per_laser        //Adds or substracts distance per Laser in a set.
        integer lingering_laser_interval //the laser's sfx and damage will repeat every time this integer reaches 0 during the laserfade_timer. It will keep reseting until the laser ends.
    
        string laser_string
        string launch_string
        string impact_string
        string area_string
    
        real aoe
        attacktype attack_type
        integer damage_type
    
        real initial_x
        real initial_y
    
        timer tmr_volley
        timer tmr_laserfade
    
        integer hashIndex
    
        integer laser_num
        integer laser_index
        
        //Fade Laser Timer Variables
        lightning array f_laser[MAX_LASERS]
        real array f_alpha[MAX_LASERS]
        real array f_fade_amount[MAX_LASERS]
    
        boolean array f_is_laser_dead[MAX_LASERS]
        integer array f_lingering_laser_interval[MAX_LASERS]
    
        real array f_lock_angle[MAX_LASERS]
        real array f_lock_distance[MAX_LASERS]
        real array f_lock_height[MAX_LASERS]
    
        real array f_launch_x[MAX_LASERS]
        real array f_launch_y[MAX_LASERS]
        real array f_launch_z[MAX_LASERS]
    
        real array f_launch_scatter_x[MAX_LASERS]
        real array f_launch_scatter_y[MAX_LASERS]
        real array f_launch_scatter_z[MAX_LASERS]
    
        real array f_impact_x[MAX_LASERS]
        real array f_impact_y[MAX_LASERS]
        real array f_impact_z[MAX_LASERS]
    
        real array f_duration[MAX_LASERS]
        boolean array f_fading[MAX_LASERS]
        boolean array f_uninterruptible[MAX_LASERS]
    
        static code timer_volley_handler
        static code timer_laserfade_handler
        static code interruption_event_handler
    
    
    
        method destroy takes nothing returns nothing
            local integer iLoop = 0
            local integer id = GetHandleId(this.source)
            call this.deallocate()
            call BJDebugMsg("deallocate")
            set this.owner = null
            set this.source = null
            set this.target = null
            set this.sentry = null
        
            set this.laser_string = null
            set this.launch_string = null
            set this.impact_string = null
            set this.area_string = null
        
            set this.grounded = false
        
            set this.follow_target = false
            set this.floating_launch = false
            set this.impact_lock = false
            set this.range_lock = false
        
            set this.attack_type = null
        
            set this.uninterruptible = false
            set this.hit_all = false
            set this.friendly_fire = false
        
            call ReleaseTimer(tmr_volley)
            call ReleaseTimer(tmr_laserfade)
        
            call RemoveSavedInteger(hash, id, this.hashIndex)

        endmethod
    
    
    
        method ApplySfxAndDamage takes real launch_x, real launch_y, real launch_z, real impact_x, real impact_y, real impact_z returns nothing
            local unit u = null
            //Launch
            if this.launch_string != null then
                call DestroyEffect(AddSpecialEffectZ(this.launch_string, launch_x, launch_y, launch_z))
            endif
            //Impact
            if this.impact_string != null then
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.impact_string, impact_x, impact_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.impact_string, impact_x, impact_y, impact_z))
                endif
            endif
            //Area
            if this.area_string != null then
                if this.grounded then
                    call DestroyEffect(AddSpecialEffect(this.area_string, impact_x, impact_y))
                else
                    call DestroyEffect(AddSpecialEffectZ(this.area_string, impact_x, impact_y, impact_z))
                endif
            endif
            //Damage
            if this.aoe > 0. then
                call GroupEnumUnitsInRange(DamageGroup, impact_x, impact_y, this.aoe + MAX_COLLISION_SIZE, null)
                loop
                    set u = FirstOfGroup(DamageGroup)
                    call GroupRemoveUnit(DamageGroup, u)
                    exitwhen u == null
                    if IsUnitInRangeXY(u, impact_x, impact_y, this.aoe) and UnitAlive(u) and /*
                    [Friendly Fire] */( (this.friendly_fire and (IsUnitEnemy(u, this.owner) or IsUnitAlly(u, this.owner))) or /*
                    [Enemy]         */  ( (not this.friendly_fire and IsUnitEnemy(u, this.owner)) or  /*
                    [Friendly]      */    (not this.friendly_fire and IsUnitAlly(u, this.owner) and u == this.target) ) ) and /*
                    [Magic Immune]  */( (this.attack_type == ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) or /*
                    [Ethereal]      */  (this.attack_type != ATTACK_TYPE_MAGIC and not IsUnitType(u, UNIT_TYPE_ETHEREAL)) ) then
                        if not this.hit_all and /*
                        [Ground]    */ ( (not IsUnitType(u, UNIT_TYPE_FLYING) and not IsUnitType(this.target, UNIT_TYPE_FLYING)) or /*
                        [Flying]    */ (IsUnitType(u, UNIT_TYPE_FLYING) and IsUnitType(this.target, UNIT_TYPE_FLYING)) ) then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        elseif this.hit_all then
                            set udg_NextDamageType = this.damage_type
                            call UnitDamageTarget(this.source, u, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                            call TriggerEvaluate(udg_ClearDamageEvent)
                        endif
                    endif
                endloop
            else
                set udg_NextDamageType = this.damage_type
                call UnitDamageTarget(this.source, this.target, this.damage, true, true, this.attack_type, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                call TriggerEvaluate(udg_ClearDamageEvent)
            endif
        endmethod
    
    
    
        static method Interruption_Event takes nothing returns nothing
            local integer id = GetHandleId(GetTriggerUnit())
            local integer counter  = LoadInteger(hash, id, COUNTER_KEY)
            local thistype this
            if GetOrderTargetUnit() == null then
                if /*
                [Move]      */  GetUnitCurrentOrder(this.source) == 851986 or /*
                [Stunned]   */  GetUnitCurrentOrder(this.source) == 851973 or /*
                [Stop]      */  GetUnitCurrentOrder(this.source) == 851972 or /*
                [Patrol]    */  GetUnitCurrentOrder(this.source) == 851990 or /*
                [Hold Poz]  */  GetUnitCurrentOrder(this.source) == 851993 or /*
                [Attack]    */  GetUnitCurrentOrder(this.source) == 851983 or /*
                [Smart]     */  GetUnitCurrentOrder(this.source) == 851971 /*
                */ then
                    loop
                        exitwhen counter < 1
                        set this = LoadInteger(hash, id, counter)
                        call PauseTimer(this.tmr_volley) // blindly stop all volleys
                        set counter = counter - 1
                    endloop
                    call SaveInteger(hash, id, COUNTER_KEY, 0) // reset counter
                endif
            else
                if /*
                [Move]      */  GetUnitCurrentOrder(this.source) == 851986 or /*
                [Patrol]    */  GetUnitCurrentOrder(this.source) == 851990 or /*
                [Attack]    */  GetUnitCurrentOrder(this.source) == 851983 or /*
                [Smart]     */  GetUnitCurrentOrder(this.source) == 851971 /*
                */ then
                    loop
                        exitwhen counter < 1
                        set this = LoadInteger(hash, id, counter)
                        call PauseTimer(this.tmr_volley) // blindly stop all volleys
                        set counter = counter - 1
                    endloop
                    call SaveInteger(hash, id, COUNTER_KEY, 0) // reset counter
                endif
            endif
        endmethod
    
    
    
        static method LaserFade_Timer takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
    
            local real source_x = GetUnitX(this.source)
            local real source_y = GetUnitY(this.source)
            local real target_x = GetUnitX(this.target)
            local real target_y = GetUnitY(this.target)
            local real angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
        
            local real launch_x = 0.
            local real launch_y = 0.
            local real launch_z = 0.
        
            local real impact_x = 0.
            local real impact_y = 0.
            local real impact_z = 0.
    
            local integer iLoop = 0
            local boolean set_is_ongoing = false
            local boolean b = false
        
            loop
                exitwhen iLoop >= this.laser_index
                if this.f_laser[iLoop] != null then
                    if this.sentry == null then
                        if not this.floating_launch then
                            set launch_x = source_x + Cos(angle) * this.launch_offset
                            set launch_y = source_y + Sin(angle) * this.launch_offset
                            set launch_z = GetUnitZ(this.source) + this.launch_height
                        else
                            set launch_x = this.f_launch_x[iLoop]
                            set launch_y = this.f_launch_y[iLoop]
                            set launch_z = this.f_launch_z[iLoop]
                        endif
                    else
                        if not this.floating_launch then
                            set launch_x = GetUnitX(this.sentry) + Cos(angle) * this.launch_offset
                            set launch_y = GetUnitY(this.sentry) + Sin(angle) * this.launch_offset
                            set launch_z = GetUnitZ(this.sentry) + this.launch_height
                        else
                            set launch_x = this.f_launch_x[iLoop]
                            set launch_y = this.f_launch_y[iLoop]
                            set launch_z = this.f_launch_z[iLoop]
                        endif
                    endif
                    set launch_x = launch_x + this.f_launch_scatter_x[iLoop]
                    set launch_y = launch_y + this.f_launch_scatter_y[iLoop]
                    set launch_z = launch_z + this.f_launch_scatter_z[iLoop]
                
                    if this.impact_lock then
                        set impact_x = target_x + Cos(this.f_lock_angle[iLoop]) * this.f_lock_distance[iLoop]
                        set impact_y = target_y + Sin(this.f_lock_angle[iLoop]) * this.f_lock_distance[iLoop]
                        set impact_z = GetUnitZ(this.target) + this.f_lock_height[iLoop]
                    else
                        set impact_x = this.f_impact_x[iLoop]
                        set impact_y = this.f_impact_y[iLoop]
                        set impact_z = this.f_impact_z[iLoop]
                    endif
                
                    if this.f_laser[iLoop] != null then
                        call MoveLightningEx(this.f_laser[iLoop], true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                    endif
                
                    if this.f_lingering_laser_interval[iLoop] > 0 then
                        set this.f_lingering_laser_interval[iLoop] = this.f_lingering_laser_interval[iLoop] - 1
                        if this.f_lingering_laser_interval[iLoop] <= 0 then
                            set this.f_lingering_laser_interval[iLoop] = this.lingering_laser_interval
                            call ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                        endif
                    endif
                
                    if this.f_fading[iLoop] then
                        set this.f_alpha[iLoop] = this.f_alpha[iLoop] - this.fade_amount
                        if this.f_laser[iLoop] != null then
                            call SetLightningColor(this.f_laser[iLoop], this.red, this.blue, this.green, this.f_alpha[iLoop])
                        endif
                        if this.f_alpha[iLoop] <= 0. then
                        
                            //EVENT
                            /*set BL_EVENT_Source = this.source
                            set BL_EVENT_Target = this.target
                            set BL_EVENT_Sentry = this.sentry
                            set BL_EVENT_Launch_x = launch_x
                            set BL_EVENT_Launch_y = launch_y
                            set BL_EVENT_Launch_z = launch_z
                            set BL_EVENT_Impact_x = impact_x
                            set BL_EVENT_Impact_y = impact_x
                            set BL_EVENT_Impact_z = impact_z
                            set BL_EVENT = 2.5
                            set BL_EVENT_Source = null
                            set BL_EVENT_Target = null
                            set BL_EVENT_Sentry = null
                            set BL_EVENT_Launch_x = 0.
                            set BL_EVENT_Launch_y = 0.
                            set BL_EVENT_Launch_z = 0.
                            set BL_EVENT_Impact_x = 0.
                            set BL_EVENT_Impact_y = 0.
                            set BL_EVENT_Impact_z = 0.
                            set BL_EVENT = 0.*/
                            //END EVENT
                        
                            set this.f_is_laser_dead[iLoop] = true
                            set this.f_fading[iLoop] = false
                            if this.f_laser[iLoop] != null then
                                call DestroyLightning(this.f_laser[iLoop])
                                set this.f_laser[iLoop] = null
                                //call BJDebugMsg("remove lightning [" + I2S(iLoop) + "]")
                            
                            // destroy if last lightning is destroyed
                                if iLoop == this.laser_num - 1 then
                                    call this.destroy()
                                endif
                            endif
                        
                        endif
                    else
                        set this.f_duration[iLoop] = this.f_duration[iLoop] - 1//0.0312500
                        if this.f_duration[iLoop] <= 0 then
                            set this.f_fading[iLoop] = true
                        endif
                    endif//if this.f_fading[iLoop] then
                
                endif//if not this.f_is_laser_dead[iLoop] then
                set iLoop = iLoop + 1
            endloop
        endmethod
    
    
    
        static method BurstLaser_Volley takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
        
            local integer id = GetHandleId(this.source)
            local integer counter  = LoadInteger(hash, id, COUNTER_KEY)
        
            local real source_x = GetUnitX(this.source)
            local real source_y = GetUnitY(this.source)
            local real target_x = GetUnitX(this.target)
            local real target_y = GetUnitY(this.target)
            local real angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
        
            local real launch_x = 0.
            local real launch_y = 0.
            local real launch_z = 0.
        
            local real launch_scatter_x = 0.
            local real launch_scatter_y = 0.
            local real launch_scatter_z = 0.
        
            local real impact_x = 0.
            local real impact_y = 0.
            local real impact_z = 0.
        
            local unit u = null
        
            if this.sentry == null then
                if not this.floating_launch then
                    set launch_x = source_x + Cos(angle + this.launch_angle) * this.launch_offset
                    set launch_y = source_y + Sin(angle + this.launch_angle) * this.launch_offset
                    set launch_z = GetUnitZ(this.source) + this.launch_height
                else
                    set launch_x = this.launch_x
                    set launch_y = this.launch_y
                    set launch_z = this.launch_z
                endif
            else
                if not this.floating_launch then
                    set launch_x = GetUnitX(this.sentry) + Cos(angle + this.launch_angle) * this.launch_offset
                    set launch_y = GetUnitY(this.sentry) + Sin(angle + this.launch_angle) * this.launch_offset
                    set launch_z = GetUnitZ(this.sentry) + this.launch_height
                else
                    set launch_x = this.launch_x
                    set launch_y = this.launch_y
                    set launch_z = this.launch_z
                endif
            endif
            set launch_scatter_x = GetRandomReal(this.launch_scatter_x*-1, this.launch_scatter_x)
            set launch_scatter_y = GetRandomReal(this.launch_scatter_y*-1, this.launch_scatter_y)
            set launch_scatter_z = GetRandomReal(this.launch_scatter_z*-1, this.launch_scatter_z)
            set launch_x = launch_x + launch_scatter_x
            set launch_y = launch_y + launch_scatter_y
            set launch_z = launch_z + launch_scatter_z
        
            if this.range_lock then //if true, start from the source's point
                if this.follow_target then
                    if this.range_per_laser != 0. then
                        set angle = Atan2(target_y - this.initial_y, target_x - this.initial_x)
                        set this.impact_x = this.impact_x + Cos(angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set this.impact_z = GetUnitZ(this.target)
                        set impact_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                    endif
                else
                    if this.range_per_laser != 0. then
                        set this.impact_x = this.impact_x + Cos(this.laser_angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(this.laser_angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                        set this.impact_z = GetUnitZ(this.target)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set impact_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                    endif
                endif
            else
                if this.follow_target then
                    if this.range_per_laser != 0. then
                        set angle = Atan2(target_y - this.initial_y, target_x - this.initial_x)
                        set this.impact_x = this.impact_x + Cos(angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set this.impact_z = GetUnitZ(this.target)
                        set impact_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                    endif
                else
                    if this.range_per_laser != 0. then
                        set this.impact_x = this.impact_x + Cos(this.laser_angle) * this.range_per_laser
                        set this.impact_y = this.impact_y + Sin(this.laser_angle) * this.range_per_laser
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                    else
                        set angle = angle + 3.14159
                        set this.impact_x = target_x + Cos(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set this.impact_y = target_y + Sin(angle) * (this.target_coll * COLLISION_PERCENTAGE)
                        set impact_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                        set impact_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                        set this.impact_z = GetUnitZ(this.target)
                    endif
                    if this.grounded then
                        set this.impact_z = 0.
                        set impact_z = 0.
                    else
                        set impact_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                    endif
                endif
            endif
        
            call ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
        
            //Laser
            if this.laser_string != null then
            
                set this.f_launch_x[this.laser_index] = launch_x
                set this.f_launch_y[this.laser_index] = launch_y
                set this.f_launch_z[this.laser_index] = launch_z
            
                if not this.floating_launch then
                    set this.f_launch_scatter_x[this.laser_index] = launch_scatter_x
                    set this.f_launch_scatter_y[this.laser_index] = launch_scatter_y
                    set this.f_launch_scatter_z[this.laser_index] = launch_scatter_z
                endif
            
                set this.f_impact_x[this.laser_index] = impact_x
                set this.f_impact_y[this.laser_index] = impact_y
                set this.f_impact_z[this.laser_index] = impact_z
            
                if this.impact_lock then
                    set this.f_lock_angle[this.laser_index] = Atan2(impact_y - target_y, impact_x - target_x)
                    set this.f_lock_distance[this.laser_index] = SquareRoot( (impact_x-target_x)*(impact_x-target_x) + (impact_y-target_y)*(impact_y-target_y) )
                    set this.f_lock_height[this.laser_index] = impact_z - GetUnitZ(this.target)
                endif
            
                set this.f_alpha[this.laser_index] = this.alpha
                set this.f_fading[this.laser_index] = false
            
                set this.fade_amount = this.alpha / this.fade_time
                set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                set this.f_lingering_laser_interval[this.laser_index] = this.lingering_laser_interval
                set this.f_duration[this.laser_index] = this.duration
                call SetLightningColor(this.f_laser[this.laser_index], this.red, this.green, this.blue, this.f_alpha[this.laser_index])
                //call BJDebugMsg("new lightning [" + I2S(this.laser_index) + "]")
                set this.laser_index = this.laser_index + 1
            
            endif
        
            //EVENT
            /*set BL_EVENT_Source = LaserSource[tID]
            set BL_EVENT_Target = LaserTarget[tID]
            set BL_EVENT_Sentry = LaserSentry[tID]
            set BL_EVENT_Launch_x = LAUNCH_X
            set BL_EVENT_Launch_y = LAUNCH_Y
            set BL_EVENT_Launch_z = LAUNCH_Z
            set BL_EVENT_Impact_x = IMPACT_X
            set BL_EVENT_Impact_y = IMPACT_Y
            set BL_EVENT_Impact_z = IMPACT_Z
            set BL_EVENT = 2.
            set BL_EVENT_Source = null
            set BL_EVENT_Target = null
            set BL_EVENT_Sentry = null
            set BL_EVENT_Launch_x = 0.
            set BL_EVENT_Launch_y = 0.
            set BL_EVENT_Launch_z = 0.
            set BL_EVENT_Impact_x = 0.
            set BL_EVENT_Impact_y = 0.
            set BL_EVENT_Impact_z = 0.
            set BL_EVENT = 0.*/
            //END EVENT
        
            if this.laser_index == this.laser_num or not UnitAlive(this.source) then
        
                //EVENT
                /*set BL_EVENT_Source = LaserSource[tID]
                set BL_EVENT_Target = LaserTarget[tID]
                set BL_EVENT_Sentry = LaserSentry[tID]
                set BL_EVENT_Launch_x = LAUNCH_X
                set BL_EVENT_Launch_y = LAUNCH_Y
                set BL_EVENT_Launch_z = LAUNCH_Z
                set BL_EVENT_Impact_x = IMPACT_X
                set BL_EVENT_Impact_y = IMPACT_Y
                set BL_EVENT_Impact_z = IMPACT_Z
                set BL_EVENT = 3.
                set BL_EVENT_Source = null
                set BL_EVENT_Target = null
                set BL_EVENT_Sentry = null
                set BL_EVENT_Launch_x = 0.
                set BL_EVENT_Launch_y = 0.
                set BL_EVENT_Launch_z = 0.
                set BL_EVENT_Impact_x = 0.
                set BL_EVENT_Impact_y = 0.
                set BL_EVENT_Impact_z = 0.
                set BL_EVENT = 0.*/
                //END EVENT
            
                //call BJDebugMsg("interrupt / end")
                call PauseTimer(this.tmr_volley)
                call SaveInteger(hash, id, COUNTER_KEY, LoadInteger(hash, id, counter) - 1) // decrease counter
                return
            
                set this.laser_num = this.laser_index //This is set in case of interruption.
        
            endif
        endmethod
    
    
    
        static method create takes unit unit_source, unit unit_target, unit unit_sentry, integer id, real damage returns thistype
    
            local thistype this = allocate()
        
            local integer handle_id = GetHandleId(unit_source)
            local integer counter = LoadInteger(hash, handle_id, COUNTER_KEY) // get instance counter
        
            local real source_x = GetUnitX(unit_source)
            local real source_y = GetUnitY(unit_source)
            local real target_x = GetUnitX(unit_target)
            local real target_y = GetUnitY(unit_target)
            local real sentry_x = 0.
            local real sentry_y = 0.
            local real startangle = Atan2(target_y - source_y, target_x - source_x)
        
            local real range_offset = 0.
            local real impact_spread_x = 0.
            local real impact_spread_y = 0.
            local real impact_spread_z = 0.
        
            local integer iLoop = 0
            local unit u = null
        
            //call BJDebugMsg("=========")
            //call BJDebugMsg("allocate")
        
            set counter = counter + 1
            call SaveInteger(hash, handle_id, COUNTER_KEY, counter) // increase instance counter
            call SaveInteger(hash, handle_id, counter, this) // bind instance to unit id
            set this.hashIndex = counter // so the instance knows where it's stored
        
            set this.owner = GetOwningPlayer(unit_source)
            set this.source = unit_source
            set this.target = unit_target
            set this.sentry = unit_sentry
        
            set this.target_coll = GetUnitCollision(unit_target)
        
            set this.laser_string = BL_LaserString[id]
            set this.red = BL_RED[id]
            set this.blue = BL_BLUE[id]
            set this.green = BL_GREEN[id]
            set this.alpha = BL_ALPHA[id]
        
            set this.launch_string = BL_LaserLaunchFX[id]
            set this.impact_string = BL_LaserImpactFX[id]
            set this.area_string = BL_LaserAreaFX[id]
        
            set this.duration = BL_LaserDuration[id]
            set this.fade_time = BL_LaserFadeTime[id]
            set this.interval = BL_LaserInterval[id]
            set this.lingering_laser_interval = BL_LaserLingerInterval[id]
            set this.aoe = BL_LaserAOE[id]
            set this.grounded = BL_IsLaserGrounded[id]
        
            set this.launch_offset = BL_LaserLaunchOffset[id]
            set this.launch_height = BL_LaserLaunchHeight[id]
            set this.launch_angle = BL_LaserLaunchAngle[id]
        
            set this.launch_scatter_x = BL_LaserLaunchScatterX[id]
            set this.launch_scatter_y = BL_LaserLaunchScatterY[id]
            set this.launch_scatter_z = BL_LaserLaunchScatterZ[id]
        
            set this.impact_scatter_x = BL_LaserImpactScatterX[id]
            set this.impact_scatter_y = BL_LaserImpactScatterY[id]
            set this.impact_scatter_z = BL_LaserImpactScatterZ[id]
        
            set this.follow_target = BL_FollowTarget[id]
            set this.floating_launch = BL_FloatingLaunch[id]
            set this.impact_lock = BL_LaserImpactLock[id]
            set this.range_lock = BL_LaserRangeLock[id]
        
            set this.range_start = BL_LaserRangeStart[id]
            set this.range_per_laser = BL_LaserRangePerLaser[id]
            set this.attack_type = BL_LaserAttackType[id]
            set this.damage_type = BL_LaserDamageType[id]
        
            set this.uninterruptible = BL_Uninterruptible[id]
            set this.hit_all = BL_LaserHitAll[id]
            set this.friendly_fire = BL_LaserFriendlyFire[id]
        
            //there's a hard limit to how many lasers you may have per set. This is because you have to declare the size of arrays of variables in a struct
            //if you want to have more that 20 lasers per set, feel free to change the value of MAX_LASERS at the top.
            if BL_NumberOfLasers[id] > MAX_LASERS then
                set this.laser_num = MAX_LASERS
            else
                set this.laser_num = BL_NumberOfLasers[id]
            endif
        
            set this.laser_index = 0
        
            if this.sentry == null then
                if this.launch_offset > 0. then
                    set this.launch_x = source_x + Cos(startangle + this.launch_angle) * this.launch_offset
                    set this.launch_y = source_y + Sin(startangle + this.launch_angle) * this.launch_offset
                else
                    set this.launch_x = source_x
                    set this.launch_y = source_y
                endif
                set this.launch_z = GetUnitZ(this.source) + this.launch_height
            else
                set sentry_x = GetUnitX(this.sentry)
                set sentry_y = GetUnitY(this.sentry)
                if this.launch_offset > 0. then
                    set this.launch_x = sentry_x + Cos(startangle + this.launch_angle) * this.launch_offset
                    set this.launch_y = sentry_y + Sin(startangle + this.launch_angle) * this.launch_offset
                else
                    set this.launch_x = sentry_x
                    set this.launch_y = sentry_y
                endif
                set this.launch_z = GetUnitZ(this.sentry) + this.launch_height
            endif
            set launch_scatter_x = GetRandomReal(this.launch_scatter_x*-1, this.launch_scatter_x)
            set launch_scatter_y = GetRandomReal(this.launch_scatter_y*-1, this.launch_scatter_y)
            set launch_scatter_z = GetRandomReal(this.launch_scatter_z*-1, this.launch_scatter_z)
            
            set range_offset = ((this.laser_num-1) * this.range_per_laser + this.range_start) * .5
            if this.range_lock then //if true, start from the source's point
                set this.impact_x = source_x + Cos(startangle) * (range_offset + this.range_start)
                set this.impact_y = source_y + Sin(startangle) * (range_offset + this.range_start)
                set startangle = startangle + BL_LaserDirectionalTilt[id]
                set this.impact_x = this.impact_x + Cos(startangle) * (range_offset)
                set this.impact_y = this.impact_y + Sin(startangle) * (range_offset)
                set impact_spread_x = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                set impact_spread_y = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                if this.grounded then
                    set this.impact_z = 0.
                    set impact_spread_z = 0.
                else
                    set this.impact_z = GetUnitZ(this.target)
                    set impact_spread_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                endif
            else
                set startangle = startangle + BL_LaserDirectionalTilt[id] + 3.14159
                if this.range_per_laser != 0. then
                    set this.impact_x = target_x + Cos(startangle) * (range_offset + this.range_start)
                    set this.impact_y = target_y + Sin(startangle) * (range_offset + this.range_start)
                else
                    set this.impact_x = target_x + Cos(startangle) * (this.target_coll * COLLISION_PERCENTAGE)
                    set this.impact_y = target_y + Sin(startangle) * (this.target_coll * COLLISION_PERCENTAGE)
                endif
                set impact_spread_x = this.impact_x + GetRandomReal(this.impact_scatter_x * -1, this.impact_scatter_x)
                set impact_spread_y = this.impact_y + GetRandomReal(this.impact_scatter_y * -1, this.impact_scatter_y)
                if this.grounded then
                    set this.impact_z = 0.
                    set impact_spread_z = 0.
                else
                    set this.impact_z = GetUnitZ(this.target)
                    set impact_spread_z = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                endif
            endif
        
            if BL_LaserLingerInterval[id] > 0 then
                set this.damage = damage / ((BL_LaserDuration[id] + BL_LaserFadeTime[id]) / BL_LaserLingerInterval[id])
            else
                set this.damage = damage
            endif
            call ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_spread_x, impact_spread_y, impact_spread_z)
        
            //Laser
            if this.laser_string != null then
            
                set this.f_launch_x[this.laser_index] = this.launch_x + launch_scatter_x
                set this.f_launch_y[this.laser_index] = this.launch_y + launch_scatter_y
                set this.f_launch_z[this.laser_index] = this.launch_z + launch_scatter_z
            
                if not this.floating_launch then
                    set this.f_launch_scatter_x[this.laser_index] = launch_scatter_x
                    set this.f_launch_scatter_y[this.laser_index] = launch_scatter_y
                    set this.f_launch_scatter_z[this.laser_index] = launch_scatter_z
                endif
            
                set this.f_impact_x[this.laser_index] = impact_spread_x
                set this.f_impact_y[this.laser_index] = impact_spread_y
                set this.f_impact_z[this.laser_index] = impact_spread_z
            
                if this.impact_lock then
                    set this.f_lock_angle[this.laser_index] = Atan2(impact_spread_y - target_y, impact_spread_x - target_x)
                    set this.f_lock_distance[this.laser_index] = SquareRoot( (impact_spread_x-target_x)*(impact_spread_x-target_x) + (impact_spread_y-target_y)*(impact_spread_y-target_y) )
                    set this.f_lock_height[this.laser_index] = impact_spread_z - GetUnitZ(this.target)
                endif
            
                set this.f_alpha[this.laser_index] = this.alpha
                set this.f_fading[this.laser_index] = false
            
                set this.fade_amount = this.alpha / this.fade_time
                set this.f_laser[this.laser_index] = AddLightningEx(this.laser_string, true, this.launch_x, this.launch_y, this.launch_z, impact_spread_x, impact_spread_y, impact_spread_z)
                set this.f_lingering_laser_interval[this.laser_index] = this.lingering_laser_interval
                set this.f_duration[this.laser_index] = this.duration
                call SetLightningColor(this.f_laser[this.laser_index], this.red, this.green, this.blue, this.f_alpha[this.laser_index])
                //call BJDebugMsg("new lightning [" + I2S(this.laser_index) + "]")
                set this.laser_index = this.laser_index + 1
            
            endif
        
            if not this.uninterruptible and this.interval > 0. then
                call TriggerRegisterUnitEvent(interrupt_trigger, this.source, EVENT_UNIT_ISSUED_TARGET_ORDER)
                call TriggerRegisterUnitEvent(interrupt_trigger, this.source, EVENT_UNIT_ISSUED_POINT_ORDER)
                call TriggerRegisterUnitEvent(interrupt_trigger, this.source, EVENT_UNIT_ISSUED_ORDER)
                call TriggerAddAction(interrupt_trigger, interruption_event_handler)
            endif
        
            set this.laser_angle = startangle + 3.14159
            set this.initial_x = this.impact_x
            set this.initial_y = this.impact_y
            set this.count = this.laser_num - 1
            set this.tmr_volley = NewTimerEx(this)
            call TimerStart(this.tmr_volley, this.interval, true, timer_volley_handler)
        
            set this.tmr_laserfade = NewTimerEx(this)
            call TimerStart(this.tmr_laserfade, .03125, true, timer_laserfade_handler)
    
            return this
        endmethod
    
    
        static method onInit takes nothing returns nothing
            set timer_volley_handler = function thistype.BurstLaser_Volley
            set timer_laserfade_handler = function thistype.LaserFade_Timer
            set interruption_event_handler = function thistype.Interruption_Event
        endmethod
    endstruct
 
endlibrary
 

Attachments

  • Burst Laser v2.0.w3x
    176 KB · Views: 40
Last edited:
function destroy
what's .u ? is it this.source in my code?
it is the attacker to which we binded the instance.

function volley
what is the value of counter supposed to be? Same as with interrupt and create? (local integer counter = LoadInteger(hash, id, COUNTER_KEY))
The "id" is handleid of the attacker of "this" btw. I forgot to say.
In volley, when one instance comes to it's end, you don't set the counter to a specific value, bust just decreae it by 1. I made this in 1 step. You also could do:

integer counter = LoadInteger(hash, id, COUNTER_KEY))
set counter = counter - 1
call SaveInteger(hash, id, COUNTER_KEY, counter)

PS: what's the purpose of the return right after you pause the timer in volley?
oops, I actually wanted only to ensure not run any trigger things, so I just did the required thing (stopping timer) and then returned.
but the other action to apply new "_num" is fine and needed of course, when the interruptin trigger exists.

Oh, man, hashtable confuse the hell out of me.
Yeh with linking each other stuff it an get a bit ugly to read when you're not very used to it.:(

Can't maybe today anymore, but tomorrow I have time to test map.
 
JASS:
if not this.uninterruptible and this.interval > 0. then
    call TriggerRegisterUnitEvent(interrupt_trigger, this.source, EVENT_UNIT_ISSUED_TARGET_ORDER)
    call TriggerRegisterUnitEvent(interrupt_trigger, this.source, EVENT_UNIT_ISSUED_POINT_ORDER)
    call TriggerRegisterUnitEvent(interrupt_trigger, this.source, EVENT_UNIT_ISSUED_ORDER)
    all TriggerAddAction(interrupt_trigger, interruption_event_handler)
 endif

^Idea is good, but that result will become not very handy.
Each time it will create 3 new events for each new attack for a unit.
This means, a unit attacks 20x -> 60 events only for this one unit. And the trigger will also run multiple times for the same unit.
That might be an issuee with a lot of attacks.

Just have the trigger globaly registered for all units and make maybe a condition to check triggering unit.
You could load for example the counter from hashtable for the unit, and check if it's bigger than "0".

you can remove the return, then inside volley fuction

Inside interruption action you need to add an action next to just stopping the volley timer (I did not think of it before)
set this.laser_num = this.laser_index //This is set in case of interruption.

^so when you stop the volley, we also just set the new amount.

The attacks looks really cool with the lightnings btw.^^ Can be great feature for futuristic fights probably!
 
Last edited:
Okay, lil' problem. What if some instances from the same unit are interruptible and some are not? (Eg, its attacks are, but its spells are not) wouldn't that cause an issue?

I did this for the events:

JASS:
        static method onInit takes nothing returns nothing
            local integer iLoop = 0
            set timer_volley_handler = function thistype.BurstLaser_Volley
            set timer_laserfade_handler = function thistype.LaserFade_Timer
            set interruption_event_handler = function thistype.Interruption_Event
            loop
                call TriggerRegisterPlayerUnitEvent(interrupt_trigger, Player(iLoop), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
                call TriggerRegisterPlayerUnitEvent(interrupt_trigger, Player(iLoop), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
                call TriggerRegisterPlayerUnitEvent(interrupt_trigger, Player(iLoop), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
                set iLoop = iLoop + 1
                exitwhen iLoop == bj_MAX_PLAYER_SLOTS
            endloop
            call TriggerAddAction(interrupt_trigger, interruption_event_handler)
        endmethod

and that for the interruption:

JASS:
        static method Interruption_Event takes nothing returns nothing
            local integer id = GetHandleId(GetTriggerUnit())
            local integer counter = LoadInteger(hash, id, COUNTER_KEY)
            local thistype this
            loop
                exitwhen counter < 1
                set this = LoadInteger(hash, id, counter)
                if not this.uninterruptible and this.interval > 0. then
                    if GetOrderTargetUnit() == null then
                        if /*
                        [Move]      */ GetUnitCurrentOrder(this.source) == 851986 or /*
                        [Stunned]   */ GetUnitCurrentOrder(this.source) == 851973 or /*
                        [Stop]      */ GetUnitCurrentOrder(this.source) == 851972 or /*
                        [Patrol]    */ GetUnitCurrentOrder(this.source) == 851990 or /*
                        [Hold Poz]  */ GetUnitCurrentOrder(this.source) == 851993 or /*
                        [Attack]    */ GetUnitCurrentOrder(this.source) == 851983 or /*
                        [Smart]     */ GetUnitCurrentOrder(this.source) == 851971 /*
                        */ then
                            call PauseTimer(this.tmr_volley)
                            call SaveInteger(hash, id, COUNTER_KEY, counter) // reset counter
                            set this.laser_num = this.laser_index //This is set in case of interruption
                            call BJDebugMsg("Interrupt")
                        endif
                    else
                        if /*
                        [Move]      */ GetUnitCurrentOrder(this.source) == 851986 or /*
                        [Patrol]    */ GetUnitCurrentOrder(this.source) == 851990 or /*
                        [Attack]    */ GetUnitCurrentOrder(this.source) == 851983 or /*
                        [Smart]     */ GetUnitCurrentOrder(this.source) == 851971 /*
                        */ then
                            call PauseTimer(this.tmr_volley)
                            call SaveInteger(hash, id, COUNTER_KEY, counter) // reset counter
                            set this.laser_num = this.laser_index //This is set in case of interruption
                            call BJDebugMsg("Interrupt")
                        endif
                    endif
                endif
                set counter = counter - 1
            endloop
        endmethod

It seems to work, but I'm not sure if I'm actually doing that right. Y'know, hashtables an all :p
 
Yep, the check seems right.

Btw, you wanna interrupt:

if ( target == null AND order == smart/attack/move/patrol/stop/stunned/holdposition) or ( target != null AND order == attack/smart/move/patrol)

what if target != null, order is "smart/move" but target is also an ally?, for example.

and any casts should also not interrupt the order?

----
but so all works without extreme FPS drop or so?
 
Last edited:
I changed the interruption trigger since. Any order will now interrupt a volley:
JASS:
        static method Interruption_Event takes nothing returns nothing
            local integer id = GetHandleId(GetTriggerUnit())
            local integer counter = LoadInteger(hash, id, COUNTER_KEY)
            local thistype this
            loop
                exitwhen counter < 1
                set this = LoadInteger(hash, id, counter)
                if not this.uninterruptible and this.interval > 0. then
                    call PauseTimer(this.tmr_volley)
                    call SaveInteger(hash, id, COUNTER_KEY, counter) // reset counter
                    set this.laser_num = this.laser_index //This is set in case of interruption
                endif
                set counter = counter - 1
            endloop
        endmethod
I also updated the system in the spell section: Burst Laser v2.0

The lag appears to be gone, but when I try to use the system to for spells it seems to drop frames eventually, but that might be the way the spell it setup itself. I've disabled that spell in the map and used another (which doesn't lag), so yeah. I'm pretty sure it doesn't lag ;P
 
Last edited:
Status
Not open for further replies.
Top