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

[1.24, vJASS] Fire Ball (RPG style)

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: skeleton
A basic Fire Bolt spell re-made and trigger-enchanted.


Features:
  • Full MUI
  • Leak Free
  • Avoided as many bj's as possible
  • Written in vJASS
  • Ability based on Channel
  • Fast Cast
  • No lag
  • Smooth movement






- Make it GUI-able.


Many thanks to Deuterium xBlackRose and Redscores for helping me optimize the code, remove leaks, make it more user-friendly and showing my mistakes.

Once again, thanks Redscores for reminding me of the CreateGroup function.

It's not necessary to give credits if you use or edit this spell.
Credits are still welcome.

Keywords:
vercas, fire, ball, bolt, fireball, firebolt, spell, skill, flame, throw.
Contents

Fire Bolt V1.04.b6 (Map)

Reviews
21:47, 25th Aug 2009 hvo-busterkomo: The coding is alright for a start, but definitely needs work. 1. Stop switching from degrees and radians. Just stick to radians. 2. Learn to use timers, and not periodic triggers. Look into how to loop through...

Moderator

M

Moderator

21:47, 25th Aug 2009
hvo-busterkomo:
The coding is alright for a start, but definitely needs work.
1. Stop switching from degrees and radians. Just stick to radians.
2. Learn to use timers, and not periodic triggers. Look into how to loop through struct instances.
3. Use the create and onDestroy methods.
 
Level 17
Joined
Mar 17, 2009
Messages
1,349
Deut's Brief Spell Review:

[-]

1. The condition could be inlined, this:
JASS:
private function Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A001' ) ) then
        return false
    endif
    return true
endfunction

Should be:
JASS:
private function Conditions takes nothing returns boolean
    return (GetSpellAbilityId() == 'A001')
endfunction


2. Set Objects' Ids into globals in order to have a more adjustable script:
JASS:
globals
    private constant integer ABIL_ID = 'A001'
endglobals

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

That should apply to everything that has to be adjusted or might be adjusted by the user.


3. Use GetTriggerUnit() instea of GetSpellAbilityUnit().


4. Since in the end you happen to use co-ordinates, directly get the co-ordinates of the unit, instead of getting its location and then the co-ordinates of the location:
JASS:
local location o = GetUnitLoc(GetSpellAbilityUnit())

Should be:
JASS:
local real xt = GetWidgetX(GetTriggerUnit())
local real yt = GetWidgetY(GetTriggerUnit())


5. When you announce the globals in the script, you don't have to add them in the variable editor and you're not obliged to used the udg_ prefix:
JASS:
private group FireBolts


6. Dead units have an HP of 0.405 and below, not 0.
JASS:
private function Filtru_1 takes nothing returns boolean
    return ( GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0 )
endfunction

Should be:
JASS:
private function Filtru_1 takes nothing returns boolean
    return ( GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > .405 )
endfunction


7. These three function could be one:
JASS:
private function Filtru_1 takes nothing returns boolean
    return ( GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0 )
endfunction

private function Filtru_2 takes nothing returns boolean
    return ( IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetEnumUnit())) == true )
endfunction

private function Filtru takes nothing returns boolean
    return Filtru_1() and Filtru_2()
endfunction

Should be:
JASS:
private function Filtru takes nothing returns boolean
    return (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > .405) and (IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetEnumUnit())) == true)
endfunction


8. You're using the Unit Custom Data, although I advice avoiding that, if you still use it you must mention it in some documentation:
JASS:
local real dmg = I2R(GetUnitUserData(GetEnumUnit()))


9. You can include the Inits together in one function:
JASS:
private function Init1 takes nothing returns nothing
    local trigger t = CreateTrigger()
    local trigger trg = CreateTrigger(  )
    local filterfunc f = Filter(function AntiLeak)
    local integer i = 0
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, f)
        set i = i + 1
        exitwhen i >= 16
    endloop
    call TriggerRegisterTimerEvent( trg, 0.01, true )
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction( trg, function Actions2 )
    call TriggerAddAction(t, function Actions1 )
    call DestroyFilter(f)
    set f = null
