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

Tainted Fountain v1.02a

  • Like
Reactions: Kam
Requires Jass NewGen Pack with the most recent Jasshelper.
Spell should be MUI and leakless.

Tainted Fountain
Conjures a fountain filled with evil energy that deals 10 damage every second to nearby enemy units and converts damage done into mana at a rate of 1.5 points of mana per damage. Mana is used to heal friendly units. If the fountain gains excess mana, it will use that mana to further damage enemy units at a rate of 0.25 damage per extra mana point.

Lasts 30 seconds.
Level 1 - Heals friendly units at a rate of 0.75 hit points per mana, 20 hit points healed every second.
Level 2 - Heals friendly units at a rate of 1 hit point per mana, 30 hit points healed every second.
Level 3 - Heals friendly units at a rate of 1.25 hit point per mana, 40 hit points healed every second.

Required Libraries:
- TimerUtils
- SoundUtils (along with Stack)

Optional Libraries:
- GroupUtils

Summons a fountain that deals damage to enemy units. Damage done by the fountain is converted into mana. The fountain's mana is used to heal friendly units at a limit per second. Excess mana is used to damage enemy units.

JASS:
//==========================================================================================
// Tainted Fountain v1.02a by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
//  - TimerUtils
//  - SoundUtils
//  * GroupUtils
//##########################################################################################
// Importing:
//  1. Copy the ability, Tainted Fountain.
//  2. Copy the unit, Tainted Fountain
//  3. Implement the required libraries.
//  4. Copy this trigger.
//  5. Configure the spell.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Notes:
//  - The spell will target wards if they aren't mechanical
//  - The spell will bug if the fountain gets moved from where it is placed
//==========================================================================================

scope TaintedFountain initializer Init

    native UnitAlive takes unit id returns boolean // Don't touch this
    
//==========================================================================================
//                              CONSTANTS
//==========================================================================================

    globals 
    
        // General Spell Settings
        private constant integer    SPELL_ID        = 'A000'    // Raw id of the spell.
        private constant integer    FOUNTAIN_ID     = 'o000'    // Raw id of the Tainted Fountain unit
        private constant integer    FOUNTAIN_ANIM   = 2         // The animation index the fountain will play  
        private constant integer    BUFF_ID         = 'BTLF'    // Buff given to the fountain for expiration timer
        private constant real       TIMER_LOOP      = 0.1       // Determines how often the timer will loop        
        private constant real       FOUNTAIN_FACE   = 270       // Determines the facing angle of hte fountain
        
        // Damage settings
        private constant attacktype ATK_TYPE        = ATTACK_TYPE_NORMAL
        private constant damagetype DMG_TYPE        = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WPN_TYPE        = null
        
        // Special Effect Settings
        private constant boolean    PRELOAD         = true      // Determines if the effects and fountain unit should be preloaded or not.
        private constant string     HEAL_SFX        = "Abilities\\Spells\\Other\\Drain\\DrainCaster.mdl" // The effect that is attached to units healing from the spell
        private constant string     HEAL_ATTACH     = "origin"  // Attachment point for HEAL_SFX
        private constant string     DRAIN_SFX       = "Abilities\\Spells\\Other\\Drain\\DrainTarget.mdl" // The effect that is attached to units getting drained from the spell
        private constant string     DRAIN_ATTACH    = "origin"  // Attachment point for DRAIN_SFX
        private constant string     EXCESS_SFX      = "Abilities\\Spells\\Undead\\DarkRitual\\DarkRitualTarget.mdl" // The sfx that will be played on the fountain when it has too much mana.
        private constant string     EXCESS_ATTACH   = "origin"  // Attachment point for EXCESS_SFX
        private constant real       DELAY_DUR       = 1.5       // Duration before EXCESS_SFX will play again
        private constant string     LIGHTNING       = "DRAL"    // Lightning path
        private constant real       OFFSET          = 0.        // The offset that lightning will be created.
        
        // Vertex color for lightning. Note that they are expressed in decimals.
        private constant real       RED             = 1 
        private constant real       GREEN           = 1 
        private constant real       BLUE            = 1      
        private constant real       ALPHA           = 1 
        
        // Sound settings:
        private constant string     SOUND_PATH      = "Abilities\\Spells\\Other\\ANrl\\FountainOfLifeLoop1.wav" // The sound path that will be looped
        private constant integer    SOUND_DURATION  = 3315      // The duration of the sound
        
    endglobals    
    
