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

Mystic Snake v1.5

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: Dion King
Hey guys, this spell gets requested quite a lot and it is one of my personal favourites, it is Mystic Snake from Medusa in DotA, for those of you that do not know what it does, it fires a snake towards an enemy which will damage and steal his mana, then increase its own damage by 20% and bounce to another enemy, it will repeat this untill there is no valid target nearby or its maximum bounce number is met, at which point if the caster is alive, it will fly back to him and restore mana equal to what it stole. It can only effect each enemy once per snake.

I am aware this is not an original idea but it is one of my favourites anyway.

Give credits if you use this spell in your map! (See credits below)

Magtheridon96 for helping with efficiency and finding bugs.
VERY big thanks to Maker for correcting an error i couldn't figure out and giving suggestions for improving the spell.
mateuspv for the snake icon and model file.

Magheridon96 for efficiency and review.
Maker for helping with an error i couldn't figure out.
DotA for the idea, icon and snake model.
mateuspv for extracting the icon and snake model.
Me, Zaio for the coding.

Create 1 timer array with size 1 called TimerUtils_timer
Create 1 integer called TimerUtils_int
Create 1 hashtable called TimerUtils_hash

Copy ALL of the following code into your maps header (the map icon above all triggers and folders)

NOTE: Do NOT give me credits for TimerUtils, i did not make it.
JASS:
//*************************************************
//*
//*   TimerUtils (Jass Version)
//*   v1.3.2.0
//*   By Magtheridon96
//*
//*   Original version by Vexorian.
//*   All the functions have O(1) complexity.
//*
//*   API:
//*       - function NewTimer takes nothing returns timer
//*           - Returns a new timer from the stack
//*       - function ReleaseTimer takes timer t returns nothing
//*           - Throws a timer back into the stack
//*       - function SetTimerData takes timer t, integer value returns nothing
//*           - Attaches a value to a timer
//*       - function GetTimerData takes timer t returns integer
//*           - Returns the attached value
//*
//*************************************************

    function SetTimerData takes timer t, integer value returns nothing
        call SaveInteger(udg_TimerUtils_hash,GetHandleId(t),0,value)
    endfunction

    function GetTimerData takes timer t returns integer
        return LoadInteger(udg_TimerUtils_hash,GetHandleId(t),0)
    endfunction

    function NewTimer takes nothing returns timer
        if 0==udg_TimerUtils_int then
            return CreateTimer()
        endif
        set udg_TimerUtils_int = udg_TimerUtils_int - 1
        return udg_TimerUtils_timer[udg_TimerUtils_int]
    endfunction

    function ReleaseTimer takes timer t returns nothing
        call PauseTimer(t)
        set udg_TimerUtils_timer[udg_TimerUtils_int] = t
        set udg_TimerUtils_int = udg_TimerUtils_int + 1
    endfunction

    function InitTrig_TimerUtils takes nothing returns nothing
        set udg_TimerUtils_hash = InitHashtable()
    endfunction
JASS:
//Mystic Snake by Zaio - http://www.hiveworkshop.com/forums/spells-569/mystic-snake-v1-5-a-204984/
//===================================================================================================================================
//To Import this spell:
//1 - Make sure you have installed TimerUtils (instructions on main page in the hidden tap called IMPORTANT:Requires)
//2 - Copy over the ability and dummy and edit them as you want
//3 - Create a hashtable named hash (if you already one named hash, skip to 4) by opening up variable editor (Ctrl + B)
//4 - Make sure "automatically create unknown variables when pasting trigger data" is enabled in map preferences
//5 - Copy over this trigger to your map
//6 - Edit the 2 configurables below for your map

constant function MSDID takes nothing returns integer
    return 'h000' // The dummy's ID (ctrl + d in object editor)
endfunction

constant function MSAID takes nothing returns integer
    return 'A000' // The ability's ID (ctrl + d in object editor)
endfunction

