• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Immolation Fireball (V 1.24)


update 1.11
12:00 AM 2/25/2012

update 1.12
12:15 AM 3/4/2012

update 1.13
7:35 PM 3/5/2012

update 1.20
2:44 AM 3/7/2012

update 1.21
1:00 PM 3/13/2012

update 1.22
10:25 PM 3/14/2012

update 1.23
5:45 PM 3/17/2012

update 1.24
11:00 PM 3/19/2012



Learn
Learn Immolation Fireball [Level 1]
Learn Immolation Fireball [Level 2]
Learn Immolation Fireball [Level 3]
Hurls a fireball at a target location which explodes creating a fire nova, damaging enemy caught in the nova and burning them for a time duration.

Level 1 - 100 fire damage and 1 per second fire damage for 10 seconds.
Level 2 - 150 fire damage and 2 per second fire damage for 15 seconds.
Level 3 - 200 fire damage and 3 per second fire damage for 20 seconds.

[Deals 75% only to buildings]
Normal
Immolation Fireball - [Level 1]
Hurls a fireball at a target location which explodes creating a fire nova, damaging enemy caught in the nova dealing 100 fire damage and 1 per second fire damage for 10 seconds.

[Deals 75% only to buildings]

Immolation Fireball - [Level 2]
Hurls a fireball at a target location which explodes creating a fire nova, damaging enemy caught in the nova dealing 150 fire damage and 2 per second fire damage for 15 seconds.

[Deals 75% only to buildings]

Immolation Fireball - [Level 3]
Hurls a fireball at a target location which explodes creating a fire nova, damaging enemy caught in the nova dealing 200 fire damage and 3 per second fire damage for 20 seconds.

[Deals 75% only to buildings]



Icon by: bigapple90



Element of Water
Cokemonkey11
Anachron
Ciebron
Deuterium
Pharaoh_

...

and others who helped me in my posts


Bribe: helped me a lot. [ he is a JASS open book :) ]
Luorax: Hints me to the use of Alloc and helped me with filtering units
Magtheridon96: Told me how to use Alloc



- may add Destructible targets
- Added Damage Over Time (DoT)
- Parbola Missile notes: the check of missile destination is based on X/Y not Z, also time is function of X/Y and Z is based on X/Y. Casting the spell too near from the caster will cause high Z variation while Z variation will not be noticed if cast normally .



Due to the lots of change a trigger for each version is implemented in the map.

V 1.00
- Release


- reduced the number of units used to create the nova ring to reduce lagging
- fixed leak problem:
if DistanceBetweenPoints(GetUnitLoc(dat.Missile),dat.CastPoint) < M_DIST then became

JASS:
set loc = GetUnitLoc(dat.Missile)
if DistanceBetweenPoints(loc,dat.CastPoint) < M_DIST then
call RemoveLocation(loc)
- added the option to select target: flying, ground and structure
- added damage factor for units and buildings


- removed .evaluate from set prevDmg = NovaDamage.evaluate(al - 1) and
- removed .evaluate from set prevRad = NovaRadius.evaluate(al - 1)
- removed = CreateGroup() from
JASS:
private struct NovaData
    group dGroup
to prevent the creation of 8190 groups at map start-up (initialization) and added the function set dat.dGroup = CreateGroup()
to the static method NovaCreate takes unit caster , real damage , real radius, real posX, real posY returns NovaData
- Added 2 function
JASS:
//Preload the dummy units to reduce lag on the first time cast
call RemoveUnit(CreateUnit(Player(12),'e000',0.,0.,0.))
call RemoveUnit(CreateUnit(Player(12),'e001',0.,0.,0.))
- Added the "BoundSentinel" to prevent game crash if the unit goes outside the map bounds


- added call DestroyGroup(dat.dGroup) to prevent leak from this group
- used the 'Alloc' module for better struct instances recycling by adding implement Alloc after declaring the struct members
- all functions that were in both onDestroy method of MislData and NovaData are now moved to 'MissileMove' and 'NovaExpand' respectively to work properly with the Alloc
- the member of the struct NovaData: unit array Nova[NOVA_SIZE] became static and changed dat.Nova[i] to Nova[(dat-1)*NOVA_SIZE + i] to work with static properly


- removed private boolexpr fFilter as it is useless
- changed NovaUnit model to "Abilities\Weapons\RedDragonBreath\RedDragonMissile.mdl"
- changed static method MatchingUnit takes nothing returns boolean
from
JASS:
        static method MatchingUnit takes nothing returns boolean
        local boolean m1 = FALSE
        local boolean CheckDone = FALSE
        local boolean mF = FALSE
        local boolean mG = FALSE
        local boolean mS = FALSE

            if FRIENDLY == 1 then
                if IsUnitEnemy(GetFilterUnit(),iFilter.Owner) then
                    set m1 = TRUE
                endif
            else
                set m1 = TRUE
            endif

            if FLYING == 1 then
                if IsUnitType(GetFilterUnit(),UNIT_TYPE_FLYING) and CheckDone == FALSE then
                set CheckDone = TRUE
                set mF = TRUE
                set mG = TRUE
                set mS = TRUE
                endif
            endif

            if GROUND == 1 then
                if IsUnitType(GetFilterUnit(),UNIT_TYPE_GROUND) and CheckDone == FALSE then
                set CheckDone = TRUE
                set mF = TRUE
                set mG = TRUE
                set mS = TRUE
                endif
            endif

            if STRUCTURE == 1 then
                if IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE) and CheckDone == FALSE then
                set CheckDone = TRUE
                set mF = TRUE
                set mG = TRUE
                set mS = TRUE
                endif
            endif
            return GetWidgetLife(GetFilterUnit()) >= .405 and m1 and mF and mG and mS and not IsUnitInGroup(GetFilterUnit(),iFilter.dGroup)
        endmethod
