• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[JASS] razor gale not MUI?

Status
Not open for further replies.
Level 13
Joined
May 11, 2008
Messages
1,198
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.

JASS:
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.
// 
// ****************************************************************

    globals
        //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
    endglobals

    //spell stats
    private constant function GlideSpeed takes integer level returns real
        return 750.0 //the distance that the caster travles per second
    endfunction
    private constant function GlideMaxDistance takes integer level returns real
        return 1000.0 //what is the furthest the caster can glide
    endfunction
    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
    endfunction

    private constant function Damage takes integer level returns real
        return 250.0*level
    endfunction
    private constant function DamageRadius takes integer level returns real
        return 160.0
    endfunction
    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
    endfunction


// END OF CALIBRATION SECTION
// ================================================================

    //Sliding and damage
    private keyword instance
    globals
        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
    endglobals

    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
        endif
        set u=null
        return false
    endfunction

    private function Periodic takes nothing returns nothing
        local integer i=0
        loop
            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
                endif
                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()
            endif
        endloop
    endfunction

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

    //Animations
    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 
    endfunction
    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())
    endfunction
    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
        endif
    endfunction
    
// ================================================================

    //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
            endif
            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()
            endif

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

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

            set instances[instanceCount]=si
            set si.index=instanceCount
            set instanceCount=instanceCount+1
            return si
        endmethod
        
        static method get takes unit u returns instance
            local integer i=0
            loop
                exitwhen i==instanceCount
                if instances[i].caster==u then
                    return instances[i]
                endif
                set i=i+1
            endloop
            return 0
        endmethod
        
        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()
                endif
            endif
        endmethod
        
        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
            endif
            call ReleaseTimer(this.t)
            call GroupClear(this.affected) //the next time this instance id is used we won't need to create a group
        endmethod
    endstruct

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

    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
            else
                //unit-target cast
                call instance.create(GetTriggerUnit(), lvl, GetUnitX(u), GetUnitY(u))
                set u = null
            endif
        endif
    endfunction
    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
            endif
        endif
    endfunction

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

endscope
 
Last edited:
Level 13
Joined
May 11, 2008
Messages
1,198
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:
Status
Not open for further replies.
Top