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

Fire Vortex v1.6

Creates a vortex of fire energy that absorbs health of nearby enemy units, when the fire stops spinning, it flies up in the air and back to the ground which deals huge damage and summons a fire elemental.
Fire elemental lasts 15 seconds.

|cffffcc00Level 1|r - 30 damage per touch.
|cffffcc00Level 2|r - 40 damage per touch.
|cffffcc00Level 3|r - 50 damage per touch.
|cffffcc00Level 4|r - 60 damage per touch.
|cffffcc00Level 5|r - 70 damage per touch.

Ground impact deals 2x damage per level plus 30.

JASS:
library FireVortex /* v1.6
*************************************************************************************
*
*   by mckill2009
*
*************************************************************************************
*
*   */ uses /*
*   
*       */ Table    /* www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084
*       */ CTL      /* www.hiveworkshop.com/forums/jass-resources-412/snippet-constant-timer-loop-32-a-201381/index7.html#post2389697
*
*************************************************************************************
*
*   Installation:
*       - Import/copy the required libraries and FireVortex code to your map
*       - Import/copy the custom ability and unit to your map and change the SPELL_ID and FIRE_ID if needed
*       - You may view the raw ID of the objects by pressing CTRL+D in the object editor
*       - You may play with the configurables below
*       
*************************************************************************************/

globals
    /********************************************************
    *   Configurables: default and recommended settings
    *********************************************************/
    //change raw id if needed
    private constant integer SPELL_ID = 'A000'
    //change raw id if needed
    private constant integer FIRE_ID = 'h000'
    //uses default lava spawn level 2, this is totally optional
    private constant integer ELEMENTAL_ID = 'nlv2'
    //the effect when fire lands the ground
    private constant string EXPLODE_SFX = "Objects\\Spawnmodels\\Undead\\UDeathSmall\\UDeathSmall.mdl"
    //the damage aoe when fire lands the ground
    private constant real EXPLODE_AOE_DAMAGE = 150.
    //the damage aoe when fire is spinning
    private constant real TOUCH_AOE_DAMAGE = 80.
    //max fly height
    private constant real FLY_HEIGHT = 500.
    //the fly or up speed
    private constant real FLY_SPEED = 15.
    //the impact to the ground speed, this is added to the impactDamage
    private constant real DROP_SPEED = 30.
    //how fast the fire be created
    private constant real CREATION_DELAY = 0.5
    //the higher the number, the faster
    private constant real SPIN_SPEED = 0.07 //in radians
    //allows the creation of a unit, see ELEMENTAL_ID
    private constant boolean ENABLE_ELEMENTAL_CREATION = true
    //set to false for elemental to live until dies
    //ENABLE_ELEMENTAL_CREATION should be true also
    private constant boolean ELEMENTAL_HAS_TIMER = true
    private attacktype ATK = ATTACK_TYPE_MAGIC
    private damagetype DMG = DAMAGE_TYPE_MAGIC
    /********************************************************
    *   Non-configurables
    ********************************************************/
    private real CreationGap
    private Table sp
    private group TempG = CreateGroup()
endglobals

/********************************************************
*   Configurables 
*********************************************************/
private function GetDamage takes integer level returns real
    return (10 * level + 20.)
endfunction

private function GetFireCount takes integer level returns integer
    return 2 * level + 2
endfunction

private function GetElementalTimer takes integer level returns real
    return 15.
endfunction
/********************************************************
*   End of configurables
*********************************************************/

private function UnitAlive takes unit u returns boolean
    return not IsUnitType(u, UNIT_TYPE_DEAD)
endfunction

