Vengeance Storm v1.2

Description

Code

How to Import

Reference

Changelog


Description
The Blademaster swings with wrathful force. After a brief pause, a savage storm of slashes erupts, ripping through everything in its path.

Effect
Level 1
- deals 35 + (15% of Agility) damage
Level 2 - deals 45 + (15% of Agility) damage
Level 3 - deals 55 + (15% of Agility) damage
Level 4 - deals 65 + (15% of Agility) damage

Others
Area of Effect: 300
Number of Slashes: 6
Stun Duration Per Hit: 0.1 seconds

JASS:
scope VengeanceStorm
    //---------------------------------------------------------------------
    // Vengeance Storm v1.2
    // Author: Blightsower
    //
    // Description:
    //     The Blademaster swings with wrathful force. After a brief pause, 
    // a savage storm of slashes erupts, ripping through everything in its 
    // path.
    //
    // Requirements:
    //  * Enable JassHelper
    //
    // How to enable JassHelper
    //  * In the Trigger Editor window, look for the menu labeled JassHelper
    //    and click Enable JassHelper
    //---------------------------------------------------------------------
    // How to Import:
    // 1. Copy and paste the objects from this map to your map namely: 
    //    * Vengeance Storm
    //    * Vengeance Storm - (Ministun)
    //    * Dummy - (Vengeance Storm)
    // 2. Copy and paste the Vengeance Storm Script into your map
    // 3. Configure the spell so that everything points to the correct 
    //    variables. Additional information about the variables are 
    //    provided for clarity.
    //---------------------------------------------------------------------

    //-------------------------------------------------------------
    // CONFIGURATION
    //-------------------------------------------------------------
    globals
        //-------------------------------------------------------------
        // Hint:
        // Press ctrl + d while the Object Editor is open
        // to reveal the object ids. 
        //-------------------------------------------------------------

        //-------------------------------------------------------------
        // HARVESTER_TYPE - the id of the unittype of the dummy used for 
        // checking trees. It should point to the id of Dummy - (Vengeance Storm)
        //-------------------------------------------------------------
        private constant integer HARVESTER_TYPE = 'h000'

        //-------------------------------------------------------------
        // HARVESTER_ORDER - the order used by the dummy for checking trees. 
        // Original value is 852018 (Peasant Harvest). 
        //-------------------------------------------------------------
        private constant integer HARVESTER_ORDER = 852018

        //-------------------------------------------------------------
        // CASTER_TYPE - the id of the unittype of the dummy that will be 
        // casting the ministun. It should point to the id of Dummy - (Vengeance Storm)
        //-------------------------------------------------------------
        private constant integer CASTER_TYPE = 'h000'

        //-------------------------------------------------------------
        // CASTER_ORDER - the order used by the dummy when casting the ministun. 
        // Original value is 852095 (Stormbolt). 
        //-------------------------------------------------------------
        private constant integer CASTER_ORDER = 852095

        //-------------------------------------------------------------
        // CASTER_ABILITY - the ability id of the ability used by the 
        // dummy to cast the ministun. It should point to the id of 
        // Vengeance Storm - (Ministun)
        //-------------------------------------------------------------
        private constant integer CASTER_ABILITY = 'A001'

        //-------------------------------------------------------------
        // TIMER_SPEED - the interval used by the timers of the spell
        // Original value is .03
        //------------------------------------------------------------- 
        private constant real TIMER_SPEED = .03
    endglobals

    module VengeanceStormConfiguration
        //-------------------------------------------------------------
        // VENGEANCE_STORM_ID - the ability id of the ability used by the 
        // main ability of the spell. It should point to the id of 
        // Vengeance Storm
        //-------------------------------------------------------------
        readonly static integer VENGEANCE_STORM_ID = 'A000'

        //-------------------------------------------------------------
        // SLASH_RANGE_MODEL - the model used by the area of effect indicator
        // of the spell. Its original size is rather small, so you will most
        // likely need to configure the scale of it.
        //-------------------------------------------------------------
        readonly static string SLASH_RANGE_MODEL = "UI\\Feedback\\SelectionCircleEnemy\\SelectionCircleEnemy.mdx"

        //-------------------------------------------------------------
        // WEAPON_MODEL - the model attached to the caster while casting the spell it
        // will disappear as soon as the circle appears
        //-------------------------------------------------------------
        readonly static string WEAPON_MODEL = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"      
        readonly static string WEAPON_ATTACHMENT = "weapon"

        //-------------------------------------------------------------
        // SLASH_RANGE_HEIGHT - value is used to raise or lower the range indicator
        //-------------------------------------------------------------
        readonly static real SLASH_RANGE_HEIGHT = -90.

        //-------------------------------------------------------------
        // getSlashRangeSize - returns the size of the 
        // area of effect indicator. Use the parameter "level" to derive
        // your desired value if the size of the area of effect is not the
        // same per level. Original value is 8
        //-------------------------------------------------------------
        static method getSlashRangeSize takes integer level returns real
            return 8.
        endmethod

        //-------------------------------------------------------------
        // getAoe - returns the size of the area of effect. If you are using 
        // a base spell with area of effect indicator, such as silence. It should
        // match this value for consistency. Use the parameter "level" to derive your 
        // desired value. Original value is 300.
        //-------------------------------------------------------------
        static method getAoe takes integer level returns real 
            return 300.
        endmethod

        //-------------------------------------------------------------
        // getInitialSlashDelay - returns the delay before the spell
        // starts its effect. Original value is .6
        //-------------------------------------------------------------
        static method getInitialSlashDelay takes integer level returns real
            return .6
        endmethod

        //-------------------------------------------------------------
        // getStormCount - returns the number of times the spell will deal
        // damage to every enemy within the target area. Original value is 6
        //-------------------------------------------------------------
        static method getStormCount takes integer level returns integer
            return 6
        endmethod

        //-------------------------------------------------------------
        // getSlashesPerStorm - returns the number of slashes that will appear
        // in the target area. Purely visual. Original value is 8
        //-------------------------------------------------------------
        static method getSlashesPerStorm takes integer level returns integer
            return 8
        endmethod

        //-------------------------------------------------------------
        // getDelayPerStorm - returns the delay before the subsequent slashes
        // are spawned after the initial slash. 
        //-------------------------------------------------------------
        static method getDelayPerStorm takes integer level returns real
            return .15
        endmethod

        //-------------------------------------------------------------
        // getAbsoluteDamage - returns the base damage of the spell.
        // Original value is 25. + (10*level)
        //-------------------------------------------------------------
        static method getAbsoluteDamage takes integer level returns real
            return 25. + (10*level)
        endmethod

        //-------------------------------------------------------------
        // getStrengthDamage - returns the bonus damage of the spell
        // derived from the Strength of the caster. 1.0 is 100% of the
        // caster's Strength. Original value is 0. + (0*level)
        //-------------------------------------------------------------
        static method getStrengthDamage takes integer level returns real
            return 0. + (0*level)
        endmethod

        //-------------------------------------------------------------
        // getAgilityDamage - returns the bonus damage of the spell
        // derived from the Agility of the caster. 1.0 is 100% of the
        // caster's Agility. Original value is 0. + (.15*level)
        //-------------------------------------------------------------
        static method getAgilityDamage takes integer level returns real
            return 0. + (.15*level)
        endmethod

        //-------------------------------------------------------------
        // getIntelligenceDamage - returns the bonus damage of the spell
        // derived from the Intelligence of the caster. 1.0 is 100% of the
        // caster's Intelligence. Original value is 0. + (0*level)
        //-------------------------------------------------------------
        static method getIntelligenceDamage takes integer level returns real
            return 0. + (0*level)
        endmethod

        readonly static boolean CAN_DESTROY_TREES = true
        readonly static boolean PRELOAD = true
    endmodule    

    native UnitAlive takes unit id returns boolean
    module VengeanceSlashConfiguration    

        //-------------------------------------------------------------
        // SLASH_MODEL - model used to represent the slashes
        // Original value is "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
        //-------------------------------------------------------------
        readonly static string SLASH_MODEL = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
         
        //-------------------------------------------------------------
        // SLASH_HIT - model used when the spell hits
        // Original value is "Abilities\\Weapons\\SearingArrow\\SearingArrowMissile.mdl"
        //-------------------------------------------------------------
        readonly static string SLASH_HIT = "Abilities\\Weapons\\SearingArrow\\SearingArrowMissile.mdl"

        //-------------------------------------------------------------
        // SLASH_ATTACHMENT - attachment point of the SLASH_HIT model
        // Original value is "chest"
        //-------------------------------------------------------------
        readonly static string SLASH_ATTACHMENT= "chest"

        //-------------------------------------------------------------
        // SLASH_HEIGHT - starting point of the slashes. Orignal value is 250
        //-------------------------------------------------------------
        readonly static real SLASH_HEIGHT = 250

        //-------------------------------------------------------------
        // ATTACK_TYPE - attacktype of the spell. Original value is ATTACK_TYPE_NORMAL
        //-------------------------------------------------------------
        readonly static attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        readonly static damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
        readonly static weapontype WEAPON_TYPE = null

        readonly static boolean CAN_MINISTUN = true

        //-------------------------------------------------------------
        // getSlashModelSize - returns the size of the model used for the
        // slashes. Original value is 1.
        //-------------------------------------------------------------
        static method getSlashModelSize takes integer level returns real
            return 1.
        endmethod

        //-------------------------------------------------------------
        // getScatterRadius - returns the spread of the slashes.Usually
        // it should match the AoE of the spell but you can go higher
        // if the slashes is not satisfactory. Orignal value is 300.
        //-------------------------------------------------------------
        static method getScatterRadius takes integer level returns real
            return 300.
        endmethod

        //-------------------------------------------------------------
        // getDuration - returns the delay before damage is dealt. It will
        // also affect the speed of the slashes visually. Original value
        // is .15
        //-------------------------------------------------------------
        static method getDuration takes integer level returns real
            return .15
        endmethod

        //-------------------------------------------------------------
        // isVengeable - is the damage filter of the spell.
        // unit u - is the unit in question
        // player p - is the owner of the spell
        // Original Conditions:
        //  - u is alive
        //  - u is not a structure
        //  - u is not magic immune
        //  - u is is an enemy of player p
        //-------------------------------------------------------------
        method isVengeable takes unit u, player p returns boolean
            return UnitAlive(u) and not(IsUnitType(u, UNIT_TYPE_STRUCTURE)) and not(IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and IsUnitEnemy(u,p)
        endmethod

        //-------------------------------------------------------------
        // onDamage - is the damage event of the spell.
        // unit caster - is the caster of the spell
        // unit target - is the enemy about to get damaged by the spell
        // real damage - is the amount of damage about to be dealt
        // attacktype attackType - attacktype of the spell
        // damagetype damageType - damagetype of the spell
        // weapontype weaponType - weapontype of the spell
        //-------------------------------------------------------------
        method onDamage takes unit caster, unit target, real damage, attacktype attackType, damagetype damageType, weapontype weaponType returns nothing
            call UnitDamageTarget(caster, target, damage, true, false, attackType, damageType, weaponType)
        endmethod

        //-------------------------------------------------------------
        // onMinistun - is the ministun event of the spell
        // unit u - target of the ministun
        // integer l - level of the ministun
        //-------------------------------------------------------------
        method onMinistun takes unit u, integer l returns nothing
            call orderCaster(u, l)
        endmethod

        //-------------------------------------------------------------
        // PRELOAD - true if you want the spell to preload the spell and models
        //-------------------------------------------------------------
        readonly static boolean PRELOAD = true
    endmodule
    //-------------------------------------------------------------
    // END OF CONFIGURATION
    //-------------------------------------------------------------

    //-------------------------------------------------------------
    // DummyCaster module - made to handle the ministun. Configuration
    // is found at the globals block
    //-------------------------------------------------------------
    module DummyCaster
        private static unit dummyCaster
        static method loadCaster takes boolean b returns nothing
            if not(b) then
                return
            endif
            set dummyCaster = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CASTER_TYPE, 0, 0, 0)
            call UnitAddAbility(dummyCaster, CASTER_ABILITY)
        endmethod
        static method orderCaster takes unit u, integer level returns nothing
            if dummyCaster == null then
                return
            endif
            call SetUnitX(dummyCaster, GetUnitX(u))
            call SetUnitY(dummyCaster, GetUnitY(u))
            call SetUnitAbilityLevel(dummyCaster, CASTER_ABILITY, level)
            call IssueTargetOrderById(dummyCaster,CASTER_ORDER,u)
        endmethod
    endmodule

    //-------------------------------------------------------------
    // TreeDestruction module - made to handle tree destruction. 
    // Configuration is found at the globals block
    //-------------------------------------------------------------
    module TreeDestruction
        private static unit treeHarvester
        static method loadHarvester takes boolean b returns nothing
            if not(b) then
                return
            endif
            set treeHarvester = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), HARVESTER_TYPE, 0, 0, 0)
            call UnitAddAbility(treeHarvester, 'Ahar')
        endmethod
        
        // calculates the radius. A modified version of GetRectFromCircleBJ(Location, Radius)
        private static method getRadius takes real x, real y, real r returns rect
        return Rect(x - r, y - r, x + r, y + r)
        endmethod

        private static method destroyTrees takes nothing returns boolean
            local destructable d = GetFilterDestructable()
            if IssueTargetOrderById(treeHarvester,HARVESTER_ORDER,d) then
                call KillDestructable(d)
            endif
            return false
        endmethod
        
        // method for selecting trees at (x, y) within radius r
        static method clearTrees takes real x, real y, real r returns nothing
            local rect reg 
            if treeHarvester == null then
                return
            endif
            set reg = getRadius(x, y, r)
            call EnumDestructablesInRect(reg, Filter(function thistype.destroyTrees), null)
            call RemoveRect(reg)
            set reg = null
        endmethod
    endmodule

    //-------------------------------------------------------------
    // VengeanceStormStages module - enum of stages
    //-------------------------------------------------------------
    module VengeanceStormStages
        readonly static string STAGE_INITIAL = "INITIAL"
        readonly static string STAGE_PRESLASH = "PRE_SLASH"
        readonly static string STAGE_SLASHING = "SLASHING"
        readonly static string STAGE_WAITING = "WAITING"
        readonly static string STAGE_DONE = "DONE"
    endmodule

    //-------------------------------------------------------------
    // VengeanceSlashCoordinates module - used for handling the (x, y, z)
    // of the slashes. Also includes methods to standardize the processes
    //-------------------------------------------------------------
    module VengeanceSlashCoordinates

        real startingX
        real startingY
        real startingZ
        real endingX
        real endingY
        real endingZ
        real scatterRadius
        static location loc = Location(0, 0)

        // a modified version of PolarProjectionBJ(Location, Dist, Angle) to
        // mutate x by scatterRadius towards a random angle
        method generateRandomX takes real x returns real
            return x + GetRandomReal(0, scatterRadius) * Cos(GetRandomDirectionDeg())
        endmethod

        // a modified version of PolarProjectionBJ(Location, Dist, Angle) to
        // mutate y by scatterRadius towards a random angle
        method generateRandomY takes real y returns real
            return y + GetRandomReal(0, scatterRadius) * Sin(GetRandomDirectionDeg())
        endmethod

        method setEndingPoint takes real x, real y returns nothing
 
            set .endingX = generateRandomX(x)
            set .endingY = generateRandomY(y)
            call MoveLocation(.loc, .endingX, .endingY)
            set .endingZ = GetLocationZ(loc)
        endmethod

        method setStartingPoint takes real x, real y returns nothing
            set .startingX = generateRandomX(x)
            set .startingY = generateRandomY(y)
            call MoveLocation(.loc, .startingX, .startingY)
            set .startingZ = SLASH_HEIGHT + GetLocationZ(loc)
        endmethod
        
        method lerp takes real a, real b returns real
            return a + (b - a) * .t
        endmethod
    endmodule

    //-------------------------------------------------------------
    // VengeanceSlash module - represents a single slash of the spell
    //-------------------------------------------------------------
    struct VengeanceSlash
        implement VengeanceSlashConfiguration
        implement VengeanceSlashCoordinates
        implement DummyCaster
 
        // uses the pattern of optimized group enumeration
        // found at: https://www.hiveworkshop.com/threads/vjass-optimization-using-a-first-of-group-loop-for-enumeration.223140/
        private static group g = CreateGroup()
       
        // dynamic indexing pattern
        // found at: https://www.hiveworkshop.com/threads/vjass-spell-templates.216166/
        private static timer slashTimer = CreateTimer()
        private static integer index = 0
        private thistype next
        private thistype prev
        
        private real t = 0.                // used for lerping
        private real x              
        private real y
        private integer l                  // level
        private unit caster 
        private player owner
        private real speed
        private real damage = 0
        private real aoe = 0
        private boolean hasDamage = false  // only true for the first slash of the batch
        private effect slash

        private static method preload takes boolean b returns nothing
            local unit p = null
            if not(b) then
                return
            endif

            set p = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'nech', 0, 0, 0)
            call DestroyEffect(AddSpecialEffectTarget(SLASH_MODEL, p, "chest"))
            call DestroyEffect(AddSpecialEffectTarget(SLASH_HIT, p, "chest"))
            call RemoveUnit(p)
            set p = null
        endmethod

        private method createSFX takes nothing returns nothing
            set .slash = AddSpecialEffect(SLASH_MODEL, .startingX, .startingY)
            call BlzSetSpecialEffectZ(.slash, .startingZ)
        endmethod

        private method update takes nothing returns boolean
            call BlzSetSpecialEffectX(.slash, lerp(.startingX, .endingX))
            call BlzSetSpecialEffectY(.slash, lerp(.startingY, .endingY))
            call BlzSetSpecialEffectZ(.slash, lerp(.startingZ, .endingZ))
            set .t = .t + .speed
            return .t > 1
        endmethod

        private method clear takes nothing returns nothing
            local unit u = null
            call this.deallocate()
            
            // only the first slash has the damage flag turned on to sync the effect
            // and the damage without setting up a weird timing code at the VengeanceStorm module
            if .hasDamage then
                call GroupEnumUnitsInRange(g, .x, .y, .aoe, null)
                loop
                    set u = FirstOfGroup(g)
                    exitwhen u == null

                    if .isVengeable(u, .owner) then      
                        call DestroyEffect(AddSpecialEffectTarget(SLASH_HIT, u, SLASH_ATTACHMENT))                  
                        call .onDamage(caster, u, .damage, .ATTACK_TYPE, .DAMAGE_TYPE, .WEAPON_TYPE)

                        if .CAN_MINISTUN then
                            call .onMinistun(u, .l)
                        endif
                    endif

                    // not destroying the group as described from the thread 
                    call GroupRemoveUnit(g,u)
                endloop
            endif

            call BlzSetSpecialEffectScale(.slash, 0.001)
            call DestroyEffect(.slash)

            set this.next.prev = this.prev
            set this.prev.next = this.next
            set index = index - 1
            if index == 0 then
                call PauseTimer(.slashTimer)
            endif
            
        endmethod

        private static method onEffect takes nothing returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this==0
 
                    // update returns true when t > 1.
                    if this.update() then
                        call this.clear()
                    endif

                set this = this.next
            endloop
        endmethod

        // expected values:
        // (x, y) - target location of the spell
        // l - level
        // p - owner of the spell
        // b - has damage (if done right, only the first slash will be true)
        // aoe - area of effect of the spell
        static method create takes real x, real y, integer l, player p, unit u, boolean b, real aoe, real damage returns VengeanceSlash

            local thistype this = thistype.allocate()
            set this.next = 0
            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this

            set .l = l          
            set .hasDamage = b
            if .hasDamage then
                set .damage = damage
                set .aoe = aoe
            endif
            set .caster = u
            set .owner = p
            set .scatterRadius = getScatterRadius(l)
            set .speed = 1. / (.getDuration(.l) / TIMER_SPEED)
            set .x = x
            set .y = y
 
            call this.setStartingPoint(.x, .y)
            call this.setEndingPoint(.x, .y)
            call this.createSFX()

            set index = index + 1
            if index == 1 then
                call TimerStart(slashTimer, TIMER_SPEED, true, function thistype.onEffect)
            endif

            return this
        endmethod

        // preload and dummy caster set up
        private static method onInit takes nothing returns nothing
            call .preload(.PRELOAD)
            call loadCaster(CAN_MINISTUN)
        endmethod

    endstruct

    //-------------------------------------------------------------
    // VengeanceStorm module - controls when, where and how many slashes
    // the spell is going to create
    //-------------------------------------------------------------
    struct VengeanceStorm
        implement VengeanceStormConfiguration
        implement VengeanceStormStages
        implement TreeDestruction

    private static timer vengeanceTimer = CreateTimer()
        private static integer index = 0
        private thistype next
        private thistype prev

        private unit caster
        private real targetX
        private real targetY
        private player owner
        private integer level
        private integer slashesLeft
        private real slashTimeLeft
        private string stage
        private effect range
        private effect weapon
        private real aoe
        private real damage
        private VengeanceSlash slash

        private static method updateSFX takes real x, real y, real z returns real
            call MoveLocation(VengeanceSlash.loc, x, y)
            set z = GetLocationZ(VengeanceSlash.loc) + z
            return z
        endmethod


        private static method getDamageFromAttribute takes unit u, real s, real a, real i returns real
            local real damage_agi = I2R(GetHeroStatBJ(bj_HEROSTAT_AGI, u, true)) * a
            local real damage_str = I2R(GetHeroStatBJ(bj_HEROSTAT_STR, u, true)) * s
            local real damage_int = I2R(GetHeroStatBJ(bj_HEROSTAT_INT, u, true)) * i
            return damage_agi + damage_str + damage_int
        endmethod

        private static method preload takes boolean b returns nothing
            local unit p = null
            if not(b) then
                return
            endif

            set p = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'nech', 0, 0, 0)
            call UnitAddAbility(p, .VENGEANCE_STORM_ID)
            call UnitRemoveAbility(p, .VENGEANCE_STORM_ID) 
            call DestroyEffect(AddSpecialEffectTarget(SLASH_RANGE_MODEL, p, "chest"))
            call RemoveUnit(p)
            set p = null
        endmethod

        private method clear takes nothing returns nothing
            call this.deallocate()

            call BlzSetSpecialEffectScale(.range, 0.001)
            call DestroyEffect(.range)
            set this.caster = null

            set this.next.prev = this.prev
            set this.prev.next = this.next
            set index = index - 1
            if index == 0 then
                call PauseTimer(.vengeanceTimer)
            endif
        endmethod

        private static method onEffect takes nothing returns nothing

            local integer i
            local boolean hasDamage

            local thistype this = thistype(0).next
            loop
                exitwhen this  == 0

                if .stage == .STAGE_INITIAL then
                    if this.slashTimeLeft > 0. then
                        set .slashTimeLeft = .slashTimeLeft - TIMER_SPEED
                    else
                        set .stage = .STAGE_PRESLASH
                       
                        // range indicator
                        set .range = AddSpecialEffect(SLASH_RANGE_MODEL, .targetX, .targetY)
                        call BlzSetSpecialEffectScale(.range, getSlashRangeSize(.level))
                        call BlzSetSpecialEffectZ(.range, updateSFX(.targetX, .targetY, SLASH_RANGE_HEIGHT))

                        call DestroyEffect(.weapon)

                        if CAN_DESTROY_TREES then
                            call clearTrees(.targetX, .targetY, .aoe)
                        endif
                    endif
                endif

                if this.stage == this.STAGE_PRESLASH then
                    set i = 1
                    set hasDamage = true
                    loop
                        exitwhen i > getSlashesPerStorm(this.level)
                        set slash = VengeanceSlash.create(.targetX, .targetY, .level, .owner, .caster, hasDamage, aoe, .damage)
                        set i = i + 1
 
                        // only the first slash stores the damage
                        set hasDamage = false
                    endloop
          
                    set .slashesLeft = .slashesLeft - 1
                    set .stage = .STAGE_WAITING
                    set .slashTimeLeft = getDelayPerStorm(.level)
                endif

                if this.stage == .STAGE_WAITING then
                    if .slashTimeLeft > 0 then
                        set .slashTimeLeft = .slashTimeLeft - TIMER_SPEED
                    else

                        // only set the stage to STAGE_DONE when
                        // all of the slashes are done executing
                        if .slashesLeft == 0 then 
                            set .stage = .STAGE_DONE
                        else
                            set .stage = .STAGE_PRESLASH
                        endif
                    endif
                endif

                // clean up step
                if this.stage == .STAGE_DONE then
                    call .clear() 
                endif
 
                set this = this.next
            endloop
        endmethod

        private static method onCast takes nothing returns nothing
            local thistype this
            if GetSpellAbilityId() == VENGEANCE_STORM_ID  then
                
                set this = thistype.allocate()
                set this.next = 0
                set this.prev = thistype(0).prev
                set thistype(0).prev.next = this
                set thistype(0).prev = this
              
                set .caster = GetTriggerUnit()
                set .owner = GetTriggerPlayer()
                set .level = GetUnitAbilityLevel(.caster, VENGEANCE_STORM_ID)
                set .targetX = GetSpellTargetX()
                set .targetY = GetSpellTargetY()
                set .aoe = getAoe(.level)
                set .damage = getAbsoluteDamage(.level) + getDamageFromAttribute(.caster, getStrengthDamage(.level), getAgilityDamage(.level), getIntelligenceDamage(.level))
                set .slashTimeLeft = getInitialSlashDelay(.level)
                set .slashesLeft = getStormCount(this.level)
                set .stage = .STAGE_INITIAL 
                set .weapon = AddSpecialEffectTarget(WEAPON_MODEL, .caster, WEAPON_ATTACHMENT)
                set index = index + 1
                if index == 1 then
                    call TimerStart(vengeanceTimer, TIMER_SPEED, true, function thistype.onEffect)
                endif
                
            endif
        endmethod

        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()

            call .preload(PRELOAD)
            call loadHarvester(CAN_DESTROY_TREES)

            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.onCast))
            set t = null
        endmethod
    endstruct
