1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  5. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  6. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  7. The results are out! Check them out.
    Dismiss Notice
  8. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  9. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  10. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Burst Laser v3.1

Submitted by Spellbound
This bundle is marked as approved. It works and satisfies the submission rules.
[​IMG]


Version History
v3.1 dummies no longer stored in unit groups but in a hashtable.
v3.0 Major rework. System was completely broken and was leaking pretty badly. Rewrote half the library and now does not use ZLibrary anymore. Limit on number of lasers removed. Burst Lasers are now assigned as weapon. Setup takes a bit longer, but now every unit can have multiple laser weapons that can be modified independently. Some variable names altered.
v2.0 - rewrote entire system to use structs instead. Arrays were bugging out.
v1.1 - Fixed a small issue with the angle of the launch coordinates bugging out on subsequent lasers in a set when the target was moving.
v1.0 - Release


Code (vJASS):


    Burst Laser v3.1 by Spellbound
        Special thanks to
        Aniki for introducing me to structs,
        ADG for helping, big time, to understant said structs,
        and IcemanBo for being very patient with debugging this system like a pro.
     
    Burst Laser is an attempt to expand the concept of lightning attacks, giving you the ability to customise it to have multiple volleys of 'lasers' with a launch and impact effect.
    While this system was initially meant to be exclusively a custom attack system, I was given the idea to make it more open to other possibilities like spells, and as such I rewrote
    the whole thing to run with BurstLaser.create(), which can be used for spells. See USAGE - ADVANCED below and check Arcane Lasers for more information.
 
    http://www.hiveworkshop.com/threads/burst-laser-v1-1.290007/
 
        REQUIREMENTS
 
    Okay, so this I think is the biggest problem with the system because it has a lot of requirements. The setup can be slightly daunting, but most of the fields are optional. Just
    read the descriptions and it should all be smooth sailing.
 
    For starters, this is vJASS, so WEX is your first requirement.
    https://www.hiveworkshop.com/threads/sharpcraft-world-editor-extended-bundle.292127/
 
    - ArmorUtils -------------- by Rising_Dusk      http://www.wc3c.net/showthread.php?t=105849
        - Logarithm ----------- by Vexorian         http://www.wc3c.net/showthread.php?t=101346
    - TimerUtils -------------- by Vexorian         http://www.wc3c.net/showthread.php?t=101322
    - GetUnitCollision -------- by Nestharus        http://www.thehelper.net/threads/getunitcollision.153722/
        - Table --------------- by Bribe            http://www.hiveworkshop.com/threads/snippet-new-table.188084/
    - DummyRecylcer ----------- by Flux             http://www.hiveworkshop.com/threads/dummy-recycler-v1-25.277659/
        - [opt] WorldBounds --- by Nestharus        https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
    - Damage Engine ----------- by Bribe            http://www.hiveworkshop.com/threads/gui-damage-engine-v3-6-0-1.201016/
 
    Pick ONE of the follwing two. The both do the same thing, but Unit Event has some neat added functionality that you can use, like detecting unit transformation, etc.
    They are required for Damage Engine to work.
    - GUI Unit Indexer -------- by Bribe            http://www.hiveworkshop.com/threads/gui-unit-indexer-1-2-0-2.197329/
    - GUI Unit Event ---------- by Bribe            http://www.hiveworkshop.com/threads/gui-unit-event-v2-2-1-0.201641/#resource-45995
 
 
        INSTALLATION
    * Make sure your editor can automatically generate variables. File > Preferences, under General, tick 'Automatically create unknown variables while pasting trigger data'.
    * Go to the Object Editor and under Abilities, copy the Cheat Death, Detect Spell, and GetUnitArmorLifeBonus spells. If you're using Unit Event instead of Unit Indexer,
      copy the other two abilities as well. Make your life easy and set their Ids to DEa1, DEa2, lif&, UErd (Removal Detect) and UEtd (Transform Detect) respectively.
    * Copy over the Requirements folder.
    * Copy over the Burst Laser folder.
 
 
        USAGE - BASIC
    BurstLaser works by assigning weapons to units. For starters, in the Object editor, the missile art of the unit must be none and the projectile speed set to the maximum. Then,
    a BurstLaser weapon must be assigned to it. This can be done on indexing, like I have in this demo map, or in any other circumstance. For example, you are making an RPG and you
    wish for your hero to obtain a new weapon. He/She/It picks up a gun, and now they can attack using BurstLaser!
 
    Before you assign a weapon, however, you must set it up for. In the BurstLaser Setup Arsenal trigger, you create weapons that have a specific number attachmed to the. This way,
    when you assign a weapon, you need only referrence that number and, voila, those parameters are copied over to your unit.
 
    The last thing to know about assigning weapons is that your unit can have multiple BurstLaser weapons. This way, you can assign multiple ones and the moment it needs to be fired,
    you need only point to the weapon slot and that specific weapon will be used. Chech USUAGE - ADVANCED to see how it is done.
 
    call BLAssignWeapon.create(the unit, the weapon slot, the arsenal number)
 
    If you desire to have your lasers launch from something other than your attacker, you can make use of the BL_LaserSentry[Index] variable. If that value is not null, the unit
    stored in that variable will act as the source of the lasers. All usual modifiers like height offset, distance offset, angle offset, launch scatter, etc apply normally. See
    the Spellbreaker for an example. SentryCreate onIndex creates the sentry and Sentry Orbit makes it go around the Spellbreaker.
 
 
        USAGE - ADVANCED
    The trigger BL Module onAttack uses Bribe'
s Damage Engine' to detect when a unit has inflicted damage and then fires a BurstLaser weapon. This is done by calling:
 
    call BurstLaser.create(unit Source, unit Target, unit Sentry [optional], integer Instance Index, integer Weapon Number, real The Damage Inflicted)
 
 
        KNOWN BUGS
    • call BurstLaser.create() on spell effect event eventually stops working. The more lasers fired per set, the fewer casts are allowed. When it ceases to work, all BurstLaser
    weapons on that one unit stops working. I haven'
