• 💀 Happy Halloween! 💀 It's time to vote for the best terrain! Check out the entries to Hive's HD Terrain Contest #2 - Vampire Folklore.❗️Poll closes on November 14, 2023. 🔗Click here to cast your vote!
  • 🏆 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!
  • 🏆 HD Level Design Contest #1 is OPEN! Contestants must create a maze with at least one entry point, and at least one exit point. The map should be made in HD mode, and should not be openable in SD. Only custom models from Hive's HD model and texture sections are allowed. The only exceptions are DNC models and omnilights. This is mainly a visual and design oriented contest, not technical. The UI and video walkthrough rules are there to give everyone an equal shot at victory by standardizing how viewers see the terrain. 🔗Click here to enter!

[JASS] razor gale not MUI?

Not open for further replies.
Level 13
May 11, 2008
update: using rain of fire as base ability seems to resolve the issue

this spell razor gale. i like it tons. but i had a fun idea for making a double of a hero go around doing the things the hero did.

basically everything the hero does, the double will do. movement, attack, spell casting, all automatic. but razor gale does not get casted. can you figure out why and how to fix it?

warden spins around jumping past units, damaging them, that's what happens. it's all fine and dandy except when i want my dummy unit to cast the spell at the exact same time. i haven't tried adding a delay to the automatic action to see if that would help. maybe it would...regardless it's odd because other spells including channeling based custom spells work.

scope RazorGalefudo initializer Init

// ****************************************************************
//   spell - Razor Gale
//   written by: Anitarf
//   requires: -xebasic
//             -xedamage
//             -TimerUtils
//             -a point and/or unit target channeling triggerer ability
//   description: The casting unit glides towards a target point as
//                long as it continues channeling or until it
//                reaches the target point or max distance and
//                deals damage to enemy units along the way.
//   technical: The spell is designed specifically for the Warden
//              model. It also disables spell sounds for the
//              duration of the spell because the sound attached to
//              the Warden's spell slam animation is too annoying
//              otherwise. If a custom warden model is used with
//              this sound removed, then the sound disabling
//              feature of this spell may be turned off.
// ****************************************************************

        //Spell settings
        private constant integer SPELL_ABILITY = 'ARz2' //the spell ability
        private constant real SPELL_PERIOD = XE_ANIMATION_PERIOD //the speed of the periodic timer that moves the caster
        private constant integer DAMAGE_PERIOD_FACTOR = 3 //optimization option, every how many periods are new targets affected

        //Animation options
        private constant real ANIM_SPEED_START = 1.2 //the animation speed at the start of the spell
        private constant real ANIM_SPEED_END = 0.9 //the animation speed at the end of the spell
        private constant boolean DISABLE_SPELL_SOUND = false //may be set to false with a custom warden model

    //spell stats
    private constant function GlideSpeed takes integer level returns real
        return 750.0 //the distance that the caster travles per second
    private constant function GlideMaxDistance takes integer level returns real
        return 1000.0 //what is the furthest the caster can glide
    private constant function GlideMinDistance takes integer level returns real
        return 1000.0 //if the target point is closer than this, the caster will still glide this far

    private constant function Damage takes integer level returns real
        return 250.0*level
    private constant function DamageRadius takes integer level returns real
        return 160.0
    private function DamageOptions takes xedamage spellDamage returns nothing
        //useful read: [url]http://www.wc3campaigns.net/showpost.php?p=1030046&postcount=19[/url]
        set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL
        set spellDamage.atype=PureWarriorAttack
        set spellDamage.tag=0 //the tag attached to the damage by xedamage
        set spellDamage.exception=UNIT_TYPE_STRUCTURE //deal no damage to structures

// ================================================================

    //Sliding and damage
    private keyword instance
        private xedamage xed
        private timer slide
        private instance array instances
        private integer instanceCount = 0

        private group tempg = CreateGroup()
        private boolexpr tempbx
        private real tempx
        private real tempy
        private instance tempsi

    private function Targets takes nothing returns boolean
        local unit u=GetFilterUnit()
        if not(IsUnitInGroup(u, tempsi.affected)) and IsUnitInRangeXY(u, tempx, tempy, DamageRadius(tempsi.level)) then
            call GroupAddUnit(tempsi.affected, u)
            set u=null
            return true
        set u=null
        return false

    private function Periodic takes nothing returns nothing
        local integer i=0
            exitwhen i>=instanceCount
            set tempsi = instances[i]
            set tempx = GetUnitX(tempsi.caster)+tempsi.dx
            set tempy = GetUnitY(tempsi.caster)+tempsi.dy
            set tempsi.time=tempsi.time-SPELL_PERIOD
            if tempsi.time>0.0 and IsTerrainWalkable(tempx,tempy) then
                call SetUnitX(tempsi.caster, tempx)
                call SetUnitY(tempsi.caster, tempy)
                if tempsi.whenToDamage <= 0 then
                    set tempsi.whenToDamage=DAMAGE_PERIOD_FACTOR
                    call GroupEnumUnitsInRange(tempg,tempx,tempy,DamageRadius(tempsi.level) + XE_MAX_COLLISION_SIZE , tempbx)
                    call xed.damageGroup(tempsi.caster, tempg, Damage(tempsi.level)) //empties the group
                set tempsi.whenToDamage=tempsi.whenToDamage-1
                set i=i+1
            else //if instance is on the list it should still be active so it's safe to call finish
                call  tempsi.finish()

// ================================================================

    private function Animation_Finish takes nothing returns nothing
        local instance si=instance(GetTimerData(GetExpiredTimer()))
        call SetUnitTimeScale(si.caster, 1.0)
        call si.destroy() //this will release the expired timer, so no need to do it here 
    private function Animation_Child takes nothing returns nothing
        local instance si=instance(GetTimerData(GetExpiredTimer()))
        call SetUnitFlyHeight(si.caster, 0.0, 48.0/0.125*si.timescale)
        call ReleaseTimer(GetExpiredTimer())
    private function Animation takes nothing returns nothing
        local instance si=instance(GetTimerData(GetExpiredTimer()))
        local timer t
        if si.active then //go into next animation cycle
            set t = NewTimer()
            call SetTimerData(t, integer(si))
            call SetUnitTimeScale(si.caster, si.timescale)
            call SetUnitAnimationByIndex(si.caster, 6)
            call SetUnitFlyHeight(si.caster, 48.0, 48.0/0.125*si.timescale)
            call TimerStart(t, 0.125/si.timescale, false, function Animation_Child) //run secondary function in this interval
            call TimerStart(GetExpiredTimer(), 0.35/si.timescale, false, function Animation) //run next interval
            set si.timescale=si.timescale+si.dtimescale*0.35/si.timescale //how the time scale changes during the interval
        else //let the animation finish
            call SetUnitTimeScale(si.caster, 1.5)
            call TimerStart(GetExpiredTimer(), 0.45, false, function Animation_Finish) //allow animation to finish
