Skip to content

Instantly share code, notes, and snippets.

@obskyr
Last active January 5, 2022 11:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save obskyr/fd7271b9abe8c376a46436c10c4ee3d2 to your computer and use it in GitHub Desktop.
Save obskyr/fd7271b9abe8c376a46436c10c4ee3d2 to your computer and use it in GitHub Desktop.
Use your arrow keys instead of a numpad in Tales of Maj'Eyal and other games where it's required!

Tales of Ar'Okés

Do you have a tenkeyless keyboard? Are you not stuck in the 1970s? Then finally, your prayers are answered. This handy script lets you use arrow keys in Tales of Maj'Eyal (and other grid-based numpad-controlled games) and still reliably walk diagonally!

How to use

  1. Install AutoHotKey, the scripting language (for Windows only)
  2. Click “Download ZIP” at the top right of this page, and extract arrow-keys-to-numpad.ahk to wherever you want it.
  3. Whenever you want to use it, simply double-click the script, and it will run in the background!

While this script runs, your arrow keys will work as such:

  • Up, down, left, and right correspond to the numpad's 8, 2, 4, and 6!
  • Pressing two arrow keys at once for a diagonal corresponds to the numpad's diagonals 7, 9, 1, and 3!
  • Pressing two opposite arrow keys at once presses 5 on the numpad, allowing you to wait in place in games where that's a thing 5 does!

Adding a new game

By default, Tales of Ar'Okés only runs in Tales of Maj'Eyal. If you want to use it with another game, you will have to add it – but it isn't very hard! Here's a step-by-step guide:

  1. After installing AutoHotKey and starting the script, right-click its tray icon and click “Window Spy”. Window Spy in the tray.
  2. Check "Follow Mouse", and mouse over an open window of the game you want to add.
  3. Copy one of these two lines - whichever one has a name that looks most relevant to the game (likely the ahk_exe line)… The Window Spy window.
  4. To enable the script in that window, open it in a text editor and add what you found to line 35 following the pattern: Added to the script.

Banpresto! Tales of Ar'Okés will now be active within that window, and you can start playing.

Settings

The various timing settings included with the script are tuned specifically to Tales of Maj'Eyal – if you find that you run too fast, or something else feels strange, you might want to change those. Thankfully, those are adjustable! On lines 16 to 26, simply edit these values:
The settings.

