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

[vJASS] Prevent Computers and Players with High Delay from casting 2 spells at once

Status
Not open for further replies.
Hey guys, I have an issue with my map where computer players are able to force the cast of 1 ability "at the same time" as another one. I was unable to reproduce the problem myself, but a player I tested with (who had a 1/4s ping) was able to reproduce it.

So I've made my spell remove the other spell and then re-add it when it's finished its effect, but the issue still happens.

Any idea how to get around it?

Here's the spell they cast first -
JASS:
    private function p takes nothing returns nothing
        local unpauseDat tempDat
        local integer index=0
        loop
            exitwhen index>dbIndex
            set tempDat=unpauseDB[index]
            set tempDat.timeLeft=tempDat.timeLeft-FIDELITY
            if tempDat.timeLeft<=0 then
                call UnitAddAbility(tempDat.u,IMPALEID)
                call PauseUnit(tempDat.u,false)
                call SetUnitTimeScale(tempDat.u,1)
                set tempCasterX=GetUnitX(tempDat.u)
                set tempCasterY=GetUnitY(tempDat.u)
                call helper_sound3d(SLAMSOUND,tempCasterX,tempCasterY,GetUnitFlyHeight(tempDat.u))
                set tempCaster=tempDat.u
                call GroupEnumUnitsInRange(grp,tempCasterX,tempCasterY,SHOUTRANGE,Filter(function f))
                set unpauseDB[index]=unpauseDB[dbIndex]
                set dbIndex=dbIndex-1
                if dbIndex==-1 then
                    call PauseTimer(time)
                endif
                call tempDat.destroy()
            endif
            set index=index+1
        endloop
    endfunction
    
    private function c takes nothing returns boolean
        local effect fx
        local unpauseDat tempDat
        if GetSpellAbilityId()==SHOUTID then
            set tempDat=unpauseDat.create()
            set tempDat.u=GetTriggerUnit()
            set tempDat.timeLeft=PAUSETIME
            set dbIndex=dbIndex+1
            set unpauseDB[dbIndex]=tempDat
            call UnitRemoveAbility(tempDat.u,IMPALEID)
            if dbIndex==0 then
                call TimerStart(time,FIDELITY,true,function p)
            endif
            set fx=AddSpecialEffect(SLAMMODEL,GetUnitX(tempDat.u),GetUnitY(tempDat.u))
            call DestroyEffect(fx)
            call SetUnitTimeScale(tempDat.u,2)
            call IssueImmediateOrder(tempDat.u,"stop")
            call PauseUnit(tempDat.u,true)
            call SetUnitAnimationByIndex(tempDat.u,3)
        endif
        set fx=null
        return false
    endfunction

Notice how it removes IMPALEID from the caster
 
Here are the full scripts -