to
JASS:
static method MatchingUnit takes nothing returns boolean
    set filterUnit=GetFilterUnit()

    if (not UnitAlive(thistype.filterUnit)) or/*
     */( IsUnitInGroup(thistype.filterUnit,iFilter.dGroup)) or/*
     */( FRIENDLY and IsUnitAlly(thistype.filterUnit,iFilter.Owner)) or/*
     */(not FLYING and IsUnitType(thistype.filterUnit,UNIT_TYPE_FLYING)) or/*
     */(not GROUND and IsUnitType(thistype.filterUnit,UNIT_TYPE_GROUND)) or/*
     */(not STRUCTURE and IsUnitType(thistype.filterUnit,UNIT_TYPE_STRUCTURE)) then
        return false
    endif
       
    return true
endmethod



- no longer use Filter instead filtering is done from FoG loop.
- replaced the usage of .dGroup by hashtable
- replaced the usage of array unit for dummy nova by a hashtable
- no longer use native UnitAlive takes unit u returns boolean and replaced by IsUnitType(u,UNIT_TYPE_DEAD)
- changed the missile dummy unit to (Abilities\Weapons\LavaSpawnMissile\LavaSpawnMissile.mdl) because I like the sound of impact :p


- corrected recycling problem by adding call dat.deallocate()


- removed private group TempGroup = CreateGroup() from the globals block and replaced by a local in
JASS:
static method NovaExpand takes nothing returns nothing
local group TempGroup = CreateGroup()

// ... rest of the function

call DestroyGroup(TempGroup)
- added immolation (DoT) to the spell


- made the missile go in Parabola Motion not Projectile Motion


- minor changes like removing unused/useless variables


- added the option to choose between Table or Formula for the spell data


- group fixing and other minor things



-PM me or post for any inquiry/problem(s)

-Credit when use.

The spell is pasted below.
JASS:
// Immolation Fireball Version 1.24

scope FNova initializer Init_IFireball

    private keyword NovaData
    private keyword MislData
    private keyword ImmoData


    globals
        // Constants
        private constant integer ABILTY_ID        = 'A000'     // rawcode of the Dummy Spell
        private constant integer M_UNIT_ID        = 'e000'     // rawcode of the Missile Unit
        private constant integer N_UNIT_ID        = 'e001'     // rawcode of the Nova Unit
        private constant integer NOVA_SIZE        = 18         // number of the Nova Units
        private constant real    M_TIMER_INTERVAL = 0.03       // this will determine the missile speed
        private constant real    N_TIMER_INTERVAL = 0.02       // this will determine the nova expand speed
        private constant real    I_TIMER_INTERVAL = 0.1        // this will determine the immolation damage frequency
        private constant real    M_DIST           = 32.00      // missile step
        private constant real    N_DIST           = 16.00      // nova expand step
        private constant real    dA               = 360/NOVA_SIZE
        private constant real    K                = 60.00
        private constant string  UnitModel        = "Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireDamage.mdl"
        private constant string  BuildingModel    = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeEmbers.mdl"
        private constant string  UAttPoint        = "chest"
        private constant string  BAttPoint        = "sprite"

        // Variables
        private timer mtm = CreateTimer()
        private timer ntm = CreateTimer()
        private timer itm = CreateTimer()
        private MislData array temp_mdat
        private NovaData array temp_ndat
        private ImmoData array temp_idat
        private integer mindex = 0
        private integer nindex = 0
        private integer iindex = 0
        private group TempGroup = CreateGroup()
        private NovaData iFilter

        private hashtable Nova_Flag = InitHashtable()
//====================================================================================================
//                                           User-edit data                                         //
//====================================================================================================
// Make sure to change the dummy spell text to be appropriate with the following variables.

// General Formula: ConstantFactor + AbilityLevel * AbilityLevelFactor + PrevLevelFactor * PrevLevelValue

// The following variable can be changed according to the desired results
  // Damage Variables:
        private constant boolean D_TABLE        = FALSE     // TRUE/FALSE to use Table/Formula

        // Table:
        private constant real array Damage                  // set the values in the function named "Spell_Data_Table"

        // Formula:
        private constant real D_CONST_FACTOR    = 50.00     // changes 'ConstantFactor'
        private constant real D_LEVEL_FACTOR    = 50.00     // changes 'AbilityLevelFactor'
        private constant real D_PREV_LVL_FACTOR = 0         // changes 'PrevLevelFactor'

  // Area of Effect (AoE) Variables:
        private constant boolean R_TABLE        = FALSE     // TRUE/FALSE to use Table/Formula

        // Table:
        private constant real array Range                   // set the values in the function named "Spell_Data_Table"

        // Formula:
        private constant real R_CONST_FACTOR    = 500.00    // changes 'ConstantFactor'
        private constant real R_LEVEL_FACTOR    = 0.00      // changes 'AbilityLevelFactor'
        private constant real R_PREV_LVL_FACTOR = 0         // changes 'PrevLevelFactor'

  // Range can be edited from the dummy spell

  // Targets:
        private constant boolean FRIENDLY  = TRUE           // TRUE/FALSE  to  harm  enemy/all  units
        private constant boolean GROUND    = TRUE           // TRUE/FALSE  to  include/exclude  ground units
        private constant boolean FLYING    = TRUE           // TRUE/FALSE  to  include/exclude  flying units
        private constant boolean STRUCTURE = TRUE           // TRUE/FALSE  to  include/exclude  structure units

  // Factors
        private constant real   UNIT_FACTOR = 1.00          // Unit damage factor.
        private constant real BUILDN_FACTOR = 0.75          // Building damage factor.

        // this will be multiplied with the spell damage
        // Becareful -ve values will heal.

  // Destroy Trees/Destructibles
  // Sorry, not done yet !!

  // Immolation (Damage Over Time)
        private constant boolean IMMOLATION      = TRUE       // TRUE/FALSE  to  Allow/Disallow Immolation (Damage Over Time)

        // Immolation Damage (ID) [per 1 Second]
        private constant boolean ID_TABLE        = FALSE      // TRUE/FALSE to use Table/Formula

        // Table:
        private constant real array ImmoDmg                  // set the values in the function named "Spell_Data_Table"

        // Formula
        private constant real ID_CONST_FACTOR    = 00.00     // changes 'ConstantFactor'
        private constant real ID_LEVEL_FACTOR    = 01.00     // changes 'AbilityLevelFactor'
        private constant real ID_PREV_LVL_FACTOR = 0         // changes 'PrevLevelFactor'

        // Immolation Length(Duration) (IL) [in seconds]
        private constant boolean IL_TABLE        = FALSE      // TRUE/FALSE to use Table/Formula

        // Table:
        private constant real array ImmoDur                  // set the values in the function named "Spell_Data_Table"

        // Formula
        private constant real IL_CONST_FACTOR    = 05.00     // changes 'ConstantFactor'
        private constant real IL_LEVEL_FACTOR    = 05.00     // changes 'AbilityLevelFactor'
        private constant real IL_PREV_LVL_FACTOR = 0         // changes 'PrevLevelFactor'
        
        private constant real CASTER_HAND_Z      = 64.00     // This value controls the height at which the missile will appear relative to the caster. Change it according to the model and its scale