endfunction


10. You must null locals (except for reals and integers) once you're done with them:
JASS:
call RemoveLocation(o)
set o = null


11. Lacks documentation.


12. Lacks creativity and originality.



[+]

13. MUI


14. Bugless


15. Very minor leaking


16. Simple and useful



[*]

This is a well done spell which functions as it must. Although it lacks creativity, it is definitely useful. Scripting could be improved, yet is very well-done.

Good job vercas! There definitely are other stuff to mention, for I only went of over the basics of Jass and didn't fully read the script to check what's going on. But it seems to be functioning alright even when spammed.
 
Level 21
Joined
Dec 9, 2007
Messages
3,096
Deut's Brief Spell Review:

[-]

1. The condition could be inlined, this:
JASS:
private function Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A001' ) ) then
        return false
    endif
    return true
endfunction
Should be:
JASS:
private function Conditions takes nothing returns boolean
    return (GetSpellAbilityId() == 'A001')
endfunction

2. Set Objects' Ids into globals in order to have a more adjustable script:
JASS:
globals
    private constant integer ABIL_ID = 'A001'
endglobals

private function Conditions takes nothing returns boolean
    return (GetSpellAbilityId() == ABIL_ID)
endfunction
That should apply to everything that has to be adjusted or might be adjusted by the user.


3. Use GetTriggerUnit() instea of GetSpellAbilityUnit().


4. Since in the end you happen to use co-ordinates, directly get the co-ordinates of the unit, instead of getting its location and then the co-ordinates of the location:
JASS:
local location o = GetUnitLoc(GetSpellAbilityUnit())
Should be:
JASS:
local real xt = GetWidgetX(GetTriggerUnit())
local real yt = GetWidgetY(GetTriggerUnit())

5. When you announce the globals in the script, you don't have to add them in the variable editor and you're not obliged to used the udg_ prefix:
JASS:
private group FireBolts

6. Dead units have an HP of 0.405 and below, not 0.
JASS:
private function Filtru_1 takes nothing returns boolean
    return ( GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0 )
endfunction
Should be:
JASS:
private function Filtru_1 takes nothing returns boolean
    return ( GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > .405 )
endfunction

7. These three function could be one:
JASS:
private function Filtru_1 takes nothing returns boolean
    return ( GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0 )
endfunction

private function Filtru_2 takes nothing returns boolean
    return ( IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetEnumUnit())) == true )
endfunction

private function Filtru takes nothing returns boolean
    return Filtru_1() and Filtru_2()
endfunction
Should be:
JASS:
private function Filtru takes nothing returns boolean
    return (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > .405) and (IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetEnumUnit())) == true)
endfunction

8. You're using the Unit Custom Data, although I advice avoiding that, if you still use it you must mention it in some documentation:
JASS:
local real dmg = I2R(GetUnitUserData(GetEnumUnit()))

9. You can include the Inits together in one function:
JASS:
private function Init1 takes nothing returns nothing
    local trigger t = CreateTrigger()
    local trigger trg = CreateTrigger(  )
    local filterfunc f = Filter(function AntiLeak)
    local integer i = 0
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, f)
        set i = i + 1
        exitwhen i >= 16
    endloop
    call TriggerRegisterTimerEvent( trg, 0.01, true )
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction( trg, function Actions2 )
    call TriggerAddAction(t, function Actions1 )
    call DestroyFilter(f)
    set f = null
endfunction

10. You must null locals (except for reals and integers) once you're done with them:
JASS:
call RemoveLocation(o)
set o = null

11. Lacks documentation.


12. Lacks creativity and originality.



[+]

13. MUI


14. Bugless


15. Very minor leaking


16. Simple and useful




[*]


This is a well done spell which functions as it must. Although it lacks creativity, it is definitely useful. Scripting could be improved, yet is very well-done.

