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

[JASS] Shuriken Strike v1.0g

WARNING: NO REAL NINJA INCLUDED.

SHURIKEN STRIKE

Releases a magical shuriken that deals damage to the target and then bounces between nearby enemies. The shuriken returns to the Hero afterwards. Area of Effect and projectile speed improves with level. Each bounce incurs a small damage loss.

Level 1 - 150 main target damage. 6 bounces. 10% damage loss.
Level 2 - 200 main target damage. 7 bounces. 9% damage loss.
Level 3 - 250 main target damage. 8 bounces. 8% damage loss.
Level 4 - 300 main target damage. 9 bounces. 7% damage loss.

Credit:
+ Geries for his Hashtable Dynamic Indexing method and help me improve the method to get a random unit from a group.
+ Maker for helping with miscellaneous issues.


+ udg_SS_Hashtable: Spell Hashtable



JASS:
// CONFIGURATION

// Constants

// Main spell raw code
constant function SS_GetSpellID takes nothing returns integer
    return 'A005'
endfunction

// Main projectile dummy raw code
constant function SS_DummyID takes nothing returns integer
    return 'h002'
endfunction

// Main projectile model effect
constant function SS_Sfx takes nothing returns string
    return "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl"
endfunction

// Blood effect
constant function SS_Sfx1 takes nothing returns string
    return "Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl"
endfunction

// Attachment point
constant function SS_AttachPoint takes nothing returns string
    return "origin"
endfunction

// Movement interval
constant function SS_Interval takes nothing returns real
    return 0.03125
endfunction

// Should a unit be hit only once or not?
// True = yes, a unit will be hit only once
// False = no, a unit can be hit multiple times

constant function SS_HitOnce takes nothing returns boolean
    return false
endfunction

// Should smart homing be enabled?
// True = enable smart homing, the shuriken will change target if the current homed unit is dead
// False = disable smart homing

constant function SS_SmartHoming takes nothing returns boolean
    return true
endfunction

// Filter valid units

function SS_FilterUnit takes unit caster, unit u returns boolean
    return IsUnitEnemy(u, GetOwningPlayer(caster)) and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, 'Avul') == 0 and not IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType (u, UNIT_TYPE_STRUCTURE)
endfunction

// Get Abilities Level

function SS_SpellLvl takes unit caster returns integer
    return GetUnitAbilityLevel (caster, SS_GetSpellID()) // Get the spell level of the caster
endfunction

// Level dependable values

// Main target damage
function SS_Damage takes integer level returns real
    return 100 + level*50.
endfunction

// The damage change (%) for each target after the last
// Set to positive if you want the spell to deals lesser damage with each bounce
// Set to negative if you want the spell to deals greater damage with each bounce

function SS_DamageRate takes integer level returns real
    return 11 - level*1.
endfunction

// Area of Effect
function SS_AoE takes integer level returns real
    return 500 + level*100.
endfunction

// Number of bounces
function SS_Bounce takes integer level returns integer
    return 5 + level
endfunction

// Projectile Speed
function SS_Speed takes integer level returns real
    return (800. + level*150.)/(1/SS_Interval())
endfunction

// Attack - Damage - Weapon type

// Attack type
function SS_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_NORMAL
endfunction

// Damage type
function SS_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_MAGIC
endfunction

// Weapon type
function SS_WeaponType takes nothing returns weapontype
    return WEAPON_TYPE_METAL_MEDIUM_SLICE
endfunction

// END CONFIGURATION

// SPELL FUNCTIONS

// Here we create a list of units that can be picked from

function SS_MakeList takes nothing returns boolean
    local unit u = GetFilterUnit()
    local integer i = LoadInteger(udg_SS_Hashtable, -4, 0)
    local integer ID = LoadInteger(udg_SS_Hashtable, -4, -3)
    local boolean b = SS_FilterUnit(LoadUnitHandle(udg_SS_Hashtable, -4, -2), u) and u != LoadUnitHandle(udg_SS_Hashtable, -4, -1)
    
    if not SS_HitOnce() then
        if b then
            set i = i + 1
            call SaveUnitHandle(udg_SS_Hashtable, -4, i, u)
        endif
    else
        if b and not IsUnitInGroup(u, LoadGroupHandle(udg_SS_Hashtable, ID, 6)) then
            set i = i + 1
            call SaveUnitHandle(udg_SS_Hashtable, -4, i, u)
        endif
    endif
    call SaveInteger(udg_SS_Hashtable, -4, 0, i)
    set u = null
    return false
