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

[Snippet] ClickCouple

Script is pretty self explanatory. This snippet provides an easy way to detect double click event. Credits to Azlier for orginal DoubleClick.

JASS:
/*****************************************************************************
*
*    ClickCouple v1.4.0.3
*       by Bannar
*
*    Detects unit double click event.
*
*    Credits to Azlier for original project. Thanks to Magtheridon96, Bribe and
*    all other hivers for helping me greatly during the development of this snippet.
*
******************************************************************************
*
*    Requirements:
*
*       RegisterPlayerUnitEvent by Bannar
*          hiveworkshop.com/threads/snippet-registerevent-pack.250266/
*
*    Optional requirements:
*
*       Table by Bribe
*          hiveworkshop.com/threads/snippet-new-table.188084/
*
******************************************************************************
*
*    Configurables:
*
*       constant real INTERVAL
*          Maximum delay between separate clicks to fire double click event.
*
******************************************************************************
*
*    Event API:
*
*       integer EVENT_PLAYER_DOUBLE_CLICK
*
*       Use RegisterNativeEvent or RegisterIndexNativeEvent for event registration.
*       GetNativeEventTrigger and GetIndexNativeEventTrigger provide access to trigger handles.
*
*
*       function GetDoubleClickingPlayer takes nothing returns player
*          Retrieves player who performed double click event.
*
*       function GetDoubleClickingPlayerId takes nothing returns integer
*          Returns index of event player.
*
*       function GetDoubleClickedUnit takes nothing returns unit
*          Retrieves unit which has been double clicked.
*
*****************************************************************************/
library ClickCouple requires RegisterPlayerUnitEvent optional Table

globals
    private constant real INTERVAL  = 0.30
endglobals

globals
    integer EVENT_PLAYER_DOUBLE_CLICK
endglobals

globals
    private timer clock = CreateTimer()
    private player eventPlayer = null
    private unit eventUnit = null
endglobals

function GetDoubleClickingPlayer takes nothing returns player
    return eventPlayer
endfunction

function GetDoubleClickingPlayerId takes nothing returns integer
    return GetPlayerId(eventPlayer)
endfunction

function GetDoubleClickedUnit takes nothing returns unit
    return eventUnit
endfunction

function GetEventClickingPlayer takes nothing returns player
    debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetEventClickingPlayer is obsolete, use GetDoubleClickingPlayer instead.")
    return GetDoubleClickingPlayer()
endfunction

function GetEventClickingPlayerId takes nothing returns integer
    debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetEventClickingPlayerId is obsolete, use GetDoubleClickingPlayerId instead.")
    return GetDoubleClickingPlayerId()
endfunction

function GetEventClickedUnit takes nothing returns unit
    debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetEventClickedUnit is obsolete, use GetDoubleClickedUnit instead.")
    return GetDoubleClickedUnit()
endfunction

function RegisterDoubleClickEvent takes player whichPlayer, code func returns nothing
    debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function RegisterDoubleClickEvent is obsolete, use RegisterIndexNativeEvent instead.")
    call RegisterIndexNativeEvent(GetPlayerId(whichPlayer), EVENT_PLAYER_DOUBLE_CLICK, func)
endfunction

function RegisterAnyDoubleClickEvent takes code func returns nothing
    debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function RegisterAnyDoubleClickEvent is obsolete, use RegisterNativeEvent instead.")
    call RegisterNativeEvent(EVENT_PLAYER_DOUBLE_CLICK, func)
endfunction

function GetDoubleClickEventTrigger takes integer playerId returns trigger
    debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetDoubleClickEventTrigger is obsolete, use GetIndexNativeEventTrigger instead.")
    return GetIndexNativeEventTrigger(playerId, EVENT_PLAYER_DOUBLE_CLICK)
endfunction