warSlam
JASS:
scope warSlam initializer i
    private struct unpauseDat
        unit u
        real timeLeft
    endstruct
    
    globals
        private constant integer SHOUTID='A00R'
        private constant integer IMPALEID='A00F'
        private constant real FIDELITY=.05
        private constant real MAXSHOUTPOWER=40.
        private constant real PAUSETIME=.45
        private constant real SHOUTRANGE=600.
        private constant string SLAMMODEL="Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
        private constant string SLAMSOUND="Units\\Orc\\HeroTaurenChieftain\\WarStompBirth1.wav"
        private group grp=CreateGroup()
        private integer dbIndex=-1
        private timer time=CreateTimer()
        private unit tempCaster
        private unpauseDat array unpauseDB
        private real tempCasterX
        private real tempCasterY
    endglobals

    private function f takes nothing returns boolean
        local real distX
        local real distY
        local real fX
        local real fY
        local real power
        local unit filter=GetFilterUnit()
        if IsUnitEnemy(filter,GetOwningPlayer(tempCaster)) and GetUnitState(filter,UNIT_STATE_LIFE)>=1 then
            set fX=GetUnitX(filter)
            set fY=GetUnitY(filter)
            set distX=fX-tempCasterX
            set distY=fY-tempCasterY
            set power=(SHOUTRANGE-SquareRoot(distX*distX+distY*distY))/SHOUTRANGE*MAXSHOUTPOWER
            call Knockback_add(filter,power,Atan2(fY-tempCasterY,fX-tempCasterX),bj_PI/4)
        endif
        set filter=null
        return false
    endfunction
    
    private function p takes nothing returns nothing
        local unpauseDat tempDat
        local integer index=0
        loop
            exitwhen index>dbIndex
            set tempDat=unpauseDB[index]
            set tempDat.timeLeft=tempDat.timeLeft-FIDELITY
            if tempDat.timeLeft<=0 then
                call UnitAddAbility(tempDat.u,IMPALEID)
                call PauseUnit(tempDat.u,false)
                call SetUnitTimeScale(tempDat.u,1)
                set tempCasterX=GetUnitX(tempDat.u)
                set tempCasterY=GetUnitY(tempDat.u)
                call helper_sound3d(SLAMSOUND,tempCasterX,tempCasterY,GetUnitFlyHeight(tempDat.u))
                set tempCaster=tempDat.u
                call GroupEnumUnitsInRange(grp,tempCasterX,tempCasterY,SHOUTRANGE,Filter(function f))
                set unpauseDB[index]=unpauseDB[dbIndex]
                set dbIndex=dbIndex-1
                if dbIndex==-1 then
                    call PauseTimer(time)
                endif
                call tempDat.destroy()
            endif
            set index=index+1
        endloop
    endfunction
    
    private function c takes nothing returns boolean
        local effect fx
        local unpauseDat tempDat
        if GetSpellAbilityId()==SHOUTID then
            set tempDat=unpauseDat.create()
            set tempDat.u=GetTriggerUnit()
            set tempDat.timeLeft=PAUSETIME
            set dbIndex=dbIndex+1
            set unpauseDB[dbIndex]=tempDat
            call UnitRemoveAbility(tempDat.u,IMPALEID)
            if dbIndex==0 then
                call TimerStart(time,FIDELITY,true,function p)
            endif
            set fx=AddSpecialEffect(SLAMMODEL,GetUnitX(tempDat.u),GetUnitY(tempDat.u))
            call DestroyEffect(fx)
            call SetUnitTimeScale(tempDat.u,2)
            call IssueImmediateOrder(tempDat.u,"stop")
            call PauseUnit(tempDat.u,true)
            call SetUnitAnimationByIndex(tempDat.u,3)
        endif
        set fx=null
        return false
    endfunction
    
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope

