• 🏆 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 compatible] Firy Stones v1.01

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
This spell is a bit easier to do thrn my last spell. I done it for a map I'm working on at the moment. The spell should Protect the caster and deal some AoE damage. And the effects should have something to do with fire.

Spell Description: Creates a ring of fire balls around the caster, they rotates around him for serval seconds. After that time they fly away from the caster. Each fire ball increases the armor of the caster by 1. Should the fire balls hit something the unit takes damage and is knocked away. Each ball can collide 3 times with an enemy. Each time the ball collides he deals lower damage and the knockback and the fly range is decreased. When a fire ball dies he deals some AoE damage and knocks all enemies around away. The fireballs don't hit Buildings, allies and Magic immune units.

PLEASE look into the script, because you can change many things in the globals. For example you can create one fire orb, which rotates around the hero in some distance and knocks all enemies away and decrease their armor. After serval seconds the orb disappears.

AND PLEASE give credits if you use this spell.

Credits: Vexorian for BoundSentinel and TimerUtils
Antiarf for IsTerrainWalkable
Rising_Dusk for Knockback, LastOrder and UnitIndexingUtils

JASS:
scope FiryStones initializer init

//*************************************************************************************************************//
//                                                   Firy Stones                                               //
//                                                        by                                                   //
//                                                      cedi                                                   //
//                                                                                                             //
//                                         needs: TimerUtils by Vexorian                                       //
//                                                Bound Sentinel by Vexorian                                   //
//                                                IsTerrainWalkable by Antitarf                                //
//                                                Dummy Model by                                               //
//                                                Knockback by Rising_Dusk                                     //
//                                                LastOrder by Rising_Dusk                                     //
//                                                UnitIndexingUtils by Rising_Dusk                             //
//*************************************************************************************************************//

//For use, copy the trigger to your map, copy the dummy create a spell and adjust the values below.

globals
    //ID of the dummy spell
    private constant integer SPELL_ID            = 'A000'
    //ID of the DoT spell
    private constant integer DOT_ID              = 'A001'
    //ID of the enstrenghten spell
    private constant integer ENSTRENGHT_ID       = 'A002'
    //ID of the dummy
    private constant integer DUMMY_ID            = 'h000'
    //How many levels does your spell have?
    private constant integer SPELL_LEVEL_COUNT   = 4
    //Amount of missiles
    private constant integer MISSILE_COUNT       = 8
    //How much armor should each missile increase? Beware Max of 40 / MISSILE COUNT.
    private constant integer ARMOR_PER_MISSILE   = 1
    //The move timer interval
    private constant real    TIMER_INTERVAL      = 0.02
    //Interval of the unit check
    private constant real    PICK_INTERVAL       = 0.10
    //Missile speed in ms
    private constant real    SPEED               = 700.00
    //Damage decrease per collision in percent
    private constant real    DAMAGE_LOOSE        = 0.15
    //Range decrease per collision in percent
    private constant real    RANGE_LOOSE         = 0.15
    //Knockback speed decrease per collision in percent
    private constant real    KNOCKBACK_LOOSE     = 0.15
    //Start turn speed.
    private constant real    SPEED_ANGLE         = 3.00
    //Increase of the turn speed.
    private constant real    SPEED_ANGLE_INC     = 0.01
    //Maximal turn speed.
    private constant real    MAX_SPEED_ANGLE     = 6.00
    //Distance between missiles and caster.
    private constant real    START_DISTANCE      = 150.00
    //Delay of the missiles.
    private constant real    MAX_SHOOT_DELAY     = 5.00
    //Size of the missiles 1.00 == 100%
    private constant real    SIZE                = 1.00
    //Collisions size of the missiles.
    private constant real    COLLISIONS_SIZE     = 75.00
    //Multipler of the damage for the end aoe damage.
    private constant real    AOE_DAMAGE_INC      = 3.00
    //Decrement of the knockback speed.
    private constant real    DECREMENT           = 2.00
    //Fly height of the missiles.
    private constant real    HEIGHT              = 50.00
    //Model of the missiles.
    private constant string  STONE_MODEL         = "Abilities\\Weapons\\BallsOfFireMissile\\BallsOfFireMissile.mdl"
    //Order string of the DoT spell.
    private constant string  DOT_ORDER_STRING    = "acidbomb"
    //Model when a missile is created.
    private constant string  CREATE_EFFECT       = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
    //Model when a missile dies.
    private constant string  DESTROY_EFFECT      = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
    //model when the missiles flies away.
    private constant string  SHOOT_EFFECT        = ""
    //Model when the missiles deal damage
    private constant string  DAMAGE_EFFECT       = ""
    //Should the missiles turn to when they fly away?
    private constant boolean TURN                = false

    // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM // SYSTEM //
    private integer  array   MAX_UNIT_HITTED[SPELL_LEVEL_COUNT]
    private real     array   RANGE[SPELL_LEVEL_COUNT]
    private real     array   AOE[SPELL_LEVEL_COUNT]
    private real     array   DAMAGE[SPELL_LEVEL_COUNT]
    private real     array   KNOCKBACK_RANGE[SPELL_LEVEL_COUNT]
    private real     array   ROTATE_TIME[SPELL_LEVEL_COUNT]
    private real             REAL_SPEED = SPEED * TIMER_INTERVAL
    private real             TEMP_REAL
    private group            TEMP_GROUP = CreateGroup()
    private player           TEMP_PLAYER