Good job vercas! There definitely are other stuff to mention, for I only went of over the basics of Jass and didn't fully read the script to check what's going on. But it seems to be functioning alright even when spammed.
Well it doesn't work as you say...
JASS:
private function Conditions takes nothing returns boolean
    return (GetSpellAbilityId() == 'A001')
endfunction
JASS:
private function Filtru takes nothing returns boolean
    return (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > .405) and (IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetEnumUnit())) == true)
endfunction
Never worked for me.

About the global variable of FireBolts, I forgot to remove it.
I will update this later.
 
Level 15
Joined
Jul 6, 2009
Messages
889
Why wouldn't you use vJASS struct which is MUCH easier?

JASS:
    set Targets = CreateGroup()
    call GroupEnumUnitsInRangeOfLoc(Targets, p, radiustargets, Condition(function Filtru))
Dynamic groups >.> Why not just have 1 global group and clear it at the end (Recycling I think), more better then creating 1 every 0.01.

JASS:
        set boom = AddSpecialEffectLoc( sfxpath, pp )
        call DestroyEffect(boom)
This:
JASS:
call DestroyEffect( AddSpecialEffectLoc( sfxpath, pp ) )

Don't use locations. Use reals, they don't need cleaning.
JASS:
    if ( ITE() ) then
        call UnitDamagePoint( GetEnumUnit(), 0.00, radiusdmg, GetLocationX(p), GetLocationY(p), dmg, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS )
        //^ Splash Damage
        set boom = AddSpecialEffectLoc( sfxpath, pp )
        call DestroyEffect(boom)
        //^ Boom effect
        call RemoveUnit( GetEnumUnit() )
    else
    endif
You don't need the else because you don't do anything.

I note you got future updates as future constant stuff but:
  • Spell rawcode
  • Attacktype
  • damagetype
  • weapontype
  • sfxpath
  • Timer Interval
  • Fireball speed
 
Level 15
Joined
Dec 19, 2007
Messages
1,233
OK but the most important is to make it not damage ally or self
And let the map makers choose the damage (set real = 1 x level of the skill x int) so they can change the 1x to whatever they want (0.5 of int etc).

And put // and what this thing do to make the maker choose what he want it to be
By the way i really like this one and i wish i could use it but it damages ally so i cannot
 
Level 21
Joined
Dec 9, 2007
Messages
3,096
I will fix it as soon as I get my WE and some other applications working again.
I will use a unit group and target damage + floating texts and maybe knockback.
I will attach structs to firebolts to store data.

And I will make a custom function to create a firebolt, so GUI user may use it too, with some variables...
 
Level 21
Joined
Dec 9, 2007
Messages
3,096
The life (real) multiplied by 1000 is the distance.
So 1.00 lifespam of the projectile means 1000 range.
1.50 life means 1500 range.

I working right now on improving it.
The_Witcher helped me a lot to work with the structs.
Custom Value and Life will no longer be a problem.
I'll make the projectile's height 25 and make it crush on cliffs as well.
 


Concept (30):

Idea
2 /10
Complexity
2 /10
Style
4 /10
Comment:
The main idea is totally not impressive, I mean, how freaking much have made this? XD
Effects (10):

All:
7 /10
Comment:
The effect fits the theme and isn't spammed / overused.
Scripting (30):

Speach:
7 /10
Leakage:
9 /10
Effective Coding:
7 /10
Comment:
The coding was ok, I see you can make alot of improvements. But for this spell its ok how you coded.

A bit more explained:
The speach (what you read) could use more comments and
try to make them as a block.

The leakage is good.

The code is very well written, but not perfect. (Just make sure to learn structs and the JESP Rules.

Score: 38/70
Final Score: 2,71 / 5

This spell is lacking in effects and idea, but the coding is ok. [3/5] vote

[ ] +Rep
[ ] Report
(Blank box means no action)

 
The_Witcher's functions were the problem. I fixed it and made the DestroyStruct function faster by adding another member to the struct in order to avoid a loop through possibly 8191 array values.

JASS:
private keyword data

globals
    private data array DATAS
    private integer total = 0
endglobals

private struct data
    unit StructUnit
    real lifespam
    real speed
    real damage
    real ar
    real tr
    integer i
endstruct

private function AttachStruct takes unit ToWhich returns data
    local data dat = data.create()
    set dat.StructUnit = ToWhich
    set dat.i = total
    set DATAS[total] = dat
    set total = total + 1
    return dat
endfunction

private function GetStruct takes unit FromWhich returns data
    local integer i = 0
    local data toReturn = 0
    loop
        exitwhen i >= total
        if DATAS[i].StructUnit == FromWhich then
            set toReturn = DATAS[i]
        endif
        set i = i + 1
    endloop
    return toReturn
endfunction

private function DestroyStruct takes unit WhichUnits returns nothing
    local data dat = GetStruct(WhichUnits)
    set total = total - 1
    set DATAS[dat.i] = DATAS[total]
    set DATAS[dat.i].i = dat.i
    call dat.destroy()
endfunction
 
Last edited:
Level 21
Joined
Dec 9, 2007
Messages
3,096
What is wrong with this code?
It doesn't work.
I tried to make all that stuff a big, fat, struct.

Please help me make it better:
JASS:
library FireBall initializer Init

globals     //        Globals of the Fire Balls and the spell.

    private constant integer    AbilityID           = 'A001' //The Ability used to demonstrate the fireball.
    private constant integer    DummyID             = 'u000' //A dummy unit to keep the effects.
    private constant real       Interval            = 50 //How many times(frames) per second should the movement be executed?

    
    private constant real       SpeedPerSecond      = 600.00    
    //    Amount of range the fire ball moves per second.
    private constant real       LifeSpam            = 2.00
    //    Amount of time the fire ball will fly.
    private constant real       AcquireTargetsRange = 60.00     
    //    Enemy units in this range are considered targets; allow the fire ball to damage more than one unit.
    private constant real       DamageSplashRadius  = 125.00    
    //    Enemy units in this range get damaged
    private constant real       TextTagSize         = 10.00     
    //    The size of the text tags (floating texts) wich show the damage amount.
    private constant real       TextTagVelocity     = 120.00    
    //    The velocity of the text tags (floating texts)
    private constant real       TextTagFadePoint    = 3.00      
    //    At this point the text tag starts fading.
    private constant real       TextTagDeathPoint   = 5.50      
    //    At this point the text tag dies.
    private constant real       TextTagZoffset      = 30.00     
    //    Height of the text tag above the target.
    private constant boolean    TextTagVisible      = true      
    //    I guess you want it visible, don't you? SET TO "false" FOR NO TEXT TAGS
    private constant string     sfxpath             = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
    //    This is the Special Effect created when the fire ball explodes.
            //    I strongly recommend using this effect for the fire ball.
    

    private          integer    Total               = 0
    private          timer      Cycle               = CreateTimer()
    
endglobals

private function Conditions takes nothing returns boolean

    return (GetSpellAbilityId() == AbilityID)
    
endfunction

private constant function AntiLeak takes nothing returns boolean
    return true
endfunction

private function Filtru takes nothing returns boolean
    return (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > .405) and (IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetEnumUnit())) == true)
endfunction



public struct Data
    unit             entity
    real             lifespam
    real             speed
    real             damage
    real             ar
    real             tr
    integer          i
    string           sfx
    
    group            Targets = CreateGroup()
    
    method onDestroy takes nothing returns nothing
        set .entity = null
    endmethod

static method create takes real x, real y, real face, real end, real spd, real dmg, real ar, real tr, string sfxp returns Data
    local Data dat = Data.allocate()
    
    set dat.entity = CreateUnit( GetOwningPlayer(GetTriggerUnit()), DummyID, x, y, face)
    set dat.lifespam = end
    set dat.speed = spd
    set dat.damage = dmg
    set dat.ar = ar
    set dat.tr = tr
    set dat.sfx = sfxp
    
    if Total <= 0 then
        call TimerStart(Cycle, 1.00 / Interval, true, function Data.cycle)
    endif
    set Total = Total + 1

    return dat
endmethod