//==========================================================================================
//                              CONFIGURATIONS
//==========================================================================================
    
    // The spell's area of effect
    private constant function Area takes integer lvl returns real 
        return 500.
    endfunction    
    
    // Determines how long a fountain will last
    private constant function Duration takes integer lvl returns real 
        return 30.
    endfunction
    
    // Damage done to enemy units per second
    private constant function Damage takes integer lvl returns real 
        return 10.
    endfunction
    
    // Determines how much mana the fountain will gain from one point of damage
    private constant function DamageRate takes integer lvl returns real 
        return 1.5
    endfunction
    
    // Determines how much a unit can be healed per second
    private constant function Heal takes integer lvl returns real 
        return 10. + 10*lvl
    endfunction
    
    // Determines how much life the fountain can heal from one mana point
    private constant function ManaRate takes integer lvl returns real
        return 0.5 + 0.25*lvl
    endfunction
    
    // Determines how much damage the fountain will deal from excess mana
    private constant function ExcessManaRate takes integer lvl returns real 
        return 0.25
    endfunction
    
    // Targets that are affected by the fountain, both healing and damaging 
    private function AffectedTargets takes unit u, player owner returns boolean
               // General settings 
        return UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MECHANICAL) and /* 
        
               // Settings to damage enemy units
            */ (IsUnitEnemy(u, owner) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) or /*
            
               // Settings to heal allied units 
            */ (IsUnitAlly(u, owner) and GetWidgetLife(u) < GetUnitState(u, UNIT_STATE_MAX_LIFE))
    endfunction
    
    // Targets that get hurt by excess mana damage
    private function ExcessManaAffectedTargets takes unit u, player owner returns boolean
        return UnitAlive(u) and IsUnitEnemy(u, owner) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
    endfunction
    
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================

    private keyword Fountain
    
    globals
        private location l = Location(0, 0)     
        private group g // Used if GroupUtils isn't present
    endglobals
    
    private function GetPointZ takes real x, real y returns real
        call MoveLocation(l, x, y)
        return GetLocationZ(l)
    endfunction
    
    // Main struct that affects the target units
    private struct Target
        unit targ
        Fountain f                      // Gets info from fountain
        real delay = 0                  // The count that will determine when to play the EXCESS_SFX
        effect sfx = null               // Effect attached to target unit.
        string path = DRAIN_SFX         // The path for the sfx. Changes if the targ unit is an ally or an enemy.
        string attach = DRAIN_ATTACH    // The attachment point for the sfx. Changes if the targ unit is an ally or an enemy.       
        lightning light                 // Lightning that will be attached to the targ unit and fount unit.
        boolean show = false            // Determines when to show the special effects.
        timer t
        
        static thistype temp            // Used to pass data for group enumerations
        static real TempReal            // Used to pass the excess mana damage
        
        static method damageUnits takes nothing returns boolean
            local unit u = GetFilterUnit()
            if ExcessManaAffectedTargets(u, GetOwningPlayer(temp.f.u)) then
                call UnitDamageTarget(temp.f.u, u, TempReal, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
            endif
            set u = null
            return false
        endmethod
        
        method showEffects takes nothing returns nothing
            set .sfx = AddSpecialEffectTarget(.path, .targ, .attach)                                      
            call SetLightningColor(.light, RED, GREEN, BLUE, ALPHA) 
            set .show = true
        endmethod
        
        method hideEffects takes nothing returns nothing
            call DestroyEffect(.sfx)
            set .sfx = null
            call SetLightningColor(.light, RED, GREEN, BLUE, 0)
            set .show = false
        endmethod
        
        // Method deals with the effects for any targets affected by the spell 
        static method onLoop takes nothing returns nothing 
            local thistype this = GetTimerData(GetExpiredTimer())
            local real tx 
            local real ty 
            local real tz 
            local real life
            local real mana
            local real max 
            local real need // Mana needed for healing
            local real gain 
            
            if UnitAlive(.targ) and UnitAlive(.f.u) then
            
                if IsUnitInRange(.targ, .f.u, Area(.f.lvl)) then
                
                    // Only set variables if the target is in range
                    
                    // Target unit settings
                    set tx = GetUnitX(.targ)
                    set ty = GetUnitY(.targ)
                    set tz = GetUnitFlyHeight(.targ) + GetPointZ(tx, ty) + OFFSET
                    set life = GetWidgetLife(.targ)
                    
                    // Fountain unit settings
                    set mana = GetUnitState(.f.u, UNIT_STATE_MANA)
                    set max = GetUnitState(.f.u, UNIT_STATE_MAX_MANA)
                    
                    // Healing effects here
                    if IsUnitAlly(.targ, GetOwningPlayer(.f.u)) then
                    
                        set need = Heal(.f.lvl) / ManaRate(.f.lvl) * TIMER_LOOP
                        
                        // Don't do anything if the fountain does not have any mana or the target has full health
                        if mana > 0.405 and life < GetUnitState(.targ, UNIT_STATE_MAX_LIFE) then
                            if mana - need > 0 then 
                                // Has enough mana, don't worry about healing over
                                call SetWidgetLife(.targ, life + Heal(.f.lvl) * TIMER_LOOP) 
                            else
                                // Can only heal with the mana that the fountain has
                                call SetWidgetLife(.targ, life + Heal(.f.lvl) * mana / need * TIMER_LOOP)
                            endif
                            
                            // Reduces mana based on how much the target was healed by
                            call SetUnitState(.f.u, UNIT_STATE_MANA, mana - (GetWidgetLife(.targ)-life) / ManaRate(.f.lvl))
                            
                            call MoveLightningEx(.light, true, .f.x, .f.y, .f.z, tx, ty, tz)                            
                            if not .show then
                                call .showEffects()
                            endif 
                            
                        // Don't show effects if the unit doesn't need healing
                        elseif .show then
                            call .hideEffects()
                        endif
                        
                    else // Damaging effects here         
                    
                        call UnitDamageTarget(.f.u, .targ, Damage(.f.lvl)*TIMER_LOOP, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
                        set gain = DamageRate(.f.lvl)*(life-GetWidgetLife(.targ))
                        
                        
                        if mana + gain < max then 
                            // No excess mana, give mana normally to the fountain
                            call SetUnitState(.f.u, UNIT_STATE_MANA, mana+gain)                            
                        else 
                            // Damage enemy units with excess mana damage   
                        
                            if mana < max then
                                call SetUnitState(.f.u, UNIT_STATE_MANA, max)
                            endif
                            
                            set temp = this
                            set TempReal = ExcessManaRate(.f.lvl) * (mana + gain - max) // Stores the damage to be dealt                            
                            static if LIBRARY_GroupUtils then
                                call GroupEnumUnitsInArea(ENUM_GROUP, .f.x, .f.y, Area(.f.lvl), Filter(function thistype.damageUnits))
                            else
                                call GroupEnumUnitsInRange(g, .f.x, .f.y, Area(.f.lvl), Filter(function thistype.damageUnits))
                            endif
                            
                            // This tells the spell when to start counting for delay between playing EXCESS_SFX
                            if .delay == 0 then
                                call DestroyEffect(AddSpecialEffectTarget(EXCESS_SFX, .f.u, EXCESS_ATTACH))
                                set .delay = .01 
                            endif
                            
                        endif
                        
                        call MoveLightningEx(.light, true, .f.x, .f.y, .f.z, tx, ty, tz)
                        if not .show then
                             call .showEffects()
                        endif
                        
                        // Done to prevent EXCESS_SFX from playing too often.
                        if .delay > 0 then 
                            if .delay == .01 then
                                set .delay = TIMER_LOOP
                            else
                                set .delay = .delay + TIMER_LOOP
                            endif
                        endif
                        if .delay > DELAY_DUR then
                            set .delay = 0
                        endif
                        
                    endif
                    
                // Hide effects if they are shown and if the target is out of range
                elseif .show then
                     call .hideEffects()
                endif
                
            else
                // This is done in case the target revives so that the spell will properly target it again
                if not UnitAlive(.targ) then
                    call GroupRemoveUnit(.f.affect, .targ)
                endif                
                
                if .sfx != null then
                    call DestroyEffect(.sfx)
                endif
                call DestroyLightning(.light)

                call ReleaseTimer(.t)
                call .destroy()
            endif

        endmethod 
        
        static method create takes unit t, Fountain f returns thistype
            local thistype this = thistype.allocate()
            set .targ = t
            
            set .f = f            
            call GroupAddUnit(f.affect, t)
            
            // Create the ligtning and hides it
            set .light = AddLightning(LIGHTNING, true, f.x, f.y, GetUnitX(t), GetUnitY(t))
            call SetLightningColor(.light, RED, GREEN, BLUE, 0)
            
            if IsUnitAlly(.targ, GetOwningPlayer(.f.u)) then
                set .path = HEAL_SFX 
                set .attach = HEAL_ATTACH
            endif   
            
            set .t = NewTimer()
            call SetTimerData(.t, this)
            call TimerStart(.t, TIMER_LOOP, true, function thistype.onLoop)
            return this
        endmethod
    endstruct
    
    private struct Fountain
        unit u                          // Refers to the fountain unit that is created
        integer lvl 
        real x         
        real y         
        real z        
        group affect                    // Store all the units currently affected by the spell.        
        sound sndLoop                   // Sound that loops when fountain is alive
        timer t                         // Timer used to do looping actions by the spell
        
        private static thistype temp    // Used to pass data for group enumerations
        
        // Creates the Target struct for affected units
        static method filter takes nothing returns boolean 
            local unit u = GetFilterUnit()
            if not IsUnitInGroup(u, temp.affect) and AffectedTargets(u, GetOwningPlayer(temp.u)) then
                call Target.create(u, temp)
            endif
            set u = null
            return false
        endmethod
        
        // Periodic method to enumerate units in range of the fountain
        static method onLoop takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            if UnitAlive(.u) then
            
                set temp = this
                static if LIBRARY_GroupUtils then
                    call GroupEnumUnitsInArea(ENUM_GROUP, .x, .y, Area(.lvl), Condition(function thistype.filter))  
                else
                    call GroupEnumUnitsInRange(g, .x, .y, Area(.lvl), Condition(function thistype.filter))  
                endif
                
            else // Fountain is dead, clean up struct stuff              
                
                static if LIBRARY_GroupUtils then
                    call ReleaseGroup(.affect)
                endif
                call ReleaseSound(.sndLoop)
                call ReleaseTimer(.t)
                call .destroy()
                
            endif
            
        endmethod
        
        static method create takes unit c, real x, real y returns thistype
            local thistype this = thistype.allocate()
            set .lvl = GetUnitAbilityLevel(c, SPELL_ID)   
            
            static if LIBRARY_GroupUtils then
                set .affect = NewGroup()  
            else
                if .affect == null then
                    set .affect = CreateGroup()
                else
                    call GroupClear(.affect)
                endif
            endif
            
            // Fountain settings
            set .u = CreateUnit(GetOwningPlayer(c), FOUNTAIN_ID, x, y, FOUNTAIN_FACE)
            call UnitAddType(.u, UNIT_TYPE_SUMMONED)
            call SetUnitAnimationByIndex(.u, FOUNTAIN_ANIM) 
            set .x = GetUnitX(.u)
            set .y = GetUnitY(.u)
            set .z = GetUnitFlyHeight(.u) + GetPointZ(.x, .y) + OFFSET
            call UnitApplyTimedLife(.u ,BUFF_ID, Duration(.lvl))
            
            // Sound creation
            set .sndLoop = RunSoundAtPoint(DefineSound(SOUND_PATH, SOUND_DURATION, true, true), .x, .y, 0)
            
            // Timer settings
            set .t = NewTimer()
            call SetTimerData(.t,this)
            call TimerStart(.t, TIMER_LOOP, true, function thistype.onLoop)

            return this
        endmethod
    endstruct
    
    private function SpellActions takes nothing returns boolean
        if GetSpellAbilityId() == SPELL_ID then
            call Fountain.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
        endif
        return false
    endfunction
    
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function SpellActions))
        
        static if not LIBRARY_GroupUtils then
            set g = CreateGroup()
        endif
        
        static if PRELOAD then
            call Preload(HEAL_SFX)
            call Preload(DRAIN_SFX)
            call Preload(EXCESS_SFX)
            call Preload(LIGHTNING)
            call RemoveUnit(CreateUnit(Player(15),FOUNTAIN_ID,0,0,0))
        endif
        
        set t = null
    endfunction
