Spell Flaming Blades

saw792

Is known to say things. That is all.
Reaction score
280
Flaming Blades

Images:



Creates a cyclone of flaming blades that each deal damage on contact.

Level 1: 30 damage per blade
Level 2: 60 damage per blade
Level 3: 90 damage per blade

Made in vJASS:
JASS:
scope FlamingBlades initializer Init
//**************************************************************************
//
//  Flaming Blades                        by saw792
//   
//  Requires:
//   - a vJASS preprocessor
//   - CSData (included in test map)
//   - dummy.mdx (included in test map)
//
//  Implementation:
//   - Create a blank trigger, convert it to custom text
//   - Paste this code into the space (replace other text)
//   - Create a dummy unit using the dummy.mdx model
//   - Create a dummy ability
//   - Configure below
//
//*************************************************************************** 
//                            Configuration
//*************************************************************************** 
  globals
    //Rawcode of dummy ability
    private constant integer ABIL_ID = 'A000'
    //Rawcode of dummy unit
    private constant integer DUMMY_ID = 'h000'
    //Special effect that appears on the ground
    private constant string SFX_GROUND = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeTarget.mdl"
    //Special effect that appears in the cyclone
    private constant string SFX_MISSILE = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
    //Special effect path of the actual blade
    private constant string SFX_BLADE = "Abilities\\Weapons\\BloodElfSpellThiefMISSILE\\BloodElfSpellThiefMISSILE.mdl"
    //Radius of spinning blades
    private constant real RADIUS = 250
    //Max height of the cyclone
    private constant real HEIGHT = 100
    //Duration (in seconds)
    private constant real DURATION = 5
    //Timer interval (0.04 recommended, any lower will cause lag)
    private constant real INTERVAL = 0.04
    //Number of individual blades (must be greater than 2)
    private constant integer NUMBER_OF_BLADES = 14
    //Number of times the cyclone rotates every second
    private constant real ROTATION = 2
    //Length of time that the cyclone spins at double speed
    private constant real DOUBLE_SPEED_TIME = 1
    //Constant that controls the rate of expansion of the cyclone during the double speed time
    //Recommended between -1 and 1
    //Positive = expand, Negative = contract
    private constant real EXPANSION_FACTOR = 0
    //Damage (per level)that each blade will do upon contact with enemy unit
    private constant real DAMAGE = 30
    //Individual units can only be damaged this often (seconds)
    //Multiple of interval is recommended
    private constant real DAMAGE_TIME = 0.24
  endglobals
  
