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

Ignite v1.1

Creates a fireball that floats over the target. The fireball lasts 16 seconds, and will emmit explosions twice, setting nearby units on fire. Burning units take damage over time, and explode twice, dealing damage to the units around them.


Just a random spell I made. Give credits if you use it and report any bugs you find.

JASS:
//Made by Destroyer95

scope Ignite initializer Initt

globals

    //***********************************************\\
    //********************Ignite*********************\\
    //***********************************************\\
    private timer T = CreateTimer()
    private group G
    private constant integer DUMMY_ID = 'h000'
    private constant integer SPELL_ID = 'A000'
    private constant real DURRATION = 16.00
    private constant real PERIOD = 0.04                                     
    private constant real RADIUS = 300
    private constant string SFX = "Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl"    //The effect that will appear on the dummy unit
    private constant string ATTACH = "overhead"                                               //Attachment point of the effect
    private constant string EXPLODE = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"         //Exploding effect
    private constant string EATTACH = "overhead"                                              //Attachment point of the exploding effect
    //***********************************************\\
    
    //***********************************************\\
    //*********************BURN**********************\\
    //***********************************************\\
    private timer BT = CreateTimer()
    private constant real BDURRATION = 6.00                                                    //B means Burn so BDURRATION = BURN DURRATION
    private constant real BPERIOD = 0.25                                                       //Same as above
    private constant string BSFX = "Environment\\LargeBuildingFire\\LargeBuildingFire1.mdl"
    private constant string BATTACH = "origin"
    private constant string BEXPLODE_SFX = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
    //***********************************************\\

    private boolexpr bool
    
endglobals

private function EXPLODE_RADIUS takes integer lvl returns real
    return I2R(lvl * 50 + 200)
endfunction

private function BEXPLODE_RADIUS takes integer lvl returns real
    return I2R(lvl * 50 + 125)
endfunction

private function MIN_DMG takes integer lvl returns real
    return I2R(lvl * 1 + 2)
endfunction

private function MAX_DMG takes integer lvl returns real
    return I2R(lvl * 2 + 3)
endfunction

private function EXPLODE_DAMAGE takes integer lvl returns real
    return I2R(lvl * 5 + 10)
endfunction

private function Filt takes nothing returns boolean
    return (IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE ) == false) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE ) == false) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL ) == false) and (GetWidgetLife(GetFilterUnit()) > 0.405)
endfunction