//====================================================================================================
// The following variables can also be changed but will affect the spell
        // This is the Attack type and damage type; they are affected
        //by the type of the target unit's armor.
        private constant attacktype ATKTYPE = ATTACK_TYPE_NORMAL    // Spell Attack Type
        private constant damagetype DMGTYPE = DAMAGE_TYPE_FIRE      // Fire Damage Type

    endglobals


    function Spell_Data_Table takes nothing returns nothing
        // If your spell is more than 3 levels just add Damage[]/Range[]/ImmoDmg[]/ImmoDur[] under its appropriate place
        // Remember that if you don't add this line the Damage/Range/ImmoDmg/ImmoDur will be 0 for level 4+
        // Damage Table
        set  Damage[1] = 50.0
        set  Damage[2] = 100.0
        set  Damage[3] = 200.0
        // Range Table
        set   Range[1] = 500.0
        set   Range[2] = 500.0
        set   Range[3] = 500.0
        // Immolation Damage Table
        set ImmoDmg[1] = 2.0
        set ImmoDmg[2] = 4.0
        set ImmoDmg[3] = 8.0
        // Immolation Duration Table
        set ImmoDur[1] = 10.0
        set ImmoDur[2] = 15.0
        set ImmoDur[3] = 30.0
//====================================================================================================
//                                           End of User-edit data                                  //
//====================================================================================================
    endfunction

    function NovaDamage takes integer al returns real
        local real prevDmg
        if D_TABLE then
            return Damage[al]
        else
            if al == 1 then
                set prevDmg = 0
            else
                set prevDmg = NovaDamage(al - 1)
            endif
            return D_CONST_FACTOR + D_LEVEL_FACTOR * al + D_PREV_LVL_FACTOR * prevDmg
        endif
    endfunction

    function NovaRadius takes integer al returns real
        local real prevRad
        if R_TABLE then
            return Range[al]
        else
            if al == 1 then
                set prevRad = 0
            else
                set prevRad = NovaRadius(al - 1)
            endif
            return R_CONST_FACTOR + R_LEVEL_FACTOR * al + R_PREV_LVL_FACTOR * prevRad
        endif
    endfunction

    function ImmoDamage takes integer al returns real
        local real prevIDmg
        if ID_TABLE then
            return ImmoDmg[al]
        else
            if al == 0 then
                set prevIDmg = 0
            else
                set prevIDmg = ImmoDamage(al - 1)
            endif
            return ID_CONST_FACTOR + ID_LEVEL_FACTOR * al + ID_PREV_LVL_FACTOR * prevIDmg
        endif
    endfunction

    function ImmoDuration takes integer al returns real
        local real prevIDur
        if IL_TABLE then
            return ImmoDur[al]
        else
            if al == 0 then
                set prevIDur = 0
            else
                set prevIDur = ImmoDuration(al - 1)
            endif
            return IL_CONST_FACTOR + IL_LEVEL_FACTOR * al + IL_PREV_LVL_FACTOR * prevIDur
        endif
    endfunction


    private struct ImmoData extends array

        unit     caster
        unit     target
        integer  executecount
        real     damage
        real     duration
        effect   FX
    
        implement Alloc


        static method Interval takes nothing returns nothing
            local thistype dat
            local integer i = 0

        loop
        exitwhen i >= iindex
            set dat = temp_idat[i]
            set dat.executecount = dat.executecount + 1

            if dat.executecount > dat.duration/I_TIMER_INTERVAL then

                set iindex = iindex - 1
                set temp_idat[i] = temp_idat[iindex]
                set i = i - 1
                set dat.caster = null
                set dat.target = null
                set dat.executecount = 0
                call DestroyEffect(dat.FX)
                set dat.FX = null

                call dat.deallocate()

                if iindex == 0 then
                    call PauseTimer(itm)
                endif

            else

            if IsUnitType(dat.target,UNIT_TYPE_STRUCTURE) then
                call UnitDamageTarget(dat.caster,dat.target,BUILDN_FACTOR*dat.damage,false,false,ATKTYPE,DMGTYPE,null)
                if dat.FX == null then
                set dat.FX = AddSpecialEffectTarget(BuildingModel,dat.target,BAttPoint)
                endif
            else
                call UnitDamageTarget(dat.caster,dat.target,  UNIT_FACTOR*dat.damage,false,false,ATKTYPE,DMGTYPE,null)
                if dat.FX == null then
                set dat.FX = AddSpecialEffectTarget(UnitModel,dat.target,UAttPoint)
                endif
            endif

            endif

        set i = i + 1
        endloop

    endmethod

    static method StartDoT takes unit caster,unit target, real damage, real duration returns nothing
        local thistype dat = thistype.allocate()
        local integer  i   = 0
        local unit     temp_u

        set temp_idat[iindex] = dat
        set dat.caster        = caster
        set dat.target        = target
        set dat.executecount  = 0
        set dat.duration      = duration
        set dat.damage        = damage

        if iindex == 0 then
            call TimerStart(itm, I_TIMER_INTERVAL , true  , function thistype.Interval )
        endif

        set iindex = iindex + 1
    endmethod

