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

Spirits v1.7



About

Importing

Configurable

Trigger (code)

Screenshots

Kinematics

Changelog

Credits (special thanks)



  • MUI: true
  • Language: JASS
  • Editor: vanilla Editor
  • Import difficulty: low
  • Spell type: active
  • Target: selfcast
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
202808-albums7226-picture83682.jpg

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

202808-albums7226-picture81852.jpg
202808-albums7226-picture81938.jpg
202808-albums7226-picture81939.jpg
202808-albums7226-picture81988.jpg
202808-albums7226-picture81940.jpg
202808-albums7226-picture81941.jpg
202808-albums7226-picture81942.jpg


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


202808-albums7226-picture83936.jpg

  • 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.


Keywords:
Spirits, Spirit, wisp, Dota, Io, oscillation, rotation, angular velocity, spin, orbit, bajaist
Contents

[Spell] Spirits (Map)

Reviews
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.

Moderator

M

Moderator

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.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
quick review!

CODE:
  • // udg_Spirits_name type MUI Description
    MUI? MUI and arrayed variable is two super different things..
  • udg_Spirits_distanceAngleI[indexMui]
    variable name must be simple and unique. uselessly long variable name will makes the code more un-readable
  • JASS:
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
    don't use BJ in this case

SUGGESTION:
  • JASS:
    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 :grin:
 
Level 5
Joined
Feb 17, 2011
Messages
95
Thanks for the quick review.

CODE:
// udg_Spirits_name type MUI Description
MUI? MUI and arrayed variable is two super different things..
Mui (*) is just there to show which variables are used for dynamic indexing.
udg_Spirits_distanceAngleI[indexMui]
variable name must be simple and unique. uselessly long variable name will makes the code more un-readable

obviously you have put a lot effort to make the code that neat, but long variable name makes it hardly-unreadable.
I thought about it, after adding Spirit_ it did seem too big.
JASS:
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
don't use BJ in this case
I'll look into this.
JASS:
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
Yes, i thought about adding filtration for flying, mechanical, building...
you could merge them all into one trigger, couldn't you?
You mean all four triggers into one?
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 :grin:
I guess you're right, i'll keep additional info here, and make it straight to the point for importing.
Thanks again
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
It is okay to use trigger register bj's because not All bj's are bad
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.
just write it like real array, integer array, etc..

You mean all four triggers into one?
yes.

I thought about it, after adding Spirit_ it did seem too big.
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 :grin:
    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
  • IsUnitInRangeXY(triggerUnit, GetUnitX(spirit), GetUnitY(spirit), udg_Spirits_damageUnit[0] + 1) == true => IsUnitInRangeXY(triggerUnit, GetUnitX(spirit), GetUnitY(spirit), udg_Spirits_damageUnit[0] + 1)
  • JASS:
                        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 :eekani:

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
 
Level 5
Joined
Feb 17, 2011
Messages
95


  • 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


if udg_Spirits_creationStyle == true then => if udg_Spirits_creationStyle then
Fixed.
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
After merging all trigger, i've changed it from periodic event to timer.
OBJECT:
  • Learn tooltip broken, incorrectly linebreaked
  • I guess you can only use 3 dummy spells, not 6 :eekani:
Didn't find broken tooltip. It's just for importing, 3 spells that do not interrupt orders, and 3 that do.
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
It would be best to just test it. When true spirits are created with SpiritsCreate function, and when it's false with SpiritsCreateDota function.
SUGGESTION:
Make total of summoned wisp configurable
I think that it would be pretty hard, maybe after all fixes.
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
Fixed.
Didn't have time to go through all you wrote, but I'll fix it asap. Thanks again:thumbs_up:

Edit:
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
you could check if the hero is dead or alive inside
function MoveSpirit takes nothing returns nothing
Fixed. It turned out to be much simpler to check in move function.
 
Last edited:
Level 29
Joined
Oct 24, 2012
Messages
6,543
Change 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
        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.
 
Level 5
Joined
Feb 17, 2011
Messages
95

  • v1.4
    • Inlined sinBJ and cosBJ
    • Optimized some IF statements
    • Optimized SpiritsUnitTypeFilter function

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
Fixed.
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.
Already did that, but didn't upload. :)
 
Level 5
Joined
Feb 17, 2011
Messages
95

  • v1.5
    • Minor optimizations
    • Angle added on every shift, for Spirits rotation, is now configurable


I suggest really to use ONE and ONLY ONE function for the spell effect event and one for the periodic effect...
There is no periodic time event, a timer controls periodic time. If that's what you meant.

And btw a period of 0.0312500 is better :)
It is configurable. :)
 
In function SpiritsManage:

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?)

Have a good day. :csmile:
 
Level 5
Joined
Feb 17, 2011
Messages
95
In function SpiritsManage:

GetSpellAbilityId() should be stored into a variable.
Fixed.

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.
For now i'll use exitwhen true method, but before updating, and when i get some time, i'll see to hastable method.

In function SpiritsEndSpell you should store UnitX/Y into variables.
Fixed.

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?)
I'm glad you liked it. :) Well it kinda makes it more fun to use with Push Out and Pull In, but sure i'll make it optional with next update. :)

Have a good day. :csmile:
Thanks, you too. :)
 
Last edited:
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.
But what if you would move them up?

Spirit[index] dies --> set Spirit[index] = Spirit[lastIndex]. And then null the spirit[lastIndex]

So the 5th spirit might get the 2nd spirit for example, but you would only loop with alive spirits.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
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.

Looks good to me.
 
Top