HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

spell won't cast=spell not mui?

05-24-2011, 03:36 PM#1
SanKakU
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?

Hidden information:
Collapse 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
05-24-2011, 04:42 PM#2
Anitarf
Does the double have the exact same ability as the hero? Does the double have enough mana to cast it? Can you also post your code for mimicking the hero's actions?
05-24-2011, 06:17 PM#3
SanKakU
ah, that was a somewhat quick reply. i was hoping you'd look at the spell. the code i wrote to mimick the actions is nothing intersting. and fyi, it's in cJASS. i can turn it into vJASS if you want. you shouldn't need me to though to get an idea as to what i'm doing.
Hidden information:

Collapse JASS:
scope ShadowServant initializer I
globals
//private location v=null
private unit z=null
private integer a=0,b=0,c=0,d=0,e=0
endglobals

define {private CD=CreateUnit(GetOwningPlayer(GetTriggerUnit()),'U004',GetUnitX(GetTriggerUnit()),GetUnitY(GetTriggerUnit()),GetUnitFacing(GetTriggerUnit()))
//CreateDrone(GetTriggerUnit(),'U004',20.0,0.0,true)
private UAA(x,y)=UnitAddAbility(z,x);SetUnitAbilityLevel(z,x,y)
private SD=//UnitRemoveAbility(z,'ARz2');
SetUnitVertexColor(z,30,30,30,250);//UnitAddAbility(z,'Aloc')  call 
SetUnitPathing(z,false)
private ord=OrderId2String(GetIssuedOrderId());private igou=GetUnitTypeId(GetOrderedUnit());private Z='U004'
private gou=GetOrderedUnit()}
private boolean to(){if igou==Z and gou !=z then;IssueTargetOrder(z,ord,GetOrderTarget());endif;return false}
private boolean po(){if igou==Z and gou !=z then;IssuePointOrder(z,ord,GetOrderPointX(),GetOrderPointY())
endif;return false}
private boolean io(){if igou==Z and gou !=z then;IssueImmediateOrder(z,ord);endif;return false}
private boolean f(){if z!=null then;//RemoveDrone
RemoveUnit(z);z=CD;SD;UAA('A001',e);UAA('ARz2',a);UAA('A0CN',c)
endif;z=CD;SD;UAA('A001',e);UAA('ARz2',a);UAA('A0CN',c);return false}
private boolean g(){d++;return false}
private boolean h(){if igou==Z then;e++;endif;return false}
private boolean i(){c++;return false}
private boolean j(){b++;return false}
private boolean k(){a++;return false}
//private boolean lrzr(){location v;unit q;if GetSpellTargetUnit()==null then;
//v=GetSpellTargetLoc();if GetUnitTypeId(GetTriggerUnit())==Z and GetTriggerUnit()!=z then;IssuePointOrder(z, "sleep", GetLocationX(v), GetLocationY(v));endif;RemoveLocation(v);v=null;endif;return false}
private void I(){trigger t=CreateTrigger();TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
TriggerAddCondition( t,Condition( function to));set t = CreateTrigger(  );TriggerRegisterAnyUnitEventBJ( t, \
EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER );TriggerAddCondition( t,Condition( function po));set t = CreateTrigger(  )
TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_ISSUED_ORDER );TriggerAddCondition( t,Condition( function io))
effectstartGT(f,'A0FS');skillGT(g,'A0FS');skillGT(h,'A001');skillGT(i,'A0CN');skillGT(j,'A001')
skillGT(k,'ARz2');//effectstartGT(lrzr,'ARz2')
};endscope


i'm guessing if i added some delay to the mimicking, then razorgale would get casted. but there shouldn't have to be one. right now i'm testing to see if having the dummy cast the spell using a copy of the scope would work, as someone implied i should try out. but that imo would be a clumsy work-around and the real problem wouldn't have been fixed...lol it highlights some of the code in red. haven't seen that. edit, hmm unless i made a mistake then that isn't working either(duplicating scope and ability)