endstruct


    private struct NovaData extends array

        unit Caster
        real expand
        real X
        real Y
        player Owner
        real dmg
        real rad
        real IDamage
        real IDuration

        implement Alloc


        static method MatchingUnit takes unit u returns boolean

            if IsUnitType(u,UNIT_TYPE_DEAD)                      or/*
            */ IsUnitType(u,UNIT_TYPE_MAGIC_IMMUNE)              or/*
            */( LoadBoolean(Nova_Flag, iFilter, GetHandleId(u))) or/*
            */(    FRIENDLY  and IsUnitAlly(u,iFilter.Owner))    or/*
            */(not FLYING    and IsUnitType(u,UNIT_TYPE_FLYING)) or/*
            */(not GROUND    and IsUnitType(u,UNIT_TYPE_GROUND)) or/*
            */(not STRUCTURE and IsUnitType(u,UNIT_TYPE_STRUCTURE)) then
                return false
            endif

                return true
            endmethod

            
        static method NovaExpand takes nothing returns nothing
            local NovaData dat
            local integer i = 0
            local integer j
            local real X
            local real Y
            local real A
            local unit u
            local unit n

            loop // this loop cycles through all the casters which have cast the spell
            exitwhen i >= nindex

                set dat = temp_ndat[i]
                // Nova Expanding
                set j = 0
                set A = 0
                set dat.expand = dat.expand + N_DIST

                loop // loop for moving the nova units
                exitwhen j >= NOVA_SIZE

                    set X = dat.X + dat.expand * Cos( A * bj_DEGTORAD )
                    set Y = dat.Y + dat.expand * Sin( A * bj_DEGTORAD )
                    set n = LoadUnitHandle( Nova_Flag , - dat , j )
                    call SetUnitX(n,X)
                    call SetUnitY(n,Y)
                    set A = A + dA

                set j = j + 1
                endloop

                // Nova Damage
                call GroupEnumUnitsInRange(TempGroup, dat.X, dat.Y, dat.expand, null)
                set iFilter = dat

                loop
                    set u = FirstOfGroup(TempGroup)
                exitwhen u == null

                    if MatchingUnit(u) then

                        if IsUnitType(u,UNIT_TYPE_STRUCTURE) then
                            call UnitDamageTarget(dat.Caster, u, BUILDN_FACTOR*dat.dmg, false, false, ATKTYPE, DMGTYPE, null)
                        else
                            call UnitDamageTarget(dat.Caster, u,   UNIT_FACTOR*dat.dmg, false, false, ATKTYPE, DMGTYPE, null)
                        endif
                        
                        if IMMOLATION == true and GetWidgetLife(u) >= 0.405 then
                            call ImmoData.StartDoT(dat.Caster,u,dat.IDamage,dat.IDuration)
                        endif

                    call SaveBoolean(Nova_Flag,dat,GetHandleId(u),true)

                    endif

                call GroupRemoveUnit(TempGroup, u)
                endloop


                if dat.expand >= dat.rad then // check if the nova has reached its max. AoE

                    set nindex = nindex - 1
                    set temp_ndat[i] = temp_ndat[nindex]
                    set i = i - 1

                    set j = 0
                    loop
                    exitwhen j >= NOVA_SIZE
                        set n = LoadUnitHandle( Nova_Flag , - dat , j )
                        call KillUnit(n)
                        set dat.expand = 0
                    set j = j + 1
                    endloop

                    call FlushChildHashtable( Nova_Flag , - dat )
                    call FlushChildHashtable( Nova_Flag ,   dat )
                    
                    call dat.deallocate()

                    if nindex == 0 then
                        call PauseTimer(ntm)
                    endif

                endif

            set i = i + 1
            endloop
            
            set n = null

        endmethod


        static method NovaCreate takes unit caster,real damage,real radius,real idamage,real iduration,real posX,real posY returns nothing
            local NovaData dat = NovaData.allocate()
            local integer i = 0
            local real A = 0.00
            local unit u

            set dat.Caster    = caster
            set dat.Owner     = GetOwningPlayer(caster)
            set dat.X         = posX
            set dat.Y         = posY
            set dat.dmg       = damage
            set dat.rad       = radius
            set dat.IDamage   = idamage
            set dat.IDuration = iduration

            loop
            exitwhen i >= NOVA_SIZE

                set u = CreateUnit( dat.Owner , N_UNIT_ID , dat.X , dat.Y , A )
                call SaveUnitHandle( Nova_Flag , - dat , i , u )
                set A = A + dA

            set i = i + 1
            endloop
            set u = null

            if nindex == 0 then
                call TimerStart(ntm,N_TIMER_INTERVAL,true,function NovaData.NovaExpand)
            endif

            set temp_ndat[nindex] = dat
            set nindex = nindex + 1

        endmethod

    endstruct


    private struct MislData extends array
    
        unit Caster
        unit Missile
        real CastPointX
        real CastPointY
        real Damage
        real Radius
        real IDamage
        real IDuration
        real face
        real a
        real h
        real k
        real v1
        real x1
        real y1

        implement Alloc


        static method MissileMove takes nothing returns nothing
            local MislData dat
            local integer i = 0
            local real x = 0
            local real y = 0
            local real z
            local real x2 = 0
            local real y2 = 0
            local real dx = 0
            local real dy = 0
            local real v

            loop
            exitwhen i >= mindex

                set dat = temp_mdat[i]
                set x  = GetUnitX(dat.Missile)
                set y  = GetUnitY(dat.Missile)
                set x2 = dat.CastPointX
                set y2 = dat.CastPointY
                set dx = x2 - x
                set dy = y2 - y

                if SquareRoot(dx*dx + dy*dy) < M_DIST then

                    set mindex = mindex - 1
                    set temp_mdat[i] = temp_mdat[mindex]
                    set i = i - 1
                    call NovaData.NovaCreate(dat.Caster,dat.Damage,dat.Radius,dat.IDamage,dat.IDuration,x,y)
                    call KillUnit(dat.Missile)
                    set dat.Missile = null
                    set dat.face = 0
                    
                    call dat.deallocate()

                    if mindex == 0 then
                        call PauseTimer(mtm)
                    endif

                else

                    set x = x + M_DIST * Cos(dat.face)
                    set y = y + M_DIST * Sin(dat.face)
                    set dx = x - dat.x1
                    set dy = y - dat.y1
                    set v = dat.v1 + SquareRoot(dx*dx + dy*dy)
                    set z = (dat.a * ( v - dat.h ) * ( v - dat.h )) + dat.k
                    call SetUnitPosition(dat.Missile,x,y)
                    call SetUnitFlyHeight(dat.Missile,z,0.0)

                endif

            set i = i + 1
            endloop

        endmethod


        static method MissileCreate takes unit u , real X , real Y returns nothing
            local MislData dat = MislData.allocate()
            local integer al = GetUnitAbilityLevel(u , ABILTY_ID)
            local real x1    = 0.0
            local real y1    = 0.0
            local real x2    = 0.0
            local real y2    = 0.0
            local real dx    = 0.0
            local real dy    = 0.0
            local real z1    = 0.0
            local real v1    = 0.0
            local real z2    = 0.0
            local real v2    = 0.0
            local real phi   = 0.0
            local real dist  = 0.0
            local real error = 0.0

            set temp_mdat[mindex] = dat

            set x1 = GetUnitX(u)
            set dat.x1 = x1
            set y1 = GetUnitY(u)
            set dat.y1 = y1

            // Fail-Safe in case the caster and the cast location is at points (x=[-1,1],y=[-1,1])
            if ((x1 >= -1 and x1 <= 1) and (y1 >= -1 and y1 <= 1) and X == x1 and Y == y1) then
                set error = 1.0
            endif

            set z1 = CASTER_HAND_Z
            set dat.k = GetUnitFlyHeight(u) + z1 + K // in case of flying units
            set x2 = X + error
            set y2 = Y + error
            set z2 = 0.0
            set phi  = SquareRoot((z2 - dat.k)/(z1 - dat.k))
            set v1 = SquareRoot(x1*x1 + y1*y1)
            set dat.v1 = v1
            set dx = x2 - x1
            set dy = y2 - y1
            set dat.Caster = u
            set dat.CastPointX = X
            set dat.CastPointY = Y
            set dist = SquareRoot(dx * dx + dy * dy)
            set dat.face = Atan2(y2 - y1, x2 - x1)
            set v2 = v1 + dist
            set dat.h  = ((phi*v1) + v2)/(1.0 + phi)
            set dat.a  = (z2 - dat.k)/((dat.h - v2)*(dat.h - v2))
            set dat.Missile = CreateUnit(GetOwningPlayer(u),M_UNIT_ID,x1,y1,dat.face*bj_RADTODEG)
            call SetUnitFlyHeight(dat.Missile,z1,0.0)
            set dat.Damage    = NovaDamage(al)
            set dat.Radius    = NovaRadius(al)
            set dat.IDamage   = ImmoDamage(al) * I_TIMER_INTERVAL
            set dat.IDuration = ImmoDuration(al)

            if mindex == 0 then
                call TimerStart(mtm,M_TIMER_INTERVAL,true,function MislData.MissileMove)
            endif

            set mindex = mindex + 1

        endmethod

    endstruct

    
