Spell Fire Disk

Tinki3

Special Member
Reaction score
418
A new, original, fully MUI, JASS/New gen enhanced spell from your favourite spellmaker :).

Import Difficulty: High-ish

Units Affected: Enemy, ground

Target Type: Point

Spell Info:

Creates a disk of fire at the target location. The disk will then begin moving from one spot to another with speed depending on the level of Fire Disk. The disk will spit fire balls outwards in a circular formation that sizzle enemies upon contact dealing damage. After a limited amount of time, the disk will explode dealing large damage to enemy ground units in an area, and will knock them back a short range. Disk destroys trees upon contact.

firediskimageyf3.jpg


Code:
JASS:
scope FireDisk

globals
    private constant integer Main_Spell_ID = 'A000'    //Raw code of "Fire Disk"
    private constant integer Dummy_Ability = 'A001'    //Raw code of "Fire Disk Dummy Ability"
    private constant integer Disk_Part_ID = 'h001'    //Raw code of "Fire Disk Part"
    private constant integer Dummy_Caster_ID = 'h002'    //Raw code of "Dummy Caster"
    private constant integer Disk_Centre_Part_ID = 'h003'    //Raw code of "Fire Disk Center Part"
    private constant string Dummy_Ability_String = "carrionswarm"    //Order string for dummy ability (match with object editor)
    private constant string Disk_Explosion_SFX_1 = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"    //The effect created at the disk's centre when it dies
    private constant string Disk_Explosion_SFX_2 = "Abilities\\Spells\\Human\\FlakCannons\\FlakTarget.mdl"   //The effect created on units that were affected by the explosion
    private constant boolean Disk_Destroys_Trees = true     //If "true" - disk will destroy trees; if "false" - disk will NOT destroy trees
    private constant integer Disk_Part_Amount = 10    //The amount of fireballs/dummy units to be created as the ring (won't be exact amount created)
    private constant real Disk_Rotation_Speed = -0.075 //The speed in which fireballs orbit the centre of the disk ('-' numbers spin disk clockwise, '+' spin disk anticlockwise)
    private constant real Disk_Move_Speed_Base = 0.75    //Formula = Disk_Move_Speed_Base * LEVEL * Disk_Move_Speed_Multiplier (disk moves at this speed)
    private constant real Disk_Move_Speed_Multiplier = 3.25    // "                    "                   "               "                "                  "                        "                        "
    private constant real Disk_Size = 130.0    //The radius of the circle (remember the full "size" of the circle will be a Disk_Size * 2 area)
    private constant real Disk_Move_Distance = 675.0   //The distance the disk moves at one time (can vary)
    private constant real Disk_Dummy_Ability_Cast_Interval = 0.1    //The interval between each cast of the Dummy_Ability_1 ability
    private constant integer Disk_Dummy_Ability_Casting_Angle_Increment_Value = 1    //MUST be a number ABOVE 0, a whole number (an integer), and should be a number that is a FACTOR of the "Disk_Part_Amount" value. This value is used to determine the angle increment in which the dummy caster unit casts the dummy spell at. The smaller the number inserted for this global value, the smaller the angle will be between each wave of fire; the higher the number is, the greater the angle will be between each wave of fire. Remember that this value is linked with the "Disk_Part_Amount" value. If you increase the Disk_Part_Amount value, you would probably want to increase the value on this line so that the fireballs are nicely spread. You will have to experiment with this value before you will understand fully what you have just read.
    private constant real Disk_Lifespan_Base = 8//The base value for how long the disk lives (formula = Disk_Lifespan_Base * LEVEL)
    private constant real Disk_Explosion_AoE = 300.0   //The AoE of the explosion when the disk dies
    private constant real Disk_Explosion_Dmg_Base = 80    //The damage base value of the explosion when the disk dies (formula = LEVEL * Disk_Explosion_Dmg_Base)
    private constant real Disk_Tree_Destroy_Area = 225.0    //The area in which trees/destructables are picked to be killed/destroyed when the disk is nearby
    private constant real Disk_Explosion_Knockback_Distance = 325.0    //The distance units are knocked back by the explosion when the disk dies
    private constant real Disk_Explosion_Knockback_Speed = 15.0    //The speed in which units are knocked back by the explosion when the disk dies
    private constant real Timer_Interval = 0.03    //The interval between each timer callback function execution
endglobals

private struct dat
    unit u
    unit array p[Disk_Part_Amount ]
    real array A[Disk_Part_Amount]
    unit c 
    integer lvl
    real s
    real a
    real a2
    integer a3
    real D
    real L
    trigger trig
    triggeraction ta
