Created
April 26, 2025 09:25
-
-
Save Lorenzo501/a3882bfe9590631453d791b987d8332d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A small part of this script was made by utilizing my WinEvent Monitor.ahk for debugging