//7(Optional) - If you want the snake icon and model, export the imports used in this map and save their name along with their path into a word document (or something similar) open up your map and import the models, set their custom paths to the ones you wrote down earlier
//8(Optional) - Change the ability and dummy icons to ReplaceableTextures\CommandButtons\BTNMysticSnakeNew.blp then change the dummy's model file to MysticSnake.mdl
//9(Optional) - Change the tooltips (highly recommended) to your preference, they have been poorly done in the test map because it is all about the map they are running in, almost all maps use different base values for health on heros and units, therefore the tooltip will need to be changed accordingly.
//10(Optional) - Customize the dummy as wanted, reduce/increase its sight radius etc and change the configurables below.
//11 - DONE!
//======================================== CONFIGURABLES ============================================================================
function MSIBN takes nothing returns integer
    return 3 // The base number of jumps + (level of ability * MSABN)
endfunction

function MSABN takes nothing returns integer
    return 1 // The extra number of bounces per level (e.g. if this is set to 1 and MSIBN is set to 3, level 1 will bounce 4 times, level 2 will bounce 5 times etc)
endfunction

function MSIMB takes nothing returns real
    return 70.00 // The mana burn base amount + (level of ability * MSAMB)
endfunction

function MSAMB takes nothing returns real
    return 30.00 // The mana burn increment per level (e.g. if this is set to 30.00 and MSIMB is set to 70.00, level 1 will burn upto 100 base mana, level 2 upto 130 mana etc)
endfunction

function MSPDI takes nothing returns real
    return 120.00 // This is the percentage of mana burn increase (if set to 120.00 = 20% increase per bounce)
endfunction

function MSEOM takes nothing returns string
    return "Abilities\\Spells\\Other\\Charm\\CharmTarget.mdl" // The effect path if the snake returns to the caster
endfunction

function MSEOT takes nothing returns string
    return "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl" // The effect path if the snake hits an enemy
endfunction

function MSEAM takes nothing returns string
    return "chest" // The attachment point for the effect if the snake returns to the caster
endfunction

function MSEAT takes nothing returns string
    return "overhead" // The attachment point for the effect if the snake hits an enemy
endfunction

function MSDPS takes nothing returns real
    return 300.00 // This is the distance the snake will travel per second
endfunction

function MSRAOE takes nothing returns real
    return 100.00 // This is the max distance the snake must be to return to the caster to restore mana
endfunction

function MSAAOE takes nothing returns real
    return 50.00 // This is the max distance the snake must be to attack a unit and steal mana
endfunction

function MSSR takes nothing returns real
    return 400.00 // This is the search radius the snake will check units in after it has mana burned a different unit
endfunction

//============================================ END CONFIGURABLES ==========================================================
//============================= DO NOT EDIT BELOW THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING! =================================

function MSFilter takes nothing returns boolean
    local unit u = GetFilterUnit()
    local boolean b = not IsUnitInGroup(u, udg_Temp_Group) and IsUnitEnemy(u, GetOwningPlayer(udg_Temp_Unit)) and not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MECHANICAL) and not (GetUnitAbilityLevel(u, 'Bams') > 0) and not (GetUnitAbilityLevel(u, 'BHds') > 0)
    set u = null
    return b
endfunction

