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

Burst Laser v3.2

full


NB: this system was made before special effects could be manipulated through functions, and as such, the overhead on it is quite significant if your lasers are going to contain a lot of launch/impact effects. When I made this system back then, it didn't appear to lag nearly as much as it does now, as you can attest from the video above, so unless v1.32 of the game is doing something very different with how units are processed, I can only recommend that you use Burst Laser sparingly - a spell, an attack for an expensive, rare unit, a summon, etc. If you are on newer versions of the game, there are way more efficient ways of doing what this system is doing, so probably avoid this.

v3.2 fixed an issue where null BLAssignWeapon instances were being destroyed, which consequently was causing the Archmage's spell to malfunction after a few casts.
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


JASS:
    Burst Laser v3.2 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/

BurstLaser Engine

BurstLaser Assign Weapon

BL Module onAttack

JASS:
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
JASS:
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
JASS:
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.2 (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...
Level 12
Joined
Jun 12, 2010
Messages
413
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.
 
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.
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 :)
 
Level 14
Joined
Jul 1, 2008
Messages
1,314
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?
 
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:
Hey there.

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

JASS:
    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.

JASS:
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:
JASS:
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:
JASS:
            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.
 
Hey there.

In static method Interruption_Event "0" could be just saved as counter, instead of saving each loop run a new value.
.
Do you mean this line? call SaveInteger(hash, id, COUNTER_KEY, counter) and turn it into: call SaveInteger(hash, id, COUNTER_KEY, 0)?

JASS:
    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
Hmm, I guess not.

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?
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.

Make private what can be private.
Right. Made the destroy and ApplySfxAndDamage private. Can you have private statics as well?

JASS:
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 ^^
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.

lightning laser
^is this member needed? maybe it was from an older version, and also:
JASS:
private integer array data

private constant integer OFFSET = 0x100000
Ah, yes, vestigial variables. I'll remove 'em.

It's no harm to do, but technicaly seen nulling/falsing these is not needed, at least not for leaks:
JASS:
            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
Cool, I was unsure whether these had to be nulled. Code needs a trim anyway.

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".
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

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.
I'm not sure I follow. Do you mean the event registrations or the handler codes?

^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.
I also experience massive lag during tests.
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 :/

The other lightning strikes from an attack could also do damage than simply standing there for visuals. Then the attacks would be more interesting.
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.
 
Do you mean this line?
Yo, it could run once then, instead of in each loop. (just tiny optimization)

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.
Ah ok, sounds reasonable. Maybe, yes,..maybe it makes sense to loop through all lightnings onInterrupt and start fading them instantly?

Can you have private statics as well?
Sure. You could also go the other way around make private struct MyStruct and then make things explicitly public that need to be public.

Cool, I was unsure whether these had to be nulled.
If you're interested in reading some up some details Memory Leaks.

so I just use the smallest amount (.03125) and use an integer to count the intervals instead.
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.

I'm not sure I follow. Do you mean the event registrations or the handler codes?
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.

not experienced any significant lag
Hm oké, strange. But I definitly have them. Will try to report closer what maybe makes it, soon.
 
Yo, it could run once then, instead of in each loop. (just tiny optimization)
So this, then?
JASS:
        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

Ah ok, sounds reasonable. Maybe, yes,..maybe it makes sense to loop through all lightnings onInterrupt and start fading them instantly?
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[/icode], 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?

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

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.
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.

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.
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?

Hm oké, strange. But I definitly have them. Will try to report closer what maybe makes it, soon.
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]
 
So this, then?
Basicly yes. If you want you can also do a if statement first if counter is even bigger than 0.

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[/icode], 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?
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.:)

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.
" 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 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 see, sounds reasonable. It seems I limited the usage a bit in my thoughts.^^
 
Basicly yes. If you want you can also do a if statement first if counter is even bigger than 0.
Eh, I think I'll leave it as-is.

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.:)
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.
JASS:
        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

" 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.
"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.

I see, sounds reasonable. It seems I limited the usage a bit in my thoughts.^^
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
 
I'm not sure if it should be before or after the duration and fading variables.
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