private function FB_Actions takes nothing returns nothing

    if GetSpellAbilityId() == ABILTY_ID then

        call MislData.MissileCreate(GetTriggerUnit(), GetSpellTargetX(),GetSpellTargetY())

    endif

endfunction


private function Init_IFireball takes nothing returns nothing
    local trigger t = CreateTrigger()

    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddAction(t,function FB_Actions)
    call Spell_Data_Table()

    //Preload the dummy units to reduce lag on first cast
    call RemoveUnit(CreateUnit(Player(12),M_UNIT_ID,0.,0.,0.))
    call RemoveUnit(CreateUnit(Player(12),N_UNIT_ID,0.,0.,0.))

endfunction

endscope

IMPORTANT: Needs JNGP for customizing it to your needs.

Keywords:
fireball, fire, ball, nova, firenova, exploding, expanding, explode, expand, Immolation, Damage, Over, Time, DoT, burning.
Contents

Immolation Fireball (Map)

Reviews
6 April 2012 Bribe: Use "static if" to filter out constant booleans in blocks which are effecively "if TRUE then" you can use "static if CONSTANT_VARIABLE then". This way the compiler (JassHelper) just needs to evaluate it once during compilation...

Moderator

M

Moderator

6 April 2012
Bribe: Use "static if" to filter out constant booleans in blocks which are effecively "if TRUE then" you can use "static if CONSTANT_VARIABLE then". This way the compiler (JassHelper) just needs to evaluate it once during compilation, instead of the engine (Warcraft 3) evaluating it many times throughout the game, dynamically.