module ClickCoupleStruct
    static method operator clicker takes nothing returns player
        debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Method ClickCoupleStruct::clicker is obsolete, use GetEventClickingPlayer instead.")
        return GetEventClickingPlayer()
    endmethod

    static method operator clicked takes nothing returns unit
        debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Method ClickCoupleStruct::clicked is obsolete, use GetEventClickedUnit instead.")
        return GetEventClickedUnit()
    endmethod

    static if thistype.onDoubleClick.exists then
        private static method onDoubleClickEvent takes nothing returns nothing
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Module ClickCoupleStruct is obsolete, use RegisterNativeEvent directly instead.")
            static if thistype.filterPlayer.exists then
                if filterPlayer(GetEventClickingPlayer()) then
                    call thistype(GetEventClickingPlayerId()).onDoubleClick()
                endif
            else
                call thistype(GetEventClickingPlayerId()).onDoubleClick()
            endif
        endmethod

        private static method onInit takes nothing returns nothing
            call RegisterNativeEvent(EVENT_PLAYER_DOUBLE_CLICK, function thistype.onDoubleClickEvent)
        endmethod
    endif
endmodule

private function FireEvent takes player p, unit u returns nothing
    local player prevPlayer = eventPlayer
    local unit prevUnit = eventUnit
    local integer playerId = GetPlayerId(p)

    set eventPlayer = p
    set eventUnit = u

    call TriggerEvaluate(GetNativeEventTrigger(EVENT_PLAYER_DOUBLE_CLICK))
    if IsNativeEventRegistered(playerId, EVENT_PLAYER_DOUBLE_CLICK) then
        call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, EVENT_PLAYER_DOUBLE_CLICK))
    endif

    set eventPlayer = prevPlayer
    set eventUnit = prevUnit
    set prevPlayer = null
    set prevUnit = null
endfunction

private module ClickCoupleInit
static if LIBRARY_Table then
    static TableArray table
else
    static hashtable table = InitHashtable()
endif

    private static method onInit takes nothing returns nothing
        set EVENT_PLAYER_DOUBLE_CLICK = CreateNativeEvent()

static if LIBRARY_Table then
        set table = TableArray[bj_MAX_PLAYER_SLOTS]
endif
        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SELECTED, function thistype.onClick)
        call TimerStart(clock, 604800, false, null)
    endmethod
endmodule

private struct ClickCouple extends array
    static method onClick takes nothing returns nothing
        local player p = GetTriggerPlayer()
        local unit u = GetTriggerUnit()
        local integer id = GetPlayerId(p)
        local integer unitId = GetHandleId(u)

static if LIBRARY_Table then
        if table[id].unit.has(unitId) and table[id].unit[unitId] == u and /*
        */ (table[id].real[unitId] + INTERVAL) > TimerGetElapsed(clock) then
            call FireEvent(p, u)
            call table[id].flush()
        else
            set table[id].unit[unitId] = u
            set table[id].real[unitId] = TimerGetElapsed(clock)
        endif
else
        if HaveSavedHandle(table, id, unitId) and LoadUnitHandle(table, id, unitId) == u and /*
        */ (LoadReal(table, id, unitId) + INTERVAL) > TimerGetElapsed(clock) then
            call FireEvent(p, u)
            call FlushChildHashtable(table, id)
        else
            call SaveUnitHandle(table, id, unitId, u)
            call SaveReal(table, id, unitId, TimerGetElapsed(clock))
        endif
endif

        set p = null
        set u = null
    endmethod

    implement ClickCoupleInit
endstruct

endlibrary
Demo code:
JASS:
scope ClickCoupleDemo initializer Init

private function OnDoubleClick takes nothing returns nothing
    call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, GetPlayerName(GetDoubleClickingPlayer()) + " double clicked: " + GetUnitName(GetDoubleClickedUnit()))
endfunction

private function Init takes nothing returns nothing
    call RegisterNativeEvent(EVENT_PLAYER_DOUBLE_CLICK, function OnDoubleClick)
endfunction

endscope
 
Last edited:
Well, if you're using UnitUserData, not having UnitIndexer would make your system .. how should I put this.. Dis-functional :p