function Loop takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local integer bn = LoadInteger(udg_hash, id, 3)
    local unit s = LoadUnitHandle(udg_hash, id, 1)
    local unit u
    local unit u2
    local unit u3
    local unit u4 = LoadUnitHandle(udg_hash, id, 2)
    local real x
    local real x2
    local real x3
    local real y
    local real y2
    local real y3
    local real a
    local real d
    local real m
    local real m2
    local real dx
    local real dy
    local group g
    local real dmg
    if 0 >= bn or u4 == null then
      set u2 = LoadUnitHandle(udg_hash, id, 0)
      if IsUnitType(u2, UNIT_TYPE_DEAD) then
        set g = LoadGroupHandle(udg_hash, id, 4)
        call DestroyGroup(g)
        set g = null
        call ReleaseTimer(t)
        call KillUnit(s)
        call FlushChildHashtable(udg_hash, id)
      else
        set x3 = GetUnitX(u2)
        set y3 = GetUnitY(u2)
        set x2 = GetUnitX(s)
        set y2 = GetUnitY(s)
        set dx = x3 - x2
        set dy = y3 - y2
        set d = SquareRoot(dx * dx + dy * dy)
        if d <= MSRAOE() then
          set m = LoadReal(udg_hash, id, 6)
          call SetUnitState(u2, UNIT_STATE_MANA, (GetUnitState(u2, UNIT_STATE_MANA) + m))
          call DestroyEffect(AddSpecialEffectTarget(MSEOM(), u2, MSEAM()))
          set g = LoadGroupHandle(udg_hash, id, 4)
          call DestroyGroup(g)
          set g = null
          call ReleaseTimer(t)
          call KillUnit(s)
          call FlushChildHashtable(udg_hash, id)
        else
          set a = Atan2(y3 - y2, x3 - x2)
          set x = x2 + MSDPS() / 33. * Cos(a)
          set y = y2 + MSDPS() / 33. * Sin(a)
          call SetUnitX(s, x)
          call SetUnitY(s, y)
          call SetUnitFacing(s, (bj_RADTODEG * a))
        endif
      endif
      set u2 = null
    else
      set x = GetUnitX(s)
      set y = GetUnitY(s)
      set u = LoadUnitHandle(udg_hash, id, 2)
      set x2 = GetUnitX(u)
      set y2 = GetUnitY(u)
      set dx = x2 - x
      set dy = y2 - y
      set d = SquareRoot(dx * dx + dy * dy)
      if d <= MSAAOE() then
        if not IsUnitType(u, UNIT_TYPE_DEAD) then
          set u3 = LoadUnitHandle(udg_hash, id, 0)
          set dmg = LoadReal(udg_hash, id, 5)
          set m2 = GetUnitState(u, UNIT_STATE_MANA)
          set m = LoadReal(udg_hash, id, 6)
          if dmg > m2 then
            set m = (m + m2)
            call SetUnitState(u, UNIT_STATE_MANA, 0.)
          else
            set m = (m + dmg)
            call SetUnitState(u, UNIT_STATE_MANA, (GetUnitState(u, UNIT_STATE_MANA) - dmg))
          endif
          call UnitDamageTarget(u3, u, dmg, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
          call DestroyEffect(AddSpecialEffectTarget(MSEOT(), u, MSEAT()))
          call SaveReal(udg_hash, id, 6, m)
          set dmg = (dmg * (MSPDI() / 100))
          call SaveReal(udg_hash, id, 5, dmg)
          set u3 = null
        endif
        set udg_Temp_Group = LoadGroupHandle(udg_hash, id, 4)
        call GroupAddUnit(udg_Temp_Group, u)
        set udg_Temp_Unit = s
        call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, MSSR(), Filter(function MSFilter))
        set u2 = FirstOfGroup(bj_lastCreatedGroup)
        if u2 != null then
            call SaveUnitHandle(udg_hash, id, 2, u2)
            set bn = (bn - 1)
            call SaveInteger(udg_hash, id, 3, bn)
            set u2 = null
        else
            call RemoveSavedHandle(udg_hash, id, 2)
        endif
      else
        set a = Atan2(y2 - y, x2 - x)
        set x3 = x + MSDPS() / 33. * Cos(a)
        set y3 = y + MSDPS() / 33. * Sin(a)
        call SetUnitX(s, x3)
        call SetUnitY(s, y3)
        call SetUnitFacing(s, (bj_RADTODEG * a))
      endif
      set u = null
    endif
    set t = null
    set s = null
    set u4 = null
endfunction