private static method ITE takes nothing returns boolean
    local Data dat
    
    local integer i

    call ForGroup(dat.Targets, function CountUnitsInGroupEnum)
    set i = bj_groupCountUnits
    if ( i >= 1 ) then
        return true
    endif
    if ( dat.lifespam <= 0.00 ) then
        return true
    endif
    
return false
endmethod

private static method Damage takes nothing returns nothing

    local Data dat

    local unit       u        = GetEnumUnit()
    local real       x        = GetUnitX(u)
    local real       y        = GetUnitY(u)
    local real       dump     = -2000000000
    local texttag    tt       = CreateTextTag()
    
    
        call SetTextTagVelocity(tt, 0, TextTagVelocity * 0.071 / 128 ) //The text will only go up. I used ' * 0.071 / 128' to avoid using a BJ.
        call SetTextTagPermanent(tt, false )
        call SetTextTagFadepoint(tt, TextTagFadePoint )
        call SetTextTagLifespan(tt, TextTagDeathPoint )
        call SetTextTagVisibility(tt, TextTagVisible )
        call SetTextTagPosUnit(tt, u, TextTagZoffset)
        if ( GetUnitAbilityLevel(u, 'Avul') == 0 ) and ( GetUnitAbilityLevel(u, 'Aloc') == 0 ) then
            call SetTextTagText(tt, ( "Damage: " + ( I2S ( R2I (dat.damage) ) + " " ) ), TextTagSize * 0.023 / 10 )
            call SetTextTagColor(tt, 230, 100, 150, 255 )
            //                        R    G    B    Alpha
        elseif ( GetUnitAbilityLevel(u, 'Avul') == 1 ) then
            call SetTextTagText(tt, "Damage: none", TextTagSize * 0.023 / 10 )
            call SetTextTagColor(tt, 230, 200, 150, 255 )
            //                        R    G    B    Alpha
        elseif ( GetUnitAbilityLevel(u, 'Aloc') == 1 ) and GetUnitTypeId(u) != DummyID then
            call SetTextTagText(tt, "Missed", TextTagSize * 0.023 / 10 )
            call SetTextTagColor(tt, 230, 200, 150, 255 )
            //                        R    G    B    Alpha
            set dump = dat.damage
            set dat.damage = 0
        elseif GetUnitTypeId(u) == DummyID then
            call SetTextTagText(tt, "Collided", TextTagSize * 0.023 / 10 )
            call SetTextTagColor(tt, 150, 200, 230, 255 )
            //                        R    G    B    Alpha
        endif
        
    
    call UnitDamageTarget( dat.entity, u, dat.damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS )
    if dump == -2000000000 then
        set dat.damage = dump
    endif
    
    set u = null
    
endmethod

private static method cycle takes nothing returns nothing
    local Data dat

    local real       angle    = GetUnitFacing( dat.entity )
    local real       dist     = dat.speed / Interval
    local real       x        = GetUnitX(dat.entity) + dist * Cos(angle * bj_DEGTORAD)
    local real       y        = GetUnitY(dat.entity) + dist * Sin(angle * bj_DEGTORAD)
    

    //Done with setup.
    
    set dat.lifespam = (dat.lifespam - (100 / Interval ) )
    call SetUnitPosition( dat.entity, x, y )
    
    call GroupEnumUnitsInRangeOfLoc(dat.Targets, Location(x, y), dat.ar, Condition(function Filtru))
    call DestroyBoolExpr( Condition( function Filtru ) )
    
    if ( dat.ITE() ) then
        call GroupEnumUnitsInRangeOfLoc(dat.Targets, Location(x, y), dat.tr, Condition(function Filtru))
        call DestroyBoolExpr( Condition( function Filtru ) )
        call ForGroup( dat.Targets, function Data.Damage )
        call DestroyEffect( AddSpecialEffect( dat.sfx, x, y ) )

        call RemoveUnit( dat.entity )
        call dat.destroy()
        set Total = Total - 1
        
        if Total <= 0 then
            call PauseTimer(Cycle)
        endif
        
    endif
    
    call GroupClear(dat.Targets)
    set dat.entity = null
    