Oh and you don't have to inline TriggerRegisterAnyUnitEventBJ
You can use RegisterPlayerUnitEvent if speed is an issue ;o
Or you could make it optional :cgrin:

Event should be a requirement (required by UnitIndexer which is already required by this library)
 
I've inlined AnyEventBJ only because registering 16 players is differend than registering 12 user-able slots.

RegisterPlayerUnitEvent can be optional, it's usage proves to be great in many cases.
Having Event greatly improves library, but since there are 'static ifs' there is a choice left for user: use additional library or not. Issue is similar to TU -> simple libraries shouldn't force people to implement moar and moar stuff, not to mention that Nes's libraries aren't light on requirements.
 
What I'm trying to say is that you CAN'T use GetUnitUserData without UnitIndexer.
GetUnitUserData returns a value attached to a unit.
UnitIndexer takes advantage of this by setting that data to the index of the unit.
Using GetUnitUserData without the UnitIndexer will always return 0. (unless you want to inline your own indexer in this >.>)
 
Read:
JASS:
*       function GetDoubleClickedUnitId takes nothing returns integer
*          - returns user data of double clicked unit (usefull with Indexers)
I know that it gonna return 0 in each case when guy isn't using Jass nor GUI indexer. It's left for user (once again), meaby he sets indexes by his own. Afterall he doesn't have to use this feature if he decides not to use userdata function.
 
I never thought of that, lol. Thats good reason Maggy for using the BJ, although it must be funny detecting double click of neutral player - is it even possible? D;

Changes have been made, can't test the "neutral double click" issue right now, I'm going off.
Added RegistePlayerUnitEvent as additional optional requirement (to make you happy Mag) D;
 
Level 16
Joined
Aug 7, 2009
Messages
1,406
Module initializer > Struct Initializer.

Because it's fashionable. It started to become popular to make certain systems usable at map init, but hell yea, it's the current fashion, we have to write our systems like that even when it's completely useless. Extra script size FTW.

I'll take it back tho' if you record with Fraps the way you double click at map init. I'm actually kinda curious.
 
There is a reason why we use module initializers. It is performed before all others.

For example, if you try to register a double click event in a module initializer when the system is using a struct initializer, the double click event will return 0 so it won't end up being registered.

One possible proposal could be to ban module initializers, but that wouldn't make any sense; which is why we have to use module initializers to ensure functionality. :)
 
I've decided to **** it. D; Too many people told me: "use that, and that", so..
There is one version for those who doesn't care about amount of requirements and more about the speed, and one for those who just want quickly implement the library without being forced to use ton of other stuff (in such case, installation equals copy + paste).
 
Last edited by a moderator:
It is quite depressing to require modules for everything. When you think about it how many modules actually NEED to be there compared to how many are actually being used (all "pro" scripts nowadays).

I ran out of time, patience and community support for the LuckyParser project, but I suppose I could make a light light version which generates the ugliest vJass syntax for you.
 
1. Your way adds more verbosity to the user's API because they have to type Filter(function Name). I for one have made many syntax errors forgetting to wrap it in a "Filter()"

2. The user can "return nothing" with my way because pJass doesn't check the return type of codes, it just checks the return type of functions passed directly to Filter/Condition.
 
Personally I only use struct API when it makes the most sense to do so. The only time you NEED structs is for modules, regardless of how ugly the alternative might be.

In the case of your system you could eliminate the struct for all but the module and everything will be aces.

Also despite Nestharus' best intentions "readonly Event" is not a good idea at all because the event can still be destroyed/fired when it's not supposed to, by the user.
 
Level 6
Joined
Jun 20, 2011
Messages
249
the event can still be destroyed/fired when it's not supposed to, by the user.
And after that the user will come to the hive complaining why is the event being destroyed when he destroys it?
Making things readonly is just a formality, everything should/could be public and nothing would go wrong if the user's IQ is above 40.
 
That's different.

