Spell Fire Pillar

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Fire Pillar:

Spell info:
GUI/JASS?: JASS
MUI?: Yes
Leaks: None
How to implement: Instructions inside the map.

Note: Requires NewGen World Editor and ABC struct attachment system.

Code:
[U][B]Description:[/B][/U]
Summons a fiery pillar at a targetted location. 
If an enemy unit walks into the pillar it will be struck by 10 fireballs, 
dealing damage and stunning for 0.1 seconds each. 
Lasts 12 seconds or until an enemy unit walks into the pillar. 
Level 1 - 30 damage per fireball. 
Level 2 - 40 damage per fireball. 
Level 3 - 50 damage per fireball.
Level 4 - 60 damage per fireball.
Cooldown 19/16/13/10 seconds.
Manacost 140/160/180/200.

Spell code:

JASS:
scope FirePillar

globals
    private constant integer SpellAID = 'A000'          //The hero spell ID
    private constant integer DummyAID = 'A001'         //The dummyspell ID
    private constant integer EffectDummyID = 'u001'   //The visible dummy's ID
    private constant integer DummyID = 'u000'        //The regular dummy's ID
    private constant real Duration = 12.0           //The duration of the spell
    private constant integer FireballCount = 10    //Number of fireballs spawned to strike the enemy unit that comes in range
    private constant real DetonationAoE = 150.0   //The detonation AoE for the Fire Pillar
    private constant string Effect = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"    //The explosion effect when the Fire Pillar activates.
    private constant boolean AoEOn = false        //"true" = AoE, "false" = single target.
//Do not change anything below here unless you know what you're doing.
    private unit FPCaster = null
    private integer FPLevel = 0
endglobals

private struct SpellData
    unit caster
    unit dummy
    real x
    real y
    trigger inrange
    timer fptimer
    boolean check
    integer level
    triggeraction inrangeaction
    method onDestroy takes nothing returns nothing
        call ClearTriggerStructA(.inrange)
        call TriggerRemoveAction(.inrange, .inrangeaction)
        call DestroyTrigger(.inrange)
        call ClearTimerStructA(.fptimer)
        call DestroyTimer(.fptimer)
    endmethod
endstruct

private function TimerActions takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local SpellData data = GetTimerStructA(t)
    
    if GetWidgetLife(data.dummy) > .405 then
        call RemoveUnit(data.dummy)
        call DestroyEffect(AddSpecialEffect(Effect, data.x, data.y))
    endif
    call data.destroy()
    
    set t = null
endfunction

private function GroupFiltering takes nothing returns boolean
    return true
endfunction

private function InRangeActions takes nothing returns nothing
    local SpellData data = GetTriggerStructA(GetTriggeringTrigger())
    local unit Victim = GetTriggerUnit()
    local location Point
    local real x
    local real y
    local integer Loop = 0
    local unit Dummy
    local group UnitGroup = CreateGroup()
    local unit First
    
    if data.check == false then
        if IsPlayerEnemy(GetOwningPlayer(data.caster), GetOwningPlayer(Victim)) == true then
            set data.check = true
            call DestroyEffect(AddSpecialEffect(Effect, data.x, data.y))
            call RemoveUnit(data.dummy)
            loop
                exitwhen Loop >= FireballCount
                    set Loop = Loop + 1
                    if AoEOn == true then
                        call GroupEnumUnitsInRange(UnitGroup, data.x, data.y, DetonationAoE, Condition(function GroupFiltering))
                        loop
                            set First = FirstOfGroup(UnitGroup)
                            exitwhen First == null
                                if IsPlayerEnemy(GetOwningPlayer(data.caster), GetOwningPlayer(First)) == true and IsUnitType(First, UNIT_TYPE_FLYING) == false then
                                    set Point = GetUnitLoc(First)
                                    set x = GetLocationX(Point)
                                    set y = GetLocationY(Point)
                                    set Dummy = CreateUnit(GetOwningPlayer(data.caster), DummyID, x, y, 0)
                                
                                    call UnitAddAbility(Dummy, DummyAID)
                                    call SetUnitAbilityLevel(Dummy, DummyAID, data.level)
                                    call IssueTargetOrder(Dummy, "thunderbolt", First)
                                    call UnitApplyTimedLife(Dummy, 'BTLF', 1.0)
                                    call RemoveLocation(Point)
                                endif
                            call GroupRemoveUnit(UnitGroup, First)
                        endloop
                        call TriggerSleepAction(.1)
                    else
                        set Dummy = CreateUnit(GetOwningPlayer(data.caster), DummyID, data.x, data.y, 0)
                        call UnitAddAbility(Dummy, DummyAID)
                        call SetUnitAbilityLevel(Dummy, DummyAID, data.level)
                        call IssueTargetOrder(Dummy, "thunderbolt", Victim)
                        call UnitApplyTimedLife(Dummy, 'BTLF', 1.0)
                        call TriggerSleepAction(.1)
                    endif
            endloop
        endif
    endif
    
    call DestroyGroup(UnitGroup)
    
    set Victim = null
    set Dummy = null
    set UnitGroup = null
    set Point = null
