• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Number Key Press Event v1.3

Allows you to perform actions when a player presses any number key (0-9). Can be useful in RPG or AOS maps. Still, there are some constrains in using this, read them in the documentation.

JASS:
library NumberKeyEvent initializer init
   
                        // Configurations
    globals
        // Key detector dummy's raw code in OE
        private constant integer DUMMY_ID   = 'h000'
       
        // Location of dummy, better to be a permanently
        // invisible spot in your map
        private constant real    DUMMY_X    = 2521.32
        private constant real    DUMMY_Y    = -2827.15
       
        // If true, system will use periodical check
        // results in more instaneous press event
        private constant boolean USE_TIMER  = true
        private constant real    CHECK_RATE = 0.01
    endglobals
   
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    *
    *                   NumberKeyEvent v1.3
    *                   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    *
    *       Description
    *       ¯¯¯¯¯¯¯¯¯¯¯
    *           Allows you to do actions when a player presses
    *           any number key (0-9)
    *
    *       Cons
    *       ¯¯¯¯
    *           - Needs calibration (check demo)
    *           - Disturbs unit selection
    *
    *       Requirement
    *       ¯¯¯¯¯¯¯¯¯¯¯
    *           -
    *
    *       How to import
    *       ¯¯¯¯¯¯¯¯¯¯¯¯¯
    *           - Copy KeyDetectorDummy unit from object editor to your map
    *           - Copy the trigger folder to your map
    *           - Configure the system accordingly
    *      
    *       API
    *       ¯¯¯
    *           function CalibrateNumberKeys takes integer playerId returns nothing
    *           function GetPressedNumberKey takes nothing returns integer
    *           function GetPressingPlayer   takes nothing returns player
    *           function RegisterAnyNumberKeyEvent   takes boolexpr func returns triggercondition
    *           function UnregisterAnyNumberKeyEvent takes triggercondition tc returns nothing
    *
    *       Notes
    *       ¯¯¯¯¯
    *           - Better to preserve an invisible area for dummy
    *             units to avoid unwanted camera panning
    *           - Player must be pressing CTRL when CalibrateNumberKeys
    *             function is called
    *           - You must allow players to calibrate their keys
    *             anytime whenever they want, in case the calibration
    *             is failed or broken
    *           - By using this system players are no longer supposed
    *             to use the control group shortcut keys: CTRL+(0-9).
    *             Because it will break the calibration. Warn them.
    *
    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
   
    globals
        private unit    array KeyUnit
        private boolean array IsCalibrated
        private boolean array IsRegistered
        private trigger Handler = CreateTrigger()
        private integer EventPressed = -1
        private player  EventPresser = null
        private player  Locale
        private timer   Timer = null
    endglobals
   
    private function onSelection takes nothing returns boolean
       
        static if USE_TIMER then
            local integer i = 0
            local integer j
            local integer k
            local player  p
           
            loop
                exitwhen i > 11
                if IsCalibrated[i] then
                    set p = Player(i)
                    set j = 0
                    loop
                        exitwhen j > 9
                        set k = i*10+j
                        if IsUnitSelected(KeyUnit[k], p) then
                            if Locale == p then
                                call ClearSelection()
                            endif
                            set EventPresser = p
                            set EventPressed = j
                            call TriggerEvaluate(Handler)
                            set EventPressed = -1
                            set EventPresser = null
                        endif
                        set j = j + 1
                    endloop
                endif
                set i = i + 1
            endloop
        else
            local unit    u  = GetTriggerUnit()
            local player  p  = GetOwningPlayer(u)
            local integer id = GetPlayerId(p)
            local integer dt = GetUnitUserData(u)
           
            if Locale == p then
                call ClearSelection()
            endif
            if IsCalibrated[id] then
                set EventPresser = p
                set EventPressed = dt
                call TriggerEvaluate(Handler)
                set EventPressed = -1
                set EventPresser = null
            elseif dt == 9 then
                set IsCalibrated[id] = true
            endif
            set u = null
        endif
       
        return false
    endfunction
   
    function CalibrateNumberKeys takes integer playerId returns nothing
       
        local integer i
       
        if IsRegistered[playerId] then
            set IsCalibrated[playerId] = true
            if Locale == Player(playerId) then
                call ClearSelection()
                set i = 0
                loop
                    exitwhen i > 9
                    call SelectUnit(KeyUnit[playerId*10+i], true)
                    call ForceUIKey(I2S(i))
                    call SelectUnit(KeyUnit[playerId*10+i], false)
                    set i = i + 1
                endloop
            endif
        endif
       
        static if USE_TIMER then
            if Timer == null then
                set Timer = CreateTimer()
                call TimerStart(Timer, CHECK_RATE, true, function onSelection)
            endif
        endif
       
    endfunction
   
    function GetPressedNumberKey takes nothing returns integer
        return EventPressed
    endfunction
   
    function GetPressingPlayer takes nothing returns player
        return EventPresser
    endfunction
   
    function RegisterAnyNumberKeyEvent takes boolexpr func returns triggercondition
        return TriggerAddCondition(Handler, func)
    endfunction
   
    function UnregisterAnyNumberKeyEvent takes triggercondition tc returns nothing
        call TriggerRemoveCondition(Handler, tc)
    endfunction
   
    private function init takes nothing returns nothing
       
        local integer i = 0
        local integer j
        local integer k
        local player  p
        static if not USE_TIMER then
            local trigger t = CreateTrigger()
            call TriggerAddCondition(t, Condition(function onSelection))
        endif
       
        set Locale = GetLocalPlayer()
        loop
            exitwhen i > 11
            set p = Player(i)
            set IsRegistered[i] = GetPlayerController(p) == MAP_CONTROL_USER and GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING
            if IsRegistered[i] then
                set j = 0
                loop
                    exitwhen j > 9
                    set k = i*10+j
                    set KeyUnit[k] = CreateUnit(p, DUMMY_ID, DUMMY_X, DUMMY_Y, 0)
                    static if not USE_TIMER then
                        call TriggerRegisterUnitEvent(t, KeyUnit[k], EVENT_UNIT_SELECTED)
                        call SetUnitUserData(KeyUnit[k], j)
                    endif
                    call SetUnitX(KeyUnit[k], DUMMY_X)
                    call SetUnitY(KeyUnit[k], DUMMY_Y)
                    call PauseUnit(KeyUnit[k], true)
                    set j = j + 1
                endloop
            endif
            set i = i + 1
        endloop
       
    endfunction
   
endlibrary

Keywords:
keyboard, number, key, press, event
Contents

Number Key Event (Map)

Reviews
Number Key Press Event v1.1 | Reviewed by BPower | 28.06.2015 Concept[/COLOR]] Fires an event when a control group is selected. Allows users to register code to this event. @Map-makers: Make sure to guid players properly throught the...

Moderator

M

Moderator


Number Key Press Event v1.1 | Reviewed by BPower | 28.06.2015

[COLOR="gray"

[COLOR="gray"

[COLOR="gray"

[COLOR="gray"

[COLOR="gray"

Concept[/COLOR]]
126248-albums6177-picture66521.png
Fires an event when a control group is selected.
Allows users to register code to this event.

@Map-makers: Make sure to guid players properly
throught the calibration process.

Great idea!
Code[/COLOR]]
126248-albums6177-picture66521.png
  • System is MUI, leakless and seems to work.
  • Players may overwrite their control groups,
    in this case the system does nothing and the player
    has to do the calibration again.
126248-albums6177-picture66523.png
  • Remove the keyword.
  • Sticking to the JPAG is recommended
  • Table and TimerUtils could be good dependencies.
Demo[/COLOR]]
126248-albums6177-picture66523.png
  • The demo is well done.
Rating[/COLOR]]
CONCEPTCODEDEMORATINGSTATUS
5/5
3/5
4/5
4/5
APPROVED
Links[/COLOR]]
126248-albums6177-picture66524.png
  • TimerUtils could be useful, as it recycles the timer.
  • Table is definetly a win.

[COLOR="gray"

[/TD]
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Does that make any difference?
Slightly. Paused units require overall less computation time then non-paused units.

Don't care
I care, along with many others.
--------------------------------------

Can't you change it to O(1)?
Wrote directly into comments, so maybe not working at all or including mistakes.
JASS:
// Step 1
set dummy = CreateUnit(Player(i), DUMMY, x, y, 0)
set table[GetHandleId(dummy)] = UIkey
set table.unit[i*9 + UIKey] = dummy
//call PauseUnit(dummy, true)

//Step 2, Calibration
loop
    exitwhen i > 9
//.....
         call SelectUnit(table.unit[id*9 + i], true)
         call ForceUIKey(I2S(i))
// ....
endloop

// Step 3 onSelect.
if table.has(GetHandleId(u)) and GetOwningPlayer(u) == p then
set eventIndex  = table[GetHandleId(u)]
set eventPlayer = p
//--> Fire trigger
Storing Player(id) in function calibrate into local player p is not a benefit.
You just need it once. GetLocalPlayer() == Player(x) is also very read-able.

Recursion safety.

Can locust units be selected? I know that you immediatly lose controll,
but maybe it still does fire an event ( users accidently killing dummy, etc )

I still want that you explain what a good calibration timeout parameter is.
How should a user know where to start/end? A short guidance is appreciated.

Register the select event only to players who are non computer, user players.

I guess it gets overwritten quickly, when users decided to assign their hero to
i.e. UIkey 1
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Updated
- Some suggestions have been applied, however, I don't use Table
- No longer requires TimerUtils
- No more overhead in calibrating keys
- Some other minor improvements

_______
Some points:
- I really think camelCase looks much better for private functions. I doubt that I will ever change this style.
- Tested, and selected locust unit does not fire event
I guess it gets overwritten quickly, when users decided to assign their hero to
i.e. UIkey 1
- Yes, players are not supposed to use that feature after using this system. Anyway, I don't think many people know about that feature to save unit control groups.
Register the select event only to players who are non computer, user players.
- I forgot this. Quick update's incoming.

_______
Quick update:
- Fixed registering problems
- Fixed selection bug
- Some minor improvements

_______
Can't you change it to O(1)?
Does O(1) means constant execution times? If so, my previous method was O(1) already since the dummy units count never increase. Faster doesn't mean O(1), iirc.
I don't want to complain your method tho since it's indeed better, I'm just a bit confused about what O(1) actually means?

_______
Now I can avoid the use of 2D array (which takes a lot of memory) by using unit group array. I wonder which one is better? Nvm, using unit groups will result in numerous overhead eventually.
 
Last edited:
Level 33
Joined
Apr 24, 2012
Messages
5,113
Does O(1) means constant execution times? If so, my previous method was O(1) already since the dummy units count never increase. Faster doesn't mean O(1), iirc.
I don't want to complain your method tho since it's indeed better, I'm just a bit confused about what O(1) actually means?

O(n) means how long the process takes. O(1) is the "fastest" process. Read the Big O Notation:
https://en.wikipedia.org/wiki/Big_O_notation
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
O(n) means how long the process takes. O(1) is the "fastest" process. Read the Big O Notation:
https://en.wikipedia.org/wiki/Big_O_notation

That is not true.

Big O notation means time/space complexity, not necessarily real time representation.

For instance, despite bubble sort being O(n^2), it will most likely perform a lot better on array of 5 items than merge/quick/heap sort which tend to be O(n log n).

Also the infamous linked list O(n) traversal is for sure a lot slower than O(n) array traverse

edit:
I want to add that O(n) is effectivelly equal to O(2n), since 2 is small factor as n increases to infinity, but still, O(n) will always be faster
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Every dummy units is property of their triggering players.
Ergo you could also go with EVEN_UNIT_SELECTED over EVENT_PLAYER_UNIT_SELECTED.

The unit event will fire very seldom ( only when a controll group is selected ),
the player unit event fires all the time ( each time any unit is selected )

Fixed. Thanks for all of your suggestions, it's been much better now.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Looks good? Did you perform ingame test?
I will check out your demo yet and write a review afterwards.

Edit: Tested and epic. Table would be a good dependency, it's used in nearly every map anyways.
TimerUtils would also be nice. I will place these infos under "link" into the review.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Remove the keyword.

Approved, because I know you will update the code if you have new ideas or apply improvements.

I didn't give a moderator rating on purpose, as I don't know how useful this turns out in the end.
Let's wait for some user opinions first.

Hey, thanks. I have just updated the code to include Table. ;)