What they are saying is with private/readonly stuff -- most of it is a formality. Technically all private/readonly things could be public and if the user is not so dumb to try to access them or to set them, then everything's cool.

If the optimizer didn't have such a problem with concatenation then private things wouldn't be such a problem, but since they can either have two OR three underscores your capabilities are pretty limited. One of my codes has an ExecuteFunc and I can't make it private, so I gave it such a random name full of numbers and letters to make it obvious.
 
This can use one timer but not like how you're currently doing. It's currently expiring every 0.25 seconds but it only needs to be an always-running timer, and you just timestamp the elapsed time when the first click is pressed, and then timestamp it when the second click is pressed, and if that is lower than 0.25 then it's a double click.
 
There was a mistake within module struct of light version of ClickCouple. Seems none noticed, however, even if peps were using this lib, most of them (including me) were prefering function api instead.

Update! Extra space, better configurables, improved api, fixed bug within module. Api includes "trigger" versions of event registers, I dont even know why it wasnt added in the first place. On top of that, module api no longer collides with UnitIndexer's module struct - honestly, operators like "player" or "unit" are too generic anyways.

Check it out, before op gonna get updated:
JASS:
/*****************************************************************************
*
*    ClickCouple v1.3.1.5
*       by Bannar aka Spinnaker
*          Credits to Azlier for original project
*
*    Detects unit double click event.
*
******************************************************************************
*
*    Optional requirements:
*
*       Event by Nestharus
*          hiveworkshop.com/forums/jass-functions-413/snippet-event-186555
*
*       RegisterPlayerUnitEvent by Magtheridon96
*          hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*
******************************************************************************
*
*    private constant real PERIOD
*       maximum delay between separate clicks to fire double click event
*
*    private constant integer ANY_PLAYER
*       index of any player event
*
*    private constant boolean UNIT_INDEXER
*       if unit indexer is present, GetEventClickedUnitId will be implemented
*
******************************************************************************
*
*    struct ClickCouple:
*
*       static method register takes player p, code c returns nothing
*          registers double click event for player p
*
*       static method registerAny takes code c returns nothing
*          registers any double click event, no matter the circumstances
*
*       static method triggerRegister takes trigger t, player p returns nothing
*          trigger version of register method
*
*       static method triggerRegisterAny takes trigger t returns nothing
*          trigger version of registerAny method
*
*
*    Functions:
*
*       function GetEventClickingPlayer takes nothing returns player
*          returns player who performed double click event
*
*       function GetEventClickingPlayerId takes nothing returns integer
*          returns index of player who triggered the event
*
*       function GetEventClickedUnit takes nothing returns unit
*          retrives unit which has been double clicked
*
*       function RegisterDoubleClickEvent takes player p, code c returns nothing
*          registers double click event for player p
*
*       function RegisterAnyDoubleClickEvent takes code c returns nothing
*          registers any double click event, no matter the circumstances
*
*       function TriggerRegisterDoubleClickEvent takes trigger t, player p returns nothing
*          trigger version of RegisterDoubleClickEvent function
*
*       function TriggerRegisterAnyDoubleClickEvent takes trigger t returns nothing
*          trigger version of RegisterAnyDoubleClickEvent function
*
*
*    module ClickCoupleStruct:
*
*       Expects:    method onDoubleClick takes nothing returns nothing
*       Optional:   method filterPlayer takes nothing returns nothing
*
*       static method operator clicker takes nothing returns player
*          returns event player
*
*       static method operator clicked takes nothing returns unit
*          returns event unit
*
*****************************************************************************/
library ClickCouple uses /*
                 */ optional Event /*
                 */ optional RegisterPlayerUnitEvent

    globals
        private constant real PERIOD = 0.30
        private constant integer ANY_PLAYER = 16
        private constant boolean UNIT_INDEXER = false
    endglobals

    globals
        private timer clock = CreateTimer()
        private player eventPlayer = null
        private unit eventUnit = null
    endglobals

    struct ClickCouple extends array
        private unit clickedUnit
        private real timeStamp

    static if LIBRARY_Event then
        private Event evt
    else
        private trigger evt
        private static real evtCaller = -1
    endif

        private static method connect takes thistype this, code c returns nothing
            static if LIBRARY_Event then
                if ( this.evt == null ) then   
                    set this.evt = CreateEvent()
                endif
                call RegisterEvent(Condition(c), this.evt)
            else
                if ( this.evt == null ) then   
                    set this.evt = CreateTrigger()
                endif
                call TriggerAddCondition(this.evt, Condition(c))
            endif
        endmethod

        private static method connectTrigger takes thistype this, trigger t returns nothing
            static if LIBRARY_Event then
                call TriggerRegisterEvent(t, this.evt)
            else
                call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "evtCaller", EQUAL, this)
            endif
            return
        endmethod

        static method register takes player p, code c returns nothing
            call connect(GetPlayerId(p), c)
        endmethod

        static method registerAny takes code c returns nothing
            call connect(ANY_PLAYER, c)
        endmethod

        static method triggerRegister takes trigger t, player p returns nothing
            call connectTrigger(GetPlayerId(p), t)
        endmethod

        static method triggerRegisterAny takes trigger t returns nothing
            call connectTrigger(ANY_PLAYER, t)
        endmethod

        private static method onClick takes nothing returns boolean
            local player prevPlayer
            local unit prevUnit
            local thistype this = GetPlayerId(GetTriggerPlayer())

            if ( GetTriggerUnit() == this.clickedUnit ) and ( this.timeStamp + PERIOD > TimerGetElapsed(clock) ) then
                set prevPlayer = eventPlayer
                set prevUnit = eventUnit

                set eventPlayer = GetTriggerPlayer()
                set eventUnit = GetTriggerUnit()

                static if LIBRARY_Event then
                    call FireEvent(thistype(ANY_PLAYER).evt)
                    call FireEvent(this.evt)
                else
                    set evtCaller = this
                    set evtCaller = ANY_PLAYER
                    call TriggerEvaluate(thistype(ANY_PLAYER).evt)
                    call TriggerEvaluate(this.evt)
                    set evtCaller = -1
                endif

                set eventPlayer = prevPlayer
                set eventUnit = prevUnit
                set prevPlayer = null
                set prevUnit = null

                set this.clickedUnit = null
                set this.timeStamp = 0
            else
                set this.clickedUnit = GetTriggerUnit()
                set this.timeStamp = TimerGetElapsed(clock)
            endif

            return false
        endmethod

        private static method onInit takes nothing returns nothing
            static if LIBRARY_RegisterPlayerUnitEvent then
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SELECTED, function thistype.onClick)
            else
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SELECTED)
                call TriggerAddCondition(t, function thistype.onClick)
                set t = null
            endif

            call TimerStart(clock, 604800, false, null)
        endmethod
    endstruct

    function GetEventClickingPlayer takes nothing returns player
        return eventPlayer
    endfunction

    function GetEventClickingPlayerId takes nothing returns integer
        return GetPlayerId(eventPlayer)
    endfunction

    function GetEventClickedUnit takes nothing returns unit
        return eventUnit
    endfunction