impale
JASS:
scope impale initializer i
    private struct impaleDat
        unit caster
        unit target
        integer steps=0
    endstruct
    
    globals
        private constant integer IMPALEID               ='A00F'
        private constant integer SECONDARYTIME          =R2I(.7/.03)
        private constant real CASTERSECONDARYLAUNCHPOWER=20.
        private constant real CASTERSECONDARYTRAJECTORY =3*bj_PI/8
        private constant real CASTERTIMESCALE           =1.05
        private constant real DAMAGE                    =200.
        private constant real INITIALLAUNCHPOWER        =30.
        private constant real PAUSEDURATION             =1.1
        private constant real TARGETSECONDARYLAUNCHPOWER=60.
        private constant real TARGETSECONDARYTRAJECTORY =11*bj_PI/8
        private constant string STRIKESOUNDSTRING       ="Sound\\Units\\Combat\\MetalHeavyChopFlesh1.wav"
        private constant string WARSTOMPSTRING          ="Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl"
        private effect fx
        private impaleDat array impaleDB
        private integer dbIndex=-1
        private timer time=CreateTimer()
    endglobals
    
    private function p takes nothing returns nothing
        local impaleDat tempDat
        local integer index=0
        local real facing
        loop
            exitwhen index>dbIndex
            set tempDat=impaleDB[index]
            set tempDat.steps=tempDat.steps+1
            if tempDat.steps==SECONDARYTIME then
                set facing=GetUnitFacing(tempDat.caster)*bj_DEGTORAD
                call Knockback_add(tempDat.caster,CASTERSECONDARYLAUNCHPOWER,facing+bj_PI,CASTERSECONDARYTRAJECTORY)
                call Knockback_add(tempDat.target,TARGETSECONDARYLAUNCHPOWER,facing+bj_PI,TARGETSECONDARYTRAJECTORY)
                call helper_sound3d(STRIKESOUNDSTRING,GetUnitX(tempDat.caster),GetUnitY(tempDat.caster),GetUnitFlyHeight(tempDat.caster))
                call UnitDamageTarget(tempDat.caster,tempDat.target,DAMAGE,true,false,ATTACK_TYPE_HERO,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
                call SetUnitTimeScale(tempDat.caster,1.)
                set impaleDB[index]=impaleDB[dbIndex]
                set dbIndex=dbIndex-1
                call tempDat.destroy()
                if dbIndex==-1 then
                    call PauseTimer(time)
                endif
            endif
            set index=index+1
        endloop
    endfunction
    
    private function c takes nothing returns boolean
        local impaleDat tempDat
        local real facing
        if GetSpellAbilityId()==IMPALEID then
            set tempDat=impaleDat.create()
            set tempDat.caster=GetTriggerUnit()
            set tempDat.target=GetSpellTargetUnit()
            set facing=GetUnitFacing(tempDat.caster)
            set fx=AddSpecialEffect(WARSTOMPSTRING,GetUnitX(tempDat.caster),GetUnitY(tempDat.caster))
            call DestroyEffect(fx)
            call Knockback_add(tempDat.target,INITIALLAUNCHPOWER,facing,bj_PI/2)
            call Knockback_add(tempDat.caster,INITIALLAUNCHPOWER,facing,bj_PI/2)
            call IssueImmediateOrder(tempDat.caster,"stop")
            call IssueImmediateOrder(tempDat.target,"stop")
            call units_timedPause(tempDat.caster,PAUSEDURATION,"set")
            call units_timedPause(tempDat.target,PAUSEDURATION,"set")
            call SetUnitTimeScale(tempDat.caster,CASTERTIMESCALE)
            call SetUnitAnimationByIndex(tempDat.caster,3)
            set dbIndex=dbIndex+1
            set impaleDB[dbIndex]=tempDat
            if dbIndex==0 then
                call TimerStart(time,.03,true,function p)
            endif
        endif
        return false
    endfunction
    
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope

Knockback
JASS:
library Knockback requires IsTerrainWalkable, units, helper
    private struct knockDat
        unit u
        real xOffs
        real yOffs
        real zOffs
    endstruct
    
    globals
        private constant integer CROWID='Arav'
        private constant integer STUNID='A00N'
        private constant real BOUNCECOEFFICIENT=.4
        private constant real FRICTION=.15
        private constant real GRAVITY=1.375
        private constant real MAXZVELOCITYTOBOUNCE=-10.
        private constant real MINFLYHEIGHT=5.
        private constant real MINFORKNOCKBACK=1.
        private constant real MINFORSTUN=5.
        private constant real MINFRICTIONFOREFFECTSQUARED=9.
        private constant real MINZVELOCITYTOBECOMEAIRBORNE=5.
        private constant string FRICTIONMODEL="Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
        private constant string STUNORDERSTRING="thunderbolt"
        private effect fx
        private integer dbIndex=-1
        private knockDat array knockDB
        private timer time=CreateTimer()
    endglobals
    
    private function p takes nothing returns nothing
        local integer index=0
        local real flyHeight
        local real unitX
        local real unitY
        local real heightDifference
        local real newX
        local real newY
        local real vel2d
        local knockDat tempDat
        loop
            exitwhen index>dbIndex
            set tempDat=knockDB[index]
            set unitX=GetUnitX(tempDat.u)
            set unitY=GetUnitY(tempDat.u)
            set newX=unitX+tempDat.xOffs
            set newY=unitY+tempDat.yOffs
            set flyHeight=GetUnitFlyHeight(tempDat.u)
            set vel2d=(tempDat.xOffs*tempDat.xOffs+tempDat.yOffs*tempDat.yOffs)
            if flyHeight<MINFLYHEIGHT then
                if IsTerrainWalkable(newX,newY) then
                    call SetUnitX(tempDat.u,unitX+tempDat.xOffs)
                    call SetUnitY(tempDat.u,unitY+tempDat.yOffs)
                    set tempDat.xOffs=tempDat.xOffs*(1-FRICTION)
                    set tempDat.yOffs=tempDat.yOffs*(1-FRICTION)
                    call SetUnitMoveSpeed(tempDat.u,GetUnitDefaultMoveSpeed(tempDat.u))
                    if tempDat.xOffs*tempDat.yOffs>MINFRICTIONFOREFFECTSQUARED then
                        set fx=AddSpecialEffect(FRICTIONMODEL,unitX,unitY)
                        call DestroyEffect(fx)
                    endif
                else
                    set tempDat.xOffs=0
                    set tempDat.yOffs=0
                    if vel2d>MINFORSTUN then
                        call UnitAddAbility(units_globalDummy,STUNID)
                        call IssueTargetOrder(units_globalDummy,STUNORDERSTRING,tempDat.u)
                        call UnitRemoveAbility(units_globalDummy,STUNID)
                    endif
                endif
                if tempDat.zOffs<MAXZVELOCITYTOBOUNCE then
                    set tempDat.zOffs=tempDat.zOffs*-1.*BOUNCECOEFFICIENT
                endif
                if tempDat.zOffs>MINZVELOCITYTOBECOMEAIRBORNE then
                    call SetUnitFlyHeight(tempDat.u,flyHeight+tempDat.zOffs,0)
                    set tempDat.zOffs=tempDat.zOffs-GRAVITY
                endif
            else
                set tempDat.zOffs=tempDat.zOffs-GRAVITY
                set heightDifference=helper_getZ(newX,newY)-helper_getZ(unitX,unitY)
                call SetUnitFlyHeight(tempDat.u,flyHeight+tempDat.zOffs-heightDifference,0)
                call SetUnitX(tempDat.u,newX)
                call SetUnitY(tempDat.u,newY)
                call SetUnitMoveSpeed(tempDat.u,0)
            endif
            if vel2d<MINFORKNOCKBACK and tempDat.zOffs>MAXZVELOCITYTOBOUNCE and tempDat.zOffs<-1*MAXZVELOCITYTOBOUNCE and flyHeight<MINFLYHEIGHT then
                set knockDB[index]=knockDB[dbIndex]
                set dbIndex=dbIndex-1
                call SetUnitFlyHeight(tempDat.u,0,0)
                call SetUnitMoveSpeed(tempDat.u,GetUnitDefaultMoveSpeed(tempDat.u))
                call tempDat.destroy()
                if dbIndex<0 then
                    call PauseTimer(time)
                endif
            endif
            set index=index+1
        endloop
    endfunction
    
    private function getUnitIndexFromStack takes unit u returns integer
        local integer index=0
        local integer returner=-1
        local knockDat tempDat
        loop
            exitwhen index>dbIndex or returner!=-1
            set tempDat=knockDB[index]
            if tempDat.u==u then
                set returner=index
            endif
            set index=index+1
        endloop
        return returner
    endfunction
    
    public function add takes unit u, real power, real direction, real trajectory returns nothing
        local integer index=getUnitIndexFromStack(u)
        local knockDat tempDat
        if index==-1 then
            set tempDat=knockDat.create()
            set tempDat.u=u
            set tempDat.xOffs=power*Cos(direction)*Cos(trajectory)
            set tempDat.yOffs=power*Sin(direction)*Cos(trajectory)
            set tempDat.zOffs=power*Sin(trajectory)
            set dbIndex=dbIndex+1
            set knockDB[dbIndex]=tempDat
            call UnitAddAbility(tempDat.u,CROWID)
            call UnitRemoveAbility(tempDat.u,CROWID)
            if dbIndex==0 then
                call TimerStart(time,.03,true,function p)
            endif
        else
            set tempDat=knockDB[index]
            set tempDat.xOffs=tempDat.xOffs+power*Cos(direction)*Cos(trajectory)
            set tempDat.yOffs=tempDat.yOffs+power*Sin(direction)*Cos(trajectory)
            set tempDat.zOffs=tempDat.zOffs+power*Sin(trajectory)
        endif
    endfunction
endlibrary

Let me know if there's something more you need to see.

The player I did it with said he "pressed e, then r about 1 second later"

e is an immediate cast and r is a targeted cast.

He said he also averaged a ping of 250ms to a bot on his server, and I was playing across the atlantic from him.... So imagine about 500ms ping.

Edit - to clarify, e is the war slam (aoe spell) and r is the impale
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
Technically there should be an instant where the ability isn't yet removed, but I can't see how a player could hope to achieve that and I doubt a computer could. Assuming the shout code is actually firing and the ability is actually being removed (but still somehow being cast), the easiest fix seems to be having a catch in the impale code.

You should probably avoid removing/adding the ability anyways since it will have undesirable side-effects, so the best solution definitely seems like my rather band-aidey suggestion assuming the cause of this issue remains a mystery.

--

Seeing as the shout doesn't actually do anything until the time expires (and all the stuff in c seems to be about preventing further actions and playing special effects), shouldn't you just be using an ability with a cast time? Channel is good for this.
 
Hi PurplePoot,

Sorry for the very late reply - I was quite busy for awhile and when I did have time to work on this I always ran into issues.

the easiest fix seems to be having a catch in the impale code

What do you mean by that exactly? Do I have to create a condition in the impale spell which checks to verify the unit isn't already casting war slam?

I tried re-creating the spell to use channel but two new problems arise. I can't make the slam animation occur using Art - Animation Names (attack,slam and spell,slam don't work). My attempt to fix this was to create a second trigger which registered spell cast, but that didn't work either because with no combination of Data - Art Duration, Data - Follow Through Time, Stats - Casting Time, and Stats - Duration was I able to get a delay between the two triggers. Additionally, even if I can play the correct animation with Art - Animation Names, one of the special effects needs to play immediately on cast.