endfunction

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellAID
endfunction

private function Actions takes nothing returns nothing
    local SpellData data = SpellData.create()
    local location Point = GetSpellTargetLoc()
    local unit Caster = GetTriggerUnit()
    local real x = GetLocationX(Point)
    local real y = GetLocationY(Point)
    local integer Level = GetUnitAbilityLevel(Caster, SpellAID)
    
    set data.dummy = CreateUnit(GetOwningPlayer(Caster), EffectDummyID, x, y, 0)
    set data.fptimer = CreateTimer()
    set data.caster = Caster
    set data.inrange = CreateTrigger()
    set data.check = false
    set data.x = x
    set data.y = y
    set data.level = Level
    
    call SetTimerStructA(data.fptimer, data)
    call TimerStart(data.fptimer, Duration, false, function TimerActions)
    call SetTriggerStructA(data.inrange, data)
    call TriggerRegisterUnitInRangeSimple(data.inrange, DetonationAoE, data.dummy)
    set data.inrangeaction = TriggerAddAction(data.inrange, function InRangeActions)
    call RemoveLocation(Point)
    
    set Point = null
    set Caster = null
endfunction

//===========================================================================
public function InitTrig takes nothing returns nothing
    local trigger trig = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( trig, Condition( function Conditions ) )
    call TriggerAddAction( trig, function Actions )
endfunction

endscope

Usage of the spell?:
It's exellent to just pop a Fire Pillar at your enemies escape route when he/she is running for his/her life. Also good for initializing an attack against someone who stands all alone with no creeps around. Also works great for harrassing with. Rather imba and could use either increased cooldown or higher manacost.

Special Thanks:
Ragnarok Online for giving me the inspiration for this spell.
Cohadar for the ABC struct attachment system.
Tinki3 for the testmap, saving me the trouble of showing people my insanely poor terrainingskills.
Flare for pointing out further customizablities.
You for reading the special thanks section for some unknown reason.

Version tracker and updates:
Version 1.1d: Fixed the TriggerAddAction leak and the spell now filters out flying units.
Version 1.1c: Now the unit is removed, not killed so it melts in better with the explosion.
Version 1.1b: Minor leakfix.
Version 1.1: Made struct private and added an easy way of making the fire pillars do AoE damage/stun.
Version 1.0b: Made the spell even easier to customize.
Version 1.0: Initial release.

Screenshot:

It's like extremely hard to get a good screenshot of this one, check it out ingame instead.

[URL=http://img230.imageshack.us/my.php?image=firepillaryn0.jpg][/URL]
 

Attachments

  • Fire Pillar v1.1d by Larcenist.w3x
    62 KB · Views: 581

Vestras

Retired
Reaction score
248
You should REALLY add a death animation to the "pillar". I mean, suddenly, it's just gone.
 

Flare

Stops copies me!
Reaction score
662
Code seems fine, I can't find any leaks, but one thing I noticed

JASS:
                exitwhen Loop >= 10


You should set that 10 to a constant, so that people can easily configure how many times a unit is hit by the fireball.

Along with the 'In Range' distance (possibly). Everything that makes the spell easy to manipulate makes it better (IMO at least).


I'll test the spell itself later though (don't have much time to test atm)
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Made it even easier to customize, thanks for pointing that out Flare.