static if UNIT_INDEXER then
    function GetEventClickedUnitId takes nothing returns unit
        return GetUnitId(eventUnit)
    endfunction
endif

    function RegisterDoubleClickEvent takes player p, code c returns nothing
        call ClickCouple.register(p, c)
    endfunction

    function RegisterAnyDoubleClickEvent takes code c returns nothing
        call ClickCouple.registerAny(c)
    endfunction

    function TriggerRegisterDoubleClickEvent takes trigger t, player p returns nothing
        call ClickCouple.triggerRegister(t, p)
    endfunction

    function TriggerRegisterAnyDoubleClickEvent takes trigger t returns nothing
        call ClickCouple.triggerRegisterAny(t)
    endfunction

    module ClickCoupleStruct
        static method operator clicker takes nothing returns player
            return GetEventClickingPlayer()
        endmethod

        static method operator clicked takes nothing returns unit
            return GetEventClickedUnit()
        endmethod

        static if thistype.onDoubleClick.exists then
            private static method onDClickEvent takes nothing returns nothing
                static if thistype.filterPlayer.exists then
                    if ( filterPlayer(clicker) ) then
                        call thistype(GetEventClickingPlayerId()).onDoubleClick()
                    endif
                else
                    call thistype(GetEventClickingPlayerId()).onDoubleClick()
                endif
            endmethod

            private static method onInit takes nothing returns nothing
                call RegisterAnyDoubleClickEvent(function thistype.onDClickEvent)
            endmethod
        endif
    endmodule

