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

Flames from Heaven

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
Flames of Heaven - [R]
Using the solar power from the sun, the firelord takes the energy and creates 12 fireballs that fly towards him, and explode, knocking back units near him, and dealing damage.
Level 1: Deals 10 damage per fireball, 100 knockback.
Level 2: Deals 15 damage per fireball, 150 knockback.
Level 3: Deals 20 damage per fireball, 200 knockback.
Level 4: Deals 25 damage per fireball, 250 knockback.

Credits to Jesus4lyf from thehelper.net for his system Key Timers 2.
Credits to Vexorian for JNGP.

Requires Jass Newgen Pack and Key Timers 2

Updated: Optimized code a bit,
In the very near future: A smooth knockback.

A fun spell to make. Heres the code.
JASS:
scope FlamesofHeaven initializer Init
    globals
    
//The ability ID of the spell - Flames of Heaven
        private constant integer ABILITY_CODE = 'A000'
        
//The Unit ID of the dummy unit
        private constant integer DUMMY_ID = 'h000'
        
//The offset per movement
        private constant real OFFSET = 20
        
//The distance the units are created away from the caster.
        private constant integer DISTANCE = 900
        
//The special effect to be played for the explosion
        private constant string EXPLOSION = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"
        
//The attack type for the damage.
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL

//The damage type for the damage.
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL

//The weapon type for the damage.
        private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS

    endglobals
    
    private function DAMAGE takes integer lvl returns real
        return 5 + (lvl * 5.)
    endfunction
    
    private function NUM_DUMMIES takes integer lvl returns real
        return 12 + (lvl * 0.)
    endfunction
    
    private function AOE takes integer lvl returns real
        return 250 + (lvl * 0.)
    endfunction
    
    private function KNOCKBACK takes integer lvl returns real
        return 50 + (lvl * 50.)
    endfunction
    
    private function AOE_Expr takes nothing returns boolean
        local Flames f = Flames.FInteger
        return f.Caster != GetFilterUnit() and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false
    endfunction

    struct Flames
        unit Caster
        unit Dummyunit
        real Angle
        real Offset
        static integer FInteger
    endstruct
    
    private function DamageUnits takes nothing returns nothing
        local Flames f = Flames.FInteger
        local real damage = DAMAGE(GetUnitAbilityLevel(f.Caster, ABILITY_CODE))
        local real knockback = KNOCKBACK(GetUnitAbilityLevel(f.Caster, ABILITY_CODE))
        local real casterx = GetUnitX(f.Caster)
        local real castery = GetUnitY(f.Caster)
        local real targetx = GetUnitX(GetEnumUnit())
        local real targety = GetUnitY(GetEnumUnit())
        local real dx = targetx - casterx
        local real dy = targety - castery
        local real tempreal = SquareRoot( dx * dx + dy * dy )
        local real offsetx
        local real offsety
        local real angle = bj_RADTODEG * Atan2(targety - castery, targetx - casterx)
        call UnitDamageTarget(f.Caster, GetEnumUnit(), damage, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
    loop
        exitwhen tempreal >= knockback
        set offsetx = targetx + 5 * Cos(angle * bj_DEGTORAD)
        set offsety = targety + 5 * Sin(angle * bj_DEGTORAD)
        call SetUnitX(GetEnumUnit(), offsetx)
        call SetUnitY(GetEnumUnit(), offsety)
        set tempreal = tempreal + 5
    endloop
    endfunction
    
    private function GroupEnum takes group g, real x, real y, real radius returns group
        call GroupEnumUnitsInRange( g, x, y, radius, Condition(function AOE_Expr))
        return g
    endfunction
    
    private function MovingFlames takes nothing returns boolean
        local Flames f = KT_GetData()
        local real offsetx
        local real offsety
        local group tempgroup
        local integer SENumber = 0
        local real radius = AOE(GetUnitAbilityLevel(f.Caster, ABILITY_CODE))
        local real casterx = GetUnitX(f.Caster)
        local real castery = GetUnitY(f.Caster)
        local real dummyx = GetUnitX(f.Dummyunit)
        local real dummyy = GetUnitY(f.Dummyunit)
        local real dx = casterx - dummyx
        local real dy = castery - dummyy
        local real tempreal = SquareRoot(dx * dx + dy * dy)
        local boolean cond = (tempreal >= 70)
    if cond then
        set f.Angle = f.Angle - 4
        set f.Offset = f.Offset - 10
        set offsetx = casterx + f.Offset * Cos(f.Angle * bj_DEGTORAD)
        set offsety = castery + f.Offset * Sin(f.Angle * bj_DEGTORAD)
        call SetUnitX(f.Dummyunit, offsetx)
        call SetUnitY(f.Dummyunit, offsety)
        call SetUnitFlyHeight(f.Dummyunit, GetUnitFlyHeight(f.Dummyunit) - 1, 0)  
    else
        set tempgroup = CreateGroup()
        call GroupEnum(tempgroup, casterx, castery, radius)
        set Flames.FInteger = f
        call ForGroup(tempgroup, function DamageUnits)
    loop
        exitwhen SENumber >= 2
        set offsetx = casterx + (20 * SENumber) * Cos((180 * SENumber) * bj_DEGTORAD)
        set offsety = castery + (20 * SENumber) * Sin((180 * SENumber) * bj_DEGTORAD)
        call DestroyEffect(AddSpecialEffect(EXPLOSION, offsetx, offsety))
        set SENumber = SENumber + 1
    endloop
        call RemoveUnit(f.Dummyunit)
        call f.destroy()
        return true
    endif
        return false
    endfunction
    
    private function CreateStructs takes unit u, player p, real a returns nothing
        local Flames f = Flames.create()
        local real x = GetUnitX(GetTriggerUnit())
        local real y = GetUnitY(GetTriggerUnit())
        local real offsetx = x + DISTANCE * Cos(a)
        local real offsety = y + DISTANCE * Sin(a)
        set f.Caster = u
        set f.Angle = a
        set f.Offset = DISTANCE
        set f.Dummyunit = CreateUnit( p, DUMMY_ID, offsetx, offsety, a)
        call KT_Add( function MovingFlames, f, 0.03 )
    endfunction

    private function OnCast takes nothing returns nothing
        local real num_dummies = NUM_DUMMIES(GetUnitAbilityLevel(GetTriggerUnit(), ABILITY_CODE))
        local integer index = 0
    if GetSpellAbilityId() == ABILITY_CODE then
    loop
        exitwhen index == num_dummies
        call CreateStructs(GetTriggerUnit(), GetTriggerPlayer(), (360 / 12) * index)
        set index = index + 1
    endloop
    endif
    endfunction

    //===========================================================================
    function Init takes nothing returns nothing
        local trigger t = CreateTrigger(  )
        local integer index = 0
        call TriggerAddAction( t, function OnCast )
    loop
        exitwhen index == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent( t, Player(index), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set index = index + 1
    endloop
    endfunction
endscope

Keywords:
vJass, Key Timers 2, Fire, Firelord, Explosion, Knockback, AOE, Spiral, Circle, Flame
Contents

Flames from Heaven (Map)

Reviews
12.12 IcemanBo: For long time as NeedsFix. Rejected. 19th Oct 2011 Bribe: Don't inline TRAUEBJ. If you care about efficiency you could use RegisterSpellEffectEvent. Your indentation got screwed up with a couple of your loops. No reason to...

Moderator

M

Moderator

12.12
IcemanBo: For long time as NeedsFix. Rejected.

19th Oct 2011
Bribe: Don't inline TRAUEBJ. If you care about efficiency you could use RegisterSpellEffectEvent.

Your indentation got screwed up with a couple of your loops.

No reason to use KT2 for this. Even TimerUtils would be a better choice here, although you should be using a single timer with a stack loop (or T32, CTL, there are so many better options).

FirstOfGroup loops would be better options here.
 
JASS:
/*
WEAPON_TYPE_WHOKNOWS == null

and setting it for triggered damages is pretty useless I think...

the function CreateStructs can just be merged with the create method of the struct, to reduce function calls

why use a custom GroupEnum which only does what the native GroupEnum does? its a waste of performance... 
not to say that the "return g" is also a waste of performance since you don't really use it...

instead of using Action, use Condition coz its faster...

and maybe fix the indentation of your code, its not that good looking to the eyes

and give credits to the person who made KT2...

I won't rate or vote for now...
*/
 
Level 15
Joined
Jul 6, 2009
Messages
889
I think that the biggest difference between an intermediate coder and a good one is the ability to structure and design their code properly.

1) You don't need to inline the TriggerRegisterAnyUnitEventBJ, it's perfectly fine to use as speed is unimportant during initialisation and it looks cleaner to have only three lines of code.

2) Why do you use triggeractions? Especially as a condition-action type. You should change it to a triggercondition.