endscope

1.02a
Tried to make code even easier to read
Removed a useless boolean member
Makes the lightning hidden initially

v1.02
Made code easier to read.
No longer requires xe system
GroupUtils is optional
Made it easier to configure spell's targets
Uses one global location to find a point's z

v1.01a
Added more comments to the structs.

v1.01
Moved lightnings with MoveLightningEx however retained the same way to make lightnings invisible.

v1.00
Released.


Please give me credits if you use this spell.
Credits to:
~ Vexorian for TimerUtils
~ RisingDusk for GroupUtils and SoundUtils
~ scorpion182 for Water Vortex which showed me a way to pass info from a struct to a static method
~ D4RK_G4ND4LF for advice on lightnings

Please give any kind of feedback on how I could improve the spell.

Keywords:
fountain,well,corrupt,tainted,heal
Contents

TaintedFountain (Map)

Reviews
14:58, 2nd Feb 2010 TriggerHappy: Clever usage of the public init trig to avoid WE errors =P Anyways, all seems good. Approved.

Moderator

M

Moderator

14:58, 2nd Feb 2010
TriggerHappy:

Clever usage of the public init trig to avoid WE errors =P
Anyways, all seems good. Approved.
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
I'd recommend using MoveLightningEx (includes height values)
you'll also need GetLocationZ for it
this way you can add something like 35 to make the lightning be centered on the unit in all aspects including height