endmethod

endstruct

private function FBCreate takes nothing returns nothing
    local integer damage = GetHeroInt(GetTriggerUnit(), true) * GetHeroLevel(GetTriggerUnit()) * GetUnitAbilityLevel(GetTriggerUnit(), AbilityID)

    local Data dat = Data.create(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), GetUnitFacing(GetTriggerUnit()), LifeSpam, SpeedPerSecond, damage, DamageSplashRadius, AcquireTargetsRange, sfxpath)
endfunction

function Init takes nothing returns nothing

    local trigger    t        = CreateTrigger()
    local filterfunc f        = Filter(function AntiLeak)
    local integer    i        = 0
    
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, f)
        set i = i + 1
        exitwhen i >= 16
    endloop
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction(t, function FBCreate )
    call DestroyFilter(f)
    set f = null
endfunction

//===========================================================================
//~~~~~~  End of movement
endlibrary

And thisone has a damn bug, all the fireballs explode at the same time.
When the first fireball(s) explode, others explode to. The ones spawned after that explode instantly.

JASS:
library FireBall initializer Init

globals     //        Globals of the Fire Balls and the spell.

    private constant integer    AbilityID           = 'A001'
    private constant integer    DummyID             = 'u000'

    
    private constant real       SpeedPerSecond      = 600.00    
    //    Amount of range the fire ball moves per second.
    private constant real       LifeSpam            = 2.00
    //    Amount of time the fire ball will fly.
    private constant real       AcquireTargetsRange = 60.00     
    //    Enemy units in this range are considered targets; allow the fire ball to damage more than one unit.
    private constant real       DamageSplashRadius  = 125.00    
    //    Enemy units in this range get damaged
    private constant real       TextTagSize         = 10.00     
    //    The size of the text tags (floating texts) wich show the damage amount.
    private constant real       TextTagVelocity     = 120.00    
    //    The velocity of the text tags (floating texts)
    private constant real       TextTagFadePoint    = 3.00      
    //    At this point the text tag starts fading.
    private constant real       TextTagDeathPoint   = 5.50      
    //    At this point the text tag dies.
    private constant real       TextTagZoffset      = 30.00     
    //    Height of the text tag above the target.
    private constant boolean    TextTagVisible      = true      
    //    I guess you want it visible, don't you? SET TO "false" FOR NO TEXT TAGS
    private constant string     sfxpath             = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
    //    This is the Special Effect created when the fire ball explodes.
            //    I strongly recommend using this effect for the fire ball.
    

    private          group      FBs                 = CreateGroup()
    private          group      Targets             = CreateGroup()
    private          unit       toPass
    
endglobals

private keyword data

globals
    private          data array DATAS
    private          integer    total               = 0
endglobals

private struct data
    unit             StructUnit
    real             lifespam
    real             speed
    real             damage
    real             ar
    real             tr
    integer          i
    string           sfx
    
    private method onDestroy takes nothing returns nothing
        set .StructUnit = null
    endmethod
endstruct

private function AttachStruct takes unit ToWhich returns data
    local data       dat      = data.create()
    set dat.StructUnit = ToWhich
    set dat.i = total
    set DATAS[total] = dat
    set total = total + 1
    return dat
endfunction

private function GetStruct takes unit FromWhich returns data
    local integer    i        = 0
    local data       toReturn = 0
    loop
        exitwhen i >= total
        if DATAS[i].StructUnit == FromWhich then
            set toReturn = DATAS[i]
        endif
        set i = i + 1
    endloop
    return toReturn
endfunction

private function DestroyStruct takes unit WhichUnit returns nothing
    local data       dat      = GetStruct(WhichUnit)
    set total = total - 1
    set DATAS[dat.i] = DATAS[total]
    set DATAS[dat.i].i = dat.i
    call dat.destroy()
endfunction
//======================================================================================================================================================
//======================================================================================================================================================
//======================================================================================================================================================
//~~~~~~ Start of creation
//===========================================================================



