HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Producing a Cursor

08-17-2009, 03:37 AM#1
Pyrogasm
So for my Race Contest #2 submission, I need a way to produce a cursor on the screen for a player, to which end I devised a library. The library produces a cursor for a specific player and a specific unit, then executes functions onClick and onCancel.

That part works fine... sort of. The issue I'm having is the cancel detection trigger is firing when it shouldn't be.

What it boils down to is this:
  • There is a periodic timer that, each time it times out, forces the player to press the cancel detection ability
  • The system detects a click
  • The onClick function removes the cancel detection ability, pauses the unit, orders it to stop, and unpauses the unit, and then the timer used in the periodic function is paused and released.
  • Somehow, immediately after onClick is called, onCancel is also called... but only some of the time
What's perplexing about this is that:
  1. It only happens occasionally.
  2. The only way onCancel can be called is if the unit is successfully ordered to use the cancel ability... but after onClick the unit does NOT have the ability any more. Additionally, the timer that runs the periodic function should have stopped running, so even if the unit still had the ability it wouldn't be ordered to use it anyway.

So, I have no idea what's going on... but maybe someone else has an idea. Here's the code:
Collapse JASS:
library ShowCursor initializer Init requires TimerUtils
    globals
        private constant integer CANCEL_DETECTION_ABILITY = 'Cncl'
        private constant string CANCEL_DETECTION_HOTKEY = "l" //Must be lowercase
        private constant string CANCEL_DETECTION_ORDERSTRING = "starfall"
        private constant real CANCEL_PERIOD = 0.50

        private constant string CAST_ORDERSTRING = "flare"
        private constant string CAST_HOTKEY = "i" //Must be lowercase


        constant integer CURSOR_TYPE_GROUND = 0 //Define your own types here
        constant integer CURSOR_TYPE_UNIT = 1
        constant integer CURSOR_TYPE_UNIT_OR_GROUND = 2
        constant integer CURSOR_TYPE_AOE_100 = 3

        public integer array CursorTypeAbilities //Don't touch this
    endglobals

    //! textmacro RegisterCastType takes NAME, ABILITY_ID
        set CursorTypeAbilities[$NAME$] = '$ABILITY_ID$'
    //! endtextmacro


    private function CursorInit takes nothing returns nothing
        //! runtextmacro RegisterCastType("CURSOR_TYPE_GROUND", "TGnd")
        //! runtextmacro RegisterCastType("CURSOR_TYPE_UNIT", "TUnt")
        //! runtextmacro RegisterCastType("CURSOR_TYPE_UNIT_OR_GROUND", "TUoG")
        //! runtextmacro RegisterCastType("CURSOR_TYPE_AOE_100", "T100")
    endfunction

    function interface CursorClick takes player sourcePlayer, unit sourceUnit, real targX, real targY, widget targWidget returns boolean
    function interface CursorCancel takes player sourcePlayer, unit sourceUnit returns nothing

    globals
        private trigger CancelTrig = null
        private trigger ClickTrig = null
        private keyword CursorData
        private CursorData array CDArray
    endglobals

    private struct CursorData
        unit Source = null
        player P
        CursorClick ClickFunc
        CursorCancel CancelFunc
        integer CursorType
        timer T = null
        boolean Ignore = false

        static method operator [] takes player P returns CursorData
            return CDArray[GetPlayerId(P)]
        endmethod
    endstruct


    private function OnCancelOrder takes nothing returns boolean
        local unit U = GetTriggerUnit()
        local CursorData C

        if GetIssuedOrderId() == OrderId(CANCEL_DETECTION_ORDERSTRING) and GetUnitAbilityLevel(U, CANCEL_DETECTION_ABILITY) > 0 then
            call BJDebugMsg("...cancel...")

            set C = CursorData[GetOwningPlayer(U)]

            call PauseTimer(C.T)
            call ReleaseTimer(C.T)
            set C.T = null

            call PauseUnit(U, true)
            call IssueImmediateOrder(U, "stop")
            call PauseUnit(U, false)

            call UnitRemoveAbility(U, CANCEL_DETECTION_ABILITY)
            call UnitRemoveAbility(U, CursorTypeAbilities[C.CursorType])
            call C.CancelFunc.execute(C.P, C.Source)
        endif

        set U = null
        return false
    endfunction

    private function OnClickOrder takes nothing returns boolean
        local unit U = GetTriggerUnit()
        local CursorData C = CursorData[GetOwningPlayer(U)]

        if GetIssuedOrderId() == OrderId(CAST_ORDERSTRING) and GetUnitAbilityLevel(U, CursorTypeAbilities[C.CursorType]) > 0 then
            call BJDebugMsg("...click...")

            call PauseTimer(C.T)
            call ReleaseTimer(C.T)
            set C.T = null

            call UnitRemoveAbility(C.Source, CANCEL_DETECTION_ABILITY)

            call PauseUnit(U, true)
            call IssueImmediateOrder(U, "stop")
            call PauseUnit(U, false)

            if C.ClickFunc.evaluate(C.P, C.Source, GetOrderPointX(), GetOrderPointY(), GetOrderTarget()) then
                call UnitRemoveAbility(C.Source, CursorTypeAbilities[C.CursorType])
            elseif GetLocalPlayer() == C.P then
                call ForceUIKey(CAST_HOTKEY)
            endif
        endif

        set U = null
        return false        
    endfunction

    private function PrematureCancel takes CursorData C returns nothing
        call PauseTimer(C.T)
        call ReleaseTimer(C.T)
        set C.T = null

        call UnitRemoveAbility(C.Source, CANCEL_DETECTION_ABILITY)
        call UnitRemoveAbility(C.Source, CursorTypeAbilities[C.CursorType])
        call C.CancelFunc.execute(C.P, C.Source)
    endfunction

    private function Periodic takes nothing returns nothing
        local CursorData C = CursorData(GetTimerData(GetExpiredTimer()))

        call BJDebugMsg("Periodic")

        if not C.Ignore then
            if GetWidgetLife(C.Source) < 0.406 then
               call PrematureCancel(C)
            else
                if GetLocalPlayer() == C.P then
                    call ForceUIKey(CANCEL_DETECTION_HOTKEY)
                endif
            endif
        else
            if GetLocalPlayer() == C.P then
                call ForceUIKey(CAST_HOTKEY)
            endif

            set C.Ignore = false
        endif
    endfunction

    function ShowCursor takes player whichPlayer, unit sourceUnit, integer cursorType, CursorClick clickFunc, CursorCancel cancelFunc, boolean overWrite returns boolean
        local CursorData C = CursorData[whichPlayer]

        if C.T == null then
            set C.T = NewTimer()
            set C.Source = sourceUnit
            set C.ClickFunc = clickFunc
            set C.CancelFunc = cancelFunc
            set C.CursorType = cursorType

            call SetTimerData(C.T, C)
        elseif not overWrite then
            return false
        endif

        if GetUnitAbilityLevel(sourceUnit, CANCEL_DETECTION_ABILITY) == 0 then
            call UnitAddAbility(sourceUnit, CANCEL_DETECTION_ABILITY)
            call UnitMakeAbilityPermanent(sourceUnit, true, CANCEL_DETECTION_ABILITY)
        endif

        if GetUnitAbilityLevel(sourceUnit, CursorTypeAbilities[cursorType]) == 0 then
            call UnitAddAbility(sourceUnit, CursorTypeAbilities[cursorType])
            call UnitMakeAbilityPermanent(sourceUnit, true, CursorTypeAbilities[cursorType])
            set C.Ignore = true
        elseif GetLocalPlayer() == whichPlayer then
            call ForceUIKey(CAST_HOTKEY)
        endif

        
        if GetLocalPlayer() == whichPlayer then
            call SelectUnit(sourceUnit, false)
            call SelectUnit(sourceUnit, true)
        endif

        call TimerStart(C.T, CANCEL_PERIOD, true, function Periodic)

        return true        
    endfunction

    private function Init takes nothing returns nothing
        local integer J = 0

        loop
            set CDArray[J] = CursorData.create()
            set CDArray[J].P = Player(J)
            set J = J+1
            exitwhen J >= 12
        endloop

        call CursorInit()

        set CancelTrig = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(CancelTrig, EVENT_PLAYER_UNIT_ISSUED_ORDER)
        call TriggerAddCondition(CancelTrig, Condition(function OnCancelOrder))


        set ClickTrig = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ClickTrig, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        call TriggerRegisterAnyUnitEventBJ(ClickTrig, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
        call TriggerAddCondition(ClickTrig, Condition(function OnClickOrder))
    endfunction
endlibrary

And attached is a testmap. Type "-" to initiate a cursor display. Every time you click a wisp should appear at the clicked location. Whenever you cancel the sequence (or the system detects a cancel), the wisps will disappear.
Attached Files
File type: w3xShowCursor.w3x (22.7 KB)
08-17-2009, 04:40 PM#2
Troll-Brain
Didn't really look what kind of stuff you do with force ui key, but if you try to pause an unit when it is casting a spell (before SPELL_EFFECT) when you unpause it the unit will try to recast it.
So be sure your spell is truly instant.

Also the unit get a point order (851973, Location(0.,0.)) when it is stunned/paused (and wasn't already stunned/paused)
08-19-2009, 10:32 AM#3
Pyrogasm
But it doesn't have the spell when it gets unpaused.... Also, the same issue occurs if I comment out the pausing/stop-ordering.
08-19-2009, 10:44 AM#4
Anachron
I tested it, it worked fine for me, I will check the code later, there are a few useful librarys I have at home.
08-19-2009, 10:48 AM#5
Pyrogasm
Try it a few times, you'll get a false cancel eventually.
08-19-2009, 10:50 AM#6
Anachron
No, not really, I tested it 5 minutes.

Anyway, checking code later, school is out now.
08-19-2009, 02:34 PM#7
Viikuna-
I tested it too and got false cancels all the time. Weird. Ill see later today if I understand any of that code.
08-19-2009, 02:50 PM#8
Alevice
Quote:
Originally Posted by Pyrogasm
So for my Race Contest #2 submission, I need a way to produce a cursor on the screen for a player

holy crap
08-19-2009, 03:47 PM#9
Troll-Brain
With the test map attached the wisps appear all the time on Location(0.,0.).
I don't have edited and saved the map.
And yes it also randomly fail.
08-19-2009, 05:12 PM#10
Pyrogasm
Yeah, I noticed the wisps appearing at (0,0). They used to appear properly, but something went wonky along the way and now they don't... I think it's the second issue you mentioned, Troll-Brain.
08-19-2009, 06:00 PM#11
Troll-Brain
Quote:
The only way onCancel can be called is if the unit is successfully ordered to use the cancel ability... but after onClick the unit does NOT have the ability any more. Additionally, the timer that runs the periodic function should have stopped running, so even if the unit still had the ability it wouldn't be ordered to use it anyway.

Maybe in some specific cases RemoveAbility isn't truly instant ?
08-19-2009, 06:03 PM#12
Pyrogasm
Oh...
You're probably right, since it takes a frame to add the ability, it probably takes one to remove it. I'll try disabling and see what happens then.