endfunction

// The loop where we index, deindex, and recycle everything
// This is also the main function of the spell

function SS_TimerLoop takes nothing returns nothing
    local integer ID
    local integer listmax = LoadInteger(udg_SS_Hashtable, -1, 0 )
    local unit caster
    local unit target
    local unit dummy
    local integer i = 0
    local real x1
    local real y1
    local real x2
    local real y2
    local unit u
    local real angle
    local integer i1
    local group g
    local group g1
    local integer i2
    local integer i3

    // Now we start looping through all the spell instances
    loop
        exitwhen i >= listmax
        // First we load the saved values of a spell instance
        set i = i + 1
        set ID = LoadInteger(udg_SS_Hashtable, -1, i)
        set caster = LoadUnitHandle(udg_SS_Hashtable, ID, 1)
        set target = LoadUnitHandle(udg_SS_Hashtable, ID, 2)
        set dummy = LoadUnitHandle(udg_SS_Hashtable, ID, 3)
        set g1 = LoadGroupHandle(udg_SS_Hashtable, ID, 6)
        set x1 = GetUnitX(dummy)
        set y1 = GetUnitY(dummy)
        set x2 = GetUnitX(target)
        set y2 = GetUnitY(target)
        set i1 = SS_SpellLvl(caster)
        set i2 = LoadInteger (udg_SS_Hashtable, ID, 5)
        if (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) < 900 then
            if caster != target then

                // The shuriken collided with an enemy so we will deal damage

                call DestroyEffect (AddSpecialEffectTarget(SS_Sfx1(), target, SS_AttachPoint()))
                call DestroyEffect (AddSpecialEffectTarget(SS_Sfx(), target, SS_AttachPoint()))
                call UnitDamageTarget(caster, target, SS_Damage(i1)*Pow(1 - (SS_DamageRate(i1)/100), SS_Bounce(i1)-i2), false, false, SS_AttackType(), SS_DamageType(), SS_WeaponType())

                if SS_HitOnce() then
                    call GroupAddUnit(g1,target)
                endif

            else

                // The shuriken returned to the caster and thus we will destroy the dummy and begin deindexing and recycling

                call KillUnit (dummy)
                call DestroyEffect (AddSpecialEffect(SS_Sfx(), x1, y1))
                call DestroyGroup (g1)

                if listmax != i then
                    call SaveInteger(udg_SS_Hashtable, LoadInteger(udg_SS_Hashtable, -1 , listmax), 0, i)
                    call SaveInteger(udg_SS_Hashtable, -1, i, LoadInteger(udg_SS_Hashtable, -1, listmax))
                    call RemoveSavedInteger(udg_SS_Hashtable, -1, listmax)
                    set i = i -1
                endif

                set listmax = listmax -1
                call SaveInteger(udg_SS_Hashtable, -1, 0, listmax )

                if LoadInteger(udg_SS_Hashtable, 0, 0) + 1 == LoadInteger(udg_SS_Hashtable, 0, -1) then
                    call FlushChildHashtable(udg_SS_Hashtable, 0) 
                    call PauseTimer(LoadTimerHandle(udg_SS_Hashtable, -2, 0))
                else
                    call SaveInteger (udg_SS_Hashtable, 0, 0, LoadInteger(udg_SS_Hashtable, 0, 0) + 1)
                    call SaveInteger(udg_SS_Hashtable, 0, LoadInteger(udg_SS_Hashtable, 0, 0), ID)
                endif

                call FlushChildHashtable(udg_SS_Hashtable, ID)
            endif

            // Now we check how many bounces are left and choose the next target

            if i2 > 1 then
                set g = CreateGroup()
                call SaveUnitHandle(udg_SS_Hashtable, -4, -1, target)
                call SaveUnitHandle(udg_SS_Hashtable, -4, -2, caster)
                call SaveInteger(udg_SS_Hashtable, -4, -3, ID)
                call GroupEnumUnitsInRange(g, x1, y1, SS_AoE(i1), Condition(function SS_MakeList))
                call DestroyGroup(g)
                set g = null 
                set i3 = LoadInteger(udg_SS_Hashtable, -4, 0)

                if i3 == 0 then
                    call SaveUnitHandle(udg_SS_Hashtable, ID, 2, caster)
                else
                    call SaveUnitHandle(udg_SS_Hashtable, ID, 2, LoadUnitHandle(udg_SS_Hashtable, -4, GetRandomInt(1, i3)))
                    call SaveInteger(udg_SS_Hashtable, ID, 5, i2 - 1)
                    call FlushChildHashtable(udg_SS_Hashtable, -4)
                endif

            else
                call SaveUnitHandle(udg_SS_Hashtable, ID, 2, caster)
            endif

        else
            if IsUnitType(caster, UNIT_TYPE_DEAD) then
                call KillUnit (dummy)
                call DestroyEffect (AddSpecialEffect(SS_Sfx(), x1, y1))
                call DestroyGroup (g1)

                if listmax != i then
                    call SaveInteger(udg_SS_Hashtable, LoadInteger(udg_SS_Hashtable, -1 , listmax), 0, i)
                    call SaveInteger(udg_SS_Hashtable, -1, i, LoadInteger(udg_SS_Hashtable, -1, listmax))
                    call RemoveSavedInteger(udg_SS_Hashtable, -1, listmax)
                    set i = i -1
                endif

                set listmax = listmax -1
                call SaveInteger(udg_SS_Hashtable, -1, 0, listmax)

                if LoadInteger(udg_SS_Hashtable, 0, 0) + 1 == LoadInteger(udg_SS_Hashtable, 0, -1) then
                    call FlushChildHashtable(udg_SS_Hashtable, 0) 
                    call PauseTimer(LoadTimerHandle(udg_SS_Hashtable, -2, 0))
                else
                    call SaveInteger (udg_SS_Hashtable, 0, 0, LoadInteger(udg_SS_Hashtable, 0, 0) + 1)
                    call SaveInteger(udg_SS_Hashtable, 0, LoadInteger(udg_SS_Hashtable, 0, 0), ID)
                endif

                call FlushChildHashtable(udg_SS_Hashtable, ID)
            endif

            if IsUnitType(target, UNIT_TYPE_DEAD) then

                if SS_SmartHoming() then
                    set g = CreateGroup()
                    call SaveUnitHandle(udg_SS_Hashtable, -4, -1, target)
                    call SaveUnitHandle(udg_SS_Hashtable, -4, -2, caster)
                    call SaveInteger(udg_SS_Hashtable, -4, -3, ID)
                    call GroupEnumUnitsInRange(g, x1, y1, SS_AoE(i1), Condition(function SS_MakeList))
                    call DestroyGroup(g)
                    set g = null
                    set i3 = LoadInteger(udg_SS_Hashtable, -4, 0)

                    if i3 == 0 then
                        call SaveUnitHandle(udg_SS_Hashtable, ID, 2, caster)
                    else
                        call SaveUnitHandle(udg_SS_Hashtable, ID, 2, LoadUnitHandle(udg_SS_Hashtable, -4, GetRandomInt(1, i3)))
                        call SaveInteger(udg_SS_Hashtable, ID, 5, i2 - 1)
                        call FlushChildHashtable(udg_SS_Hashtable, -4)
                    endif

                else
                    call SaveUnitHandle(udg_SS_Hashtable, ID, 2, caster)
                endif
            else
                // Move all the projectiles!
                set angle = Atan2(y2-y1, x2-x1)
                call SetUnitX(dummy, x1 + (SS_Speed(i1))*Cos(angle))
                call SetUnitY(dummy, y1 + (SS_Speed(i1))*Sin(angle))
                call SetUnitFacing(dummy, angle*bj_RADTODEG)
            endif
        endif
    set caster = null
    set target = null
    set dummy = null
    set g1 = null
    endloop