private function Conditions takes nothing returns boolean

    return (GetSpellAbilityId() == AbilityID)
    
endfunction

private function Create takes nothing returns nothing

    local real       ox       = GetUnitX(GetTriggerUnit())
    local real       oy       = GetUnitY(GetTriggerUnit())
    local real       px       = GetLocationX(GetSpellTargetLoc())
    local real       py       = GetLocationY(GetSpellTargetLoc())
    
    local real       angle    = bj_RADTODEG * Atan2(py - oy, px - ox)
    local real       x        = ox + 15 * Cos(angle * bj_DEGTORAD)
    local real       y        = oy + 15 * Sin(angle * bj_DEGTORAD)
    
    local unit       u        = CreateUnit( GetOwningPlayer(GetTriggerUnit()), DummyID, x, y, angle)
    
    local data       dat      = AttachStruct(u)
    
    set dat.lifespam = GetRandomReal( LifeSpam - 0.50, LifeSpam - 0.50 )
    set dat.speed = GetRandomReal(SpeedPerSecond - 200.00, SpeedPerSecond + 200.00)
    set dat.damage = ( GetHeroInt(GetSpellAbilityUnit(), true) * GetUnitAbilityLevel(GetSpellAbilityUnit(), AbilityID) )
    set dat.ar = GetRandomReal(AcquireTargetsRange - 20.00, AcquireTargetsRange + 20.00)
    set dat.tr = GetRandomReal(DamageSplashRadius - 25.00, DamageSplashRadius + 25.00)
    set dat.sfx = sfxpath
    
    call GroupAddUnit( FBs, u )
    
    set u = null

endfunction

private constant function AntiLeak takes nothing returns boolean
    return true
endfunction


//===========================================================================
//~~~~~~  End of creation
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~ Start of movement
//===========================================================================


private function Filtru takes nothing returns boolean

    return (GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > .405) and (IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetEnumUnit())) == true)

endfunction

private function ITE takes nothing returns boolean

    local data       dat = GetStruct(GetEnumUnit())

    call ForGroup(Targets, function CountUnitsInGroupEnum)
    if ( bj_groupCountUnits >= 1 ) then
        return true
    endif
    if ( dat.lifespam <= 0.00 ) then
        return true
    endif
    
set bj_groupCountUnits = 0
return false
endfunction

private function Damage takes nothing returns nothing

    local unit       u        = GetEnumUnit()
    local real       x        = GetUnitX(u)
    local real       y        = GetUnitY(u)
    local real       dump
    local data       dat      = GetStruct( toPass )
    local texttag    tt       = CreateTextTag()
    
    
        call SetTextTagVelocity(tt, 0, TextTagVelocity * 0.071 / 128 ) //The text will only go up. I used ' * 0.071 / 128' to avoid using a BJ.
        call SetTextTagPermanent(tt, false )
        call SetTextTagFadepoint(tt, TextTagFadePoint )
        call SetTextTagLifespan(tt, TextTagDeathPoint )
        call SetTextTagVisibility(tt, TextTagVisible )
        call SetTextTagPosUnit(tt, u, TextTagZoffset)
        if ( GetUnitAbilityLevel(u, 'Avul') == 0 ) and ( GetUnitAbilityLevel(u, 'Aloc') == 0 ) then
            call SetTextTagText(tt, ( "Damage: " + ( I2S ( R2I (dat.damage) ) + " " ) ), TextTagSize * 0.023 / 10 )
            call SetTextTagColor(tt, 230, 100, 150, 255 )
            //                        R    G    B    Alpha
        elseif ( GetUnitAbilityLevel(u, 'Avul') == 1 ) then
            call SetTextTagText(tt, "Damage: none", TextTagSize * 0.023 / 10 )
            call SetTextTagColor(tt, 230, 200, 150, 255 )
            //                        R    G    B    Alpha
        elseif ( GetUnitAbilityLevel(u, 'Aloc') == 1 ) and GetUnitTypeId(u) != DummyID then
            call SetTextTagText(tt, "Missed", TextTagSize * 0.023 / 10 )
            call SetTextTagColor(tt, 230, 200, 150, 255 )
            //                        R    G    B    Alpha
            set dump = dat.damage
            set dat.damage = 0
        elseif GetUnitTypeId(u) == DummyID then
            call SetTextTagText(tt, "Collided", TextTagSize * 0.023 / 10 )
            call SetTextTagColor(tt, 150, 200, 230, 255 )
            //                        R    G    B    Alpha
        endif
        
    
    call UnitDamageTarget( toPass, u, dat.damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS )
    set dat.damage = dump
    
    set u = null
    
