Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Lorenzo501/a3882bfe9590631453d791b987d8332d to your computer and use it in GitHub Desktop.
Save Lorenzo501/a3882bfe9590631453d791b987d8332d to your computer and use it in GitHub Desktop.
/*
This script fixes numerous bugs and has a few quality-of-life features.
*/
#Requires AutoHotkey 2.0
#SingleInstance Off ; E.g. sometimes necessary when the script needs to restart gw2
A_DetectHiddenWindows := true
; The non-UIA instance runs gw2 elevated and then becomes an UIA instance if there isn't one already, to make it work when the active gw2 window is elevated
if (!InStr(A_AhkPath, "_UIA.exe"))
{
; To remove blue flickering at startup, the gw2 exe properties are set to both compatibility mode "Windows Vista (Service Pack 2)" and settings "Run this program as an administrator".
; This cmd runs an admin task that's added to Task Scheduler w/ start program action "%ProgramFiles%\Guild Wars 2\Gw2-64.exe", so there won't be an elevation prompt every time you start
; gw2. It's not a perfect solution but it has a more balanced result as opposed to hiding the glitchy fullscreen window for an arbitrary time
Run("schtasks /run /tn `"Guild Wars 2`"",, "Hide")
; The non-UIA process will launch this script with UIA as long as there's no process with UIA already
if (!WinExist(A_ScriptFullPath " ahk_exe AutoHotkey64_UIA.exe"))
Run("*UIAccess " A_ScriptFullPath)
ExitApp()
}
else if (WinGetList(A_ScriptFullPath " ahk_exe AutoHotkey64_UIA.exe").Length > 1)
ExitApp()
A_DetectHiddenWindows := false
TraySetIcon("pifmgr.dll", 36, true)
A_IconTip := "GW2 𝙋★𝙒𝙀𝙍-𝑼𝑷"
A_HotkeyInterval := 0 ; To disable the warning dialog (can otherwise appear when a hotkey was being pressed down for too long while being postponed)
A_CoordModeMouse := "Screen"
A_KeyDelay := -1, A_KeyDuration := -1, A_MouseDelay := -1 ; For Send b/c it reverts to the Event SendMode when another AHK script installs a native low-level keyboard/mouse hook
autoRun := {IsActive: false} ; Ad hoc object, usable w/ dot notation (no need for class)
autoFly := {IsActive: false, CurrentDirectionalHotkey: "w"} ; Ad hoc object, usable w/ dot notation (no need for class)
chat := {IsActive: false} ; Ad hoc object, usable w/ dot notation (no need for class)
HotIfWinActive("ahk_exe Gw2-64.exe") ; For hotkeys that are created at runtime
; Deals w/ hotkey suspension and terminates the macro when the game window doesn't exist any longer and wasn't being restarted either (keep this at the bottom of the auto-execute section)
ManageApp()
#HotIf (WinActive("ahk_exe Gw2-64.exe"))
; To hide the OSD
Volume_Up::SoundSetVolume("+2")
Volume_Down::SoundSetVolume("-2")
~w::
~s::autoRun.IsActive := false
~w up::return ; The ToggleChatState fn requires this as the default b/c the auto-fly feature can temporarily change it, so it needs to exist beforehand as well
$r::Send("{Blind^+!#}r"), autoRun.IsActive := !autoRun.IsActive ; Can't use ~ prefix b/c then it won't send the hotkey when postponed o.o and the Blind mode allows CapsLock to capitalize it
$t::Send("{Blind^+!#}t"), autoRun.IsActive := !autoRun.IsActive ; " "
; Auto-Fly (required resolution: Full Screen - 2560 x 1440, and you need full War Gliding Mastery to make leaning forward reliable, by having the energy used by doing so get tracked)
~Space::
{
; Runs the auto-fly activator in a new thread as soon as the latest auto-fly activator thread is done executing (or straight away if none exists ofcourse)
SetTimer(TryActivateAutoFly, 100)
TryActivateAutoFly()
{
SetTimer(, 0)
autoFly.CurrentDirectionalHotkey := "w"
loop (40)
{
Sleep(100)
if (DetermineFlyStatus())
{
autoFly.IsActive := true
break
}
}
if (autoFly.IsActive)
{
Hotkey("w", FlyDirectionHotkey_W)
Hotkey("~w up", (*) => (Sleep(200), (autoFly.IsActive ? Send("{" autoFly.CurrentDirectionalHotkey " down}") : Exit())), "On") ; Can't block
Hotkey("s", FlyDirectionHotkey_S)
Hotkey("r", (*) => autoRun.IsActive := !autoRun.IsActive)
Hotkey("t", (*) => autoRun.IsActive := !autoRun.IsActive)
checkpoint := 1128 ; Initially the startpoint of the energy bar that appears when you start flying (to determine whether or not the character is successfully leaning forward)
progressColor := 0x181B18
Send("{w down}")
SetTimer(TryDeactivateAutoFly, 50)
while (autoFly.IsActive)
{
Sleep(1000)
if (autoFly.CurrentDirectionalHotkey = "w" && !chat.IsActive)
{
if (DetermineHasNewProgress())
continue
else
UpdateAnyLostProgress(), Send("{w up}"), Sleep(200), Send("{" autoFly.CurrentDirectionalHotkey " down}")
}
}
; Tries to release the auto-fly key late in case it programmatically became pressed down again
if (!GetKeyState(autoFly.CurrentDirectionalHotkey, "P"))
Send("{" autoFly.CurrentDirectionalHotkey " up}")
; Resumes auto-run
if (autoRun.IsActive)
{
if (chat.IsActive)
{
MouseGetPos(&cursorXPrevious, &cursorYPrevious)
Send("{Click " A_ScreenWidth " 0 0}")
Sleep(100)
Send("{LButton}{Click " cursorXPrevious " " cursorYPrevious " 0}r{Enter}")
}
else
Send("r")
}
}
; Disables anything that it can at the earliest occasion
TryDeactivateAutoFly()
{
if (!DetermineFlyStatus())
{
SetTimer(, 0)
; Tries to release the auto-fly key
if (!GetKeyState(autoFly.CurrentDirectionalHotkey, "P"))
Send("{" autoFly.CurrentDirectionalHotkey " up}")
autoFly.IsActive := false
Hotkey("~w", (*) => autoRun.IsActive := false)
Hotkey("~w up", (*) => Exit())
Hotkey("~s", (*) => autoRun.IsActive := false)
Hotkey("$r", (*) => (Send("{Blind^+!#}r"), autoRun.IsActive := !autoRun.IsActive))
Hotkey("$t", (*) => (Send("{Blind^+!#}t"), autoRun.IsActive := !autoRun.IsActive))
}
}
DetermineFlyStatus() => PixelGetColor(1111, 1230) = 0x764E1B && PixelGetColor(1455, 1230) = 0xAA6404
FlyDirectionHotkey_W(*)
{
if (chat.IsActive)
Send("w")
else
{
autoRun.IsActive := false
Send("{s up}"), autoFly.CurrentDirectionalHotkey := "w"
}
}
FlyDirectionHotkey_S(*)
{
if (chat.IsActive)
Send("s")
else
{
autoRun.IsActive := false
Send("{w up}{s down}"), autoFly.CurrentDirectionalHotkey := "s"
}
}
DetermineHasNewProgress()
{
static endpoint := 1270
hasNewProgress := false
loop
{
if (checkpoint < endpoint && PixelGetColor(checkpoint + A_Index, 1230) = progressColor)
++checkpoint, hasNewProgress := true
else
return hasNewProgress
}
}
UpdateAnyLostProgress()
{
static startpoint := 1128, offset := 10
loop
{
if (checkpoint > startpoint + offset && PixelGetColor(checkpoint - (A_Index * offset) + 1, 1230) != progressColor)
checkpoint -= offset
else if (checkpoint > startpoint && checkpoint <= startpoint + offset && PixelGetColor(checkpoint - A_Index + 1, 1230) != progressColor)
--checkpoint
else
return
}
}
}
}
~Esc::
{
; Resumes sufficient-fly-energy mode when necessary
if (chat.IsActive && autoFly.IsActive)
Send("{" autoFly.CurrentDirectionalHotkey " up}{" autoFly.CurrentDirectionalHotkey " down}")
SetChatState("Disable")
}
$F3::
F4::LButtonAboutFace("F3") ; Keep LButton pressed (not RButton) when using About Face if you need to keep the enemy targeted (otherwise you'll lose the target, but rly can't be automated)
; ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
; Dodge Jump
$v::Send(DetermineMountedIconVisible() ? "v" : "v{Space}")
; Required resolution: Full Screen - 2560 x 1440
; Mount-up hotkey which fixes the lost press state of the mouse buttons and the sudden rotation on mount-up (does not get triggered by our artificial input, except if #InputLevel gets used)
$'::
{
if (GetKeyState("LButton") && !GetKeyState("RButton"))
{
Critical()
; The first scope fixes the gw2 warclaw mount-up bug and the second scope has a gw2 bugfix which prevents the LButton from getting released while the player unmounts
if (DetermineWarclawIconVisible())
Send("{LButton up}"), Sleep(100), Send("{RButton down}"), WiggleCamera(), Send("'{RButton up}"), Sleep(100), Send("{LButton down}")
else
Send("{LButton up}'"), Sleep(100), Send("{LButton down}")
}
else if (!GetKeyState("LButton") && GetKeyState("RButton"))
{
Critical()
; This fixes the gw2 warclaw mount-up bug
if (DetermineWarclawIconVisible())
Send("{RButton up}{RButton down}"), WiggleCamera()
; This has a gw2 bugfix which prevents the RButton from getting released while the player mounts/unmounts
Send("'{RButton up}"), Sleep(200), Send("{RButton down}")
}
else if (GetKeyState("LButton") && GetKeyState("RButton"))
{
Critical()
; This fixes the gw2 warclaw mount-up bug
if (DetermineWarclawIconVisible())
Send("{LButton up}{RButton up}{LButton down}{RButton down}"), WiggleCamera()
; This has a gw2 bugfix which prevents the LButton and RButton from getting released while the player mounts/unmounts
Send("'{LButton up}{RButton up}"), Sleep(200), Send("{LButton down}{RButton down}")
}
else if (DetermineWarclawIconVisible()) ; And when neither LButton nor RButton is being pressed down
{
Critical()
MouseGetPos(&cursorXPrevious, &cursorYPrevious)
Send("{Click " A_ScreenWidth " 0 R D}")
WiggleCamera()
Send("'{RButton up}")
Sleep(30)
Send("{Click " cursorXPrevious " " cursorYPrevious " 0}")
}
else ; When neither LButton nor RButton is being pressed down, and warclaw icon is not visible
Send("'")
}
; Prevents hotkey interference when the chat is being used
~Enter::
{
; Resumes sufficient-fly-energy mode when necessary
if (chat.IsActive && autoFly.IsActive)
Send("{" autoFly.CurrentDirectionalHotkey " up}{" autoFly.CurrentDirectionalHotkey " down}")
SetChatState("Toggle")
; Disables the input field of the chat when necessary to keep it in sync with the script no matter what (e.g. if "/s" was used then gw2 normally doesn't disable the input field)
if (!chat.IsActive)
{
MouseGetPos(&cursorXPrevious, &cursorYPrevious)
Send("{Click " A_ScreenWidth " 0 0}")
Sleep(100)
Send("{LButton}{Click " cursorXPrevious " " cursorYPrevious " 0}")
}
}
; Required resolution: Full Screen - 2560 x 1440
LButton::
{
MouseGetPos(&cursorXPrevious, &cursorYPrevious)
if (chat.IsActive)
{
if
(
DetermineWarclawIconVisible() &&
DeterminePositionIsOnMountIcon(cursorXPrevious, cursorYPrevious)
)
SetChatState("Disable"), MountWarclaw()
else if (cursorXPrevious > 490 || cursorYPrevious < 1147) ; Detects if anything other than the chat gets clicked
{
Send("{LButton}")
SetChatState("Disable")
if (autoFly.IsActive)
Send("{" autoFly.CurrentDirectionalHotkey " up}{" autoFly.CurrentDirectionalHotkey " down}")
}
else
Send("{LButton}")
}
else if (cursorXPrevious < 482 && cursorYPrevious > 1370)
Send("{LButton}"), SetChatState("Toggle")
else if (cursorXPrevious < 482 && cursorYPrevious > 1147) ; In this case the player might've clicked on a name in the chat to whisper someone, which shouldn't enable the input field
Send("{LButton}{Click " A_ScreenWidth " 0}"), Sleep(50), Send("{LButton}"), Sleep(50), Send("{LButton}{Click " cursorXPrevious " " cursorYPrevious " 0}")
else if
(
DetermineWarclawIconVisible() &&
DeterminePositionIsOnMountIcon(cursorXPrevious, cursorYPrevious)
)
MountWarclaw()
else
Send("{LButton down}")
; Detects when manual-run overrides auto-run
if (GetKeyState("RButton"))
autoRun.IsActive := false
MountWarclaw() => (Critical(), Send("{Click 1753 1374 R D}"), WiggleCamera(), Send("'{RButton up}"), Sleep(10), Send("{Click " cursorXPrevious " " cursorYPrevious " 0}"))
DeterminePositionIsOnMountIcon(x, y) => x > 1678 && x < 1728 && y > 1349 && y < 1398
}
; These serve mainly as postponeable hotkeys (and apparently you cannot make a btn-down hotkey w/o making a btn-up hotkey, and vice versa, except w/ btn-down you could do `KeyWait+Release`)
LButton up::Send("{LButton up}")
RButton::
{
Send("{RButton down}")
; Detects when manual-run overrides auto-run
if (GetKeyState("LButton"))
autoRun.IsActive := false
}
RButton up::Send("{RButton up}")
;********** LIBRARY **********
/*
If compatibility mode + admin doesn't reliably remove blue flickering at startup, then use this instead:
EVENT_OBJECT_CREATE := 0x8000
DllCall("SetWinEventHook", "UInt", EVENT_OBJECT_CREATE, "UInt", EVENT_OBJECT_CREATE, "Ptr", 0, "Ptr", CallbackCreate(HandleCreatedGuildWars2Event), "UInt", 0, "UInt", 0, "UInt", 0)
*/
; This will remove the blue flickering issue when the gw2 launcher creates a fullscreen wnd
HandleCreatedGuildWars2Event(hWinEventHook, event, hWnd, *)
{
try
if (WinGetClass(hWnd) = "ArenaNet_Gr_Window_Class")
{
WinSetTransparent(0, hWnd)
Sleep(2050)
WinSetTransparent("Off", hWnd)
}
}
; Terminates the macro if the game doesn't restart itself within 30 seconds. And also has recursive code, which restarts this method each time the game crashes and immediately restarts.
; On top of that it will suspend the hotkeys while the game hasn't been launched yet
ManageApp()
{
static fullscreenState := 2
Suspend(true)
if (WinWait("ahk_exe Gw2-64.exe",, 30))
{
while (WinExist())
{
Sleep(1000)
DllCall("Shell32\SHQueryUserNotificationState", "UInt*", &monitorState := 0)
if (monitorState = fullscreenState)
{
Suspend(false)
break
}
}
WinWaitClose()
ManageApp()
}
else
ExitApp()
}
; Uses the About Face feature without unintentionally turning the camera along with it (requires the keybind that's used in guild wars 2)
LButtonAboutFace(fnKey)
{
Hotkey("$4", (*) => Send("4"), "On") ; Makes it postponeable
Critical()
static cursorMoveThreshold := 5
if (!GetKeyState("LButton") && GetKeyState("RButton")) ; With RButton down, that means the cursor is already in a safe spot
{
BlockInput("MouseMove")
Send("{RButton up}")
; This prevents About Face from doing nothing
if (!autoRun.IsActive)
Send("r")
; Holds the LButton pressed down while it sends the fn key (the following stops auto-run: {LButton down} + {RButton down} = manual-run + camera rotating along with the character)
Send("{LButton down}{" fnKey "}{LButton up}")
if (!autoRun.IsActive)
Sleep(40), Send("r")
; Delay logically pressing RButton down again to prevent the camera from turning 180 degrees
Sleep(525)
BlockInput("MouseMoveOff")
MouseGetPos(&startCursorX, &startCursorY)
Critical("Off")
Sleep(-1)
SetTimer(() => Hotkey("$4", "Off"), -1) ; Makes it use the faster native fn instead of being postponeable (after any postponed ones have been sent)
Sleep(1)
; Make the right mouse button become pressed down again as long as it's still physically pressed down, but only as soon as it stops leading to unwanted behaviour
while (GetKeyState("RButton", "P"))
{
MouseGetPos(&cursorX, &cursorY)
if
(
GetKeyState("LButton", "P") ||
cursorX < startCursorX - cursorMoveThreshold ||
cursorX > startCursorX + cursorMoveThreshold ||
cursorY < startCursorY - cursorMoveThreshold ||
cursorY > startCursorY + cursorMoveThreshold
)
{
Send("{RButton down}")
return
}
Sleep(0)
}
}
else if (!GetKeyState("LButton")) ; No mouse buttons are being pressed down, that means the cursor might be in an unsafe spot! So watch out for that when using the About Face fn key
{
; This prevents About Face from doing nothing
if (!autoRun.IsActive)
Send("r")
; Holds the LButton pressed down (whether it's in a safe spot or not, moving the cursor programmatically looks bad so I'm skipping that) while it sends the fn key
Send("{LButton down}{" fnKey "}{LButton up}")
; Postpone any RButton down presses that might occur, to prevent the camera from turning 180 degrees, and stop auto-run again if it was activated above
if (autoRun.IsActive)
Sleep(525)
else
Sleep(40), Send("r"), Sleep(485)
}
else if (!GetKeyState("RButton") && !autoRun.IsActive) ; This prevents About Face from doing nothing when merely LButton is pressed
Send("r{" fnKey "}"), Sleep(40), Send("r")
else ; When both LButton and RButton are being pressed down then there are no issues (it will intentionally rotate both the character and the camera)
Send("{" fnKey "}")
SetTimer(() => Hotkey("$4", "Off"), -1) ; Makes it use the faster native fn instead of being postponeable (after any postponed ones have been sent)
}
SetChatState(newState)
{
if (newState = "Disable")
Hotkey("~w", "On"), Hotkey("~w up", "On"), Hotkey("$r", "On"), Hotkey("$t", "On"), Hotkey("$v", "On"), Hotkey("$'", "On"), chat.IsActive := false
else if (newState = "Toggle")
Hotkey("~w", "Toggle"), Hotkey("~w up", "Toggle"), Hotkey("$r", "Toggle"), Hotkey("$t", "Toggle"), Hotkey("$v", "Toggle"), Hotkey("$'", "Toggle"), chat.IsActive := !chat.IsActive
}
; Required resolution: Full Screen - 2560 x 1440
DetermineWarclawIconVisible() => PixelGetColor(1703, 1373) = 0x082410
DetermineMountedIconVisible() => PixelGetColor(1703, 1373) = 0x212103
; This locks the camera onto the cursor if RButton is pressed down, allowing the camera to remain in place instead of the warclaw mount-up bug causing it to randomly rotate
WiggleCamera()
{
mouseDelayPrevious := SetMouseDelay(1) ; Using the lowest possible mouse delay for a wiggle that's barely noticeable (going even lower will make the wiggle stop working)
Send("{Click -15 0 0 Rel}{Click 15 0 0 Rel}")
A_MouseDelay := mouseDelayPrevious
}
@Lorenzo501
Copy link
Author

A small part of this script was made by utilizing my WinEvent Monitor.ahk for debugging

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment