Run RunZ with vimd.exe

Run RunZ with vimd.exe

General:
    Gen_Save_Win := []
    vim.SetAction("<down>", "向下移动")
    vim.SetAction("<up>", "向上移动")
    vim.SetAction("<left>", "向左移动")
    vim.SetAction("<right>", "向右移动")
    vim.SetAction("<home>", "home键")
    vim.SetAction("<end>", "end键")
    vim.SetAction("<Gen_InsertMode>", "插入模式")
    vim.SetAction("<Gen_NormalMode>", "浏览模式")
    vim.SetAction("<Gen_Toggle>", "启用/禁用vim热键(General插件)")
    vim.SetAction("<Reload>", "重新加载")
    vim.SetAction("<WindowMoveDown>", "窗口移动到下方")
    vim.SetAction("<WindowMoveUp>", "窗口移动到上方")
    vim.SetAction("<WindowMoveLeft>", "窗口移动到左侧")
    vim.SetAction("<WindowMoveRight>", "窗口移动到右侧")
    vim.SetAction("<WindowMoveCenter>", "窗口移动到中间")
    vim.SetAction("<WindowMax>", "最大化窗口")
    vim.SetAction("<WindowMin>", "最小化窗口")
    vim.SetAction("<WindowRestore>", "还原当前窗口")
    vim.SetAction("<FullScreen>", "全屏当前程序")
    vim.SetAction("<NextTab>", "下一个标签")
    vim.SetAction("<PrevTab>", "前一个标签")
    vim.SetAction("<CloseTab>", "关闭当前标签")
    vim.SetAction("<NewTab>", "新建标签")
    vim.SetAction("<ActivateTab1>", "激活第一个标签")
    vim.SetAction("<ActivateTab2>", "激活第二个标签")
    vim.SetAction("<ActivateTab3>", "激活第三个标签")
    vim.SetAction("<ActivateTab4>", "激活第四个标签")
    vim.SetAction("<ActivateTab5>", "激活第五个标签")
    vim.SetAction("<ActivateTab6>", "激活第六个标签")
    vim.SetAction("<ActivateTab7>", "激活第七个标签")
    vim.SetAction("<ActivateTab8>", "激活第八个标签")
    vim.SetAction("<ActivateTab9>", "激活第九个标签")
    vim.SetAction("<ActivateTab0>", "激活最后一个标签")
    vim.SetAction("<pgup>", "上一页")
    vim.SetAction("<pgdn>", "下一页")
    vim.SetAction("<enter>", "回车键")
    vim.SetAction("<space>", "空格键")
    vim.SetAction("<backspace>", "退格键")
    vim.SetAction("<Pass>", "屏蔽按键")
    vim.SetAction("<Media_Next>", "播放下一首")
    vim.SetAction("<Media_Prev>", "播放上一首")
    vim.SetAction("<Media_Play_Pause>", "播放/停止")
    vim.SetAction("<Media_Stop>", "停止播放")
    vim.SetAction("<RemoveToolTip>", "清除屏幕的ToolTip")
    vim.SetAction("<ShowHelp>", "显示所有按键帮助信息")
    vim.SetAction("<ToggleCapsLock>", "切换大小写")
    vim.SetAction("<MouseUp>", "向上移动鼠标")
    vim.SetAction("<MouseDown>", "向下移动鼠标")
    vim.SetAction("<MouseLeft>", "向左移动鼠标")
    vim.SetAction("<MouseRight>", "向右移动鼠标")
    vim.SetAction("<SearchInWeb>", "在网络搜索剪切板内容")
    vim.SetAction("<RunZ>", "打开RunZ工具")
    vim.SetAction("<Test>", "测试")

    vim.SetWin("General", "General")
    vim.SetMode("insert", "General")
    vim.map("<Esc>", "<Gen_NormalMode>", "General")
    vim.SetMode("normal", "General")
    vim.map("i", "<Gen_InsertMode>", "General")
    vim.map("0", "<0>", "General")
    vim.map("1", "<1>", "General")
    vim.map("2", "<2>", "General")
    vim.map("3", "<3>", "General")
    vim.map("4", "<4>", "General")
    vim.map("5", "<5>", "General")
    vim.map("6", "<6>", "General")
    vim.map("7", "<7>", "General")
    vim.map("8", "<8>", "General")
    vim.map("9", "<9>", "General")
    vim.map("k", "<up>", "General")
    vim.map("j", "<down>", "General")
    vim.map("k", "<up>", "General")
    vim.map("h", "<left>", "General")
    vim.map("l", "<right>", "General")
    vim.map("zj", "<WindowMoveDown>", "General")
    vim.map("zk", "<WindowMoveUp>", "General")
    vim.map("zh", "<WindowMoveLeft>", "General")
    vim.map("zl", "<WindowMoveRight>", "General")
    vim.map("zc", "<WindowMoveCenter>", "General")
    vim.map("zf", "<fullscreen>", "General")
    vim.map("zm", "<WindowMax>", "General")
    vim.map("zn", "<WindowMin>", "General")
    vim.map("zr", "<WindowRestore>", "General")
    vim.map("t", "<NewTab>", "General")
    vim.map("x", "<CloseTab>", "General")
    vim.map("gn", "<NextTab>", "General")
    vim.map("gp", "<PrevTab>", "General")
    vim.map("g1", "<ActivateTab1>", "General")
    vim.map("g2", "<ActivateTab2>", "General")
    vim.map("g3", "<ActivateTab3>", "General")
    vim.map("g4", "<ActivateTab4>", "General")
    vim.map("g5", "<ActivateTab5>", "General")
    vim.map("g6", "<ActivateTab6>", "General")
    vim.map("g7", "<ActivateTab7>", "General")
    vim.map("g8", "<ActivateTab8>", "General")
    vim.map("g9", "<ActivateTab9>", "General")
    vim.map("g0", "<ActivateTab0>", "General")
    vim.BeforeActionDo("Gen_Before", "General")
return

Gen_Before()
{
    Global vim
    WinGet, MenuID, ID, AHK_CLASS #32768
    If MenuID
        return True
}

<Gen_InsertMode>:
    vim.SetMode("Insert", vim.CheckWin())
    Tooltip, Insert模式, , , 19
    Settimer, cancelTooltip, 1200
return
<Gen_NormalMode>:
    vim.SetMode("Normal", vim.CheckWin())
    Tooltip, Normal模式, , , 19
    Settimer, cancelTooltip, 1200
return
<Gen_Toggle>:
    Gen_Toggle()