endfunction

private function Move takes nothing returns nothing

    local unit       u        = GetEnumUnit()
    local data       dat      = GetStruct( u )

    local real       angle    = GetUnitFacing( u )
    local real       dist     = dat.speed / 50
    local real       x        = GetUnitX(u) + dist * Cos(angle * bj_DEGTORAD)
    local real       y        = GetUnitY(u) + dist * Sin(angle * bj_DEGTORAD)
    

    //Done with setup.
    
    set dat.lifespam = (dat.lifespam - 0.02)
    call SetUnitPosition( u, x, y )
    
    call GroupEnumUnitsInRangeOfLoc(Targets, Location(x, y), dat.ar, Condition(function Filtru))
    call DestroyBoolExpr( Condition( function Filtru ) )
    
    if ( ITE() ) then
        set toPass = u
        call GroupEnumUnitsInRangeOfLoc(Targets, Location(x, y), dat.tr, Condition(function Filtru))
        call DestroyBoolExpr( Condition( function Filtru ) )
        call ForGroup( Targets, function Damage )
        call DestroyEffect( AddSpecialEffect( sfxpath, x, y ) )

        call DestroyStruct( u )
        call RemoveUnit( u )
        
    endif
    
    call GroupClear(Targets)
    set u = null
    
endfunction

private function Pick takes nothing returns nothing

    call ForGroup( FBs, function Move )
    
endfunction

function Init takes nothing returns nothing

    local trigger    t        = CreateTrigger()
    local filterfunc f        = Filter(function AntiLeak)
    local integer    i        = 0
    
    local trigger    trg      = CreateTrigger(  )
    
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, f)
        set i = i + 1
        exitwhen i >= 16
    endloop
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction(t, function Create )
    call DestroyFilter(f)
    set f = null
    
    call TriggerRegisterTimerEvent( trg, 0.02, true )
    call TriggerAddAction( trg, function Pick )
    
endfunction

//===========================================================================
//~~~~~~  End of movement
endlibrary
 
Level 15
Joined
Dec 19, 2007
Messages
1,233
Please help me improve this spell.

Be tough when commenting.

Suggestions and ideas for improvement and functionalities are welcome!

Review: + are benefit while - are disadvantage


+ The code has no leak
+ It damage only enemy
+ the map maker can edit freely thanks for comments

- it spread when you throw 5 fireballs to damage the same spot -
make the player think he has no control on the mana he wasted.
-still lack creativity since you can see that this spell has one effect - nuke that damage the first units he meets.

recommended:
fix it so the fireballs explode all same place and not random range.
make more ways of creativity such as: make the fireball do something if something happens. (on heroes it do double damage, if random number between 1 and 2 (50) is 1 then do double damage, make it explode on towers and make no damage will definitely make the best of it since - it is the best idea ever when u fight with mass towers nearby and it isn't complected as terrain)

my review: 4/5 (spread of the skill need to be fixed as soon as possible)
note: I will use it in my map that will be released on the near future in this site, will give you credit and a link to download it ^.^.
I will visit again soon to see it in better versions.
 
Level 21
Joined
Dec 9, 2007
Messages
3,096
Yeah...
Look above, what did I do there?
They COLLIDE. The floating text shows "Collided".
Using the same acquisition range as splash means one-target damage.
By the way, it jumps over ally units and locust targets...
My scripts apparently don't work. I need someone to help me fix them before uploading...
 
Top