Also did I use the onDestroy method correctly? I really had no idea how to do it so I just read a few spells in the spell section then guessed from there.

You should REALLY add a death animation to the "pillar". I mean, suddenly, it's just gone.

It's a doodad <base> crap, it hasn't gotten any death animation.
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Oh bummer, I always keep my structs private since I always use the struct Spelldata. Guess I forgot it this time, fixing right away.
 

Vestras

Retired
Reaction score
248
> It's a doodad <base> crap, it hasn't gotten any death animation.

Take another model? Or get Hatebreeder or someone else to animate it.
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
I provide the spell code, if you don't like the model then take those 10 seconds changing the model in the object editor.

Version 1.1 added, one question though. Will turning the AoE on result in the spell not being MUI anymore? I was just wondering since there's a wait in there, though the globals are redeclared inside the loop too so I do not know if it's MUI or not.
 

Flare

Stops copies me!
Reaction score
662
You mean here?

JASS:
            loop
                exitwhen Loop &gt;= FireballCount
                    set Loop = Loop + 1
                    if AoEOn == true then
                        set FPCaster = data.caster
                        set FPLevel = data.level
                        call GroupEnumUnitsInRange(UnitGroup, data.x, data.y, DetonationAoE, Condition(function GroupFiltering))
                        call ForGroup(UnitGroup, function GroupActions)
                        call TriggerSleepAction(.1)
                    else
                        set Dummy = CreateUnit(GetOwningPlayer(data.caster), DummyID, data.x, data.y, 0)
                        call UnitAddAbility(Dummy, DummyAID)
                        call SetUnitAbilityLevel(Dummy, DummyAID, data.level)
                        call IssueTargetOrder(Dummy, &quot;thunderbolt&quot;, Victim)
                        call UnitApplyTimedLife(Dummy, &#039;BTLF&#039;, 1.0)
                        call TriggerSleepAction(.1)
                    endif
            endloop


If you are using locals (or redeclaring globals at the start of the loop), it's still MUI. Just try repeated casts and see if it works as intended.

If you are worried about it not being MUI, you could use a FirstOfGroup loop within the function that has all your locals/struct data
JASS:
loop
exitwhen FirstOfGroup (g) == null
set u = FirstOfGroup (g)
//Actions using u
call GroupRemoveUnit (u)
endloop

//When all units have been removed from the group, u will have to be null
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Yeh it works as intended, though I should destroy that group inside the loop, not outside.

I'll just stick to this until someone jumps in saying that it's not MUI, I somehow feel more comfortable using callback functions rather than using FirstOfGroup loops.
 

GoGo-Boy

You can change this now in User CP
Reaction score
40
Hmm I like the idea and I really like the model you took for this:thup:

However as ELSKER suggested I'd add one (best one that fits :D) minor explosion effect to this Pillar when it disappears. Another thing I'd prefer is making the pillar taking a random target or multiple. Because it looks kinda weird when many units are close to it and only one gets damaged.
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Change the constant variable AoEOn to true and it will deal damage to all nearby units.

Also it's quite difficult timing an explosion to fit when the unit dies, since it dissapears around 1-2 seconds after it's killed.
 

GoGo-Boy

You can change this now in User CP
Reaction score
40
Oh should have read what you wrote about the AOE^^

Remove the doodad then instead of destroying. That way you can time it perfeclty.
 

Tinki3

Special Member
Reaction score
418
Neat Spell.

In the "Actions" function, you are leaking a triggeraction with:
JASS:
call TriggerAddAction(data.inrange, function InRangeActions)


I would suggest adding a triggeraction struct variable and using that to define/set the action for the trigger:
JASS:
set data.trigaction = TriggerAddAction(data.inrange, function InRangeActions)


And then you'd obviously need to remove the triggeraction from the trigger inside your onDestroy method:
JASS:
call TriggerRemoveAction(.inrange, .trigaction)

(Remember to do this before destroying + clearing the trigger variable).


I also noticed that you were just filtering units that were enemies of the caster.
This means air units can trigger the effects, so filter them out.
 

SanKakU

Member
Reaction score
21
sounds interesting, but what does it look like? i might try updating this one if the original poster doesn't get to it first.
 
General chit-chat
Help Users

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top