endfunction

// Initiate function

function Trig_SS_Conditions takes nothing returns boolean
    local unit caster
    local unit target
    local integer ID
    local integer listmax
    local real x1
    local real y1
    local real x2
    local real y2

    if GetSpellAbilityId() == SS_GetSpellID() then
        set caster = GetTriggerUnit()
        set target = GetSpellTargetUnit()
        set x1 = GetUnitX(caster)
        set y1 = GetUnitY(caster)
        set x2 = GetUnitX(target)
        set y2 = GetUnitY(target)
        set ID = LoadInteger(udg_SS_Hashtable, 0, 0)

        // Now we start indexing the spell

        if ID>0 then
            call SaveInteger(udg_SS_Hashtable, 0, 0, ID - 1)
            set ID = LoadInteger (udg_SS_Hashtable, 0, ID)
        else
            set ID = LoadInteger(udg_SS_Hashtable, 0, -1) + 1
            call SaveInteger(udg_SS_Hashtable, 0,-1, ID)

            if ID == 1 then
                call TimerStart (LoadTimerHandle(udg_SS_Hashtable, -2, 0), SS_Interval(), true, function SS_TimerLoop)
            endif
        endif

        // Save the values related to this spell instance
        call SaveUnitHandle(udg_SS_Hashtable, ID, 1, caster)
        call SaveUnitHandle(udg_SS_Hashtable, ID, 2, target)
        call SaveUnitHandle (udg_SS_Hashtable, ID, 3, CreateUnit(GetTriggerPlayer(), SS_DummyID(), x1, y1, Atan2(y2-y1, x2-x1)*bj_RADTODEG))
        call SaveInteger(udg_SS_Hashtable, ID, 5, SS_Bounce(SS_SpellLvl(caster)))
        call SaveGroupHandle(udg_SS_Hashtable, ID, 6, CreateGroup())

        set listmax = LoadInteger (udg_SS_Hashtable, -1, 0) + 1
        call SaveInteger( udg_SS_Hashtable, -1, 0, listmax )
        call SaveInteger (udg_SS_Hashtable, -1, listmax, ID)
        call SaveInteger (udg_SS_Hashtable, ID, 0, listmax)

        set caster = null
        set target = null
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Shuriken_Strike takes nothing returns nothing
    local trigger SS = CreateTrigger()
    set udg_SS_Hashtable = InitHashtable ()
    call SaveTimerHandle (udg_SS_Hashtable, -2, 0, CreateTimer())
    call TriggerRegisterAnyUnitEventBJ(SS, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(SS, Condition( function Trig_SS_Conditions ) )
    set SS = null
endfunction


+ v1.0: Initial release
+ v1.0a: Fixed some cache issues plus the new dummy removal thing.
+ v1.0b: Fixed the bug with getting a "true" random unit with each bounce.
+ v1.0c: Improved the method to get a random unit for better performance. Thanks Geries.
+ v1.0d: Added better importing instructions plus fixing a bug in the code that prevents the spell from compiling in JNPG (thanks Maker).
+ v1.0e: Improved the spell according to Maker's awesome review :D
+ v1.0f: Fixed a major bug that causes the shuriken to get stuck if the caster is dead plus adding a new mechanic (Smart Homing) to the spell.
+ v1.0g: Fixed an issue with the timer and indexing method.


Keywords:
shuriken, ninjutsu, ninja, assassin, mcninja, batman, spell, jass
Contents

Shuriken Strike v1.0g (Map)

Reviews
Approved The spell works and looks good. Bonus points for having nice tooltip. I found this one very satisfactory to cast. Originality is a bit lacking, there are alredy two similar spells in the database, but they are in GUI. The blood effects...

Moderator

M

Moderator

Reviewed by Maker, Shuriken Strike v1.0e, 17th Jan 2013

Approved

The spell works and looks good. Bonus points for having nice tooltip.
I found this one very satisfactory to cast.
Originality is a bit lacking, there are alredy two similar spells in the database, but they are in GUI.
The blood effects, which are pleasant, seem to spawn above the targets, you could look into that.

4.5/5

Reviewed by Maker, Shuriken Strike v1.0d, 15th Jan 2013

Approved

The indexing is hard to follow especially with hashtables. The indexing works perfectly so no complaints there.
You could look into using arrays the next time.
The spell could benefit from having a sound effect when the glaives hit, otherwise thing looks smooth.
Originality is a bit lacking, there is alredy two similar spells in the database, but they are in GUI.
All in all the spell works and looks good. Bonus points for having good tooltip.

Suggested changes:
  • Change default weapon type to WEAPON_TYPE_METAL_MEDIUM_SLICE
  • ATTACK_TYPE_FIRE is the same as ATTACK_TYPE_MAGIC
    Magic is the base damage type, you could use that
  • You could add configurability whether the shurikens hit the same target only once or everal times
  • In SS_TimerLoop, rethink where the last destroygroup and group nulling should be
 
Review:
1)To be cached to add some speed:
GetUnitX(caster)
GetUnitY(caster)
GetUnitX(target)
GetUnitY(target)
LoadInteger(udg_SS_Hashtable, 0, 0)
2)Minimum dist(900) can be somewhat configurable.
3)because of the new discovery of Maker,removing unit does not exactly clean the leak.
Please configure the object data of the dummy then go to Art - Death Time then set it to 0
4)[jass=]call SaveInteger(udg_SS_Hashtable, -1, 0, listmax )
if LoadInteger(udg_SS_Hashtable, 0, 0) == LoadInteger(udg_SS_Hashtable, 0, -1) + 1 then[/code]
You dont need to save/load it because you already have this:
set listmax = listmax -1