Known bugs

  • Because of the inherently imperfect nature of listening to key changes in AutoHotKey, numpad keys may rarely get “stuck” (i.e. held even after you've released the key). To cancel this, simply press that direction again. In practice, I find that this doesn't lead to many problems – but if it does, there's no refund! 😋
; Tales of Ar'Okés v1.1
; June 2019 - @obskyr (https://twitter.com/obskyr)
;
; Do you have a tenkeyless keyboard? Are you not stuck in the 1970s?
; Then finally, your prayers are answered.
; This handy script lets you use arrow keys in Tales of Maj'Eyal (and other
; grid-based numpad-controlled games) and still reliably walk diagonally!
;
; Changelog:
; v1.1 (June 2019)
; Pressing two opposite arrow keys at once now presses 5 on the numpad
; (in order to wait in place in games where that's a thing 5 does).
; v1.0 (June 2019)
; It's alive!
; ========= Settings =========
; (Times are in milliseconds.)
; The grace period for pressing another arrow key to produce a diagonal.
DIAGONAL_WAIT := 200
; How long to hold the arrow key(s) until key repeat kicks in.
TYPEMATIC_DELAY := 250
; How long between key repeats ("run speed", more or less).
; Setting this much lower than 100 may result in unreliability.
TYPEMATIC_REPEAT := 100
; Windows to activate the script in.
; Add other games you want to use the script with here!
; Format: "GroupAdd, NumpadMovementWindow, <WinTitle>"
; Info on the WinTitle parameter can be found here:
; https://www.autohotkey.com/docs/misc/WinTitle.htm
GroupAdd, NumpadMovementWindow, ahk_exe t-engine.exe
GroupAdd, NumpadMovementWindow, ahk_exe t-engine-debug.exe
; ============================
pressedArrowKeys := {"Up": GetKeyState("Up", "P")
, "Down": GetKeyState("Down", "P")
, "Left": GetKeyState("Left", "P")
, "Right": GetKeyState("Right", "P")}
ghostArrowKeys := {"Up": 0
, "Down": 0
, "Left": 0
, "Right": 0}
waitActive := False
arrowKeyPressed(key)
{
global pressedArrowKeys
global ghostArrowKeys
global DIAGONAL_WAIT
global waitActive
if (pressedArrowKeys[key]) {
; Gotta ignore typematic repeats.
return
}
pressedArrowKeys[key] := 1
keysToCheck := {"Up": True
, "Down": True
, "Left": True
, "Right": True}
keysToCheck[key] := False
for keytoCheck, chk in keysToCheck {
if (chk and pressedArrowKeys[keyToCheck]) {
; This makes the keypress trigger immediately if a second arrow
; key is pressed while another is already held. If it turns out
; that the desired behavior if for the DIAGONAL_WAIT to be
; fully waited for even when a diagonal is pressed is desired,
; ghostArrowKeys can be used to trigger the press later instead.
updateNumpad()
return
}
}
ghostArrowKeys[key] := 1
if (not waitActive) {
waitActive := True
wait := -DIAGONAL_WAIT
SetTimer, updateNumpad, %wait%, 1
}
}
arrowKeyReleased(key)
{
global pressedArrowKeys
global ghostArrowKeys
global DIAGONAL_WAIT
global waitActive
global curNumpadKey
if (not pressedArrowKeys[key]) {
; Reduntant, but to be safe...!
return
}
pressedArrowKeys[key] := 0
keysStillHeld := False
for k, held in pressedArrowKeys {
if (held) {
keysStillHeld := True
break
}
}
if (keysStillHeld) {
; Key state is cleared here too - this makes sure that if you hold
; a diagonal but release one of the keys less than DIAGONAL_WAIT ms
; before the current TYPEMATIC_DELAY or TYPEMATIC_REPEAT elapses,
; another press of the previous keys won't occur. This does create
; some weird behavior when holding incompatible keys, though - for
; example, holding left and right and subsequently letting go of right
; will make the typematic for walking left re-trigger from the start.
clearTypematicTimers()
curNumpadKey :=
wait := -DIAGONAL_WAIT
SetTimer, updateNumpad, %wait%, 1
} else {
if (waitActive) {
pressOnNumpad(arrowKeysToNumber(ghostArrowKeys))
clearGhostArrowKeys()
waitActive := False
}
clearTypematicTimers()
curNumpadKey :=
}
}
curNumpadKey :=
doNumpad()
{
global curNumpadKey
pressOnNumpad(curNumpadKey)
}
pressOnNumpad(num)
{
if (not num) {
; For debugging:
; throw "Called pressOnNumpad without a number!"
clearTypematicTimers()
waitActive := False
} else {
Send {Blind}{Numpad%num%}
}
}
arrowKeysToNumber(keys)
{
if (keys["Left"]) {
if (keys["Up"]) {
return 7
} else if (keys["Down"]) {
return 1
} else if (keys["Right"]) {
return 5
} else {
return 4
}
} else if (keys["Right"]) {
if (keys["Up"]) {
return 9
} else if (keys["Down"]) {
return 3
} else {
return 6
}
} else if (keys["Up"]) {
if (keys["Down"]) {
return 5
} else {
return 8
}
} else if (keys["Down"]) {
return 2
} else {
return
}
}
updateNumpad()
{
global pressedArrowKeys
global curNumpadKey
global waitActive
clearGhostArrowKeys()
newNumpadKey := arrowKeysToNumber(pressedArrowKeys)
if (newNumpadKey != curNumpadKey) {
curNumpadKey := newNumpadKey
clearTypematicTimers()
if (curNumpadKey) {
startNumpadTypematic()
}
}
waitActive := False
}
startNumpadTypematic()
{
global TYPEMATIC_DELAY
doNumpad()
delay := -TYPEMATIC_DELAY
SetTimer, runNumpadRepeat, %delay%, 1
}
runNumpadRepeat()
{
global TYPEMATIC_REPEAT
doNumpad()
SetTimer, doNumpad, %TYPEMATIC_REPEAT%, 1
}
clearTypematicTimers() {
SetTimer, updateNumpad, Off
SetTimer, runNumpadRepeat, Off
SetTimer, doNumpad, Off
}
clearGhostArrowKeys()
{
global ghostArrowKeys
ghostArrowKeys["Up"] := 0
ghostArrowKeys["Down"] := 0
ghostArrowKeys["Left"] := 0
ghostArrowKeys["Right"] := 0
}
#IfWinActive, ahk_group NumpadMovementWindow
SendMode Input
*$Up::
*$Down::
*$Left::
*$Right::
arrowKeyPressed(LTrim(A_ThisHotkey, "$*"))
return
*$Up Up::
*$Down Up::
*$Left Up::
*$Right Up::
arrowKeyReleased(StrSplit(A_ThisHotkey, A_Space, "$*", 2)[1])
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment