Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
The spell is based on Io's ability Spirits from Dota. Note that this spell is not an exact replica, Spirits from Dota was just used as a reference point.
Description from Dota
Extended Description
Hero creates n (number of Spirits is configurable) Spirits. Spirits spin around the hero and damage any enemy unit they come in contact with. Upon creation, hero is given two more abilities Push Out and Pull In, to control Spirits oscillation distance, distance between hero and orbit of Spirits. If Pull In or Push Out is used twice in a row orbiting distance will become current oscillation distance. There are two ways that Spirits can be created, customized and Dota styled, and they are always created one by one. If a Spirit touches an enemy hero it explodes dealing AOE damage.
Difference between customized and Dota styled creation of Spirits
For visualisation look under kinematics tab.
Customized style
Push Out and Pull In abilities are added at the end of the creation
Spirits are created at position of caster, one by one, and are moving towards the minimal oscillation (orbiting) distance. When one reach minimal oscilation distance, another Spirits is created, and so on.
function SpiritsCreate takes integer curIndex returns nothing
Dota style
Push Out and Pull In abilities are added at the beginning of the creation
Spirits are created, one by one, at the minimal oscillation (orbiting) distance.
function SpiritsCreateDota takes integer curIndex returns nothing
Push Out and Pull In icons
Icons used in provided map are created by CRAZYRUSSIAN and you can find them at these links:
Import difficulty: low
These settings are optional, user should set them to their liking. You can look at the provided map for all settings.
External requirements: none
Trigger:
Simple copy and paste. Remember to allow pasting of variables: File/Preferences/Automatically create unknown variables while pasting trigger data (English version)
If not already, set initial value for IS_maxIndex to -1.
Units:
A hero or heroes with Spirits ability.
A Spirit unit:
Scaling Value: 0.5
Detection radius of Spirit (dmgUnit[0]) should be relative to scaling value
Model File: Wisp
Sight Radius (Day): 0
Sight Radius (Night): 0
Abilities:
This spell requires 3 dummy abilities: Spirits, Push Out and Pull In. If you don't want them to interrupt orders use Mana Shield, Wind Walk and Berserk, clear all unneeded values. For all 3:
Duration: 0.01
Or 3 spells based off of channel.
Stats:
Number of Spirits
Duration time
Periodic time for Spirits movement
Spirit ability ID
Push Out ability ID
Pull In ability ID
Spirit unit ID
Locusts ability ID
Damage:
Number of spell levels
Damage to units
Damage to heroes
Area of effect
Detection radius of Spirits
Attack type
Damage type
Damage to unit type for flying, mechanical, structure
Visuals:
Creation style
Minimal oscillation distance
Maximal oscillation distance
"Speed" of oscillation, distance traveled from/to caster, while pushing out/pulling in
Starting angle at which Spirits are created
Clockwise/anticlockwise rotation
Queued Spirits animation
Effect played upon Spirits explosion
Angle added on every shift, for Spirits rotation
One trigger control the spell: Spirits.
JASS:
// ===================================================================================================================================
// Global variables
// IS - Io's Spirits
// DI - dynamic indexing
//
//
// udg_IS_name type DI Description
//
// abiUnitId[0] integer Spirits ability
// abiUnitId[1] integer Push Out ability
// abiUnitId[2] integer Pull In ability
// abiUnitId[3] integer Spirit unit
// abiUnitId[4] integer Locust ability
// angCreate[] real * Angle of Spirits while created
// angMove[] real * Angle of Spirits while moving
// caster[] unit * Caster
// creationStyle boolean Customized or Dota styled creation of Spirits
// distAng[0] real Ammount added to angle on each iteration
// distAng[1] real Speed of oscillation
// distAng[2] real Ammount added to oscillation while Spirits are created
// distAng[3] real Starting angle for creation of Spirits
// distAngC[] real * Counts number of iterations
// distCreate[] real * Distance of Spirits while created
// distMove[] real * Distance of Spirits while moving
// distOscMax real Max oscillation
// distOscMin real Min oscillation
// ddistOscPullIn[] boolean * Turns on/off Pull In ability
// distOscPushOut[] boolean * Turns on/off Push Out ability
// dmgAttackType attack type Attack type
// dmgGroup unit group Unit group for AOE damage
// dmgHero[] real Damage to heroes per level
// dmgHero[0] real Area of effect
// dmgType damage type Damage type
// dmgUnit[] real Damage to units per level
// dmgUnit[0] real Detection radius of Spirit
// dmgUnitType boolean Damage to unit type
// durTime real Duration of ability
// durTimer[] real * Keeps track of duration of ability
// maxIndex integer * Max index
// nSpirits integer Number of Spirits
// nSpiritsCreated[] integer * Number of created Spirits
// nSpiritsCurrent[] integer * Current number of Spirits
// ownPlayer player Owning player of current spirit, used for unit group filter
// perTime real Periodic time for SpiritsMove function
// pertimer timer Periodic timer for SpiritsMove function
// sfxAnim string Queued Spirits animation
// sfxModel string Effect played upon Spirits explosion
// SpiritsCollisionTrigger trigger * Trigger created on each cast for detectiong Spirits collision
// spiritHash hashtable * Spirit
//
// ===================================================================================================================================
// Function list
//
// function SpiritsInitialization takes nothing returns nothing
// This function is used to set/configure spell values
// ----------------------------------------------------------
// function SpiritsUnitTypeFilter takes unit filt returns boolean
// This function is used to filter unit types for damage
// filt; unit; filter unit
// ----------------------------------------------------------
// function SpiritsIsAliveFilter takes unit filt returns boolean
// This function is used to filter alive units
// filt; unit; filter unit
// ----------------------------------------------------------
// function SpiritsUnitGroupFilter takes nothing returns boolean
// This function is filter for damage group
// ----------------------------------------------------------
// function SpiritsDynamicIndex takes integer curIndex returns nothing
// This function is used to manage indexes at the end of spell
// and to destroy SpiritsCollisionTrigger
// curIndex; integer; current index
// ----------------------------------------------------------
// function SpiritsEndSpell takes integer curIndex returns nothing
// This function is used to end spell if caster dies
// or if spell is used before duration expire
// or if duration expire
// curIndex; integer; current index
// ----------------------------------------------------------
// function SpiritsCreate takes integer curIndex returns nothing
// This function is used for customized creation of Spirits
// Spirits are created at position of caster
// and moved, over time, one by one to minimal oscillation distance
// curIndex; integer; current index
// ----------------------------------------------------------
//function SpiritsCreateDota takes integer curIndex returns nothing
// This function is used to create Spirits as in Dota
// Spirits are created one by one at minimal oscillation distance
// curIndex; integer; current index
// ----------------------------------------------------------
//function SpiritsMove takes nothing returns nothing
// This function is used to move Spirits,
// check which creation style is used,
// manage oscillation distance
// and check if caster is dead or duration expired
// ----------------------------------------------------------
// function SpiritsDamage takes integer curIndex, unit triggeredSpirit, unit triggerUnit returns nothing
// This function is used to deal damage
// triggeredSpirit; unit; triggered spirit
// triggerUnit; unit; triggering unit
// curIndex; integer; current index
// ----------------------------------------------------------
// function SpiritsCollision takes nothing returns boolean
// This function is used as a condition function for SpiritsCollisionTrigger
// SpiritsCollisionTrigger is dynamically created trigger used to detect units in range of spirits
// ----------------------------------------------------------
// function SpiritsManage takes nothing returns nothing
// This function is used to detect which ability is being used,
// set/reset spell values
//
// ===================================================================================================================================
Spirits
JASS:
//=====================================================================================================================================
// About
// Spirits v1.7
// Created by bajaist
// This spell: http://www.hiveworkshop.com/forums/spells-569/spirits-v1-7-a-250320/
//
// For more resources visit:
// www.thehelper.net
// www.hiveworkshop.com
// www.wc3c.net
//
//
//
// MUI: true
// Language: JASS
// Editor: vanilla Editor
// Import difficulty: low
// Spell type: no target
//
// Io summons 5 ancient Spirits over the course of 3 seconds. The Spirits dance around Io in a circle to protect him.
// If an enemy hero moves close enough to touch a Spirit, the Spirit releases its life energy in a burst,
// dealing damage to all enemies in a 300 area of effect. Non hero units only take minor damage upon touching a spirit
// and do not cause them to explode.
// ===================================================================================================================================
// Importing
// These settings are optional, user should set them to their liking. You can look at the provided map for all settings.
// --------------------------------------------------------
// External requirements: none
// --------------------------------------------------------
// Triggers:
// Simple copy and paste. Remember to allow pasting of variables:
// File/Preferences/Automatically create unknown variables while pasting trigger data (English version)
// If not already, set initial value for IS_maxIndex to -1.
// --------------------------------------------------------
// Units:
// A hero or heroes with Spirits ability.
// A Spirit unit:
// Scaling Value: 0.5 damageUnit[0] should be relative to scaling value
// Model File: Wisp
// Sight Radius (Day): 0
// Sight Radius (Night): 0
// --------------------------------------------------------
// Abilities:
// This spell requires 3 dummy abilities: Spirits, Push Out and Pull In.
// If you don't want them to interrupt orders use Mana Shield, Wind Walk and Berserk, clear all unneeded values. For all 3:
// Duration: 0.01
// Or 3 spells based off of channel.
//
// ===================================================================================================================================
function SpiritsInitialization takes nothing returns nothing
// This function is used to set/configure spell values
local integer l = 1 // l; integer; counter, helper variable
local integer rotation // rotation; integer; sets clockwise/anticlockwise rotation for Spirits
local integer nLevels // levels; integer; number of spell levels
local real ang // ang; real; angle added on every shift, for Spirits rotation
// Note: changing any value too much may cause malfunctions
// ======================== Spell Configuration ==================================================================================
// ============== Stats ==============
set udg_IS_nSpirits = 5 // Set number of Spirits
// Note: duration countdown timer always starts when Push Out and Pull In abilities are added
set udg_IS_durTime = 19 // Set duration time
set udg_IS_perTime = 0.025 // Periodic time for Spirits rotation
// Note: Use ctrl + D in Object Editor to see raw data
set udg_IS_abiUnitID[0] = 'A007' // Spirits ability
set udg_IS_abiUnitID[1] = 'A006' // Push Out ability
set udg_IS_abiUnitID[2] = 'A004' // Pull In ability
set udg_IS_abiUnitID[3] = 'e000' // Spirit unit
set udg_IS_abiUnitID[4] = 'Aloc' // Locust ability
// ============== Damage ==============
set nLevels = 4 // Number of spell levels
// Damage calculator per level
loop
exitwhen l > nLevels
// ------------------------------
set udg_IS_dmgUnit[l] = l * 6 + 2 // damage to units
set udg_IS_dmgHero[l] = l * 25 // damage to heroes
// ------------------------------
set l = l + 1 // Note: for heroes and for end of spell, damage is AOE
endloop
set udg_IS_dmgHero[0] = 300 // Area of effect
set udg_IS_dmgUnit[0] = 40 // Detection radius of Spirit
set udg_IS_dmgAttackType = ATTACK_TYPE_HERO // Attack type
set udg_IS_dmgType = DAMAGE_TYPE_MAGIC // Damage type
set udg_IS_dmgUnitType[1] = false // Damage to flying
set udg_IS_dmgUnitType[2] = true // Damage to mechanical
set udg_IS_dmgUnitType[3] = true // Damage to structure
// ============== Visuals ==============
// There are 2, dota style - Spirits are created at minimal oscillation distance
// and customized - Spirits are created at position of caster and are sent one by one to minimal oscillation distance
set udg_IS_creationStyle = true // Set to false for Dota styled creation of Spirits
set udg_IS_distOscMin = 100 // Minimal oscillation distance
set udg_IS_distOscMax = 875 // Maximal oscillation distance
set udg_IS_distAng[1] = 8 // Set "speed" of oscillation, distance traveled from/to caster, while pushing out/pulling in
set udg_IS_distAng[3] = 0 // Starting angle at which Spirits are created
set rotation = 1 // Set to -1 for clockwise rotation
set udg_IS_sfxAnim = "Stand Work" // Queued Spirits animation
set udg_IS_sfxModel = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl" // Effect played upon Spirits explosion
set ang = 3 // Angle added on every shift, for Spirits rotation
// ======================== End of Spell Configuration ===========================================================================
set udg_IS_maxIndex = 0 // Setting maxIndex from -1 to 0
set udg_IS_distAng[0] = rotation * ang // Anticlockwise/clockwise rotation, angle while rotated is increased by 3
set udg_IS_distAng[2] = udg_IS_distOscMin / (360 / udg_IS_nSpirits / ang) // Distance traveled on each iteration for SpiritsCreate function (customized creation)
if udg_IS_creationStyle then // If creation style is customized
set udg_IS_durTime = udg_IS_durTime + udg_IS_perTime * (360 / ang) // Extending duration time for customized creation of Spirits
endif
set udg_IS_spiritHash = InitHashtable() // spiritHash; hashtable; stores spirit units
set udg_IS_dmgGroup = CreateGroup() // damageGroup; unit group; unit froup for AOE damage
set udg_IS_perTimer = CreateTimer() // perTimer; timer; periodic timer for Spirits rotation
endfunction
// ----------------------------------------------------------
function SpiritsUnitTypeFilter takes unit filt returns boolean
// This function is used to filter unit types for damage
// filt; unit; filter unit
if IsUnitType(filt, UNIT_TYPE_FLYING) then // Damage to flying
return udg_IS_dmgUnitType[1]
elseif IsUnitType(filt, UNIT_TYPE_MECHANICAL) then // Damage to mechanical
return udg_IS_dmgUnitType[2]
elseif IsUnitType(filt, UNIT_TYPE_STRUCTURE) then // Damage to structure
return udg_IS_dmgUnitType[3]
endif
return true
endfunction
// ----------------------------------------------------------
function SpiritsIsAliveFilter takes unit filt returns boolean
// This function is used to filter alive units
// filt; unit; filter unit
return GetWidgetLife(filt) > 0.405 and not IsUnitType(filt, UNIT_TYPE_DEAD) and GetUnitTypeId(filt) != 0
endfunction
// ----------------------------------------------------------
function SpiritsUnitGroupFilter takes nothing returns boolean
// This function is filter for damage group
return SpiritsUnitTypeFilter(GetFilterUnit()) and SpiritsIsAliveFilter(GetFilterUnit()) and IsUnitEnemy(GetFilterUnit(), udg_IS_ownPlayer)
endfunction
// ----------------------------------------------------------
function SpiritsDynamicIndex takes integer curIndex returns nothing
// This function is used to manage indexes at the end of spell
// and to destroy SpiritsCollisionTrigger
// curIndex; integer; current index
// Clearing both events and conditions of SpiritsCollisionTrigger, flushing data inside trigger
call FlushChildHashtable(udg_IS_spiritHash, GetHandleId(udg_IS_SpiritsCollisionTrigger[curIndex]))
call DestroyTrigger(udg_IS_SpiritsCollisionTrigger[curIndex])
set udg_IS_SpiritsCollisionTrigger[curIndex] = null
call FlushChildHashtable(udg_IS_spiritHash, GetHandleId(udg_IS_caster[curIndex])) // Clearing child hashtable
call UnitRemoveAbility(udg_IS_caster[curIndex], udg_IS_abiUnitID[1]) // Removes Push Out and
call UnitRemoveAbility(udg_IS_caster[curIndex], udg_IS_abiUnitID[2]) // Pull In abilities
// =================== Max index to current index =======================================
set udg_IS_durTimer[curIndex] = udg_IS_durTimer[udg_IS_maxIndex] // Duration timer
set udg_IS_caster[curIndex] = udg_IS_caster[udg_IS_maxIndex] // Caster
set udg_IS_angMove[curIndex] = udg_IS_angMove[udg_IS_maxIndex] // Angle of Spirits while moving
set udg_IS_distMove[curIndex] = udg_IS_distMove[udg_IS_maxIndex] // Distance of Spirits while moving
set udg_IS_angCreate[curIndex] = udg_IS_angCreate[udg_IS_maxIndex] // Angle of Spirits while created
set udg_IS_distCreate[curIndex] = udg_IS_distCreate[udg_IS_maxIndex] // Distance of Spirits while created
set udg_IS_distAngC[curIndex] = udg_IS_distAngC[udg_IS_maxIndex] // Counts number of iterations
set udg_IS_distOscPushOut[curIndex] = udg_IS_distOscPushOut[udg_IS_maxIndex] // Push Out
set udg_IS_distOscPullIn[curIndex] = udg_IS_distOscPullIn[udg_IS_maxIndex] // Pull In
set udg_IS_nSpiritsCreated[curIndex] = udg_IS_nSpiritsCreated[udg_IS_maxIndex] // Number of created Spirits
set udg_IS_nSpiritsCurrent[curIndex] = udg_IS_nSpiritsCurrent[udg_IS_maxIndex] // Current number of Spirits
set udg_IS_maxIndex = udg_IS_maxIndex - 1 // Lowering max index
if udg_IS_maxIndex == 0 then // If no one is using the spell
call PauseTimer(udg_IS_perTimer) // pause periodic timer
endif
endfunction
// ----------------------------------------------------------
function SpiritsEndSpell takes integer curIndex returns nothing
// This function is used to end spell if caster dies
// or if spell is used before duration expire
// or if duration expire
// curIndex; integer; current index
local integer c = 1 // c; integer; counter, helper variable
local unit fog // fog; unit; first of group; used for damageGroup unit group
local unit spirit // spirit; unit; current Spirit, helper variable
// =========== Reamaining Spirits burst, at the end of spell, releasing damage ================
loop
exitwhen c > udg_IS_nSpirits
set spirit = LoadUnitHandle(udg_IS_spiritHash, GetHandleId(udg_IS_caster[curIndex]), c)
// If Spirit is alive
if SpiritsIsAliveFilter(spirit) then
// Picking units in group for aoe damage around each Spirit
set udg_IS_ownPlayer = GetOwningPlayer(spirit)
call GroupEnumUnitsInRange(udg_IS_dmgGroup, GetUnitX(spirit), GetUnitY(spirit), udg_IS_dmgHero[0], Filter(function SpiritsUnitGroupFilter))
loop
set fog = FirstOfGroup(udg_IS_dmgGroup)
exitwhen fog == null
// Damaging unit in group
call UnitDamageTarget(udg_IS_caster[curIndex], fog, udg_IS_dmgUnit[GetUnitAbilityLevel(udg_IS_caster[curIndex], udg_IS_abiUnitID[0])], false, false, udg_IS_dmgAttackType, udg_IS_dmgType, WEAPON_TYPE_WHOKNOWS)
call GroupRemoveUnit(udg_IS_dmgGroup, fog)
endloop
call DestroyEffect(AddSpecialEffect(udg_IS_sfxModel, GetUnitX(spirit), GetUnitY(spirit))) // Adding special effect at position of spirit
call RemoveUnit(spirit) // Spirit explode at the end of spell
endif
set c = c + 1
endloop
call SpiritsDynamicIndex(curIndex) // Managing indexes
// fog is already nulled
set spirit = null
endfunction
// ----------------------------------------------------------
function SpiritsCreate takes integer curIndex returns nothing
// This function is used for customized creation of Spirits
// Spirits are created at position of caster
// and moved, over time, one by one to minimal oscillation distance
// curIndex; integer; current index
local unit spirit // spirit; unit; created Spirit, helper variable
if udg_IS_distCreate[curIndex] == 0 then // If angle is reset, Spirit reached minimal oscillation distance, and another is created
// Creating Spirit for caster, at position of caster
set spirit = CreateUnit(GetOwningPlayer(udg_IS_caster[curIndex]), udg_IS_abiUnitID[3], GetUnitX(udg_IS_caster[curIndex]), GetUnitY(udg_IS_caster[curIndex]), 0)
call UnitAddAbility(spirit, udg_IS_abiUnitID[4]) // add Locust to Spirit
call SaveUnitHandle(udg_IS_spiritHash, GetHandleId(udg_IS_caster[curIndex]), udg_IS_nSpiritsCreated[curIndex], spirit)
call QueueUnitAnimation(spirit, udg_IS_sfxAnim)
else
set spirit = LoadUnitHandle(udg_IS_spiritHash, GetHandleId(udg_IS_caster[curIndex]), udg_IS_nSpiritsCreated[curIndex])
endif
// =================== Moving Spirit towards minimal oscillation distance ======================================================================
call SetUnitX(spirit, GetUnitX(udg_IS_caster[curIndex]) + udg_IS_distCreate[curIndex] * Cos(udg_IS_angCreate[curIndex] * bj_DEGTORAD))
call SetUnitY(spirit, GetUnitY(udg_IS_caster[curIndex]) + udg_IS_distCreate[curIndex] * Sin(udg_IS_angCreate[curIndex] * bj_DEGTORAD))
set udg_IS_distCreate[curIndex] = udg_IS_distCreate[curIndex] + udg_IS_distAng[2] // Always increases distance
set udg_IS_angCreate[curIndex] = udg_IS_angCreate[curIndex] + udg_IS_distAng[0] // Always increases angle
// =================== Checking if all Spirits are created or next Spirit should be ============================================================
// If distance of Spirits while created is same as minimimal oscillation distance
if udg_IS_distCreate[curIndex] == udg_IS_distOscMin then
// Adding "unit comes within event", for each Spirit, to SpiritsCollisionTrigger
call TriggerRegisterUnitInRange(udg_IS_SpiritsCollisionTrigger[curIndex], spirit, udg_IS_dmgUnit[0], null)
set udg_IS_nSpiritsCreated[curIndex] = udg_IS_nSpiritsCreated[curIndex] + 1 // Counts current number of Spirits, from 1 - 5 since creation
set udg_IS_angCreate[curIndex] = udg_IS_distAng[3] // Resets angle of Spirits while created
set udg_IS_distCreate[curIndex] = 0 // Resets distance of Spirits while created
// Note: All 5 Spirits are meant to use the same path from caster to minimal oscillation distance
if udg_IS_nSpiritsCreated[curIndex] == udg_IS_nSpirits + 1 then // If all Spirits are created:
call UnitAddAbility(udg_IS_caster[curIndex], udg_IS_abiUnitID[1]) // add Push Out to Caster
call UnitAddAbility(udg_IS_caster[curIndex], udg_IS_abiUnitID[2]) // add Pull In to Caster
endif
endif
set spirit = null
endfunction
// ----------------------------------------------------------
function SpiritsCreateDota takes integer curIndex returns nothing
// This function is used to create Spirits as in Dota
// Spirits are created one by one at minimal oscillation distance
// curIndex; integer; current index
local unit spirit // spirit; unit; created Spirit, helper variable
// Creating Spirit for caster, at position of caster
// Spirit is immediately moved to minimal oscialtion distance by MoveSpirit function
set spirit = CreateUnit(GetOwningPlayer(udg_IS_caster[curIndex]), udg_IS_abiUnitID[3], GetUnitX(udg_IS_caster[curIndex]), GetUnitY(udg_IS_caster[curIndex]), 0)
call UnitAddAbility(spirit, udg_IS_abiUnitID[4]) // add Locust to Spirit
call SaveUnitHandle(udg_IS_spiritHash, GetHandleId(udg_IS_caster[curIndex]), udg_IS_nSpiritsCreated[curIndex], spirit)
// Adding "unit comes within event", for each Spirit, to SpiritsCollisionTrigger
call TriggerRegisterUnitInRange(udg_IS_SpiritsCollisionTrigger[curIndex], spirit, udg_IS_dmgUnit[0], null)
call QueueUnitAnimation(spirit, udg_IS_sfxAnim)
if udg_IS_nSpiritsCreated[curIndex] == 1 then // If all Spirits are created:
call UnitAddAbility(udg_IS_caster[curIndex], udg_IS_abiUnitID[1]) // add Push Out to Caster
call UnitAddAbility(udg_IS_caster[curIndex], udg_IS_abiUnitID[2]) // add Pull In to Caster
endif
set udg_IS_nSpiritsCreated[curIndex] = udg_IS_nSpiritsCreated[curIndex] + 1 // Increasing number of created spirits
set spirit = null
endfunction
// ----------------------------------------------------------
function SpiritsMove takes nothing returns nothing
// This function is used to move Spirits,
// check which creation style is used,
// manage oscillation distance
// and check if caster is dead or duration expired
local integer c // c; integer; counter, helper variable
local integer curIndex = 1 // curIndex; integer; current index
local unit spirit // spirit; unit; current Spirit, helper variable
local unit fog // fog; unit; first of group; used for damageGroup unit group
local real x // x; real; new x coordinate, helper variable
local real y // y; real; new y coordiante, helper variable
local real a = 360 / udg_IS_nSpirits // a; real; angle
// ===================
loop
exitwhen curIndex > udg_IS_maxIndex
// =================== Checking if duration time expired =====================================================
if udg_IS_durTimer[curIndex] >= udg_IS_durTime or not SpiritsIsAliveFilter(udg_IS_caster[curIndex]) then
call SpiritsEndSpell(curIndex)
else
// =================== Checking if all Spirits are created ===================================
if udg_IS_nSpiritsCreated[curIndex] < udg_IS_nSpirits + 1 then
if udg_IS_creationStyle then
call SpiritsCreate(curIndex) // Create another Spirit
elseif udg_IS_distAngC[udg_IS_maxIndex] >= (360 / udg_IS_nSpirits) then // Reseting counter
set udg_IS_distAngC[udg_IS_maxIndex] = 0
call SpiritsCreateDota(curIndex) // Create another Spirit
else
set udg_IS_distAngC[udg_IS_maxIndex] = udg_IS_distAngC[udg_IS_maxIndex] + RAbsBJ(udg_IS_distAng[0]) // Adding "ang degrees" to counter
endif
endif
// ================ Setting up oscialtion distance ===========================================
if udg_IS_distMove[curIndex] < udg_IS_distOscMin + 1 then // If oscillation distance is minimal:
set udg_IS_distOscPullIn[curIndex] = false // turn off Pull in
set udg_IS_distMove[curIndex] = udg_IS_distOscMin // set oscillation distance to minimal oscillation distance
elseif udg_IS_distMove[curIndex] > udg_IS_distOscMax - 1 then // If oscillation distance is maximal:
set udg_IS_distOscPushOut[curIndex] = false // turn off Push Out
set udg_IS_distMove[curIndex] = udg_IS_distOscMax // set oscillation distance to maximal oscillation distance
elseif udg_IS_distOscPushOut[curIndex] then // If Push Out is on:
set udg_IS_distMove[curIndex] = udg_IS_distMove[curIndex] + udg_IS_distAng[1] // Increase oscillation distance
elseif udg_IS_distOscPullIn[curIndex] then // If Pull In is on:
set udg_IS_distMove[curIndex] = udg_IS_distMove[curIndex] - udg_IS_distAng[1] // decrease oscillation distance
endif
set udg_IS_angMove[curIndex] = udg_IS_angMove[curIndex] + udg_IS_distAng[0] // Always increase angle
set udg_IS_distAngC[curIndex] = udg_IS_distAngC[curIndex] + 1 // Iteration counter
// ================ Moving Spirits ===========================================================
set c = 1
loop
// current number of Spirits; transfering from create to move function
exitwhen c == udg_IS_nSpiritsCreated[curIndex]
// Loading spirits
set spirit = LoadUnitHandle(udg_IS_spiritHash, GetHandleId(udg_IS_caster[curIndex]), c)
if udg_IS_distAng[0] > 0 then
// anticlockwise, (1 - c) * a sets it to the last created Spirit
set x = GetUnitX(udg_IS_caster[curIndex]) + udg_IS_distMove[curIndex] * Cos((udg_IS_angMove[curIndex] + (1 - c) * a) * bj_DEGTORAD)
set y = GetUnitY(udg_IS_caster[curIndex]) + udg_IS_distMove[curIndex] * Sin((udg_IS_angMove[curIndex] + (1 - c) * a) * bj_DEGTORAD)
else
// cloclwise, (c - 1) * a sets it to the last created Spirit
set x = GetUnitX(udg_IS_caster[curIndex]) + udg_IS_distMove[curIndex] * Cos((udg_IS_angMove[curIndex] + (c - 1) * a) * bj_DEGTORAD)
set y = GetUnitY(udg_IS_caster[curIndex]) + udg_IS_distMove[curIndex] * Sin((udg_IS_angMove[curIndex] + (c - 1) * a) * bj_DEGTORAD)
endif
call SetUnitX(spirit, x) // Moving Spirit to new coordinates
call SetUnitY(spirit, y)
set c = c + 1
endloop
set udg_IS_durTimer[curIndex] = udg_IS_durTimer[curIndex] + udg_IS_perTime // Increasing duration time
set curIndex = curIndex + 1 // Increasing index
endif
endloop
// fog is already nulled
set spirit = null
endfunction
// ----------------------------------------------------------
function SpiritsDamage takes integer curIndex, unit triggeredSpirit, unit triggerUnit returns nothing
// This function is used to deal damage
// triggeredSpirit; unit; triggered spirit
// triggerUnit; unit; triggering unit
// curIndex; integer; current index
local unit fog // fog; unit; first of group; used for damageGroup unit group
// ================= Dealing damage ====================================================================================
// If Spirit is alive
if SpiritsIsAliveFilter(triggeredSpirit) then
// If triggerting unit is alive, and an enemy
if SpiritsIsAliveFilter(triggerUnit) and SpiritsUnitTypeFilter(triggerUnit) and IsUnitEnemy(triggerUnit, GetOwningPlayer(udg_IS_caster[curIndex])) then
if IsUnitType(triggerUnit, UNIT_TYPE_HERO) then // If enemy unit is a hero
set udg_IS_nSpiritsCurrent[curIndex] = udg_IS_nSpiritsCurrent[curIndex] - 1 // Lowering number of Spirits
// Picking units in group for aoe damage around enemy hero
set udg_IS_ownPlayer = GetOwningPlayer(triggeredSpirit)
call GroupEnumUnitsInRange(udg_IS_dmgGroup, GetUnitX(triggerUnit), GetUnitY(triggerUnit), udg_IS_dmgHero[0], Filter(function SpiritsUnitGroupFilter))
loop
set fog = FirstOfGroup(udg_IS_dmgGroup)
exitwhen fog == null
// Damaging unit in group
call UnitDamageTarget(udg_IS_caster[curIndex], fog, udg_IS_dmgUnit[GetUnitAbilityLevel(udg_IS_caster[curIndex], udg_IS_abiUnitID[0])], false, false, udg_IS_dmgAttackType, udg_IS_dmgType, WEAPON_TYPE_WHOKNOWS)
call GroupRemoveUnit(udg_IS_dmgGroup, fog)
endloop
call DestroyEffect(AddSpecialEffect(udg_IS_sfxModel, GetUnitX(triggeredSpirit), GetUnitY(triggeredSpirit))) // Adding special effect at position of spirit
call RemoveUnit(triggeredSpirit) // Spirit explode at the end of spell
else
// If enemy unit is not a hero
call UnitDamageTarget(udg_IS_caster[curIndex], triggerUnit, udg_IS_dmgUnit[GetUnitAbilityLevel(udg_IS_caster[curIndex], udg_IS_abiUnitID[0])], false, false, udg_IS_dmgAttackType, udg_IS_dmgType, WEAPON_TYPE_WHOKNOWS)
endif
endif
endif
if udg_IS_nSpiritsCurrent[curIndex] == 0 then // If there are no Spirits left
call SpiritsDynamicIndex(curIndex) // Lowering index
endif
// fog is already nulled
set triggeredSpirit = null
set triggerUnit = null
endfunction
// ----------------------------------------------------------
function SpiritsCollision takes nothing returns nothing
// This function is used as a condition function for SpiritsCollisionTrigger
// SpiritsCollision trigger is dynamically created trigger used to detect units in range of spirits
local integer c = 1 // c; integer; counter, helper variable
local unit spirit // spirit; unit; current Spirit, helper variable
local unit triggerUnit = GetTriggerUnit() // triggerUnit; unit; helper variable
local integer curIndex = LoadInteger(udg_IS_spiritHash, GetHandleId(GetTriggeringTrigger()), 0) // curIndex; integer; current index
// ================= Checking which Spirit enemy unit came in contact with =============================================
loop
exitwhen c > udg_IS_nSpirits
// Loading spirits
set spirit = LoadUnitHandle(udg_IS_spiritHash, GetHandleId(udg_IS_caster[curIndex]), c)
// Checking if triggering unit is in range of each Spirit
if IsUnitInRangeXY(triggerUnit, GetUnitX(spirit), GetUnitY(spirit), udg_IS_dmgUnit[0] + 1) then
call SpiritsDamage(curIndex, spirit, triggerUnit)
endif
set c = c + 1
endloop
endfunction
// ----------------------------------------------------------
function SpiritsManage takes nothing returns nothing
// This function is used to detect which ability is being used,
// set/reset spell values
local integer curIndex = 1 // curIndex; integer; counter, helper variable
local unit triggerUnit = GetTriggerUnit() // triggerUnit; unit; helper variable
// =========== Initializating spell values, 0.01s event =======================================
if udg_IS_maxIndex == -1 then // maxIndex is used as a helper variable
call SpiritsInitialization() // setting spell values
// =========== Ability being used is Spirits ==================================================
elseif GetSpellAbilityId() == udg_IS_abiUnitID[0] then
loop
exitwhen curIndex > udg_IS_maxIndex
if udg_IS_caster[curIndex] == triggerUnit then
call SpiritsEndSpell(curIndex) // Ending spell if activated before duration expire
endif
set curIndex = curIndex + 1
endloop
set udg_IS_maxIndex = udg_IS_maxIndex + 1 // Incereasing max index
// ========== Reseting spell values =======================================================
set udg_IS_caster[udg_IS_maxIndex] = triggerUnit // caster
set udg_IS_durTimer[udg_IS_maxIndex] = 0 // duration time
set udg_IS_angMove[udg_IS_maxIndex] = udg_IS_distAng[3] // angle of Spirits while moving
set udg_IS_distMove[udg_IS_maxIndex] = udg_IS_distOscMin // distance of Spirits while moving
set udg_IS_angCreate[udg_IS_maxIndex] = udg_IS_distAng[3] // angle of Spirits while created
set udg_IS_distCreate[udg_IS_maxIndex] = 0 // distance of Spirits while created
set udg_IS_distAngC[udg_IS_maxIndex] = (360 / udg_IS_nSpirits) // counts number of iterations
set udg_IS_distOscPushOut[udg_IS_maxIndex] = false // Push Out
set udg_IS_distOscPullIn[udg_IS_maxIndex] = false // Pull In
set udg_IS_nSpiritsCreated[udg_IS_maxIndex] = 1 // Number of created Spirits
set udg_IS_nSpiritsCurrent[udg_IS_maxIndex] = udg_IS_nSpirits // Current number of Spirits
// ========== Creating trigger for detecting Spirits collision ============================
// Trigger is destroyed at the end of spell
set udg_IS_SpiritsCollisionTrigger[udg_IS_maxIndex] = CreateTrigger() // Creating trigger
call SaveInteger(udg_IS_spiritHash, GetHandleId(udg_IS_SpiritsCollisionTrigger[udg_IS_maxIndex]), 0, udg_IS_maxIndex) // Saving curIndex
call TriggerAddCondition(udg_IS_SpiritsCollisionTrigger[udg_IS_maxIndex], Condition(function SpiritsCollision)) // Adding condtions to trigger
if udg_IS_maxIndex == 1 then // If only one hero is using the spell and is first to start
call TimerStart(udg_IS_perTimer, udg_IS_perTime, true, function SpiritsMove) // turn on periodic timer for SpiritsMove function
endif
// =========== Ability being used is Push Out =================================================
elseif GetSpellAbilityId() == udg_IS_abiUnitID[1] then
loop // Looping through units
exitwhen curIndex > udg_IS_maxIndex
if udg_IS_caster[curIndex] == triggerUnit then
if udg_IS_distMove[curIndex] == udg_IS_distOscMax then // If distance of oscillation is maximal:
set udg_IS_distOscPushOut[curIndex] = false // turn off Push Out
elseif udg_IS_distOscPushOut[curIndex] then // Sets Spirits oscillation distance at current if
set udg_IS_distOscPushOut[curIndex] = false // casted while pushing out
else
set udg_IS_distOscPullIn[curIndex] = false // Turn off Pull In
set udg_IS_distOscPushOut[curIndex] = true // Turn on Push Out
set udg_IS_distMove[curIndex] = udg_IS_distMove[curIndex] + udg_IS_distAng[1] // Incerease oscillation distance
endif
endif
set curIndex = curIndex + 1
endloop
// ========= Ability being used is Pull In ====================================================
elseif GetSpellAbilityId() == udg_IS_abiUnitID[2] then
loop // Looping through units
exitwhen curIndex > udg_IS_maxIndex
if udg_IS_caster[curIndex] == triggerUnit then
if udg_IS_distMove[curIndex] == udg_IS_distOscMin then // If distance of oscillation is minimal:
set udg_IS_distOscPullIn[curIndex] = false // turn off Pull In
elseif udg_IS_distOscPullIn[curIndex] then // Sets Spirits oscillation distance at current if
set udg_IS_distOscPullIn[curIndex] = false // casted while pulling in
else
set udg_IS_distOscPushOut[curIndex] = false // Turn off Push Out
set udg_IS_distOscPullIn[curIndex] = true // Turn on Pull In
set udg_IS_distMove[curIndex] = udg_IS_distMove[curIndex] - udg_IS_distAng[1] // Decrease oscillation distance
endif
endif
set curIndex = curIndex + 1
endloop
endif
set triggerUnit = null
endfunction
// ======================= Init Trigger Spirits Initialization =======================================================================
function InitTrig_Spirits takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterTimerEvent(t, 0.01, false) // This event is used only for spell configuration
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function SpiritsManage))
set t = null
endfunction
All pictures from description can be found in this album.
Spirits up close
Learn tooltip (3 spirits)
Customized creation
Dota styled creation
Push Out ability (5 spirits)
Pull In ability (7 spirits)
Two casters
Speed
v = w * d
v is speed of Spirit
w is angular velocity
d is distance between position of caster and Spirit, oscillation distance
Angular velocity is constant, distance from caster oscillates, meaning Spirits speed is greater as further as they go from caster.
Position formula
X = Xc + dx Y = Yc + dy
X, Y is position of spirits
Xc, Yc is position of caster
dx, dy is diference between position of Spirit and caster
dx = d * cos(a) dy = d * sin(a)
d is distance between position of caster and Spirit, oscillation distance
a is relative angle between position at which Spirits are created and first Spirit, a is always increasing (anticlockwise) or always decreasing (clockwise), periodically, to simulate rotation
There are n Spirits, so now formula is:
b = 360 / n dx = d * cos(a + c * b) dy = d * sin(a + c * b)
n is number of Spirits
b is relative angle between any two adjacent Spirits
c is counter for Spirits, 1, 2, 3, ..., n, where first is point of origin
Spirits are created every "b degrees". But their actual position of creation is always starting a degrees. To set this formula to last created Spirit in reference to the first, while rotating clockwise:
dx = d * cos(a + (c - 1) * b) dy = d * sin(a + (c - 1) * b)
And anticlockwise:
dx = d * cos(a + (1 - c) * b) = d * cos(a - (c - 1) * b) dy = d * sin(a + (1 - c) * b) = d * sin(a - (c - 1) * b) Final position formula, clockwise:
X = Xc + d * cos(a + (c - 1) * b) Y = Yc + d * sin(a + (c - 1) * b)
And anticlockwise:
X = Xc + d * cos(a - (c - 1) * b) Y = Yc + d * sin(a - (c - 1) * b)
While Pushing Out, Pulling In and for customized creation, beside a, d is also changing value periodically:
Customized creation - d is increasing from 0 to minimal oscillation distance
Push Out - d is increasing
Pull In - d is decreasing
Customized creation
Dota styled creation
Spirits
Caster
n is number of Spirits
a is relative angle between position at which Spirits are created and first Spirit, a is always increasing (anticlockwise) or always decreasing (clockwise), periodically, to simulate rotation
b is relative angle between any two adjacent Spirits
d is distance between position of Spirit and caster
e is starting angle at which Spirits are created
(Xc, Yc) is position of caster
(x, y) is relative coordinate system
(X, Y) is map coordinate system
v1.0
Published
v1.1
Shortened variable names
Shortened description/info
Merged two TriggerRegisterAnyUnitEventBJ into one
v1.2
Merged all triggers into one
Damage to unit type for flying, mechanical, structure is now configurable
Spirits death effect at the end of spell is now configurable
Added filter functions for alive, unit type and unit group
Removed death event, now hero death is checked in SpiritsMove function
v1.3
Removed some unneeded variables and created few non-array variables
Number of Spirits is now configurable
v1.4
Inlined sinBJ and cosBJ
Optimized some IF statements
Optimized SpiritsUnitTypeFilter function
v1.5
Minor optimizations
Angle added on every shift, for Spirits rotation, is now configurable
v1.6
Minor optimizations
v1.7
Spirits collision detection is now done in separate trigger, dynamically created/destroyed at the beginning/end of spell
Spell created by bajaist. Credits are not necessary, but would be appreciated. If you want to modify the spell for your custom map, feel free to do so. Please do not distribute or claim this spell to be your own.
12:10, 14th Jun 2014
BPower:
Code looks okay to me, except some minor flaws.
The concept is nothing new, but still good.
I appreciate that you outlined your code with comments.
Nice presentation.
Approved.
12:10, 14th Jun 2014
BPower:
Code looks okay to me, except some minor flaws.
The concept is nothing new, but still good.
I appreciate that you outlined your code with comments.
Nice presentation.
Approved.
if GetWidgetLife(fog) > 0.405 and not IsUnitType(fog, UNIT_TYPE_DEAD) and IsUnitEnemy(fog, GetOwningPlayer(udg_Spirits_caster[indexMui])) and GetUnitTypeId(fog) != 0 then
you could add extra filtration which user could configure
you could merge them all into one trigger, couldn't you?
obviously you have put a lot effort to make the code that neat, but long variable name makes it hardly-unreadable.
Nice presentation, although the spell is not so original, but I suggest you just to give simple description and straight-to-the-point importing instruction, none want to read those blocks of narative texts just to import a spell
if GetWidgetLife(fog) > 0.405 and not IsUnitType(fog, UNIT_TYPE_DEAD) and IsUnitEnemy(fog, GetOwningPlayer(udg_Spirits_caster[indexMui])) and GetUnitTypeId(fog) != 0 then
you could add extra filtration which user could configure
Nice presentation, although the spell is not so original, but I suggest you just to give simple description and straight-to-the-point importing instruction, none want to read those blocks of narative texts just to import a spell
it's okay if you only call it once, in this case he used it twice which inside each of that BJ contains one loop, so it's the same as he is uselessly doing loop twice..
Mui (*) is just there to show which variables are used for dynamic indexing.
the prefix is okay IMO, this is just example udg_Spirits_abilityUnitID => udg_Spirits_spellID
Okay, here is full review
CODE:
JASS:
set udg_Spirits_abilityUnitID[0] = 'A007' // Spirits ability
set udg_Spirits_abilityUnitID[1] = 'A006' // Push Out ability
set udg_Spirits_abilityUnitID[2] = 'A004' // Pull In ability
set udg_Spirits_abilityUnitID[3] = 'e000' // Spirit unit
set udg_Spirits_abilityUnitID[4] = 'Aloc' // Locust ability
set udg_Spirits_damageHero[0] = 300 // Area of effect
set udg_Spirits_damageUnit[0] = 40 // Detection radius of Spirit, Spirits Damage trigger event
set udg_Spirits_effect[0] = "Stand Work" // Queued Spirits animation
set udg_Spirits_effect[1] = "Units\\NightElf\\Wisp\\WispExplode.mdl" // Effect played upon Spirits explosion
set udg_Spirits_effect[2] = "chest" // Attachment point for effect[1]
set udg_Spirits_distanceAngle[1] = 8 // Set "speed" of oscillation, distance traveled from/to caster, while pushing out/pulling in
set udg_Spirits_distanceAngle[3] = 0 // Starting angle at which Spirits are created
set udg_Spirits_oscillationDistRange[2] = 875 // Max oscillation
set udg_Spirits_oscillationDistRange[1] = 100 // Min oscillation
don't use array if you only use few same type variable, since array takes much more memory than non-array
JASS:
set levels = 4 // Number of spell levels
loop
exitwhen c > levels
set udg_Spirits_damageUnit[c] = c * 6 + 2 // dmgUnit; real; damage to units
set udg_Spirits_damageHero[c] = c * 25 // dmgHero; real; damage to heroes
set c = c + 1 // Note: for heroes and for end of spell, damage is AOE
endloop
add different function to configure the damage which takes integer level the current way is less user friendly, then you can store the damage into variable on cast event by calling that function
if udg_Spirits_creationStyle == true then => if udg_Spirits_creationStyle then
call TriggerRegisterTimerEventPeriodic(gg_trg_Spirits_Create_Move, udg_Spirits_periodicTime)
just use one global timer variable, then native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing
This is a big problem
JASS:
function InitTrig_Spirits_Initialization takes nothing returns nothing
endfunction
you should move all initializations into that function, so everything will be per-loaded or whatever, at loading screen, and you don't need to create that local trigger
if GetWidgetLife(fog) > 0.405 and not IsUnitType(fog, UNIT_TYPE_DEAD)
IMO => if not IsUnitType(fog, UNIT_TYPE_DEAD)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
you could check if the hero is dead or alive inside function MoveSpirit takes nothing returns nothing
JASS:
// ================= Dealing damage ====================================================================================
// If Spirit is alive
if GetWidgetLife(triggeredSpirit) > 0.405 and not IsUnitType(triggeredSpirit, UNIT_TYPE_DEAD) and GetUnitTypeId(triggeredSpirit) != 0 then
// If triggerting unit is alive, and an enemy
if GetWidgetLife(triggerUnit) > 0.405 and not IsUnitType(triggerUnit, UNIT_TYPE_DEAD) and IsUnitEnemy(triggerUnit, GetOwningPlayer(udg_Spirits_caster[indexMui])) and GetUnitTypeId(triggerUnit) != 0 then
// If enemy unit is a hero
if IsUnitType(triggerUnit, UNIT_TYPE_HERO) == true then
call KillUnit(triggeredSpirit) // Spirit explode upon touching enemy hero
set udg_Spirits_nSpiritsCurrent[indexMui] = udg_Spirits_nSpiritsCurrent[indexMui] - 1 // Lowering number of Spirits
call DestroyEffect(AddSpecialEffectTarget(udg_Spirits_effect[1], triggerUnit, udg_Spirits_effect[2])) // Adding special effect on enemy hero
// Picking units in group for aoe damage around enemy hero
call GroupEnumUnitsInRange(udg_Spirits_damageGroup, GetUnitX(triggerUnit), GetUnitY(triggerUnit), udg_Spirits_damageHero[0], null)
loop
set fog = FirstOfGroup(udg_Spirits_damageGroup)
exitwhen fog == null
if GetWidgetLife(fog) > 0.405 and not IsUnitType(fog, UNIT_TYPE_DEAD) and IsUnitEnemy(fog, GetOwningPlayer(udg_Spirits_caster[indexMui])) and GetUnitTypeId(fog) != 0 then
// Damaging unit in group
call UnitDamageTarget(udg_Spirits_caster[indexMui], fog, udg_Spirits_damageUnit[GetUnitAbilityLevel(udg_Spirits_caster[indexMui], udg_Spirits_abilityUnitID[0])], false, false, udg_Spirits_attackType, udg_Spirits_damageType, WEAPON_TYPE_WHOKNOWS)
endif
call GroupRemoveUnit(udg_Spirits_damageGroup, fog)
endloop
else
// If enemy unit is not a hero
call UnitDamageTarget(udg_Spirits_caster[indexMui], triggerUnit, udg_Spirits_damageUnit[GetUnitAbilityLevel(udg_Spirits_caster[indexMui], udg_Spirits_abilityUnitID[0])], false, false, udg_Spirits_attackType, udg_Spirits_damageType, WEAPON_TYPE_WHOKNOWS)
endif
endif
endif
move it inside the loop, delete triggeredWisp. the current way can only support one triggered wisp => bug
JASS:
// If Spirit is alive
if GetWidgetLife(triggeredSpirit) > 0.405 and not IsUnitType(triggeredSpirit, UNIT_TYPE_DEAD) and GetUnitTypeId(triggeredSpirit) != 0 then
use arrayed boolean which become false when wisp is dead then if boolean[loopMui] then
set x = GetUnitX(udg_Spirits_caster[indexMui]) + udg_Spirits_distanceMove[indexMui] * CosBJ(udg_Spirits_angleMove[indexMui] + (1 - c) * 72)
set y = GetUnitY(udg_Spirits_caster[indexMui]) + udg_Spirits_distanceMove[indexMui] * SinBJ(udg_Spirits_angleMove[indexMui] + (1 - c) * 72)
why use BJ?
OBJECT:
Learn tooltip broken, incorrectly linebreaked
I guess you can only use 3 dummy spells, not 6
MISCS:
JASS:
// creationStyle; boolean; There are 2, dota style - Spirits are created at minimal oscillation distance
// and customized - Spirits are created at position of caster and are sent one by one to minimal oscillation distance
set udg_Spirits_creationStyle = true // Set to false for Dota styled creation of Spirits
please, explain what happens if it's true and if it's false
SUGGESTION:
Make total of summoned wisp configurable
call KillUnit(spirit) // Spirit explode at the end of spell
Just remove it, then create special effect with specific z height so that user could configure the explode sfx
okay, after I consider it, this is not a full-review, I will review more after you make the code more readable and understandable, but this obviously still needs several fixes. Good luck
call TriggerRegisterTimerEventPeriodic(gg_trg_Spirits_Create_Move, udg_Spirits_periodicTime)
just use one global timer variable, then native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing
// creationStyle; boolean; There are 2, dota style - Spirits are created at minimal oscillation distance
// and customized - Spirits are created at position of caster and are sent one by one to minimal oscillation distance
set udg_Spirits_creationStyle = true // Set to false for Dota styled creation of Spirits
please, explain what happens if it's true and if it's false
call KillUnit(spirit) // Spirit explode at the end of spell
Just remove it, then create special effect with specific z height so that user could configure the explode sfx
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
you could check if the hero is dead or alive inside function MoveSpirit takes nothing returns nothing
function SpiritsUnitTypeFilter takes unit filt returns boolean
// This function is used to filter unit types for damage
// filt; unit; filter unit
if IsUnitType(filt, UNIT_TYPE_FLYING) then
if udg_IS_dmgUnitType[1] then
return true
endif
elseif IsUnitType(filt, UNIT_TYPE_MECHANICAL) then
if udg_IS_dmgUnitType[2] then
return true
endif
elseif IsUnitType(filt, UNIT_TYPE_STRUCTURE) then
if udg_IS_dmgUnitType[3] then
return true
endif
else
return true
endif
return false
endfunction
to this.
JASS:
function SpiritsUnitTypeFilter takes unit filt returns boolean
// This function is used to filter unit types for damage
// filt; unit; filter unit
if IsUnitType(filt, UNIT_TYPE_FLYING) then
return udg_IS_dmgUnitType[1]
elseif IsUnitType(filt, UNIT_TYPE_MECHANICAL) then
return udg_IS_dmgUnitType[2]
elseif IsUnitType(filt, UNIT_TYPE_STRUCTURE) then
return udg_IS_dmgUnitType[3]
endif
return true
endfunction
You should inline CosBJ and SinBJ
change this
JASS:
if udg_IS_creationStyle == true then
call SpiritsCreate(curIndex)
to this
JASS:
if udg_IS_creationStyle then
call SpiritsCreate(curIndex)
You should never use == true or == false in an if condition check.
Use If condition then for a true comparison or If not condition then for a false comparison.
function SpiritsUnitTypeFilter takes unit filt returns boolean
// This function is used to filter unit types for damage
// filt; unit; filter unit
if IsUnitType(filt, UNIT_TYPE_FLYING) then
return udg_IS_dmgUnitType[1]
elseif IsUnitType(filt, UNIT_TYPE_MECHANICAL) then
return udg_IS_dmgUnitType[2]
elseif IsUnitType(filt, UNIT_TYPE_STRUCTURE) then
return udg_IS_dmgUnitType[3]
endif
return true
endfunction
if udg_IS_creationStyle == true then
call SpiritsCreate(curIndex)
to this
JASS:
if udg_IS_creationStyle then
call SpiritsCreate(curIndex)
You should never use == true or == false in an if condition check.
Use If condition then for a true comparison or If not condition then for a false comparison.
GetSpellAbilityId() should be stored into a variable.
Then you loop each time to maxIndex. That's not needed. Once you found matching unit[index] you can take usage of exitwhen true to prevent useless checks.
Or even better for whole system would be to use hashtable for it. You easily cwould store the index of unit into hashtable. (you can use HandleId of unit as parent key)
This would remove the loop searching method for the right index.
In function SpiritsEndSpell you should store UnitX/Y into variables.
In your creation part you logically increase this variable: udg_IS_nSpiritsCreated[curIndex] = udg_IS_nSpiritsCreated[curIndex] + 1
But in your GetsDamage function you only decrease this: set udg_IS_nSpiritsCurrent[curIndex] = udg_IS_nSpiritsCurrent[curIndex] - 1
even you use this condtion for loop: exitwhen c == udg_IS_nSpiritsCreated[curIndex] (so here are also possible redudant checks, once a unit gets damage)
You maybe can try to combine these two variables, and only use one. You would need to deindex the spirity more dynamic, but you could prevent unneeded bigger looping with it.
You only would loop from 1 - NumberOfExistingSpirits[Index].
I experienced sometimes a little bug, or idk. When I used the "push-out" ability, most of times they moved out to the max offset. But sometimes they only moved out like the half of way. (stepwise)
I like you commented your spell code! It's always not cool to read a big code without any comments.
For me, it even was a bit too much of comments... for example you always explain your local variables. I think that's not really needed.
But I guess better a bit too much comments than no comments.
I would work a bit at the spell's tooltip. Maybe try to keep it a bit shorter and cleaner, so it would be a bit more easy to read.
Yo, that's it. Overall I really like the spell and I can think of usefulnes in it. I don't know the spell from dota, but it this push/pull a must have?
It's a nice idea, but it bothers me a bit that it takes 2 more spell-slots for the hero. (would it be possible to make it as optional easily?)
Then you loop each time to maxIndex. That's not needed. Once you found matching unit[index] you can take usage of exitwhen true to prevent useless checks.
Or even better for whole system would be to use hashtable for it. You easily cwould store the index of unit into hashtable. (you can use HandleId of unit as parent key)
This would remove the loop searching method for the right index.
In your creation part you logically increase this variable: udg_IS_nSpiritsCreated[curIndex] = udg_IS_nSpiritsCreated[curIndex] + 1
But in your GetsDamage function you only decrease this: set udg_IS_nSpiritsCurrent[curIndex] = udg_IS_nSpiritsCurrent[curIndex] - 1
even you use this condtion for loop: exitwhen c == udg_IS_nSpiritsCreated[curIndex] (so here are also possible redudant checks, once a unit gets damage)
You maybe can try to combine these two variables, and only use one. You would need to deindex the spirity more dynamic, but you could prevent unneeded bigger looping with it.
You only would loop from 1 - NumberOfExistingSpirits[Index].
I've already thought about that, but not enough to find a good solution. Whole problem is because (simplified):
JASS:
loop
exitwhen c == udg_IS_nSpiritsCreated[curIndex]
set x = GetUnitX(udg_IS_caster[curIndex]) + udg_IS_distMove[curIndex] * Cos((udg_IS_angMove[curIndex] + (1 - c) * a) * bj_DEGTORAD)
endloop
c goes from 1 to udg_nSpiritsCreated. If ,for example, max number of Spirits is 5, 4 are created and 2nd exploded, then if i lower udg_nSpiritsCreated or use udg_nSpiritsCurrent, their rotation would mess up. After fifth is created, c would go from 1 to 4, and fifth wouldn't rotate.
I experienced sometimes a little bug, or idk. When I used the "push-out" ability, most of times they moved out to the max offset. But sometimes they only moved out like the half of way. (stepwise)
Hm, i guess you used Push Out twice in a row. If you use it at min offset, and again before they reach max, Spirits will stop pushing out. Offset can be set anywhere between min and max.
Edit: Same goes for Pull In.
I like you commented your spell code! It's always not cool to read a big code without any comments.
For me, it even was a bit too much of comments... for example you always explain your local variables. I think that's not really needed.
But I guess better a bit too much comments than no comments.
Hehe, you're right, when i look at it now it kinda looks silly. The thing is, i didn't start this as a spell, but as i way to learn and familiarize with jass, so when i began, i've commented on every single thing for my own sake, and just went with it later on.
I would work a bit at the spell's tooltip. Maybe try to keep it a bit shorter and cleaner, so it would be a bit more easy to read.
There are +3 dummy spells based of channel in object editor, i'll make a simplified tooltip for them, so people can choose whichever they prefer.
Yo, that's it. Overall I really like the spell and I can think of usefulnes in it. I don't know the spell from dota, but it this push/pull a must have?
It's a nice idea, but it bothers me a bit that it takes 2 more spell-slots for the hero. (would it be possible to make it as optional easily?)
c goes from 1 to udg_nSpiritsCreated . If ,for example, max number of Spirits is 5, 4 are created and 2nd exploded, then if i lower udg_nSpiritsCreated or use udg_nSpiritsCurrent , their rotation would mess up. After fifth is created, c would go from 1 to 4, and fifth wouldn't rotate.
That would get rid of unnecessary loops, but fifth would jump from his to position of second, which might look ugly. For now, i'll put IsAlive check to cut down those unnecessary loops, before i test your way.
GetWidgetLife(filt) > 0.405 and not IsUnitType(filt, UNIT_TYPE_DEAD) and GetUnitTypeId(filt) != 0 --> You don't have to do the life check, it is covered within the two other conditions.
Passed in variables don't have to be nulled.
JASS:
// fog is already nulled
set triggeredSpirit = null
set triggerUnit = null
You could null the caster unit variable aswell once the spell is finished.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.