endglobals

//Max amount of units hit by one missile.
private function SET_MAX_UNIT_HITTED takes nothing returns nothing
    set MAX_UNIT_HITTED[1] = 2
    set MAX_UNIT_HITTED[2] = 3
    set MAX_UNIT_HITTED[3] = 4
    set MAX_UNIT_HITTED[4] = 5
endfunction

//Max range.
private function SET_RANGE takes nothing returns nothing
    set RANGE[1] = 700.00
    set RANGE[2] = 750.00
    set RANGE[3] = 800.00
    set RANGE[4] = 850.00
endfunction

//AoE of the end damage
private function SET_AOE takes nothing returns nothing
    set AOE[1] = 150.00
    set AOE[2] = 200.00
    set AOE[3] = 250.00
    set AOE[4] = 300.00
endfunction

//Damage dealt by the missiles.
private function SET_DAMAGE takes nothing returns nothing
    set DAMAGE[1] = 150.00
    set DAMAGE[2] = 200.00
    set DAMAGE[3] = 250.00
    set DAMAGE[4] = 300.00
endfunction

//Not real, its the knockback speed in ms.
private function SET_KNOCKBACK_RANGE takes nothing returns nothing
    set KNOCKBACK_RANGE[1] = 350.00
    set KNOCKBACK_RANGE[2] = 400.00
    set KNOCKBACK_RANGE[3] = 550.00
    set KNOCKBACK_RANGE[4] = 600.00
endfunction

//The missiles rotate x seconds around the caster.
private function SET_ROTATE_TIME takes nothing returns nothing
    set ROTATE_TIME[1] = 5.00
    set ROTATE_TIME[2] = 6.00
    set ROTATE_TIME[3] = 7.00
    set ROTATE_TIME[4] = 8.00
endfunction

//*************************************************************************************************************//
//                                                    SYSTEM                                                   //
//*************************************************************************************************************//

private function IsPossibleTarget takes nothing returns boolean
    return GetWidgetLife( GetFilterUnit() ) > 0.405 and IsUnitEnemy( GetFilterUnit(), TEMP_PLAYER ) and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false
endfunction