If you're going to screw up alignment at least screw it up consistently:

JASS:
                loop
                @    @set u = FirstOfGroup(TempGroup) //Why indent this but not the following lines?
                //It is more standard to inline all contents inside of block/endblock.
                exitwhen u == null
                call GroupRemoveUnit(TempGroup, u)
                endloop

For now, it gets approved.
 
Level 16
Joined
Jun 24, 2009
Messages
1,409
If you reduce the number of dummy units to a fine value it could make the circle lumpy so I don't think it would be a nice solution.
For fire ring you could circulate, with expanding offset, a few dummies with the Phoenix Fire Missile model. It would look a bit better and less laggy.
 
Is fFilter really needed?
Merge actions with conditions.
Spell "Missle" as "Missile", which is the correct word :p
Use coordinates instead of locations (GetSpellTargetX() and GetSpellTargetY()) and evaluate Z of the missile as well.
JASS:
if DistanceBetweenPoints(GetUnitLoc(dat.Missile),dat.CastPoint) < M_DIST then //GetUnitLoc creates a leak.
JASS:
GetWidgetLife(GetFilterUnit()) >= .305 //.405 is the right value
Reduce NOVA_SIZE to 10; 72 is ridiculously high. It's not only the unit creation that will eventually lag, it will be the animations as well.

This requires lots of fixes. I'd advise you to use external libraries as well.
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
Well, I see a few things that could be improved:
1, method onDestroy takes nothing returns nothing -> method destroy takes nothing returns nothing. It's possible to override the destroy method itself, which is a better solution as it doesn't use trigger evaluation.

2, local thistype dat -> local thistype this. It's easier to read this.

3,set dat.Nova[i] -> set thistype.Nova[this*NOVA_SIZE+i]. Make the struct extend array (no unnecessary garbage generated) and make "Nova" a static unit array. Use Alloc if you have problems with recycling.

4, group dGroup = CreateGroup() Don't do this EVER. This creates 8190 new groups at map init, which can increase the loading time a lot - not to mention the useless groups. Do all the initalization in the constructor method.

5, The new filter method:

JASS:
native UnitAlive takes unit u returns boolean //goes to the top
private static unit filterUnit
static method MatchingUnit takes nothing returns boolean
    set filterUnit=GetFilterUnit()
            
    if not UnitAlive(thistype.filterUnit) or/*
    */ not IsUnitInGroup(thistype.filterUnit,iFilter.dGroup) or/*
    */ (FRIENDLY and not IsUnitAlly(thistype.filterUnit,iFilter.Owner)) or/*
    */ (FLYING and not IsUnitType(thistype.filterUnit,UNIT_TYPE_FLYING)) or/*
    */ (GROUND and not IsUnitType(thistype.filterUnit,UNIT_TYPE_GROUND)) or/*
    */ (STRUCTURE and not IsUnitType(thistype.filterUnit,UNIT_TYPE_STRUCTURE)) then
        return false
    endif
            
    return true
endmethod
The constants are changed to boolean.

6,
JASS:
set prevDmg = NovaDamage(al - 1)
set prevRad = NovaRadius(al - 1)

JASS2 allows recursive function calls.
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
1, if you use the onDestroy method, the destroy method itself is generated by the JH, which evaluates a trigger from a trigger array(!). By overriding the destroy method and calling this.deallocate() at the bottom of the method, you can fasten up and shorten things by a lot.

2, It's simply easier to read as it's highlighted with a unique colour. That's all - it's also a better programming practice to use these keywords when prossible (in my opinion)

3, Everything is explained here.

4, I guess you don't want similar things in your map init function:
JASS:
set i=0
loop
    exitwhen i==8191
    set s___foo_tempGroup=CreateGroup()
    set i=i+1
endloop

5, My filter method is a billion times faster and it's also easier to read. The new, UnitAlive native is fail-safe, unlike the GetWidgetLife solution.
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
1, if you use the onDestroy method, the destroy method itself is generated by the JH, which evaluates a trigger from a trigger array(!). By overriding the destroy method and calling this.deallocate() at the bottom of the method, you can fasten up and shorten things by a lot.
I will practice and learn on this one

2, It's simply easier to read as it's highlighted with a unique colour. That's all - it's also a better programming practice to use these keywords when prossible (in my opinion)
It didn't color the word in my editor

3, Everything is explained here.
I took a look at it yesterday but couldn't understand much. Maybe I will take another look.
4, I guess you don't want similar things in your map init function:
JASS:
set i=0
loop
    exitwhen i==8191
    set s___foo_tempGroup=CreateGroup()
    set i=i+1
endloop
Modified it yesterday, and the new version will have the correction. Thanks for your hint it notifies me to this 8190 create fact :thumbs_up:

5, My filter method is a billion times faster and it's also easier to read. The new, UnitAlive native is fail-safe, unlike the GetWidgetLife method.
Worth a try.

Thanks for the comment. (+rep -if possible-)


<<< EDIT >>>

I am trying to add immolation (Damage over time) effect but that could be challenging.
 
Last edited:
Level 16
Joined
Aug 7, 2009
Messages
1,406
Get rid of the locations ASAP. They're hella fast, compared to pure X/Y coordinates. Each native that uses locations have 1/2 other versions that take or return reals (X/Y and sometimes even Z; GetUnitRallyPoint is the only exception I know)

Also, you still have to change the unit filter. Mine is way more clear compared to yours, it's fail-safe as I said, and it's also much faster. No locals, no bunch of useless if blocks, only one, clear line (which is split up to multiple lines, using comments; it's still only one line after it's compiled)
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
Also, you still have to change the unit filter. Mine is way more clear compared to yours, it's fail-safe as I said, and it's also much faster. No locals, no bunch of useless if blocks, only one, clear line (which is split up to multiple lines, using comments; it's still only one line after it's compiled)