struct FireSpin extends array
    unit fire
    real angle
    real damage
    real impactDamage
    real duration
    real dist
    real height
    real elementalLife
    real x
    real y
    boolean direction
    boolean fly
    group g
    
    implement CTL
        local unit first
        local real xFire
        local real yFire
    implement CTLExpire
        if .fire==null then
            call .destroy()
        else
            set xFire = GetUnitX(.fire)
            set yFire = GetUnitY(.fire)
            if .duration > 0 then
                set .duration = .duration - 0.03125
                if .direction then
                    set .angle = .angle + SPIN_SPEED
                else
                    set .angle = .angle - SPIN_SPEED
                endif     
                call SetUnitX(.fire, .x+.dist*Cos(.angle))
                call SetUnitY(.fire, .y+.dist*Sin(.angle))
                call GroupEnumUnitsInRange(TempG, xFire, yFire, TOUCH_AOE_DAMAGE, null)
                loop
                    set first = FirstOfGroup(TempG)
                    exitwhen first==null
                    if UnitAlive(first) and IsUnitEnemy(first, GetOwningPlayer(.fire)) then
                        call UnitDamageTarget(.fire, first, .damage, false, false, ATK, DMG, null)
                    endif
                    call GroupRemoveUnit(TempG, first)
                endloop                
            else
                if .fly then
                    set .height = .height + FLY_SPEED
                    if .height > FLY_HEIGHT then
                        set .fly = false
                    endif
                else
                    set .height = .height - DROP_SPEED
                endif   
                
                call SetUnitFlyHeight(.fire, .height, 0)   
                
                if 0 > .height then
                    call DestroyEffect(AddSpecialEffect(EXPLODE_SFX, xFire, yFire))
                    call GroupEnumUnitsInRange(TempG, xFire, yFire, EXPLODE_AOE_DAMAGE, null)
                    loop
                        set first = FirstOfGroup(TempG)
                        exitwhen first==null
                        if UnitAlive(first) and IsUnitEnemy(first, GetOwningPlayer(.fire)) then
                            call UnitDamageTarget(.fire, first, .impactDamage, false, false, ATK, DMG, null)
                        endif
                        call GroupRemoveUnit(TempG, first)
                    endloop 
                    
                    static if ENABLE_ELEMENTAL_CREATION then
                        set first = CreateUnit(GetOwningPlayer(.fire), ELEMENTAL_ID, xFire, yFire, 0)
                        call UnitAddType(first, UNIT_TYPE_SUMMONED)
                        static if ELEMENTAL_HAS_TIMER then
                            call UnitApplyTimedLife(first, 'BTLF', .elementalLife)
                        endif
                        set first = null
                    endif
                    
                    call KillUnit(.fire)
                    set .fire = null
                endif
            endif
        endif
    implement CTLEnd
    
    static method spin takes unit f, real damage, real x, real y, real el returns nothing
        local thistype this = create()
        local integer id = GetHandleId(f)
        set .fire = f
        set .direction = false
        if sp.boolean[id] then
            set .direction = true
        endif
        set .duration = GetRandomReal(7, 12)  
        set .elementalLife = el
        set .fly = true
        set .height = GetUnitFlyHeight(f)
        set .x = x
        set .y = y
        set .angle = Atan2(GetUnitY(f)-y, GetUnitX(f)-x)
        set .dist = SquareRoot((GetUnitX(f)-x)*(GetUnitX(f)-x)+(GetUnitY(f)-y)*(GetUnitY(f)-y))
        set .impactDamage = (damage*2)+(DROP_SPEED*2)
        set .damage = damage*0.3125
        call UnitAddAbility(f, 'Arav')
    endmethod
endstruct

private struct FireVortexCast extends array
    player p
    real a
    real damage
    real elementalLife
    real delay
    real down
    real up    
    real x
    real y
    integer count
    integer index
    boolean check
    boolean directionUp
    boolean directionDown
    group g
    
    static thistype DATA
    
    private static method startSpinning takes nothing returns nothing
        local thistype this = DATA
        call FireSpin.spin(GetEnumUnit(), this.damage, this.x, this.y, this.elementalLife)    
    endmethod    
    
    implement CTL
        local unit fire
        local integer id
    implement CTLExpire
        if .count > .index then
            set .delay = .delay + 0.03125
            if .delay > CREATION_DELAY then
                set .delay = 0
                set .index = .index + 1
                if .check then
                    set .check = false
                    set fire = CreateUnit(.p, FIRE_ID, .x+.up*Cos(.a), .y+.up*Sin(.a), 0)
                    set .up = .up - CreationGap
                    set id = GetHandleId(fire)
                    if .directionUp then
                        set .directionUp = false
                        set sp.boolean[id] = true
                    else
                        set .directionUp = true
                    endif
                else
                    set .check = true
                    set fire = CreateUnit(.p, FIRE_ID, .x+.down*Cos(.a), .y+.down*Sin(.a), 0)
                    set .down = .down + CreationGap
                    set id = GetHandleId(fire)
                    if .directionDown then
                        set .directionDown = false
                        set sp.boolean[id] = true
                    else
                        set .directionDown = true
                    endif
                endif
                set sp.real[id] = .a
                call GroupAddUnit(.g, fire)
                set fire = null
            endif          
        else
            set DATA = this
            call ForGroup(.g, function thistype.startSpinning)
            call DestroyGroup(.g)
            set .p = null
            set .g = null
            call .destroy()          
        endif
    implement CTLEnd
    
    static method run takes unit u, real x, real y returns nothing
        local thistype this = create()
        local integer level = GetUnitAbilityLevel(u, SPELL_ID)
        set .p = GetTriggerPlayer()
        set .a = Atan2(GetUnitY(u)-y, GetUnitX(u)-x)
        set .damage = GetDamage(level)
        set .elementalLife = GetElementalTimer(level)
        set .delay = 0
        set .down = CreationGap
        set .up = -CreationGap
        set .x = x
        set .y = y
        set .count = GetFireCount(level)
        set .index = 0
        set .check = true
        set .directionUp = true
        set .directionDown = true
        set .g = CreateGroup()
    endmethod 

    static method cast takes nothing returns boolean
        if GetSpellAbilityId()==SPELL_ID then
            call thistype.run(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
        endif
        return false
    endmethod
    
    static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function thistype.cast))
        set t = null     
        set CreationGap = TOUCH_AOE_DAMAGE + 20.
        set sp = Table.create()
    endmethod