struct Stone
    unit missile = null
    effect model = null
    integer hitcounter = 0
    integer place = 0
    real dist = START_DISTANCE
    real rotatetime = 0.00
    real range = 0.00
    real angle = 0.00
    real speedangle = SPEED_ANGLE
    real vx = 0.00
    real vy = 0.00
    real dmg = 0.00
    real kbrange = 0.00
    real ptime = 0.00
    group hits = null
    boolean rotate = true
    FiryStone root = 0
    
    private method Cast takes unit u returns nothing
        local unit dummy = CreateUnit( GetOwningPlayer( .missile ), DUMMY_ID, GetUnitX( u ), GetUnitY( u ), 0.00 )
        call UnitAddAbility( dummy, DOT_ID )
        call SetUnitAbilityLevel( dummy, DOT_ID, .root.level )
        call IssueTargetOrder( dummy, DOT_ORDER_STRING, u )
        call UnitApplyTimedLife( dummy, 'BTLF', 2.00 )
        set dummy = null
        set u = null
    endmethod
    
    private method Dealdamage takes unit u, real x, real y returns nothing
        local real angle = Atan2(GetUnitY( u ) - y, GetUnitX( u ) - x)
        call .Cast( u )
        call UnitDamageTarget( .missile, u, .dmg, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null )
        call KnockbackTarget( .missile, u, angle, .kbrange, DECREMENT )
        call DestroyEffect( AddSpecialEffect( DAMAGE_EFFECT, x, y ) )
        set .dmg = .dmg * ( 1.00 - DAMAGE_LOOSE )
        set .range = .range * ( 1.00 - RANGE_LOOSE )
        set .kbrange = .kbrange * ( 1.00 - KNOCKBACK_LOOSE )
        set .hitcounter = .hitcounter - 1
        if .hitcounter <= 0 then
            call .destroy()
        endif
        set u = null
    endmethod
    
    static method create takes FiryStone root, real angle, integer i returns Stone
        local Stone data = Stone.allocate()
        local real x = GetUnitX( root.caster ) + Cos( angle * bj_DEGTORAD ) * START_DISTANCE
        local real y = GetUnitY( root.caster ) + Sin( angle * bj_DEGTORAD ) * START_DISTANCE
        set data.root = root
        set data.angle = angle
        set data.missile = CreateUnit( GetOwningPlayer( root.caster ), DUMMY_ID, x, y, angle )
        set data.model = AddSpecialEffectTarget( STONE_MODEL, data.missile, "origin" )
        set data.hits = CreateGroup()
        set data.rotatetime = ROTATE_TIME[root.level] + GetRandomReal( -MAX_SHOOT_DELAY, MAX_SHOOT_DELAY )
        set data.range = RANGE[root.level]
        set data.dmg = DAMAGE[root.level]
        set data.kbrange = KNOCKBACK_RANGE[root.level]
        set data.hitcounter = MAX_UNIT_HITTED[root.level]
        set data.place = i
        call SetUnitFlyHeight( data.missile, HEIGHT, 0.00 )
        call SetUnitScale( data.missile, SIZE, SIZE, SIZE )
        call DestroyEffect( AddSpecialEffect( CREATE_EFFECT, x, y ) )
        return data
    endmethod
    
    method onDestroy takes nothing returns nothing
        local unit u
        local real x = GetUnitX( .missile )
        local real y = GetUnitY( .missile )
        local integer i = 0
        set TEMP_PLAYER = GetOwningPlayer( .missile )
        call GroupEnumUnitsInRange( TEMP_GROUP, x, y, AOE[.root.level], Condition( function IsPossibleTarget ) )
        loop
            set u = FirstOfGroup( TEMP_GROUP )
            exitwhen u == null
            call .Cast( u )
            set TEMP_REAL = Atan2(GetUnitY( u ) - y, GetUnitX( u ) - x)
            call UnitDamageTarget( .missile, u, .dmg * AOE_DAMAGE_INC, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null )
            call KnockbackTarget( .missile, u, TEMP_REAL, .kbrange, DECREMENT )
            call DestroyEffect( AddSpecialEffect( DAMAGE_EFFECT, x, y ) )
            call GroupRemoveUnit( TEMP_GROUP, u )
        endloop
        call GroupClear( TEMP_GROUP )
        call DestroyEffect( AddSpecialEffect( DESTROY_EFFECT, x, y ) )
        call DestroyEffect( .model )
        call KillUnit( .missile )
        call GroupClear( .hits )
        call DestroyGroup( .hits )
        set i = GetUnitAbilityLevel( .root.caster, ENSTRENGHT_ID )
        if i <= ARMOR_PER_MISSILE then
            call UnitRemoveAbility( .root.caster, ENSTRENGHT_ID )
        else
            call SetUnitAbilityLevel( .root.caster, ENSTRENGHT_ID, i - ARMOR_PER_MISSILE )
        endif
        set .root.There[.place] = false
        set .root.stonecount = .root.stonecount - 1
        if .root.stonecount <= 0 then
            set .root.destroyMe = true
        endif
    endmethod
    
    private method Move takes nothing returns nothing
        local real x = GetUnitX( .missile ) + .vx
        local real y = GetUnitY( .missile ) + .vy
        set .dist = .dist + REAL_SPEED
        set .range = .range - REAL_SPEED
        call SetUnitX( .missile, x )
        call SetUnitY( .missile, y )
        if IsTerrainWalkable( x, y ) == false or .range <= 0.00 then
            call .destroy()
        endif
    endmethod
    
    private method Rotate takes nothing returns nothing
        if .speedangle >= MAX_SPEED_ANGLE then
            set .speedangle = MAX_SPEED_ANGLE
        else
            set .speedangle = .speedangle + SPEED_ANGLE_INC
        endif
        set .angle = .angle + .speedangle
        if .angle > 360.00 then
            set .angle = .angle - 360.00
        elseif .angle < 0.00 then
            set .angle = .angle + 360.00
        endif
        call SetUnitX( .missile, GetUnitX( .root.caster ) + Cos( .angle * bj_DEGTORAD ) * START_DISTANCE )
        call SetUnitY( .missile, GetUnitY( .root.caster ) + Sin( .angle * bj_DEGTORAD ) * START_DISTANCE )
    endmethod
    
    private method RotateEx takes nothing returns nothing
        set .speedangle = .speedangle - SPEED_ANGLE_INC * 3.00
        set .angle = .angle + .speedangle
        if .angle > 360.00 then
            set .angle = .angle - 360.00
        elseif .angle < 0.00 then
            set .angle = .angle + 360.00
        endif
        call SetUnitX( .missile, GetUnitX( .root.caster ) + Cos( .angle * bj_DEGTORAD ) * .dist )
        call SetUnitY( .missile, GetUnitY( .root.caster ) + Sin( .angle * bj_DEGTORAD ) * .dist )
    endmethod
    
    method Control takes nothing returns nothing
        local unit u
        local real x
        local real y
        if GetWidgetLife( .root.caster ) <= 0.405 then
            call .destroy()
        endif
        if .rotate == true then
            set .rotatetime = .rotatetime - TIMER_INTERVAL
            if .rotatetime <= 0.00 then
                call DestroyEffect( AddSpecialEffect( SHOOT_EFFECT, GetUnitX( .missile ), GetUnitY( .missile ) ) )
                set .rotate = false
                set TEMP_REAL = Atan2(GetUnitY( .missile ) - GetUnitY( .root.caster ), GetUnitX( .missile ) - GetUnitX( .root.caster ) )
                set .vx = Cos( TEMP_REAL ) * REAL_SPEED
                set .vy = Sin( TEMP_REAL ) * REAL_SPEED
            else
                call .Rotate()
            endif
        else
            call .Move()
            if TURN then
                call .RotateEx()
            endif
        endif
        set .ptime = .ptime + TIMER_INTERVAL
        if .ptime >= PICK_INTERVAL then
            set .ptime = 0.00
            set x = GetUnitX( .missile )
            set y = GetUnitY( .missile )
            set TEMP_PLAYER = GetOwningPlayer( .missile )
            call GroupEnumUnitsInRange( TEMP_GROUP, x, y, COLLISIONS_SIZE, Condition( function IsPossibleTarget ) )
            loop
                set u = FirstOfGroup( TEMP_GROUP )
                exitwhen u == null
                if IsUnitInGroup( u, .hits ) == false then
                    call GroupAddUnit( .hits, u )
                    call .Dealdamage( u, x, y )
                endif
                call GroupRemoveUnit( TEMP_GROUP, u )
            endloop
            call GroupClear( TEMP_GROUP )
        endif
    endmethod
    