endscope

1. Copy and paste the objects from this map to your map namely:
  • Vengeance Storm
  • Vengeance Storm - (Ministun)
  • Dummy - (Vengeance Storm)
2. Copy and paste the Vengeance Storm Script into your map
3. Configure the spell so that everything points to the correct variables. Additional information about the variables are provided for clarity.

The spell is heavily inspired by Vengeance Storm from Dragon Nest. The screenshots don't fully depict what it looks like in game.


This is what I was trying to create. I do not own the embedded video.

Credits

Vexorian - dummy.mdx for the dummy caster model

v1.0
  • initial release
v1.1
  • moved the damage calculation from VengeanceSlash module to VengeanceStorm module
  • added sanity checks to the TreeDestruction and DummyCaster modules
  • added SLASH_RANGE_HEIGHT to adjust the range indicator's height
  • added special effects when casting
v1.2
  • changed the way the spell uses location
Previews
Contents

Blightsower Vengeance Storm v1.2 (Map)

Reviews
Wrda
// calculates the radius. A modified version of GetRectFromCircleBJ(Location, Radius) private static method getRadius takes real x, real y, real r returns rect return Rect(x - r, y - r, x + r, y + r) endmethod A little bit of...

Rheiko

Spell Reviewer
Level 25
Joined
Aug 27, 2013
Messages
4,120
Isn't this vJASS? I mean sure it's now supported by the built-in world editor but I think it's better to use the proper tag, no?
I don't know. No biggie, though.