// ================================================================

    //Spell instance
    private struct instance
        private integer index

        private timer t //this timer needs to be on a per-caster basis
        real timescale
        real dtimescale

        boolean active = true
        boolean interrupted = false
        integer whenToDamage = DAMAGE_PERIOD_FACTOR
        unit caster
        integer level
        real dx
        real dy
        group affected
        real time
        static method create takes unit caster, integer level, real targetx, real targety returns instance
            local instance si=instance.allocate()
            local real distance
            local real factor=1.0

            call UnitAddAbility(caster, XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(caster, XE_HEIGHT_ENABLER)
            set si.caster=caster
            set si.level=level
            set si.dx=targetx-GetUnitX(caster)
            set si.dy=targety-GetUnitY(caster)
            set distance = SquareRoot(si.dx*si.dx+si.dy*si.dy)+1.0 //to avoid division by zero later

            if distance>GlideMaxDistance(level) then
                set factor = GlideMaxDistance(level)/distance
            elseif distance<GlideMinDistance(level) then
                set factor = GlideMinDistance(level)/distance
            set si.time = factor*distance/GlideSpeed(level) //duration of the spell
            set factor = factor/si.time*SPELL_PERIOD
            set si.dx=si.dx*factor //distance traveled per interval
            set si.dy=si.dy*factor

            set si.timescale=ANIM_SPEED_START
            set si.dtimescale=(ANIM_SPEED_END-ANIM_SPEED_START)/(GlideMaxDistance(level)/GlideSpeed(level)) //animation speed change per second

            if si.affected == null then //create a group if this is the first time this instance id is used
                set si.affected=CreateGroup()

            set si.t=NewTimer() //animation timer
            call SetTimerData(si.t, integer(si))
            call TimerStart(si.t, 0.0, false, function Animation)

            //neccessary evil, the spell sounds like crap if the warden's animation sounds are allowed to play
            if DISABLE_SPELL_SOUND then
                call VolumeGroupSetVolume(SOUND_VOLUMEGROUP_SPELLS, 0.0)

            if instanceCount==0 then //slide timer
                set slide=NewTimer()
                call TimerStart(slide, SPELL_PERIOD, true, function Periodic)

            set instances[instanceCount]=si
            set si.index=instanceCount
            set instanceCount=instanceCount+1
            return si
        static method get takes unit u returns instance
            local integer i=0
                exitwhen i==instanceCount
                if instances[i].caster==u then
                    return instances[i]
                set i=i+1
            return 0
        method finish takes nothing returns nothing
            set this.active=false //spell stopped, wait for animation to finish and then end it
            set instanceCount=instanceCount-1
            set instances[this.index]=instances[instanceCount]
            set instances[instanceCount].index=this.index

            if instanceCount==0 then
                call ReleaseTimer(slide)
                //end of evil
                if DISABLE_SPELL_SOUND then
                    call VolumeGroupReset()
        method onDestroy takes nothing returns nothing
            if not(this.interrupted) then //if the channeling wasn't interrupted by the player...
                call IssueImmediateOrder(this.caster, "stop") //...then stop it now
            call ReleaseTimer(this.t)
            call GroupClear(this.affected) //the next time this instance id is used we won't need to create a group

// ================================================================

    private function SpellEffect takes nothing returns nothing
        local integer lvl
        local unit u
        local location l
        if GetSpellAbilityId() == SPELL_ABILITY then
            set lvl =GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ABILITY)
            set u = GetSpellTargetUnit()
            if u == null then
                //point-target cast
                set l = GetSpellTargetLoc()
                call instance.create(GetTriggerUnit(), lvl, GetLocationX(l), GetLocationY(l))
                call RemoveLocation(l)
                set l = null
                //unit-target cast
                call instance.create(GetTriggerUnit(), lvl, GetUnitX(u), GetUnitY(u))
                set u = null
    private function SpellStop takes nothing returns nothing
        local instance si
        if GetSpellAbilityId() == SPELL_ABILITY then
            set si = instance.get(GetTriggerUnit())
            if si != 0 then
                call si.finish()
                set si.interrupted=true

    private function Init takes nothing returns nothing
        //init spellcast trigger
        local trigger tr = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( tr, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddAction( tr, function SpellEffect )

        set tr = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( tr, EVENT_PLAYER_UNIT_SPELL_ENDCAST )
        call TriggerAddAction( tr, function SpellStop )
        //init damage filter
        set tempbx=Condition(function Targets)
        //init xedamage
        set xed=xedamage.create()
        call DamageOptions(xed)

Last edited:
Level 13
May 11, 2008
Have you modified the raw code to match your ability, this line here:
private constant integer SPELL_ABILITY = 'ARz2'

yes, that's not the issue, although it does make me wonder if i should make another instance of the scope for an ability with another rawcode, and give the dummy the extra ability instead of the original one. it could be a work-around.

edit: ya that won't work

when i debug the spell it gets registered as point order like you might expect but for unknown reason the double doesn't get the order.
Last edited:
Not open for further replies.