endstruct

private function OutControl takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local FiryStone data = GetTimerData( t )
    set t = null
    call data.Main()
endfunction

struct FiryStone
    unit caster = null
    timer time = null
    integer stonecount = MISSILE_COUNT
    integer level = 0
    boolean destroyMe = false
    boolean array There[MISSILE_COUNT]
    Stone array stones[MISSILE_COUNT]
    
    method onDestroy takes nothing returns nothing
        call PauseTimer( .time )
        call ReleaseTimer( .time )
    endmethod
    
    method Main takes nothing returns nothing
        local integer i = 0
        if .destroyMe == true then
            call .destroy()
        endif
        loop
            exitwhen i == MISSILE_COUNT
            if .There[i] == true then
                call .stones[i].Control()
            endif
            set i = i + 1
        endloop
    endmethod
    
    static method create takes unit caster, integer level returns FiryStone
        local FiryStone data = FiryStone.allocate()
        local integer i = 0
        local real angle = 360.00 / I2R( MISSILE_COUNT )
        set data.caster = caster
        set data.time = NewTimer()
        set data.level = level
        call UnitAddAbility( caster, ENSTRENGHT_ID )
        call SetUnitAbilityLevel( caster, ENSTRENGHT_ID, MISSILE_COUNT * ARMOR_PER_MISSILE )
        loop
            exitwhen i == MISSILE_COUNT
            set data.There[i] = true
            set data.stones[i] = Stone.create( data, angle * I2R( i ), i )
            set i = i + 1
        endloop
        call SetTimerData( data.time, data )
        call TimerStart( data.time, TIMER_INTERVAL, true, function OutControl )
        return data
    endmethod
    