I am making those variables integer for easy-editing by the users.
You see setting 0 and 1 is easier than true and false.
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
That's the worst argument I've ever heard. Why would it be easier? Because you have to type 3 less characters, and spend twice more time on figuring out whether "0" or "1" means true, whilst "true/false" are obvious? Seriously. The boolean version is also way faster; just compare my filter method to yours.

I also highly doubt Bribe would approve it with those integers; just read his feedback on it :)
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
JASS:
native UnitAlive takes unit u returns boolean //goes to the top
private static unit filterUnit
static method MatchingUnit takes nothing returns boolean
    set filterUnit=GetFilterUnit()
            
    if not UnitAlive(thistype.filterUnit) or/*
    */ not IsUnitInGroup(thistype.filterUnit,iFilter.dGroup) or/*
    */ (FRIENDLY and not IsUnitAlly(thistype.filterUnit,iFilter.Owner)) or/*
    */ (FLYING and not IsUnitType(thistype.filterUnit,UNIT_TYPE_FLYING)) or/*
    */ (GROUND and not IsUnitType(thistype.filterUnit,UNIT_TYPE_GROUND)) or/*
    */ (STRUCTURE and not IsUnitType(thistype.filterUnit,UNIT_TYPE_STRUCTURE)) then
        return false
    endif
            
    return true
endmethod

A little help on how to implement this filter.
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
The UnitAlive line can be anywhere inside those struct; I prefer them at the top of my triggers, under the "scope/library" line.

Add the static filterUnit member, and replace the filter with mine. It basically checks all the required conditions; if the unit is dead, if it's been already hit by the spell, and the booleans. I've just realised that I should do the checks the opposite way, here's the right one:

JASS:
static method MatchingUnit takes nothing returns boolean
    set thistype.filterUnit=GetFilterUnit()
    // even set bj_lastCreatedUnit is valid, but it breaks GUI compatibility.
    // I use those blizzard globals myself, but I'm using only vJASS. For private
    // resources and if you have only (v)JASS triggers, it's the best solution.
            
    if  not UnitAlive(thistype.filterUnit) or/*
    */  not IsUnitInGroup(thistype.filterUnit,iFilter.dGroup) or/*
    */ (not FRIENDLY and IsUnitAlly(thistype.filterUnit,iFilter.Owner)) or/*
    */ (not FLYING and IsUnitType(thistype.filterUnit,UNIT_TYPE_FLYING)) or/*
    */ (not GROUND and IsUnitType(thistype.filterUnit,UNIT_TYPE_GROUND)) or/*
    */ (not STRUCTURE and IsUnitType(thistype.filterUnit,UNIT_TYPE_STRUCTURE)) then
        return false
    endif
            
    return true
endmethod

So, if one of the required conditions are not met (for example: the unit is a structure and STRUCTURE is set to false), it'll return false automatically. If all the conditions are met, it won't go inside the if block and will return true automatically.
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
I guess I know what was the problem, I modified the filter like this and worked normally:

JASS:
static method MatchingUnit takes nothing returns boolean
    set filterUnit=GetFilterUnit()

    if (not UnitAlive(thistype.filterUnit)) or/*
     */( IsUnitInGroup(thistype.filterUnit,iFilter.dGroup)) or/*
     */( FRIENDLY and IsUnitAlly(thistype.filterUnit,iFilter.Owner)) or/*
     */(not FLYING and IsUnitType(thistype.filterUnit,UNIT_TYPE_FLYING)) or/*
     */(not GROUND and IsUnitType(thistype.filterUnit,UNIT_TYPE_GROUND)) or/*
     */(not STRUCTURE and IsUnitType(thistype.filterUnit,UNIT_TYPE_STRUCTURE)) then
        return false
    endif
       
    return true
endmethod
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
Yea, true, forgot that we're checking in which cases the unit won't be damaged again. My fault. Although that not keyword is still needed before FRIENDLY (because it should return false if hitting allies is not allowed and the unit is an ally)

JASS:
static method MatchingUnit takes nothing returns boolean
    set thistype.filterUnit=GetFilterUnit()

    if (not UnitAlive(thistype.filterUnit)) or/*
     */(    IsUnitInGroup(thistype.filterUnit,iFilter.dGroup)) or/*
     */(not FRIENDLY and IsUnitAlly(thistype.filterUnit,iFilter.Owner)) or/*
     */(not FLYING and IsUnitType(thistype.filterUnit,UNIT_TYPE_FLYING)) or/*
     */(not GROUND and IsUnitType(thistype.filterUnit,UNIT_TYPE_GROUND)) or/*
     */(not STRUCTURE and IsUnitType(thistype.filterUnit,UNIT_TYPE_STRUCTURE)) then
        return false
    endif
       
    return true
endmethod
 
I don't recommend the UnitAlive native because it breaks Vexorian's map optimizer which is a really helpful tool for map makers.

I recommend instead to use "not IsUnitType(u, UNIT_TYPE_DEAD)".