function Start takes nothing returns boolean
    local unit u
    local timer t
    local unit u2
    local real x
    local real y
    local integer id
    if GetSpellAbilityId() == MSAID() then
      set t = NewTimer()
      set id = GetHandleId(t)
      set u = GetTriggerUnit()
      set x = GetUnitX(u)
      set y = GetUnitY(u)
      set u2 = GetSpellTargetUnit()
      set bj_lastCreatedUnit = CreateUnit(GetTriggerPlayer(), MSDID(), x, y, bj_RADTODEG * Atan2(GetUnitY(u2) - y, GetUnitX(u2) - x))
      call TimerStart(t, 0.03125, true, function Loop)
      call SaveUnitHandle(udg_hash, id, 0, u)
      call SaveUnitHandle(udg_hash, id, 1, bj_lastCreatedUnit)
      call SaveUnitHandle(udg_hash, id, 2, u2)
      call SaveInteger(udg_hash, id, 3, MSIBN() + (MSABN() * GetUnitAbilityLevel(u, MSAID())))
      call SaveGroupHandle(udg_hash, id, 4, CreateGroup())
      call SaveReal(udg_hash, id, 5, (MSIMB() + (MSAMB() * GetUnitAbilityLevel(u, MSAID()))))
      set u = null
      set u2 = null
      set t = null
    endif
    return false
endfunction

function InitTrig_Mystic_Snake takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Start ) )
    if udg_hash == null then
      set udg_hash = InitHashtable()
    endif
    set t = null
endfunction

Changelog

1.5 - Updated most of what Maker put, prefixed the two functions Spinnaker mentioned with "constant" and changed the Start function with the one Spinnaker provied (Thanks you two!)
1.4 - Another efficiency update.
1.3 - Efficiency update and fixed a very minor leak which i had not noticed before.
1.2 - Added the original snake model and icon, as well as importing instructions for them.
1.1 - Using TimerUtils.

Keywords:
mystic, snake, mana, steal, burn, medusa, return, homing, bounce, bounces, damage, increase, jass, mui, spamable, feedback, restore, DotA, defense, of
Contents

Just another Warcraft III map (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 14 Nov 2011 Bribe: This should use a FirstOfGroup loop instead of a filter. As Maker said you should use a single timer, because the timeout is less than 0.07 seconds. Rather than...
Level 7
Joined
Jul 3, 2011
Messages
251
QQ. About the linked list, although i know how to do the timer part, how do i do the linked list part? I figure a global will be required for the current number of casts, however i can't figure out how to do it without conflictions with other spells or requiring another hashtable, which i would prefer not to do.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
QQ. About the linked list, although i know how to do the timer part, how do i do the linked list part? I figure a global will be required for the current number of casts, however i can't figure out how to do it without conflictions with other spells or requiring another hashtable, which i would prefer not to do.

2 global arrays


given you do instancing it is

add
JASS:
set next[instance] = next[0]
set prev[instance] = 0
set next[prev[0]] = instance
set next[0] = instance

remove
JASS:
set next[prev[instance]] = next[instance]
set prev[next[instance]] = prev[instance]
 
Level 7
Joined
Jul 3, 2011
Messages
251
Hey, I'm afraid that coding in GUI is a bit of a hassle when you know JASS, it's just overkill compared to how simple and quickly you can do it in JASS.

Apart from that I have kind of left hive now, I check in every now and then but I haven't coded in Warcraft 3 for multiple months, sorry.
 
Level 7
Joined
Jul 3, 2011
Messages
251
I don't have a reason to update this one. It has not been rejected due to leaking or anything, the spell works perfectly fine without leaks and is highly configurable. The group issue that Bribe was complaining about is negligible and I don't want to edit my timers as they want, because even though it's less work for the load to do, it can cause a few ticks to be missed out on, especially if the timer was already running from the spell being cast before. This spell has no reason to be rejected based on the majority of accepted spells on this site, the mods are just being overly picky here.

So for that reason I'm not updating the spell as the mods requested.
 
Top