Let me know if you want to see the script or the channel object data I last tested with.

And thanks again,
 
Level 6
Joined
Jun 19, 2010
Messages
143
I am trying to read your script, but my knowledge of jass is still limited. I have a difficulty distinguishing & understanding what struct, scope & library uses for. Can you briefly explain me the meaning of using them respectively? then refer me some tutorials on making, working & using them or each of them. Although I know there is some ready-made libraries and to use them, just follow the instruction to import them into a map but....
Thank you very much.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Try to reverse this :

JASS:
 call IssueImmediateOrder(tempDat.u,"stop")
 call PauseUnit(tempDat.u,true)

Most of time, just giving the order stop work, but i remember that in the other cases i used pause/stop/unpause.
Yes i know the unit is being paused for a moment there, i'm just saying how i stop a received order properly.

Probably nothing revelant, but instead of "stop" you could use the order 851973 ("stun order).
 
Try to reverse this :

JASS:
 call IssueImmediateOrder(tempDat.u,"stop")
 call PauseUnit(tempDat.u,true)

Most of time, just giving the order stop work, but i remember that in the other cases i used pause/stop/unpause.
Yes i know the unit is being paused for a moment there, i'm just saying how i stop a received order properly.

Probably nothing revelant, but instead of "stop" you could use the order 851973 ("stun order).

Hi Troll-Brain,

Thanks for the reply, that seems logical that the pause would be instant and that the stop order should come next, but unfortunately that didn't fix the issue. Computer players (and presumably humans with high latency) are still able to cast both spells at once.

I also tried using the order you said. Is it an immediate order?

JASS:
call IssueImmediateOrderById(tempDat.u,STUNORDER)

(Where STUNORDER is 851973 as you said)

If so, that also isn't fixing the issue.

Would it be helpful for me to get a video of what's happening?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Technically the stun order is issued when an unit is being paused or stunned (and it wasn't already stunned/paused).
For the first it's a point order, while the second is a target order.

You are not supposed to use this "hidden" order, even more with an immediate order, but i've tried plenty of times, seems to work fine.
And if the unit had a previous immediate order, such as "holdposition", it keeps it.
It's something cool for forbid orders, it can even be more easily filtered than "stop", since there is no way that an immediate order "815973" is not given by trigger.

Now, about your problem, a video wouldn't hurt, it could even help us to guess what's happening.
 
Technically the stun order is issued when an unit is being paused or stunned (and it wasn't already stunned/paused).
For the first it's a point order, while the second is a target order.

You are not supposed to use this "hidden" order, even more with an immediate order, but i've tried plenty of times, seems to work fine.
And if the unit had a previous immediate order, such as "holdposition", it keeps it.
It's something cool for forbid orders, it can even be more easily filtered than "stop", since there is no way that an immediate order "815973" is not given by trigger.

So should I try it with target order too? I'm a bit confused as to what you mean.
Now, about your problem, a video wouldn't hurt, it could even help us to guess what's happening.

I'm having trouble getting xFire video to work right now but here's a direct download -

https://docs.google.com/open?id=0B5p-Ruo7uA87UW5fZ1FjVzBRbVNPWUFFdEExMDBNdw
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
No, keep the target order.
And if you're not confident, use "stop" instead, that wouldn't matter here.

If you want a sample where the stun better is better than the stop order, make a trigger which forbid point order "move", and before moving the unit give him the order "holdposition", then the move order.

Unfortunately it seems that was the brown player concerned by the bug, right ?
I mean we don't see his point of view.
 
No, keep the target order.
And if you're not confident, use "stop" instead, that wouldn't matter here.

Keep the target order? you mean the current one? Currently it's an immediate order. My question was - should I try changing the immediate order to a target order?

Unfortunately it seems that was the brown player concerned by the bug, right ?
I mean we don't see his point of view.

Nope, that's PLAYER_NEUTRAL_AGGRESSIVE. We can't see his point of view because he's a bot.
 
Have you tried to disable the ability, instead of remove/add ?

I assume you mean using the the SetPlayerAbilityAvailable() native?

I just tried it and it didn't work.

I've recorded another video from within the replay viewer so I could keep the screen centered on the neutral hostile hero (skip to 30 seconds).

This is the unconverted video recorded with xfire so if it is too large or you're unable to play it on your system I can upload the converted version.

https://docs.google.com/open?id=0B5p-Ruo7uA87aXJPVlJlRUVRSjJSdERHLWtEdVZlQQ
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Invalid link for the video.

Hmm, and you say that even a real human can abuse it ?

Make sure the unit is not unpaused before the end by an other code. (display all orders ids issued, i'm not talking about strings, integers, but with strings together for convenience)

Also, that should be the same, but try to disable the ability with an unit requirement for the spell.
 
Here is the mp4 version - should work:

https://docs.google.com/open?id=0B5p-Ruo7uA87V3QwUEE3Sm9RSkdNWFhrSmRIV0oyUQ

Yes, I played from Europe on the US East server with a player from western canada, so he had a tremendous lag and somehow that allowed him to "pressed e, then r about 1 second later"

(display all order ids)

You mean with a trigger like -

TriggerRegisterAnyUnitEventBJ(EVENT_PLAYER_UNIT_SPELL_EFFECT)
BJDebugmsg(GetSpellAbilityId())

?

I'm not sure how that will "make sure the unit is not unpaused before the end by another code"
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
No i mean print orders (point/no target/target order events).
Because if i'm not wrong when you unpause an unit, an order is issued, no ?

You could also use a hook with a display text on PauseUnit why not.

For the video, what i meant about the "point of view" was in fact the unit interface, no more, no less).

You got what i meant about disable the ability through an unit requirement ?

Also, an other way to disable an ability is with a dummy chanel ability which use the field "disable all other abilites", or something like that.
IIRC it disable even 'Aatk' and 'Amov' abilities though.

Just in case it's a problem of ui refresh (unit interface), you can add/remove a dummy ability.
For example the xp bar is not refresh quickly until you do that this add/remove.
Again i'm just saying all things i have in mind that could be related to your problem, even if it's unlikely.

And finally if none of these silly workaround work, you can still avoid the impale trigger to run while it shouldn't be.
In fact, that should be the best solution (unless the spell icon is still displayed, but really i can't guess more, and the bug will still be fixed)

Oh, and one more thing, some spells don't stop order, like windwalk.
So technically you can have several spells being cast as the same time.
 
Last edited:
No i mean print orders (point/no target/target order events).
Because if i'm not wrong when you unpause an unit, an order is issued, no ?

I understand now, but hook does the same thing so I've used that instead.

You could also use a hook with a display text on PauseUnit why not.

Now we're getting somewhere.

bTSxB.jpg


It looks like the neutral unit is really waiting that .45 seconds before it uses the second ability, and somehow it magically has the precision to target a unit that I thought would (surely) be out of range after .45 seconds.

I'm not sure what to do at this point to get rid of the issue. I suppose bots are inherently able to abuse this.

You got what i meant about disable the ability through an unit requirement ?

I didn't, but now I understand. Anyway the hook just showed that it's unnecessary.

And finally if none of these silly workaround work, you can still avoid the impale trigger to run while it shouldn't be.
In fact, that should be the best solution (unless the spell icon is still displayed, but really i can't guess more, and the bug will still be fixed)

I think this might be my best bet. I can leave the impale ability disabled a little longer after the war stomp is cast.

Oh, and one more thing, some spells don't stop order, like windwalk.
So technically you can have several spells being cast as the same time.

I've tested this by trying to queue impale after war stomp but it doesn't work. war stomp resets the queue.

Edit - I've fixed the issue by introducing a follow-through time. Here's the new "war slam" code -

JASS:
scope warSlam initializer i
    private struct unpauseDat
        unit u
        real timeLeft
        boolean attacked
    endstruct
    
    globals
        private constant integer SHOUTID='A00R'
        private constant integer IMPALEID='A00F'
        private constant real AVERAGEUNITHEIGHT=60.
        private constant real FIDELITY=.05
        private constant real MAXSHOUTPOWER=40.
        private constant real PAUSETIME=.45
        private constant real FOLLOWTHROUGHTIME=.2
        private constant real SHOUTRANGE=600.
        private constant string SLAMMODEL="Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
        private constant string SLAMSOUND="Units\\Orc\\HeroTaurenChieftain\\WarStompBirth1.wav"
        private group grp=CreateGroup()
        private integer dbIndex=-1
        private timer time=CreateTimer()
        private unit tempCaster
        private unpauseDat array unpauseDB
        private real tempCasterX
        private real tempCasterY
        private real tempCasterZ
    endglobals

    private function f takes nothing returns boolean
        local real distX
        local real distY
        local real distZ
        local real fX
        local real fY
        local real fZ
        local real power
        local real distXY
        local unit filter=GetFilterUnit()
        if IsUnitEnemy(filter,GetOwningPlayer(tempCaster)) and GetUnitState(filter,UNIT_STATE_LIFE)>=1 then
            set fX=GetUnitX(filter)
            set fY=GetUnitY(filter)
            set fZ=GetUnitFlyHeight(filter)+helper_getZ(fX,fY)+AVERAGEUNITHEIGHT
            set distZ=fZ-tempCasterZ
            set distX=fX-tempCasterX
            set distY=fY-tempCasterY
            set power=(SHOUTRANGE-SquareRoot(distX*distX+distY*distY+distZ*distZ))/SHOUTRANGE*MAXSHOUTPOWER
            set distXY=SquareRoot(distX*distX+distY*distY)
            call Knockback_add(filter,power,Atan2(fY-tempCasterY,fX-tempCasterX),Atan2(distZ,distXY))
        endif
        set filter=null
        return false
    endfunction
    
    private function p takes nothing returns nothing
        local unpauseDat tempDat
        local integer index=0
        loop
            exitwhen index>dbIndex
            set tempDat=unpauseDB[index]
            set tempDat.timeLeft=tempDat.timeLeft-FIDELITY
            if tempDat.timeLeft<=0 and tempDat.attacked==false then
                set tempDat.attacked=true
                set tempCasterX=GetUnitX(tempDat.u)
                set tempCasterY=GetUnitY(tempDat.u)
                set tempCasterZ=GetUnitFlyHeight(tempDat.u)+helper_getZ(tempCasterX,tempCasterY)
                call helper_sound3d(SLAMSOUND,tempCasterX,tempCasterY,GetUnitFlyHeight(tempDat.u))
                set tempCaster=tempDat.u
                call GroupEnumUnitsInRange(grp,tempCasterX,tempCasterY,SHOUTRANGE,Filter(function f))
            endif
            if tempDat.timeLeft<=(0-FOLLOWTHROUGHTIME) and tempDat.attacked then
                call UnitAddAbility(tempDat.u,IMPALEID)
                call PauseUnit(tempDat.u,false)
                call SetUnitTimeScale(tempDat.u,1)
                set unpauseDB[index]=unpauseDB[dbIndex]
                set dbIndex=dbIndex-1
                if dbIndex==-1 then
                    call PauseTimer(time)
                endif
                call tempDat.destroy()
            endif
            set index=index+1
        endloop
    endfunction
    
    private function c takes nothing returns boolean
        local effect fx
        local unpauseDat tempDat
        if GetSpellAbilityId()==SHOUTID then
            set tempDat=unpauseDat.create()
            set tempDat.u=GetTriggerUnit()
            set tempDat.attacked=false
            set tempDat.timeLeft=PAUSETIME
            set dbIndex=dbIndex+1
            set unpauseDB[dbIndex]=tempDat
            call UnitRemoveAbility(tempDat.u,IMPALEID)
            if dbIndex==0 then
                call TimerStart(time,FIDELITY,true,function p)
            endif
            set fx=AddSpecialEffect(SLAMMODEL,GetUnitX(tempDat.u),GetUnitY(tempDat.u))
            call DestroyEffect(fx)
            call SetUnitTimeScale(tempDat.u,2)
            call IssueImmediateOrder(tempDat.u,"stop")
            call PauseUnit(tempDat.u,true)
            call SetUnitAnimationByIndex(tempDat.u,3)
        endif
        set fx=null
        return false
    endfunction
    
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope

Thanks again to everyone who helped.
 
Last edited:
So you think in my follow through time, instead of extending the duration of the pause, instead to extend the duration of impale unavailability?

That makes sense and might look better. I'll try it out and see which I prefer.

Thanks again.

Edit - I've moved the "unpause unit" part up in the previous if block and it still works properly (now the follow through time is only for keeping impale disabled for an extra brief duration)

Here's the new script -

JASS:
scope warSlam initializer i
    private struct unpauseDat
        unit u
        real timeLeft
        boolean attacked
    endstruct
    
    globals
        private constant integer SHOUTID='A00R'
        private constant integer IMPALEID='A00F'
        private constant real AVERAGEUNITHEIGHT=60.
        private constant real FIDELITY=.05
        private constant real MAXSHOUTPOWER=40.
        private constant real PAUSETIME=.45
        private constant real FOLLOWTHROUGHTIME=.2
        private constant real SHOUTRANGE=600.
        private constant string SLAMMODEL="Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
        private constant string SLAMSOUND="Units\\Orc\\HeroTaurenChieftain\\WarStompBirth1.wav"
        private group grp=CreateGroup()
        private integer dbIndex=-1
        private timer time=CreateTimer()
        private unit tempCaster
        private unpauseDat array unpauseDB
        private real tempCasterX
        private real tempCasterY
        private real tempCasterZ
    endglobals

    private function f takes nothing returns boolean
        local real distX
        local real distY
        local real distZ
        local real fX
        local real fY
        local real fZ
        local real power
        local real distXY
        local unit filter=GetFilterUnit()
        if IsUnitEnemy(filter,GetOwningPlayer(tempCaster)) and GetUnitState(filter,UNIT_STATE_LIFE)>=1 then
            set fX=GetUnitX(filter)
            set fY=GetUnitY(filter)
            set fZ=GetUnitFlyHeight(filter)+helper_getZ(fX,fY)+AVERAGEUNITHEIGHT
            set distZ=fZ-tempCasterZ
            set distX=fX-tempCasterX
            set distY=fY-tempCasterY
            set power=(SHOUTRANGE-SquareRoot(distX*distX+distY*distY+distZ*distZ))/SHOUTRANGE*MAXSHOUTPOWER
            set distXY=SquareRoot(distX*distX+distY*distY)
            call Knockback_add(filter,power,Atan2(fY-tempCasterY,fX-tempCasterX),Atan2(distZ,distXY))
        endif
        set filter=null
        return false
    endfunction
    
    private function p takes nothing returns nothing
        local unpauseDat tempDat
        local integer index=0
        loop
            exitwhen index>dbIndex
            set tempDat=unpauseDB[index]
            set tempDat.timeLeft=tempDat.timeLeft-FIDELITY
            if tempDat.timeLeft<=0 and tempDat.attacked==false then
                call PauseUnit(tempDat.u,false)
                call SetUnitTimeScale(tempDat.u,1)
                set tempDat.attacked=true
                set tempCasterX=GetUnitX(tempDat.u)
                set tempCasterY=GetUnitY(tempDat.u)
                set tempCasterZ=GetUnitFlyHeight(tempDat.u)+helper_getZ(tempCasterX,tempCasterY)
                call helper_sound3d(SLAMSOUND,tempCasterX,tempCasterY,GetUnitFlyHeight(tempDat.u))
                set tempCaster=tempDat.u
                call GroupEnumUnitsInRange(grp,tempCasterX,tempCasterY,SHOUTRANGE,Filter(function f))
            endif
            if tempDat.timeLeft<=(0-FOLLOWTHROUGHTIME) and tempDat.attacked then
                call UnitAddAbility(tempDat.u,IMPALEID)
                set unpauseDB[index]=unpauseDB[dbIndex]
                set dbIndex=dbIndex-1
                if dbIndex==-1 then
                    call PauseTimer(time)
                endif
                call tempDat.destroy()
            endif
            set index=index+1
        endloop
    endfunction
    
    private function c takes nothing returns boolean
        local effect fx
        local unpauseDat tempDat
        if GetSpellAbilityId()==SHOUTID then
            set tempDat=unpauseDat.create()
            set tempDat.u=GetTriggerUnit()
            set tempDat.attacked=false
            set tempDat.timeLeft=PAUSETIME
            set dbIndex=dbIndex+1
            set unpauseDB[dbIndex]=tempDat
            call UnitRemoveAbility(tempDat.u,IMPALEID)
            if dbIndex==0 then
                call TimerStart(time,FIDELITY,true,function p)
            endif
            set fx=AddSpecialEffect(SLAMMODEL,GetUnitX(tempDat.u),GetUnitY(tempDat.u))
            call DestroyEffect(fx)
            call SetUnitTimeScale(tempDat.u,2)
            call IssueImmediateOrder(tempDat.u,"stop")
            call PauseUnit(tempDat.u,true)
            call SetUnitAnimationByIndex(tempDat.u,3)
        endif
        set fx=null
        return false
    endfunction
    
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
You could still create an ability per player (assuming a player can't have several units with this ability).
Also i don't remember if hide/unhide an unit work for this unit requirement thing.
If you use that, place the dummy unit outside the playable map area, but not outside the whole map (GetWorldBounds), else it will crash soon or later.

But you're right i have not thought about MUI.
 
Status
Not open for further replies.
Top