and some knowledge about lightnings:
Create lightning from 0/0 to 0/0
it will have a length of 0 and won't be rendered
it will be rendered again as soon as you expand it
 
Level 17
Joined
Mar 17, 2009
Messages
1,349
Well anachron my friend i must agree and disagree with you... it's not good but it's also not THAT bad as you make it seem :p

Anyways yeah since Anachron has a point... you should start dividing things into blocks as indentation alone is not useful to make a code easily readable...
How? Use a mix of skipping lines, indentation, & comment-separators if we could call it that (shown in the example below)

Check out this script for a spell of mine (this is not for advertisement's sake but for the sake of giving an example) and how it's neatly indented & blocked:
JASS:
scope Balminess

globals
    private integer array DAMAGE_INTERVALS
    private real array DAMAGE_INTERVAL_TIME_GAP
    private real array BASE_DAMAGE
    private real array DAMAGE_MULTIPLIER
endglobals

//===========================================================================
        
    ////////////////////////////////////
    //          ADJUSTABLES           //
    ////////////////////////////////////
      
    globals
    
        // --- IDs ---
        private constant integer    ABILITY_ID                      = 'A008'            // The ID of the Balminess ability
        private constant integer    BUFF_ABILITY_ID                 = 'A009'            // The ID of the BA_Burn ability
        private constant integer    BUFF_ID                         = 'B002'            // The ID of the BA_Burn buff
        
        // --- Missile settings ---
        private constant string     MISSILE_MODEL                   = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"       // The model of the missile
        private constant real       MISSILE_SPEED                   = 30.               // The speed of the missile
        private constant real       MISSILE_COLLISION_RANGE         = 30.               // The collision range of the missile
        private constant real       MISSILE_HEIGHT                  = 80.               // The height of missile
        private constant real       MISSILE_SIZE                    = 1.                // The size of the missile
        private constant real       MISSILE_OFFSET                  = 0.                // The missile's offset from the caster at creation
        
        // --- Damage and attack types settings ---
        private constant attacktype ATTACK_TYPE                     = ATTACK_TYPE_CHAOS // The attack type of the damage dealt
        private constant damagetype DAMAGE_TYPE                     = DAMAGE_TYPE_FIRE  // The damage type of the damage dealt
        
        // --- Special effects settings ---
        private constant string     TARGET_SFX                      = "Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireDamage.mdl"  // The special effect attached to the target being damaged
        private constant string     TARGET_SFX_ATTACHMENT_PT        = "chest"           // The attachment point for the special effect on the target being damaged
        
        // --- Core settings ---
        private constant real       TIMEOUT                         = .03               // The time gap at which the main timer runs
        
    endglobals

    private function Setup takes nothing returns nothing
        
        // General Setup note:
        //      the array integer refers to the level of the intended ability
        
        
        // --- Damage properties ---
        
        // Damage formula would be:
        //      BASE_DAMAGE + (DAMAGE_MULTIPLIER x Target's armor)
        
        // The number of intervals for the damage to occur
        set DAMAGE_INTERVALS[1] = 10
        set DAMAGE_INTERVALS[2] = 10
        set DAMAGE_INTERVALS[3] = 10
        set DAMAGE_INTERVALS[4] = 10
        
        // The number of the time gap between each damage interval
        set DAMAGE_INTERVAL_TIME_GAP[1] = 1.
        set DAMAGE_INTERVAL_TIME_GAP[2] = 1.
        set DAMAGE_INTERVAL_TIME_GAP[3] = 1.
        set DAMAGE_INTERVAL_TIME_GAP[4] = 1.
        
        // The base damage dealt regardless of the target's armor
        set BASE_DAMAGE[1] = 20.
        set BASE_DAMAGE[2] = 20.
        set BASE_DAMAGE[3] = 20.
        set BASE_DAMAGE[4] = 20.
        
        // The factor to which the targer's armor is multiplied and whose product is then dealt in damage
        set DAMAGE_MULTIPLIER[1] = 1.
        set DAMAGE_MULTIPLIER[2] = 2.
        set DAMAGE_MULTIPLIER[3] = 3.
        set DAMAGE_MULTIPLIER[4] = 4.
        
    endfunction

    ////////////////////////////////////
    //        ADJUSTABLES END         //
    ////////////////////////////////////

//===========================================================================
    
private struct BA

//= Struct Variables ========================================================

    unit tr     // Triggering unit
    player pt   // Owner of the triggering unit
    integer lvl // Level of the ability
    
    unit ta     // Target unit
    real tx     // Target's X
    real ty     // Target's Y
    effect e    // Effect attached to target

    unit m      // Missile
    
    real f      // Armor info
    
    real tco    // Timer counter
    integer ico // Interval counter
    
    timer time  // Timer variable
    
    debug boolean bool
    
//= destroy ================================================================

    private method destroy takes nothing returns nothing
        call PauseTimer(this.time)
        call FlushTimerUserData(this.time)
        call DestroyTimer(this.time)
        
        call UnitRemoveAbility(this.ta, BUFF_ID)
        
        call DestroyEffect(this.e)
        
        set this.tr     = null
        set this.pt     = null
        set this.ta     = null
        set this.e      = null
        set this.m      = null
        set this.time   = null
        set this.ico    = 0
        
        debug call BJDebugMsg("|cffc3dbffBalminess:|r Struct instance |cffff0000" + I2S(this) + "|r destroyed")
        debug call BJDebugMsg(" ")
        debug set this.bool = false
        
        call .deallocate()
    endmethod
    
//= Damage Over Time ========================================================

    private static method DoT takes nothing returns nothing
        
    // --- Announcing struct instance and running time counter ---
        local BA d = BA(GetTimerUserData(GetExpiredTimer()))
        set d.tco = d.tco + TIMEOUT
        
    // --- Debug ---
        debug if d.bool == false then
        debug      call BJDebugMsg("|cffc3dbffBalminess:|r Target hit")
        debug      call BJDebugMsg("|cffffcc00Struct instance:|r |cffff0000" + I2S(d) + "|r")
        debug      call BJDebugMsg(" ")
        debug      set d.bool = true
        debug endif
        
    // --- Checking if the target is dead to terminate the instance ---
        if IsUnitType(d.ta, UNIT_TYPE_DEAD) then
            debug call BJDebugMsg("|cffc3dbffBalminess:|r Target died")
            debug call BJDebugMsg("|cffffcc00Struct instance:|r |cffff0000" + I2S(d) + "|r")
            debug call BJDebugMsg(" ")
            
            set d.tco = 0.
            call d.destroy()
            return
        endif
        
    // --- Checking if it's time to damage ---
        if d.tco >= DAMAGE_INTERVAL_TIME_GAP[d.lvl] then
            set d.tco = 0.
            set d.ico = d.ico + 1
            
            debug call BJDebugMsg("|cffc3dbffBalminess:|r Target damaged -- Damage instance: " + I2S(d.ico))
            debug call BJDebugMsg("|cffffcc00Struct instance:|r |cffff0000" + I2S(d) + "|r")
            debug call BJDebugMsg(" ")
            
    // --- Getting target's armor ---
            if GetUnitArmor(d.ta) > 0. then
                set d.f = GetUnitArmor(d.ta)
            else
                set d.f = 0.
            endif
    
    // --- Damaging target ---
            call UnitDamageTarget(d.tr, d.ta, (BASE_DAMAGE[d.lvl] + (DAMAGE_MULTIPLIER[d.lvl] * d.f)), true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
            
    // --- Checking if all intervals ran to terminate the instance ---
            if d.ico >= DAMAGE_INTERVALS[d.lvl] then
                debug call BJDebugMsg("|cffc3dbffBalminess:|r All intervals ran")
                debug call BJDebugMsg("|cffffcc00Struct instance:|r |cffff0000" + I2S(d) + "|r")
                debug call BJDebugMsg(" ")
                
                call d.destroy()
            endif
        endif
    endmethod
    
//= Missile's Motion ========================================================

    private static method Loop takes nothing returns nothing
    
    // --- Announcing struct instance ---
        local BA d = BA(GetTimerUserData(GetExpiredTimer()))
    
    // --- Debug ---
        debug if d.bool == false then
        debug      call BJDebugMsg("|cffc3dbffBalminess:|r Missile is moving")
        debug      call BJDebugMsg("|cffffcc00Struct instance:|r |cffff0000" + I2S(d) + "|r")
        debug      call BJDebugMsg(" ")
        debug      set d.bool = true
        debug endif
        
    // --- Moving missile ---
        set d.tx = GetUnitX(d.ta)
        set d.ty = GetUnitY(d.ta)
        call MoveMissileDT(d.m, d.tx, d.ty, MISSILE_SPEED)
        
    // --- Checking if missile collided with target to detroy missile ---
        if GetDistanceBetweenPointsDT(GetUnitX(d.m), GetUnitY(d.m), d.tx, d.ty) <= MISSILE_COLLISION_RANGE then
            call CastTargetDT(d.m, d.ta, BUFF_ABILITY_ID, "curse", d.lvl)
            call DestroyMissileDT(d.m)
            set d.e = AddSpecialEffectTarget(TARGET_SFX, d.ta, TARGET_SFX_ATTACHMENT_PT)
            
            debug set d.bool = false
    
    // --- Running damage over time ---
            call PauseTimer(d.time)
            call TimerStart(d.time, TIMEOUT, true, function BA.DoT)
        endif
    endmethod
    
//= Initial Actions =========================================================

    private static method Create takes nothing returns nothing
    
    // --- Allocating new struct instance ---
        local BA d = BA.allocate()
    
    // --- Debug ---
        debug call BJDebugMsg("|cffc3dbffBalminess:|r Initiated")
        debug call BJDebugMsg("|cffffcc00Struct instance:|r |cffff0000" + I2S(d) + "|r")
        debug call BJDebugMsg(" ")
        
    // --- Setting some variables ---
        set d.tr = GetTriggerUnit()
        set d.pt = GetOwningPlayer(d.tr)
        set d.lvl = GetUnitAbilityLevel(d.tr, ABILITY_ID)
        
        set d.ta = GetSpellTargetUnit()
        
        set d.m = CreateMissileDT(d.pt, GetUnitX(d.tr), GetUnitY(d.tr), GetUnitFacing(d.tr), MISSILE_OFFSET, MISSILE_MODEL, "origin", MISSILE_HEIGHT, MISSILE_SIZE)
    
    // --- Creating and running timer to move missile ---
        set d.time = CreateTimer()
        call SetTimerUserData(d.time, integer(d))
        call TimerStart(d.time, TIMEOUT, true, function BA.Loop)
    endmethod

//= Condition and Initializer ===============================================

    private static method ConditionCheck takes nothing returns boolean
        return GetSpellAbilityId() == ABILITY_ID
    endmethod

    private static method onInit takes nothing returns nothing
        call SpellInitDT(EVENT_PLAYER_UNIT_SPELL_EFFECT, function BA.Create, function BA.ConditionCheck)
        
        call Setup()
        
        call PreloadSpecialEffectDT(MISSILE_MODEL)
        call PreloadSpecialEffectDT(TARGET_SFX)
    endmethod

endstruct
    
endscope

EDIT:
even inside the same function, when you have a different set of code that is responsible for doing something (ex: moving the unit) then another set that does another thing (ex: healing the unit), skip a line between the 2 different sets... & if you give it a subtitle in comment form the same way as in the example that would be actually awesome :)
 
Top