endstruct

private function Action takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local integer lvl = GetUnitAbilityLevel( u, SPELL_ID )
    local FiryStone data = FiryStone.create( u, lvl )
    set u = null
endfunction

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

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function IsSpell ) )
    call TriggerAddAction( t, function Action )
    
    call SET_MAX_UNIT_HITTED()
    call SET_RANGE()
    call SET_AOE()
    call SET_DAMAGE()
    call SET_KNOCKBACK_RANGE()
    call SET_ROTATE_TIME()
    
    set t = null
endfunction

endscope

cedi


Keywords:
Ring, Orb, Fire, Knockback, Burn, DoT, Roatate, Move, AoE, Explosion, Heat, Missile
Contents

Firy Stones v1.01 (Map)

Reviews
10:10, 20th Dec 2009 TriggerHappy: Review for Spell The only thing stopping me from approving this is the public structs. Please make your structs private. You don't need to null u in Dealdamage. FirstOfGroup Loops are the slowest...

Moderator

M

Moderator

10:10, 20th Dec 2009
TriggerHappy:

Review for Spell

The only thing stopping me from approving this is the public structs.
  • Please make your structs private.
  • You don't need to null u in Dealdamage.
  • FirstOfGroup Loops are the slowest method of group retrieving
    AFAIK. It would be better to use ForGroup or use the Filter directly.

Status

Feel free to message me here if you have any issues with
my review or if you have updated your resource and want it reviewed again.