private struct burn

    unit caster
    unit target
    integer lvl
    effect eff
    real durr = BDURRATION

    static integer Total = 0
    static burn array arr
    
    static method Loop takes nothing returns nothing
        local integer i = 0
        local burn b
        local unit temp
        loop
            exitwhen i >= burn.Total
            set b = burn.arr[i]
            if b.durr >= 0 and GetWidgetLife(b.target) > 0.405 then
                call UnitDamageTarget(b.caster, b.target, GetRandomReal(MIN_DMG(b.lvl), MAX_DMG(b.lvl)), false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
                if b.durr == BDURRATION / 2 then
                    call GroupEnumUnitsInRange(G, GetUnitX(b.target), GetUnitY(b.target), BEXPLODE_RADIUS(b.lvl), bool)
                    call DestroyEffect(AddSpecialEffect(BEXPLODE_SFX, GetUnitX(b.target), GetUnitY(b.target)))
                    loop
                        set temp = FirstOfGroup(G)
                        exitwhen temp == null
                        if IsUnitEnemy(temp, GetOwningPlayer(b.caster)) then
                            call UnitDamageTarget(b.caster, temp, EXPLODE_DAMAGE(b.lvl), false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
                        endif
                        call GroupRemoveUnit(G, temp)
                    endloop
                endif
                set b.durr = b.durr - BPERIOD
            else
                call GroupEnumUnitsInRange(G, GetUnitX(b.target), GetUnitY(b.target), BEXPLODE_RADIUS(b.lvl), bool)
                call DestroyEffect(AddSpecialEffect(BEXPLODE_SFX, GetUnitX(b.target), GetUnitY(b.target)))
                loop
                    set temp = FirstOfGroup(G)
                    exitwhen temp == null
                    if IsUnitEnemy(temp, GetOwningPlayer(b.caster)) then
                        call UnitDamageTarget(b.caster, temp, EXPLODE_DAMAGE(b.lvl), false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
                    endif
                    call GroupRemoveUnit(G, temp)
                endloop
                call DestroyEffect(b.eff)
                set b.eff = null
                set b.caster = null
                set b.target = null
                call b.destroy()
                set burn.Total = burn.Total - 1
                set burn.arr[i] = burn.arr[burn.Total]
                set i = i - 1
            endif
            set i = i + 1
        endloop
        if burn.Total == 0 then
            call PauseTimer(BT)
        endif
    endmethod
    
    static method Start takes unit caster, unit target, integer level returns nothing
        local burn b = burn.allocate()
        set b.target = target
        set b.caster = caster
        set b.lvl = level
        set b.eff = AddSpecialEffectTarget(BSFX, b.target, BATTACH)
        if burn.Total == 0 then
            call TimerStart(BT, BPERIOD, true, function burn.Loop)
        endif
        set burn.arr[burn.Total] = b
        set burn.Total = burn.Total + 1
    endmethod
    
endstruct

private struct data

    unit target
    unit caster
    unit dummy
    real durr = DURRATION
    integer lvl
    effect eff
    
    static integer Total = 0
    static data array arr
    
    static method onLoop takes nothing returns nothing
        local integer i = 0
        local data d
        local real x
        local real y
        local unit temp
        loop
            exitwhen i >= data.Total
            set d = data.arr[i]
            if d.durr >= 0 and GetWidgetLife(d.target) > 0.405 then
                set x = GetUnitX(d.target)
                set y = GetUnitY(d.target)
                call SetUnitX(d.dummy, x)
                call SetUnitY(d.dummy, y)
                if d.durr == DURRATION / 4 or d.durr == DURRATION * 3 / 4 then
                    call DestroyEffect(AddSpecialEffectTarget(EXPLODE, d.target, EATTACH))
                    call GroupEnumUnitsInRange(G, x, y, EXPLODE_RADIUS(d.lvl), bool)
                    loop
                        set temp = FirstOfGroup(G)
                        exitwhen temp == null
                        if IsUnitEnemy(temp, GetOwningPlayer(d.caster)) then
                            call burn.Start(d.caster, temp, d.lvl)
                        endif
                        call GroupRemoveUnit(G, temp)
                    endloop
                endif
                set d.durr = d.durr - PERIOD
            else
                call DestroyEffect(d.eff)
                call RemoveUnit(d.dummy)
                set d.dummy = null
                set d.target = null
                set d.caster = null
                set d.eff = null
                call d.destroy()
                set data.Total = data.Total - 1
                set data.arr[i] = data.arr[data.Total]
                set i = i - 1
            endif 
            set i = i + 1
        endloop
        if data.Total == 0 then
            call PauseTimer(T)
        endif
    endmethod
    
    static method Start takes unit cast, unit targ returns nothing
        local data d = data.allocate()
        set d.target = targ
        set d.caster = cast
        set d.lvl = GetUnitAbilityLevel(cast, SPELL_ID)
        set d.dummy = CreateUnit(GetOwningPlayer(cast), DUMMY_ID, GetUnitX(targ), GetUnitY(targ), 0.00)
        set d.eff = AddSpecialEffectTarget(SFX, targ, ATTACH)
        if data.Total == 0 then
            call TimerStart(T, PERIOD, true, function data.onLoop)
        endif
        set data.arr[data.Total] = d
        set data.Total = data.Total + 1
    endmethod

endstruct

private function Cond takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_ID
endfunction

private function Act takes nothing returns nothing
    call data.Start(GetTriggerUnit(), GetSpellTargetUnit())
endfunction

private function Initt takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function Cond))
    call TriggerAddAction(t, function Act)
    
    set G = CreateGroup()
    set bool = Condition(function Filt)
endfunction

endscope

//Made by Destroyer95

Keywords:
Ignite, Burn, DoT, Damage over Time
Contents

Ignite (Map)

Reviews
12:49, 10th Jun 2010 TriggerHappy: It's good enough. There's no need to initialize the group (G) inside a function. Just initialize it upon declaration. I would suggest not using first of group loops. There are better ways of checking if a...

Moderator

M

Moderator

12:49, 10th Jun 2010
TriggerHappy:

It's good enough.

  • There's no need to initialize the group (G) inside a function. Just initialize it upon declaration.
  • I would suggest not using first of group loops.
  • There are better ways of checking if a unit is dead than life checks (UnitAlive, Type check).
 
Level 9
Joined
Dec 12, 2007
Messages
489
well nice spell... although again, it's very simple...
JASS:
private function EXPLODE_RADIUS takes integer lvl returns real
    return I2R(lvl * 50 + 200)
endfunction
rather than having that conversion, you could
JASS:
private function EXPLODE_RADIUS takes integer lvl returns real
    return lvl * 50 + 200.
endfunction
with that point in 200, WE will recognize it as reals now.
I see the point that you want your spell to be customizable, and yet your spell has hardcoded value when it will explode, you might want to change that.
also you might want to make the damage type and attack type to be customizable.

and is it just me, or does all of your spells submitted has a same pattern? (like trying to use single timer, struct based, DoT)
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Review said:
Ignite

Overall this was a pretty well implemented spell. The separation of the burn and ignite processes makes the code very easy to follow. The tooltip I believe is a little faulty, it threw me off at first and I was thinking this spell was really bugged.

- The affected unit is "supposed" to explode twice, but I experienced many more explosions than that. The spell ended up lasting almost 20 seconds and exploding roughly 5 times. I see that it could just be the tooltip that is at fault here.

- You can eliminate the "I2R" function calls in your constants by typecasting. I'll give an example:

JASS:
private function EXPLODE_RADIUS takes integer lvl returns real
    return I2R(lvl * 50 + 200)
endfunction

This could be transformed into:

JASS:
private function EXPLODE_RADIUS takes integer lvl returns real
    return lvl * 50.0 + 200.0
endfunction

- Your implementation is very nice. You simplify things by simply running the struct from the actions. Good job.

- You can initialize "G" in the globals block, it is not necessary that you do it in the initializer function.

- In your struct you constantly refer to the name of the struct manually. Please use the "thistype" operator as it makes everything much easier.

- Struct members do not need to be nulled. They are global arrays.

JASS:
set d.dummy = null
set d.target = null
set d.caster = null
set d.eff = null
call d.destroy()

You should also declare an "onDestroy" method (not 100% necessary) in which you destroy the effect that is used, it just makes the code easier to follow for you and everybody else. Again, this isn't absolutely necessary but it's always good to do things properly.

- I think this is best explained by example:

JASS:
                if b.durr == BDURRATION / 2 then

I suggest you add a boolean member to detect whether or not this explosion has fired and flag it on/off when needed, because if the BPERIOD is not a factor of BDURRATION (which is spelled wrong, by the way :D) then it won't ever fire off.

- Your unit-group loops aren't actually necessary. If you save the struct to a global you can reference it without any problems in the enumeration function, and do all of your effects there; this saves the trouble of filtering all the units and then looping through them *again* to run your effects.

I see Dark_Axl already covered the typecasting I mentioned.

Dark_Axl said:
and is it just me, or does all of your spells submitted has a same pattern? (like trying to use single timer, struct based, DoT)

I personally really favor his "pattern" as you call it, though I don't know anything about his other spells. The idea of using a single timer with a stack is favorable, though.

It would seem I have to use
tags for my reviews, as the table puts all of my JASS into the same line : (
 
Level 8
Joined
Jul 28, 2008
Messages
211
- The affected unit is "supposed" to explode twice, but I experienced many more explosions than that. The spell ended up lasting almost 20 seconds and exploding roughly 5 times. I see that it could just be the tooltip that is at fault here.
Well the true spell explosion (where the fireball explodes) happens only twice. The other explosions you saw are the explosions of ignited units (that got ignited by the fireball explosion) because the caster gets ignited too. The explosions have different effects so you can see which ones are the fireball explosions and which are the ignite explosions.
 
Top