3) You don't need to set your variables before the line that checks if the ability is cast your ability. You set index to zero, and num_dummies to something. You set two things on every single ability that is cast. That's inefficient and irrelevant.

4) You divide 360 by 12. It should be divided by how many dummy units there will be.

5) You shouldn't be converting degrees to radians, and vice versa so much. Just use radians throughout the entire code, and convert to degrees only for unit facing.

6) Function arguments should have more meaningful names. "a", "p", and "u" do not tell the user anything. "angle", "owningPlayer", and "sourceUnit" do though.

7) Relating to point 5, if you removed the line that started the MovingFlames function, you will relise you create the dummy units in a completely wrong coordinate.

8) You should store functions that are called frequently into variables. In the OnCast function, you call GetTriggerUnit() up to 36 times.

9) Why do you actually use GetTriggerUnit() in your CreateStruct function? Why not use the unit argument you have.

10) Rather than using a function, why not just use the struct's create method? It looks far neater and organised.

11) The timer period (0.03) should be a configurable constant real global.

12) Rather than having an instance for each dummy unit, have it all in one. It is more efficient as you are calling the callback function only once compared to twelve times.

13) Rather than checking the distance between the dummy and the source unit, have a variable ("distanceRemaining"?) that decrements. Once that variable hits 0 (or 70), that is when you consider the ability 'done'.

14) Indent properly.

15) You leak a group. You never DestroyGroup on tempgroup. Infact, you shouldn't even be using dynamic groups. Use a global group as you only do everything instantly. (What's that bj group that Bribe suggests to use O_O?)

16) Your GroupEnum function is completely pointless. It's equivalent to a BJ function.

17) Rather than declaring another local Flames variable inside AOE_Expr, just use Flames.FInteger directly. So "Flames.FInteger.Caster".

18) You can privatise the Flames struct. You can move AOE_Expr under the Flames struct or use a keyword. Although I'd advise doing the former.

19) Move the DamageUnits inside of the AOE_Expr. No need for to enumerate the units and then apply a ForGroup. Just do everything inside the enumeration.

20) Relating to number 8, you can store GetEnumUnit() into a local variable.

21) Why do you use a loop to knock the target unit back? It's all instant, just set the unit's X/Y in two function lines, rather than this loop nonsense.

22) Store the AOE of the ability as a struct member so you don't call it every 0.03 seconds.

23) What's up with decreasing the height of the unit by 33.33 every second? If you're going to decrease height, make the rate a configurable constant global.

24) All your configurable functions should be constant. DAMAGE, NUM_DUMMIES, etc.
 
Level 15
Joined
Jul 6, 2009
Messages
889
Anyway when you use map optimizer all constant function calls will be replaced with that value what could be 'A000'. By doing this you don' t lose any performance and still keep flexibility to apply new changes very fast.

Apparently this. I don't even know O_O. But it works xP

Also: "Flames of Heaven - [R] [Level %d]" ~ You might want to fix that up.
Oh yes, and as Adiktuz said, give credit to Jesus4Lyf.
 
Level 6
Joined
Aug 20, 2009
Messages
95
finally managed to test it...

some more suggestions:
-maybe add the possibility of minor damage upon contact with the flames
-maybe find a knockback system so that the knockback isn't instant...

Yes, I need to fix the Knockback, I was a little lazy, so I didn't. Ill implement one shortly, and also fix the other things.
Also, I didn't know about the GroupEnum, I was told it returned nothing so there needed to be a function call.
And It gives a syntax error when dealing with Flames.FInteger.Caster
 
Last edited:
JASS:
 private function NUM_DUMMIES takes integer lvl returns real
        return 12 + (lvl * 0.)
    endfunction
    
    private function AOE takes integer lvl returns real
        return 250 + (lvl * 0.)
    endfunction

These should be constant.

EDIT: On second thought, if you want to make AoE vary per level, don't use 0. If you wanted it to be constant, you could've just said:

JASS:
private constant real AoE = 250.00

Same for the number of dummies.

I think i understand why you made them this way. You wanted to make them constant, but by using (lvl * 0.), you're giving users a chance to easily configure the AoE to make it level dependent :D

5/5 for your dedication to easy configuration, but you should work on efficiency. Just listen to Adiktuz and xBlackRose. They know more about vJASS than i do.
Infact, i'm learning right now :D
 
Top