Path of Shadows
by kenny!
v11
v11
General Information:
This is a really fun spell that i just had to make and share with everyone. It may look a little basic, but the overall application of a skill like this is limitless. It took a while to get all the kinks worked out, but i think i finally got most of it down packed. So yeah, download the map, give it a try, as the screenshots will not do it justice. I left many options configurable so you can muck around and do almost anything with it. Have fun!
Requires:
- GTrigger by Jesus4Lyf.
- JassHelper 0.9.G.0 or higher (Can be found in the latest NewGen pack).
Description:
Gives the hero the ability to move rapidly to the target point or unit of an order it receives.
Level 1 - Moves at a rapid pace and has a maximum distance of 600. Also deals 40 damage to units who are hit by the hero. Has a 14 second cooldown.
Spell will not trigger if the target point or unit is within 200 distance.
Level 2 - Moves at a very rapid pace and has a maximum distance of 800. Also deals 80 damage to units who are hit by the hero. Has a 12 second cooldown.
Spell will not trigger if the target point or unit is within 300 distance.
Level 3 - Moves at an extremely rapid pace and has a maximum distance of 1000. Also deals 120 damage to units who are hit by the hero. Has a 10 second cooldown.
Spell will not trigger if the target point or unit is within 400 distance.
Note: The cooldown for this ability has been changed to 2 seconds for testing purposes.
Level 1 - Moves at a rapid pace and has a maximum distance of 600. Also deals 40 damage to units who are hit by the hero. Has a 14 second cooldown.
Spell will not trigger if the target point or unit is within 200 distance.
Level 2 - Moves at a very rapid pace and has a maximum distance of 800. Also deals 80 damage to units who are hit by the hero. Has a 12 second cooldown.
Spell will not trigger if the target point or unit is within 300 distance.
Level 3 - Moves at an extremely rapid pace and has a maximum distance of 1000. Also deals 120 damage to units who are hit by the hero. Has a 10 second cooldown.
Spell will not trigger if the target point or unit is within 400 distance.
Note: The cooldown for this ability has been changed to 2 seconds for testing purposes.
Technical Details:
- When a unit is given an order it will 'dash' to the target location/unit of that order.
- Orders include: "move", "attack" and "smart".
- Movement speed, timescale, cooldown, maximum distance, minimum distance, mana cost and damage are all configurable in the trigger.
- Requires three (3) abilities from the object editor to use.
- It is MUI/MPI and all that.
- It is leakless and lagless and quite efficient.
- Importing difficulty is probably around easy/medium just because of the 3 abilities needed.
- It is pretty awesome.
Implementation:
JASS:
//------------------------------------------------------------------------------------\\
// \\
// To Implement: \\
// \\
// - THIS VERSION REQUIRES GTrigger by Jesus4Lyf. Either copy the trigger labelled \\
// GT or create a new trigger in your map, convert it to custom script and paste \\
// the script for GTrigger into it. \\
// \\
// - Copy the PathofShadows trigger below this, or make a new trigger in your map \\
// and name it PathofShadows, then copy the script from this map to you own. \\
// \\
// - Copy the 3 abilities needed for this skill from this map to yours. \\
// IT IS HIGHLY RECOMMENDED THAT YOU DO THIS AS THEY ARE ALREADY SET UP FOR YOU. \\
// \\
// Tip: The abilities in the object editor have their usage as an editor suffix \\
// so that you can easily see which ability needs to be under which raw code \\
// in the trigger. Example: Path of Shadows (READY_ID) relates to READY_ID, \\
// and Path of Shadows (COOLDOWN_ID) relates to COOLDOWN_ID in the trigger. \\
// \\
// - Once you have copied the abilities, change the raw codes in the PathofShadows \\
// trigger to the ones in your map, then change any configurables you want. \\
// \\
// - Add the ability to a unit and enjoy <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile :)" loading="lazy" data-shortname=":)" />. \\
// \\
//------------------------------------------------------------------------------------\\
//------------------------------------------------------------------------------------\\
// \\
// Credits: \\
// \\
// - Not really sure who did the icon i have used for PathOfShadows, but i do not \\
// take credit for the original icon. I did, however, make the passive one, but \\
// credit for both icons should go to the original maker, whoever he/she is. \\
// \\
// - Credits to Vexorian for original Check Pathability idea, and to Anitarf for \\
// the up-to-date version of it. \\
// \\
// - Credits to Vexorian for SimError. \\
// \\
// - Credits to Tinki3 for the test map. \\
// \\
// - Credits to Jesus4Lyf for GTrigger. It is awesome. \\
// \\
// - And thanks to anyone who helped with this spell. \\
// \\
//------------------------------------------------------------------------------------\\
Config Descriptions:
JASS:
//------------------------------------------------------------------------------------\\
// \\
// Config Descriptions: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// Below is a description of all the constant globals and functions used in Path \\
// of Shadows. \\
// \\
// GLOBALS: \\
// ¯¯¯¯¯¯¯¯¯ \\
// Integers and Reals: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - LEARN_ID - Raw code of the hero ability 'Path of Shadows' - Used for \\
// learning the ability. \\
// \\
// - READY_ID - Raw code of the Initial dummy ability - This is the main \\
// ability used by the spell. \\
// \\
// - COOLDOWN_ID - Raw code of the extra dummy ability added when the unit uses \\
// the 'dash'. \\
// \\
// - ANIM_INDEX - Animation of the unit by index - it is best to use the \\
// walking animation. \\
// \\
// - RED - Red tint given to unit while 'dashing'. 255 is max. \\
// \\
// - GREEN - Green tint given to unit while 'dashing'. 255 is max. \\
// \\
// - BLUE - Blue tint given to unit while 'dashing'. 255 is max. \\
// \\
// - ALPHA - Alpha given to the unit while 'dashing'. Alpha = transparency.\\
// 255 is max. \\
// \\
// - INTERVAL - Interval given for the repeating timer. 0.04 is highly \\
// recommended. If you need a higher frequency 0.02 is okay. \\
// It is far easier to get an accurate distance if the interval \\
// can be easily multiplied into both max dist and move speed. \\
// \\
// - DISTANCE - Distance in front of the unit to check pathability. \\
// Does not need to be changed. \\
// \\
// - COLLISION - The distance away from the hero a unit needs to be to get \\
// damaged. \\
// \\
// Special Effects: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - PLAY_DASH_SFX - Boolean to decide whether or not to play the dash sfx at \\
// the start and end of the 'dash'. \\
// \\
// - DASH_SFX - Special effect played at the start and end of the 'dash'. \\
// \\
// - PLAY_COOLDOWN_SFX - Boolean to decide whether or not to play the Cooldown_sfx \\
// when the cooldown is finished. \\
// \\
// - COOLDOWN_SFX - Special effect played to signal the cooldown is over. \\
// \\
// - COOLDOWN_POINT - Attachment point of the Cooldown_sfx attached to the unit \\
// when the cooldown is over. \\
// \\
// - PLAY_DAMAGE_SFX - Boolean to decide whether or not to play the Damage_sfx \\
// when a unit is damaged. \\
// \\
// - DAMAGE_SFX - Special effect played when a unit gets damaged. \\
// \\
// - DAMAGE_POINT - Attachment point of the Damage_sfx attached to a unit \\
// when it is damaged. \\
// \\
// Error Message: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - ERROR_MSG - Error message for when you do not have enough mana. \\
// \\
// Booleans: \\
// ¯¯¯¯¯¯¯¯¯¯ \\
// - ALLOW_PRELOAD - Whether or not to allow preloading of special effects. \\
// \\
// - ALLOW_PATHING - Whether or not to check for pathable terrain. \\
// \\
// - ALLOW_MANA_COST - Whether or not to allow the mana cost. \\
// \\
// - ALLOW_DAMAGE - Whether or not to damage units in the way. \\
// \\
// - ALLOW_ERROR_MSG - Whether or not to show the error message if you have no \\
// mana. \\
// \\
// - ALLOW_PAUSING - Whether or not to pause the shadow trail images. Some \\
// may like the effect when they are unpaused, others may \\
// not. \\
// \\
// - ALLOW_ORDER_MOVE - Whether or not the "move" order will set off the dash. \\
// \\
// - ALLOW_ORDER_SMART - Whether or not the "smart" order will set off the dash. \\
// \\
// - ALLOW_ORDER_ATTACK - Whether or not the "attack" order will set off the dash. \\
// \\
// Shadow Trail Variables: \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ \\
// - OFFSET - Distance behind the caster at which images are created. \\
// \\
// - ALPHA_START - Starting alpha value of the images created. \\
// \\
// - ALPHA_MINUS - How much ALPHA_START is decreased by every INTERVAL. \\
// \\
// - LOCUST_ID - Raw code of the locust ability. \\
// \\
// - CROW_ID - Raw code of the crow form ability. \\
// \\
// FUNCTIONS: \\
// ¯¯¯¯¯¯¯¯¯¯¯ \\
// - Max_dist - Maximum distance of the dash effect. \\
// \\
// - Min_dist - If the distance between the two points (unit and target \\
// point) is less then this, then the spell will not trigger. \\
// Set to 0.00 if you do not want a minimum distance. \\
// \\
// - Movement_speed - Movement speed of the unit (In units/second). \\
// Currently: 1.5, 2.0, 2.5 times max movespeed in a map (522). \\
// \\
// - Time_scale - Timescale of the unit/level (makes the unit look like its \\
// running fast). \\
// \\
// - Cool_down - Cooldown of the ability. If this is set to zero (0.00) than \\
// the spell will automatically change the cooldown to the \\
// lowest possible cooldown that will not result in an error. \\
// This is usually Distance / Speed. \\
// \\
// - Mana_cost - Mana cost of the ability, if you want it to cost mana. \\
// \\
// - Damage - Damage dealt to units hit, per level. \\
// \\
// - Enemy_filt - Used to filter out units that should be hit. Checking for \\
// enemies is done internally. \\
// \\
//------------------------------------------------------------------------------------\\
Script:
JASS:
//------------------------------------------------------------------------------------\\
// Path of Shadows [v11] \\
// By kenny! \\
// Constructed using vJASS \\
// Requires NewGen WE & GTrigger \\
//------------------------------------------------------------------------------------\\
scope PathofShadows initializer Init
globals
// Configurables:
// Integers and Reals:
private constant integer LEARN_ID = 039;A000039;
private constant integer READY_ID = 039;A001039;
private constant integer COOLDOWN_ID = 039;A002039;
private constant integer ANIM_INDEX = 7
private constant integer RED = 128
private constant integer GREEN = 128
private constant integer BLUE = 128
private constant integer ALPHA = 250
private constant real INTERVAL = 0.04 // Or 0.02
private constant real DISTANCE = 50.00
private constant real COLLISION = 100.00
// Special Effects:
private constant boolean PLAY_DASH_SFX = true
private constant string DASH_SFX = "Abilities\\Spells\\Undead\\CarrionSwarm\\CarrionSwarmDamage.mdl"
private constant boolean PLAY_COOLDOWN_SFX = true
private constant string COOLDOWN_SFX = "Abilities\\Spells\\Undead\\CarrionSwarm\\CarrionSwarmDamage.mdl"
private constant string COOLDOWN_POINT = "overhead"
private constant boolean PLAY_DAMAGE_SFX = true
private constant string DAMAGE_SFX = "Abilities\\Spells\\Undead\\DeathandDecay\\DeathandDecayTarget.mdl"
private constant string DAMAGE_POINT = "head"
// Error Messsage:
private constant string ERROR_MSG = "Your hero does not have enough mana to walk the path of shadows."
// Booleans:
private constant boolean ALLOW_PRELOAD = true
private constant boolean ALLOW_PATHING = true
private constant boolean ALLOW_MANA_COST = false
private constant boolean ALLOW_DAMAGE = true
private constant boolean ALLOW_ERROR_MSG = true
private constant boolean ALLOW_SHADOWS = true
private constant boolean ALLOW_PAUSING = false
private constant boolean ALLOW_ORDER_MOVE = true
private constant boolean ALLOW_ORDER_SMART = true
private constant boolean ALLOW_ORDER_ATTACK = true
// Attack/Damage/Weapon types of damage dealt:
private constant attacktype A_TYPE = ATTACK_TYPE_CHAOS
private constant damagetype D_TYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype W_TYPE = WEAPON_TYPE_WHOKNOWS
// Variables for the shadow trail:
private constant real OFFSET = 20.00
private constant integer ALPHA_START = 250
private constant integer ALPHA_MINUS = 50
private constant integer LOCUST_ID = 039;Aloc039;
private constant integer CROW_ID = 039;Amrf039;
endglobals
//---------------------------------------------------------------------\\
private function Max_dist takes integer lvl returns real
return 400.00 + (200.00 * lvl)
endfunction
private function Min_dist takes integer lvl returns real
return 100.00 + (100.00 * lvl)
endfunction
private function Movement_speed takes integer lvl returns real
return 1000.00 + (500.00 * lvl)
endfunction
private function Time_scale takes integer lvl returns real
return 1.00 + (1.00 * lvl)
endfunction
private function Cool_down takes integer lvl returns real
return 2.00 + (0.00 * lvl)
endfunction
private function Damage_dealt takes integer lvl returns real
return 40.00 * lvl
endfunction
private function Mana_cost takes integer lvl returns integer
return 20 + (20 * lvl)
endfunction
private function Enemy_filt takes nothing returns boolean
return GetWidgetLife(GetFilterUnit()) > 0.405 and IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE) == false and GetUnitFlyHeight(GetFilterUnit()) < 40.00
endfunction
//---------------------------------------------------------------------\\
// \\
// DO NOT TOUCH PAST THIS POINT UNLESS YOU KNOW WHAT YOUR ARE DOING! \\
// \\
//---------------------------------------------------------------------\\
globals
// Needed non-constant globals:
private item array H1 // Needed for hidding necessary items.
private integer H1_total = 0 // Needed for counting hidden items.
private real Game_maxX = 0.00 // Used to set up map bounds.
private real Game_minX = 0.00 // Used to set up map bounds.
private real Game_maxY = 0.00 // Used to set up map bounds.
private real Game_minY = 0.00 // Used to set up map bounds.
private item Path_item = null // Item needed for pathing detection.
private rect Path_rect = null // Rect needed for pathing detection.
private sound Error_sound = null // Error sound used for error message.
// Needed Constant Globals:
private constant real MAX_RANGE = 10.00 // Needed for checking pathing.
private constant integer ORDER_STOP = 851972 // Order ID of the command "stop".
private constant integer ORDER_MOVE = 851986 // Order ID of the command "move".
private constant integer ORDER_SMART = 851971 // Order ID of the command "Smart".
private constant integer ORDER_ATTACK = 851983 // Order ID of the command "attack".
endglobals
//---------------------------------------------------------------------\\
private function Safe_x takes real x returns real
if x < Game_minX then
return Game_minX
elseif x > Game_maxX then
return Game_maxX
endif
return x
endfunction
private function Safe_y takes real y returns real
if y < Game_minY then
return Game_minY
elseif y > Game_maxY then
return Game_maxY
endif
return y
endfunction
//---------------------------------------------------------------------\\
private function Filter_items takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set H1[H1_total] = GetEnumItem()
call SetItemVisible(H1[H1_total],false)
set H1_total = H1_total + 1
endif
endfunction
private function Check_pathability takes real x1, real y1 returns boolean
local real x2 = 0.00
local real y2 = 0.00
call MoveRectTo(Path_rect,x1,y1)
call EnumItemsInRect(Path_rect,null,function Filter_items)
call SetItemPosition(Path_item,x1,y1)
set x2 = GetItemX(Path_item)
set y2 = GetItemY(Path_item)
call SetItemVisible(Path_item,false)
loop
exitwhen H1_total <= 0
set H1_total = H1_total - 1
call SetItemVisible(H1[H1_total],true)
set H1[H1_total] = null
endloop
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) < MAX_RANGE * MAX_RANGE
endfunction
//---------------------------------------------------------------------\\
private function Sim_error takes player whichplayer, string message returns nothing
set message = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00" + message + "|r"
if (GetLocalPlayer() == whichplayer) then
call ClearTextMessages()
call DisplayTimedTextToPlayer(whichplayer,0.5125,0.97120,2.00,message)
call StartSound(Error_sound)
endif
endfunction
//---------------------------------------------------------------------\\
private struct Shadow
unit shadow = null
integer alpha = 0
static thistype array S
static integer Total = 0
static timer Timer = null
static method create takes unit caster, integer id, real angle, real timescale, real x, real y returns thistype
local thistype s = thistype.allocate()
set s.alpha = ALPHA_START
set s.shadow = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),id,x,y,GetUnitFacing(caster))
call SetUnitColor(s.shadow,GetPlayerColor(GetOwningPlayer(caster)))
call SetUnitVertexColor(s.shadow,RED,GREEN,BLUE,s.alpha)
call SetUnitAnimationByIndex(s.shadow,ANIM_INDEX)
call UnitAddAbility(s.shadow,LOCUST_ID)
call SetUnitPathing(s.shadow,false)
if ALLOW_PAUSING then
call SetUnitTimeScale(s.shadow,0.00)
call PauseUnit(s.shadow,true)
else
call SetUnitTimeScale(s.shadow,timescale)
endif
call SetUnitX(s.shadow,x + OFFSET * Cos(angle))
call SetUnitY(s.shadow,y + OFFSET * Sin(angle))
if GetUnitFlyHeight(caster) > 1.00 then
call UnitAddAbility(s.shadow,CROW_ID)
call UnitRemoveAbility(s.shadow,CROW_ID)
call SetUnitFlyHeight(s.shadow,GetUnitFlyHeight(caster),0.00)
endif
if .Total == 0 then
call TimerStart(.Timer,INTERVAL,true,function thistype.update)
endif
set .S[.Total] = s
set .Total = .Total + 1
return s
endmethod
static method update takes nothing returns nothing
local thistype s = 0
local integer i = 0
loop
exitwhen i >= .Total
set s = .S<i>
if s.alpha <= 0 then
call s.destroy()
set .Total = .Total - 1
set .S<i> = .S[.Total]
set i = i - 1
else
call SetUnitVertexColor(s.shadow,RED,GREEN,BLUE,s.alpha)
call SetUnitAnimationByIndex(s.shadow,ANIM_INDEX)
set s.alpha = s.alpha - ALPHA_MINUS
endif
set i = i + 1
endloop
if .Total == 0 then
call PauseTimer(.Timer)
endif
endmethod
method onDestroy takes nothing returns nothing
call RemoveUnit(.shadow)
set .shadow = null
endmethod
static method onInit takes nothing returns nothing
set .Timer = CreateTimer()
endmethod
endstruct
//---------------------------------------------------------------------\\
private struct Movement
unit cast = null
widget t = null
group g = null
real cx = 0.00
real cy = 0.00
real tx = 0.00
real ty = 0.00
real move = 0.00
real dist = 0.00
real cos = 0.00
real sin = 0.00
integer ticks = 0
integer lvl = 0
integer id = 0
static thistype array M
static integer Total = 0
static timer Timer = null
static group Group = null
static boolexpr Filt = null
method resetPath takes real distance, real castx, real casty, real targx, real targy, widget targ, integer order returns nothing
local real angle = Atan2((targy - casty),(targx - castx))
if distance < .dist then
set .ticks = R2I(distance / .move)
endif
set .cos = Cos(angle)
set .sin = Sin(angle)
set .tx = targx
set .ty = targy
set .t = targ
set .id = order
call SetUnitFacing(.cast,angle * bj_RADTODEG)
endmethod
static method isDashing takes unit whichUnit returns thistype
local integer i = 0
loop
exitwhen i >= .Total
if .M<i>.cast == whichUnit then
return .M<i>
endif
set i = i + 1
endloop
return -1
endmethod
static method create takes unit caster, real distance, real castx, real casty, real targx, real targy, widget targ, integer order returns thistype
local thistype m = thistype.allocate()
local real angle = 0.00
set m.cast = caster
set m.cx = castx
set m.cy = casty
set m.tx = targx
set m.ty = targy
set m.lvl = GetUnitAbilityLevel(m.cast,COOLDOWN_ID)
set m.move = Movement_speed(m.lvl) * INTERVAL
set m.dist = distance
set angle = Atan2((m.ty - m.cy),(m.tx - m.cx))
set m.cos = Cos(angle)
set m.sin = Sin(angle)
set m.t = targ
set m.id = order
if m.dist > Max_dist(m.lvl) then
set m.dist = Max_dist(m.lvl)
endif
set m.ticks = R2I(m.dist / m.move)
call SetUnitPathing(m.cast,false)
call SetUnitFacing(m.cast,angle * bj_RADTODEG)
call SetUnitTimeScale(m.cast,Time_scale(m.lvl))
call SetUnitVertexColor(m.cast,RED,GREEN,BLUE,ALPHA)
if ALLOW_DAMAGE then
set m.g = CreateGroup()
endif
if PLAY_DASH_SFX then
call DestroyEffect(AddSpecialEffect(DASH_SFX,m.cx,m.cy))
endif
if .Total == 0 then
call TimerStart(.Timer,INTERVAL,true,function thistype.update)
endif
set .M[.Total] = m
set .Total = .Total + 1
return m
endmethod
static method update takes nothing returns nothing
local thistype m = 0
local integer i = 0
local integer j = 0
local real x = 0.00
local real y = 0.00
local unit u = null
loop
exitwhen i >= .Total
set m = .M<i>
set x = GetUnitX(m.cast)
set y = GetUnitY(m.cast)
if ALLOW_PATHING then
if Check_pathability(x + DISTANCE * m.cos,y + DISTANCE * m.sin) == false then
set j = 1
endif
endif
if m.ticks <= 0 or j == 1 or GetWidgetLife(m.cast) < 0.406 then
call m.destroy()
set .Total = .Total - 1
set .M<i> = .M[.Total]
set i = i - 1
set j = 0
else
set x = Safe_x(x + m.move * m.cos)
set y = Safe_y(y + m.move * m.sin)
call SetUnitX(m.cast,x)
call SetUnitY(m.cast,y)
call SetUnitAnimationByIndex(m.cast,ANIM_INDEX)
call IssueImmediateOrderById(m.cast,ORDER_STOP)
if ALLOW_SHADOWS then
call Shadow.create(m.cast,GetUnitTypeId(m.cast),Atan2((m.cy - y),(m.cx - x)),Time_scale(m.lvl),x,y)
endif
if ALLOW_DAMAGE then
call GroupEnumUnitsInRange(.Group,x,y,COLLISION,.Filt)
loop
set u = FirstOfGroup(.Group)
exitwhen u == null
call GroupRemoveUnit(.Group,u)
if not(IsUnitInGroup(u,m.g)) and IsUnitEnemy(u,GetOwningPlayer(m.cast)) == true then
call GroupAddUnit(m.g,u)
call UnitDamageTarget(m.cast,u,Damage_dealt(m.lvl),false,false,A_TYPE,D_TYPE,W_TYPE)
if PLAY_DAMAGE_SFX then
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_SFX,u,DAMAGE_POINT))
endif
endif
endloop
endif
set m.cx = x
set m.cy = y
set m.dist = m.dist - m.move
set m.ticks = m.ticks - 1
endif
set i = i + 1
endloop
if .Total == 0 then
call PauseTimer(.Timer)
endif
set u = null
endmethod
method onDestroy takes nothing returns nothing
call SetUnitPathing(.cast,true)
call SetUnitTimeScale(.cast,1.00)
call SetUnitAnimation(.cast,"stand")
call SetUnitVertexColor(.cast,255,255,255,255)
if .t != null then
call IssueTargetOrderById(.cast,.id,.t)
else
set .tx = .tx + 10.00 * .cos
set .ty = .ty + 10.00 * .sin
call IssuePointOrderById(.cast,.id,.tx,.ty)
endif
if PLAY_DASH_SFX then
call DestroyEffect(AddSpecialEffect(DASH_SFX,.cx,.cy))
endif
if ALLOW_DAMAGE then
call GroupClear(.g)
call DestroyGroup(.g)
endif
set .g = null
set .t = null
set .cast = null
endmethod
static method onInit takes nothing returns nothing
set .Timer = CreateTimer()
set .Group = CreateGroup()
set .Filt = Filter(function Enemy_filt)
endmethod
endstruct
//---------------------------------------------------------------------\\
private struct Cooldown
unit cast = null
real time = 0.00
static thistype array C
static integer Total = 0
static timer Timer = null
static method create takes nothing returns thistype
local thistype c = thistype.allocate()
local integer lvl = 0
set c.cast = GetTriggerUnit()
set lvl = GetUnitAbilityLevel(c.cast,READY_ID)
set c.time = Cool_down(lvl)
call UnitRemoveAbility(c.cast,READY_ID)
call UnitAddAbility(c.cast,COOLDOWN_ID)
call SetUnitAbilityLevel(c.cast,COOLDOWN_ID,lvl)
if .Total == 0 then
call TimerStart(.Timer,INTERVAL,true,function thistype.update)
endif
set .C[.Total] = c
set .Total = .Total + 1
return c
endmethod
static method update takes nothing returns nothing
local thistype c = 0
local integer i = 0
loop
exitwhen i >= .Total
set c = .C<i>
if c.time <= 0.00 or GetWidgetLife(c.cast) < 0.406 then
call c.destroy()
set .Total = .Total - 1
set .C<i> = .C[.Total]
set i = i - 1
else
set c.time = c.time - INTERVAL // Unfortunately needed to, incase caster died.
endif
set i = i + 1
endloop
if .Total == 0 then
call PauseTimer(.Timer)
endif
endmethod
method onDestroy takes nothing returns nothing
local string sfx = ""
local integer lvl = GetUnitAbilityLevel(.cast,COOLDOWN_ID)
call UnitRemoveAbility(.cast,COOLDOWN_ID)
call UnitAddAbility(.cast,READY_ID)
call SetUnitAbilityLevel(.cast,READY_ID,lvl)
if PLAY_COOLDOWN_SFX then
set sfx = COOLDOWN_SFX
if GetLocalPlayer() != GetOwningPlayer(.cast) then
set sfx = ""
endif
call DestroyEffect(AddSpecialEffectTarget(sfx,.cast,COOLDOWN_POINT))
endif
set .cast = null
endmethod
static method onInit takes nothing returns nothing
set .Timer = CreateTimer()
endmethod
endstruct
//---------------------------------------------------------------------\\
private function Move_actions takes nothing returns boolean
local unit cast = GetTriggerUnit()
local widget targ = GetOrderTarget()
local real castx = GetUnitX(cast)
local real casty = GetUnitY(cast)
local real targx = 0.00
local real targy = 0.00
local real dist = 0.00
local integer lvl = GetUnitAbilityLevel(cast,READY_ID)
local integer id = GetIssuedOrderId()
local Movement m = Movement.isDashing(cast)
if lvl > 0 or GetUnitAbilityLevel(cast,COOLDOWN_ID) > 0 then
if targ != null then
set targx = GetWidgetX(targ)
set targy = GetWidgetY(targ)
else
set targx = GetOrderPointX()
set targy = GetOrderPointY()
endif
set dist = SquareRoot((targx - castx) * (targx - castx) + (targy - casty) * (targy - casty))
if m == -1 and lvl > 0 then
if dist > Min_dist(lvl) then
if ALLOW_MANA_COST then
if GetUnitState(cast,UNIT_STATE_MANA) >= Mana_cost(lvl) then
call SetUnitState(cast,UNIT_STATE_MANA,GetUnitState(cast,UNIT_STATE_MANA) - Mana_cost(lvl))
call Cooldown.create()
call Movement.create(cast,dist,castx,casty,targx,targy,targ,id)
else
if ALLOW_ERROR_MSG then
call Sim_error(GetOwningPlayer(cast),ERROR_MSG)
endif
endif
else
call Cooldown.create()
call Movement.create(cast,dist,castx,casty,targx,targy,targ,id)
endif
endif
else
call m.resetPath(dist,castx,casty,targx,targy,targ,id)
endif
endif
set cast = null
set targ = null
return false
endfunction
//---------------------------------------------------------------------\\
private function Learn_actions takes nothing returns boolean
local unit cast = GetLearningUnit()
if GetUnitAbilityLevel(cast,LEARN_ID) == 1 then
call UnitAddAbility(cast,READY_ID)
else
if GetUnitAbilityLevel(cast,READY_ID) > 0 then
call IncUnitAbilityLevel(cast,READY_ID)
elseif GetUnitAbilityLevel(cast,COOLDOWN_ID) > 0 then
call IncUnitAbilityLevel(cast,COOLDOWN_ID)
endif
endif
set cast = null
return false
endfunction
//---------------------------------------------------------------------\\
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
set Game_maxX = GetRectMaxX(bj_mapInitialPlayableArea) - 64.00
set Game_minX = GetRectMinX(bj_mapInitialPlayableArea) + 64.00
set Game_maxY = GetRectMaxY(bj_mapInitialPlayableArea) - 64.00
set Game_minY = GetRectMinY(bj_mapInitialPlayableArea) + 64.00
if ALLOW_ORDER_MOVE then
call GT_RegisterPointOrderEvent(trig,ORDER_MOVE)
call GT_RegisterTargetOrderEvent(trig,ORDER_MOVE)
endif
if ALLOW_ORDER_SMART then
call GT_RegisterPointOrderEvent(trig,ORDER_SMART)
call GT_RegisterTargetOrderEvent(trig,ORDER_SMART)
endif
if ALLOW_ORDER_ATTACK then
call GT_RegisterPointOrderEvent(trig,ORDER_ATTACK)
call GT_RegisterTargetOrderEvent(trig,ORDER_ATTACK)
endif
call TriggerAddCondition(trig,Condition(function Move_actions))
call GT_AddLearnsAbilityAction(function Learn_actions,LEARN_ID)
if ALLOW_PRELOAD then
call DestroyEffect(AddSpecialEffect(DASH_SFX,0.00,0.00))
call DestroyEffect(AddSpecialEffect(COOLDOWN_SFX,0.00,0.00))
call DestroyEffect(AddSpecialEffect(DAMAGE_SFX,0.00,0.00))
endif
set Error_sound = CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
set Path_rect = Rect(0.00,0.00,128.00,128.00)
set Path_item = CreateItem(039;ciri039;,0.00,0.00)
call SetItemVisible(Path_item,false)
endfunction
endscope
</i></i></i></i></i></i></i></i>
Screenshots:
They are pretty average, so it is much better to download the map to see its full effect.
Path of Shadows: Ready
Path of Shadows: Moving
Path of Shadows: In cooldown
Path of Shadows: Moving
Path of Shadows: In cooldown
Updates:
Previous updates can be seen in the below spoiler.
- 23rd Dec - Initial release
- 23rd Dec - Updated test map. Changed map description to correctly display spell name.
- 26th Dec - Updated the code. Changed some really stupid mistakes by me. Made it a bit more efficient.
- 18th Jan - Updated the spell to allow people to choose to check for pathable terrain using a modified version of Vexorians CheckPathablility function.
- 14th Feb - Spell is now independent (Requires no systems). Changed a few things in the script, now works a bit better.
- 5th March - Various spell updates and changes, including:
- User can now specify whether or not the spell needs mana. And change the mana cost accordingly.
- User can now specify whether or not to damage enemy units. And change the damage accordingly, as well as the area of effect.
- User can specify a minimum distance, so that the spell will not trigger if you click within that distance.
- Fixed cooldown issue, so that is a user attempts to put 0.00 as the cooldown, it will be set to the lowest possible cooldown without causing any problems (distance / speed).
- Added a sim error if the hero does not have enough mana. User can choose to show it or not.
- Minor coding updates and changes. Made things easier to read. Standardized most, if not all, function names and parameters.
- 25th March - Few more spell and documentation updates, including:
- Spell now uses single global group for GroupEnum function.
- More script standardization.
- Documentation for configurables section has been removed from spell script and added into another trigger. Makes it easier to read, and takes up less space within the important trigger.
- Slight changes to how distance is calculated. Now works a bit better when targetting units (no "bounce" effect on most occasions).
- Changed from Vexorians old Check Pathability function to Anitarf's more up-to-date version for Check Pathability. Credits to both of them for the ideas.
- This is now version 7. Version 6 was an unreleased version due to the updates i found shortly after finishing version 6. Updates from version 6 (which are included in version 7) include:
- Removed DistanceXY and AngleXY functions. Now calculates distance and angle directly. I hope Lord Panda is happy .
- Few minor scripting adjustments.
- More comments throughout the script, to explain what happens.
- Path of Shadows now stops if the unit somehow dies during the spells effect.
- 27th March - Fixed leveling up bug found by CraZyFroG. Version 8 is now out.
- 11th April - Version 9 is out. Hopefully the last version of this spell. Update includes:
- Changed the safety on the minimum cooldown to make sure no bugs will occur.
- Found out that i wasn't pausing the second timer needed for the spell, it now pauses (can't believe no one saw this).
- Ported the spell to use Jesus4Lyf's GTrigger Event System. Therefore you now need that. If you really don't want to import GTrigger, i have left a stand alone version for you to use. However, the one that uses GTrigger is better, so use it .
- Few minor optimisations of things that were overlooked. Nothing life changing.
- 23rd April - Version 10 released. THIS VERSION REQUIRES GTRIGGER, there is no stand-alone version anymore. Couple more changes made after reading through the script carefully. Updates include:
- More minor optimizations.
- Made the dash work when targeting items and destructables as well as units, not just units.
- Got rid of the hack-job cooldown check to avoid double-frees. Replaced it with a small O(n) search (don't worry it is only fired once during the entire spell, at the start). This also means that the spell can now have a 0.00 second cooldown if you want.
- More standardisation of global and function names.
- Unit is now un-paused when the dash triggers, keeping its command card. I think this looks much better.
- Unit is re-ordered its last order before the dash, when the dash finishes. This is to fix a very rare bug that can cause the unit to begin walking back to the start position for a split second.
- Unit is constantly issued stop order to fix issues with unpausing the unit. This also helps with distance accuracy.
- Added options to allow "move", "smart" and "attack" orders separately. Now the user can specify which orders they want to use, which can be very handy. This is mainly due to how much easier GTrigger made the triggering of this spell.
- 29th June - Version 11 released. Updates include:
- Minor optimisations to script, including removal of few unnecessary variables.
- Added functionally to the spell. If a unit is currently dashing and a new order is given, the unit will re-direct its dash to the new target point/unit. The overall distance of the dash can not increase from the original distance if this is done.
- Added a shadow trail to the spell. Thought it needed some eye-candy to get people interested.
- Removed one of the effects available for change by end users.
- Updated the struct stack to use the 0 index.
- Renamed various variables and functions to make a bit more sense.