return
Gen_Toggle()
{
    Global vim, Gen_Save_Win
    WinGetClass, c, A
    WinGet, f, ProcessPath, ahk_class %c%
    WinName:=vim.CheckWin()
    If not Gen_Save_Win[c]
    {
        If Strlen(winName) And IsObject(vim.GetWin(winName))
        {
            MsgBox, , VimDesktop, 当前窗口 [ %winName% ] 已经拥有Vim模式`n不允许替换为通用(General)模式！
            return
        }
    }
    winObj := vim.GetWin(c)
    If not Isobject(winObj)
    {
        vim.copy("General", c, c, f)
        Gen_Save_Win[c] := True
        Tooltip, 复制VIMD热键到当前窗口成功, , , 19
    }
    Else
    {
        if vim.Toggle(c)
            Tooltip, 激活当前窗口的VIMD热键, , , 19
        else
            Tooltip, 取消当前窗口的VIMD热键, , , 19
    }
    Settimer, cancelTooltip, 1200
}
cancelTooltip:
    Tooltip, , , , 19
return
<reload>:
    reload
return
<Exit>:
    ExitApp
return
<down>:
    send, {down}
return
<up>:
    send, {up}
return
<left>:
    send, {left}
return
<right>:
    send, {right}
return
<home>:
    send, {home}
return
<end>:
    send, {end}
return
<pgup>:
    send, {pgup}
return
<pgdn>:
    send, {pgdn}
return
<enter>:
    send, {enter}
return
<space>:
    send, {space}
return
<backspace>:
    send, {backspace}
return
<Media_Next>:
    send, {Media_Next}
return
<Media_Prev>:
    send, {Media_Prev}
return
<Media_Stop>:
    send, {Media_Stop}
return
<Media_Play_Pause>:
    send, {Media_Play_Pause}
return

<WindowMoveDown>:
    WindowPadMove("0, +1, 1.0, 0.5")
Return
<WindowMoveUp>:
    WindowPadMove("0, -1, 1.0, 0.5")
Return
<WindowMoveLeft>:
    WindowPadMove("-1, 0, 0.5, 1.0")
Return
<WindowMoveRight>:
    WindowPadMove("+1, 0, 0.5, 1.0")
Return
<WindowMoveCenter>:
    WindowPadMove("0, 0, 0.5, 0.7")
return
<WindowMax>:
    WinMaximize, A
return
<WindowMin>:
    WinMinimize, A
return
<WindowRestore>:
    WinRestore, A
return

; 全屏当前程序
<FullScreen>:
    FullScreen()
return
FullScreen() {
    WinGet, windowID, ID, A
    WindowState := FullScreenID[windowID]
    If Strlen(windowState) = 0
        WindowState := 0
    If WindowState
    {
        WinSet, Style, ^0xC40000, ahk_id %WindowID%
        Loop, Parse, windowState, `,
        {
            If A_Index = 2
                WinPosX := A_LoopField
            If A_Index = 3
                WinPosY := A_LoopField
            If A_Index = 4
                WindowWidth := A_LoopField
            If A_Index = 5
                WindowHeight := A_LoopField
        }
        WinMove, ahk_id %WindowID%, , WinPosX, WinPosY, WindowWidth, WindowHeight
        WindowState := 0
    }
    Else
    {
        WinGetPos, WinPosX, WinPosY, WindowWidth, WindowHeight, ahk_id %WindowID%
        WinSet, Style, ^0xC40000, ahk_id %WindowID%
        WinMove, ahk_id %WindowID%, , 0, 0, A_ScreenWidth, A_ScreenHeight
        WindowState := 1 ", " WinPosX ", " WinPosY ", " WindowWidth ", " WindowHeight
        ;Msgbox 再按一下刚刚的热键退出全屏
    }
    FullScreenID[windowID] := WindowState
}

;====================================================================================
;
; WindowPad.ahk
; Version: 0.4
; Last Updated: 11/5/2009
;
; This file contains functions which are useful for manipulating and moving windows.
; This is a stripped down version of WindowPad v.1.56 by Lexikos.
; Downloaded from http://www.autohotkey.com/forum/topic21703.html
;

WindowPadMove(P)
{
    StringSplit, P, P, `, , %A_Space%%A_Tab%
    ; Params: 1:dirX, 2:dirY, 3:widthFactor, 4:heightFactor, 5:window

    if P1 =
        P1 = R
    if P2 =
        P2 = R

    WindowPad_WinExist(P5)

    if !WinExist()
        return

    ; Determine width/height factors.
    if (P1 or P2) {
        ; to a side
        widthFactor := P3+0 ? P3 : (P1 ? 0.5 : 1.0)
        heightFactor := P4+0 ? P4 : (P2 ? 0.5 : 1.0)
    } else {
        ; to center
        widthFactor  := P3+0 ? P3 : 1.0
        heightFactor := P4+0 ? P4 : 1.0
    }

    ; Move the window!
    MoveWindowInDirection(P1, P2, widthFactor, heightFactor)
}

MaximizeToggle(P)
{
    WindowPad_WinExist(P)

    WinGet, state, MinMax
    if state
        WinRestore
    else
        WinMaximize
}

; Does the grunt work of the script.
MoveWindowInDirection(sideX, sideY, widthFactor, heightFactor)
{
    WinGetPos, x, y, w, h

    hwnd:=WinExist()
    if (can_restore := GetWindowProperty(hwnd, "wpHasRestorePos"))
    {   ; Window has restore info. Check if it is where we last put it.
        last_x := GetWindowProperty(hwnd, "wpLastX")
        last_y := GetWindowProperty(hwnd, "wpLastY")
        last_w := GetWindowProperty(hwnd, "wpLastW")
        last_h := GetWindowProperty(hwnd, "wpLastH")
    }
    if (can_restore && last_x = x && last_y = y && last_w = w && last_h = h)
    {   ; Window is where we last put it. Check if user wants to restore.
        if SubStr(sideX, 1, 1) = "R"
        {   ; Restore on X-axis.
            restore_x := GetWindowProperty(hwnd, "wpRestoreX")
            restore_w := GetWindowProperty(hwnd, "wpRestoreW")
            StringTrimLeft, sideX, sideX, 1
        }
        if SubStr(sideY, 1, 1) = "R"
        {   ; Restore on Y-axis.
            restore_y := GetWindowProperty(hwnd, "wpRestoreY")
            restore_h := GetWindowProperty(hwnd, "wpRestoreH")
            StringTrimLeft, sideY, sideY, 1
        }
        if (restore_x != "" || restore_y != "")
        {   ; If already at the "restored" size and position, do the normal thing instead.
            if ((restore_x = x || restore_x = "") && (restore_y = y || restore_y = "")
                && (restore_w = w || restore_w = "") && (restore_h = h || restore_h = ""))
            {
                restore_x =
                restore_y =
                restore_w =
                restore_h =
            }
        }
    }
    else
    {   ; Next time user requests the window be "restored" use this position and size.
        SetWindowProperty(hwnd, "wpHasRestorePos", true)
        SetWindowProperty(hwnd, "wpRestoreX", x)
        SetWindowProperty(hwnd, "wpRestoreY", y)
        SetWindowProperty(hwnd, "wpRestoreW", w)
        SetWindowProperty(hwnd, "wpRestoreH", h)

        if SubStr(sideX, 1, 1) = "R"
            StringTrimLeft, sideX, sideX, 1
        if SubStr(sideY, 1, 1) = "R"
            StringTrimLeft, sideY, sideY, 1
    }

    ; If no direction specified, restore or only switch monitors.
    if (sideX+0 = "" && restore_x = "")
        restore_x := x, restore_w := w
    if (sideY+0 = "" && restore_y = "")
        restore_y := y, restore_h := h

    ; Determine which monitor contains the center of the window.
    m := GetMonitorAt(x+w/2, y+h/2)

    ; Get work area of active monitor.
    gosub CalcMonitorStats
    ; Calculate possible new position for window.
    gosub CalcNewSizeAndPosition

    ; If the window is already there,
    if (newx ", " newy ", " neww ", " newh) = (x ", " y ", " w ", " h)
    {   ; ..move to the next monitor along instead.

        if (sideX or sideY)
        {   ; Move in the direction of sideX or sideY.
            SysGet, monB, Monitor, %m% ; get bounds of entire monitor (vs. work area)
            x := (sideX=0) ? (x+w/2) : (sideX>0 ? monBRight : monBLeft) + sideX
            y := (sideY=0) ? (y+h/2) : (sideY>0 ? monBBottom : monBTop) + sideY
            newm := GetMonitorAt(x, y, m)
        }
        else
        {   ; Move to center (Numpad5)
            newm := m+1
            SysGet, mon, MonitorCount
            if (newm > mon)
                newm := 1
        }

        if (newm != m)
        {
            m := newm
            ; Move to opposite side of monitor (left of a monitor is another monitor's right edge)
            sideX *= -1
            sideY *= -1
            ; Get new monitor's work area.
            gosub CalcMonitorStats
        }
        else
        {
            ; No monitor to move to, alternate size of window instead.
            if sideX
                widthFactor /= 2
            else if sideY
                heightFactor /= 2
            else
                widthFactor *= 1.5
            gosub CalcNewSizeAndPosition
        }

        ; Calculate new position for window.
        gosub CalcNewSizeAndPosition
    }

    ; Restore before resizing...
    WinGet, state, MinMax
    if state
        WinRestore

    WinDelay := A_WinDelay
    SetWinDelay, 0

    ; Move the window!
    WinMove, , , newx, newy, neww, newh

    ; For console windows and other windows which have size restrictions, check
    ; that the window was actually resized. If not, reposition based on actual size.
    WinGetPos, newx, newy, realw, realh
    if (neww != realw || newh != realh)
    {
        neww := realw
        newh := realh
        gosub CalcNewPosition
        if ((newm = "" || newm = m) && (newx ", " newy ", " realw ", " realh)=(x ", " y ", " w ", " h))
        {
            if sideX
                neww //= 2
            else if sideY
                newh //= 2
            else
                neww := Round(neww*1.5)
            ; Size first, since the window size will probably be restricted in some way.
            WinMove, , , , , neww, newh
            WinGetPos, , , neww, newh
            gosub CalcNewPosition
        }
        WinMove, , , newx, newy
    }

    ; Explorer uses WM_EXITSIZEMOVE to detect when a user finishes moving a window
    ; in order to save the position for next time. May also be used by other apps.
    PostMessage, 0x232

    SetWinDelay, WinDelay

    ; Remember where we put it, to detect if the user moves it.
    SetWindowProperty(hwnd, "wpLastX", newx)
    SetWindowProperty(hwnd, "wpLastY", newy)
    SetWindowProperty(hwnd, "wpLastW", neww)
    SetWindowProperty(hwnd, "wpLastH", newh)
    return

CalcNewSizeAndPosition:
    ; Calculate new size.
    if (IsResizable()) {
        neww := restore_w != "" ? restore_w : Round(monWidth * widthFactor)
        newh := restore_h != "" ? restore_h : Round(monHeight * heightFactor)
    } else {
        neww := w
        newh := h
    }
CalcNewPosition:
    ; Calculate new position.
    newx := restore_x != "" ? restore_x : Round(monLeft + (sideX+1) * (monWidth  - neww)/2)
    newy := restore_y != "" ? restore_y : Round(monTop  + (sideY+1) * (monHeight - newh)/2)
    return

CalcMonitorStats:
    ; Get work area (excludes taskbar-reserved space.)
    SysGet, mon, MonitorWorkArea, %m%
    monWidth  := monRight - monLeft
    monHeight := monBottom - monTop
    return
}

; Get/set window property. type should be int, uint or float.
GetWindowProperty(hwnd, property_name, type="int") {
    return DllCall("GetProp", "uint", hwnd, "str", property_name, type)
}
SetWindowProperty(hwnd, property_name, data, type="int") {
    return DllCall("SetProp", "uint", hwnd, "str", property_name, type, data)
}

; Get the index of the monitor containing the specified x and y co-ordinates.
GetMonitorAt(x, y, default=1)
{
    SysGet, m, MonitorCount
    ; Iterate through all monitors.
    Loop, %m%
    {
        ; Check if the window is on this monitor.
        SysGet, Mon, Monitor, %A_Index%
        if (x >= MonLeft && x <= MonRight && y >= MonTop && y <= MonBottom)
            return A_Index
    }

    return default
}

IsResizable()
{
    WinGetClass, Class
    if Class = Chrome_XPFrame
        return true
    if Class = ConsoleWindowClass
        return false
    WinGet, Style, Style
    return (Style & 0x40000) ; WS_SIZEBOX
}

WindowPad_WinExist(WinTitle)
{
    if WinTitle = P
        return WinPreviouslyActive()
    if WinTitle = M
    {
        MouseGetPos, , , win
        return WinExist("ahk_id " win)
    }
    return WinExist(WinTitle!="" ? WinTitle : "A")
}

; Note: This may not work properly with always-on-top windows. (Needs testing)
WinPreviouslyActive()
{
    active := WinActive("A")
    WinGet, win, List

    ; Find the active window.
    ; (Might not be win1 if there are always-on-top windows?)
    Loop, %win%
        if (win%A_Index% = active)
        {
            if (A_Index < win)
                N := A_Index+1

            ; hack for PSPad: +1 seems to get the document (child!) window, so do +2
            ifWinActive, ahk_class TfPSPad
                N += 1

            break
        }

    ; Use WinExist to set Last Found Window (for consistency with WinActive())
    return WinExist("ahk_id " . win%N%)
}

;
; Switch without moving/resizing (relative to screen)
;
WindowScreenMove(P)
{
    SetWinDelay, 0

    StringSplit, P, P, `, , %A_Space%%A_Tab%
    ; 1:Next|Prev|Num, 2:Window

    WindowPad_WinExist(P2)

    WinGet, state, MinMax
    if state = 1
        WinRestore

    WinGetPos, x, y, w, h

    ; Determine which monitor contains the center of the window.
    ms := GetMonitorAt(x+w/2, y+h/2)

    SysGet, mc, MonitorCount

    ; Determine which monitor to move to.
    if P1 in , N, Next
    {
        md := ms+1
        if (md > mc)
            md := 1
    }
    else if P1 in P, Prev, Previous
    {
        md := ms-1
        if (md < 1)
            md := mc
    }
    else if P1 is integer
        md := P1

    if (md=ms or (md+0)="" or md<1 or md>mc)
        return

    ; Get source and destination work areas (excludes taskbar-reserved space.)
    SysGet, ms, MonitorWorkArea, %ms%
    SysGet, md, MonitorWorkArea, %md%
    msw := msRight - msLeft, msh := msBottom - msTop
    mdw := mdRight - mdLeft, mdh := mdBottom - mdTop

    ; Calculate new size.
    if (IsResizable()) {
        w := Round(w*(mdw/msw))
        h := Round(h*(mdh/msh))
    }

    ; Move window, using resolution difference to scale co-ordinates.
    WinMove, , , mdLeft + (x-msLeft)*(mdw/msw), mdTop + (y-msTop)*(mdh/msh), w, h

    if state = 1
        WinMaximize
}

;
; "Gather" windows on a specific screen.
;
GatherWindows(md=1)
{
    global ProcessGatherExcludeList

    SetWinDelay, 0

    ; List all visible windows.
    WinGet, win, List

    ; Copy bounds of all monitors to an array.
    SysGet, mc, MonitorCount
    Loop, %mc%
        SysGet, mon%A_Index%, MonitorWorkArea, %A_Index%

    if md = M
    {
        ; Special exception for 'M', since the desktop window
        ; spreads across all screens.
        CoordMode, Mouse, Screen
        MouseGetPos, x, y
        md := GetMonitorAt(x, y, 0)
    }
    else if md is not integer
    {
        ; Support A, P and WinTitle.
        ; (Gather at screen containing specified window.)
        WindowPad_WinExist(md)
        WinGetPos, x, y, w, h
        md := GetMonitorAt(x+w/2, y+h/2, 0)
    }
    if (md<1 or md>mc)
        return

    ; Destination monitor
    mdx := mon%md%Left
    mdy := mon%md%Top
    mdw := mon%md%Right - mdx
    mdh := mon%md%Bottom - mdy

    Loop, %win%
    {
        ; If this window matches the GatherExclude group, don't touch it.
        if (WinExist("ahk_group GatherExclude ahk_id " . win%A_Index%))
            continue

        ; Set Last Found Window.
        if (!WinExist("ahk_id " . win%A_Index%))
            continue

        WinGet, procname, ProcessName
        ; Check process (program) exclusion list.
        if procname in %ProcessGatherExcludeList%
            continue

        WinGetPos, x, y, w, h

        ; Determine which monitor this window is on.
        xc := x+w/2, yc := y+h/2
        ms := 0
        Loop, %mc%
            if (xc >= mon%A_Index%Left && xc <= mon%A_Index%Right
                && yc >= mon%A_Index%Top && yc <= mon%A_Index%Bottom)
            {
                ms := A_Index
                break
            }
        ; If already on destination monitor, skip this window.
        if (ms = md)
            continue

        WinGet, state, MinMax
        if (state = 1) {
            WinRestore
            WinGetPos, x, y, w, h
        }

        if ms
        {
            ; Source monitor
            msx := mon%ms%Left
            msy := mon%ms%Top
            msw := mon%ms%Right - msx
            msh := mon%ms%Bottom - msy

            ; If the window is resizable, scale it by the monitors' resolution difference.
            if (IsResizable()) {
                w *= (mdw/msw)
                h *= (mdh/msh)
            }

            ; Move window, using resolution difference to scale co-ordinates.
            WinMove, , , mdx + (x-msx)*(mdw/msw), mdy + (y-msy)*(mdh/msh), w, h
        }
        else
        {
            ; Window not on any monitor, move it to center.
            WinMove, , , mdx + (mdw-w)/2, mdy + (mdh-h)/2
        }

        if state = 1
            WinMaximize
    }
}

GetLastMinimizedWindow()
{
    WinGet, w, List

    Loop %w%
    {
        wi := w%A_Index%
        WinGet, m, MinMax, ahk_id %wi%
        if m = -1 ; minimized
        {
            lastFound := wi
            break
        }
    }

    return "ahk_id " . (lastFound ? lastFound : 0)
}

;================================== 功能代码 :由北风一叶提供
;下一个标签
<NextTab>:
    send ^{tab}
return

;上一个标签
<PrevTab>:
    send ^+{tab}
return

;关闭当前标签
<CloseTab>:
    send ^{w}
return

;新建标签
<NewTab>:
    send ^{t}
return

;激活第一个标签
<ActivateTab1>:
    send ^{1}
return

;激活第二个标签
<ActivateTab2>:
    send ^{2}
return

;激活第三个标签
<ActivateTab3>:
    send ^{3}
return

;激活第四个标签
<ActivateTab4>:
    send ^{4}
return

;激活第五个标签
<ActivateTab5>:
    send ^{5}
return

;激活第六个标签
<ActivateTab6>:
    send ^{6}
return

;激活第七个标签
<ActivateTab7>:
    send ^{7}
return

;激活第八个标签
<ActivateTab8>:
    send ^{8}
return

;激活第九个标签
<ActivateTab9>:
    send ^{8}^{tab}
return

;激活最后一个标签
<ActivateTab0>:
    send ^{9}
return

;清除 ToolTip 计时器
<RemoveToolTip>:
    Global showToolTipStatus

    SetTimer, <RemoveToolTip>, Off
    ToolTip
    showToolTipStatus := false
return

;进入、退出模式时显示提示
DisplayMode(ahk_class_name, mode_name)
{
    Global vim
    Global showToolTipStatus

    ToolTip, 进入 %mode_name% 模式
    showToolTipStatus := true

    SetTimer, <RemoveToolTip>, 500
}

ShowHelp()
{
    Global vim

    modeObj := vim.GetMode(vim.LastFoundWin)

    np := "help`n"
    np .= "=====================`n"

    for i, k in modeObj.keymapList
    {
        if (i >= 0 && i <= 9)
        {
            Continue
        }

        act := vim.GetAction(modeObj.GetKeyMap(i))
        if (act.Type = 1)
        {
            ActionDescList := act.Comment
            np .= i "`t"  %ActionDescList%[i] "`n"
        }
        else
        {
            np .= i "`t" act.Comment "`n"
        }
    }

    MouseGetPos, posx, posy, A
    posx += 40
    posy += 40
    Tooltip, %np%, %posx%, %posy%
}

<ShowHelp>:
    Global showToolTipStatus

    if (!showToolTipStatus)
    {
        ShowHelp()
    }
    else
    {
        ToolTip
    }

    showToolTipStatus := !showToolTipStatus
return

<RunZ>:
    if (FileExist(A_ScriptDir "\vimd.exe"))
    {
        Run, %A_ScriptDir%\vimd.exe "%A_ScriptDir%\tools\RunZ.ahk"
    }
    else
    {
        Run, %A_ScriptDir%\tools\RunZ.ahk
    }
return

<ToggleCapsLock>:
    GetKeyState, CapsLockState, CapsLock, T
    if CapsLockState = D
        SetCapsLockState, AlwaysOff
    else
        SetCapsLockState, AlwaysOn
return

<MouseUp>:
    MouseMove, 0, -10, 0, R
return

<MouseDown>:
    MouseMove, 0, 10, 0, R
return

<MouseLeft>:
    MouseMove, -10, 0, 0, R
return

<MouseRight>:
    MouseMove, 10, 0, 0, R
return

<SearchInWeb>:
    Run, https://www.baidu.com/s?wd=%clipboard%
return

<Test>:
    Msgbox, 测试
return

SwitchIME(dwLayout)
{
    HKL := DllCall("LoadKeyboardLayout", Str, dwLayout, UInt, 1)
    ControlGetFocus, ctl, A
    SendMessage, 0x50, 0, HKL, %ctl%, A
}

<SwitchToEngIME>:
    ; 下方代码可只保留一个
    SwitchIME(0x04090409) ; 英语(美国) 美式键盘
    SwitchIME(0x08040804) ; 中文(中国) 简体中文-美式键盘
return

<SwitchToEngIMEAndEsc>:
    Send, {esc}
    ; 下方代码可只保留一个
    SwitchIME(0x04090409) ; 英语(美国) 美式键盘
    SwitchIME(0x08040804) ; 中文(中国) 简体中文-美式键盘
return

General:
    Gen_Save_Win := []
    vim.SetAction("<down>", "向下移动")
    vim.SetAction("<up>", "向上移动")
    vim.SetAction("<left>", "向左移动")
    vim.SetAction("<right>", "向右移动")
    vim.SetAction("<home>", "home键")
    vim.SetAction("<end>", "end键")
    vim.SetAction("<Gen_InsertMode>", "插入模式")
    vim.SetAction("<Gen_NormalMode>", "浏览模式")
    vim.SetAction("<Gen_Toggle>", "启用/禁用vim热键(General插件)")
    vim.SetAction("<Reload>", "重新加载")
    vim.SetAction("<WindowMoveDown>", "窗口移动到下方")
    vim.SetAction("<WindowMoveUp>", "窗口移动到上方")
    vim.SetAction("<WindowMoveLeft>", "窗口移动到左侧")
    vim.SetAction("<WindowMoveRight>", "窗口移动到右侧")
    vim.SetAction("<WindowMoveCenter>", "窗口移动到中间")
    vim.SetAction("<WindowMax>", "最大化窗口")
    vim.SetAction("<WindowMin>", "最小化窗口")
    vim.SetAction("<WindowRestore>", "还原当前窗口")
    vim.SetAction("<FullScreen>", "全屏当前程序")
    vim.SetAction("<NextTab>", "下一个标签")
    vim.SetAction("<PrevTab>", "前一个标签")
    vim.SetAction("<CloseTab>", "关闭当前标签")
    vim.SetAction("<NewTab>", "新建标签")
    vim.SetAction("<ActivateTab1>", "激活第一个标签")
    vim.SetAction("<ActivateTab2>", "激活第二个标签")
    vim.SetAction("<ActivateTab3>", "激活第三个标签")
    vim.SetAction("<ActivateTab4>", "激活第四个标签")
    vim.SetAction("<ActivateTab5>", "激活第五个标签")
    vim.SetAction("<ActivateTab6>", "激活第六个标签")
    vim.SetAction("<ActivateTab7>", "激活第七个标签")
    vim.SetAction("<ActivateTab8>", "激活第八个标签")
    vim.SetAction("<ActivateTab9>", "激活第九个标签")
    vim.SetAction("<ActivateTab0>", "激活最后一个标签")
    vim.SetAction("<pgup>", "上一页")
    vim.SetAction("<pgdn>", "下一页")
    vim.SetAction("<enter>", "回车键")
    vim.SetAction("<space>", "空格键")
    vim.SetAction("<backspace>", "退格键")
    vim.SetAction("<Pass>", "屏蔽按键")
    vim.SetAction("<Media_Next>", "播放下一首")
    vim.SetAction("<Media_Prev>", "播放上一首")
    vim.SetAction("<Media_Play_Pause>", "播放/停止")
    vim.SetAction("<Media_Stop>", "停止播放")
    vim.SetAction("<RemoveToolTip>", "清除屏幕的ToolTip")
    vim.SetAction("<ShowHelp>", "显示所有按键帮助信息")
    vim.SetAction("<ToggleCapsLock>", "切换大小写")
    vim.SetAction("<MouseUp>", "向上移动鼠标")
    vim.SetAction("<MouseDown>", "向下移动鼠标")
    vim.SetAction("<MouseLeft>", "向左移动鼠标")
    vim.SetAction("<MouseRight>", "向右移动鼠标")
    vim.SetAction("<SearchInWeb>", "在网络搜索剪切板内容")
    vim.SetAction("<RunZ>", "打开RunZ工具")
    vim.SetAction("<Test>", "测试")

    vim.SetWin("General", "General")
    vim.SetMode("insert", "General")
    vim.map("<Esc>", "<Gen_NormalMode>", "General")
    vim.SetMode("normal", "General")
    vim.map("i", "<Gen_InsertMode>", "General")
    vim.map("0", "<0>", "General")
    vim.map("1", "<1>", "General")
    vim.map("2", "<2>", "General")
    vim.map("3", "<3>", "General")
    vim.map("4", "<4>", "General")
    vim.map("5", "<5>", "General")
    vim.map("6", "<6>", "General")
    vim.map("7", "<7>", "General")
    vim.map("8", "<8>", "General")
    vim.map("9", "<9>", "General")
    vim.map("k", "<up>", "General")
    vim.map("j", "<down>", "General")
    vim.map("k", "<up>", "General")
    vim.map("h", "<left>", "General")
    vim.map("l", "<right>", "General")
    vim.map("zj", "<WindowMoveDown>", "General")
    vim.map("zk", "<WindowMoveUp>", "General")
    vim.map("zh", "<WindowMoveLeft>", "General")
    vim.map("zl", "<WindowMoveRight>", "General")
    vim.map("zc", "<WindowMoveCenter>", "General")
    vim.map("zf", "<fullscreen>", "General")
    vim.map("zm", "<WindowMax>", "General")
    vim.map("zn", "<WindowMin>", "General")
    vim.map("zr", "<WindowRestore>", "General")
    vim.map("t", "<NewTab>", "General")
    vim.map("x", "<CloseTab>", "General")
    vim.map("gn", "<NextTab>", "General")
    vim.map("gp", "<PrevTab>", "General")
    vim.map("g1", "<ActivateTab1>", "General")
    vim.map("g2", "<ActivateTab2>", "General")
    vim.map("g3", "<ActivateTab3>", "General")
    vim.map("g4", "<ActivateTab4>", "General")
    vim.map("g5", "<ActivateTab5>", "General")
    vim.map("g6", "<ActivateTab6>", "General")
    vim.map("g7", "<ActivateTab7>", "General")
    vim.map("g8", "<ActivateTab8>", "General")
    vim.map("g9", "<ActivateTab9>", "General")
    vim.map("g0", "<ActivateTab0>", "General")
    vim.BeforeActionDo("Gen_Before", "General")
return

Gen_Before()
{
    Global vim
    WinGet, MenuID, ID, AHK_CLASS #32768
    If MenuID
        return True
}

<Gen_InsertMode>:
    vim.SetMode("Insert", vim.CheckWin())
    Tooltip, Insert模式, , , 19
    Settimer, cancelTooltip, 1200
return
<Gen_NormalMode>:
    vim.SetMode("Normal", vim.CheckWin())
    Tooltip, Normal模式, , , 19
    Settimer, cancelTooltip, 1200
return
<Gen_Toggle>:
    Gen_Toggle()
return
Gen_Toggle()
{
    Global vim, Gen_Save_Win
    WinGetClass, c, A
    WinGet, f, ProcessPath, ahk_class %c%
    WinName:=vim.CheckWin()
    If not Gen_Save_Win[c]
    {
        If Strlen(winName) And IsObject(vim.GetWin(winName))
        {
            MsgBox, , VimDesktop, 当前窗口 [ %winName% ] 已经拥有Vim模式`n不允许替换为通用(General)模式！
            return
        }
    }
    winObj := vim.GetWin(c)
    If not Isobject(winObj)
    {
        vim.copy("General", c, c, f)
        Gen_Save_Win[c] := True
        Tooltip, 复制VIMD热键到当前窗口成功, , , 19
    }
    Else
    {
        if vim.Toggle(c)
            Tooltip, 激活当前窗口的VIMD热键, , , 19
        else
            Tooltip, 取消当前窗口的VIMD热键, , , 19
    }
    Settimer, cancelTooltip, 1200
}
cancelTooltip:
    Tooltip, , , , 19
return
<reload>:
    reload
return
<Exit>:
    ExitApp
return
<down>:
    send, {down}
return
<up>:
    send, {up}
return
<left>:
    send, {left}
return
<right>:
    send, {right}
return
<home>:
    send, {home}
return
<end>:
    send, {end}
return
<pgup>:
    send, {pgup}
return
<pgdn>:
    send, {pgdn}
return
<enter>:
    send, {enter}
return
<space>:
    send, {space}
return
<backspace>:
    send, {backspace}
return
<Media_Next>:
    send, {Media_Next}
return
<Media_Prev>:
    send, {Media_Prev}
return
<Media_Stop>:
    send, {Media_Stop}
return
<Media_Play_Pause>:
    send, {Media_Play_Pause}
return

<WindowMoveDown>:
    WindowPadMove("0, +1, 1.0, 0.5")
Return
<WindowMoveUp>:
    WindowPadMove("0, -1, 1.0, 0.5")
Return
<WindowMoveLeft>:
    WindowPadMove("-1, 0, 0.5, 1.0")
Return
<WindowMoveRight>:
    WindowPadMove("+1, 0, 0.5, 1.0")
Return
<WindowMoveCenter>:
    WindowPadMove("0, 0, 0.5, 0.7")
return
<WindowMax>:
    WinMaximize, A
return
<WindowMin>:
    WinMinimize, A
return
<WindowRestore>:
    WinRestore, A
return

; 全屏当前程序
<FullScreen>:
    FullScreen()
return
FullScreen() {
    WinGet, windowID, ID, A
    WindowState := FullScreenID[windowID]
    If Strlen(windowState) = 0
        WindowState := 0
    If WindowState
    {
        WinSet, Style, ^0xC40000, ahk_id %WindowID%
        Loop, Parse, windowState, `,
        {
            If A_Index = 2
                WinPosX := A_LoopField
            If A_Index = 3
                WinPosY := A_LoopField
            If A_Index = 4
                WindowWidth := A_LoopField
            If A_Index = 5
                WindowHeight := A_LoopField
        }
        WinMove, ahk_id %WindowID%, , WinPosX, WinPosY, WindowWidth, WindowHeight
        WindowState := 0
    }
    Else
    {
        WinGetPos, WinPosX, WinPosY, WindowWidth, WindowHeight, ahk_id %WindowID%
        WinSet, Style, ^0xC40000, ahk_id %WindowID%
        WinMove, ahk_id %WindowID%, , 0, 0, A_ScreenWidth, A_ScreenHeight
        WindowState := 1 ", " WinPosX ", " WinPosY ", " WindowWidth ", " WindowHeight
        ;Msgbox 再按一下刚刚的热键退出全屏
    }
    FullScreenID[windowID] := WindowState
}

;====================================================================================
;
; WindowPad.ahk
; Version: 0.4
; Last Updated: 11/5/2009
;
; This file contains functions which are useful for manipulating and moving windows.
; This is a stripped down version of WindowPad v.1.56 by Lexikos.
; Downloaded from http://www.autohotkey.com/forum/topic21703.html
;

WindowPadMove(P)
{
    StringSplit, P, P, `, , %A_Space%%A_Tab%
    ; Params: 1:dirX, 2:dirY, 3:widthFactor, 4:heightFactor, 5:window

    if P1 =
        P1 = R
    if P2 =
        P2 = R

    WindowPad_WinExist(P5)

    if !WinExist()
        return

    ; Determine width/height factors.
    if (P1 or P2) {
        ; to a side
        widthFactor := P3+0 ? P3 : (P1 ? 0.5 : 1.0)
        heightFactor := P4+0 ? P4 : (P2 ? 0.5 : 1.0)
    } else {
        ; to center
        widthFactor  := P3+0 ? P3 : 1.0
        heightFactor := P4+0 ? P4 : 1.0
    }

    ; Move the window!
    MoveWindowInDirection(P1, P2, widthFactor, heightFactor)
}

MaximizeToggle(P)
{
    WindowPad_WinExist(P)

    WinGet, state, MinMax
    if state
        WinRestore
    else
        WinMaximize
}

; Does the grunt work of the script.
MoveWindowInDirection(sideX, sideY, widthFactor, heightFactor)
{
    WinGetPos, x, y, w, h

    hwnd:=WinExist()
    if (can_restore := GetWindowProperty(hwnd, "wpHasRestorePos"))
    {   ; Window has restore info. Check if it is where we last put it.
        last_x := GetWindowProperty(hwnd, "wpLastX")
        last_y := GetWindowProperty(hwnd, "wpLastY")
        last_w := GetWindowProperty(hwnd, "wpLastW")
        last_h := GetWindowProperty(hwnd, "wpLastH")
    }
    if (can_restore && last_x = x && last_y = y && last_w = w && last_h = h)
    {   ; Window is where we last put it. Check if user wants to restore.
        if SubStr(sideX, 1, 1) = "R"
        {   ; Restore on X-axis.
            restore_x := GetWindowProperty(hwnd, "wpRestoreX")
            restore_w := GetWindowProperty(hwnd, "wpRestoreW")
            StringTrimLeft, sideX, sideX, 1
        }
        if SubStr(sideY, 1, 1) = "R"
        {   ; Restore on Y-axis.
            restore_y := GetWindowProperty(hwnd, "wpRestoreY")
            restore_h := GetWindowProperty(hwnd, "wpRestoreH")
            StringTrimLeft, sideY, sideY, 1
        }
        if (restore_x != "" || restore_y != "")
        {   ; If already at the "restored" size and position, do the normal thing instead.
            if ((restore_x = x || restore_x = "") && (restore_y = y || restore_y = "")
                && (restore_w = w || restore_w = "") && (restore_h = h || restore_h = ""))
            {
                restore_x =
                restore_y =
                restore_w =
                restore_h =
            }
        }
    }
    else
    {   ; Next time user requests the window be "restored" use this position and size.
        SetWindowProperty(hwnd, "wpHasRestorePos", true)
        SetWindowProperty(hwnd, "wpRestoreX", x)
        SetWindowProperty(hwnd, "wpRestoreY", y)
        SetWindowProperty(hwnd, "wpRestoreW", w)
        SetWindowProperty(hwnd, "wpRestoreH", h)

        if SubStr(sideX, 1, 1) = "R"
            StringTrimLeft, sideX, sideX, 1
        if SubStr(sideY, 1, 1) = "R"
            StringTrimLeft, sideY, sideY, 1
    }

    ; If no direction specified, restore or only switch monitors.
    if (sideX+0 = "" && restore_x = "")
        restore_x := x, restore_w := w
    if (sideY+0 = "" && restore_y = "")
        restore_y := y, restore_h := h

    ; Determine which monitor contains the center of the window.
    m := GetMonitorAt(x+w/2, y+h/2)

    ; Get work area of active monitor.
    gosub CalcMonitorStats
    ; Calculate possible new position for window.
    gosub CalcNewSizeAndPosition

    ; If the window is already there,
    if (newx ", " newy ", " neww ", " newh) = (x ", " y ", " w ", " h)
    {   ; ..move to the next monitor along instead.

        if (sideX or sideY)
        {   ; Move in the direction of sideX or sideY.
            SysGet, monB, Monitor, %m% ; get bounds of entire monitor (vs. work area)
            x := (sideX=0) ? (x+w/2) : (sideX>0 ? monBRight : monBLeft) + sideX
            y := (sideY=0) ? (y+h/2) : (sideY>0 ? monBBottom : monBTop) + sideY
            newm := GetMonitorAt(x, y, m)
        }
        else
        {   ; Move to center (Numpad5)
            newm := m+1
            SysGet, mon, MonitorCount
            if (newm > mon)
                newm := 1
        }

        if (newm != m)
        {
            m := newm
            ; Move to opposite side of monitor (left of a monitor is another monitor's right edge)
            sideX *= -1
            sideY *= -1
            ; Get new monitor's work area.
            gosub CalcMonitorStats
        }
        else
        {
            ; No monitor to move to, alternate size of window instead.
            if sideX
                widthFactor /= 2
            else if sideY
                heightFactor /= 2
            else
                widthFactor *= 1.5
            gosub CalcNewSizeAndPosition
        }

        ; Calculate new position for window.
        gosub CalcNewSizeAndPosition
    }

    ; Restore before resizing...
    WinGet, state, MinMax
    if state
        WinRestore

    WinDelay := A_WinDelay
    SetWinDelay, 0

    ; Move the window!
    WinMove, , , newx, newy, neww, newh

    ; For console windows and other windows which have size restrictions, check
    ; that the window was actually resized. If not, reposition based on actual size.
    WinGetPos, newx, newy, realw, realh
    if (neww != realw || newh != realh)
    {
        neww := realw
        newh := realh
        gosub CalcNewPosition
        if ((newm = "" || newm = m) && (newx ", " newy ", " realw ", " realh)=(x ", " y ", " w ", " h))
        {
            if sideX
                neww //= 2
            else if sideY
                newh //= 2
            else
                neww := Round(neww*1.5)
            ; Size first, since the window size will probably be restricted in some way.
            WinMove, , , , , neww, newh
            WinGetPos, , , neww, newh
            gosub CalcNewPosition
        }
        WinMove, , , newx, newy
    }

    ; Explorer uses WM_EXITSIZEMOVE to detect when a user finishes moving a window
    ; in order to save the position for next time. May also be used by other apps.
    PostMessage, 0x232

    SetWinDelay, WinDelay

    ; Remember where we put it, to detect if the user moves it.
    SetWindowProperty(hwnd, "wpLastX", newx)
    SetWindowProperty(hwnd, "wpLastY", newy)
    SetWindowProperty(hwnd, "wpLastW", neww)
    SetWindowProperty(hwnd, "wpLastH", newh)
    return

CalcNewSizeAndPosition:
    ; Calculate new size.
    if (IsResizable()) {
        neww := restore_w != "" ? restore_w : Round(monWidth * widthFactor)
        newh := restore_h != "" ? restore_h : Round(monHeight * heightFactor)
    } else {
        neww := w
        newh := h
    }
CalcNewPosition:
    ; Calculate new position.
    newx := restore_x != "" ? restore_x : Round(monLeft + (sideX+1) * (monWidth  - neww)/2)
    newy := restore_y != "" ? restore_y : Round(monTop  + (sideY+1) * (monHeight - newh)/2)
    return

CalcMonitorStats:
    ; Get work area (excludes taskbar-reserved space.)
    SysGet, mon, MonitorWorkArea, %m%
    monWidth  := monRight - monLeft
    monHeight := monBottom - monTop
    return
}

; Get/set window property. type should be int, uint or float.
GetWindowProperty(hwnd, property_name, type="int") {
    return DllCall("GetProp", "uint", hwnd, "str", property_name, type)
}
SetWindowProperty(hwnd, property_name, data, type="int") {
    return DllCall("SetProp", "uint", hwnd, "str", property_name, type, data)
}

; Get the index of the monitor containing the specified x and y co-ordinates.
GetMonitorAt(x, y, default=1)
{
    SysGet, m, MonitorCount
    ; Iterate through all monitors.
    Loop, %m%
    {
        ; Check if the window is on this monitor.
        SysGet, Mon, Monitor, %A_Index%
        if (x >= MonLeft && x <= MonRight && y >= MonTop && y <= MonBottom)
            return A_Index
    }

    return default
}

IsResizable()
{
    WinGetClass, Class
    if Class = Chrome_XPFrame
        return true
    if Class = ConsoleWindowClass
        return false
    WinGet, Style, Style
    return (Style & 0x40000) ; WS_SIZEBOX
}

WindowPad_WinExist(WinTitle)
{
    if WinTitle = P
        return WinPreviouslyActive()
    if WinTitle = M
    {
        MouseGetPos, , , win
        return WinExist("ahk_id " win)
    }
    return WinExist(WinTitle!="" ? WinTitle : "A")
}

; Note: This may not work properly with always-on-top windows. (Needs testing)
WinPreviouslyActive()
{
    active := WinActive("A")
    WinGet, win, List

    ; Find the active window.
    ; (Might not be win1 if there are always-on-top windows?)
    Loop, %win%
        if (win%A_Index% = active)
        {
            if (A_Index < win)
                N := A_Index+1

            ; hack for PSPad: +1 seems to get the document (child!) window, so do +2
            ifWinActive, ahk_class TfPSPad
                N += 1

            break
        }

    ; Use WinExist to set Last Found Window (for consistency with WinActive())
    return WinExist("ahk_id " . win%N%)
}

;
; Switch without moving/resizing (relative to screen)
;
WindowScreenMove(P)
{
    SetWinDelay, 0

    StringSplit, P, P, `, , %A_Space%%A_Tab%
    ; 1:Next|Prev|Num, 2:Window

    WindowPad_WinExist(P2)

    WinGet, state, MinMax
    if state = 1
        WinRestore

    WinGetPos, x, y, w, h

    ; Determine which monitor contains the center of the window.
    ms := GetMonitorAt(x+w/2, y+h/2)

    SysGet, mc, MonitorCount

    ; Determine which monitor to move to.
    if P1 in , N, Next
    {
        md := ms+1
        if (md > mc)
            md := 1
    }
    else if P1 in P, Prev, Previous
    {
        md := ms-1
        if (md < 1)
            md := mc
    }
    else if P1 is integer
        md := P1

    if (md=ms or (md+0)="" or md<1 or md>mc)
        return

    ; Get source and destination work areas (excludes taskbar-reserved space.)
    SysGet, ms, MonitorWorkArea, %ms%
    SysGet, md, MonitorWorkArea, %md%
    msw := msRight - msLeft, msh := msBottom - msTop
    mdw := mdRight - mdLeft, mdh := mdBottom - mdTop

    ; Calculate new size.
    if (IsResizable()) {
        w := Round(w*(mdw/msw))
        h := Round(h*(mdh/msh))
    }

    ; Move window, using resolution difference to scale co-ordinates.
    WinMove, , , mdLeft + (x-msLeft)*(mdw/msw), mdTop + (y-msTop)*(mdh/msh), w, h

    if state = 1
        WinMaximize
}

;
; "Gather" windows on a specific screen.
;
GatherWindows(md=1)
{
    global ProcessGatherExcludeList

    SetWinDelay, 0

    ; List all visible windows.
    WinGet, win, List

    ; Copy bounds of all monitors to an array.
    SysGet, mc, MonitorCount
    Loop, %mc%
        SysGet, mon%A_Index%, MonitorWorkArea, %A_Index%

    if md = M
    {
        ; Special exception for 'M', since the desktop window
        ; spreads across all screens.
        CoordMode, Mouse, Screen
        MouseGetPos, x, y
        md := GetMonitorAt(x, y, 0)
    }
    else if md is not integer
    {
        ; Support A, P and WinTitle.
        ; (Gather at screen containing specified window.)
        WindowPad_WinExist(md)
        WinGetPos, x, y, w, h
        md := GetMonitorAt(x+w/2, y+h/2, 0)
    }
    if (md<1 or md>mc)
        return

    ; Destination monitor
    mdx := mon%md%Left
    mdy := mon%md%Top
    mdw := mon%md%Right - mdx
    mdh := mon%md%Bottom - mdy

    Loop, %win%
    {
        ; If this window matches the GatherExclude group, don't touch it.
        if (WinExist("ahk_group GatherExclude ahk_id " . win%A_Index%))
            continue

        ; Set Last Found Window.
        if (!WinExist("ahk_id " . win%A_Index%))
            continue

        WinGet, procname, ProcessName
        ; Check process (program) exclusion list.
        if procname in %ProcessGatherExcludeList%
            continue

        WinGetPos, x, y, w, h

        ; Determine which monitor this window is on.
        xc := x+w/2, yc := y+h/2
        ms := 0
        Loop, %mc%
            if (xc >= mon%A_Index%Left && xc <= mon%A_Index%Right
                && yc >= mon%A_Index%Top && yc <= mon%A_Index%Bottom)
            {
                ms := A_Index
                break
            }
        ; If already on destination monitor, skip this window.
        if (ms = md)
            continue

        WinGet, state, MinMax
        if (state = 1) {
            WinRestore
            WinGetPos, x, y, w, h
        }

        if ms
        {
            ; Source monitor
            msx := mon%ms%Left
            msy := mon%ms%Top
            msw := mon%ms%Right - msx
            msh := mon%ms%Bottom - msy

            ; If the window is resizable, scale it by the monitors' resolution difference.
            if (IsResizable()) {
                w *= (mdw/msw)
                h *= (mdh/msh)
            }

            ; Move window, using resolution difference to scale co-ordinates.
            WinMove, , , mdx + (x-msx)*(mdw/msw), mdy + (y-msy)*(mdh/msh), w, h
        }
        else
        {
            ; Window not on any monitor, move it to center.
            WinMove, , , mdx + (mdw-w)/2, mdy + (mdh-h)/2
        }

        if state = 1
            WinMaximize
    }
}

GetLastMinimizedWindow()
{
    WinGet, w, List

    Loop %w%
    {
        wi := w%A_Index%
        WinGet, m, MinMax, ahk_id %wi%
        if m = -1 ; minimized
        {
            lastFound := wi
            break
        }
    }

    return "ahk_id " . (lastFound ? lastFound : 0)
}

;================================== 功能代码 :由北风一叶提供
;下一个标签
<NextTab>:
    send ^{tab}
return

;上一个标签
<PrevTab>:
    send ^+{tab}
return

;关闭当前标签
<CloseTab>:
    send ^{w}
return

;新建标签
<NewTab>:
    send ^{t}
return

;激活第一个标签
<ActivateTab1>:
    send ^{1}
return

;激活第二个标签
<ActivateTab2>:
    send ^{2}
return

;激活第三个标签
<ActivateTab3>:
    send ^{3}
return

;激活第四个标签
<ActivateTab4>:
    send ^{4}
return

;激活第五个标签
<ActivateTab5>:
    send ^{5}
return

;激活第六个标签
<ActivateTab6>:
    send ^{6}
return

;激活第七个标签
<ActivateTab7>:
    send ^{7}
return

;激活第八个标签
<ActivateTab8>:
    send ^{8}
return

;激活第九个标签
<ActivateTab9>:
    send ^{8}^{tab}
return

;激活最后一个标签
<ActivateTab0>:
    send ^{9}
return

;清除 ToolTip 计时器
<RemoveToolTip>:
    Global showToolTipStatus

    SetTimer, <RemoveToolTip>, Off
    ToolTip
    showToolTipStatus := false
return

;进入、退出模式时显示提示
DisplayMode(ahk_class_name, mode_name)
{
    Global vim
    Global showToolTipStatus

    ToolTip, 进入 %mode_name% 模式
    showToolTipStatus := true

    SetTimer, <RemoveToolTip>, 500
}

ShowHelp()
{
    Global vim

    modeObj := vim.GetMode(vim.LastFoundWin)

    np := "help`n"
    np .= "=====================`n"

    for i, k in modeObj.keymapList
    {
        if (i >= 0 && i <= 9)
        {
            Continue
        }

        act := vim.GetAction(modeObj.GetKeyMap(i))
        if (act.Type = 1)
        {
            ActionDescList := act.Comment
            np .= i "`t"  %ActionDescList%[i] "`n"
        }
        else
        {
            np .= i "`t" act.Comment "`n"
        }
    }

    MouseGetPos, posx, posy, A
    posx += 40
    posy += 40
    Tooltip, %np%, %posx%, %posy%
}

<ShowHelp>:
    Global showToolTipStatus

    if (!showToolTipStatus)
    {
        ShowHelp()
    }
    else
    {
        ToolTip
    }

    showToolTipStatus := !showToolTipStatus
return

<RunZ>:
    Run, %A_ScriptDir%\tools\RunZ.ahk
return

<ToggleCapsLock>:
    GetKeyState, CapsLockState, CapsLock, T
    if CapsLockState = D
        SetCapsLockState, AlwaysOff
    else
        SetCapsLockState, AlwaysOn
return

<MouseUp>:
    MouseMove, 0, -10, 0, R
return

<MouseDown>:
    MouseMove, 0, 10, 0, R
return

<MouseLeft>:
    MouseMove, -10, 0, 0, R
return

<MouseRight>:
    MouseMove, 10, 0, 0, R
return

<SearchInWeb>:
    Run, https://www.baidu.com/s?wd=%clipboard%
return

<Test>:
    Msgbox, 测试
return

SwitchIME(dwLayout)
{
    HKL := DllCall("LoadKeyboardLayout", Str, dwLayout, UInt, 1)
    ControlGetFocus, ctl, A
    SendMessage, 0x50, 0, HKL, %ctl%, A
}

<SwitchToEngIME>:
    ; 下方代码可只保留一个
    SwitchIME(0x04090409) ; 英语(美国) 美式键盘
    SwitchIME(0x08040804) ; 中文(中国) 简体中文-美式键盘
return

<SwitchToEngIMEAndEsc>:
    Send, {esc}
    ; 下方代码可只保留一个
    SwitchIME(0x04090409) ; 英语(美国) 美式键盘
    SwitchIME(0x08040804) ; 中文(中国) 简体中文-美式键盘
return


Add note

Add note

FileEncoding, UTF-8 ; This file needs to be saved/encoded with "UTF-8 with BOM"

; Use Swedish characters on US-English keyboard
![:: Send, å
!+{:: Send, Å
!':: Send, ä
!+':: Send, Ä
!;:: Send, ö
!+;:: Send, Ö  ; does not work

; HHKB settings to mimic macOS behavior on Windows
; Lwin = <^
LWin::LCtrl
<^Right:: Send, {End}
<^Left:: Send, {Home}
<^Up:: Send, {PgUp}
<^Down:: Send, {PgDn}
<^Tab::Send {LWin down}{Tab}{LWin up}

FileEncoding, UTF-8 ; This file needs to be saved/encoded with "UTF-8 with BOM"

; Use Swedish characters on US-English keyboard
![:: Send, å
!+{:: Send, Å
!':: Send, ä
!+':: Send, Ä
!;:: Send, ö
!+;:: Send, Ö  ; does not work

; HHKB settings to mimic macOS behavior on Windows
; Leave righ hand side modifier keys at their defaults
LWin::LCtrl
<^Right:: Send, {End}
<^Left:: Send, {Home}
<^Up:: Send, {PgUp}
<^Down:: Send, {PgDn}
<^Tab::Send {LWin down}{Tab}{LWin up}


Moved monster clicker click location

Moved monster clicker click location

; -----------------------------------------------------------------------------------------
; Clicker Heroes Sw1ft Bot Lib
; by Sw1ftb
; -----------------------------------------------------------------------------------------

CoordMode, Pixel, Screen

libVersion=4.0

winName := "Clicker Heroes"

global ProgressBar, ProgressBarTime ; progress bar controls

exitThread := false
exitDRThread := false

chWinId := ""

; All the script coordinates are based on these four default dimensions.
chWidth := 1136
chHeight := 640
chMargin := 8
chTopMargin := 30

chTotalWidth := chWidth + chMargin * 2
chTotalHeight := chHeight + chMargin + chTopMargin

; Calculated
xScreenL := 0
yScreenT := 0
xScreenR := 0
yScreenB := 0

; Calculated
leftMarginOffset := 0
topMarginOffset := 0

; Calculated
aspectRatio := 1
hBorder := 0
vBorder := 0

zzz := 175 ; sleep delay (in ms) after a click
lvlUpDelay := 4 ; time (in seconds) between lvl up clicks
barUpdateDelay := 30 ; time (in seconds) between progress bar updates
coinPickUpDelay := 5 ; time (in seconds) needed to pick up all coins from a clickable
nextHeroDelay := 5 ; extra gold farm delay (in seconds) between heroes

scrollDelay := 325 ; base delay (in ms)
scrollClickDelay := 20 ; delay per click (in ms)

dialogBoxClass := "#32770"

blueColor := 0x60BEFF
yellowColor := 0xFECB00
dimmedYellowColor := 0x7E6500
goldColor := 0xFFB423
brightGoldColor := 0xFFD911

severityLevels := {"OFF":0, "WARN":1, "USER":2, "INFO":3, "DEBUG":4, "TRACE":5}

gameModes := {"INIT":0, "PROGRESSING":1, "FARMING":2, "FIGHTING":3}

; -- Images -------------------------------------------------------------------------------

; Combat zone offsets
CZTO := 170
CZLO := 500
CZBO := CZTO - chHeight
CZRO := CZLO - chWidth

imageFilePath := "images\"

imgSmile := {file:"smile.png", topOffset:0, leftOffset:1090, bottomOffset:0, rightOffset:0}
imgQuality := {file:"quality.png", topOffset:0, leftOffset:1090, bottomOffset:0, rightOffset:0}
imgProgression := {file:"progression.png", topOffset:0, leftOffset:1090, bottomOffset:0, rightOffset:0}
imgAscend := {file:"ascend.png", topOffset:0, leftOffset:1090, bottomOffset:0, rightOffset:0}

imgClickable := {file:"clickable.png", topOffset:CZTO, leftOffset:CZLO, bottomOffset:0, rightOffset:0}
imgBoss := {file:"boss_clock.png", topOffset:155, leftOffset:805, bottomOffset:-445, rightOffset:-290}

imgSkillBar := {file:"skill_bar.png", topOffset:0, leftOffset:575, bottomOffset:0, rightOffset:-496}
imgSkillLocked := {file:"skill_locked.png", topOffset:0, leftOffset:575, bottomOffset:0, rightOffset:-496}

imgCombatTab := {file:"combat_tab.png", topOffset:0, leftOffset:0, bottomOffset:CZBO, rightOffset:CZRO}

imgHire := {file:"hire.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgCoin := {file:"coin.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgMaxLvl := {file:"max_lvl.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgDimmedSkill := {file:"skill_dimmed.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgSkill := {file:"skill.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgChefBuff := {file:"way_of_the_chef.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgKingsBuff := {file:"way_of_kings.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgCid := {file:"cid.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgDK := {file:"dk.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgDKG := {file:"dk_g.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgMax := {file:"max.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgGog := {file:"gog.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgSolomon := {file:"solomon.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgGilded := {file:"gilded.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgBuyUpgrades := {file:"upgrades.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgClanRaid := {file:"clan_raid.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgClanFight := {file:"clan_fight.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgClanFightAgain := {file:"clan_fight_again.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgClanCollect := {file:"clan_collect.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgYes := {file:"yes.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:0}
imgClose := {file:"close.png", topOffset:0, leftOffset:575, bottomOffset:0, rightOffset:0}

; TODO: REMOVE AFTER BETA
imgWarning := {file:"w.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

; -- Coordinates --------------------------------------------------------------------------

; Top LVL UP button when scrolled to the bottom
xLvl := 80
yLvl := 285
oLvl := 107 ; offset to next button

buttonSize := 34

; Progression/Farm Mode
xMode := 1121
yMode := 281

; 0.23 ascend button
xAscend := 1121
yAscend := 322

; Ascend Yes button
xYes := 500
yYes := 510 ; redesigned ascend window

oTab := 69 ; offset to next tab
xCombatTab := 52
xAncientTab := xCombatTab + oTab*3
xRelicTab := xCombatTab + oTab*4 + 10 ; Halloween fix
xClanTab := xCombatTab + oTab*5
yTab := 130

xRelic := 103
yRelic := 380

xUpgradeNo := 660
yUpgradeNo := 580

xSalvageJunk := 280
ySalvageJunk := 470

xDestroyYes := 500
yDestroyYes := 430

; Scrollbar
xScroll := 554
yUp := 219
yDown := 653
top2BottomClicks := 45

xGilded := 135
yGilded := 582

xGildedDown := 1060
yGildedDown := 493

xGildedClose := 1090
yGildedClose := 54

rangers := ["Dread Knight", "Atlas", "Terra", "Phthalo", "Banana", "Lilin", "Cadmia", "Alabaster", "Astraea", "Chiron", "Moloch", "Bomber Max", "Gog", "Wepwawet", "Betty", "Midas"]

rangerPositions := {}
rangerPositions[1] := {x:445, y:290} ; Dread Knight
rangerPositions[2] := {x:675, y:290} ; Atlas
rangerPositions[3] := {x:900, y:290} ; Terra
rangerPositions[4] := {x:220, y:355} ; Phthalo
rangerPositions[5] := {x:445, y:355} ; Banana
rangerPositions[6] := {x:675, y:355} ; Lilin
rangerPositions[7] := {x:900, y:355} ; Cadmia
rangerPositions[8] := {x:220, y:425} ; Alabaster
rangerPositions[9] := {x:445, y:425} ; Astraea
rangerPositions[10] := {x:675, y:425} ; Chiron
rangerPositions[11] := {x:900, y:425} ; Moloch
rangerPositions[12] := {x:220, y:490} ; Bomber Max
rangerPositions[13] := {x:445, y:490} ; Gog
rangerPositions[14] := {x:675, y:490} ; Wepwawet

; Buy Available Upgrades button
xBuy := 370
yBuy := 582

xFocus := 564
yFocus := 69

xMonster := 1010
yMonster := 164

; Safety zones (script will pause when entering)
safetyZones := {}
safetyZones[1] := {x1:8, y1:104, x2:575, y2:153} ; tabs
safetyZones[2] := {x1:1096, y1:29, x2:1144, y2:74} ; settings
safetyZones[3] := {x1:678, y1:600, x2:949, y2:670} ; sale + shop
safetyZones[4] := {x1:79, y1:554, x2:194, y2:612} ; gilded

; The wrench
xSettings := 1121
ySettings := 52

xSettingsClose := 961
ySettingsClose := 52

xSave := 286
ySave := 112

xSkill := 201
oSkill := 36 ; offset to next skill
ySkill2nd := 410 ; at bottom

xPrevZone := 679
xMiddleZone := 858
xPlusOneZone := 922
xNextZone := 1044
yZone := 70

xNewGild := 1105
yNewGild := 560
xOpenGild := 570
yOpenGild := 360
xCloseGild := 924
yCloseGild := 133

xBuyRubiesClose := 1081
yBuyRubiesClose := 96

; -----------------------------------------------------------------------------------------

; Load system default settings
#Include system\ch_bot_lib_default_settings.ahk

IfNotExist, ch_bot_lib_settings.ahk
{
	FileCopy, system\ch_bot_lib_default_settings.ahk, ch_bot_lib_settings.ahk
}

#Include *i ch_bot_lib_settings.ahk

; -----------------------------------------------------------------------------------------
; -- Functions
; -----------------------------------------------------------------------------------------

clientCheck() {
	global
	if (!isBrowserClient()) {
		calculateSteamAspectRatio() ; Steam
	} else {
		local xPos, yPos
		calculateBrowserOffsets() ; Browser
		fullScreenOption := false

		if (useImageSearch and locateImage(imgQuality, xPos, yPos)) {
			showTraceSplash("Switching to low quality")
			clickPos(xPos, yPos, 1, 1)
		}
	}
}

calculateBrowserOffsets() {
	global
	local w, h
	winName := "Lvl.*Clicker Heroes.*" . browser
	IfWinExist, % winName
	{
		showDebugSplash("Calculating browser offsets (" . scriptName . ")")
		WinActivate
		WinGetPos, xWinPos, yWinPos, w, h
		WinGet, chWinId, ID, A

		local leftMargin := (w - chWidth) // 2
		leftMarginOffset := leftMargin - chMargin
		topMarginOffset := browserTopMargin - chTopMargin

		xScreenL := xWinPos + leftMargin
		yScreenT := yWinPos + browserTopMargin
		xScreenR := xScreenL + chWidth
		yScreenB := yScreenT + chHeight
	} else {
		showWarningSplash("Clicker Heroes started in " . browser . "?")
	}
}

calculateSteamAspectRatio() {
	global
	local w, h
	IfWinExist, % winName
	{
		WinActivate
		WinGetPos, xWinPos, yWinPos, w, h
		WinGet, chWinId, ID, A

		; Fullscreen sanity checks
		if (fullScreenOption) {
			if (w <> A_ScreenWidth || h <> A_ScreenHeight) {
				showWarningSplash("The fullScreenOption should be set to false!")
				return
			}
		} else if (w = A_ScreenWidth && h = A_ScreenHeight) {
			showWarningSplash("The fullScreenOption should be set to true!")
			return
		}

		if (w != chTotalWidth || h != chTotalHeight) {
			showDebugSplash("Calculating Steam aspect ratio (" . scriptName . ")")

			local winWidth := fullScreenOption ? w : w - 2 * chMargin
			local winHeight := fullScreenOption ? h : h - chTopMargin - chMargin
			local horizontalAR := winWidth/chWidth
			local verticalAR := winHeight/chHeight

			; Take the lowest aspect ratio and calculate border size
			if (horizontalAR < verticalAR) {
				aspectRatio := horizontalAR
				vBorder := (winHeight - chHeight * aspectRatio) // 2
			} else {
				aspectRatio := verticalAR
				hBorder := (winWidth - chWidth * aspectRatio) // 2
			}
		}

		xScreenL := fullScreenOption ? xWinPos : xWinPos + chMargin
		yScreenT := fullScreenOption ? yWinPos : yWinPos + chTopMargin
		xScreenR := fullScreenOption ? xWinPos + w : xWinPos + w - chMargin
		yScreenB := fullScreenOption ? yWinPos + h : yWinPos + h - chMargin
	} else {
		showWarningSplash("Clicker Heroes started in Steam?")
	}
}

switchToCombatTab() {
	global
	if (useImageSearch and locateImage(imgCombatTab)) {
		clickAwayImage(imgCombatTab)
	} else {
		clickPos(xCombatTab, yTab)
		sleep % zzz * 4
	}
}

switchToAncientTab() {
	global
	clickPos(xAncientTab, yTab)
	sleep % zzz * 2
}

switchToRelicTab() {
	global
	clickPos(xRelicTab, yTab)
	sleep % zzz * 2
}

switchToClanTab() {
	global
	clickPos(xClanTab, yTab)
	sleep % zzz * 2
}

scrollToTop() {
	global
	clickPos(xScroll, yUp, top2BottomClicks)
	sleep % scrollDelay + top2BottomClicks * scrollClickDelay
}

scrollToBottom() {
	global
	clickPos(xScroll, yDown, top2BottomClicks)
	sleep % scrollDelay + top2BottomClicks * scrollClickDelay
}

scrollUp(clickCount:=1) {
	global
	clickPos(xScroll, yUp, clickCount)
	sleep % scrollDelay + clickCount * scrollClickDelay
}

scrollDown(clickCount:=1) {
	global
	clickPos(xScroll, yDown, clickCount)
	sleep % scrollDelay + clickCount * scrollClickDelay
}

; Scroll down fix when at bottom and scroll bar don't update correctly
scrollWayDown(clickCount:=1) {
	global
	scrollUp()
	scrollDown(clickCount + 1)
	sleep % nextHeroDelay * 1000
}

maxClick(xCoord, yCoord, clickCount:=1, absolute:=0) {
	global
	ControlSend,, {shift down}{vk51 down}, ahk_id %chWinId% ; {q}, {vk51} or {sc010}
	clickPos(xCoord, yCoord, clickCount, absolute)
	ControlSend,, {vk51 up}{shift up}, ahk_id %chWinId%
	sleep % zzz
}

ctrlClick(xCoord, yCoord, clickCount:=1, sleepSome:=1, absolute:=0) {
	global
	ControlSend,, {ctrl down}, ahk_id %chWinId%
	clickPos(xCoord, yCoord, clickCount, absolute)
	ControlSend,, {ctrl up}, ahk_id %chWinId%
	if (sleepSome) {
		sleep % zzz
	}
}

zClick(xCoord, yCoord, clickCount:=1, absolute:=0) {
	global
	ControlSend,, {vk5A down}, ahk_id %chWinId% ; {z}, {vk5A} or {sc02C}
	clickPos(xCoord, yCoord, clickCount, absolute)
	ControlSend,, {vk5A up}, ahk_id %chWinId%
	sleep % zzz
}

clickPos(xCoord, yCoord, clickCount:=1, absolute:=0) {
	global
	local xAdj := absolute ? xCoord : getAdjustedX(xCoord)
	local yAdj := absolute ? yCoord : getAdjustedY(yCoord)
 	ControlClick, x%xAdj% y%yAdj%, ahk_id %chWinId%,,, %clickCount%, NA
}

getAdjustedX(x) {
	global
	local leftMargin := fullScreenOption ? 0 : chMargin + leftMarginOffset
	return round(aspectRatio*(x - chMargin) + leftMargin + hBorder)
}

getAdjustedY(y) {
	global
	local topMargin := fullScreenOption ? 0 : chTopMargin + topMarginOffset
	return round(aspectRatio*(y - chTopMargin) + topMargin + vBorder)
}

playNotificationSound() {
	if (playNotificationSounds) {
		SoundPlay, %A_WinDir%\Media\Windows User Account Control.wav
	}
}

playWarningSound() {
	if (playWarningSounds) {
		SoundPlay, %A_WinDir%\Media\tada.wav
	}
}

showTraceSplash(text, seconds:=1) {
	showSplash(text, seconds, 0, "TRACE")
}

showDebugSplash(text, seconds:=1) {
	showSplash(text, seconds, 0, "DEBUG")
}

showUserSplash(text, seconds:=2) {
	showSplash(text, seconds, 1, "USER")
}

showWarningSplash(text, seconds:=4) {
	showSplash(text, seconds, 2, "WARN")
}

showSplash(text, seconds:=2, sound:=1, level="INFO") {
	global
	if (seconds > 0) {
		if (severityLevels[level] <= showSeverityLevel) {
			progress,% "w" wSplash " x" xSplash " y" ySplash " zh0 fs10", %text%,,% script
		}
		if (sound = 1) {
			playNotificationSound()
		} else if (sound = 2) {
			playWarningSound()
		}
		sleep % seconds * 1000
		progress, off
	}
	logger(text, level)
}

logArray(name, array) {
	local value := ""
	loop % array.Length()
	{
		value .= array[A_Index]
		if (A_Index < array.Length()) {
			value .= ", "
		}
	}
	logVariable(name, "[" . value . "]")
}

logVariable(name, value, isBool:=0, level="DEBUG") {
	if (isBool) {
		value := value ? "true" : "false"
	}
	logger(name . " = " . value, level)
}

; 0:OFF, 1:WARN, 2:USER, 3:INFO, 4:DEBUG, 5:TRACE
logger(msg, level, fileSuffix:="") {
	global
	local localTime := A_Now
	local currentDate
	local currentDateTime
	local fileName
	if (severityLevels[level] <= logSeverityLevel) {
		FormatTime, currentDate, localTime, yyyy-MM-dd
		FormatTime, currentDateTime, localTime, yyyy-MM-dd HH:mm:ss
		fileName := "logs\" . currentDate . fileSuffix . ".txt"
		FileAppend, % currentDateTime . "`t" . level . "`t" . msg . "`n", %fileName%
	}
}

startProgress(title, min:=0, max:=100) {
	global
	if (showProgressBar) {
		gui, new
		gui, margin, 0, 0
		gui, font, s18
		gui, add, progress,% "w" wProgressBar " h28 range" min "-" max " -smooth vProgressBar"
		gui, add, text, w92 vProgressBarTime x+2
		gui, show,% "na x" xProgressBar " y" yProgressBar,% script " - " title
	}
}

updateProgress(position, remainingTime) {
	if (showProgressBar) {
		guicontrol,, ProgressBar,% position
		guicontrol,, ProgressBarTime,% formatSeconds(remainingTime)
	}
}

stopProgress() {
	if (showProgressBar) {
		gui, destroy
	}
}

formatSeconds(s) {
	time := 19990101 ; *Midnight* of an arbitrary date.
	time += %s%, seconds
	FormatTime, timeStr, %time%, HH:mm:ss
	return timeStr
}

secondsSince(startTime) {
	return (A_TickCount - startTime) // 1000
}

toggleFlag(flagName, byref flag) {
	flag := !flag
	flagValue := flag ? "On" : "Off"
	showUserSplash("Toggled " . flagName . " " . flagValue)
}

screenShot() {
	global
	local activeWinId
	if (!isBrowserClient()) { ; Steam only
		WinGet, activeWinId, ID, A ; remember current active window...
		WinActivate, ahk_id %chWinId%
		send {f12 down}{f12 up} ; screenshot
		sleep % zzz
		WinActivate, ahk_id %activeWinId% ; ... and restore focus back
	}
}

scrollToZone(zone) {
	scrollZone(getCurrentZone(), zone)
}

scrollZone(fromZone, toZone) {
	global
	local zones := toZone - fromZone
	local xZone := zones > 0 ? xNextZone : xPrevZone

	if (zones != 0) {
		clickPos(xZone, yZone, abs(zones))
		sleep % scrollDelay + 25 + abs(zones) * scrollClickDelay
		clickPos(xMiddleZone, yZone)
		sleep % zzz
	}
}

horizontalSkills(x, y, skills, absolute:=0) {
	global

	loop % skills
	{
		clickPos(x, y, 1, absolute)
		sleep 25
		x += oSkill
	}
	sleep % zzz * 2
}

getCurrentZone() {
	global
	local title, currentZone
	if (isBrowserClient()) {
		WinGetTitle, title, ahk_id %chWinId%
		currentZone := SubStr(title, 5, InStr(title, "-") - 6)
		return currentZone
	} else {
		return 0
	}
}

reFocus() {
	global
	clickPos(xFocus, yFocus)
	sleep 25
}

isBrowserClient() {
	return A_TitleMatchMode = "regex"
}

; -----------------------------------------------------------------------------------------
; Note that all image/pixel searches are done with absolute coordinates relative to the
; screen. The CH window is required to be visible and in default size for this to work.
; -----------------------------------------------------------------------------------------

clickAwayImage(image) {
	local xImg := 0, yImg := 0
	if (locateImage(image)) {
		while (locateImage(image, xImg, yImg)) {
			clickPos(xImg, yImg, 1, 1)
			sleep 250
		}
		sleep 1000
		return 1
	}
	return 0
}

upLocator(image, what, byref xPos, byref yPos, byref retries:=0, clickCount:=5, absolute:=0, startAt:=0, silent:=0) {
	return locator(image, what, xPos, yPos, retries, clickCount, absolute, startAt, silent, 1)
}

; Try to locate the given image one screen at a time
locator(image, what, byref xPos, byref yPos, byref retries:=0, clickCount:=5, absolute:=0, startAt:=0, silent:=0, directionUp:=0) {
	global

	local attempts := ceil(45 / clickCount)
	local attempt := 0
	local keepGoing := true

	while (!locateImage(image, xPos, yPos, absolute, startAt, directionUp)) {
		keepGoing := directionUp ? !locateImage(imgCid) : true
		if (++attempt <= attempts and keepGoing) {
			if (directionUp) {
				scrollUp(clickCount)
				startAt := 0 ; only offset once
			} else {
				scrollDown(clickCount)
			}
		} else if (retries-- != 0) {
			if (!silent) {
				showDebugSplash("Could not locate " . what . "! Trying again...")
			}
			if (directionUp) {
				scrollToBottom()
			} else {
				scrollToTop()
			}
			attempt := 0
		} else {
			return 0
		}
	}
	return 1
}

locateImage(image, byref xPos:="", byref yPos:="", absolute:=0, startAt:=0, directionUp:=0) {
	if (directionUp) {
		return locateImageUp(image, xPos, yPos, absolute, startAt)
	} else {
		return locateImageDown(image, xPos, yPos, absolute, startAt)
	}
}

; Bottom up image search in chunks (size equal to the distance between two lvl up buttons)
locateImageUp(image, byref xPos:="", byref yPos:="", absolute:=0, startAt:=0) {
	global

	local yT := yScreenT + image.topOffset
	local searchCount := ceil((yScreenB - yT) / oLvl)
	local offset := 0
	if (startAt > 0) {
		offset := startAt - yScreenB
		searchCount := ceil((startAt - yT) / oLvl)
	}
	local topOffset := offset + yScreenB - yScreenT - oLvl
	local bottomOffset := offset

	; msgbox % "searchCount=" . searchCount . ", offset=" . offset . ", topOffset=" . topOffset . ", bottomOffset=" . bottomOffset

	loop % searchCount
	{
		if (locateImageDown(image, xPos, yPos, absolute, topOffset,, bottomOffset)) {
			return 1
		} else {
			topOffset -= oLvl
			if (A_Index > 1) { ; don't offset bottom until round two
				bottomOffset -= oLvl
			}
		}
	}
	return 0
}

; Top down image search
locateImageDown(image, byref xPos:="", byref yPos:="", absolute:=0, topOffset:=0, leftOffset:=0, bottomOffset:=0, rightOffset:=0) {
	global
	local imageFile := imageFilePath . image.file

	if (yScreenB = 0) {
		return 0 ; CH not started
	}

	local xL := xScreenL
	local yT := yScreenT
	local xR := xScreenR
	local yB := yScreenB

	xL += leftOffset ? leftOffset : image.leftOffset
	yT += topOffset ? topOffset : image.topOffset
	xR += rightOffset ? rightOffset : image.rightOffset
	yB += bottomOffset ? bottomOffset : image.bottomOffset

	; msgbox % "file=" . image.file . ", topOffset=" . image.topOffset . ", leftOffset=" . image.leftOffset . ", bottomOffset=" . image.bottomOffset . ", rightOffset=" . image.rightOffset
	; msgbox % "Searching from (" . xL . ", " . yT . ") to (" . xR . ", " . yB . ")"

	if (xL > xR or yT > yB) {
		msgbox,,% script,% "ImageSearch failed! xL (" . xL . ") > xR (" . xR . ") or yT (" . yT  . ") > yB (" . yB . ")"
		exit
	}
	reFocus()
	ImageSearch xPos, yPos, xL, yT, xR, yB, *30 %imageFile%
	if (ErrorLevel = 2) {
		playWarningSound()
		msgbox,,% script,% "ImageSearch failed! Could not open: " . imageFile
		exit
	} else if (ErrorLevel = 0 and !absolute) {
		; Absolute --> Relative
		xPos -= xWinPos
		yPos -= yWinPos
	}
	return !ErrorLevel
}

; Search for a specific pixel color within the given region
locatePixel(pixelColor, xL, yT, xR, yB, byref xPos:="", byref yPos:="") {
	global

	reFocus()
	PixelSearch, xPos, yPos, xL, yT, xR, yB, %pixelColor%,, Fast RGB
	if (ErrorLevel = 0) {
		; Absolute --> Relative
		xPos -= xWinPos
		yPos -= yWinPos
	}
	return !ErrorLevel
}

matchPixelColor(color, x, y) {
	reFocus()
	PixelGetColor, pixelColor, x, y, RGB
	; msgbox % "Is " . pixelColor . " at (" . x . ", " . y . ") equal to " . color . "?"
	return color = pixelColor
}

; -----------------------------------------------------------------------------------------
; Clicker Heroes Sw1ft Bot Lib
; by Sw1ftb
; -----------------------------------------------------------------------------------------

CoordMode, Pixel, Screen

libVersion=4.0

winName := "Clicker Heroes"

global ProgressBar, ProgressBarTime ; progress bar controls

exitThread := false
exitDRThread := false

chWinId := ""

; All the script coordinates are based on these four default dimensions.
chWidth := 1136
chHeight := 640
chMargin := 8
chTopMargin := 30

chTotalWidth := chWidth + chMargin * 2
chTotalHeight := chHeight + chMargin + chTopMargin

; Calculated
xScreenL := 0
yScreenT := 0
xScreenR := 0
yScreenB := 0

; Calculated
leftMarginOffset := 0
topMarginOffset := 0

; Calculated
aspectRatio := 1
hBorder := 0
vBorder := 0

zzz := 175 ; sleep delay (in ms) after a click
lvlUpDelay := 4 ; time (in seconds) between lvl up clicks
barUpdateDelay := 30 ; time (in seconds) between progress bar updates
coinPickUpDelay := 5 ; time (in seconds) needed to pick up all coins from a clickable
nextHeroDelay := 5 ; extra gold farm delay (in seconds) between heroes

scrollDelay := 325 ; base delay (in ms)
scrollClickDelay := 20 ; delay per click (in ms)

dialogBoxClass := "#32770"

blueColor := 0x60BEFF
yellowColor := 0xFECB00
dimmedYellowColor := 0x7E6500
goldColor := 0xFFB423
brightGoldColor := 0xFFD911

severityLevels := {"OFF":0, "WARN":1, "USER":2, "INFO":3, "DEBUG":4, "TRACE":5}

gameModes := {"INIT":0, "PROGRESSING":1, "FARMING":2, "FIGHTING":3}

; -- Images -------------------------------------------------------------------------------

; Combat zone offsets
CZTO := 170
CZLO := 500
CZBO := CZTO - chHeight
CZRO := CZLO - chWidth

imageFilePath := "images\"

imgSmile := {file:"smile.png", topOffset:0, leftOffset:1090, bottomOffset:0, rightOffset:0}
imgQuality := {file:"quality.png", topOffset:0, leftOffset:1090, bottomOffset:0, rightOffset:0}
imgProgression := {file:"progression.png", topOffset:0, leftOffset:1090, bottomOffset:0, rightOffset:0}
imgAscend := {file:"ascend.png", topOffset:0, leftOffset:1090, bottomOffset:0, rightOffset:0}

imgClickable := {file:"clickable.png", topOffset:CZTO, leftOffset:CZLO, bottomOffset:0, rightOffset:0}
imgBoss := {file:"boss_clock.png", topOffset:155, leftOffset:805, bottomOffset:-445, rightOffset:-290}

imgSkillBar := {file:"skill_bar.png", topOffset:0, leftOffset:575, bottomOffset:0, rightOffset:-496}
imgSkillLocked := {file:"skill_locked.png", topOffset:0, leftOffset:575, bottomOffset:0, rightOffset:-496}

imgCombatTab := {file:"combat_tab.png", topOffset:0, leftOffset:0, bottomOffset:CZBO, rightOffset:CZRO}

imgHire := {file:"hire.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgCoin := {file:"coin.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgMaxLvl := {file:"max_lvl.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgDimmedSkill := {file:"skill_dimmed.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgSkill := {file:"skill.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgChefBuff := {file:"way_of_the_chef.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgKingsBuff := {file:"way_of_kings.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgCid := {file:"cid.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgDK := {file:"dk.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgDKG := {file:"dk_g.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgMax := {file:"max.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgGog := {file:"gog.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgSolomon := {file:"solomon.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgGilded := {file:"gilded.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgBuyUpgrades := {file:"upgrades.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgClanRaid := {file:"clan_raid.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgClanFight := {file:"clan_fight.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgClanFightAgain := {file:"clan_fight_again.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}
imgClanCollect := {file:"clan_collect.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

imgYes := {file:"yes.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:0}
imgClose := {file:"close.png", topOffset:0, leftOffset:575, bottomOffset:0, rightOffset:0}

; TODO: REMOVE AFTER BETA
imgWarning := {file:"w.png", topOffset:CZTO, leftOffset:0, bottomOffset:0, rightOffset:CZRO}

; -- Coordinates --------------------------------------------------------------------------

; Top LVL UP button when scrolled to the bottom
xLvl := 80
yLvl := 285
oLvl := 107 ; offset to next button

buttonSize := 34

; Progression/Farm Mode
xMode := 1121
yMode := 281

; 0.23 ascend button
xAscend := 1121
yAscend := 322

; Ascend Yes button
xYes := 500
yYes := 510 ; redesigned ascend window

oTab := 69 ; offset to next tab
xCombatTab := 52
xAncientTab := xCombatTab + oTab*3
xRelicTab := xCombatTab + oTab*4 + 10 ; Halloween fix
xClanTab := xCombatTab + oTab*5
yTab := 130

xRelic := 103
yRelic := 380

xUpgradeNo := 660
yUpgradeNo := 580

xSalvageJunk := 280
ySalvageJunk := 470

xDestroyYes := 500
yDestroyYes := 430

; Scrollbar
xScroll := 554
yUp := 219
yDown := 653
top2BottomClicks := 45

xGilded := 135
yGilded := 582

xGildedDown := 1060
yGildedDown := 493

xGildedClose := 1090
yGildedClose := 54

rangers := ["Dread Knight", "Atlas", "Terra", "Phthalo", "Banana", "Lilin", "Cadmia", "Alabaster", "Astraea", "Chiron", "Moloch", "Bomber Max", "Gog", "Wepwawet", "Betty", "Midas"]

rangerPositions := {}
rangerPositions[1] := {x:445, y:290} ; Dread Knight
rangerPositions[2] := {x:675, y:290} ; Atlas
rangerPositions[3] := {x:900, y:290} ; Terra
rangerPositions[4] := {x:220, y:355} ; Phthalo
rangerPositions[5] := {x:445, y:355} ; Banana
rangerPositions[6] := {x:675, y:355} ; Lilin
rangerPositions[7] := {x:900, y:355} ; Cadmia
rangerPositions[8] := {x:220, y:425} ; Alabaster
rangerPositions[9] := {x:445, y:425} ; Astraea
rangerPositions[10] := {x:675, y:425} ; Chiron
rangerPositions[11] := {x:900, y:425} ; Moloch
rangerPositions[12] := {x:220, y:490} ; Bomber Max
rangerPositions[13] := {x:445, y:490} ; Gog
rangerPositions[14] := {x:675, y:490} ; Wepwawet

; Buy Available Upgrades button
xBuy := 370
yBuy := 582

xFocus := 564
yFocus := 69

xMonster := 920
yMonster := 164

; Safety zones (script will pause when entering)
safetyZones := {}
safetyZones[1] := {x1:8, y1:104, x2:575, y2:153} ; tabs
safetyZones[2] := {x1:1096, y1:29, x2:1144, y2:74} ; settings
safetyZones[3] := {x1:678, y1:600, x2:949, y2:670} ; sale + shop
safetyZones[4] := {x1:79, y1:554, x2:194, y2:612} ; gilded

; The wrench
xSettings := 1121
ySettings := 52

xSettingsClose := 961
ySettingsClose := 52

xSave := 286
ySave := 112

xSkill := 201
oSkill := 36 ; offset to next skill
ySkill2nd := 410 ; at bottom

xPrevZone := 679
xMiddleZone := 858
xPlusOneZone := 922
xNextZone := 1044
yZone := 70

xNewGild := 1105
yNewGild := 560
xOpenGild := 570
yOpenGild := 360
xCloseGild := 924
yCloseGild := 133

xBuyRubiesClose := 1081
yBuyRubiesClose := 96

; -----------------------------------------------------------------------------------------

; Load system default settings
#Include system\ch_bot_lib_default_settings.ahk

IfNotExist, ch_bot_lib_settings.ahk
{
	FileCopy, system\ch_bot_lib_default_settings.ahk, ch_bot_lib_settings.ahk
}

#Include *i ch_bot_lib_settings.ahk

; -----------------------------------------------------------------------------------------
; -- Functions
; -----------------------------------------------------------------------------------------

clientCheck() {
	global
	if (!isBrowserClient()) {
		calculateSteamAspectRatio() ; Steam
	} else {
		local xPos, yPos
		calculateBrowserOffsets() ; Browser
		fullScreenOption := false

		if (useImageSearch and locateImage(imgQuality, xPos, yPos)) {
			showTraceSplash("Switching to low quality")
			clickPos(xPos, yPos, 1, 1)
		}
	}
}

calculateBrowserOffsets() {
	global
	local w, h
	winName := "Lvl.*Clicker Heroes.*" . browser
	IfWinExist, % winName
	{
		showDebugSplash("Calculating browser offsets (" . scriptName . ")")
		WinActivate
		WinGetPos, xWinPos, yWinPos, w, h
		WinGet, chWinId, ID, A

		local leftMargin := (w - chWidth) // 2
		leftMarginOffset := leftMargin - chMargin
		topMarginOffset := browserTopMargin - chTopMargin

		xScreenL := xWinPos + leftMargin
		yScreenT := yWinPos + browserTopMargin
		xScreenR := xScreenL + chWidth
		yScreenB := yScreenT + chHeight
	} else {
		showWarningSplash("Clicker Heroes started in " . browser . "?")
	}
}

calculateSteamAspectRatio() {
	global
	local w, h
	IfWinExist, % winName
	{
		WinActivate
		WinGetPos, xWinPos, yWinPos, w, h
		WinGet, chWinId, ID, A

		; Fullscreen sanity checks
		if (fullScreenOption) {
			if (w <> A_ScreenWidth || h <> A_ScreenHeight) {
				showWarningSplash("The fullScreenOption should be set to false!")
				return
			}
		} else if (w = A_ScreenWidth && h = A_ScreenHeight) {
			showWarningSplash("The fullScreenOption should be set to true!")
			return
		}

		if (w != chTotalWidth || h != chTotalHeight) {
			showDebugSplash("Calculating Steam aspect ratio (" . scriptName . ")")

			local winWidth := fullScreenOption ? w : w - 2 * chMargin
			local winHeight := fullScreenOption ? h : h - chTopMargin - chMargin
			local horizontalAR := winWidth/chWidth
			local verticalAR := winHeight/chHeight

			; Take the lowest aspect ratio and calculate border size
			if (horizontalAR < verticalAR) {
				aspectRatio := horizontalAR
				vBorder := (winHeight - chHeight * aspectRatio) // 2
			} else {
				aspectRatio := verticalAR
				hBorder := (winWidth - chWidth * aspectRatio) // 2
			}
		}

		xScreenL := fullScreenOption ? xWinPos : xWinPos + chMargin
		yScreenT := fullScreenOption ? yWinPos : yWinPos + chTopMargin
		xScreenR := fullScreenOption ? xWinPos + w : xWinPos + w - chMargin
		yScreenB := fullScreenOption ? yWinPos + h : yWinPos + h - chMargin
	} else {
		showWarningSplash("Clicker Heroes started in Steam?")
	}
}

switchToCombatTab() {
	global
	if (useImageSearch and locateImage(imgCombatTab)) {
		clickAwayImage(imgCombatTab)
	} else {
		clickPos(xCombatTab, yTab)
		sleep % zzz * 4
	}
}

switchToAncientTab() {
	global
	clickPos(xAncientTab, yTab)
	sleep % zzz * 2
}

switchToRelicTab() {
	global
	clickPos(xRelicTab, yTab)
	sleep % zzz * 2
}

switchToClanTab() {
	global
	clickPos(xClanTab, yTab)
	sleep % zzz * 2
}

scrollToTop() {
	global
	clickPos(xScroll, yUp, top2BottomClicks)
	sleep % scrollDelay + top2BottomClicks * scrollClickDelay
}

scrollToBottom() {
	global
	clickPos(xScroll, yDown, top2BottomClicks)
	sleep % scrollDelay + top2BottomClicks * scrollClickDelay
}

scrollUp(clickCount:=1) {
	global
	clickPos(xScroll, yUp, clickCount)
	sleep % scrollDelay + clickCount * scrollClickDelay
}

scrollDown(clickCount:=1) {
	global
	clickPos(xScroll, yDown, clickCount)
	sleep % scrollDelay + clickCount * scrollClickDelay
}

; Scroll down fix when at bottom and scroll bar don't update correctly
scrollWayDown(clickCount:=1) {
	global
	scrollUp()
	scrollDown(clickCount + 1)
	sleep % nextHeroDelay * 1000
}

maxClick(xCoord, yCoord, clickCount:=1, absolute:=0) {
	global
	ControlSend,, {shift down}{vk51 down}, ahk_id %chWinId% ; {q}, {vk51} or {sc010}
	clickPos(xCoord, yCoord, clickCount, absolute)
	ControlSend,, {vk51 up}{shift up}, ahk_id %chWinId%
	sleep % zzz
}

ctrlClick(xCoord, yCoord, clickCount:=1, sleepSome:=1, absolute:=0) {
	global
	ControlSend,, {ctrl down}, ahk_id %chWinId%
	clickPos(xCoord, yCoord, clickCount, absolute)
	ControlSend,, {ctrl up}, ahk_id %chWinId%
	if (sleepSome) {
		sleep % zzz
	}
}

zClick(xCoord, yCoord, clickCount:=1, absolute:=0) {
	global
	ControlSend,, {vk5A down}, ahk_id %chWinId% ; {z}, {vk5A} or {sc02C}
	clickPos(xCoord, yCoord, clickCount, absolute)
	ControlSend,, {vk5A up}, ahk_id %chWinId%
	sleep % zzz
}

clickPos(xCoord, yCoord, clickCount:=1, absolute:=0) {
	global
	local xAdj := absolute ? xCoord : getAdjustedX(xCoord)
	local yAdj := absolute ? yCoord : getAdjustedY(yCoord)
 	ControlClick, x%xAdj% y%yAdj%, ahk_id %chWinId%,,, %clickCount%, NA
}

getAdjustedX(x) {
	global
	local leftMargin := fullScreenOption ? 0 : chMargin + leftMarginOffset
	return round(aspectRatio*(x - chMargin) + leftMargin + hBorder)
}

getAdjustedY(y) {
	global
	local topMargin := fullScreenOption ? 0 : chTopMargin + topMarginOffset
	return round(aspectRatio*(y - chTopMargin) + topMargin + vBorder)
}

playNotificationSound() {
	if (playNotificationSounds) {
		SoundPlay, %A_WinDir%\Media\Windows User Account Control.wav
	}
}

playWarningSound() {
	if (playWarningSounds) {
		SoundPlay, %A_WinDir%\Media\tada.wav
	}
}

showTraceSplash(text, seconds:=1) {
	showSplash(text, seconds, 0, "TRACE")
}

showDebugSplash(text, seconds:=1) {
	showSplash(text, seconds, 0, "DEBUG")
}

showUserSplash(text, seconds:=2) {
	showSplash(text, seconds, 1, "USER")
}

showWarningSplash(text, seconds:=4) {
	showSplash(text, seconds, 2, "WARN")
}

showSplash(text, seconds:=2, sound:=1, level="INFO") {
	global
	if (seconds > 0) {
		if (severityLevels[level] <= showSeverityLevel) {
			progress,% "w" wSplash " x" xSplash " y" ySplash " zh0 fs10", %text%,,% script
		}
		if (sound = 1) {
			playNotificationSound()
		} else if (sound = 2) {
			playWarningSound()
		}
		sleep % seconds * 1000
		progress, off
	}
	logger(text, level)
}

logArray(name, array) {
	local value := ""
	loop % array.Length()
	{
		value .= array[A_Index]
		if (A_Index < array.Length()) {
			value .= ", "
		}
	}
	logVariable(name, "[" . value . "]")
}

logVariable(name, value, isBool:=0, level="DEBUG") {
	if (isBool) {
		value := value ? "true" : "false"
	}
	logger(name . " = " . value, level)
}

; 0:OFF, 1:WARN, 2:USER, 3:INFO, 4:DEBUG, 5:TRACE
logger(msg, level, fileSuffix:="") {
	global
	local localTime := A_Now
	local currentDate
	local currentDateTime
	local fileName
	if (severityLevels[level] <= logSeverityLevel) {
		FormatTime, currentDate, localTime, yyyy-MM-dd
		FormatTime, currentDateTime, localTime, yyyy-MM-dd HH:mm:ss
		fileName := "logs\" . currentDate . fileSuffix . ".txt"
		FileAppend, % currentDateTime . "`t" . level . "`t" . msg . "`n", %fileName%
	}
}

startProgress(title, min:=0, max:=100) {
	global
	if (showProgressBar) {
		gui, new
		gui, margin, 0, 0
		gui, font, s18
		gui, add, progress,% "w" wProgressBar " h28 range" min "-" max " -smooth vProgressBar"
		gui, add, text, w92 vProgressBarTime x+2
		gui, show,% "na x" xProgressBar " y" yProgressBar,% script " - " title
	}
}

updateProgress(position, remainingTime) {
	if (showProgressBar) {
		guicontrol,, ProgressBar,% position
		guicontrol,, ProgressBarTime,% formatSeconds(remainingTime)
	}
}

stopProgress() {
	if (showProgressBar) {
		gui, destroy
	}
}

formatSeconds(s) {
	time := 19990101 ; *Midnight* of an arbitrary date.
	time += %s%, seconds
	FormatTime, timeStr, %time%, HH:mm:ss
	return timeStr
}

secondsSince(startTime) {
	return (A_TickCount - startTime) // 1000
}

toggleFlag(flagName, byref flag) {
	flag := !flag
	flagValue := flag ? "On" : "Off"
	showUserSplash("Toggled " . flagName . " " . flagValue)
}

screenShot() {
	global
	local activeWinId
	if (!isBrowserClient()) { ; Steam only
		WinGet, activeWinId, ID, A ; remember current active window...
		WinActivate, ahk_id %chWinId%
		send {f12 down}{f12 up} ; screenshot
		sleep % zzz
		WinActivate, ahk_id %activeWinId% ; ... and restore focus back
	}
}

scrollToZone(zone) {
	scrollZone(getCurrentZone(), zone)
}

scrollZone(fromZone, toZone) {
	global
	local zones := toZone - fromZone
	local xZone := zones > 0 ? xNextZone : xPrevZone

	if (zones != 0) {
		clickPos(xZone, yZone, abs(zones))
		sleep % scrollDelay + 25 + abs(zones) * scrollClickDelay
		clickPos(xMiddleZone, yZone)
		sleep % zzz
	}
}

horizontalSkills(x, y, skills, absolute:=0) {
	global

	loop % skills
	{
		clickPos(x, y, 1, absolute)
		sleep 25
		x += oSkill
	}
	sleep % zzz * 2
}

getCurrentZone() {
	global
	local title, currentZone
	if (isBrowserClient()) {
		WinGetTitle, title, ahk_id %chWinId%
		currentZone := SubStr(title, 5, InStr(title, "-") - 6)
		return currentZone
	} else {
		return 0
	}
}

reFocus() {
	global
	clickPos(xFocus, yFocus)
	sleep 25
}

isBrowserClient() {
	return A_TitleMatchMode = "regex"
}

; -----------------------------------------------------------------------------------------
; Note that all image/pixel searches are done with absolute coordinates relative to the
; screen. The CH window is required to be visible and in default size for this to work.
; -----------------------------------------------------------------------------------------

clickAwayImage(image) {
	local xImg := 0, yImg := 0
	if (locateImage(image)) {
		while (locateImage(image, xImg, yImg)) {
			clickPos(xImg, yImg, 1, 1)
			sleep 250
		}
		sleep 1000
		return 1
	}
	return 0
}

upLocator(image, what, byref xPos, byref yPos, byref retries:=0, clickCount:=5, absolute:=0, startAt:=0, silent:=0) {
	return locator(image, what, xPos, yPos, retries, clickCount, absolute, startAt, silent, 1)
}

; Try to locate the given image one screen at a time
locator(image, what, byref xPos, byref yPos, byref retries:=0, clickCount:=5, absolute:=0, startAt:=0, silent:=0, directionUp:=0) {
	global

	local attempts := ceil(45 / clickCount)
	local attempt := 0
	local keepGoing := true

	while (!locateImage(image, xPos, yPos, absolute, startAt, directionUp)) {
		keepGoing := directionUp ? !locateImage(imgCid) : true
		if (++attempt <= attempts and keepGoing) {
			if (directionUp) {
				scrollUp(clickCount)
				startAt := 0 ; only offset once
			} else {
				scrollDown(clickCount)
			}
		} else if (retries-- != 0) {
			if (!silent) {
				showDebugSplash("Could not locate " . what . "! Trying again...")
			}
			if (directionUp) {
				scrollToBottom()
			} else {
				scrollToTop()
			}
			attempt := 0
		} else {
			return 0
		}
	}
	return 1
}

locateImage(image, byref xPos:="", byref yPos:="", absolute:=0, startAt:=0, directionUp:=0) {
	if (directionUp) {
		return locateImageUp(image, xPos, yPos, absolute, startAt)
	} else {
		return locateImageDown(image, xPos, yPos, absolute, startAt)
	}
}

; Bottom up image search in chunks (size equal to the distance between two lvl up buttons)
locateImageUp(image, byref xPos:="", byref yPos:="", absolute:=0, startAt:=0) {
	global

	local yT := yScreenT + image.topOffset
	local searchCount := ceil((yScreenB - yT) / oLvl)
	local offset := 0
	if (startAt > 0) {
		offset := startAt - yScreenB
		searchCount := ceil((startAt - yT) / oLvl)
	}
	local topOffset := offset + yScreenB - yScreenT - oLvl
	local bottomOffset := offset

	; msgbox % "searchCount=" . searchCount . ", offset=" . offset . ", topOffset=" . topOffset . ", bottomOffset=" . bottomOffset

	loop % searchCount
	{
		if (locateImageDown(image, xPos, yPos, absolute, topOffset,, bottomOffset)) {
			return 1
		} else {
			topOffset -= oLvl
			if (A_Index > 1) { ; don't offset bottom until round two
				bottomOffset -= oLvl
			}
		}
	}
	return 0
}

; Top down image search
locateImageDown(image, byref xPos:="", byref yPos:="", absolute:=0, topOffset:=0, leftOffset:=0, bottomOffset:=0, rightOffset:=0) {
	global
	local imageFile := imageFilePath . image.file

	if (yScreenB = 0) {
		return 0 ; CH not started
	}

	local xL := xScreenL
	local yT := yScreenT
	local xR := xScreenR
	local yB := yScreenB

	xL += leftOffset ? leftOffset : image.leftOffset
	yT += topOffset ? topOffset : image.topOffset
	xR += rightOffset ? rightOffset : image.rightOffset
	yB += bottomOffset ? bottomOffset : image.bottomOffset

	; msgbox % "file=" . image.file . ", topOffset=" . image.topOffset . ", leftOffset=" . image.leftOffset . ", bottomOffset=" . image.bottomOffset . ", rightOffset=" . image.rightOffset
	; msgbox % "Searching from (" . xL . ", " . yT . ") to (" . xR . ", " . yB . ")"

	if (xL > xR or yT > yB) {
		msgbox,,% script,% "ImageSearch failed! xL (" . xL . ") > xR (" . xR . ") or yT (" . yT  . ") > yB (" . yB . ")"
		exit
	}
	reFocus()
	ImageSearch xPos, yPos, xL, yT, xR, yB, *30 %imageFile%
	if (ErrorLevel = 2) {
		playWarningSound()
		msgbox,,% script,% "ImageSearch failed! Could not open: " . imageFile
		exit
	} else if (ErrorLevel = 0 and !absolute) {
		; Absolute --> Relative
		xPos -= xWinPos
		yPos -= yWinPos
	}
	return !ErrorLevel
}

; Search for a specific pixel color within the given region
locatePixel(pixelColor, xL, yT, xR, yB, byref xPos:="", byref yPos:="") {
	global

	reFocus()
	PixelSearch, xPos, yPos, xL, yT, xR, yB, %pixelColor%,, Fast RGB
	if (ErrorLevel = 0) {
		; Absolute --> Relative
		xPos -= xWinPos
		yPos -= yWinPos
	}
	return !ErrorLevel
}

matchPixelColor(color, x, y) {
	reFocus()
	PixelGetColor, pixelColor, x, y, RGB
	; msgbox % "Is " . pixelColor . " at (" . x . ", " . y . ") equal to " . color . "?"
	return color = pixelColor
}