Honestly I got this idea when playing Monter with friends. It got a command called "-bind" that instructs me to press CTRL for some times then I can use 1-6 key to use item, and honestly, it looks incredible! How on earth can he do that? Then I decide to run some searches how to make such thing possible. I read through some tips from Blizzard and have just found that CTRL feature at that very time. Well, it doesn't look incredible anymore after knowing the secret :p
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
I think you can remove the (hash)table part for the unit thing.
You only need one single integer so in fact you can use the UnitUserData directly.
If you dont want to do that, having a unit indexer and an integer array instead of (hash)table is imo better.

Nothing against table... just something against hashtable.

EDIT:
set Ht.integer[GetHandleId(KeyUnit[k])] = j
Better yet, you can have J stored on the index of k:
set EventId[k] = j
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I think you can remove the (hash)table part for the unit thing.
You only need one single integer so in fact you can use the UnitUserData directly.
If you dont want to do that, having a unit indexer and an integer array instead of (hash)table is imo better.

Nothing against table... just something against hashtable.

EDIT:
set Ht.integer[GetHandleId(KeyUnit[k])] = j
Better yet, you can have J stored on the index of k:
set EventId[k] = j

UnitIndexer conflicts a lot of times, since there are a lot of variations out there (Bribe's GUI one, Nestharus', AIDS, etc.) and since there is no standard which one should be used for users and spell makers. You should really avoid using UnitIndexer in your systems and spells, just my suggestion.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
UnitIndexer conflicts a lot of times, since there are a lot of variations out there (Bribe's GUI one, Nestharus', AIDS, etc.) and since there is no standard which one should be used for users and spell makers. You should really avoid using UnitIndexer in your systems and spells, just my suggestion.
Using more than one indexer in your map will mostly causing troubles.
Avoiding unit indexers has no point. Sure, some systems don't need them so requiring them for everything is not good, but avoiding them is equally bad.
Also, people shouldn't use multiple unit indexers anyway and it's fairly easy to use static ifs to support several unless their APIs are too different.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
I think that the calibration is pretty much map specific as it cannot be done automatically.

I would implement it giving every player a dummy unit at the start who will tell them to hold control and use an ability (that triggers calibration).
Then that unit is removed.
Players just shouldnt be so stupid to overwrite those settings.
 
This is how I implement optional compatibility with unit indexers.

JASS:
globals
    SomeStrut array UnitData
endglobals

function GetUnitStruct takes unit u returns SomeStruct
    return UnitData[GetUnitUserData(u)]
endfunction

function Init takes nothing returns nothing
    local SomeStruct data = SomeStruct.create()
    local unit someUnit = CreateUnit(...)
    local integer uid = GetUnitUserData(someUnit)

    if (uid <= 0) then
        call SetUnitUserData(someUnit, data)
        set uid = data
    endif

    set UnitData[uid] = data
endfunction
Any decent unit indexer should assign a unit's ID directly after it's created, so if one is not found it's set to the struct id.
 
Last edited:
return UnitData[GetUnitUserData(u)]
->
return GetUnitUserData(u)

?

GetUnitUserData(u) will return the unit's ID, not the struct instance, when a unit indexer is present.

Of course you could use static ifs to create less overhead, but then you have to require a single indexer, or for users to change the requirement themselves.
 
Top