Also, using a null filter does not leak since patch 1.23 or 1.24, so that is the preferable way to do FoG loops now (because it doesn't leak, and the filter is a really heavy operation so FoG loops are like 2-4x faster). Since you already use a FirstOfGroup loop as well, this will be even more of a killer improvement for you.
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
I don't recommend the UnitAlive native because it breaks Vexorian's map optimizer which is a really helpful tool for map makers.

What is Vexorian's map optimizer (I hope its not one of those hard-for-me-to-understand stuff) ? Do I need to use it ?

... (because it doesn't leak, and the filter is a really heavy operation ...

heavy operation because it calls the function MatchingUnits many times or the Filter is made to be like this.

Is it because that its argument is a codefunc that makes it heavy ?

Since you already use a FirstOfGroup loop as well

Will the number of calls of the function Matching unit be the same for Filter and the FoG loop (in other words: what is the benefits of calling the function MatchingUnit from the FoG loop than the Filter if the number of calls are the same) ?
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
What is Vexorian's map optimizer (I hope its not one of those hard-for-me-to-understand stuff) ? Do I need to use it ?

http://www.wc3c.net/showthread.php?t=79326
You don't have to use it; people use it on maps that they release to lower file size and gain extra speed.


Is it because that its argument is a codefunc that makes it heavy ?

Passing a null boolexpr is way faster, because if there's a filter passed, WC3 opens a new thread for each unit, which is a heavy operation. Without a filter, using an "if", it's way faster.

JASS:
call GroupEnumUnitsInRange(TempGroup, dat.Cx, dat.Cy, dat.expand, null)
loop
    set u = FirstOfGroup(TempGroup)
    exitwhen u == null
    call GroupRemoveUnit(TempGroup, u)
    if thistype.MatchingUnit(u) then //MatchingUnit must have the filtered unit as a parameter
        call GroupAddUnit(dat.dGroup, u)
        if IsUnitType(u,UNIT_TYPE_STRUCTURE) then
            call UnitDamageTarget(dat.Caster, u, BUILDN_FACTOR*dat.dmg, false, false, ATKTYPE, DMGTYPE, null)
        else
            call UnitDamageTarget(dat.Caster, u, UNIT_FACTOR*dat.dmg, false, false, ATKTYPE, DMGTYPE, null)
        endif
    endif
endloop
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
I made some modification, I use two hashtables; one for saving the nova unit and one is working as .dGroup

Here are the modified script:

JASS:
globals
        @private hashtable Nova = InitHashtable()@
        @private hashtable Flag = InitHashtable()@
endglobals

private struct NovaData extends array

       static method MatchingUnit takes unit FilterUnit returns boolean
            local unit u = FilterUnit

        if IsUnitType(u,UNIT_TYPE_DEAD)                      or/*
        */@( LoadBoolean(Flag, iFilter, GetHandleId(u)))      or@/*
        */(    FRIENDLY  and IsUnitAlly(u,iFilter.Owner))    or/*
        */(not FLYING    and IsUnitType(u,UNIT_TYPE_FLYING)) or/*
        */(not GROUND    and IsUnitType(u,UNIT_TYPE_GROUND)) or/*
        */(not STRUCTURE and IsUnitType(u,UNIT_TYPE_STRUCTURE)) then
            return false
        endif

            return true
        endmethod

        static method NovaExpand takes nothing returns nothing
            local NovaData dat
            local integer i = 0
            local integer j
            local real x
            local real y
            local real a
            local unit u
            @local unit n@

            loop // this loop cycles through all the casters which have cast the spell
                exitwhen i >= nindex
                set dat = temp_ndat[i]
                // Nova Expanding
                set j = 0
                set a = 0
                set dat.expand = dat.expand + N_DIST

                loop // loop for moving the nova units
                    exitwhen j >= NOVA_SIZE
                    set x = dat.X + dat.expand * Cos( a * bj_DEGTORAD )
                    set y = dat.Y + dat.expand * Sin( a * bj_DEGTORAD )
                    @set n = LoadUnitHandle( Nova , dat , j )@
                    @call SetUnitX(n,x)@
                    @call SetUnitY(n,y)@
                    set j = j + 1
                    set a = a + dA
                endloop
                // Damage Units that the nova has reached

                call GroupEnumUnitsInRange(TempGroup, dat.X, dat.Y, dat.expand, @null@)
                set iFilter = dat
                loop
                    set u = FirstOfGroup(TempGroup)
                    exitwhen u == null
                    @if MatchingUnit(u) then@
                        if IsUnitType(u,UNIT_TYPE_STRUCTURE) then
                            call UnitDamageTarget(dat.Caster, u, BUILDN_FACTOR*dat.dmg, false, false, ATKTYPE, DMGTYPE, null)
                        else
                            call UnitDamageTarget(dat.Caster, u,   UNIT_FACTOR*dat.dmg, false, false, ATKTYPE, DMGTYPE, null)
                        endif
                    @call SaveBoolean(Flag,dat,GetHandleId(u),true)@
                    endif

                    call GroupRemoveUnit(TempGroup, u)
                endloop

                if dat.expand >= dat.rad then // check if the nova has reached its max. AoE
                    set nindex = nindex - 1
                    set temp_ndat[i] = temp_ndat[nindex]
                    set i = i - 1

                    set j = 0
                    loop
                    exitwhen j >= NOVA_SIZE
                        @set n = LoadUnitHandle( Nova , dat , j )@
                        call KillUnit(n)
                        set n = null
                        set dat.expand = 0
                    set j = j + 1
                    endloop
                    @call FlushChildHashtable( Nova , dat )@
                    @call FlushChildHashtable( Flag , dat )@

                    if nindex == 0 then
                        call PauseTimer(ntm)
                    endif

                endif

                set i = i + 1
            endloop

            set u = null

        endmethod

endstruct
I thought it was better to publish it here first before I release another version for the spell.
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
removed private group TempGroup = CreateGroup() from the globals block and replaced by a local in

Reading/writing arrays/locals are almost similarly fast. If I were you, I'd add a static group member to the struct, and use that - the enumeration is instant, there shouldn't be problems.

Also, you should make GroupUtils optional; you can detect if it's installed like this:

JASS:
static if LIBRARY_GroupUtils then
    set thistype.tempGroup=NewGroup()
else
    set thistype.tempGroup=CreateGroup()
endif

//...

static if LIBRARY_GroupUtils then
    call ReleaseGroup(thistype.tempGroup)
else
    call DestroyGroup(thistype.tempGroup)
endif

EDIT: oh, even more; the group can be static, there's no need to create it dynamically. Just create it at map init. You don't even have to call GroupClear, since it's cleared during enumeration.
 
Top