t the faintest idea why something so bizarre would happen at all.
 
 
        EVENTS
    You also have access to events through "Game - BL_EVENT becomes Equal to 1.00" or "call TriggerRegisterVariableEvent( YourTrigger, "udg_BL_EVENT", EQUAL, 1.00 )"
    These variables are meant to be readonly, so DO NOT SET THEM TO ANYTHING.
 
    When udg_BL_EVENT = 1.00 -------- A Laser set starts. This triggers on the very first Laser in a set.
    When udg_BL_EVENT = 2.00 -------- Every time a laser in a set fires.
    When udg_BL_EVENT = 3.00 -------- When the last Laser fires.
    When udg_BL_EVENT = 4.00 -------- When the Laser set instance ends.
 
        The values you can use following an EVENT are:
     
        udg_BL_EVENT_Source
        udg_BL_EVENT_Target
        udg_BL_EVENT_Sentry
     
        udg_BL_EVENT_Launch_x
        udg_BL_EVENT_Launch_y
        udg_BL_EVENT_Launch_z
     
        udg_BL_EVENT_Impact_x
        udg_BL_EVENT_Impact_y
        udg_BL_EVENT_Impact_z
     
        udg_BL_EVENT_DummyLaunch    - this give you access to the launch dummy, should you want to add effects to it and such.
        udg_BL_EVENT_DummyImpact    - same as above
        udg_BL_EVENT_DummyArea      - this one is ONLY available if your impact and area scales are different. Otherwise simply refer to udg_BL_EVENT_DummyImpact
                                      since they share the exact same co-ordinates.
     
        udg_BL_EVENT_Damage - this is affected by LaserDivideDamage[ID]. If true, udg_BL_EVENT_Damage == your base damage / number of lasers in a set.

        I assume the use of those variables are self-explanatory. Setting them to anything does nothing.
 
 
        LIGHTNING EFFECTS
    The lightning effect I use in this demo map are of my own making. You can download there here: https://www.hiveworkshop.com/threads/lightning-effect-pack.290740/


  • Code (vJASS):
    library BurstLaser requires BL, GetUnitCollision, TimerUtils, ArmorUtils, DummyRecycler

        globals
            private hashtable hash = InitHashtable()
            private hashtable lightningStorage = InitHashtable()
            private hashtable dummyStorage = InitHashtable()
            private trigger interrupt_trigger = CreateTrigger()
            private group DamageGroup = CreateGroup()
            private location zLoc = Location(0., 0.)
           
            private constant real MAX_COLLISION_SIZE = 196.
            private constant real COLLISION_PERCENTAGE = .8
            private constant real TIMEOUT = .03125
            private constant real DEATH_TIME = 1.
            private constant integer COUNTER_KEY = -1
           
            //LaserInstance-specific vars
            private integer MaxLasers = 0
            private integer array LaserIndex
            private lightning array Laser
            private timer LaserTimer = CreateTimer()
        endglobals
       
        struct LaserInstance
       
            real xLaunch
            real yLaunch
            real zLaunch
            real xImpact
            real yImpact
            real zImpact
            real xLaunchUpdate
            real yLaunchUpdate
            real zLaunchUpdate
            real xImpactUpdate
            real yImpactUpdate
            real zImpactUpdate
            real lifespan
            real red
            real blue
            real green
            real alpha
            real lockAngle
            real dot
            real dotReset
            real fadeAmount
            unit source
            unit target
            unit sentry
            boolean updateLaunchPoint
            boolean updateImpactPoint
            integer laserSetInstance
            integer laserSlot
           
           
            private method destroy takes nothing returns nothing
                set this.source = null
                set this.target = null
                set this.sentry = null
                set this.laserSetInstance = 0
                call this.deallocate()
            endmethod
           
           
            private static method Timer takes nothing returns nothing
                local integer i = 0
                local integer storageIndex
                local integer iRec
                local thistype this
                local thistype LaserSet
                local BurstLaser BLSet
               
                local real launch_x
                local real launch_y
                local real launch_z
               
                local real impact_x
                local real impact_y
                local real impact_z
               
                local unit mark
                local boolean updateLaser = false
                local boolean destroySet
               
                loop
               
                    set i = i + 1
                    exitwhen i > MaxLasers
                    set this = LaserIndex[i]
                    set BLSet = this.laserSetInstance
                   
                    if not BLSet.destructionPhase then
                   
                        if this.updateLaunchPoint then
                            if this.sentry == null then
                                set mark = this.source
                            else
                                set mark = this.sentry
                            endif
                            set launch_x = GetUnitX(mark) - this.xLaunchUpdate
                            set launch_y = GetUnitY(mark) - this.yLaunchUpdate
                            call MoveLocation(zLoc, launch_x, launch_y)
                            set launch_z = (GetUnitFlyHeight(mark) + GetLocationZ(zLoc)) - this.zLaunchUpdate
                            set updateLaser = true
                        else
                            set launch_x = this.xLaunch
                            set launch_y = this.yLaunch
                            set launch_z = this.zLaunch
                        endif
                       
                        if this.updateImpactPoint then
                            set impact_x = GetUnitX(this.target) - this.xImpactUpdate
                            set impact_y = GetUnitY(this.target) - this.yImpactUpdate
                            call MoveLocation(zLoc, impact_x, impact_y)
                            set impact_z = (GetUnitFlyHeight(this.target) + GetLocationZ(zLoc)) - this.zImpactUpdate
                            set updateLaser = true
                        else
                            set impact_x = this.xImpact
                            set impact_y = this.yImpact
                            set impact_z = this.zImpact
                        endif
                       
                        if updateLaser then
                            call MoveLightningEx(Laser[i], true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                        endif
                       
                        if this.lifespan > 0. then
                            set this.lifespan = this.lifespan - TIMEOUT
                        else
                            set this.alpha = this.alpha - this.fadeAmount
                            if this.alpha <= 0. then
                                call DestroyLightning(Laser[i])
                                set Laser[i] = null
                                call this.destroy()
                                set iRec = i
                                loop
                                    set Laser[iRec] = Laser[iRec + 1]
                                    set LaserIndex[iRec] = LaserIndex[iRec + 1]
                                    set iRec = iRec + 1
                                    exitwhen iRec > MaxLasers
                                endloop
                                call RemoveSavedHandle(lightningStorage, BLSet, this.laserSlot)
                                set MaxLasers = MaxLasers - 1
                               
                                //Check if all lasers from the set have been fired and have been destroyed
                                if BLSet.laser_count == BLSet.laser_num then
                                    set destroySet = true
                                    set storageIndex = 0
                                    loop
                                        set storageIndex = storageIndex + 1
                                        exitwhen storageIndex > BLSet.laser_num
                                        if LoadLightningHandle(lightningStorage, BLSet, storageIndex) != null then
                                           set destroySet = false
                                        endif
                                    endloop
                                    if destroySet then
                                        set BLSet.destructionPhase = true
                                        if BLSet.interval <= 0. then
                                            set BLSet.interval = DEATH_TIME
                                        endif
                                    endif
                                endif
                            else
                                call SetLightningColor(Laser[i], this.red, this.green, this.blue, this.alpha)
                            endif
                        endif
                       
                        //Lingering Laser damage will only fire if greater than 0.
                        if this.dotReset > 0. then
                            set this.dot = this.dot - TIMEOUT
                            if this.dot <= 0. then
                                set this.dot = this.dotReset
                                call BurstLaser.ApplySfxAndDamage(launch_x, launch_y, launch_z, impact_x, impact_y, impact_z, BLSet)
                            endif
                        endif
                       
                    endif
                   
                endloop
               
                if MaxLasers < 1 then
                    call PauseTimer(LaserTimer)
                endif
            endmethod
           
           
            static method create takes real launch_x, real launch_y, real launch_z, real impact_x, real impact_y, real impact_z, BurstLaser BLSet returns thistype
           
                local real x
                local real y
                local unit mark
                local integer i
                local thistype this = allocate()
               
                set this.laserSetInstance = BLSet
               
                set this.source = BLSet.source
                set this.target = BLSet.target
                set this.sentry = BLSet.sentry
               
                set this.xLaunch = launch_x
                set this.yLaunch = launch_y
                set this.zLaunch = launch_z
               
                set this.xImpact = impact_x
                set this.yImpact = impact_y
                set this.zImpact = impact_z
               
                set this.updateLaunchPoint = BLSet.update_launch
                if this.updateLaunchPoint then
                    if this.sentry == null then
                        set mark = this.source
                    else
                        set mark = this.sentry
                    endif
                    set x = GetUnitX(this.sentry)
                    set y = GetUnitY(this.sentry)
                    set this.xLaunchUpdate = x - launch_x
                    set this.yLaunchUpdate = y - launch_y
                    call MoveLocation(zLoc, launch_x, launch_y)
                    set this.zLaunchUpdate = (GetUnitFlyHeight(mark) + GetLocationZ(zLoc)) - launch_z
                    set mark = null
                endif
               
                set this.updateImpactPoint = BLSet.update_impact
                if this.updateImpactPoint then
                    set x = GetUnitX(this.target)
                    set y = GetUnitY(this.target)
                    set this.xImpactUpdate = x - impact_x
                    set this.yImpactUpdate = y - impact_y
                    call MoveLocation(zLoc, impact_x, impact_y)
                    set this.zImpactUpdate = (GetUnitFlyHeight(this.target) + GetLocationZ(zLoc)) - impact_z
                endif
               
                set this.red            = BLSet.red
                set this.blue           = BLSet.blue
                set this.green          = BLSet.green
                set this.alpha          = BLSet.alpha
                set this.fadeAmount     = BLSet.alpha / BLSet.fade_time * TIMEOUT
                set this.dot            = BLSet.laser_dot
                set this.dotReset       = BLSet.laser_dot
                set this.lifespan       = BLSet.duration
               
                set MaxLasers = MaxLasers + 1
                if MaxLasers == 1 then
                    call TimerStart(LaserTimer, TIMEOUT, true, function thistype.Timer)
                endif
                set Laser[MaxLasers] = AddLightningEx(BLSet.laser_string, true, launch_x, launch_y, launch_z, impact_x, impact_y, impact_z)
                call SetLightningColor(Laser[MaxLasers], this.red, this.green, this.blue, this.alpha)
                set LaserIndex[MaxLasers] = this
               
                call SaveLightningHandle(lightningStorage, BLSet, BLSet.laser_count, Laser[MaxLasers])
                set this.laserSlot = BLSet.laser_count
               
                return this
            endmethod
           
           
        endstruct
       
       
       
        struct BurstLaser
            unit source
            unit target
            unit sentry
           
            unit dummy_launch
            unit dummy_impact
            unit dummy_area
           
            player owner
            integer id
           
            real damage
           
            real red
            real blue
            real green
            real alpha
            real fade_amount
            real fade_time
           
            real duration
            integer count
            real interval
           
            real lock_angle
            real lock_distance
            real lock_height
           
            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 launch_scale
            real impact_x
            real impact_y
            real impact_z
            real impact_scatter_x
            real impact_scatter_y
            real impact_scatter_z
            real impact_scale
            real area_scale
            real lock_on_angle
           
            real target_coll
           
            real laser_angle
            boolean update_source_loc   //If true, subsequent lasers in a volley will launch from the initial laser's launch point and not relative to the source.
            boolean update_target_loc   //If false, Lasers in a set will strike the same point instead of updating their impact location to follow a moving target.
            boolean launch_from_target  //If true, the launch point will be relative to the target's location, not the source.
            boolean update_launch       //if true then Lasers will update their launch location relative to the source's.
            boolean update_impact       //if true then Lasers will update their impact location relative to the target's.
            boolean fixed_impact        //If true lasers will impact a fixed distance from the source. range_start and range_per_laser will modify this value.
            real range_start            //Adds or subtracts a certain amount of distance from a laser's impact location.
            real range_per_laser        //Adds or substracts distance per Laser in a set.
            real laser_dot              //the laser's sfx and damage will repeat every laser_dot seconds 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
            integer dummy_count
           
            boolean reduce_launch_overhead
            boolean reduce_impact_overhead
           
            integer laser_num
            integer laser_count
           
            real deathTime
            boolean destructionPhase
            integer hashIndex
           
            static code timer_volley_handler
            static code interruption_event_handler
           
           
            method destroy takes nothing returns nothing
                local integer id = GetUnitUserData(this.source)
                local integer i = 0
                local unit dummy
               
                //EVENT
                set udg_BL_EVENT_Source = this.source
                set udg_BL_EVENT_Target = this.target
                set udg_BL_EVENT_Sentry = this.sentry
                set udg_BL_EVENT_Launch_x = launch_x
                set udg_BL_EVENT_Launch_y = launch_y
                set udg_BL_EVENT_Launch_z = launch_z
                set udg_BL_EVENT_Impact_x = impact_x
                set udg_BL_EVENT_Impact_y = impact_y
                set udg_BL_EVENT_Impact_z = impact_z
                set udg_BL_EVENT_Damage = this.damage
                set udg_BL_EVENT = 4.
                set udg_BL_EVENT_Source = null
                set udg_BL_EVENT_Target = null
                set udg_BL_EVENT_Sentry = null
                set udg_BL_EVENT_Launch_x = 0.
                set udg_BL_EVENT_Launch_y = 0.
                set udg_BL_EVENT_Launch_z = 0.
                set udg_BL_EVENT_Impact_x = 0.
                set udg_BL_EVENT_Impact_y = 0.
                set udg_BL_EVENT_Impact_z = 0.
                set udg_BL_EVENT_Damage = 0.
                set udg_BL_EVENT = 0.
                //END EVENT
               
                set this.owner = null
                set this.source = null
                set this.target = null
                set this.sentry = null
               
                if this.dummy_launch != null then
                    set this.dummy_launch = null
                endif
                if this.dummy_impact != null then
                    set this.dummy_impact = null
                endif
                if this.dummy_area != null then
                    set this.dummy_area = null
                endif
               
                loop
                    set i = i + 1
                    exitwhen i > this.dummy_count
                    call RecycleDummy(LoadUnitHandle(dummyStorage, this, i))
                endloop
                call FlushChildHashtable(dummyStorage, this)
               
                call ReleaseTimer(tmr_volley)
                call RemoveSavedInteger(hash, id, this.hashIndex)
               
                call this.deallocate()
            endmethod
           
           
            static method ApplySfxAndDamage takes real launch_x, real launch_y, real launch_z, real impact_x, real impact_y, real impact_z, BurstLaser this returns nothing
                local unit u = null
               
                //Launch
                if this.reduce_launch_overhead then
                    if this.launch_string != null then
                        if this.dummy_launch == null then
                            set this.dummy_launch = GetRecycledDummy(launch_x, launch_y, launch_z, 0.)
                            set this.dummy_count = this.dummy_count + 1
                            call SaveUnitHandle(dummyStorage, this, this.dummy_count, this.dummy_launch)
                        else
                            call SetUnitX(this.dummy_launch, launch_x)
                            call SetUnitY(this.dummy_launch, launch_y)
                            call MoveLocation(zLoc, launch_x, launch_y)
                            call SetUnitFlyHeight(this.dummy_launch, launch_z + GetLocationZ(zLoc), 0.)
                        endif
                        set udg_BL_EVENT_Dummy_Launch = this.dummy_launch
                        call SetUnitScale(this.dummy_launch, this.launch_scale, 0., 0.)
                        call DestroyEffect(AddSpecialEffectTarget(this.launch_string, this.dummy_launch, "origin"))
                    endif
                else
                    if this.launch_string != null then
                        set this.dummy_launch = GetRecycledDummy(launch_x, launch_y, launch_z, 0.)
                        call SetUnitScale(this.dummy_launch, this.launch_scale, 0., 0.)
                        call DestroyEffect(AddSpecialEffectTarget(this.launch_string, this.dummy_launch, "origin"))
                        set this.dummy_count = this.dummy_count + 1
                        call SaveUnitHandle(dummyStorage, this, this.dummy_count, this.dummy_launch)
                        set udg_BL_EVENT_Dummy_Launch = this.dummy_launch
                        set this.dummy_launch = null
                    endif
                endif
               
                //Impact and Area
                if this.reduce_impact_overhead then
                    call MoveLocation(zLoc, impact_x, impact_y)
                    if this.impact_string != null then
                        if this.dummy_impact == null then
                            set this.dummy_impact = GetRecycledDummy(impact_x, impact_y, impact_z, 0.)
                            set this.dummy_count = this.dummy_count + 1
                            call SaveUnitHandle(dummyStorage, this, this.dummy_count, this.dummy_impact)
                        else
                            call SetUnitX(this.dummy_impact, impact_x)
                            call SetUnitY(this.dummy_impact, impact_y)
                            call SetUnitFlyHeight(this.dummy_impact, impact_z + GetLocationZ(zLoc), 0.)
                        endif
                        set udg_BL_EVENT_Dummy_Impact = this.dummy_impact
                        call SetUnitScale(this.dummy_impact, this.impact_scale, 0., 0.)
                        call DestroyEffect(AddSpecialEffectTarget(this.impact_string, this.dummy_impact, "origin"))
                    endif
                    if this.area_string != null then
                        if this.area_scale != this.impact_scale then
                            if this.dummy_area == null then
                                set this.dummy_area = GetRecycledDummy(impact_x, impact_y, impact_z, 0.)
                                set this.dummy_count = this.dummy_count + 1
                            call SaveUnitHandle(dummyStorage, this, this.dummy_count, this.dummy_area)
                            else
                                call SetUnitX(this.dummy_area, impact_x)
                                call SetUnitY(this.dummy_area, impact_y)
                                call SetUnitFlyHeight(this.dummy_area, impact_z + GetLocationZ(zLoc), 0.)
                            endif
                            set udg_BL_EVENT_Dummy_Area = this.dummy_area
                            call SetUnitScale(this.dummy_impact, this.area_scale, 0., 0.)
                            call DestroyEffect(AddSpecialEffectTarget(this.area_string, this.dummy_area, "origin"))
                        else
                            if this.dummy_impact == null then
                                set this.dummy_impact = GetRecycledDummy(impact_x, impact_y, impact_z, 0.)
                                set this.dummy_count = this.dummy_count + 1
                                call SaveUnitHandle(dummyStorage, this, this.dummy_count, this.dummy_impact)
                            else
                                call SetUnitX(this.dummy_impact, impact_x)
                                call SetUnitY(this.dummy_impact, impact_y)
                                call SetUnitFlyHeight(this.dummy_impact, impact_z + GetLocationZ(zLoc), 0.)
                            endif
                            set udg_BL_EVENT_Dummy_Impact = this.dummy_impact
                            call SetUnitScale(this.dummy_impact, this.impact_scale, 0., 0.)
                            call DestroyEffect(AddSpecialEffectTarget(this.area_string, this.dummy_impact, "origin"))
                        endif
                    endif
                else
                    if this.impact_string != null then
                        set this.dummy_impact = GetRecycledDummy(impact_x, impact_y, impact_z, 0.)
                        set this.dummy_count = this.dummy_count + 1
                        call SaveUnitHandle(dummyStorage, this, this.dummy_count, this.dummy_impact)
                        call DestroyEffect(AddSpecialEffectTarget(this.impact_string, this.dummy_impact, "origin"))
                        set udg_BL_EVENT_Dummy_Impact = this.dummy_impact
                    endif
                    if this.area_string != null then
                        if this.area_scale != this.impact_scale then
                            set this.dummy_area = GetRecycledDummy(impact_x, impact_y, impact_z, 0.)
                            set this.dummy_count = this.dummy_count + 1
                            call SaveUnitHandle(dummyStorage, this, this.dummy_count, this.dummy_area)
                            set udg_BL_EVENT_Dummy_Area = this.dummy_area
                            call SetUnitScale(this.dummy_area, this.area_scale, 0., 0.)
                            call DestroyEffect(AddSpecialEffectTarget(this.area_string, this.dummy_area, "origin"))
                            set this.dummy_area = null
                        else
                            if this.dummy_impact == null then
                                set this.dummy_impact = GetRecycledDummy(impact_x, impact_y, impact_z, 0.)
                                set this.dummy_count = this.dummy_count + 1
                                call SaveUnitHandle(dummyStorage, this, this.dummy_count, this.dummy_impact)
                            endif
                            call SetUnitScale(this.dummy_impact, this.impact_scale, 0., 0.)
                            call DestroyEffect(AddSpecialEffectTarget(this.area_string, this.dummy_impact, "origin"))
                        endif
                        if this.dummy_impact != null then
                            set this.dummy_impact = null
                        endif
                    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
           
           
           
            private static method Interruption_Event takes nothing returns nothing
                local integer id = GetUnitUserData(GetTriggerUnit())
                local integer counter = LoadInteger(hash, id, COUNTER_KEY)
                //local integer i = 0
                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)
                        loop
                            exitwhen i >= this.laser_num
                            set this.lifespan <= 0.
                            set this.alpha = true
                            set i = i + 1
                        endloop*/

                        set this.laser_num = this.laser_count //This is set in case of interruption
                    endif
                    set counter = counter - 1
                endloop
                call SaveInteger(hash, id, COUNTER_KEY, 0)
            endmethod
           
           
           
            private static method BurstLaser_Volley takes nothing returns nothing
                local thistype this = GetTimerData(GetExpiredTimer())
               
                local integer id = GetUnitUserData(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
               
                local real xLaunch
                local real yLaunch
                local real zLaunch
               
                local real launch_scatter_x
                local real launch_scatter_y
                local real launch_scatter_z
               
                local real xImpact
                local real yImpact
                local real zImpact
               
                local boolean destroySet
                local integer i
               
                if not this.destructionPhase then
               
                    if this.laser_count < this.laser_num and UnitAlive(this.source) then //If there are still lasers in the set to be fired
                       
                        if this.sentry == null then
                            if this.launch_from_target then
                                if this.update_source_loc then
                                    set angle = this.lock_on_angle//Atan2(this.launch_y - target_y, this.launch_x - target_x) + this.launch_angle
                                    set xLaunch = target_x + Cos(angle) * this.launch_offset
                                    set yLaunch = target_y + Sin(angle) * this.launch_offset
                                    call MoveLocation(zLoc, xLaunch, yLaunch)
                                    set zLaunch = GetUnitFlyHeight(this.source) + this.launch_height + GetLocationZ(zLoc)
                                else
                                    set xLaunch = this.launch_x
                                    set yLaunch = this.launch_y
                                    set zLaunch = this.launch_z
                                endif
                            else
                                if this.update_source_loc then
                                    set angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
                                    set xLaunch = source_x + Cos(angle + this.launch_angle) * this.launch_offset
                                    set yLaunch = source_y + Sin(angle + this.launch_angle) * this.launch_offset
                                    call MoveLocation(zLoc, xLaunch, yLaunch)
                                    set zLaunch = GetUnitFlyHeight(this.source) + this.launch_height + GetLocationZ(zLoc)
                                else
                                    set xLaunch = this.launch_x
                                    set yLaunch = this.launch_y
                                    set zLaunch = this.launch_z
                                endif
                            endif
                        else
                            if this.update_source_loc then
                                set angle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
                                set xLaunch = GetUnitX(this.sentry) + Cos(angle + this.launch_angle) * this.launch_offset
                                set yLaunch = GetUnitY(this.sentry) + Sin(angle + this.launch_angle) * this.launch_offset
                                call MoveLocation(zLoc, xLaunch, yLaunch)
                                set zLaunch = GetUnitFlyHeight(this.sentry) + this.launch_height + GetLocationZ(zLoc)
                            else
                                set xLaunch = this.launch_x
                                set yLaunch = this.launch_y
                                set zLaunch = 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 xLaunch = xLaunch + launch_scatter_x
                        set yLaunch = yLaunch + launch_scatter_y
                        set zLaunch = zLaunch + launch_scatter_z
                       
                        if this.fixed_impact then //if true, start from the source's point
                            if this.update_target_loc then
                                if this.range_per_laser != 0. then
                                    set angle = Atan2(target_y - yLaunch, target_x - xLaunch)
                                    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 xImpact = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                                    set yImpact = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                                else
                                    set angle = Atan2(yLaunch - target_y, xLaunch - target_x)
                                    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 xImpact = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                                    set yImpact = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                                endif
                                call MoveLocation(zLoc, xImpact, yImpact)
                                if this.grounded then
                                    set this.impact_z = 0. + GetLocationZ(zLoc)
                                    set zImpact = 0. + GetLocationZ(zLoc)
                                else
                                    set this.impact_z = GetUnitFlyHeight(this.target) + GetLocationZ(zLoc)
                                    set zImpact = 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 xImpact = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                                    set yImpact = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                                else
                                    set angle = Atan2(yLaunch - this.initial_y, xLaunch - this.initial_x)
                                    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 xImpact = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                                    set yImpact = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                                    call MoveLocation(zLoc, impact_x, yImpact)
                                    set this.impact_z = GetUnitFlyHeight(this.target) + GetLocationZ(zLoc)
                                endif
                                call MoveLocation(zLoc, xImpact, yImpact)
                                if this.grounded then
                                    set this.impact_z = 0. + GetLocationZ(zLoc)
                                    set zImpact = 0. + GetLocationZ(zLoc)
                                else
                                    set zImpact = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                                endif
                            endif
                        else
                            if this.update_target_loc then
                                if this.range_per_laser != 0. then
                                    set angle = Atan2(target_y - yLaunch, target_x - xLaunch)
                                    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 xImpact = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                                    set yImpact = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                                else
                                    set angle = Atan2(yLaunch - target_y, xLaunch - target_x)
                                    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 xImpact = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                                    set yImpact = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                                endif
                                call MoveLocation(zLoc, xImpact, yImpact)
                                if this.grounded then
                                    set this.impact_z = 0. + GetLocationZ(zLoc)
                                    set zImpact = 0. + GetLocationZ(zLoc)
                                else
                                    set this.impact_z = GetUnitFlyHeight(this.target) + GetLocationZ(zLoc)
                                    set zImpact = 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 xImpact = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                                    set yImpact = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                                else
                                    set angle = Atan2(yLaunch - this.initial_y, xLaunch - this.initial_x)
                                    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 xImpact = this.impact_x + GetRandomReal(this.impact_scatter_x*-1, this.impact_scatter_x)
                                    set yImpact = this.impact_y + GetRandomReal(this.impact_scatter_y*-1, this.impact_scatter_y)
                                    call MoveLocation(zLoc, xImpact, yImpact)
                                    set this.impact_z = GetUnitFlyHeight(this.target) + GetLocationZ(zLoc)
                                endif
                                call MoveLocation(zLoc, xImpact, yImpact)
                                if this.grounded then
                                    set this.impact_z = 0. + GetLocationZ(zLoc)
                                    set zImpact = 0. + GetLocationZ(zLoc)
                                else
                                    set zImpact = this.impact_z + GetRandomReal(this.impact_scatter_z*-1, this.impact_scatter_z) + this.target_coll * COLLISION_PERCENTAGE
                                endif
                            endif
                        endif
                       
                        call BurstLaser.ApplySfxAndDamage(xLaunch, yLaunch, zLaunch, xImpact, yImpact, zImpact, this)
                       
                        //Laser
                        if this.laser_string != null then
                            call LaserInstance.create(xLaunch, yLaunch, zLaunch, xImpact, yImpact, zImpact, this)
                        endif
                        set this.laser_count = this.laser_count + 1
                       
                        //EVENT
                        set udg_BL_EVENT_Source = this.source
                        set udg_BL_EVENT_Target = this.target
                        set udg_BL_EVENT_Sentry = this.sentry
                        set udg_BL_EVENT_Launch_x = xLaunch
                        set udg_BL_EVENT_Launch_y = yLaunch
                        set udg_BL_EVENT_Launch_z = zLaunch
                        set udg_BL_EVENT_Impact_x = xImpact
                        set udg_BL_EVENT_Impact_y = yImpact
                        set udg_BL_EVENT_Impact_z = zImpact
                        set udg_BL_EVENT_Damage = this.damage
                        set udg_BL_EVENT = 2.
                        set udg_BL_EVENT_Source = null
                        set udg_BL_EVENT_Target = null
                        set udg_BL_EVENT_Sentry = null
                        set udg_BL_EVENT_Launch_x = 0.
                        set udg_BL_EVENT_Launch_y = 0.
                        set udg_BL_EVENT_Launch_z = 0.
                        set udg_BL_EVENT_Impact_x = 0.
                        set udg_BL_EVENT_Impact_y = 0.
                        set udg_BL_EVENT_Impact_z = 0.
                        set udg_BL_EVENT_Damage = 0.
                        set udg_BL_EVENT_Dummy_Launch = null
                        set udg_BL_EVENT_Dummy_Impact = null
                        set udg_BL_EVENT = 0.
                        //END EVENT
                       
                    elseif this.laser_count == this.laser_num or not UnitAlive(this.source) then
                   
                        //EVENT
                        set udg_BL_EVENT_Source = this.source
                        set udg_BL_EVENT_Target = this.target
                        set udg_BL_EVENT_Sentry = this.sentry
                        set udg_BL_EVENT_Launch_x = xLaunch
                        set udg_BL_EVENT_Launch_y = yLaunch
                        set udg_BL_EVENT_Launch_z = zLaunch
                        set udg_BL_EVENT_Impact_x = xImpact
                        set udg_BL_EVENT_Impact_y = yImpact
                        set udg_BL_EVENT_Impact_z = zImpact
                        set udg_BL_EVENT_Damage = this.damage
                        set udg_BL_EVENT = 3.
                        set udg_BL_EVENT_Source = null
                        set udg_BL_EVENT_Target = null
                        set udg_BL_EVENT_Sentry = null
                        set udg_BL_EVENT_Launch_x = 0.
                        set udg_BL_EVENT_Launch_y = 0.
                        set udg_BL_EVENT_Launch_z = 0.
                        set udg_BL_EVENT_Impact_x = 0.
                        set udg_BL_EVENT_Impact_y = 0.
                        set udg_BL_EVENT_Impact_z = 0.
                        set udg_BL_EVENT_Damage = 0.
                        set udg_BL_EVENT = 0.
                        //END EVENT
                       
                        call PauseTimer(this.tmr_volley)
                        call SaveInteger(hash, id, COUNTER_KEY, LoadInteger(hash, id, counter) - 1) // decrease counter
                       
                        set this.laser_num = this.laser_count //This is set in case of interruption.
                       
                        set i = 0
                        set destroySet = true
                        loop
                            set i = i + 1
                            exitwhen i > this.laser_num
                            if LoadLightningHandle(lightningStorage, this, i) != null then
                                set destroySet = false
                            endif
                            if destroySet then
                                //destructionPhase is used to terminate the instance slightly after
                                //the last laser in a set has been destroyed to allow special effects
                                //on dummies to play out.
                                set this.destructionPhase = true
                                if this.interval <= 0. then
                                    set this.interval = DEATH_TIME
                                endif
                            endif
                        endloop
                    endif
                else
               
                    set this.deathTime = this.deathTime - this.interval
                    if this.deathTime <= 0. then
                        call this.destroy()
                    endif
                                       
                endif
            endmethod
           
           
           
            static method create takes unit unit_source, unit unit_target, unit unit_sentry, BLAssignWeapon wpn, integer id, real damage returns thistype
           
                local thistype this = allocate()
               
                local integer idSource = GetUnitUserData(unit_source)
                local integer counter = LoadInteger(hash, idSource, 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
               
                local real range_offset = 0.
                local real launch_spread_x = 0.
                local real launch_spread_y = 0.
                local real launch_spread_z = 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
               
                set counter = counter + 1
                call SaveInteger(hash, idSource, COUNTER_KEY, counter) // increase instance counter
                call SaveInteger(hash, idSource, 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 = wpn.LaserString[id]
                set this.red = wpn.RED[id]
                set this.blue = wpn.BLUE[id]
                set this.green = wpn.GREEN[id]
                set this.alpha = wpn.ALPHA[id]
               
                set this.launch_string = wpn.LaserLaunchFX[id]
                set this.impact_string = wpn.LaserImpactFX[id]
                set this.area_string = wpn.LaserAreaFX[id]
               
                set this.launch_scale = wpn.LaserLaunchScale[id]
                set this.impact_scale = wpn.LaserImpactScale[id]
                set this.area_scale = wpn.LaserAreaScale[id]
               
                set this.duration = wpn.LaserDuration[id]
                set this.fade_time = wpn.LaserFadeTime[id]
                set this.interval = wpn.LaserInterval[id]
                set this.laser_dot = wpn.LaserDoT[id]
                set this.aoe = wpn.LaserAOE[id]
                set this.grounded = wpn.IsLaserGrounded[id]
               
                set this.launch_offset = wpn.LaserLaunchOffset[id]
                set this.launch_height = wpn.LaserLaunchHeight[id]
                set this.launch_angle = wpn.LaserLaunchAngle[id]
               
                set this.launch_scatter_x = wpn.LaserLaunchScatterX[id]
                set this.launch_scatter_y = wpn.LaserLaunchScatterY[id]
                set this.launch_scatter_z = wpn.LaserLaunchScatterZ[id]
               
                set this.impact_scatter_x = wpn.LaserImpactScatterX[id]
                set this.impact_scatter_y = wpn.LaserImpactScatterY[id]
                set this.impact_scatter_z = wpn.LaserImpactScatterZ[id]
               
                set this.update_target_loc = wpn.UpdateTargetLoc[id]
                set this.update_source_loc = wpn.UpdateSourceLoc[id]
                set this.launch_from_target = wpn.LaunchFromTargetLoc[id]
                set this.update_launch = wpn.UpdateLaunchPoint[id]
                set this.update_impact = wpn.UpdateImpactPoint[id]
                set this.fixed_impact = wpn.FixedImpactLoc[id]
               
                set this.range_start = wpn.LaserRangeStart[id]
                set this.range_per_laser = wpn.LaserRangePerLaser[id]
                set this.attack_type = wpn.LaserAttackType[id]
                set this.damage_type = wpn.LaserDamageType[id]
               
                set this.uninterruptible = wpn.Uninterruptible[id]
                set this.hit_all = wpn.LaserHitAll[id]
                set this.friendly_fire = wpn.LaserFriendlyFire[id]
                set this.reduce_launch_overhead = wpn.ReduceLaunchOverhead[id]
                set this.reduce_impact_overhead = wpn.ReduceImpactOverhead[id]
               
                set this.destructionPhase = false
                set this.deathTime = DEATH_TIME
               
                set this.laser_num = wpn.NumberOfLasers[id]
                set this.laser_count = 0
               
                if this.sentry == null then
                    if this.launch_from_target then
                        set startangle = Atan2(source_y - target_y, source_x - target_x) + this.launch_angle
                        if this.update_source_loc  then
                            set this.lock_on_angle = startangle
                        endif
                        if this.launch_offset > 0. then
                            set this.launch_x = target_x + Cos(startangle) * this.launch_offset
                            set this.launch_y = target_y + Sin(startangle) * this.launch_offset
                        else
                            set this.launch_x = target_x
                            set this.launch_y = target_y
                        endif
                        call MoveLocation(zLoc, this.launch_x, this.launch_y)
                        set this.launch_z = GetUnitFlyHeight(this.target) + this.launch_height + GetLocationZ(zLoc)
                    else
                        set startangle = Atan2(target_y - source_y, target_x - source_x) + this.launch_angle
                        if this.launch_offset > 0. then
                            set this.launch_x = source_x + Cos(startangle) * this.launch_offset
                            set this.launch_y = source_y + Sin(startangle) * this.launch_offset
                        else
                            set this.launch_x = source_x
                            set this.launch_y = source_y
                        endif
                        call MoveLocation(zLoc, this.launch_x, this.launch_y)
                        set this.launch_z = GetUnitFlyHeight(this.source) + this.launch_height + GetLocationZ(zLoc)
                    endif
                else
                    set sentry_x = GetUnitX(this.sentry)
                    set sentry_y = GetUnitY(this.sentry)
                    set startangle = Atan2(target_y - sentry_y, target_x - sentry_x) + this.launch_angle
                    if this.launch_offset > 0. then
                        set this.launch_x = sentry_x + Cos(startangle) * this.launch_offset
                        set this.launch_y = sentry_y + Sin(startangle) * this.launch_offset
                    else
                        set this.launch_x = sentry_x
                        set this.launch_y = sentry_y
                    endif
                    call MoveLocation(zLoc, this.launch_x, this.launch_y)
                    set this.launch_z = GetUnitFlyHeight(this.sentry) + this.launch_height + GetLocationZ(zLoc)
                endif
               
                set launch_spread_x = this.launch_x + GetRandomReal(this.launch_scatter_x*-1, this.launch_scatter_x)
                set launch_spread_y = this.launch_y + GetRandomReal(this.launch_scatter_y*-1, this.launch_scatter_y)
                set launch_spread_z = this.launch_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.fixed_impact then //if true, start from the source's point
                    set startangle = Atan2(target_y - source_y, target_x - source_x)
                    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 + wpn.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)
                    call MoveLocation(zLoc, impact_spread_x, impact_spread_y)
                    if this.grounded then
                        set this.impact_z = 0. + GetLocationZ(zLoc)
                        set impact_spread_z = 0.
                    else
                        set this.impact_z = GetUnitFlyHeight(this.target) + GetLocationZ(zLoc)
                        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 = Atan2(target_y - source_y, target_x - source_x) + wpn.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)
                    call MoveLocation(zLoc, impact_spread_x, impact_spread_y)
                    if this.grounded then
                        set this.impact_z = 0. + GetLocationZ(zLoc)
                        set impact_spread_z = 0.
                    else
                        set this.impact_z = GetUnitFlyHeight(this.target) + GetLocationZ(zLoc)
                        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 lasers can inflict DoT, diving the damage by the maximum number of hits the laser can inflict.
                if wpn.LaserDoT[id] > 0. then
                    set this.damage = damage / ((wpn.LaserDuration[id] + wpn.LaserFadeTime[id]) / wpn.LaserDoT[id])
                else
                    set this.damage = damage
                endif
               
                set this.dummy_count = 0
                call BurstLaser.ApplySfxAndDamage(launch_spread_x, launch_spread_y, launch_spread_z, impact_spread_x, impact_spread_y, impact_spread_z, this)
               
                //Laser
                if this.laser_string != null then
                    call LaserInstance.create(launch_spread_x, launch_spread_y, launch_spread_z, impact_spread_x, impact_spread_y, impact_spread_z, this)
                endif
                set this.laser_count = this.laser_count + 1
               
                //EVENT
                set udg_BL_EVENT_Source = this.source
                set udg_BL_EVENT_Target = this.target
                set udg_BL_EVENT_Sentry = this.sentry
                set udg_BL_EVENT_Launch_x = launch_x
                set udg_BL_EVENT_Launch_y = launch_y
                set udg_BL_EVENT_Launch_z = launch_z
                set udg_BL_EVENT_Impact_x = impact_x
                set udg_BL_EVENT_Impact_y = impact_y
                set udg_BL_EVENT_Impact_z = impact_z
                set udg_BL_EVENT_Damage = this.damage
                set udg_BL_EVENT = 1.
                set udg_BL_EVENT_Source = null
                set udg_BL_EVENT_Target = null
                set udg_BL_EVENT_Sentry = null
                set udg_BL_EVENT_Launch_x = 0.
                set udg_BL_EVENT_Launch_y = 0.
                set udg_BL_EVENT_Launch_z = 0.
                set udg_BL_EVENT_Impact_x = 0.
                set udg_BL_EVENT_Impact_y = 0.
                set udg_BL_EVENT_Impact_z = 0.
                set udg_BL_EVENT_Damage = 0.
                set udg_BL_EVENT = 0.
                //END EVENT
               
                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
                if this.laser_num > 1 then
                    set this.tmr_volley = NewTimerEx(this)
                    call TimerStart(this.tmr_volley, this.interval, true, timer_volley_handler)
                endif
               
                return this
            endmethod
           
           
            private static method onInit takes nothing returns nothing
                local integer iLoop = 0
                set timer_volley_handler = function thistype.BurstLaser_Volley
                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
           
        endstruct
       
    endlibrary
  • Code (vJASS):
    library BL
     
        /*** If you're getting a duplicate of UnitAlive, you can delete or comment out this one ***/
        native UnitAlive takes unit id returns boolean
     
        globals
            public boolean array UsesBurstLaser
            public unit array LaserSentry
            private constant integer MAX_WEAPONS = 3
        endglobals
     
        struct BLAssignWeapon
            integer instance

            boolean array Uninterruptible[MAX_WEAPONS]      //If true, lasers will keep firing even if the source is stunned or dead.
            boolean array IsLaserGrounded[MAX_WEAPONS]      //If true, then your Lasers will always strike the ground. Invalidates LaserSpreadZ. Can be useful for AoE ground attacks.
            boolean array UpdateImpactPoint[MAX_WEAPONS]    //If true, the Laser will latch on to it's impact point relative to it's target. That can cause weird behaviour, use trial and error.
            integer array NumberOfLasers[MAX_WEAPONS]       //The number of lasers that will be shot per attack. Will default to 1 if the value it less than 1.
            real array LaserDuration[MAX_WEAPONS]           //How long an individual laser will last before it starts to fade. 32 == 1 second.
            real array LaserFadeTime[MAX_WEAPONS]           //How long it takes for each laser to fade. 32 == 1 second.
            real array LaserInterval[MAX_WEAPONS]           //Time between laser in seconds.
            real array LaserDoT[MAX_WEAPONS]                //the number of intervals that will pass before the sfx and damage are repeated PER LASER. Set to <= 0 to ignore. Useful for impact-locked lasers.
            string array LaserLaunchFX[MAX_WEAPONS]         //The string of the special effect that plays on the launch dummy.
            string array LaserImpactFX[MAX_WEAPONS]         //The string of the special effect that plays on the impact dummy.
            string array LaserAreaFX[MAX_WEAPONS]           //The string of the special effect that plays on the impact dummy in addition to the ImpactFX. Works better with an AOE laser.
            string array LaserString[MAX_WEAPONS]           //The string of lightning effect itself.
            real array LaserAOE[MAX_WEAPONS]                //If greater that 0. will deal damage in an area around the impact.

            real array LaserLaunchOffset[MAX_WEAPONS]       //How far forward with the Launch effect be. Use negative values to move backwards.
            real array LaserLaunchHeight[MAX_WEAPONS]       //Adjusts the Launch effect along the Z axis. Positive value go up, negative values go down.
            real array LaserLaunchAngle[MAX_WEAPONS]        //By how many radians will the Launch effect be offset by? Positive values adjust the angle counter-clockwise.
            real array LaserLaunchScatterX[MAX_WEAPONS]     //How much your laser's launch will scatter along the X axis.
            real array LaserLaunchScatterY[MAX_WEAPONS]     //How much your laser's launch will scatter along the Y axis.
            real array LaserLaunchScatterZ[MAX_WEAPONS]     //How much your laser's launch will scatter along the Z axis.
            real array LaserLaunchScale[MAX_WEAPONS]        //The size of your launch special effects. 1. is 100%.
            real array LaserImpactScatterX[MAX_WEAPONS]     //How much your laser will spread out along the X axis.
            real array LaserImpactScatterY[MAX_WEAPONS]     //How much your laser will spread out along the Y axis.
            real array LaserImpactScatterZ[MAX_WEAPONS]     //How much your laser will spread out along the Z axis.
            real array LaserImpactScale[MAX_WEAPONS]        //The size of your impact special effects. 1. is 100%.
            real array LaserAreaScale[MAX_WEAPONS]          //Setting this value different from LaserImpactScale will mean a new dummy will have to be created.
                                                            //Unless really necessary, it's recommended to keep this the same value as LaserImpactScale.

            boolean array LaserDivideDamage[MAX_WEAPONS]    //If true, the total damage will be divided amoung the number of lasers.
            boolean array UpdateTargetLoc[MAX_WEAPONS]      //If false, Lasers in a set will strike the same point instead of updating their impact location to follow a moving target.
            boolean array UpdateSourceLoc[MAX_WEAPONS]      //If false, consequence shots in a volley will launch from the initial point
            boolean array UpdateLaunchPoint[MAX_WEAPONS]    //If true, lasers will follow source if it moves away.
            boolean array LaunchFromTargetLoc[MAX_WEAPONS]  //If true, then launch point will be relative to the target rather than the source. Sentries take priority, however.
            boolean array FixedImpactLoc[MAX_WEAPONS]       //If true will set LaserRangeStart's starting point to the attacker (source of the laser) instead of the target
            real array LaserRangeStart[MAX_WEAPONS]         //Adds or subtracts a certain amount of distance from it's starting point.
            real array LaserRangePerLaser[MAX_WEAPONS]      //Adds or substracts distance per Laser in a set.
            real array LaserDirectionalTilt[MAX_WEAPONS]    //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.

            attacktype array LaserAttackType[MAX_WEAPONS]   //Vanilla attack type like ATTACK_TYPE_HERO, ATTACK_TYPE_PIERCE, etc
            integer array LaserDamageType[MAX_WEAPONS]      //The damage type from Damage Engine
            boolean array LaserHitAll[MAX_WEAPONS]          //The laser will harm all units in the LaserAOE. If LaserAOE is <= 0. this does nothing.
            boolean array LaserFriendlyFire[MAX_WEAPONS]    //AOE damage will damage allies
            unit array LaserSentry[MAX_WEAPONS]             //This is optional. If something is set as the Sentry, the lasers will shoot from that unit instead.

            real array RED[MAX_WEAPONS]                     //Red tint of the Laser. 1. is 100%, and 0. is 0%.
            real array BLUE[MAX_WEAPONS]                    //Blue tint of the Laser. 1. is 100%, and 0. is 0%.
            real array GREEN[MAX_WEAPONS]                   //Green tint of the Laser. 1. is 100%, and 0. is 0%.
            real array ALPHA[MAX_WEAPONS]                   //Transparency of the Laser. 1. fully visible. 0. is completely transparent.

            boolean array ReduceLaunchOverhead[MAX_WEAPONS] //If true, only 1 dummy will be used for your launch special effect, but if the launch scatters per volley, the
                                                            //special effects may jump around since they're bound to only one unit. Some special effects don't have that
                                                            //problem, so it's preferrable to test lasers with this set to true first, as the alternative means a new dummy
                                                            //per laser per launch, which creates a lot of overhead.
            boolean array ReduceImpactOverhead[MAX_WEAPONS] //Same as above, but for impacts and area special effects.

            method destroy takes nothing returns nothing
                local integer iLoop = 0
                loop
                    exitwhen iLoop > MAX_WEAPONS
                    set this.LaserString[iLoop]             = null
                    set this.RED[iLoop]                     = 1.
                    set this.BLUE[iLoop]                    = 1.
                    set this.GREEN[iLoop]                   = 1.
                    set this.ALPHA[iLoop]                   = 1.
                    set this.LaserLaunchFX[iLoop]           = null
                    set this.LaserImpactFX[iLoop]           = null
                    set this.LaserAreaFX[iLoop]             = null
                    set this.LaserLaunchScale[iLoop]        = 1.
                    set this.LaserImpactScale[iLoop]        = 1.
                    set this.LaserAreaScale[iLoop]          = 1.
                    set this.NumberOfLasers[iLoop]          = 1
                    set this.LaserDuration[iLoop]           = 0.
                    set this.LaserFadeTime[iLoop]           = 0.
                    set this.LaserInterval[iLoop]           = 0.
                    set this.LaserDoT[iLoop]                = 0.
                    set this.LaserAOE[iLoop]                = 0.
                    set this.IsLaserGrounded[iLoop]         = false
                    set this.LaserLaunchOffset[iLoop]       = 0.
                    set this.LaserLaunchHeight[iLoop]       = 0.
                    set this.LaserLaunchAngle[iLoop]        = 0.
                    set this.LaserLaunchScatterX[iLoop]     = 0.
                    set this.LaserLaunchScatterY[iLoop]     = 0.
                    set this.LaserLaunchScatterZ[iLoop]     = 0.
                    set this.LaserImpactScatterX[iLoop]     = 0.
                    set this.LaserImpactScatterY[iLoop]     = 0.
                    set this.LaserImpactScatterZ[iLoop]     = 0.
                    set this.UpdateTargetLoc[iLoop]         = false
                    set this.UpdateLaunchPoint[iLoop]       = false
                    set this.UpdateImpactPoint[iLoop]       = false
                    set this.LaunchFromTargetLoc[iLoop]     = false
                    set this.UpdateSourceLoc[iLoop]         = false
                    set this.FixedImpactLoc[iLoop]          = false
                    set this.LaserRangeStart[iLoop]         = 0.
                    set this.LaserRangePerLaser[iLoop]      = 0.
                    set this.LaserDirectionalTilt[iLoop]    = 0.
                    set this.LaserAttackType[iLoop]         = null
                    set this.LaserDamageType[iLoop]         = 0
                    set this.LaserDivideDamage[iLoop]       = false
                    set this.Uninterruptible[iLoop]         = false
                    set this.LaserHitAll[iLoop]             = false
                    set this.LaserFriendlyFire[iLoop]       = false
                    set this.ReduceLaunchOverhead[iLoop]    = true
                    set this.ReduceImpactOverhead[iLoop]    = true
                    set iLoop = iLoop + 1
                endloop
                call this.deallocate()
            endmethod


            static method operator [] takes unit u returns thistype
                return thistype(GetUnitUserData(u)).instance
            endmethod


            static method create takes unit u, integer wpn_num, integer arsenal_num returns thistype
                local integer id = GetUnitUserData(u)
                local thistype this
             
                if thistype(id).instance == 0 then
                    set this = allocate()
                    set thistype(id).instance = this
                    set UsesBurstLaser[id] = true
                else
                    set this = thistype(id).instance
                endif
             
                if BL_NumberOfLasers[arsenal_num] > 0 then
                    set this.NumberOfLasers[wpn_num]    = BL_NumberOfLasers[arsenal_num]
                else
                    set this.NumberOfLasers[wpn_num]    = 1
                endif
             
                set this.LaserString[wpn_num]           = BL_LaserString[arsenal_num]
                set this.RED[wpn_num]                   = BL_RED[arsenal_num]
                set this.BLUE[wpn_num]                  = BL_BLUE[arsenal_num]
                set this.GREEN[wpn_num]                 = BL_GREEN[arsenal_num]
                set this.ALPHA[wpn_num]                 = BL_ALPHA[arsenal_num]
                set this.LaserLaunchFX[wpn_num]         = BL_LaserLaunchFX[arsenal_num]
                set this.LaserImpactFX[wpn_num]         = BL_LaserImpactFX[arsenal_num]
                set this.LaserAreaFX[wpn_num]           = BL_LaserAreaFX[arsenal_num]
                set this.LaserLaunchScale[wpn_num]      = BL_LaserLaunchScale[arsenal_num]
                set this.LaserImpactScale[wpn_num]      = BL_LaserImpactScale[arsenal_num]
                set this.LaserAreaScale[wpn_num]        = BL_LaserAreaScale[arsenal_num]
                set this.LaserDuration[wpn_num]         = BL_LaserDuration[arsenal_num]
                set this.LaserFadeTime[wpn_num]         = BL_LaserFadeTime[arsenal_num]
                set this.LaserInterval[wpn_num]         = BL_LaserInterval[arsenal_num]
                set this.LaserDoT[wpn_num]              = BL_LaserDoT[arsenal_num]
                set this.LaserAOE[wpn_num]              = BL_LaserAOE[arsenal_num]
                set this.IsLaserGrounded[wpn_num]       = BL_IsLaserGrounded[arsenal_num]
                set this.LaserLaunchOffset[wpn_num]     = BL_LaserLaunchOffset[arsenal_num]
                set this.LaserLaunchHeight[wpn_num]     = BL_LaserLaunchHeight[arsenal_num]
                set this.LaserLaunchAngle[wpn_num]      = BL_LaserLaunchAngle[arsenal_num]
                set this.LaserLaunchScatterX[wpn_num]   = BL_LaserLaunchScatterX[arsenal_num]
                set this.LaserLaunchScatterY[wpn_num]   = BL_LaserLaunchScatterY[arsenal_num]
                set this.LaserLaunchScatterZ[wpn_num]   = BL_LaserLaunchScatterZ[arsenal_num]
                set this.LaserImpactScatterX[wpn_num]   = BL_LaserImpactScatterX[arsenal_num]
                set this.LaserImpactScatterY[wpn_num]   = BL_LaserImpactScatterY[arsenal_num]
                set this.LaserImpactScatterZ[wpn_num]   = BL_LaserImpactScatterZ[arsenal_num]
                set this.UpdateTargetLoc[wpn_num]       = BL_UpdateTargetLoc[arsenal_num]
                set this.UpdateSourceLoc[wpn_num]       = BL_UpdateSourceLoc[arsenal_num]
                set this.UpdateLaunchPoint[wpn_num]     = BL_UpdateLaunchPoint[arsenal_num]
                set this.UpdateImpactPoint[wpn_num]     = BL_UpdateImpactPoint[arsenal_num]
                set this.LaunchFromTargetLoc[wpn_num]   = BL_LaunchFromTargetLoc[arsenal_num]
                set this.FixedImpactLoc[wpn_num]        = BL_FixedImpactLoc[arsenal_num]
                set this.LaserRangeStart[wpn_num]       = BL_LaserRangeStart[arsenal_num]
                set this.LaserRangePerLaser[wpn_num]    = BL_LaserRangePerLaser[arsenal_num]
                set this.LaserDirectionalTilt[wpn_num]  = BL_LaserDirectionalTilt[arsenal_num]
                set this.LaserAttackType[wpn_num]       = BL_LaserAttackType[arsenal_num]
                set this.LaserDamageType[wpn_num]       = BL_LaserDamageType[arsenal_num]
                set this.LaserDivideDamage[wpn_num]     = BL_LaserDivideDamage[arsenal_num]
                set this.Uninterruptible[wpn_num]       = BL_Uninterruptible[arsenal_num]
                set this.LaserHitAll[wpn_num]           = BL_LaserHitAll[arsenal_num]
                set this.LaserFriendlyFire[wpn_num]     = BL_LaserFriendlyFire[arsenal_num]
                set this.ReduceLaunchOverhead[wpn_num]  = BL_ReduceLaunchOverhead[arsenal_num]
                set this.ReduceImpactOverhead[wpn_num]  = BL_ReduceImpactOverhead[arsenal_num]
             
                return this
            endmethod
             
        endstruct
     
        //Weapon Setup variables
        globals
            string array BL_LaserString
            real array BL_RED
            real array BL_BLUE
            real array BL_GREEN
            real array BL_ALPHA
            string array BL_LaserLaunchFX
            string array BL_LaserImpactFX
            string array BL_LaserAreaFX
            real array BL_LaserLaunchScale
            real array BL_LaserImpactScale
            real array BL_LaserAreaScale
            integer array BL_NumberOfLasers
            real array BL_LaserDuration
            real array BL_LaserFadeTime
            real array BL_LaserInterval
            real array BL_LaserDoT
            real array BL_LaserAOE
            boolean array BL_IsLaserGrounded
            real array BL_LaserLaunchOffset
            real array BL_LaserLaunchHeight
            real array BL_LaserLaunchAngle
            real array BL_LaserLaunchScatterX
            real array BL_LaserLaunchScatterY
            real array BL_LaserLaunchScatterZ
            real array BL_LaserImpactScatterX
            real array BL_LaserImpactScatterY
            real array BL_LaserImpactScatterZ
            boolean array BL_UpdateTargetLoc
            boolean array BL_UpdateSourceLoc
            boolean array BL_UpdateLaunchPoint
            boolean array BL_UpdateImpactPoint
            boolean array BL_LaunchFromTargetLoc
            boolean array BL_FixedImpactLoc
            real array BL_LaserRangeStart
            real array BL_LaserRangePerLaser
            real array BL_LaserDirectionalTilt
            attacktype array BL_LaserAttackType
            integer array BL_LaserDamageType
            boolean array BL_LaserDivideDamage
            boolean array BL_Uninterruptible
            boolean array BL_LaserHitAll
            boolean array BL_LaserFriendlyFire
            boolean array BL_ReduceLaunchOverhead
            boolean array BL_ReduceImpactOverhead
        endglobals
     
    endlibrary
  • Code (vJASS):

    library BurstLaserOnAttackModule initializer init requires BurstLaser

        globals
            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 Weapon_Index = 1
            local BLAssignWeapon blwpn
            //local integer Struct_ID = BLAssignWeapon[udg_DamageEventSource]
            if BL_UsesBurstLaser[Source_ID] and udg_DamageEventType == 0 and not udg_IsDamageSpell then
                set udg_DamageEventAmount = 0.
             
                //If you want to have temporary sentries, you may create them here and apply an expiration timer to them. Used mostly for very conplex launch effects.
             
                //Here you can change the Weapon_Index to a different value to switch the laser weapon used. For example, the Gryphon Rider has 2 laser weapons, and
                //Weapon_Index 2 is used against air units. Otherwise, the defaul index is 1.
                if GetUnitTypeId(udg_DamageEventSource) == 'hgry' then
                    if IsUnitType(udg_DamageEventTarget, UNIT_TYPE_FLYING) then
                        set Weapon_Index = 2
                    endif
                 
                endif
             
                //Here we check if the damage will be split per laser in a set or not.
                set blwpn = BLAssignWeapon[udg_DamageEventSource]
                if blwpn.LaserDivideDamage[Weapon_Index] then
                    set Damage = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget)) / blwpn.NumberOfLasers[Weapon_Index]
                else
                    set Damage = GetFullDamage(udg_DamageEventPrevAmt, GetUnitArmor(udg_DamageEventTarget))
                endif
             
                call BurstLaser.create(udg_DamageEventSource, udg_DamageEventTarget, BL_LaserSentry[Source_ID], blwpn, Weapon_Index, 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
     
Contents

Burst Laser v3.1 (Map)

Reviews
KILLCIDE
A lot of libraries, but the system is rad. The demo and API are also pretty simple to follow. The only downside are the bugs you mentioned in the README and the lightning effects getting all wonky on air units over terrain. Needs Fixed Nothing...
  1. _Guhun_

    _Guhun_

    Joined:
    Jun 12, 2010
    Messages:
    354
    Resources:
    7
    Spells:
    6
    Tutorials:
    1
    Resources:
    7
    That's pretty cool. One should be able to design some sc2-like attacks using this :)

    I don't' know much about vJASS, but I've seen those libs often enough to think it isn't much of a problem having that many requirements, except compatibility if someone uses a similar system to one that you use.

    The amount of customization is really good, I couldn't think of anything to add. Sweet lightning effects, btw.
     
  2. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Yeah, that's true, but I was still hoping of being able to produce something with fewer requirements. I guess it can't be helped :p
    Thanks for the comment :)
     
  3. zv27

    zv27

    Joined:
    Aug 21, 2010
    Messages:
    296
    Resources:
    0
    Resources:
    0
    Too many external libraries for simple spell.
    Why?
     
  4. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    It's not a spell, it's a customisable custom attack system.
     
  5. zv27

    zv27

    Joined:
    Aug 21, 2010
    Messages:
    296
    Resources:
    0
    Resources:
    0
    Oupss hahaha
     
  6. Emm-A-

    Emm-A-

    Joined:
    Jul 1, 2008
    Messages:
    1,313
    Resources:
    0
    Resources:
    0
    hey Spellbound,
    I think, that the visuals are truly excellent! It looks really great, but unfortunately, with it being a custom attack system, it is unlikely to be implemented by a lot of users.
    Sry I did not take the time to examine your code, but do you think, uploading the core as a module, that may be used by the combat system or just freely as a point target spell makes sense?
     
  7. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Hmm, that wouldn't be that hard to accomplish. Good suggestion, I'll see what I can do.

    EDIT: Well since nothing ever works out the way to plan it, so has the update to make this not uniquely a custom attack system. I *think* my id allocation library may be the culprit, but all the other allocation methods I've found require structs, and structs are like Mandarin to me, so fuck that.

    Unless I magically grow more neurons and programming starts making sense to me, this system is staying like that, sorry :( That is, after I fix that apparent bug where some unit's lightning effects are somehow being replaced by one another.

    PS: anyone wishing to improve upon it is welcome to.
     
    Last edited: Nov 26, 2016
  8. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Okay, wow, I finally managed to fix this damn thing. Special thanks to Aniki and especially IcemanBo for debugging this.
     
  9. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Hey there.

    In
    static method Interruption_Event
    "0" could be just saved as counter, instead of saving each loop run a new value.

    Code (vJASS):

        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])

    ^inside fade-- on loop start already is checked that lightning is not
    null
    , and is the dead variable actually needed? I mean it's always checked if it's null already.

    Hm, doesn't it look a bit weird if lightnigs stay and fade slowly out, if the unit gets interrupted, like moves away? In theory lightnings could still be visible for like a second or so? What you think?

    Make private what can be private.

    Code (vJASS):

    if BL_NumberOfLasers[id] > MAX_LASERS then
        set this.laser_num = MAX_LASERS
    else
        set this.laser_num = BL_NumberOfLasers[id]
    endif

    ^it's totaly fine, but just wann show some BJs are actually useful:
    set this.laser_num = IMaxBJ(this.laser_num, MAX_LASERS)


    or
    3.14159
    -> could just be
    bj_PI
    ^^

    lightning laser

    ^is this member needed? maybe it was from an older version, and also:
    Code (vJASS):
    private integer array data

    private constant integer OFFSET = 0x100000


    It's no harm to do, but technicaly seen nulling/falsing these is not needed, at least not for leaks:
    Code (vJASS):

                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


     set this.f_duration[iLoop] = this.f_duration[iLoop] - 1//0.0312500

    ^why use a constant value of "1" btw to subrract the value? It should always directly relate to the interval, in this case to "0.0312500".

    The onIndex initialization works fine, but is the step necessary, as you actually can only refer to the unit type, when an attack starts?
    [vJASS] - Learning Structs
    ^similar like the in bottom of the post.

    ======

    ^Most of the points are not so relevant. But do you also experience mass lags and FPS drop?
    I could not normaly fight honestly, do you or someone have same problem?
    I'm not sure where the source exactly is, and I havent done any debugging, but from the looks I couldn't really spot something mainly wrong in the brust code.
     
  10. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Do you mean this line?
    call SaveInteger(hash, id, COUNTER_KEY, counter)
    and turn it into:
    call SaveInteger(hash, id, COUNTER_KEY, 0)
    ?

    Hmm, I guess not.

    It would, but I suppose it's up to the player to decide how they want to fade their lightnings. The duration might be high but fading should probably remain a low number. Still, I added the FloatingLaunch specially for that, so the that lightning would remain on at it's launch coordinates regardless of where the attacker went. I suppose I could add a line or something to end the duration of the lightning and start the fadeout immediately... not sure how to proceed there.

    Right. Made the destroy and ApplySfxAndDamage private. Can you have private statics as well?

    Oh, neat, definitely going to use
    bj_PI
    , though I'll keep the if BL_NumberofLaser[id] > MAX_LASERS then since it save me from having another function call. Still useful in less demanding systems though.

    Ah, yes, vestigial variables. I'll remove 'em.

    Cool, I was unsure whether these had to be nulled. Code needs a trim anyway.

    That's because the duration of the laser and the fade duration are different. A laser might last 5 seconds but fade in 3, so I just use the smallest amount (.03125) and use an integer to count the intervals instead. Also the lightning needs to be moved :p

    I'm not sure I follow. Do you mean the event registrations or the handler codes?

    That's really weird. The only lag I experience with this new version is from a discarded spell I tried to make using the system. I really don't experience any lag at all, just 1 - 2 frame drops when things get a bit chaotic with the lasers all over the place. I'm also running an ENB on top of things, but I don't even have that beast of a PC. I've tested the system many times and have not experienced any significant lag :/

    There is a variable called LaserLingerInterval[ID] that indicates how many times your special effects and damage will repeat for as long as your laser is active. The calculation is (LaserDuration[ID] + LaserFadeTime[ID]) / LaserLingerInterval[ID]. I think I misread what you wrote, lol. The other lightning effects DO deal damage. The damage may look insignificant if you set LaserDivideDamage[ID] to true, but the damage is being dealt.
     
  11. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Which unit, specifically? Is it the Gryphon Rider / Gargoyle / Archmage / Spellbreaker / Rifleman? The lag could be cause by SpecialEffectZ so I you can, try testing it with the Gryphon Rider and see how the frames do.
     
  12. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Yo, it could run once then, instead of in each loop. (just tiny optimization)

    Ah ok, sounds reasonable. Maybe, yes,..maybe it makes sense to loop through all lightnings onInterrupt and start fading them instantly?

    Sure. You could also go the other way around make
    private struct MyStruct
    and then make things explicitly
    public
    that need to be public.

    If you're interested in reading some up some details Memory Leaks.

    I see, but imo the proper way would be to define the correct timeout, and then substarct the interval of it (.03125).
    This way it also be correct if *why ever* the .03125 is changed. My thinking is: it relates to the timeout, so then make it also a relative value, instead of an not absolute.

    Ehm, I'm not sure I understood. But with my way it would assign values onInit for each unit type, while your does for each unit onIndex.

    Hm oké, strange. But I definitly have them. Will try to report closer what maybe makes it, soon.
     
  13. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    So this, then?
    Code (vJASS):

            private 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)
                        set this.laser_num = this.laser_index //This is set in case of interruption
                    endif
                    set counter = counter - 1
                endloop
                call SaveInteger(hash, id, COUNTER_KEY, 0)
            endmethod


    Hmm, I am having trouble understanding the hashtable and how the counter/COUNTER_KEY stores stuff. So if I was to do [/lass]set this.f_duration[index] = 0[/ljass], would I have to create a new loop inside the existing loop and run it from 1 to laser_num and set their durations to 0?

    Right, done.

    Oh, I never thought of it that way. Which way would be have the smallest performance hit, though? I can use a constant for the smallest interval and have the timer subtract that constant from the duration and fade times.

    I don't know, checking for unit type doesn't sound very malleable. What if the laser is a weapon type that the unit can switch on an off?

    I hope you're successful. I really don't lag at all when I test this. You could try setting the gryphon rider's LaserLaunchFX[ID] in the BurstLaser Indexing trigger to null and see if it still lags. If it doesn't, that means AddSpecialEffectZ is the culprit.[/quote][/QUOTE]
     
  14. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Basicly yes. If you want you can also do a if statement first if counter is even bigger than 0.

    Inside the existing loop of the interruptio trigger you currently loop through all existing instances. So yes I think you need a second loop inside so you can loop through all the lightnigs which are binded to the instance, and then set "fading = true" blindly for all.
    If you feel we can chat about these hashtable stuff or so, it's really actually pretty easy if you got it once.:)

    " instead of an not absolute." -> " instead of an absolute." of course, but you understood it. :p ... hm I'm not sure I understand the part with "smallest interval". I think something like ~0.03125000~ is okay/smooth when it's used to be up to date with unit movements.

    I see, sounds reasonable. It seems I limited the usage a bit in my thoughts.^^
     
  15. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Eh, I think I'll leave it as-is.

    Perhaps, but I seem to be getting a handle on it, but I'll need a lot more practice to be able to use it comfortably. Just to be safe, though, can you verify the position of
    exitwhen iLoop == this.laser_num
    in this function? I'm not sure if it should be before or after the duration and fading variables.
    Code (vJASS):

            private static method Interruption_Event takes nothing returns nothing
                local integer id = GetHandleId(GetTriggerUnit())
                local integer counter = LoadInteger(hash, id, COUNTER_KEY)
                local integer iLoop = 0
                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)
                        loop
                            exitwhen iLoop == this.laser_num
                            set this.f_duration[iLoop] = 0
                            set this.f_fading[iLoop] = true
                            set iLoop = iLoop + 1
                        endloop
                        set this.laser_num = this.laser_index //This is set in case of interruption
                    endif
                    set counter = counter - 1
                endloop
                call SaveInteger(hash, id, COUNTER_KEY, 0)
            endmethod


    "smallest interval" is just my way of saying .03125 seconds. But both methods do the same thing, however, so idk if it's worth changing the way the duration and the fading is timed. If subtracting time has better performance, then I will change it.

    That what I did myself initially, then Emm-A- suggested it might be limiting to make thing only a custom attack system, so I remade it to work with spells as well :p
     
  16. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    writing loop-exit condition on top is in most cases the best way, because when the exitwhen is at bottom, the loop will always run at least once, even the condition won't met.
    But it's enough if it loops until
    this.laser_index
    , so
    exitwhen iLoop == this.laser_num

    ->
    exitwhen iLoop >=this.laser_index


    It was not really suggestion because of performance, but it just makes more sense for me.

    ----

    The AddSpecialEffectZ function was/is a reason for the extreme fps drops, I don't think it can be used to have it called so often and dynamicly.
    The mass creation seems to heavy. If there are similar functions often called it's probably also too heavy. :(
     
  17. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    I was afraid that might be the case. The initial versions of the system use dummy units but that lagged my pc to hell and back, so the only alternative I can think of is to use one dummy unit and use it to control the Z of the destroyed special effect. The only problem is that the unit can't reliably move to all the coordinates if they happen to be occurring within very short time periods, not to mention most special effect will travel along with the unit. I did a test with the Riflemen and their launch animations just wouldn't play, and if two of them fired at practically the same time one of them wouldn't play the impact special effects as well. The following function just isn't reliable:
    Code (vJASS):

           private 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
                if FX_DUMMY == null then
                    set FX_DUMMY = CreateUnit(Player(15), 'dumi', 0., 0., 0.)
                endif
                //Launch
                if this.launch_string != null then
                    call SetUnitX(FX_DUMMY, launch_x)
                    call SetUnitY(FX_DUMMY, launch_y)
                    call SetUnitZ(FX_DUMMY, launch_z)
                    call DestroyEffect(AddSpecialEffectTarget(this.launch_string, FX_DUMMY, "origin"))
                endif
                //Impact
                if this.impact_string != null then
                    call SetUnitX(FX_DUMMY, impact_x)
                    call SetUnitY(FX_DUMMY, impact_y)
                    call SetUnitZ(FX_DUMMY, impact_z)
                    call DestroyEffect(AddSpecialEffectTarget(this.impact_string, FX_DUMMY, "origin"))
                endif
                //Area
                if this.area_string != null then
                    call SetUnitX(FX_DUMMY, impact_x)
                    call SetUnitY(FX_DUMMY, impact_y)
                    call SetUnitZ(FX_DUMMY, impact_z)
                    call DestroyEffect(AddSpecialEffectTarget(this.area_string, FX_DUMMY, "origin"))
                endif

                ... etc
     
    I have no idea how to circumvent not using AddSpecialEffectZ :/
    I mean, Memory Hacking would certainly remove the need for some libraries, but I think that's waaaay out of my league.
     
    Last edited: Dec 19, 2016
  18. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    New dummy units don't need to be created each time. The same ones can be used, maybe a recylce system would help.

    I'm not sure it's a good idea to use memory hack for official submissions at the moment. Even you might use it for your personal projects, though.
     
    Last edited: Dec 20, 2016
  19. Dat-C3

    Dat-C3

    Joined:
    Mar 15, 2012
    Messages:
    2,470
    Resources:
    10
    Models:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    1
    Resources:
    10
    Could try a dummy pool which is a term I use that means create a whole bunch of dummy units as game starts though not all at once, just a few at a time then combine that with a dummy recycler. Should be able to bypass that dumb lag then.