//************************************************************************
//                       Do not edit below here
//************************************************************************
  globals
    private constant integer num = NUMBER_OF_BLADES - 1
    private timer time
  endglobals
  
  private struct Data
    unit array u [num]
    real array x [num]
    real array y [num]
    effect array e [num]
    effect f
    real tx
    real ty
    integer ticks = 0
    integer level
    group g = CreateGroup()
    group g2 = CreateGroup()
    unit caster
    
      static method create takes nothing returns Data
        local Data d = Data.allocate()
        local location s = GetSpellTargetLoc()
        local integer i = 0
        set d.caster = GetTriggerUnit()
        set d.level = GetUnitAbilityLevel(d.caster, ABIL_ID)
        set d.tx = GetLocationX(s)
        set d.ty = GetLocationY(s)
        set d.f = AddSpecialEffect(SFX_GROUND, d.tx, d.ty)
        loop
          exitwhen i == NUMBER_OF_BLADES
          set d.u<i> = CreateUnit(GetOwningPlayer(GetTriggerUnit()), DUMMY_ID, d.tx, d.ty, 0)
          set d.e<i> = AddSpecialEffectTarget(SFX_BLADE, d.u<i>, &quot;origin&quot;)
          call UnitAddAbility(d.u<i>, &#039;Amrf&#039;)
          call UnitRemoveAbility(d.u<i>, &#039;Amrf&#039;)
          set d.x<i> = d.tx + i * (RADIUS / (num)) * Cos((i * 360 / (NUMBER_OF_BLADES - 2)) * bj_DEGTORAD)
          set d.y<i> = d.ty + i * (RADIUS / (num)) * Sin((i * 360 / (NUMBER_OF_BLADES - 2)) * bj_DEGTORAD)
          call SetUnitFlyHeight(d.u<i>, i * (HEIGHT / (num)), 0)
          set i = i + 1
        endloop
        call RemoveLocation(s)
        set s = null
        return d
      endmethod
  
      method onDestroy takes nothing returns nothing
        local integer i = 0
        call DestroyEffect(.f)
        call DestroyGroup(.g)
        call DestroyGroup(.g2)
        loop
          exitwhen i == NUMBER_OF_BLADES
          call DestroyEffect(.e<i>)
          call ShowUnit(.u<i>, false)
          call KillUnit(.u<i>)
          set i = i + 1
        endloop
      endmethod
      
  endstruct
  
  private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == ABIL_ID
  endfunction
  
  private function Filt takes nothing returns boolean
    return IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false
  endfunction
  
  private function Callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Data d = GetCSData(t)
    local integer i = 0
    local real j = (DURATION / INTERVAL) - (DOUBLE_SPEED_TIME / INTERVAL) - 1
    local real k
    local integer s = 0
    local unit u
    if ModuloReal(d.ticks, DAMAGE_TIME / INTERVAL) == 0 then
      call GroupClear(d.g2)
    endif
    if d.ticks &gt;= (DURATION / INTERVAL) then
      call d.destroy()
      call PauseTimer(t)
      call DestroyTimer(t)
    elseif d.ticks &gt;= j then
      loop
        exitwhen i == NUMBER_OF_BLADES
        set k = i * (RADIUS / (num)) + i * (d.ticks - j) * EXPANSION_FACTOR
        if k &lt; 0 then
          set k = 0
        endif
        call GroupEnumUnitsInRange(d.g, d.x<i>, d.y<i>, 50, Filter(function Filt))
        loop
          set u = FirstOfGroup(d.g)
          exitwhen u == null or s == 20
          if IsUnitInGroup(u, d.g2) == false and IsUnitEnemy(u, GetOwningPlayer(d.caster)) then
            call UnitDamageTarget(d.u<i>, u, DAMAGE * d.level , true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
          endif
          call GroupAddUnit(d.g2, u)
          call GroupRemoveUnit(d.g, u)
          set s = s + 1
        endloop
        set d.x<i> = d.tx + k * Cos(((i * 360 / (NUMBER_OF_BLADES - 2)) + (d.ticks * (360 / (1 / INTERVAL / (2 * ROTATION))))) * bj_DEGTORAD)
        set d.y<i> = d.ty + k * Sin(((i * 360 / (NUMBER_OF_BLADES - 2)) + (d.ticks * (360 / (1 / INTERVAL / (2 * ROTATION))))) * bj_DEGTORAD)
        call SetUnitX(d.u<i>, d.x<i>)
        call SetUnitY(d.u<i>, d.y<i>)
        if i &gt; (NUMBER_OF_BLADES / 2) then
          call DestroyEffect(AddSpecialEffectTarget(SFX_MISSILE, d.u<i>, &quot;origin&quot;))
        endif
        set i = i + 1
      endloop
      set d.ticks = d.ticks + 1
    else
      loop
        exitwhen i == NUMBER_OF_BLADES
        call GroupEnumUnitsInRange(d.g, d.x<i>, d.y<i>, 50, Filter(function Filt))
        loop
          set u = FirstOfGroup(d.g)
          exitwhen u == null or s == 20
          if IsUnitInGroup(u, d.g2) == false and IsUnitEnemy(u, GetOwningPlayer(d.caster)) then
            call UnitDamageTarget(d.u<i>, u, DAMAGE * d.level, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
          endif
          call GroupAddUnit(d.g2, u)
          call GroupRemoveUnit(d.g, u)
          set s = s + 1
        endloop
        set d.x<i> = d.tx + i * (RADIUS / (num)) * Cos(((i * 360 / (NUMBER_OF_BLADES - 2)) + (d.ticks * (360 / (1 / INTERVAL / ROTATION)))) * bj_DEGTORAD)
        set d.y<i> = d.ty + i * (RADIUS / (num)) * Sin(((i * 360 / (NUMBER_OF_BLADES - 2)) + (d.ticks * (360 / (1 / INTERVAL / ROTATION)))) * bj_DEGTORAD)
        call SetUnitX(d.u<i>, d.x<i>)
        call SetUnitY(d.u<i>, d.y<i>)
        if i &gt; (NUMBER_OF_BLADES / 2) then
          call DestroyEffect(AddSpecialEffectTarget(SFX_MISSILE, d.u<i>, &quot;origin&quot;))
        endif
        set i = i + 1
      endloop
      set d.ticks = d.ticks + 1
    endif
    set u = null
  endfunction
  
  private function Actions takes nothing returns nothing
    local Data d = Data.create()
    set time = CreateTimer()
    call SetCSData(time, d)
    call TimerStart(time, INTERVAL, true, function Callback)
  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 Conditions))
    call TriggerAddAction(t, function Actions)
    set t = null
  endfunction
  
endscope</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i>


MUI: Yes
Leakless: Seems to be

Comments and suggestions are welcome.

Thankyou.

EDIT: Updated
EDIT2: Updated, now uses a global timer.
 

Attachments

  • Flaming Blades.w3x
    33 KB · Views: 592
Reaction score
456
You don't null the timer in Actions function.

No need to null globals in Data's onDestroy method. And instead of removing unit, hide it and kill it:
JASS:
method onDestroy takes nothing returns nothing
    local integer i = 0
    call DestroyEffect(.f)
    call DestroyGroup(.g)
    call DestroyGroup(.g2)
    loop
        exitwhen i == NUMBER_OF_BLADES
        call DestroyEffect(.e<i>)
        call HideUnit(.u<i>)
        call KillUnit(.u<i>)
        set i = i + 1
    endloop
endmethod</i></i></i>


Then:
JASS:
set d.x<i> = d.tx + i * (RADIUS / (NUMBER_OF_BLADES - 1)) * Cos((i * 360 / (NUMBER_OF_BLADES - 2)) * bj_DEGTORAD)
set d.y<i> = d.ty + i * (RADIUS / (NUMBER_OF_BLADES - 1)) * Sin((i * 360 / (NUMBER_OF_BLADES - 2)) * bj_DEGTORAD)
call SetUnitFlyHeight(d.u<i>, i * (HEIGHT / (NUMBER_OF_BLADES - 1)), 0)</i></i></i>

You've "num" global variable for "NUMBER_OF_BLADES - 1" already, why not to use it?
It's missing parentheses, or am I mistaken. Now it multiplies 360 by i every time, and divides it. Shouldn't i be multiplied by (360 / (NUMBER_OF_BLADES - 2))?
 

saw792

Is known to say things. That is all.
Reaction score
280
Uberplayer said:
No need to null globals in Data's onDestroy method. And instead of removing unit, hide it and kill it:

I couldn't remember whether you needed to or not, so I did anyway. I'll remove it now though. And yes hiding and killing does sound better.
Uberplayer said:
You've "num" global variable for "NUMBER_OF_BLADES - 1" already, why not to use it?

The num variable was added in later, as the syntax checker would not accept "NUMBER_OF_BLADES - 1" as a valid size for arrays within the struct. I did overlook replacing the remaining others.

Uberplayer said:
It's missing parentheses, or am I mistaken. Now it multiplies 360 by i every time, and divides it. Shouldn't i be multiplied by (360 / (NUMBER_OF_BLADES - 2))?

It is exactly the same difference. Try it with any fraction. I am merely saving myself parentheses. The only time you need brackets is when you are changing from multiplication and division to addition and subtraction, and vice versa.

EDIT:
Uberplayer said:
You don't null the timer in Actions function.

Nor do I need to. Nulling timers can cause (admittedly, rare) bugs. Ask around.
 

cr4xzZz

Also known as azwraith_ftL.
Reaction score
51
Nor do I need to. Nulling timers can cause (admittedly, rare) bugs. Ask around.
I'm gonna ask you where did you hear that. Never experienced such "rare bugs". Cool spell though.
 

Romek

Super Moderator
Reaction score
963
I've never heard of, or experienced your "Timer Nulling Bug".

And either way, even if it does cause a rare bug, you're still leaking. So I suggest you null it :)
Or if you're really worried, you could put the timer into the struct. Then it becomes a global array, and doesn't need nulling.
 

Flare

Stops copies me!
Reaction score
662
Or if you're really worried, you could put the timer into the struct. Then it becomes a global array, and doesn't need nulling.
Then you have 8000 timer variables, most of which will go unused - that's quite wasteful :p

There would be the option of using a global timer variable i.e.
JASS:
set GTimer = CreateTimer ()
...


...
set GTimer = GetExpiredTimer ()
 

Romek

Super Moderator
Reaction score
963
Then you have 8000 timer variables, most of which will go unused - that's quite wasteful :p

Oh well, it's not like it'd make much difference.

There would be the option of using a global timer variable i.e.
JASS:
set GTimer = CreateTimer ()
...


...
set GTimer = GetExpiredTimer ()

Couldn't that make the spell non-MUI? (If the timer was started while it was running, or destroyed after while it was running for another spell).

You could also use TT, which uses a single timer, or a Timer-Stack.
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
A single global timer can always be used fully MUI as long as they use high frequencies. This would require instance counting too for pausing/starting the timer so that it's not running when not used.
 

Flare

Stops copies me!
Reaction score
662
Couldn't that make the spell non-MUI?
If you suck, then yes. Otherwise, no. You're only using the variable for creating the timer (and the data is being attached to the timer directly), then just using the variable to avoid repetitive function calls i.e.

JASS:
set GTimer = GetExpiredTimer ()
local Data d = GetData (GTimer)
if something then
  call RemoveData (GTimer)
  call PauseTimer (GTimer)
  call DestroyTimer (GTimer)
//Or recycle, whichever applies to you
endif

//Rather than calling GetExpiredTimer 4 times ^_^

You won't have any leaks, since you always have the GetExpiredTimer reference (unless you supply a null codeFunc)
 
Reaction score
456
> Then you have 8000 timer variables
It's one array. Not 8000 timer variables.
 

Romek

Super Moderator
Reaction score
963
I'd like to know more about this timer bug. I've never heard of it.. :confused:

You might as well use TT if you're going to be attaching everything to a single timer as well.
 
Reaction score
456
> Which would be equivalent to ~8000 timer variables, right?
That'd be so retarted if it was like that.
 

Romek

Super Moderator
Reaction score
963
> Which would be equivalent to ~8000 timer variables, right?
That'd be so retarted if it was like that.
I'm sure that variables don't mean much if they're not set to a value.
So 8000 null timer variables shouldn't be much of a problem, as long as the timers which are created end up being destroyed.
 

saw792

Is known to say things. That is all.
Reaction score
280
Hmm... it seems I have confused my bugs... slightly.

The bug I was refering to actually only occurs after destroying a timer, and then setting it to null. Which I actually did. Wrong way around guys, sorry. Fixed.
 

Sim

Forum Administrator
Staff member
Reaction score
534
On a side note, I like test map features, such as press ESC and regain hp/mana, resurrect, etc.

We have a test map made by Tinki3 to be used especially for this!

;)

Approved.
 

Sim

Forum Administrator
Staff member
Reaction score
534
saw792, you are currently an active user.

Could you update it in a near future?
 
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