Rejected
 
Level 10
Joined
Aug 19, 2008
Messages
491
JASS:
private function OutControl takes nothing returns nothing
    //If you're only using the timer once, there's no need to create a local.
    //GetExpireTimer() doesn't leak. Actually declaring a local out of it
    //will make it leak, so it's better both for processing and 'leakage' if you don't.
    //This is especially important if the timer has a low expire time, like 0.02
    //(By the way, timers has a minimum of 0.035, but that's not important)
    local timer t = GetExpiredTimer()   
    local FiryStone data = GetTimerData( t )
    set t = null
    call data.Main()
endfunction

    //You're also using alot of "unsafe" loops aswell
    //Like this:
        loop
            exitwhen i == MISSILE_COUNT
            if .There[i] == true then
                call .stones[i].Control()
            endif
            set i = i + 1
        endloop
    //It's 'unsafe' because IF (note the if) the CPU
    //miss the exitwhen, you're stuck with an infinite loop
    //which crash the game
    //It's alot safer using
    exitwhen i >= MISSILE_COUNT
    //Because even if the CPU miss the exitwhen,
    //it won't crash the game

    //Other than that, I can't say much more
    //I'm new to structs so there are several things
    //I do not understand, and I can't give you guides
    //on how to improve them.

    //Hope it helped
    //- Cheezeman
 
Level 15
Joined
Jul 6, 2009
Messages
889
JASS:
        loop
            exitwhen i == MISSILE_COUNT
            set data.There[i] = true
            set data.stones[i] = Stone.create( data, angle * I2R( i ), i )
            set i = i + 1
        endloop
Don't need I2R.

You don't need to convert integers to real, but you do need to convert reals to integer.
You don't need to set t to null in init trigger, but meh, it doesn't matter.
JASS:
            if .There[i] == true then
JASS:
if .There[i] then
Don't need == true. Though that is quite small thing as well.

The spell is done nicely, I like it. Although I don't know why it doesn't damage buildings.... fire burns buildings >.>
 
After using spell 1st time my PC with duo core CPU 2.33 GH and memory 2 GB laged for ~0.75 seconds. Meaby make it more lagless; meaby any code optimize?
The spell do hight damage. It is like spell cause damage more than one time at period when unit get close to hero.

I know zero of jass coding, even how you people make triggers like codelines...
4/5 cuz of lag
 
First cast lag usually can only be minimized and not fixed...

At previos coment i wrote my PC memory and speed. How can you "rate" lag of this spell? Hight? Medium? Low?

EDIT: Deuterium is right. I dont blame Cedi at lag enymore.
My PC do strange things sometimes, like lag at any game or application for 0.5-1 sec
Cedi spell is well done. 5/5 :) And sry for blaming you Cedi.
 
Last edited:
Level 17
Joined
Mar 17, 2009
Messages
1,349
Well, even after preloading the SFX, the same lag still occured.
So it's not his fault.

And it's actually not something to nag about, as the SFX can be changed in the globals... actually the globals are very detailed.

So again I say, there's nothing that can be done about the lag except changing SFX, which can be done by any user. So don't blame the cedi :)
 
Level 10
Joined
May 19, 2008
Messages
176
There is one thing you can do:

JASS:
call KillUnit( CreateUnit( Player( 0 ), DUMMY_ID, 0.00, 0.00, 0.00 ) )

Writh it in the init function, that could decrease the lag a bit. ( Preload the dummy unit )

WARNING! Its free-hand, so may I made a mistake in one of the parameter...

cedi
 
Level 2
Joined
Feb 25, 2010
Messages
10
Hi

I have downloaded your spell and added it to my map. but for some reason, i do not get the model. everything works, except that you cant see any stones spinning auround your character. help?

Edit: Maked it work. I changed the model art of the dummy to the fire rocks :) lovely spell thought 5/5
 
Last edited:
Top