endstruct

endlibrary


v1.6
- Converted to vJass and has added description

v1.5
- Multiple creation of fire instead of 5 only (customable)
- Dead units filtered
- Button position changed
- Code fully optimized

v1.4
- Added condition on mechanical units
- Codes reduced
- Functions have Condition & Action

v1.3
- Variables nulled
- Code optimized
- Hahtable setup inside code

v1.2
- Converted to JASS
- Description completely changed
- Smooth rotating vortex

v1.1
- Codes Reduced
- HP damage changed from 'Set life to #' to 'Unit - Damage Target'



CTL by Nesthsurus
Table by Bribe



Keywords:
fire, vortex, warcraft, circle, system, real, mage, power, jass, vjass
Contents

FireVortex (Map)

Reviews
Fire Vortex v1.6 | Reviewed by Maker | 3rd Aug 2013 APPROVED The spell is MUI and lekless [tr] When the fires circle on ground, they could have some sound effect Fix the Text - Tooltip - Learn and Text - Tooltip -...

Moderator

M

Moderator


Fire Vortex v1.6 | Reviewed by Maker | 3rd Aug 2013
APPROVED


126248-albums6177-picture66521.png


  • The spell is MUI and lekless
126248-albums6177-picture66523.png


  • When the fires circle on ground, they could have some sound effect
  • Fix the Text - Tooltip - Learn and Text - Tooltip - Normal
  • You could use RegisterSpellEffectEvent, make it optional
  • You could make use of private in FireSpin
  • Instead of .direction boolean, you could use a real and get rid
    of the if/then/else that either adds or subtracts
[tr]


Reviewed by Maker, Fire Vortex v1.5, 11th Jan 2011

Required changes:
  • Don't use an array when you create the dummies, don't use a unit array in FV_LOOP
  • You should only initialize the hash if it is null, otherwise it could have been initialized already and stuff saved into it

Suggestions:
  • Use one timer to run all instances, not a separate timer for each instance
  • Angle changing value could be configurable
  • Use only radians for the angles
Maker, Fire Vortex v1.4, 2nd Nov 2011

Are you sure you want the dummies to give vision?
Change the icon's command card position.
Filter out dead units in the damage filter.
In FV_Loop, you're setting variables even when you don't necessarily need them, when duration <= 0.

11th Jul 2011
Bribe

It'd be faster to use a group and use ForGroup
to loop through it, instead of creating/destroying
a timer for every instance. It's also lighter on
RAM because it just uses 1 group instead of ??
timers.
 
Level 12
Joined
Apr 16, 2010
Messages
584
Well first thing that i noticed is that you REALLY NEED to use loops.
You should make another variable for speed of dummies that create vortex.
In third trigger why you subtract HP instead of dealing damage? Use Cause Target Damage.
Looks leakless. I won't vote for now because i didn't test it...
 
Level 12
Joined
Apr 16, 2010
Messages
584
I'll give you a prove that subtracting HP is bad.
When map has a system of kills it informs a player who killed who, so:
  • Game - Display to (All players) for 5.00 seconds the text: (((Name of (Owner of (Killing unit))) + has killed ) + ((Name of (Owner of (Triggering unit))) + for 250 gold!))
when you subtract HP and target is about to die, it won't show the right text, there won't be names in it!
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
I'll give you a prove that subtracting HP is bad.
When map has a system of kills it informs a player who killed who, so:
  • Game - Display to (All players) for 5.00 seconds the text: (((Name of (Owner of (Killing unit))) + has killed ) + ((Name of (Owner of (Triggering unit))) + for 250 gold!))
when you subtract HP and target is about to die, it won't show the right text, there won't be names in it!

Dude I know that, I just forgot...
 