That aside, from the pictures, this looks wicked.
I wish you could upload some previews with gifs, though. Since the in-game visual of your resources is usually awesome.
 
Isn't this vJASS? I mean sure it's now supported by the built-in world editor but I think it's better to use the proper tag, no?
I don't know. No biggie, though.
fixed.

That aside, from the pictures, this looks wicked.
I wish you could upload some previews with gifs, though. Since the in-game visual of your resources is usually awesome.
maybe in the future
@Rheiko the future is now
 
Last edited:

Wrda

Spell Reviewer
Level 27
Joined
Nov 18, 2012
Messages
1,967
JASS:
// calculates the radius. A modified version of GetRectFromCircleBJ(Location, Radius)
        private static method getRadius takes real x, real y, real r returns rect
        return Rect(x - r, y - r, x + r, y + r)
        endmethod

A little bit of indention problem.


JASS:
method setEndingPoint takes real x, real y returns nothing
            local location l
            set .endingX = generateRandomX(x)
            set .endingY = generateRandomY(y)
            set l = Location(.endingX, .endingY)
            set .endingZ = GetLocationZ(l)
            call RemoveLocation(l)
        endmethod


        private static method adjustSfxZ takes real x, real y, real z returns real
            local location l = Location(x, y)
            set z = GetLocationZ(l) + z
            call RemoveLocation(l)
            return z
        endmethod