If subtracting time has better performance
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. :(
 
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:
JASS:
       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:
Well if the SpecialEffectZ was lagging for you, then the dummy method is going to be worse because the initial version of the system used dummies - from a recycler. It was lag hell. I tried a new version with DummyRecycler and it's just the same result all over again.

I could try something different which is to have two dummies per instance - one for launch and one for impact - rather than two per laser. It could work, but just out of curiosity, I've attached the dummy version. Does it lag less or more than the AddSpecialEffctZ version?

EDIT: At the peak of the fight vs riflemen, I get about 20 fps drop if I use two dummies per instance rather than per laser. Far more manageable, but there will still be a need to modify certain special effects to not update their position when playing their death animations.
 

Attachments

  • Burst Laser v2.1.w3x
    194.9 KB · Views: 90
Last edited:
The map attached on post #21 uses two dummies per lightning effect. Since I don't get lag with AddSpecialEffectZ I wanted to see if the lag with the dummy units was less or worse.

v2.2 (attached here) uses up to two dummy units per instance, not lightning effect. The lag is less severe, but now the impact effects just jump around with the dummy. I need a 3D artist to help me understand how to modify effects to make it work with this, then it'll be viable.
 

Attachments

  • Burst Laser v2.2.w3x
    195.6 KB · Views: 82
Oh. ..

Have you tried to use enough dummies so it won't "jump" with the units, but not with creating mass units, but with using something like

Dummy Recycler v1.25
[Snippet] DelayedDummyRecycler
[Snippet] Unit Recycler

I would preload like 30-50 dummy units or so at init, and then would give it a try.
New creation is always pretty heavy, so it might also help a bit when done too often.

Have not read code now again how exactly the code manages effect creation, but too massive effect spam can also cause some FPS drop.
Maybe adding some sort of timeout, to create effects only each 0.1-0.2 seconds instead of each 0.03125000 seconds would maybe little help, too. (idk, if it would be the case for you)

Next week or so I can probably test again, too.
 
I am already using Dummy Recycler, though I didn't know you could preload dummies. Isn't Warcraft III bad with handling large numbers of units though? (Most of the stuff I do, I plan them with the idea that they would be usable in an altered melee. So, large armies.).

v2.1 uses a dummy for every lightning effect with Dummy Recycler.
 
v2.1 lags a lot when your have a bunch of units though. The cluster of riflemen to the right is meant to stress-test what an actual melee combat may contain (even if it's a bit much). There is a significant amount of lag that is reduced when I use only two dummies per instance. Idk, I can't really tell if the lag is just me expecting too much of my system and using 2 dummies per laser is enough.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Hey SpellBound I've a suggestion

Instead of attaching the configuration data (which is based on the unittype) to uDex when a unit enters the map, I guess it would be better to just attach them directly to the unittypeId and then just run the configurations once on map init.
Example:
JASS:
library BurstLaserConfiguration

    private struct Init extends array

        static method getIndex takes integer unittype returns integer
           //convert the unittype to a small integer so you can use them on arrays
        endmethod

        static method onInit takes nothing returns nothing
            local integer id = getIndex('hrif')

            set vars[id] = value
            //...

            set id = getIndex('hpea')
            set vars[id] = value
            //...

            //Same for other unittypes

        endmethod

    endstruct

endlibrary


Or better if you can even attach the configuration to an abiliy instead of the unittype or unituserdata so that a unit can have multiple laser attacks at once.
 
Huh, I was unaware that you could actually do that. I think someone else tried to explain that method to me before but I didn't get it until now :p
It would be cool to have multiple laser attacks, tho there is one issue think could arise from this: if you want to modify the laser (like say you want to increase the colour of a laser if the unit has a specific buff), wouldn't that change the laser for all units of the same type/with the same ability instead of working individually?
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
It would be cool to have multiple laser attacks, tho there is one issue think could arise from this: if you want to modify the laser (like say you want to increase the colour of a laser if the unit has a specific buff), wouldn't that change the laser for all units of the same type/with the same ability instead of working individually?
There's a way to handle it. Upon the lasers' creation, you're gonna apply the default color configuration (The one set on map init) and if the user wants to manipulate the color (or damage for example) of the lasers of a certain unit, you could just give them methods for editing the stats of a specific laser instance, then they can freely manipulate the stats dynamically most preferably upon creation since you've already provided them the events. Now, what you have to add is another method to retrieve the current fired/started lasers. But of course you could think of another design for this, It's just an example.
 
@KILLCIDE MAJOR system rework. I stess-tested this and it's not leaking anymore. Would really appreciate it if one of the code mods could take a look at it :)

PS: I use a unit group to store dummies and recycle them afterwards. The group is them destroyed because it's per-attack-instance. Should I use a hashtable instead? Is group creation/destruction slower than hashtables?
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
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

Suggestions

  • A brief description of the configurables required to setup a weapon would be appreciated. I know some of them are pretty self explanatory, but others are not (especially the booleans).
  • For Setup Arsenal, look into modules.
  • You should really look into encapsulation. Pretty much everything is public and it just feels wrong. Like here "...These variables are meant to be readonly, so DO NOT SET THEM TO ANYTHING." Why not make them readonly or private so that they will get an error if they attempt to do something?
  • Delete the Unused Stuff folder. There is really no point of that being there :p

Status


Approved
 
I completely forgot to reply to this lol

  • A brief description of the configurables required to setup a weapon would be appreciated. I know some of them are pretty self explanatory, but others are not (especially the booleans).
I'll see about updating the Setup Arsenal with proper documentation. They're in Assign Weapon as opposed to Setup Arsenal because the latter trigger was created at a later date. Assign Weapon was originally a combination of both.

  • For Setup Arsenal, look into modules.
Can you give me a brief example of module would work for Setup Arsenal? I've never worked with modules before.

  • You should really look into encapsulation. Pretty much everything is public and it just feels wrong. Like here "...These variables are meant to be readonly, so DO NOT SET THEM TO ANYTHING." Why not make them readonly or private so that they will get an error if they attempt to do something?
Hmm, I'll do some research.

  • Delete the Unused Stuff folder. There is really no point of that being there :p
Well they're mostly there a backups for overlooked ideas but I guess I can remove them for uploading to the Hive.

Thanks for the review!
 
I spent the day trying to update it but for some reason I'm getting quite severe lag with it even with all dummies removed. I'm hardly an expert on optimisation, but I'd say this system should be used sparingly at best given how much inherent overhead is present within it. The number of calculations and if checks it contain just so it can be more customisable is a bit absurd, now that I'm taking a new look at it. That, and the fact that it has a baked-in dependency on Damage Engine is not exactly a good thing, especially if you want to use something else (or no DDS at all, since that's a possibility with 1.31 now).

I'll keep chipping away at it, but I can't promise that anything will come of it. The current version seems to be stable enough as it is so it might be best to not update it at all and avoid accidentally breaking everything.
 
Level 9
Joined
Dec 16, 2017
Messages
339
It's from the test map, it was the first time testing it, as i want to make summon units that behave like locust swarm, but only 1 unit at a time, and add one of these effects to his attack.
 
Level 9
Joined
Dec 16, 2017
Messages
339
After i do 5-6 casts, or after 10-15 seconds of game time if i don't cast it

But also when the map starts, i have 1 text with 1 error, but as i cast, or i don't and time elapses, i get a full screen of them like showed above
 
Top