Needs Fix
That's all i can see,because now,we are experiencing JASS bb code bugs w/c makes the code unreadable.

At least,you made a non-storm-related spell :D
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
Review:
1)To be cached to add some speed:
GetUnitX(caster)
GetUnitY(caster)
GetUnitX(target)
GetUnitY(target)
LoadInteger(udg_SS_Hashtable, 0, 0)
2)Minimum dist(900) can be somewhat configurable.
3)because of the new discovery of Maker,removing unit does not exactly clean the leak.
Please configure the object data of the dummy then go to Art - Death Time then set it to 0
4)[jass=]call SaveInteger(udg_SS_Hashtable, -1, 0, listmax )
if LoadInteger(udg_SS_Hashtable, 0, 0) == LoadInteger(udg_SS_Hashtable, 0, -1) + 1 then[/code]
You dont need to save/load it because you already have this:
set listmax = listmax -1

Needs Fix
That's all i can see,because now,we are experiencing JASS bb code bugs w/c makes the code unreadable.

At least,you made a non-storm-related spell :D

1/ Geries' thumb rule that I followed: something used less than 3 times doesn't deserve a local. Let's see what Mag has to say. And the integer you mentioned is already cached.

2/ Completely unnecessary imo

3/ It is already done. Open the test map and see for yourself.