you can do this with only one location instead of creating it over and over. Use MoveLocation to move the same location variable, without destroying it.

JASS:
private method update takes nothing returns boolean
updateSFX looks better as a name, it's more descriptive.
You use so many comments even for things that are obvious such as explaining the parameters of the functions/methods :p seems a bit unnecessary.

However, the spell looks pretty good, perfect for an executioner.

Approved
 
Spell has been updated.

updateSFX looks better as a name, it's more descriptive.
Done.

JASS:
method setEndingPoint takes real x, real y returns nothing
local location l
set .endingX = generateRandomX(x)
set .endingY = generateRandomY(y)
set l = Location(.endingX, .endingY)
set .endingZ = GetLocationZ(l)
call RemoveLocation(l)
endmethod


private static method adjustSfxZ takes real x, real y, real z returns real
local location l = Location(x, y)
set z = GetLocationZ(l) + z
call RemoveLocation(l)
return z
endmethod
you can do this with only one location instead of creating it over and over. Use MoveLocation to move the same location variable, without destroying it.
Done.

Also, changed the spell's delayPerStorm to make the spell cast faster. If you already have the spell downloaded, you can copy the code and paste it over your existing one if you don't feel like redownloading it again.
 
Level 3
Joined
Sep 26, 2023
Messages
17
Update: Now the range indicator's height can be adjusted using SLASH_RANGE_HEIGHT variable and will adjust based on the Z of the terrain. Also, special effect attached to weapon when casting.

Edit: I wont be touching the spell until after a review. Thanks
@Blightsower excellent spell :thumbs_up:. I noticed that, when the slashes occur, their Z is based on the height that is set regardless of the different level of terrain height.
 

Rheiko

Spell Reviewer
Level 25
Joined
Aug 27, 2013
Messages
4,120
It could also happen if you use GetLocationZ at a point where a destructible object such as Elevator and Footswitch is placed.
To quote WaterKnight:
A typical mistake is also when you have animated walkable destructables. Walkable destructables change the result of GetLocationZ and animations are not in sync, only rendered when player has view there etc.
A desync can also happen when 2 users on the same OS have different visual settings. But yes, main cause is usually from terrain deformations. Although I'm not sure, this may include abilities such as War Stomp and Shockwave.

Besides, common.j already stated it clearly:
JASS:
// This function is asynchronous. The values it returns are not guaranteed synchronous between each player.
//  If you attempt to use it in a synchronous manner, it may cause a desync.
native GetLocationZ takes location whichLocation returns real

So yeah, use it with caution.
 
Top