i'm going to run debug script on the hero and double and see what it says. hmm...debug isn't showing any discrepency...i mean it only shows one order issued which must be the caster issued the order it shows it like all the other orders. the major difference being that for whatever reason, the double is NOT getting the orders issued to it. other spells it shows the double performing them and debug shows them issued the order. so i guess we can confirm the double isn't getting an order and trying to do it and failing. the double doesn't seem to be getting any order at all. which we already knew. but the point is that the order issued to the caster is plainly visible, so idk why it isn't being carried over to the double. i suspected all this, but i can't explain it. i didn't write the spell, so i'm not sure why it appears to be non-MUI and i don't know how to fix it. it even puzzles me further that duplicating the scope with new raw codes and abilities to match doesn't alleviate the situation.

edit: adding another line to debug code to check if point is being lost somehow when usin rzrgale..ya the point isn't lost so idk what the prob is

and ya ofc the double has mana it's not that the double can't cast the spell, it's that it's not being casted automatically...
05-24-2011, 07:46 PM#4
Anitarf
If the double is not getting orders issued to it then that's not a problem with the code of Razor Gale (which is MUI), since that code only runs when the unit casts the spell which happens after it is issued an order to cast the spell. If this is a learned hero skill, are you sure the double also has it learned?

Edit: try giving the hero a different ability instead of the Razor Gale custom ability. For example, give your hero Pocket Factory which is also a point-target ability, but shows a projectile so it's quite easy to see it it was cast or not, then use the ability ingame and see if the double also uses it. If not, then there is clearly no problem with the Razor Gale code.
05-24-2011, 08:23 PM#5
SanKakU
Quote:
Originally Posted by Anitarf
If the double is not getting orders issued to it then that's not a problem with the code of Razor Gale (which is MUI), since that code only runs when the unit casts the spell which happens after it is issued an order to cast the spell. If this is a learned hero skill, are you sure the double also has it learned?

Edit: try giving the hero a different ability instead of the Razor Gale custom ability. For example, give your hero Pocket Factory which is also a point-target ability, but shows a projectile so it's quite easy to see it it was cast or not, then use the ability ingame and see if the double also uses it. If not, then there is clearly no problem with the Razor Gale code.
i thought i was very clear. i've already tried other spells. they work just fine.

edit: yes in case you're wondering i'm trying pocket factory and it's working flawlessly, along with others

here's a test map if you really want to confirm it that badly.
note: "-lvl" as chat command will levelup your hero to level 6+. the hero in question is the red warden-looking hero with the sword near the red warden
Attached Files
File type: w3x!!!!!!!!!!AFrontier.w3x (3.4 MB)
05-25-2011, 02:09 PM#6
Anitarf
Quote:
Originally Posted by SanKakU
i thought i was very clear. i've already tried other spells. they work just fine.
Okay, but why do you keep saying the spell is not MUI when the spell's code is clearly not the problem here? I tested the map and like you said, the order is not getting copied properly, so why should the Razor Gale code have anything to do with it failing? It's the order copying code that is not getting the job done.

I Tried switching the triggering abilities for the Birds spell and the Razor Gale: the first ability order got copied same as before, so now both heroes used razor gale (only it didn't go anywhere because the birds spell didn't have a channeling duration) and the second ability order didn't get copied, so now Birds no longer worked for the duplicate hero. So, the problem is apparently in the triggering ability for Razor Gale, not the code, try switching it to some other targeted channeling ability and see if it works.
05-25-2011, 03:57 PM#7
SanKakU
your code uses language that is very alien to me, i don't understand it, so how could i know 100 percent it is MUI? i'll try figuring out if the problem is with the object in object editor...

edit: the spell seems to be working ok with rain of fire.
05-25-2011, 06:38 PM#8
Anitarf
Apparently something in the original triggering spell made its order not get copied. I'm as baffled as you are by this, I would try experimenting with it to see if I could find the reason but unfortunately cjass is very alien to me so I couldn't do anything with your order-copying code. I guess as long as you found a different triggering spell where order copying works, this isn't such a big problem any more.
05-25-2011, 06:55 PM#9
SanKakU
well it's a large map with a ton of triggers, so it was probably hard for you to find the 3 relevant triggers... fudo, DebugS and MSGS fudo is the hero abilities and debugS is the debugging stuff, which uses msgs... the map is a really long way from being finished so it's not easy to navigate the triggers atm.