endlibrary
 
Update commited.

No api changes thus no compatibility problems. I've resignated from struct api posted in previous version (above). Library before update, have had register/registerForPlayer methods within ClickCouple struct, but just for internal use. It wasnt even listed in api, although it was bad of me not to privatize struct to prevent any interfecence.

If Event existed, events were declared as readonly (while triggers were private), which could also lead to some unintented use. Now its fixed. ClickCouple is basically static struct and it does not "extend" Event (no create/destroy methods relying on Event's instances) what gives another reason why those changes were commited.

Whatmore, RegisterAnyDoubleClickEvent vs ClickCouple.registerAny, clearly the former is more readable and better fits jass environment.
 
Hop hop.

I've realised that this:
JASS:
static if LIBRARY_Event then
    globals
        private integer array events
    endglobals
else
    globals
        private real caller = -1
        private trigger array triggers
    endglobals
endif
Doesn't do what it is suppose to do. So I've moved those into struct to force desired outcome :) Of course that led to some reorganization of code, however, that was interval part of script.
No api changes, nothing that could interfere with user outter code, so compatibility is kept. Everything for you guys :p

And yes, I've tested all possible compiled versions between commiting this update.
 
Updated from 1.3.3.8 to 1.4.0.0.

Now Wurst friendly:
- Removed TriggerRegisterAnyDoubleClickEvent function
- Removed TriggerRegisterVariableEvent related globals. FireEvent now uses TriggerEvaluate directly

- Removed UNIT_INDEXER private global, there is no need for messing with Indexers in ClickCouple
- Removed conditional function GetEventClickedUnitId
- No longer supports deprecated RegisterPlayerUnitEvent, use newer one found in RegisterNativeEvent pack
- Added EVENT_PLAYER_DOUBLE_CLICK global integer for event registration

Following are now obsolete and will be removed in the future:
- Entire module ClickCoupleStruct. Use function API instead, which serves the exact same purpose with less code involved. Modules should only be used for initialization and inheritance purposes.
- RegisterDoubleClickEvent function, deprecated in favor of RegisterIndexNativeEvent
- RegisterAnyDoubleClickEvent function, deprecated in favor of RegisterNativeEvent
- GetDoubleClickEventTrigger function, deprecated in favor of GetIndexNativeEventTrigger

Improvements:
- Natively supports 24-player mode due to RegisterNativeEvent
- Now properly fires double-click event when multiple units of the same type are nearby unit which becomes double-clicked. Previously in such circumstances event was not fired due to several units being selected at once (default behavior for Warcraft III interface).
One can wonder why none has reported this issue before :)
As a method of choice, hashtable has been used to make this possible. Consequently, Table has been added as an optional resource.

Updated demo code to reflect changes made.
Updated documentation.
Appended missing credits.

Edit:

Release 1.4.0.3. Following functions have been made obsolete:
- GetEventClickingPlayer in favor of GetDoubleClickingPlayer
- GetEventClickingPlayerId in favor of GetDoubleClickingPlayerId
- GetEventClickedUnit in favor of GetDoubleClickedUnit

Renamed internal constant PERIOD to INTERVAL.
Updated documentation and demo code to reflect the change.
 
Last edited:
Top