4/ I am not entirely sure. Geries left it there for a reason. I will need to consult him.

EDIT: Okay I talked to Mag. You are wrong about the 4th thing. It is correctly written like that. And about the cache issue I fixed them.
 
Last edited:
Level 14
Joined
Aug 8, 2010
Messages
1,022
I am a GUIer, so i can't say anything about the code.
The spell is neat and i like it a lot. There is only one thing you may add - check if there are any enemies in the way of the shuriken while it's returning to the caster and deal damage to them if there are any. Because it looks really weird when the shuriken passes straight to an enemy with no damage.. This only a suggestion, remember!
The spell is pretty simple and neat. I'll give 4/5! :)
 
Last edited:
Level 16
Joined
Dec 15, 2011
Messages
1,423
I am a GUIer, so i can't say anything about the code.
The spell is neat and i like it a lot. There is only one thing you may add - check if there are any enemies in the way of the shuriken while it's returning to the caster and deal damage to them if there are any. Because it looks really weird when the shuriken passes straight to an enemy with no damage.. This only a suggestion, remember!
The spell is pretty simple and neat. I'll give 4/5! :)


Thanks for taking your time testing. However about your suggestion allows me to point out that the shuriken passes enemies w/o dealing dmg when it is bouncing. Your suggestion therefore may create an inconsistency within the spell. Nice one anyway :D
 
Level 14
Joined
Aug 8, 2010
Messages
1,022
Thanks for taking your time testing. However about your suggestion allows me to point out that the shuriken passes enemies w/o dealing dmg when it is bouncing. Your suggestion therefore may create an inconsistency within the spell. Nice one anyway :D
Well, you can let the user decide if it should deal damage or not. I mean, make it as a configurable. If it's true - deal damage, else don't. :wink:
 
Level 5
Joined
Dec 9, 2012
Messages
125
This is a very nice spell. The shruiken is very good and the bouncing effect even on enemies it has already encountered is nice. However I don't find its very powerful, and there isn't any cooldown. I'd just suggest raising the strength and and raising the cooldown accordingly by maybe a second or two. Just so people can't spam 24/7 :p
Great spell!
4.5/5
 
Top