endstruct

private struct dat2
    real c
    real OX
    real OY
    real dmg
    unit U
    unit array P[100]
endstruct

private function Unit_Filter takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(bj_ghoul[40])) and GetWidgetLife(GetFilterUnit()) > 0.405 and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) != true and IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING) != true
endfunction

private function Destroy_Destructs takes nothing returns nothing
    call KillDestructable(GetEnumDestructable())
endfunction

private function Refund takes unit u, real m, real m2, integer lvl returns nothing    
    call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA) + (m - m2))
    call UnitRemoveAbility(u, Main_Spell_ID)
    call UnitAddAbility(u, Main_Spell_ID)
    call SetUnitAbilityLevel(u, Main_Spell_ID, lvl)
endfunction

private function Error takes player P,string S returns nothing
    local sound s = CreateSoundFromLabel("InterfaceError", false, false, false, 10, 10)
    
    if(GetLocalPlayer() == P) then
        call ClearTextMessages()
        call DisplayTimedTextToPlayer(P, 0.52, -1.0, 2.0, "|cffffcc00" + S + "|r" )
        call StartSound(s)
    endif
    
    call KillSoundWhenDone(s)
    set s = null
endfunction

private function Knockback takes nothing returns nothing
    local timer T = GetExpiredTimer()
    local dat2 dd = dat2(GetHandleInt(T, "dd"))
    local integer i = 1
    local real X
    local real Y
    local real PX 
    local real PY
    local real ang
    local unit p
    local rect R
    
    if dd.c > 0 then
        set dd.c = dd.c - Disk_Explosion_Knockback_Speed
        loop
            exitwhen dd.P<i> == null
            set X = GetUnitX(dd.P<i>)
            set Y = GetUnitY(dd.P<i>)
            set ang = Atan2(Y-dd.OY, X-dd.OX)
            set PX = X + Disk_Explosion_Knockback_Speed * Cos(ang)
            set PY = Y + Disk_Explosion_Knockback_Speed * Sin(ang)
            set R = Rect(X - 135, Y - 135, X + 135, Y + 135)
            call EnumDestructablesInRect(R, null, function Destroy_Destructs)
            call SetUnitPosition(dd.P<i>, PX, PY)
            call UnitDamageTarget(dd.U, dd.P<i>, dd.dmg, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
            call DestroyEffect(AddSpecialEffect(Disk_Explosion_SFX_2, X, Y))
            call RemoveRect(R)
            set R = null
            set i = i + 1
        endloop
    else
        call PauseTimer(T)
        call FlushHandleLocals(T)
        call dd.destroy()
        call DestroyTimer(T)
    endif
    
    set T = null
endfunction

private function Shoot_Flames takes nothing returns nothing
    local trigger trg = GetTriggeringTrigger()
    local dat d2 = dat(GetHandleInt(trg, &quot;d&quot;))
    local real X = GetUnitX(d2.c)
    local real Y = GetUnitY(d2.c)
    local real PX
    local real PY
    local unit z
    
    if  d2.a3 &lt;= Disk_Part_Amount then
        set PX =  X + Disk_Size/1.4 * Cos(d2.A[d2.a3])
        set PY =  Y + Disk_Size/1.4 * Sin(d2.A[d2.a3])
        set z = CreateUnit(GetOwningPlayer(d2.u), Dummy_Caster_ID,  X, Y, 0)
        call UnitAddAbility(z, Dummy_Ability)
        call SetUnitAbilityLevel(z, Dummy_Ability, d2.lvl)  
        call IssuePointOrder(z, Dummy_Ability_String, PX, PY)
        set d2.a3 = d2.a3 + Disk_Dummy_Ability_Casting_Angle_Increment_Value
        set z = null
    else
        set d2.a3 = 1
    endif
    
    set trg = null
endfunction

private function Move_Disk takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local dat d = dat(GetHandleInt(t, &quot;d&quot;))
    local real X1 = GetUnitX(d.c)
    local real Y1 = GetUnitY(d.c)
    local real X2 = X1 + d.s * Cos(d.a)
    local real Y2 = Y1 + d.s * Sin(d.a)
    local integer i = 1
    local real X3 
    local real Y3 
    local real PX
    local real PY
    local rect R     
    local boolexpr b
    local timer T
    local group g
    local dat2 dd
    
    if d.L &lt;  Disk_Lifespan_Base * d.lvl then
        if Disk_Destroys_Trees == true then
            set R = Rect(X1 - Disk_Tree_Destroy_Area, Y1 - Disk_Tree_Destroy_Area, X1 + Disk_Tree_Destroy_Area, Y1 + Disk_Tree_Destroy_Area)
            call EnumDestructablesInRect(R, null, function Destroy_Destructs)
            call RemoveRect(R)
            set R = null
        endif
        if d.D &lt;= Disk_Move_Distance and IsTerrainPathable(X2, Y2, PATHING_TYPE_WALKABILITY) != true then
            call SetUnitPosition(d.c, X2, Y2)
            set d.D = d.D + d.s
            loop
                exitwhen i &gt; Disk_Part_Amount
                set X3 = GetUnitX(d.p<i>)
                set Y3 = GetUnitY(d.p<i>)
                set d.A<i> = d.A<i> + Disk_Rotation_Speed
                set PX =  X1 + Disk_Size * Cos(d.A<i>)
                set PY =  Y1 + Disk_Size * Sin(d.A<i>)
                call SetUnitPosition(d.p<i>, PX, PY)
                set i = i + 1
            endloop
        else
            set d.a = GetRandomReal(1, 360)
            set d.D = 0
        endif
        set d.L = d.L + Timer_Interval
    else
        set i = 1
        set dd = dd.create()
        set g = CreateGroup()
        set dd.c = Disk_Explosion_Knockback_Distance
        set dd.OX = X1
        set dd.OY = Y1
        set dd.dmg =  (Disk_Explosion_Dmg_Base * d.lvl)/ (Disk_Explosion_Knockback_Distance/Disk_Explosion_Knockback_Speed)
        set dd.U = d.u
        set T = CreateTimer()
        set b = Condition(function Unit_Filter)
        set bj_ghoul[40] = d.u
        call GroupEnumUnitsInRange(g, X1, Y1, Disk_Explosion_AoE, b)
        loop
            set dd.P<i> = FirstOfGroup(g)
            exitwhen dd.P<i> == null
            set i = i + 1
            call GroupRemoveUnit(g, dd.P[i-1])
        endloop
        call SetHandleInt(T, &quot;dd&quot;, dd)
        call TimerStart(T, Timer_Interval, true, function Knockback)
        call DestroyBoolExpr(b)
        call DestroyGroup(g)
        call DestroyEffect(AddSpecialEffect(Disk_Explosion_SFX_1, X1, Y1))
        call TriggerRemoveAction(d.trig, d.ta)
        call FlushHandleLocals(d.trig)
        call DestroyTrigger(d.trig)
        call PauseTimer(t)
        call FlushHandleLocals(t)
        call d.destroy()
        call DestroyTimer(t)
        set T = null
        set b = null
        set g = null
    endif
    
    set t = null
endfunction

private function Cast takes nothing returns nothing
    local location P = GetSpellTargetLoc()
    local real m = GetUnitState(GetTriggerUnit(), UNIT_STATE_MANA)
    local real A1 = GetLocationX(P)
    local real B1 = GetLocationY(P)
    local real A2
    local real B2
    local real ZX
    local real m2
    local boolean Z
    local integer M = 1
    local integer J = 0
    local integer x = 1
    local timer t
    local real ang
    local dat d
    
    loop
        exitwhen M &gt; Disk_Part_Amount 
        set ZX = 360/Disk_Part_Amount * M * bj_DEGTORAD
        set A2 =  A1 + Disk_Size * Cos(ZX)
        set B2 =  B1 + Disk_Size * Sin(ZX)
        if IsTerrainPathable(A2, B2, PATHING_TYPE_WALKABILITY) then 
            set J = 1
        endif
        set M = M + 1
    endloop
    
    set Z = IsTerrainPathable(A1, B1, PATHING_TYPE_WALKABILITY) != true and J == 0
    call RemoveLocation(P)
    set P = null
    
    if Z == false then
        call Error(GetOwningPlayer(GetTriggerUnit()), &quot;Cannot create disk there&quot; )
        call TriggerSleepAction(0.1)
        set m2 = GetUnitState(GetTriggerUnit(), UNIT_STATE_MANA)
        call Refund(GetTriggerUnit(), m, m2, GetUnitAbilityLevel(GetTriggerUnit(), Main_Spell_ID))
        return
    endif
    
    set t = CreateTimer()
    set d = dat.create()
    set d.u = GetTriggerUnit()
    set d.c = CreateUnit(GetOwningPlayer(d.u),  Disk_Centre_Part_ID, A1, B1, 0)
    set d.lvl = GetUnitAbilityLevel(d.u, Main_Spell_ID)
    set d.s = Disk_Move_Speed_Base * d.lvl * Disk_Move_Speed_Multiplier
    set d.a = Atan2(B1-GetUnitY(d.u), A1-GetUnitX(d.u)) 
    set d.a2 = GetRandomReal(1, 360)
    set d.a3 = GetRandomInt(1, Disk_Part_Amount)
    set d.D = 0
    set d.L = 0
    set d.trig = CreateTrigger()
    call TriggerRegisterTimerEvent(d.trig, Disk_Dummy_Ability_Cast_Interval, true)
    set d.ta = TriggerAddAction(d.trig, function Shoot_Flames)
    call UnitApplyTimedLife(d.c,  &#039;BTLF&#039;, Disk_Lifespan_Base * d.lvl)
    
    loop
        exitwhen x &gt; Disk_Part_Amount 
        set ang = 360/Disk_Part_Amount * x * bj_DEGTORAD
        set d.p[x] = CreateUnit(GetOwningPlayer(d.u),  Disk_Part_ID, A1 + Disk_Size * Cos(ang), B1 + Disk_Size * Sin(ang), Atan2(GetUnitY(d.p[x]) - B1, GetUnitX(d.p[x]) - A1))
        set d.A[x] = Atan2(GetUnitY(d.p[x]) - B1, GetUnitX(d.p[x]) - A1)
        call UnitApplyTimedLife(d.p[x], &#039;BTLF&#039;, Disk_Lifespan_Base * d.lvl)
        set x = x + 1
    endloop
    
    call SetHandleInt(d.trig, &quot;d&quot;, d)
    call SetHandleInt(t, &quot;d&quot;, d)
    call TimerStart(t, Timer_Interval, true, function Move_Disk)
    
    set t = null
endfunction

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

function InitTrig_Fire_Disk takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(t, Condition( function Conditions ) )
    call TriggerAddAction(t, function Cast )    
    set t = null
endfunction

endscope

</i></i></i></i></i></i></i></i></i></i></i></i></i></i>


Read Me:
* IMPLEMENTATION *
__________________


What's Needed?
--------------

>> Variable - "GameCache" (see variables menu)
>> Unit - "Dummy Caster"
>> Unit - "Fire Disk Center Part"
>> Unit - "Fire Disk Part"
>> Ability - "Fire Disk"
>> Ability - "Fire Disk Dummy Ability"
>> Trigger - "Fire Disk"
>> System - "Local Handle Vars" (located in the trigger header)
>> Tool - "JASS New Gen" (get it here if you don't have: http://wc3campaigns.net/showthread.php?t=90999)

How do I Implement The Spell?
-----------------------------

==Please Read Carefully If You're A Novice==

1. Firstly, copy and paste the needed abilities, units, trigger, system & game cache variable
into your map. Note that the system should be pasted into your map's trigger header,
& that the variable "GameCache" should have the same name when pasted into your map,
as in my map (quite important if your a novice). Also, the trigger needs to have exactly
the same name as well, with it being "Fire Disk" (don't forget the space).

2. Now look at the "Fire Disk" trigger under "globals".
The first 5 lines are what's gonna make the spell work properly.
Those numbers/letters 'A000', 'A001' ... etc, are called rawcodes.
'A000' is for the Fire Disk ability, and the others are for the dummy abilities and units.
Each object has a specific rawcode, which means they would've most likely changed
when they were pasted into your map.
What you need to do is to change those rawcodes to that of your map's rawcodes for
those 5 pieces of data.
Rawcodes can be viewed by pressing CTRL + D when in the Object Editor.
They will be the first 4 symbols next to your object's icon.
Replace all the rawcodes under "globals" accordingly with the rawcodes from your map, & remember
to put them between the ' marks (important).

3. Give the ability Fire Disk to some unit and test to see if it works.
If it fails, I advise you to read steps 1 & 2 again.

If you're getting errors with the trigger, check that the trigger's name is exactly
the same as the one in this map, and that you have all the stuff from " What's Needed?" in your map.


Have fun,
Tinki3

The spell looks far better in-game.
Enjoy.
 

Attachments

  • Fire Disk.w3x
    56.3 KB · Views: 449

waaaks!

Zinctified
Reaction score
255
cool...ill take this spell and implement in my project after this spell will be approved
 

NetherHawk

New Member
Reaction score
26
awesome spell =DD

@tinki3, i see you can set the struct to a handle(i.e. call SetHandleInt(t, "d", d) ), does that mean all information invovling that struct will be carried over when u (i.e. local dat d = dat(GetHandleInt(t, "d")) )?
 

Tinki3

Special Member
Reaction score
418
> does that mean all information invovling that struct will be carried over when u (i.e. local dat d = dat(GetHandleInt(t, "d")) )?

Yes, it does.

> It bugs up when casted on black mass out bounds

I'll have to fix that, thanks for pointing it out.
 

PurgeandFire

zxcvmkgdfg
Reaction score
509
Pretty good, I'm not sure about SetUnitPosition compared to SetUnitX and Y, so I won't bother.

But, I'm pretty sure you're not supposed to destroy Boolexprs when you use it more than once because it is hazardous. ;)
 

Tinki3

Special Member
Reaction score
418
> I'm not sure about SetUnitPosition compared to SetUnitX and Y, so I won't bother

SetUnitX and Y crashes the game if the points are outside the map bounds.
Just another reason to use SetUnitPosition I suppose...

> I'm pretty sure you're not supposed to destroy Boolexprs when you use it more than once because it is hazardous

IME, it is hazardous if you destroy a boolexpr if you only use it "once".
If that makes any sense... Though I have had a problem like this occur before, believe it.
 

~GaLs~

† Ғσſ ŧħə ѕαĸε Φƒ ~Ğ䣚~ †
Reaction score
180
>>SetUnitX and Y crashes the game if the points are outside the map bounds.
Just another reason to use SetUnitPosition I suppose...

No, there is difference between setunitx/y and setunitposition.
You can do a test.

Create an ability base on channel. Make it channel about 10 seconds. The create 2 trigger that makes the unit slides to any direction you want.

1st trigger uses SetUnitPosition, and another uses SetUnitX and SetUnitY. You'll see the difference.
 

PurgeandFire

zxcvmkgdfg
Reaction score
509
Oh, sorry Tinki, I was wrong.

Actually, Conditions do leak. However, they are saved in a table so when the condition is reused, the one in the table is used instead of a new one. If your only going to use a condition once, then you can destroy it.

I got mixed up. :D

Aweesome spell by the look of the screenshots. I don't have WE on this comp.
 

Tinki3

Special Member
Reaction score
418
> there is difference between setunitx/y and setunitposition

Yes, I know that. SetUnitX/Y are faster than SetUnitPosition,
but if a coordinate is outside the map bounds, the game will crash.

Not the most exciting function to use IMO & IME.
 

Cohadar

master of fugue
Reaction score
209
This spell lags too much.
Suggestions:

Use SetUnitX/Y instead SetUnitPos,
this means that you will have to learn how to check if it gets out of borders,
I say it is about time you learn that.

Also learn to use ForGroup instead of loops.
 

~GaLs~

† Ғσſ ŧħə ѕαĸε Φƒ ~Ğ䣚~ †
Reaction score
180
>>Yes, I know that. SetUnitX/Y are faster than SetUnitPosition,
but if a coordinate is outside the map bounds, the game will crash.

1. I don't really think it is faster by the way... I just know SetUnitX/Y takes the avantage that don't interupts any channel spell.
2. You can have a boolean comparison to check if it is out of bound?

>>Though, if you're complaining because you have 256MB of RAM, don't
Try preload?
I have heard something about preload would reduce lag. I don't how to do it, I just heard of it, long time ago.
 

Cohadar

master of fugue
Reaction score
209
It is faster because SetUnitX/Y don't do pathing checks of terrain.
And for map bounds you can register LEAVER_REGION event on playable map area and simply return any units that get out to the border.

ForGroup in this case would also add to speed.

Using gamecache is not helping you either.
 

~GaLs~

† Ғσſ ŧħə ѕαĸε Φƒ ~Ğ䣚~ †
Reaction score
180
>>Using gamecache is not helping you either
You must use his ABC!
 

Cohadar

master of fugue
Reaction score
209
I did not say that. I am refusing to directly mention my sys from now on because of people like ~Gals~.

All things said, this spell needs some optimizing,
I recommend you start with recoding it to use ForGroup.
 

Tinki3

Special Member
Reaction score
418
> Using gamecache is not helping you either

Yea, realised that, but I'm so used to using it.

> ForGroup in this case would also add to speed

I wonder if I'd run into some problems regarding getting the struct's data
from the source function to the callback function though.

I'd be able to use GetExpiredTimer() in a ForGroup callback wouldn't I?

> it is faster because SetUnitX/Y don't do pathing checks of terrain

I'd rather have a function that checks for me automatically, rather
than doing it manually by adding more code, even though it is slightly faster.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top