Level 19
Joined
Feb 25, 2009
Messages
2,004

  • Unit - Move FV_Vortex1[FV_Index[2]] instantly to (FV_Point3[FV_Index[2]] offset by (Distance between FV_Point3[FV_Index[2]] and FV_Point4[FV_Index[2]]) towards ((Angle from FV_Point3[FV_Index[2]] to FV_Point4[FV_Index[2]]
Calculate the distance/angle before using it, will save space on the screen

  • Unit Group - Pick every unit in (Units within 1000.00 of (Position of FV_DummyUnit[FV_Index[3]]) matching ((((Matching unit) belongs to an ally of (Owner of FV_DummyUnit[FV_Index[3]])) Equal to False) and (((Matching unit) has buff Fire Vortex HP-MP ) Equal to True))) and do (Actions)
Leaks a location, and again use variable for the group


* Use formulas instead of thousand ITE calls, save time and space

Idea is kinda old and is (I think) done many times around here
2/5 rate, neutral vote.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
Trigger comments:

  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • (Level of Fire Vortex for FV_Caster[FV_Index[2]]) Equal to 1
    • Then - Actions
      • Set FV_DummyDur[FV_Index[2]] = 15.00
    • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Level of Fire Vortex for FV_Caster[FV_Index[2]]) Equal to 2
        • Then - Actions
          • Set FV_DummyDur[FV_Index[2]] = 25.00
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Level of Fire Vortex for FV_Caster[FV_Index[2]]) Equal to 3
            • Then - Actions
              • Set FV_DummyDur[FV_Index[2]] = 35.00
            • Else - Actions
You only support three levels. You could cough up an equation.

->
  • Set FV_DummyDur[FV_Index[2]] = 5 + 10 * level of ability
Why use an array here for the location? No use for that. You also leak the offset point. You're setting and removing target point of ability being cast thrice (or possibly five times), when you should only set and remove it once.
  • Set FV_Point1[FV_Index[2]] = (Target point of ability being cast)
  • Unit - Create 1 Fire Vortex (Rotating) for (Owner of FV_Caster[FV_Index[2]]) at (FV_Point1[FV_Index[2]] offset by FV_Offset1[FV_Index[2]] towards ((Facing of FV_DummyUnit[FV_Index[2]]) + FV_Rotation[FV_Index[2]]) degrees) facing FV_Point1[FV_Index[2]]
  • Custom script: call RemoveLocation(udg_FV_Point1[udg_FV_Index[2]])
The last two if/then/else blocks...what id the ability level is 4? you get less fires than at level 3.

Leaks (x5):
  • Unit - Move FV_Vortex1[FV_Index[2]] instantly to (FV_Point3[FV_Index[2]] offset by (Distance between FV_Point3[FV_Index[2]] and FV_Point4[FV_Index[2]]) towards ((Angle from FV_Point3[FV_Index[2]] to FV_Point4[FV_Index[2]]) + FV_Distance[FV_Index[2]]) degrees), facing (Distance between FV_Point3[FV_Index[2]] and FV_Point4[FV_Index[2]]) degrees
The [4] and [5] dummies might not even exist, yet the locations are still created/removed and the trigger attempts to move the unit. Make a check to see whether the dummy exists or not.

Location leak:
  • Unit Group - Pick every unit in (Units within 1000.00 of (Position of FV_DummyUnit[FV_Index[3]]) matching ((((Matching unit) belongs to an ally of (Owner of FV_DummyUnit[FV_Index[3]])) Equal to False) and (((Matching unit) has buff Fire Vortex HP-MP ) Equal to True))) and do (Actions)
    • Loop - Actions
      • Unit - Cause FV_DummyUnit[FV_Index[3]] to damage (Picked unit), dealing 7.50 damage of attack type Spells and damage type Normal
      • Unit - Set mana of (Picked unit) to ((Mana of (Picked unit)) - 7.50)
      • Special Effect - Create a special effect attached to the overhead of (Picked unit) using Abilities\Spells\Items\ResourceItems\ResourceEffectTarget.mdl
      • Special Effect - Destroy (Last created special effect)


The looping triggers are still on even if there are no instances of the spell going on due to FV_Timer and FV_TimerDam variables.
 
Level 10
Joined
Sep 19, 2011
Messages
527
Nice spell :).

A tip. Maybe you can change the condition exitwhen of your loops:

exitwhen i > FV_LOOP_INDEX()

->

set i = FV_LOOP_INDEX()

loop
// ... actions
set i = i - 1
exitwhen i < 0

By this way, you don't need to call every time to FV_LOOP_INDEX() function.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
Make the function UnitAlive to this:
JASS:
function UnitAlive takes unit u returns boolean
    return not(GetUnitTypeId(u) == 0 and IsUnitType(u, UNIT_TYPE_DEAD))
endfunction

GetUnitTypeId(u) == 0 is needed only when using the same group over time, the group might contain some trace of a unit that has been removed from the game. In this case a new group is enumerated every time, so there is no need for that check.
 
Level 13
Joined
Mar 29, 2012
Messages
530
What a coincidence! Actually, wanted to post this when this spell just uploaded...

I am making a spell named Fire Vortex too, but for a contest...And when i am done...I found a spell with same name just uploaded... :D
 
Top