Init Plan B update

Init Plan B update

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

; #Warn All
#Persistent
#NoEnv
#InstallKeybdHook

#Include %A_ScriptDir%
#Include ch_bot_lib.ahk

SetControlDelay, -1

scriptName=CH Sw1ft Bot
scriptVersion=3.x
minLibVersion=1.4

script := scriptName . " v" . scriptVersion

clickerName := "monster_clicker.ahk - AutoHotkey"

scheduleReload := false
scheduleStop := false

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

#Include system\wm_messages.ahk

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

IfNotExist, ch_bot_settings.ahk
{
	FileCopy, system\ch_bot_default_settings.ahk, ch_bot_settings.ahk
}

; Load user settings
#Include *i ch_bot_settings.ahk

if (libVersion != minLibVersion) {
	showWarningSplash("The bot lib version must be " . minLibVersion . "!")
	ExitApp
}

if (useConfigurationAssistant) {
	configurationAssistant()
}

clientCheck()

if (deepRunClicks) {
	Run, "%A_ScriptDir%\monster_clicker.ahk",, UseErrorLevel
	if (ErrorLevel != 0) {
		playWarningSound()
		msgbox,,% script,% "Failed to auto-start monster_clicker.ahk (system error code = " . A_LastError . ")!"
	}
}

handleAutorun()

; -----------------------------------------------------------------------------------------
; -- Hotkeys (+=Shift, !=Alt, ^=Ctrl, #=Win)
; -----------------------------------------------------------------------------------------

; Show the cursor position with Alt+Middle Mouse Button
!mbutton::
	mousegetpos, xpos, ypos
	msgbox,,% script,% "Cursor position: x" xpos-leftMarginOffset " y" ypos-topMarginOffset
return

; Top margin calculation when clicking in the center of the ancient tab eye
^mbutton::
	mousegetpos, xpos, ypos
	msgbox,,% script,% "Set browserTopMargin to: " . ypos-102
return

; -- Main Hotkeys -------------------------------------------------------------------------

; Start the Speed run loop with Ctrl+F1
^F1::
	loopSpeedRun()
return

; Start a Deep run with Ctrl+F2 (expecting to start where a Speed run finishes)
^F2::
	deepRun()
return

; Start a Vision run loop with Ctrl+F3
^F3::
	loopVisionRun()
return

; Pause/unpause the script
Pause::Pause
return

; Abort any active run or initiated ascension with Alt+Pause
!Pause::
	showSplashAlways("Aborting...")
	exitThread := true
return

; Schedule a stop after finishing the current run with Shift+Pause
+Pause::
	toggleFlag("scheduleStop", scheduleStop)
return

; Reload the script (needed after configuration changes)
!F5::
	critical
	global scheduleReload := true
	handleScheduledReload()
return

; Schedule a script reload after finishing the current run, then restart it
+^F5::
	toggleFlag("scheduleReload", scheduleReload)
return

; Re-initialize coordinates (needed after moving or re-sizing the client window)
!F6::
	critical
	clientCheck()
	clickerInitialize()
return

; -- Supplementary Hotkeys ----------------------------------------------------------------

; Suspend/Unsuspend all other Hotkeys with Ctrl+Esc
^Esc::Suspend, Toggle
return

; Open the Ancients Optimizer and auto-import game save data
^F5::
	critical
	openAncientsOptimizer()
return

; Set previous ranger as re-gild target
^F6::
	reGildRanger := reGildRanger > rangers.MinIndex() ? reGildRanger-1 : reGildRanger
	showSplashAlways("Re-gild ranger set to " . rangers[reGildRanger])
return

; Set next ranger as re-gild target
^F7::
	reGildRanger := reGildRanger < rangers.MaxIndex() ? reGildRanger+1 : reGildRanger
	showSplashAlways("Re-gild ranger set to " . rangers[reGildRanger])
return

; Move "reGildCount" gilds to the target ranger
^F8::
	critical
	playNotificationSound()
	msgbox, 4,% script,% "Move " . reGildCount . " gilds to " . rangers[reGildRanger] . "?"
	ifmsgbox no
		return
	clickerPause()
	regild(reGildRanger, reGildCount)
return

; Open new gilds
^F9::
	critical
	clickerPause()
	openNewGilds()
return

; Autosave the game
^F11::
	critical
	save()
return

; Raid once for free with Win+F6
#F6::
	critical
	raid()
return

; One paid raid
#F7::
	critical
	raid(1)
return

; Paid raids
#F8::
	critical
	raid(1, raidAttempts)
return

; Toggle boolean (true/false) flags with Shift+Ctrl+Fx

+^F1::
	toggleFlag("autoAscend", autoAscend)
return

+^F2::
	toggleFlag("screenShotRelics", screenShotRelics)
return

+^F6::
	toggleFlag("playNotificationSounds", playNotificationSounds)
return

+^F7::
	toggleFlag("playWarningSounds", playWarningSounds)
return

+^F8::
	toggleFlag("showSplashTexts", showSplashTexts)
return

+^F11::
	toggleFlag("saveBeforeAscending", saveBeforeAscending)
return

+^F12::
	toggleFlag("debug", debug)
return

; -- Test Hotkeys -------------------------------------------------------------------------

; Ctrl+Alt+F1 should scroll down to the bottom
^!F1::
	scrollToBottom()
return

; Ctrl+Alt+F2 should switch to the relics tab and then back
^!F2::
	switchToRelicTab()
	switchToCombatTab()
return

; Alt+F1 to F4 are here to test the individual parts of the full speed run loop

!F1::
	getClickable()
return

!F2::
	initRun()
return

!F3::
	switchToCombatTab()
	speedRun()
return

!F4::
	ascend(autoAscend)
return

; Test one Midas start with Win+F1
#F1::
	midasStart()
return

; Loop Midas start > init run > ascend, twice
#F2::
	loop 2
	{
		midasStart()
		initRun()
		ascend(autoAscend)
	}
return

testSearch(image, info:="") {
	global
	local extraInfo := info != "" ? " (" . info . ")" : ""
	local x, y
	msgbox,,% script,% "Search for " . image.file . "?" . extraInfo
	if (locateImage(image, x, y)) {
		msgbox,,% script,% "Success! (" . x . ", " . y . ")"
	} else {
		msgbox,,% script,% "Failed!"
	}
}

testLocate(image, clickCount:=5) {
	global
	local x, y
	msgbox,,% script,% "Locate " . image.file . "?"
	if (locator(image, image.file, x, y, clickCount, 1)) {
		msgbox,,% script,% "Success! (" . x . ", " . y . ")"
	} else {
		msgbox,,% script,% "Failed!"
	}
}

; Image search tests
#F3::
	msgbox,,% script,% "After clickable/Midas start state expected."
	testSearch(imgQuality, "Set high quality")
	testSearch(imgSmile, "Set low quality")
	testSearch(imgProgression, "Toggle progression mode")
	testSearch(imgClickable)
	switchToCombatTab()
	testSearch(imgCombat)
	testSearch(imgHire)
	testSearch(imgCoin)
	testSearch(imgDimmedSkill, "Lvl someone to 1")
	testSearch(imgSkill, "Lvl someone to 10")
	testLocate(imgCid)
	testSearch(imgClickstorm, "Buy Cid's skill")
	testSearch(imgSkillBar)
	testSearch(imgSkillLocked)
	testLocate(imgMercedes)
	testLocate(imgReferi, 2)
	testSearch(imgMetalDetector, "Lvl Broyle to 100")
	testSearch(imgGoldenClicks, "Lvl Midas to 100")
	testLocate(imgBeastlord)
	testSearch(imgAscension, "Lvl Amenhotep to 150")
	testLocate(imgDK)
	testSearch(imgFrigidEnchant, "Lvl Frostleaf to 100")

	msgbox,,% script,% "Locate " . imgGilded.file . "?"
	if (locateGilded(x, y, isNew)) {
		msgbox,,% script,% "Success! (" . x . ", " . y . ")"
	} else {
		msgbox,,% script,% "Failed!"
	}
	scrollToBottom()
	testSearch(imgBuyUpgrades)

	switchToAncientTab()
	testLocate(imgSolomon)
	msgbox,,% script,% "Done."
return

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

; Automatically configure initDownClicks and yLvlInit settings.
configurationAssistant() {
	global

	if (irisLevel < 145) {
		playWarningSound()
		msgbox,,% script,% "Your Iris do not fulfill the minimum level requirement of 145 or higher!"
		exit
	}

	if (irisThreshold(2010)) { ; Astraea
		initDownClicks := [6,5,6,5,6,3]
		yLvlInit := 241
	} else if (irisThreshold(1760)) { ; Alabaster
		initDownClicks := [6,6,6,5,6,3]
		yLvlInit := 227
	} else if (irisThreshold(1510)) { ; Cadmia
		initDownClicks := [6,6,6,6,6,3]
		yLvlInit := 240
	} else if (irisThreshold(1260)) { ; Lilin
		initDownClicks := [6,6,6,6,6,3]
		yLvlInit := 285
	} else if (irisThreshold(1010)) { ; Banana
		initDownClicks := [6,7,6,7,6,3]
		yLvlInit := 240
	} else if (irisThreshold(760)) { ; Phthalo
		initDownClicks := [6,7,7,6,7,3]
		yLvlInit := 273
	} else if (irisThreshold(510)) { ; Terra
		initDownClicks := [7,7,7,7,7,3]
		yLvlInit := 240
	} else if (irisThreshold(260)) { ; Atlas
		initDownClicks := [7,7,7,8,7,3]
		yLvlInit := 273
	} else { ; Dread Knight
		initDownClicks := [7,8,7,8,7,4]
		yLvlInit := 257
	}

	if (irisLevel < optimalLevel - 1001) {
		local levels := optimalLevel - 1001 - irisLevel
		playNotificationSound()
		msgbox,,% script,% "Your Iris is " . levels . " levels below the recommended ""optimal level - 1001"" rule."
	}
}

; Check if Iris is within a certain threshold that can cause a toggling behaviour between different settings
irisThreshold(lvl) {
	global
	local upperThreshold := lvl + 19
	local lowerThreshold := lvl - 20
	if (irisLevel >= lowerThreshold and irisLevel < upperThreshold) {
		playWarningSound()
		msgbox,,% script,% "Threshold proximity warning! You should level up your Iris to " . upperThreshold . " or higher."
	}
	return irisLevel > lvl
}

; Level up and upgrade all heroes
initRun() {
	global
	local hasLeveledHeroes := false

	switchToCombatTab()
	reFocus()

	if (!useImageSearch) {
		if (initPlanB) {
			local clicks := irisLevel > 1600 ? 6 : 7
			loop 6
			{
				upgrade2(2)
				scrollDown(clicks)
			}
			upgrade2()
		} else {
			upgrade(initDownClicks[1],2,,2) ; cid --> brittany
			upgrade(initDownClicks[2]) ; fisherman --> leon
			upgrade(initDownClicks[3]) ; seer --> mercedes
			upgrade(initDownClicks[4],,,,2) ; bobby --> king
			upgrade(initDownClicks[5],2,,,2) ; ice --> amenhotep
			upgrade(initDownClicks[6],,,2) ; beastlord --> shinatobe
			upgrade(0,,,,,true) ; grant & frostleaf
		}
	} else {
		local foundDK := false
		local xButton, yButton, xDK, yDK, x

		locateImage(imgCoin, xButton, yButton) ; get a x coordinate for the hire/lvl up buttons

		loop 9 ; pages
		{
			foundDK := locateImage(imgDK, xDK, yDK)
			loop 5 ; attempts per page
			{
				if (locateImage(imgDimmedSkill, x, yButton)
						or locateImage(imgHire, x, yButton)) {
					if (foundDK and yButton > yDK) {
						; Don't level anything below Frostleaf
						continue
					}
					ctrlClick(xButton, yButton, 2, 1, 1)
				} else {
					break
				}
			}
			if (foundDK) {
				if (locateImage(imgFrigidEnchant)) {
					hasLeveledHeroes := true
				}
				break
			}
			scrollDown(5)
		}
	}
	scrollToBottom()
	buyAvailableUpgrades()

	return useImageSearch ? hasLeveledHeroes and locateImage(imgSkillBar) and !locateImage(imgSkillLocked) : 1
}

upgrade(times, cc1:=1, cc2:=1, cc3:=1, cc4:=1, skip:=false) {
	global

	if (!skip) {
		ctrlClick(xLvl, yLvlInit, cc1)
		ctrlClick(xLvl, yLvlInit + oLvl, cc2)
	}
	ctrlClick(xLvl, yLvlInit + oLvl*2, cc3)
	ctrlClick(xLvl, yLvlInit + oLvl*3, cc4)

	scrollDown(times)
}

upgrade2(clickCount:=1) {
	global
	local y := 213

	loop
	{
		ctrlClick(xLvl, y, clickCount)
		y += 74
	} until y > 669
}

midasStart() {
	global

	local xl := xLvl
	local yl := yLvl - 10

	local midasZone1 := midasZoneConfig[1]
	local midasDelay1 := midasZoneConfig[2]

	local midasExtraZone := midasZoneConfig[3]
	local midasExtraDelay := midasZoneConfig[4]

	local midasZone2 := midasZoneConfig[5]
	local midasDelay2 := midasZoneConfig[6]

	local fromZone := midasExtraZone > 0 ? midasExtraZone : midasZone1

	switchToCombatTab()
	startMonitoring()
	reFocus()

	if (!useImageSearch) {
		loop 3 {
			clickPos(xMonster, yMonster) ; Break idle
			sleep 30
		}
		scrollToZone(1, midasZone1)
		ctrlClick(xl, yl) ; Cid x 100
		clickPos(xSkill + oSkill, ySkillTop) ; Clickstorm
		sleep % zzz

		scrollToBottom()
		clickPos(xl, yl+oLvl) ; Natalia
		sleep % midasDelay1 * 1000
		ctrlClick(xl, yl+oLvl) ; Natalia x 100

		if (midasExtraZone > 0) {
			scrollToZone(midasZone1, midasExtraZone)
			horizontalSkills(xSkill, ySkill2nd, 4)
			sleep % midasExtraDelay * 1000
		}
		scrollDown(8)
		scrollToZone(fromZone, midasZone2)
		ctrlClick(xl, yl+oLvl) ; Broyle x 100
		sleep % midasDelay2 * 1000
		ctrlClick(xl, yl+oLvl*3) ; Midas x 100
		verticalSkills(xSkill + oSkill*4) ; Metal Detector + Golden Clicks

		toggleMode()
		activateSkills("1-4-5")
		sleep % coinPickUpDelay * 1000
	} else {
		if (locateImage(imgProgression)) {
			showDebugSplash("Toggle farm mode")
			toggleMode()
		}
		scrollToZone(1, midasZone1)
		locator(imgCid, "Cid", xl, yl)
		buySkill(imgClickstorm, xl-137, yl+60, 2, 7)

		scrollToBottom()
		upLocator(imgCoin, "Coin", xl, yl)
		ctrlClick(xl, yl, 1, 1, 1)
		sleep % midasDelay1 * 1000
		ctrlClick(xl, yl, 1, 1, 1)
		if (midasExtraZone > 0) {
			scrollToZone(midasZone1, midasExtraZone)
			sleep % midasExtraDelay * 1000
			ctrlClick(xl, yl, 1, 1, 1)
		}
		if (locateImage(imgMercedes)) {
			scrollDown(8)
		} else {
			showDebugSplash("Unknown location, relocating...")
			switchToCombatTab()
			scrollDown(18)
		}
		scrollToZone(fromZone, midasZone2)
		locator(imgReferi, "Referi", xl, yl, 2)
		xl -= 155
		yl += 60
		buySkill(imgMetalDetector, xl, yl-oLvl*3, 5, 5)
		sleep % midasDelay2 * 1000
		buySkill(imgGoldenClicks, xl, yl-oLvl, 5, 6)

		toggleMode()
		activateSkills("1-4-5")
		sleep % coinPickUpDelay * 1000
		zClick(xl, yl-oLvl, 1, 1) ; Midas 125
	}
	stopMonitoring()
}

buySkill(image, xLvlUp, yLvlUp, skill, skills) {
	global
	local xSkill, ySkill
	; msgbox % image.file . " (" . xLvlUp . ", " . yLvlUp . ")"
	if (!locateImage(image) or locateImage(imgSkillLocked)) {
		reFocus()
		ctrlClick(xLvlUp, yLvlUp, 1, 1, 1) ; x 100
		zClick(xLvlUp, yLvlUp, 2, 1) ; x 50
		loop 5
		{
			if (locateImage(imgDimmedSkill)) {
				sleep 2500
			}
			if (locateImage(image, xSkill, ySkill)) {
				clickPos(xSkill, ySkill, 1, 1)
				sleep % zzz
				horizontalSkills(xSkill - oSkill*(skill-1), ySkill, skills, 1)
				break
			} else {
				sleep 2500
				zClick(xLvlUp, yLvlUp, 2, 1)
			}
		}
	}
}

getEndZone() {
	global
	return endLvlActive > endLvlIdle ? endLvlActive : endLvlIdle
}

getState() {
	global

	if (!WinExist(winName)) {
		return -3 ; no ch window
	}
	if (!useImageSearch) {
		return -2 ; no vision
	}
	if (getCurrentZone() = 0) {
		return -1 ; vision, but not in browser
	}
	if (!locateImage(imgSmile)) {
		return 0 ; vision, but not finding anything
	}
	if (!locateImage(imgProgression) and getCurrentZone() < irisLevel) {
		return 1 ; ready to start
	}
	if (getCurrentZone() < getEndZone()) {
		return 2 ; ready for progression
	} else {
		return 3 ; ready to ascend
	}
}

loopVisionRun() {
	global
	local state := 0
	showSplashAlways("Starting vision runs...")

	loop
	{
		state := getState()
		if (state < 1) {
			showWarningSplash("Start failed (state = " . state . ")! Trying again...")
			clientCheck()
			clickerInitialize()
			continue
		}
		if (state = 1) {
			if (hasClickable()) {
				showDebugSplash("(1) Clickable start...")
				getClickable(useImageSearch)
				sleep % coinPickUpDelay * 1000
				switchToCombatTab()
				ctrlClick(xLvl, yLvl+oLvl) ; Force progression
				toggleMode()
			} else {
				showDebugSplash("(1) Midas start...")
				midasStart()
			}
		}
		if (getState() = 2) {
			showDebugSplash("(2) Progressing...")
			visionRun()
		}
		if (getState() = 3) {
			showDebugSplash("(3) Ascending...")
			if (saveBeforeAscending) {
				save()
			}
			ascend(autoAscend)
			if (levelSolomon) {
				solomonLeveler(solomonLevels)
			}
			handleScheduledStop()
			handleScheduledReload("loopVisionRun")
		}
	}
}

visionRun() {
	global
	exitThread := false
	isResuming := false

	showSplash("Starting vision run...", 1, 0)
	startMonitoring()
	reFocus()

	local isInitiated := false
	local isClickerRunning := false
	local hasActivatedSkills := false

	local xBtn := 0, yBtn := 0, isNew := 0
	local xSkill := 0, ySkill := 0, skillSearch := false

	local zone := getCurrentZone()
	local startZone := zone
	local initZone := 145
	local endZone := getEndZone()
	local stopHuntZone := endZone - ceil(stopHuntThreshold * 250 / 7)

	local t := 0

	local comboDelay := deepRunCombo[1]
	comboIndex := 2

	startProgress("Vision Run", zone // barUpdateDelay, endZone // barUpdateDelay)

	loop
	{
		if (exitThread) {
			SetTimer, comboTimer, off
			clickerStop()
			stopProgress()
			stopMonitoring()
			showSplashAlways("Vision run aborted!")
			exit
		}
		; Traverse bottom up till we find the first gilded hero/ranger we can lvl up
		if (mod(t, 90) = 0 or isResuming) {
			if (locateGilded(xBtn, yBtn, isNew)) {
				maxClick(xBtn, yBtn, 1, 1)
				if (isNew) {
					showDebugSplash("New gilded hero found")
					sleep % coinPickUpDelay
					buyAvailableUpgrades()
				}
				skillSearch := true
				isResuming := false
			}
		}
		if (mod(t, 15) = 0) {
			; Make sure we are progressing
			if (!locateImage(imgProgression)) {
				showDebugSplash("Toggle progression mode")
				toggleMode()
			}
			if (!isInitiated and zone > initZone) {
				; If enough gold, run init
				showDebugSplash("Initializing...")
				isInitiated := initRun()
				isResuming := true
			}
		}
		zone := getCurrentZone()
		; Active zone?
		if (zone > endLvlIdle) {
			if (deepRunClicks) {
				if (!isClickerRunning) {
					; Yup, start hammering!
					showDebugSplash("Start external clicker")
					clickerStart() ; ~38 CPS
					isClickerRunning := true
					Gosub, comboTimer
					SetTimer, comboTimer, % comboDelay * 1000 + 1000
				}
				clickPos(xMonster, yMonster) ; Jugg combo safety click
				sleep 30
			}
		; If option enabled, activate skills once at start
		} else if (zone = startZone and activateSkillsAtStart and !hasActivatedSkills and isInitiated) {
			showDebugSplash("Activate skills at start")
			activateSkills(speedRunStartCombo[2])
			hasActivatedSkills := true
		}
		; Level up...
		if (mod(t, lvlUpDelay) = 0) {
			if (matchPixelColor(blueColor, xBtn+xWinPos, yBtn+yWinPos)) {
				if (skillSearch) {
					; Aquire possible new skills
					while (locateImage(imgSkill, xSkill, ySkill)) {
						clickPos(xSkill, ySkill, 1, 1)
						sleep % 500
					}
					if (!locateImage(imgDimmedSkill)) {
						skillSearch := false
					}
				}
				; ... when we can afford to do so
				ctrlClick(xBtn, yBtn, 1, 1, 1)
			} else if (!matchPixelColor(goldColor, xBtn-51+xWinPos, yBtn+yWinPos)) {
				if (!matchPixelColor(brightGoldColor, xBtn-51+xWinPos, yBtn+yWinPos)) {
					; ... or not, lost sight of our gilded hero
					showDebugSplash("Lost sight of our gilded hero")
					isResuming := true
				}
			}
		}
		; Let's go fishing!
		if (mod(t, clickableHuntDelay) = 0 and zone < stopHuntZone) {
			getClickable(useImageSearch)
		}
		t += 1
		updateProgress(zone // barUpdateDelay, endZone - zone, 1) ; show lvls remaining
		sleep 1000
	} until zone >= endZone

	SetTimer, comboTimer, off
	clickerStop()
	stopProgress()
	stopMonitoring()

	showSplash("Vision run completed.")
}

loopSpeedRun() {
	global
	local mode := hybridMode ? "hybrid" : "speed"
	showSplashAlways("Starting " . mode . " runs...")
	loop
	{
		if (useMidasStart) {
			midasStart()
			getClickable()
		} else {
			getClickable()
			sleep % coinPickUpDelay * 1000
		}
		initRun()
		if (activateSkillsAtStart) {
			activateSkills(speedRunStartCombo[2])
		}
		speedRun()
		if (hybridMode) {
			deepRun()
		}
		if (saveBeforeAscending) {
			save()
		}
		ascend(autoAscend)
		handleScheduledStop()
		handleScheduledReload("loopSpeedRun")
	}
}

; All heroes/rangers are expected to "insta-kill" everything at max speed (i.e. around
; 7 minutes per 250 levels). Only the last 2-3 minutes should slow down slightly.
speedRun() {
	global

	local stint := 0
	local stints := 0
	local tMax := 7 * 60 ; seconds
	local lMax := 250 ; zones

	local lvlAdjustment := round(firstStintAdjustment * lMax / tMax)
	local zoneLvl := gildedRanger * lMax + lvlAdjustment ; approx zone lvl where we can buy our gilded ranger @ lvl 150
	local lvls := zoneLvl - irisLevel ; lvl's to get there

	local firstStintButton := 1
	local firstStintTime := 0
	local midStintTime := 0

	if (lvls > lMax + 2*60*lMax/tMax) ; add a mid stint if needed
	{
		midStintTime := tMax
		lvls -= lMax
		stints += 1
	} else if (lvls > lMax) {
		firstStintButton := 2
	}

	if (lvls > 0)
	{
		firstStintTime := ceil(lvls * tMax / lMax)
		stints += 1
	}

	local srDuration := speedRunTime * 60
	local totalClickDelay := nextHeroDelay * stints
	local lastStintTime := srDuration - firstStintTime - midStintTime - totalClickDelay
	stints += 1

	local lastStintButton := gildedRanger = 9 ? 3 : 2 ; special case for Astraea

	if (debug)
	{
		local nl := "`r`n"
		local s := "    " ; Reddit friendly formatting
		local output := ""
		output .= s . "irisLevel = " . irisLevel . nl
		output .= s . "optimalLevel = " . optimalLevel . nl
		output .= s . "speedRunTime = " . speedRunTime . nl
		if (hybridMode) {
			output .= s . "deepRunTime = " . deepRunTime . nl
		}
		output .= s . "gildedRanger = " . rangers[gildedRanger] . nl
		output .= s . "-----------------------------" . nl
		output .= s . "initDownClicks = "
		for i, e in initDownClicks {
			output .= e " "
		}
		output .= nl
		output .= s . "yLvlInit = " . yLvlInit . nl
		output .= s . "firstStintAdjustment = " . firstStintAdjustment . "s" . nl
		output .= s . "-----------------------------" . nl
		output .= s . "lvlAdjustment = " . lvlAdjustment . nl
		output .= s . "zoneLvl = " . zoneLvl . nl
		output .= s . "lvls = " . lvls . nl
		output .= s . "firstStintButton = " . firstStintButton . nl
		output .= s . "firstStintTime = " . formatSeconds(firstStintTime) . nl
		output .= s . "midStintTime = " . formatSeconds(midStintTime) . nl
		output .= s . "lastStintTime = " . formatSeconds(lastStintTime) . nl
		output .= s . "totalClickDelay = " . formatSeconds(totalClickDelay) . nl

		clipboard := % output
		msgbox % output
		return
	}

	showSplash("Starting speed run...")

	if (irisLevel < 2 * lMax + 10) ; Iris high enough to start with a ranger?
	{
		switchToCombatTab()
		scrollDown(initDownClicks[1])
		toggleMode(!useMidasStart) ; toggle to progression mode
		lvlUp(firstStintTime, 0, 3, ++stint, stints) ; nope, let's bridge with Samurai
		scrollToBottom()
	} else {
		scrollToBottom()
		toggleMode(!useMidasStart) ; toggle to progression mode
		if (firstStintTime > 0) {
			lvlUp(firstStintTime, 1, firstStintButton, ++stint, stints)
			scrollWayDown(3)
		}
	}
	if (midStintTime > 0) {
		lvlUp(midStintTime, 1, 2, ++stint, stints)
		scrollWayDown(2)
	}
	lvlUp(lastStintTime, 1, lastStintButton, ++stint, stints)

	showSplash("Speed run completed.")
}

lvlUp(seconds, buyUpgrades, button, stint, stints) {
	global
	exitThread := false

	local y := yLvl + oLvl * (button - 1)
	local title := "Speed Run Progress (" . stint . "/" . stints . ")"

	startMonitoring()
	startProgress(title, 0, seconds // barUpdateDelay)

	if (buyUpgrades) {
		ctrlClick(xLvl, y)
		buyAvailableUpgrades()
	}
	maxClick(xLvl, y)

	local t := 0

	loop % seconds
	{
		if (exitThread) {
			stopProgress()
			stopMonitoring()
			showSplashAlways("Speed run aborted!")
			exit
		}
		if (mod(t, lvlUpDelay) = 0) {
			ctrlClick(xLvl, y, 1, 0)
		}
		t += 1
		updateProgress(t // barUpdateDelay, seconds - t)
		sleep 1000
	}
	stopProgress()
	stopMonitoring()
}

deepRun() {
	global
	exitThread := false

	local drDuration := deepRunTime * 60
	local button := gildedRanger = 9 ? 3 : 2 ; special case for Astraea
	local y := yLvl + oLvl * (button - 1)

	showSplash("Starting deep run...")

	startMonitoring()
	startProgress("Deep Run Progress", 0, drDuration // barUpdateDelay)
	clickerStart()

	local comboDelay := deepRunCombo[1]
	local comboIndex := 2
	local stopHuntIndex := drDuration - stopHuntThreshold * 60
	local t := 0

	loop % drDuration
	{
		if (exitThread) {
			clickerStop()
			stopProgress()
			stopMonitoring()
			showSplashAlways("Deep run aborted!")
			exit
		}
		if (deepRunClicks) {
			clickPos(xMonster, yMonster)
		}
		if (mod(t, comboDelay) = 0) {
			activateSkills(deepRunCombo[comboIndex])
			comboIndex := comboIndex < deepRunCombo.MaxIndex() ? comboIndex+1 : 2
		}
		if (mod(t, lvlUpDelay) = 0) {
			ctrlClick(xLvl, y, 1, 0)
		}
		if (mod(t, clickableHuntDelay) = 0 and t < stopHuntIndex) {
			getClickable()
		}
		t += 1
		updateProgress(t // barUpdateDelay, drDuration - t)
		sleep 1000
	}

	clickerStop()
	stopProgress()
	stopMonitoring()

	showSplash("Deep run ended.")
}

clickerStart() {
	sendClickerMsg(WM_CLICKER_START)
}

clickerPause() {
	sendClickerMsg(WM_CLICKER_PAUSE)
}

clickerStop() {
	sendClickerMsg(WM_CLICKER_STOP)
}

clickerReload() {
	sendClickerMsg(WM_CLICKER_RELOAD)
}

clickerInitialize() {
	sendClickerMsg(WM_CLICKER_INITIALIZE)
}

sendClickerMsg(msg) {
	global
	if (deepRunClicks) {
		DetectHiddenWindows, on
		PostMessage, %msg%,,,,% clickerName
		DetectHiddenWindows, off
	}
}

getClickerStatus() {
	global
	if (deepRunClicks) {
		DetectHiddenWindows, on
		SendMessage, %WM_CLICKER_STATUS%,,,,% clickerName
		DetectHiddenWindows, off
		if (ErrorLevel != "FAIL") {
			return ErrorLevel
		} else {
			showDebugSplash("SendMessage failed! monster_clicker.ahk started?")
		}
	}
	return 0
}

openSaveDialog() {
	global

	clickPos(xSettings, ySettings)
	sleep % zzz * 3
	clickPos(xSave, ySave)
	sleep % zzz * 4
}

save() {
	global
	local fileName := "ch" . A_NowUTC . ".txt"
	local newFileName := ""

	openSaveDialog()

	; Change the file name...
	if (saveMode = 1) {
		ControlSetText, Edit1, %fileName%, ahk_class %dialogBoxClass%
	} else {
		ControlSend, Edit1, %fileName%, ahk_class %dialogBoxClass%
	}
	sleep % zzz * 4
	; ... and double-check that it's correct
	ControlGetText, newFileName, Edit1, ahk_class %dialogBoxClass%
	if (newFileName = fileName) {
		ControlClick, %saveButtonClassNN%, ahk_class %dialogBoxClass%,,,, NA
	} else {
		ControlSend,, {esc}, ahk_class %dialogBoxClass%
	}

	sleep % zzz * 3
	clickPos(xSettingsClose, ySettingsClose)
	sleep % zzz
}

openAncientsOptimizer() {
	global

	local templateFileName := "system\ancients_optimizer_loader.html"
	FileRead, loaderSourceTemplate, %templateFileName%

	local loaderFileName := A_Temp . "\ch_ao_" . A_NowUTC . ".html"
	local file = FileOpen(loaderFileName, "w")
	if !IsObject(file)
	{
		MsgBox % "Can't open " . loaderFileName . " for writing."
		return
	}

	openSaveDialog()

	; Abort saving. Clipboard is good enough
	ControlSend,, {esc}, ahk_class %dialogBoxClass%

	sleep % zzz * 3
	clickPos(xSettingsClose, ySettingsClose)

	; Write loader file
	local loaderSource := StrReplace(loaderSourceTemplate, "#####SAVEGAME#####", Clipboard)

	file.write(loaderSource)
	file.Close()

	Run, %loaderFileName%
	sleep % zzz * 5
	FileDelete, %loaderFileName%
}

ascend(autoYes:=false) {
	global
	exitThread := false

	local x, y

	startMonitoring()

	if (autoYes) {
		if (autoAscendDelay > 0) {
			showWarningSplash(autoAscendDelay . " seconds till ASCENSION! (Abort with Alt+Pause)", autoAscendDelay)
			if (exitThread) {
				exitThread := false
				stopMonitoring()
				showSplashAlways("Ascension aborted!")
				exit
			}
		}
	} else {
		playWarningSound()
		msgbox, 260,% script,Salvage Junk Pile & Ascend? ; default no
		ifmsgbox no
			exit
	}

	salvageJunkPile() ; must salvage junk relics before ascending
	toggleMode()
	switchToCombatTab()
	scrollDown(ascDownClicks)

	if (useImageSearch and locator(imgBeastlord, "Beastlord", x, y)) {
		if (!locateImage(imgAscension)) {
			buySkill(imgAscension, x-279, y+60-oLvl, 4, 4)
		}
	}
	verticalSkills(xSkill + oSkill*3) ; ASCENSION

	sleep % zzz * 4
	clickPos(xYes, yYes)
	sleep % zzz * 2

	stopMonitoring()
}

salvageJunkPile() {
	global

	switchToRelicTab()

	if (autoAscend) {
		if (screenShotRelics || displayRelicsDuration > 0) {
			clickPos(xRelic, yRelic) ; focus
		}

		if (screenShotRelics) {
			screenShot()
		}

		if (displayRelicsDuration > 0) {
			showWarningSplash("Salvaging junk in " . displayRelicsDuration . " seconds! (Abort with Alt+Pause)", displayRelicsDuration)
			if (exitThread) {
				exitThread := false
				showSplashAlways("Salvage aborted!")
				exit
			}
		}

		if (screenShotRelics || displayRelicsDuration > 0) {
			clickPos(xRelic+100, yRelic) ; remove focus
		}
	}

	clickPos(xSalvageJunk, ySalvageJunk)
	sleep % zzz * 4
	clickPos(xDestroyYes, yDestroyYes)
	sleep % zzz * 2
}

buyAvailableUpgrades() {
	global
	clickPos(xBuy, yBuy)
	sleep % zzz * 3
}

openNewGilds() {
	global

	clickPos(xNewGild, yNewGild, 100)
	sleep 3000
	clickPos(xOpenGild, yOpenGild)
	sleep 2000
	clickPos(xCloseGild, yCloseGild)
	sleep 250
}

raid(doSpend:=0, attempts:=1) {
	global
	local xBtn := 0, yBtn := 0, raidDuration := 34 * 1000
	local isClickerRunning := getClickerStatus()

	switchToClanTab()
	sleep 1000
	clickAwayImage(imgClanRaid)

	if (!isClickerRunning) {
		clickerStart()
		sleep 2500
	}
	loop % attempts
	{
		if (clickAwayImage(imgClanCollect)) {
			break
		}
		if (doSpend) {
			if (locateImage(imgClanFightAgain, xBtn, yBtn)) {
				while (!locateImage(imgYes)) {
					clickPos(xBtn, yBtn, 1, 1)
					sleep 250
				}
				clickAwayImage(imgYes)
				clickAwayImage(imgClanFight)
				sleep % raidDuration
			}
		} else {
			if (clickAwayImage(imgClanFight)) {
				sleep % raidDuration
			}
		}
	}
	if (!isClickerRunning) {
		clickerStop()
	}
}

clickAwayImage(image) {
	if (locateImage(image)) {
		while (locateImage(image, x, y)) {
			clickPos(x, y, 1, 1)
			sleep 250
		}
		sleep 1000
		return 1
	}
	return 0
}

; Move "gildCount" gilds to given ranger
regild(ranger, gildCount) {
	global
	switchToCombatTab()
	scrollToBottom()

	clickPos(xGilded, yGilded)
	sleep % zzz * 2

	clickPos(xGildedDown, yGildedDown, top2BottomClicks)
	sleep % scrollDelay + top2BottomClicks * scrollClickDelay

	ControlSend,, {shift down}, ahk_id %chWinId%
	clickPos(rangerPositions[ranger].x, rangerPositions[ranger].y, gildCount)
	sleep % 1000 * gildCount/100*6
	ControlSend,, {shift up}, ahk_id %chWinId%

	clickPos(xGildedClose, yGildedClose)
	sleep % zzz * 2
}

; Toggle between farm and progression modes
toggleMode(toggle:=1) {
	global
	if (toggle) {
		ControlSend,, {sc01E}, ahk_id %chWinId% ; {a}, {vk41} or {sc01E}
		sleep % zzz
	}
}

activateSkills(skills) {
	global
	reFocus()
	loop,parse,skills,-
	{
		ControlSend,,% A_LoopField, ahk_id %chWinId%
		sleep 50
	}
}

startMonitoring() {
	global
	setTimer, checkMousePosition, 250
	if (useImageSearch) {
		setTimer, checkWindowVisibility, 20000
	}
}

stopMonitoring() {
	global
	setTimer, checkMousePosition, off
	if (useImageSearch) {
		setTimer, checkWindowVisibility, off
	}
}

handleScheduledReload(function := "") {
	global
	local params := function != "" ? "/autorun " . function : ""
	if (scheduleReload) {
		showSplashAlways("Reloading bot... " . params, 3)
		Run "%A_AhkPath%" /restart "%A_ScriptFullPath%" %params%
	}
}

handleScheduledStop() {
	global
	if (scheduleStop) {
		showSplashAlways("Scheduled stop. Exiting...")
		scheduleStop := false
		exit
	}
}

handleAutorun() {
	global
	option = %1%
	function = %2%
	if (option = "/autorun") {
		if (function = "loopSpeedRun") {
			loopSpeedRun()
		} else if (function = "loopVisionRun") {
			loopVisionRun()
		}
	}
}

hasClickable() {
	global
	return useImageSearch ? locateImage(imgClickable) : 0
}

; Try to find the first gilded hero/ranger we can lvl up
locateGilded(byref xPos, byref yPos, byref isNew) {
	global
	isNew := 0
	local xAbs, yAbs, startAt := 0

	if (!locateImage(imgBuyUpgrades)) {
		if (!locateImage(imgCombat)) {
			switchToCombatTab()
		}
		scrollToBottom()
	}

	while (upLocator(imgGilded, "Gilded hero", xAbs, yAbs, 5, 2, 1, startAt)) {
		local xPixel := xAbs + 83 ; HI[R]E
		local yPixel := yAbs + 38
		if (matchPixelColor(dimmedYellowColor, xPixel, yPixel)) {
			startAt := yAbs
			continue
		} else if (matchPixelColor(yellowColor, xPixel, yPixel)) {
			isNew := 1
		}
		; Transform to screen relative and offset coordinates to the lvl up button
		xPos := xAbs - xWinPos + 57
		yPos := yAbs - yWinPos + 56
		return 1
	}
	return 0
}

solomonLeveler(levels) {
	global
	local x, y
	if (useImageSearch) {
		switchToAncientTab()
		if (locator(imgSolomon, "Solomon", x, y)) {
			; Offset coordinates to the lvl up button
			x -= 365
			y += 28
			clickPos(x, y, levels, 1)
			sleep % zzz * 2
		}
	}
}

checkSafetyZones() {
	global
	local window, x, y
	local i, sz
	local xL, yT, xR, yB

	MouseGetPos,,, window
	if (window = WinExist(winName)) {

		WinActivate
		MouseGetPos, x, y

		for i, sz in safetyZones
		{
			xL := getAdjustedX(sz.x1)
			yT := getAdjustedY(sz.y1)
			xR := getAdjustedX(sz.x2)
			yB := getAdjustedY(sz.y2)

			if (x > xL && y > yT && x < xR && y < yB) {
				playNotificationSound()
				if (useImageSearch and locateImage(imgProgression)) {
					msgbox,,% script,Click safety pause engaged. Resume?
					if (!locateImage(imgCombat)) {
						switchToCombatTab()
					}
					isResuming := true
					reFocus()
				} else {
					msgbox,,% script,Click safety pause engaged. Continue?
				}
			}
		}
	}
}

; -----------------------------------------------------------------------------------------
; -- Subroutines
; -----------------------------------------------------------------------------------------

; Safety zone around the in-game tabs (that triggers an automatic script pause when breached)
checkMousePosition:
	checkSafetyZones()
return

checkWindowVisibility:
	if (!locateImage(imgSmile)) {
		WinActivate, ahk_id %chWinId%
	}
return

comboTimer:
	activateSkills(deepRunCombo[comboIndex])
	comboIndex := comboIndex < deepRunCombo.MaxIndex() ? comboIndex+1 : 2
return

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

; #Warn All
#Persistent
#NoEnv
#InstallKeybdHook

#Include %A_ScriptDir%
#Include ch_bot_lib.ahk

SetControlDelay, -1

scriptName=CH Sw1ft Bot
scriptVersion=3.x
minLibVersion=1.4

script := scriptName . " v" . scriptVersion

clickerName := "monster_clicker.ahk - AutoHotkey"

scheduleReload := false
scheduleStop := false

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

#Include system\wm_messages.ahk

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

IfNotExist, ch_bot_settings.ahk
{
	FileCopy, system\ch_bot_default_settings.ahk, ch_bot_settings.ahk
}

; Load user settings
#Include *i ch_bot_settings.ahk

if (libVersion != minLibVersion) {
	showWarningSplash("The bot lib version must be " . minLibVersion . "!")
	ExitApp
}

if (useConfigurationAssistant) {
	configurationAssistant()
}

clientCheck()

if (deepRunClicks) {
	Run, "%A_ScriptDir%\monster_clicker.ahk",, UseErrorLevel
	if (ErrorLevel != 0) {
		playWarningSound()
		msgbox,,% script,% "Failed to auto-start monster_clicker.ahk (system error code = " . A_LastError . ")!"
	}
}

handleAutorun()

; -----------------------------------------------------------------------------------------
; -- Hotkeys (+=Shift, !=Alt, ^=Ctrl, #=Win)
; -----------------------------------------------------------------------------------------

; Show the cursor position with Alt+Middle Mouse Button
!mbutton::
	mousegetpos, xpos, ypos
	msgbox,,% script,% "Cursor position: x" xpos-leftMarginOffset " y" ypos-topMarginOffset
return

; Top margin calculation when clicking in the center of the ancient tab eye
^mbutton::
	mousegetpos, xpos, ypos
	msgbox,,% script,% "Set browserTopMargin to: " . ypos-102
return

; -- Main Hotkeys -------------------------------------------------------------------------

; Start the Speed run loop with Ctrl+F1
^F1::
	loopSpeedRun()
return

; Start a Deep run with Ctrl+F2 (expecting to start where a Speed run finishes)
^F2::
	deepRun()
return

; Start a Vision run loop with Ctrl+F3
^F3::
	loopVisionRun()
return

; Pause/unpause the script
Pause::Pause
return

; Abort any active run or initiated ascension with Alt+Pause
!Pause::
	showSplashAlways("Aborting...")
	exitThread := true
return

; Schedule a stop after finishing the current run with Shift+Pause
+Pause::
	toggleFlag("scheduleStop", scheduleStop)
return

; Reload the script (needed after configuration changes)
!F5::
	critical
	global scheduleReload := true
	handleScheduledReload()
return

; Schedule a script reload after finishing the current run, then restart it
+^F5::
	toggleFlag("scheduleReload", scheduleReload)
return

; Re-initialize coordinates (needed after moving or re-sizing the client window)
!F6::
	critical
	clientCheck()
	clickerInitialize()
return

; -- Supplementary Hotkeys ----------------------------------------------------------------

; Suspend/Unsuspend all other Hotkeys with Ctrl+Esc
^Esc::Suspend, Toggle
return

; Open the Ancients Optimizer and auto-import game save data
^F5::
	critical
	openAncientsOptimizer()
return

; Set previous ranger as re-gild target
^F6::
	reGildRanger := reGildRanger > rangers.MinIndex() ? reGildRanger-1 : reGildRanger
	showSplashAlways("Re-gild ranger set to " . rangers[reGildRanger])
return

; Set next ranger as re-gild target
^F7::
	reGildRanger := reGildRanger < rangers.MaxIndex() ? reGildRanger+1 : reGildRanger
	showSplashAlways("Re-gild ranger set to " . rangers[reGildRanger])
return

; Move "reGildCount" gilds to the target ranger
^F8::
	critical
	playNotificationSound()
	msgbox, 4,% script,% "Move " . reGildCount . " gilds to " . rangers[reGildRanger] . "?"
	ifmsgbox no
		return
	clickerPause()
	regild(reGildRanger, reGildCount)
return

; Open new gilds
^F9::
	critical
	clickerPause()
	openNewGilds()
return

; Autosave the game
^F11::
	critical
	save()
return

; Raid once for free with Win+F6
#F6::
	critical
	raid()
return

; One paid raid
#F7::
	critical
	raid(1)
return

; Paid raids
#F8::
	critical
	raid(1, raidAttempts)
return

; Toggle boolean (true/false) flags with Shift+Ctrl+Fx

+^F1::
	toggleFlag("autoAscend", autoAscend)
return

+^F2::
	toggleFlag("screenShotRelics", screenShotRelics)
return

+^F6::
	toggleFlag("playNotificationSounds", playNotificationSounds)
return

+^F7::
	toggleFlag("playWarningSounds", playWarningSounds)
return

+^F8::
	toggleFlag("showSplashTexts", showSplashTexts)
return

+^F11::
	toggleFlag("saveBeforeAscending", saveBeforeAscending)
return

+^F12::
	toggleFlag("debug", debug)
return

; -- Test Hotkeys -------------------------------------------------------------------------

; Ctrl+Alt+F1 should scroll down to the bottom
^!F1::
	scrollToBottom()
return

; Ctrl+Alt+F2 should switch to the relics tab and then back
^!F2::
	switchToRelicTab()
	switchToCombatTab()
return

; Alt+F1 to F4 are here to test the individual parts of the full speed run loop

!F1::
	getClickable()
return

!F2::
	initRun()
return

!F3::
	switchToCombatTab()
	speedRun()
return

!F4::
	ascend(autoAscend)
return

; Test one Midas start with Win+F1
#F1::
	midasStart()
return

; Loop Midas start > init run > ascend, twice
#F2::
	loop 2
	{
		midasStart()
		initRun()
		ascend(autoAscend)
	}
return

testSearch(image, info:="") {
	global
	local extraInfo := info != "" ? " (" . info . ")" : ""
	local x, y
	msgbox,,% script,% "Search for " . image.file . "?" . extraInfo
	if (locateImage(image, x, y)) {
		msgbox,,% script,% "Success! (" . x . ", " . y . ")"
	} else {
		msgbox,,% script,% "Failed!"
	}
}

testLocate(image, clickCount:=5) {
	global
	local x, y
	msgbox,,% script,% "Locate " . image.file . "?"
	if (locator(image, image.file, x, y, clickCount, 1)) {
		msgbox,,% script,% "Success! (" . x . ", " . y . ")"
	} else {
		msgbox,,% script,% "Failed!"
	}
}

; Image search tests
#F3::
	msgbox,,% script,% "After clickable/Midas start state expected."
	testSearch(imgQuality, "Set high quality")
	testSearch(imgSmile, "Set low quality")
	testSearch(imgProgression, "Toggle progression mode")
	testSearch(imgClickable)
	switchToCombatTab()
	testSearch(imgCombat)
	testSearch(imgHire)
	testSearch(imgCoin)
	testSearch(imgDimmedSkill, "Lvl someone to 1")
	testSearch(imgSkill, "Lvl someone to 10")
	testLocate(imgCid)
	testSearch(imgClickstorm, "Buy Cid's skill")
	testSearch(imgSkillBar)
	testSearch(imgSkillLocked)
	testLocate(imgMercedes)
	testLocate(imgReferi, 2)
	testSearch(imgMetalDetector, "Lvl Broyle to 100")
	testSearch(imgGoldenClicks, "Lvl Midas to 100")
	testLocate(imgBeastlord)
	testSearch(imgAscension, "Lvl Amenhotep to 150")
	testLocate(imgDK)
	testSearch(imgFrigidEnchant, "Lvl Frostleaf to 100")

	msgbox,,% script,% "Locate " . imgGilded.file . "?"
	if (locateGilded(x, y, isNew)) {
		msgbox,,% script,% "Success! (" . x . ", " . y . ")"
	} else {
		msgbox,,% script,% "Failed!"
	}
	scrollToBottom()
	testSearch(imgBuyUpgrades)

	switchToAncientTab()
	testLocate(imgSolomon)
	msgbox,,% script,% "Done."
return

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

; Automatically configure initDownClicks and yLvlInit settings.
configurationAssistant() {
	global

	if (irisLevel < 145) {
		playWarningSound()
		msgbox,,% script,% "Your Iris do not fulfill the minimum level requirement of 145 or higher!"
		exit
	}

	if (irisThreshold(2010)) { ; Astraea
		initDownClicks := [6,5,6,5,6,3]
		yLvlInit := 241
	} else if (irisThreshold(1760)) { ; Alabaster
		initDownClicks := [6,6,6,5,6,3]
		yLvlInit := 227
	} else if (irisThreshold(1510)) { ; Cadmia
		initDownClicks := [6,6,6,6,6,3]
		yLvlInit := 240
	} else if (irisThreshold(1260)) { ; Lilin
		initDownClicks := [6,6,6,6,6,3]
		yLvlInit := 285
	} else if (irisThreshold(1010)) { ; Banana
		initDownClicks := [6,7,6,7,6,3]
		yLvlInit := 240
	} else if (irisThreshold(760)) { ; Phthalo
		initDownClicks := [6,7,7,6,7,3]
		yLvlInit := 273
	} else if (irisThreshold(510)) { ; Terra
		initDownClicks := [7,7,7,7,7,3]
		yLvlInit := 240
	} else if (irisThreshold(260)) { ; Atlas
		initDownClicks := [7,7,7,8,7,3]
		yLvlInit := 273
	} else { ; Dread Knight
		initDownClicks := [7,8,7,8,7,4]
		yLvlInit := 257
	}

	if (irisLevel < optimalLevel - 1001) {
		local levels := optimalLevel - 1001 - irisLevel
		playNotificationSound()
		msgbox,,% script,% "Your Iris is " . levels . " levels below the recommended ""optimal level - 1001"" rule."
	}
}

; Check if Iris is within a certain threshold that can cause a toggling behaviour between different settings
irisThreshold(lvl) {
	global
	local upperThreshold := lvl + 19
	local lowerThreshold := lvl - 20
	if (irisLevel >= lowerThreshold and irisLevel < upperThreshold) {
		playWarningSound()
		msgbox,,% script,% "Threshold proximity warning! You should level up your Iris to " . upperThreshold . " or higher."
	}
	return irisLevel > lvl
}

; Level up and upgrade all heroes
initRun() {
	global
	local hasLeveledHeroes := false

	switchToCombatTab()
	reFocus()

	if (!useImageSearch) {
		if (initPlanB) {
			local y
			local clicks := irisLevel > 1500 ? 6 : 7

			loop 7
			{
				y := 213
				loop
				{
					ctrlClick(xLvl, y, 2)
					y += 74
				} until y > 669
				scrollDown(clicks)
			}
		} else {
			upgrade(initDownClicks[1],2,,2) ; cid --> brittany
			upgrade(initDownClicks[2]) ; fisherman --> leon
			upgrade(initDownClicks[3]) ; seer --> mercedes
			upgrade(initDownClicks[4],,,,2) ; bobby --> king
			upgrade(initDownClicks[5],2,,,2) ; ice --> amenhotep
			upgrade(initDownClicks[6],,,2) ; beastlord --> shinatobe
			upgrade(0,,,,,true) ; grant & frostleaf
		}
	} else {
		local foundDK := false
		local xButton, yButton, xDK, yDK, x

		locateImage(imgCoin, xButton, yButton) ; get a x coordinate for the hire/lvl up buttons

		loop 9 ; pages
		{
			foundDK := locateImage(imgDK, xDK, yDK)
			loop 5 ; attempts per page
			{
				if (locateImage(imgDimmedSkill, x, yButton)
						or locateImage(imgHire, x, yButton)) {
					if (foundDK and yButton > yDK) {
						; Don't level anything below Frostleaf
						continue
					}
					ctrlClick(xButton, yButton, 2, 1, 1)
				} else {
					break
				}
			}
			if (foundDK) {
				if (locateImage(imgFrigidEnchant)) {
					hasLeveledHeroes := true
				}
				break
			}
			scrollDown(5)
		}
	}
	scrollToBottom()
	buyAvailableUpgrades()

	return useImageSearch ? hasLeveledHeroes and locateImage(imgSkillBar) and !locateImage(imgSkillLocked) : 1
}

upgrade(times, cc1:=1, cc2:=1, cc3:=1, cc4:=1, skip:=false) {
	global

	if (!skip) {
		ctrlClick(xLvl, yLvlInit, cc1)
		ctrlClick(xLvl, yLvlInit + oLvl, cc2)
	}
	ctrlClick(xLvl, yLvlInit + oLvl*2, cc3)
	ctrlClick(xLvl, yLvlInit + oLvl*3, cc4)

	scrollDown(times)
}

midasStart() {
	global

	local xl := xLvl
	local yl := yLvl - 10

	local midasZone1 := midasZoneConfig[1]
	local midasDelay1 := midasZoneConfig[2]

	local midasExtraZone := midasZoneConfig[3]
	local midasExtraDelay := midasZoneConfig[4]

	local midasZone2 := midasZoneConfig[5]
	local midasDelay2 := midasZoneConfig[6]

	local fromZone := midasExtraZone > 0 ? midasExtraZone : midasZone1

	switchToCombatTab()
	startMonitoring()
	reFocus()

	if (!useImageSearch) {
		loop 3 {
			clickPos(xMonster, yMonster) ; Break idle
			sleep 30
		}
		scrollToZone(1, midasZone1)
		ctrlClick(xl, yl) ; Cid x 100
		clickPos(xSkill + oSkill, ySkillTop) ; Clickstorm
		sleep % zzz

		scrollToBottom()
		clickPos(xl, yl+oLvl) ; Natalia
		sleep % midasDelay1 * 1000
		ctrlClick(xl, yl+oLvl) ; Natalia x 100

		if (midasExtraZone > 0) {
			scrollToZone(midasZone1, midasExtraZone)
			horizontalSkills(xSkill, ySkill2nd, 4)
			sleep % midasExtraDelay * 1000
		}
		scrollDown(8)
		scrollToZone(fromZone, midasZone2)
		ctrlClick(xl, yl+oLvl) ; Broyle x 100
		sleep % midasDelay2 * 1000
		ctrlClick(xl, yl+oLvl*3) ; Midas x 100
		verticalSkills(xSkill + oSkill*4) ; Metal Detector + Golden Clicks

		toggleMode()
		activateSkills("1-4-5")
		sleep % coinPickUpDelay * 1000
	} else {
		if (locateImage(imgProgression)) {
			showDebugSplash("Toggle farm mode")
			toggleMode()
		}
		scrollToZone(1, midasZone1)
		locator(imgCid, "Cid", xl, yl)
		buySkill(imgClickstorm, xl-137, yl+60, 2, 7)

		scrollToBottom()
		upLocator(imgCoin, "Coin", xl, yl)
		ctrlClick(xl, yl, 1, 1, 1)
		sleep % midasDelay1 * 1000
		ctrlClick(xl, yl, 1, 1, 1)
		if (midasExtraZone > 0) {
			scrollToZone(midasZone1, midasExtraZone)
			sleep % midasExtraDelay * 1000
			ctrlClick(xl, yl, 1, 1, 1)
		}
		if (locateImage(imgMercedes)) {
			scrollDown(8)
		} else {
			showDebugSplash("Unknown location, relocating...")
			switchToCombatTab()
			scrollDown(18)
		}
		scrollToZone(fromZone, midasZone2)
		locator(imgReferi, "Referi", xl, yl, 2)
		xl -= 155
		yl += 60
		buySkill(imgMetalDetector, xl, yl-oLvl*3, 5, 5)
		sleep % midasDelay2 * 1000
		buySkill(imgGoldenClicks, xl, yl-oLvl, 5, 6)

		toggleMode()
		activateSkills("1-4-5")
		sleep % coinPickUpDelay * 1000
		zClick(xl, yl-oLvl, 1, 1) ; Midas 125
	}
	stopMonitoring()
}

buySkill(image, xLvlUp, yLvlUp, skill, skills) {
	global
	local xSkill, ySkill
	; msgbox % image.file . " (" . xLvlUp . ", " . yLvlUp . ")"
	if (!locateImage(image) or locateImage(imgSkillLocked)) {
		reFocus()
		ctrlClick(xLvlUp, yLvlUp, 1, 1, 1) ; x 100
		zClick(xLvlUp, yLvlUp, 2, 1) ; x 50
		loop 5
		{
			if (locateImage(imgDimmedSkill)) {
				sleep 2500
			}
			if (locateImage(image, xSkill, ySkill)) {
				clickPos(xSkill, ySkill, 1, 1)
				sleep % zzz
				horizontalSkills(xSkill - oSkill*(skill-1), ySkill, skills, 1)
				break
			} else {
				sleep 2500
				zClick(xLvlUp, yLvlUp, 2, 1)
			}
		}
	}
}

getEndZone() {
	global
	return endLvlActive > endLvlIdle ? endLvlActive : endLvlIdle
}

getState() {
	global

	if (!WinExist(winName)) {
		return -3 ; no ch window
	}
	if (!useImageSearch) {
		return -2 ; no vision
	}
	if (getCurrentZone() = 0) {
		return -1 ; vision, but not in browser
	}
	if (!locateImage(imgSmile)) {
		return 0 ; vision, but not finding anything
	}
	if (!locateImage(imgProgression) and getCurrentZone() < irisLevel) {
		return 1 ; ready to start
	}
	if (getCurrentZone() < getEndZone()) {
		return 2 ; ready for progression
	} else {
		return 3 ; ready to ascend
	}
}

loopVisionRun() {
	global
	local state := 0
	showSplashAlways("Starting vision runs...")

	loop
	{
		state := getState()
		if (state < 1) {
			showWarningSplash("Start failed (state = " . state . ")! Trying again...")
			clientCheck()
			clickerInitialize()
			continue
		}
		if (state = 1) {
			if (hasClickable()) {
				showDebugSplash("(1) Clickable start...")
				getClickable(useImageSearch)
				sleep % coinPickUpDelay * 1000
				switchToCombatTab()
				ctrlClick(xLvl, yLvl+oLvl) ; Force progression
				toggleMode()
			} else {
				showDebugSplash("(1) Midas start...")
				midasStart()
			}
		}
		if (getState() = 2) {
			showDebugSplash("(2) Progressing...")
			visionRun()
		}
		if (getState() = 3) {
			showDebugSplash("(3) Ascending...")
			if (saveBeforeAscending) {
				save()
			}
			ascend(autoAscend)
			if (levelSolomon) {
				solomonLeveler(solomonLevels)
			}
			handleScheduledStop()
			handleScheduledReload("loopVisionRun")
		}
	}
}

visionRun() {
	global
	exitThread := false
	isResuming := false

	showSplash("Starting vision run...", 1, 0)
	startMonitoring()
	reFocus()

	local isInitiated := false
	local isClickerRunning := false
	local hasActivatedSkills := false

	local xBtn := 0, yBtn := 0, isNew := 0
	local xSkill := 0, ySkill := 0, skillSearch := false

	local zone := getCurrentZone()
	local startZone := zone
	local initZone := 145
	local endZone := getEndZone()
	local stopHuntZone := endZone - ceil(stopHuntThreshold * 250 / 7)

	local t := 0

	local comboDelay := deepRunCombo[1]
	comboIndex := 2

	startProgress("Vision Run", zone // barUpdateDelay, endZone // barUpdateDelay)

	loop
	{
		if (exitThread) {
			SetTimer, comboTimer, off
			clickerStop()
			stopProgress()
			stopMonitoring()
			showSplashAlways("Vision run aborted!")
			exit
		}
		; Traverse bottom up till we find the first gilded hero/ranger we can lvl up
		if (mod(t, 90) = 0 or isResuming) {
			if (locateGilded(xBtn, yBtn, isNew)) {
				maxClick(xBtn, yBtn, 1, 1)
				if (isNew) {
					showDebugSplash("New gilded hero found")
					sleep % coinPickUpDelay
					buyAvailableUpgrades()
				}
				skillSearch := true
				isResuming := false
			}
		}
		if (mod(t, 15) = 0) {
			; Make sure we are progressing
			if (!locateImage(imgProgression)) {
				showDebugSplash("Toggle progression mode")
				toggleMode()
			}
			if (!isInitiated and zone > initZone) {
				; If enough gold, run init
				showDebugSplash("Initializing...")
				isInitiated := initRun()
				isResuming := true
			}
		}
		zone := getCurrentZone()
		; Active zone?
		if (zone > endLvlIdle) {
			if (deepRunClicks) {
				if (!isClickerRunning) {
					; Yup, start hammering!
					showDebugSplash("Start external clicker")
					clickerStart() ; ~38 CPS
					isClickerRunning := true
					Gosub, comboTimer
					SetTimer, comboTimer, % comboDelay * 1000 + 1000
				}
				clickPos(xMonster, yMonster) ; Jugg combo safety click
				sleep 30
			}
		; If option enabled, activate skills once at start
		} else if (zone = startZone and activateSkillsAtStart and !hasActivatedSkills and isInitiated) {
			showDebugSplash("Activate skills at start")
			activateSkills(speedRunStartCombo[2])
			hasActivatedSkills := true
		}
		; Level up...
		if (mod(t, lvlUpDelay) = 0) {
			if (matchPixelColor(blueColor, xBtn+xWinPos, yBtn+yWinPos)) {
				if (skillSearch) {
					; Aquire possible new skills
					while (locateImage(imgSkill, xSkill, ySkill)) {
						clickPos(xSkill, ySkill, 1, 1)
						sleep % 500
					}
					if (!locateImage(imgDimmedSkill)) {
						skillSearch := false
					}
				}
				; ... when we can afford to do so
				ctrlClick(xBtn, yBtn, 1, 1, 1)
			} else if (!matchPixelColor(goldColor, xBtn-51+xWinPos, yBtn+yWinPos)) {
				if (!matchPixelColor(brightGoldColor, xBtn-51+xWinPos, yBtn+yWinPos)) {
					; ... or not, lost sight of our gilded hero
					showDebugSplash("Lost sight of our gilded hero")
					isResuming := true
				}
			}
		}
		; Let's go fishing!
		if (mod(t, clickableHuntDelay) = 0 and zone < stopHuntZone) {
			getClickable(useImageSearch)
		}
		t += 1
		updateProgress(zone // barUpdateDelay, endZone - zone, 1) ; show lvls remaining
		sleep 1000
	} until zone >= endZone

	SetTimer, comboTimer, off
	clickerStop()
	stopProgress()
	stopMonitoring()

	showSplash("Vision run completed.")
}

loopSpeedRun() {
	global
	local mode := hybridMode ? "hybrid" : "speed"
	showSplashAlways("Starting " . mode . " runs...")
	loop
	{
		if (useMidasStart) {
			midasStart()
			getClickable()
		} else {
			getClickable()
			sleep % coinPickUpDelay * 1000
		}
		initRun()
		if (activateSkillsAtStart) {
			activateSkills(speedRunStartCombo[2])
		}
		speedRun()
		if (hybridMode) {
			deepRun()
		}
		if (saveBeforeAscending) {
			save()
		}
		ascend(autoAscend)
		handleScheduledStop()
		handleScheduledReload("loopSpeedRun")
	}
}

; All heroes/rangers are expected to "insta-kill" everything at max speed (i.e. around
; 7 minutes per 250 levels). Only the last 2-3 minutes should slow down slightly.
speedRun() {
	global

	local stint := 0
	local stints := 0
	local tMax := 7 * 60 ; seconds
	local lMax := 250 ; zones

	local lvlAdjustment := round(firstStintAdjustment * lMax / tMax)
	local zoneLvl := gildedRanger * lMax + lvlAdjustment ; approx zone lvl where we can buy our gilded ranger @ lvl 150
	local lvls := zoneLvl - irisLevel ; lvl's to get there

	local firstStintButton := 1
	local firstStintTime := 0
	local midStintTime := 0

	if (lvls > lMax + 2*60*lMax/tMax) ; add a mid stint if needed
	{
		midStintTime := tMax
		lvls -= lMax
		stints += 1
	} else if (lvls > lMax) {
		firstStintButton := 2
	}

	if (lvls > 0)
	{
		firstStintTime := ceil(lvls * tMax / lMax)
		stints += 1
	}

	local srDuration := speedRunTime * 60
	local totalClickDelay := nextHeroDelay * stints
	local lastStintTime := srDuration - firstStintTime - midStintTime - totalClickDelay
	stints += 1

	local lastStintButton := gildedRanger = 9 ? 3 : 2 ; special case for Astraea

	if (debug)
	{
		local nl := "`r`n"
		local s := "    " ; Reddit friendly formatting
		local output := ""
		output .= s . "irisLevel = " . irisLevel . nl
		output .= s . "optimalLevel = " . optimalLevel . nl
		output .= s . "speedRunTime = " . speedRunTime . nl
		if (hybridMode) {
			output .= s . "deepRunTime = " . deepRunTime . nl
		}
		output .= s . "gildedRanger = " . rangers[gildedRanger] . nl
		output .= s . "-----------------------------" . nl
		output .= s . "initDownClicks = "
		for i, e in initDownClicks {
			output .= e " "
		}
		output .= nl
		output .= s . "yLvlInit = " . yLvlInit . nl
		output .= s . "firstStintAdjustment = " . firstStintAdjustment . "s" . nl
		output .= s . "-----------------------------" . nl
		output .= s . "lvlAdjustment = " . lvlAdjustment . nl
		output .= s . "zoneLvl = " . zoneLvl . nl
		output .= s . "lvls = " . lvls . nl
		output .= s . "firstStintButton = " . firstStintButton . nl
		output .= s . "firstStintTime = " . formatSeconds(firstStintTime) . nl
		output .= s . "midStintTime = " . formatSeconds(midStintTime) . nl
		output .= s . "lastStintTime = " . formatSeconds(lastStintTime) . nl
		output .= s . "totalClickDelay = " . formatSeconds(totalClickDelay) . nl

		clipboard := % output
		msgbox % output
		return
	}

	showSplash("Starting speed run...")

	if (irisLevel < 2 * lMax + 10) ; Iris high enough to start with a ranger?
	{
		switchToCombatTab()
		scrollDown(initDownClicks[1])
		toggleMode(!useMidasStart) ; toggle to progression mode
		lvlUp(firstStintTime, 0, 3, ++stint, stints) ; nope, let's bridge with Samurai
		scrollToBottom()
	} else {
		scrollToBottom()
		toggleMode(!useMidasStart) ; toggle to progression mode
		if (firstStintTime > 0) {
			lvlUp(firstStintTime, 1, firstStintButton, ++stint, stints)
			scrollWayDown(3)
		}
	}
	if (midStintTime > 0) {
		lvlUp(midStintTime, 1, 2, ++stint, stints)
		scrollWayDown(2)
	}
	lvlUp(lastStintTime, 1, lastStintButton, ++stint, stints)

	showSplash("Speed run completed.")
}

lvlUp(seconds, buyUpgrades, button, stint, stints) {
	global
	exitThread := false

	local y := yLvl + oLvl * (button - 1)
	local title := "Speed Run Progress (" . stint . "/" . stints . ")"

	startMonitoring()
	startProgress(title, 0, seconds // barUpdateDelay)

	if (buyUpgrades) {
		ctrlClick(xLvl, y)
		buyAvailableUpgrades()
	}
	maxClick(xLvl, y)

	local t := 0

	loop % seconds
	{
		if (exitThread) {
			stopProgress()
			stopMonitoring()
			showSplashAlways("Speed run aborted!")
			exit
		}
		if (mod(t, lvlUpDelay) = 0) {
			ctrlClick(xLvl, y, 1, 0)
		}
		t += 1
		updateProgress(t // barUpdateDelay, seconds - t)
		sleep 1000
	}
	stopProgress()
	stopMonitoring()
}

deepRun() {
	global
	exitThread := false

	local drDuration := deepRunTime * 60
	local button := gildedRanger = 9 ? 3 : 2 ; special case for Astraea
	local y := yLvl + oLvl * (button - 1)

	showSplash("Starting deep run...")

	startMonitoring()
	startProgress("Deep Run Progress", 0, drDuration // barUpdateDelay)
	clickerStart()

	local comboDelay := deepRunCombo[1]
	local comboIndex := 2
	local stopHuntIndex := drDuration - stopHuntThreshold * 60
	local t := 0

	loop % drDuration
	{
		if (exitThread) {
			clickerStop()
			stopProgress()
			stopMonitoring()
			showSplashAlways("Deep run aborted!")
			exit
		}
		if (deepRunClicks) {
			clickPos(xMonster, yMonster)
		}
		if (mod(t, comboDelay) = 0) {
			activateSkills(deepRunCombo[comboIndex])
			comboIndex := comboIndex < deepRunCombo.MaxIndex() ? comboIndex+1 : 2
		}
		if (mod(t, lvlUpDelay) = 0) {
			ctrlClick(xLvl, y, 1, 0)
		}
		if (mod(t, clickableHuntDelay) = 0 and t < stopHuntIndex) {
			getClickable()
		}
		t += 1
		updateProgress(t // barUpdateDelay, drDuration - t)
		sleep 1000
	}

	clickerStop()
	stopProgress()
	stopMonitoring()

	showSplash("Deep run ended.")
}

clickerStart() {
	sendClickerMsg(WM_CLICKER_START)
}

clickerPause() {
	sendClickerMsg(WM_CLICKER_PAUSE)
}

clickerStop() {
	sendClickerMsg(WM_CLICKER_STOP)
}

clickerReload() {
	sendClickerMsg(WM_CLICKER_RELOAD)
}

clickerInitialize() {
	sendClickerMsg(WM_CLICKER_INITIALIZE)
}

sendClickerMsg(msg) {
	global
	if (deepRunClicks) {
		DetectHiddenWindows, on
		PostMessage, %msg%,,,,% clickerName
		DetectHiddenWindows, off
	}
}

getClickerStatus() {
	global
	if (deepRunClicks) {
		DetectHiddenWindows, on
		SendMessage, %WM_CLICKER_STATUS%,,,,% clickerName
		DetectHiddenWindows, off
		if (ErrorLevel != "FAIL") {
			return ErrorLevel
		} else {
			showDebugSplash("SendMessage failed! monster_clicker.ahk started?")
		}
	}
	return 0
}

openSaveDialog() {
	global

	clickPos(xSettings, ySettings)
	sleep % zzz * 3
	clickPos(xSave, ySave)
	sleep % zzz * 4
}

save() {
	global
	local fileName := "ch" . A_NowUTC . ".txt"
	local newFileName := ""

	openSaveDialog()

	; Change the file name...
	if (saveMode = 1) {
		ControlSetText, Edit1, %fileName%, ahk_class %dialogBoxClass%
	} else {
		ControlSend, Edit1, %fileName%, ahk_class %dialogBoxClass%
	}
	sleep % zzz * 4
	; ... and double-check that it's correct
	ControlGetText, newFileName, Edit1, ahk_class %dialogBoxClass%
	if (newFileName = fileName) {
		ControlClick, %saveButtonClassNN%, ahk_class %dialogBoxClass%,,,, NA
	} else {
		ControlSend,, {esc}, ahk_class %dialogBoxClass%
	}

	sleep % zzz * 3
	clickPos(xSettingsClose, ySettingsClose)
	sleep % zzz
}

openAncientsOptimizer() {
	global

	local templateFileName := "system\ancients_optimizer_loader.html"
	FileRead, loaderSourceTemplate, %templateFileName%

	local loaderFileName := A_Temp . "\ch_ao_" . A_NowUTC . ".html"
	local file = FileOpen(loaderFileName, "w")
	if !IsObject(file)
	{
		MsgBox % "Can't open " . loaderFileName . " for writing."
		return
	}

	openSaveDialog()

	; Abort saving. Clipboard is good enough
	ControlSend,, {esc}, ahk_class %dialogBoxClass%

	sleep % zzz * 3
	clickPos(xSettingsClose, ySettingsClose)

	; Write loader file
	local loaderSource := StrReplace(loaderSourceTemplate, "#####SAVEGAME#####", Clipboard)

	file.write(loaderSource)
	file.Close()

	Run, %loaderFileName%
	sleep % zzz * 5
	FileDelete, %loaderFileName%
}

ascend(autoYes:=false) {
	global
	exitThread := false

	local x, y

	startMonitoring()

	if (autoYes) {
		if (autoAscendDelay > 0) {
			showWarningSplash(autoAscendDelay . " seconds till ASCENSION! (Abort with Alt+Pause)", autoAscendDelay)
			if (exitThread) {
				exitThread := false
				stopMonitoring()
				showSplashAlways("Ascension aborted!")
				exit
			}
		}
	} else {
		playWarningSound()
		msgbox, 260,% script,Salvage Junk Pile & Ascend? ; default no
		ifmsgbox no
			exit
	}

	salvageJunkPile() ; must salvage junk relics before ascending
	toggleMode()
	switchToCombatTab()
	scrollDown(ascDownClicks)

	if (useImageSearch and locator(imgBeastlord, "Beastlord", x, y)) {
		if (!locateImage(imgAscension)) {
			buySkill(imgAscension, x-279, y+60-oLvl, 4, 4)
		}
	}
	verticalSkills(xSkill + oSkill*3) ; ASCENSION

	sleep % zzz * 4
	clickPos(xYes, yYes)
	sleep % zzz * 2

	stopMonitoring()
}

salvageJunkPile() {
	global

	switchToRelicTab()

	if (autoAscend) {
		if (screenShotRelics || displayRelicsDuration > 0) {
			clickPos(xRelic, yRelic) ; focus
		}

		if (screenShotRelics) {
			screenShot()
		}

		if (displayRelicsDuration > 0) {
			showWarningSplash("Salvaging junk in " . displayRelicsDuration . " seconds! (Abort with Alt+Pause)", displayRelicsDuration)
			if (exitThread) {
				exitThread := false
				showSplashAlways("Salvage aborted!")
				exit
			}
		}

		if (screenShotRelics || displayRelicsDuration > 0) {
			clickPos(xRelic+100, yRelic) ; remove focus
		}
	}

	clickPos(xSalvageJunk, ySalvageJunk)
	sleep % zzz * 4
	clickPos(xDestroyYes, yDestroyYes)
	sleep % zzz * 2
}

buyAvailableUpgrades() {
	global
	clickPos(xBuy, yBuy)
	sleep % zzz * 3
}

openNewGilds() {
	global

	clickPos(xNewGild, yNewGild, 100)
	sleep 3000
	clickPos(xOpenGild, yOpenGild)
	sleep 2000
	clickPos(xCloseGild, yCloseGild)
	sleep 250
}

raid(doSpend:=0, attempts:=1) {
	global
	local xBtn := 0, yBtn := 0, raidDuration := 34 * 1000
	local isClickerRunning := getClickerStatus()

	switchToClanTab()
	sleep 1000
	clickAwayImage(imgClanRaid)

	if (!isClickerRunning) {
		clickerStart()
		sleep 2500
	}
	loop % attempts
	{
		if (clickAwayImage(imgClanCollect)) {
			break
		}
		if (doSpend) {
			if (locateImage(imgClanFightAgain, xBtn, yBtn)) {
				while (!locateImage(imgYes)) {
					clickPos(xBtn, yBtn, 1, 1)
					sleep 250
				}
				clickAwayImage(imgYes)
				clickAwayImage(imgClanFight)
				sleep % raidDuration
			}
		} else {
			if (clickAwayImage(imgClanFight)) {
				sleep % raidDuration
			}
		}
	}
	if (!isClickerRunning) {
		clickerStop()
	}
}

clickAwayImage(image) {
	if (locateImage(image)) {
		while (locateImage(image, x, y)) {
			clickPos(x, y, 1, 1)
			sleep 250
		}
		sleep 1000
		return 1
	}
	return 0
}

; Move "gildCount" gilds to given ranger
regild(ranger, gildCount) {
	global
	switchToCombatTab()
	scrollToBottom()

	clickPos(xGilded, yGilded)
	sleep % zzz * 2

	clickPos(xGildedDown, yGildedDown, top2BottomClicks)
	sleep % scrollDelay + top2BottomClicks * scrollClickDelay

	ControlSend,, {shift down}, ahk_id %chWinId%
	clickPos(rangerPositions[ranger].x, rangerPositions[ranger].y, gildCount)
	sleep % 1000 * gildCount/100*6
	ControlSend,, {shift up}, ahk_id %chWinId%

	clickPos(xGildedClose, yGildedClose)
	sleep % zzz * 2
}

; Toggle between farm and progression modes
toggleMode(toggle:=1) {
	global
	if (toggle) {
		ControlSend,, {sc01E}, ahk_id %chWinId% ; {a}, {vk41} or {sc01E}
		sleep % zzz
	}
}

activateSkills(skills) {
	global
	reFocus()
	loop,parse,skills,-
	{
		ControlSend,,% A_LoopField, ahk_id %chWinId%
		sleep 50
	}
}

startMonitoring() {
	global
	setTimer, checkMousePosition, 250
	if (useImageSearch) {
		setTimer, checkWindowVisibility, 20000
	}
}

stopMonitoring() {
	global
	setTimer, checkMousePosition, off
	if (useImageSearch) {
		setTimer, checkWindowVisibility, off
	}
}

handleScheduledReload(function := "") {
	global
	local params := function != "" ? "/autorun " . function : ""
	if (scheduleReload) {
		showSplashAlways("Reloading bot... " . params, 3)
		Run "%A_AhkPath%" /restart "%A_ScriptFullPath%" %params%
	}
}

handleScheduledStop() {
	global
	if (scheduleStop) {
		showSplashAlways("Scheduled stop. Exiting...")
		scheduleStop := false
		exit
	}
}

handleAutorun() {
	global
	option = %1%
	function = %2%
	if (option = "/autorun") {
		if (function = "loopSpeedRun") {
			loopSpeedRun()
		} else if (function = "loopVisionRun") {
			loopVisionRun()
		}
	}
}

hasClickable() {
	global
	return useImageSearch ? locateImage(imgClickable) : 0
}

; Try to find the first gilded hero/ranger we can lvl up
locateGilded(byref xPos, byref yPos, byref isNew) {
	global
	isNew := 0
	local xAbs, yAbs, startAt := 0

	if (!locateImage(imgBuyUpgrades)) {
		if (!locateImage(imgCombat)) {
			switchToCombatTab()
		}
		scrollToBottom()
	}

	while (upLocator(imgGilded, "Gilded hero", xAbs, yAbs, 5, 2, 1, startAt)) {
		local xPixel := xAbs + 83 ; HI[R]E
		local yPixel := yAbs + 38
		if (matchPixelColor(dimmedYellowColor, xPixel, yPixel)) {
			startAt := yAbs
			continue
		} else if (matchPixelColor(yellowColor, xPixel, yPixel)) {
			isNew := 1
		}
		; Transform to screen relative and offset coordinates to the lvl up button
		xPos := xAbs - xWinPos + 57
		yPos := yAbs - yWinPos + 56
		return 1
	}
	return 0
}

solomonLeveler(levels) {
	global
	local x, y
	if (useImageSearch) {
		switchToAncientTab()
		if (locator(imgSolomon, "Solomon", x, y)) {
			; Offset coordinates to the lvl up button
			x -= 365
			y += 28
			clickPos(x, y, levels, 1)
			sleep % zzz * 2
		}
	}
}

checkSafetyZones() {
	global
	local window, x, y
	local i, sz
	local xL, yT, xR, yB

	MouseGetPos,,, window
	if (window = WinExist(winName)) {

		WinActivate
		MouseGetPos, x, y

		for i, sz in safetyZones
		{
			xL := getAdjustedX(sz.x1)
			yT := getAdjustedY(sz.y1)
			xR := getAdjustedX(sz.x2)
			yB := getAdjustedY(sz.y2)

			if (x > xL && y > yT && x < xR && y < yB) {
				playNotificationSound()
				if (useImageSearch and locateImage(imgProgression)) {
					msgbox,,% script,Click safety pause engaged. Resume?
					if (!locateImage(imgCombat)) {
						switchToCombatTab()
					}
					isResuming := true
					reFocus()
				} else {
					msgbox,,% script,Click safety pause engaged. Continue?
				}
			}
		}
	}
}

; -----------------------------------------------------------------------------------------
; -- Subroutines
; -----------------------------------------------------------------------------------------

; Safety zone around the in-game tabs (that triggers an automatic script pause when breached)
checkMousePosition:
	checkSafetyZones()
return

checkWindowVisibility:
	if (!locateImage(imgSmile)) {
		WinActivate, ahk_id %chWinId%
	}
return

comboTimer:
	activateSkills(deepRunCombo[comboIndex])
	comboIndex := comboIndex < deepRunCombo.MaxIndex() ? comboIndex+1 : 2
return


mod-ahk: bind arrows to scroll lock

mod-ahk: bind arrows to scroll lock

;Control key Ctrl- ^
;Alt key Alt- !
;Shift key Shift- +
;Windows key Win- #

#Persistent
#SingleInstance Force
DetectHiddenWindows, On
DetectHiddenText, On
SetTitleMatchMode, RegEx ;match titles and classes using regular expressions
SetTitleMatchMode Slow ;match titles and classes using regular expressions
CoordMode, Mouse, Screen
CoordMode, ToolTip, Screen

;detect cygwin
global CygPath
CygPath = C:\Program Files (x86)\cygwin
IfNotExist %CygPath%
    CygPath = C:\Cygwin
IfNotExist %CygPath%
    CygPath = C:\Cygwin64

;detect browser
global BrowserPath
BrowserPath = C:\Program Files (x86)\Mozilla Firefox\firefox.exe
IfNotExist %BrowserPath%
    BrowserPath = "C:\Users\rr-\AppData\Local\Google\Chrome\Application\chrome.exe"

;detect im
global IMClass
global IMPath
IMClass := "Buddy List"
IMPath := "C:\Program Files (x86)\pidgin\pidgin.exe"
If !FileExist(IMPath) {
    ;wtw
    IMClass := "ahk_class {B993D471-D465-43f2-BBA5-DEEA18A1789E}"
    IMPath := "C:\Program Files\WTW\WTW.exe"
    If !FileExist(IMPath) {
        IMPath := "C:\Program Files\K2T\WTW\WTW.exe"
    }
}

;disable windows+l
RegWrite, REG_DWORD, HKEY_CURRENT_USER, Software\Microsoft\Windows\CurrentVersion\Policies\System, DisableLockWorkstation, 1

;detect screen resolution
global MonitorWorkAreaLeft
global MonitorWorkAreaRight
global MonitorWorkAreaTop
global MonitorWorkAreaBottom
SysGet, MonitorPrimary, MonitorPrimary
SysGet, MonitorWorkArea, MonitorWorkArea, %MonitorPrimary%
;...and set terminal position basing on this
TermX := MonitorWorkAreaRight - 920
TermY := 45
RemoteTermX := 45
RemoteTermY := 45

;activate/run a program
FRun(window, path, folder)
{
    if (!window or !WinExist(window))
    {
        Run, %path%, %folder%
    }
    else
    {
        WinShow
        WinActivate
    }
    return
}

return

;replace stuff in clipboard when it changes
OnClipboardChange:
{
    if (A_EventInfo == 1) and (SubStr(clipboard, 1, 7) == "http://")
    {
        StringReplace, clipboard, clipboard, [, `%5b, All
        StringReplace, clipboard, clipboard, \, `%5c, All
        StringReplace, clipboard, clipboard, ], `%5d, All
    }
    return
}

#If FileExist(CygPath)
    ;cygwin - activate/run
    #Enter::
    {
        Run, %CygPath%\bin\mintty.exe --title "Terminal" --size "110`,35" --position "%TermX%`,%TermY%" --class "mintty_thetty" --exec /bin/zsh -l, %CygPath%
        return
    }
    +^![::
    {
        Run, %CygPath%\bin\mintty.exe --title "Terminal" --size "110`,35" --position "%TermX%`,%TermY%" --class "mintty_thetty" --exec /bin/zsh -l, %CygPath%
        return
    }
    ^![::
    {
        if (!WinExist("ahk_class mintty_thetty"))
        {
            Run, %CygPath%\bin\mintty.exe --title "Terminal" --size "110`,35" --position "%TermX%`,%TermY%" --class "mintty_thetty" --exec /bin/zsh -c 'while /bin/true`; do /bin/zsh -l`; cygstart "%A_AhkPath%" "%A_ScriptDir%\hide.ahk"`; clear; done', %CygPath%
            WinWait ahk_class mintty_thetty
        }
        else
        {
            WinShow
            WinActivate
        }
        return
    }

    ;ssh to burza - activate/run
    +^!]::
    {
        Run, %CygPath%\bin\mintty.exe --title "Remote terminal" --size "190`,65" --position "%RemoteTermX%`,%RemoteTermY%" --class mintty_ssh --exec /bin/ssh burza, %CygPath%/bin
        return
    }
    ^!]::
    {
        if (!WinExist("ahk_class mintty_ssh"))
        {
            Run, %CygPath%\bin\mintty.exe --title "Remote terminal" --size "190`,65" --position "%RemoteTermX%`,%RemoteTermY%" --class mintty_ssh --exec /bin/ssh burza, %CygPath%/bin
            WinWait ahk_class mintty_ssh
        }
        else
        {
            WinShow
            WinActivate
        }
        return
    }

    ;text editor - activate/run + fix screen position
    +^!N::
    {
        IfWinExist, ahk_class Vim
        {
            WinShow
            WinActivate
        }
        else
        {
            Run, %CygPath%\bin\mintty.exe --size "180`,60" --position "100`,100" --class "Vim" --exec /bin/zsh -l -i -c /bin/vim, %CygPath%/bin
            WinWait, ahk_class Vim
        }
        return
    }

    ;vifm - activate/run
    #E::
    {
        IfWinExist, ahk_class Vifm
        {
            WinShow
            WinActivate
        }
        else
        {
            Run, %CygPath%\bin\mintty.exe --window max --class "Vifm" --exec /bin/zsh -l -i -c vifm, %CygPath%/bin
            WinWait, ahk_class Vifm
        }
        return
    }

    ;vifm - just run
    +#E::
    {
        Run, %CygPath%\bin\mintty.exe --window max --class "Vifm" --exec /bin/zsh -l -i -c vifm, %CygPath%/bin
        return
    }

;im
#If FileExist(IMPath)
    ;im - activate/run
    +^!M::
        if (!WinExist(IMClass))
        {
            Run, %IMPath% -x
        }
        else
        {
            WinShow
            WinActivate
        }
        return

;browser
#If FileExist(BrowserPath)
    ;web browser - activate/run
    ^!;::
        if WinExist("Mozilla Firefox")
        {
            WinShow
            WinActivate
            Send, !d
        }
        else if WinExist("Vimperator")
        {
            WinShow
            WinActivate
            Send, !d
        }
        else if WinExist("ahk_class Chrome_WidgetWin_1")
        {
            WinActivate
            ControlFocus, Chrome_OmniboxView1
            SendInput, {Home}+{End}
        }
        else
        {
            IfExist %BrowserPath%
                Run, %BrowserPath%
        }
        return

;adobe products
#IfWinActive ahk_group adobe
    ~LAlt::return
    LAlt UP::Send {Escape}
#IfWinActive
#IfWinActive ahk_class illustrator
+wheelup::
    send {Ctrl Down}{=}{Ctrl Up}
    return
+wheeldown::
    send {Ctrl Down}-{Ctrl Up}
    return
#IfWinActive

;close active window
#Q::
    ActiveHwnd := WinExist("A")
    WinClose
    return

;fullscreen
#F::
    WinGet MX, MinMax, A
    if MX
        WinRestore A
    else
        WinMaximize A
    return

;restore active window
#S::
    ActiveHwnd := WinExist("A")
    PostMessage 0x112, 0xf120 ;WM_SYSCOMMAND, SC_RESTORE
    return

;minimize active window
#W::
    if WinActive("WTW$")
    {
        WinHide
    }
    else if WinActive("Buddy List")
    {
        WinHide
    }
    else
    {
        ActiveHwnd := WinExist("A")
        PostMessage 0x112, 0xF020 ;WM_SYSCOMMAND, SC_MINIMIZE
    }
    return

;set active window as always on top
#A::WinSet, AlwaysOnTop, Toggle, A

;transparency
AddTransparency(delta)
{
    WinGet, Transparency, Transparent, A
    if ! Transparency
        Transparency = 255
    Transparency := Transparency + delta
    if Transparency <= 10
    {
        WinSet, Transparent, 10, A
    }
    else if Transparency >= 255
    {
        WinSet, Transparent, 255, A
        WinSet, Transparent, OFF, A
    }
    else
    {
        WinSet, Transparent, %Transparency%, A
    }
    return
}
#WheelUp::AddTransparency(12)
#WheelDown::AddTransparency(-12)
#Z::AddTransparency(-12)
+#Z::AddTransparency(12)

;directional focus
DirectionalFocus(direction)
{
    DetectHiddenWindows, Off
    ActiveHwnd := WinExist("A")
    WinGetPos bx, by, _, _, ahk_id %ActiveHwnd%
    WinGet windows, List
    Loop %windows%
    {
        id := windows%A_Index%
        WinGetPos wx, wy, _, _, ahk_id %id%
        if (direction == "left")
            condition := wx < bx
        else if (direction == "right")
            condition := wx > bx
        else if (direction == "up")
            condition := wy < by
        else if (direction == "down")
            condition := wy > by
        else
        {
            MsgBox % "Bad direction"
            return
        }
        if (condition)
        {
            WinGetTitle, wtitle, ahk_id %id%
            if (wtitle != "")
            {
                WinActivate, ahk_id %id%
                break
            }
        }
    }
    DetectHiddenWindows, On
    return
}
#H::DirectionalFocus("left")
#J::DirectionalFocus("down")
#K::DirectionalFocus("up")
#L::DirectionalFocus("right")
+#H::SendInput, {LWin down}{LShift down}{Left}{LShift up}{LWin up}
+#L::SendInput, {LWin down}{LShift down}{Right}{LShift up}{LWin up}

;music
#X::Send {Volume_Up}
+#X::Volume_Down
#C::Send {Media_Next}
+#C::Media_Prev
#V::Send {Media_Play_Pause}

;screenshots
+#I::Run, shot -w -i --output=Z:/
#I::Run, shot -i --output=Z:/

;reload autohotkey
!F12::Reload

;toggle arrows
~ScrollLock::
    Sleep, 300
    if GetKeyState("ScrollLock", "T")
       Run, %CygPath%\bin\zsh.exe -i -l -c 'arrows 1', , Hide
    else
        Run, %CygPath%\bin\zsh.exe -i -l -c 'arrows 0', , Hide


Launch: remove PasteToNotepad

Launch: remove PasteToNotepad

; 该配置文件内容为 ahk 代码
; 增加标签后，在 UserCmd 和 return 之间添加
; @("标签名", "功能描述)

UserCmd:
    @("ClearClipboardFormat", "清除剪切板中文字的格式")
    @("SearchInWeb", "在浏览器（百度）搜索剪切板或输入内容")
    @("RunClipboard", "使用 ahk 的 Run 运行剪切板内容")
    @("T2S", "繁体转简体剪切板内容")
    @("ShowIp", "显示 IP")
    @("Calendar", "用浏览器打开万年历")
    @("Dictionary", "词典，依赖 bash 和 ydcv")
return

ShowIp:
    DisplayText(A_IPAddress1
            . "`r`n" . A_IPAddress2
            . "`r`n" . A_IPAddress3
            . "`r`n" . A_IPAddress4)
return

Dictionary:
    word := Args[2]
    if (word == "")
    {
        word := clipboard
    }

    DisplayText(RunAndGetOutput("echo " . word . " | ydcv"))
return

Calendar:
    Run % "http://www.baidu.com/baidu?wd=%CD%F2%C4%EA%C0%FA"
return

T2S:
    Run, notepad
    clipboard := Kanji_t2s(clipboard)
    Send, ^v
return

ClearClipboardFormat:
    clipboard := clipboard
return

SearchInWeb:
    word := clipboard
    if (Args.Length() > 1)
    {
        word := Args[2]
    }

    Run, https://www.baidu.com/s?wd=%word%
return

RunClipboard:
    Run, %clipboard%
return

; 该配置文件内容为 ahk 代码
; 增加标签后，在 UserCmd 和 return 之间添加
; @("标签名", "功能描述)

UserCmd:
    @("PasteToNotepad", "在记事本显示剪切板内容")
    @("ClearClipboardFormat", "清除剪切板中文字的格式")
    @("SearchInWeb", "在浏览器（百度）搜索剪切板或输入内容")
    @("RunClipboard", "使用 ahk 的 Run 运行剪切板内容")
    @("T2S", "繁体转简体剪切板内容")
    @("ShowIp", "显示 IP")
    @("Calendar", "用浏览器打开万年历")
    @("Dictionary", "词典，依赖 bash 和 ydcv")
return

PasteToNotepad:
    Run, notepad
    Send, ^v^{home}
return

ShowIp:
    DisplayText(A_IPAddress1
            . "`r`n" . A_IPAddress2
            . "`r`n" . A_IPAddress3
            . "`r`n" . A_IPAddress4)
return

Dictionary:
    word := Args[2]
    if (word == "")
    {
        word := clipboard
    }

    DisplayText(RunAndGetOutput("echo " . word . " | ydcv"))
return

Calendar:
    Run % "http://www.baidu.com/baidu?wd=%CD%F2%C4%EA%C0%FA"
return

T2S:
    Run, notepad
    clipboard := Kanji_t2s(clipboard)
    Send, ^v
return

ClearClipboardFormat:
    clipboard := clipboard
return

SearchInWeb:
    word := clipboard
    if (Args.Length() > 1)
    {
        word := Args[2]
    }

    Run, https://www.baidu.com/s?wd=%word%
return

RunClipboard:
    Run, %clipboard%
return


Code style

Code style

; ref http://git.oschina.net/linxinhong/VimDesktop/raw/master/Plugins/WizNote
;     /WizNote.ahk?dir=0&filepath=Plugins%2FWizNote%2FWizNote.ahk_class
;     &oid=60a9a6ecb68a3ae7ef6c5b5af540e84bc8139fc1
;     &sha=a78a6e18504e5bcecc5926e0176ed89bd661307a

WizNote:
    ; insert模式
    vim.mode("insert", "WizNoteMainFrame")

    vim.map("<esc>", "<Normal_Mode_WizNoteMainFrame>", "WizNoteMainFrame")

    ; normal模式
    vim.mode("normal", "WizNoteMainFrame")

    vim.map("i", "<Insert_Mode_WizNoteMainFrame>", "WizNoteMainFrame")

    vim.map("j", "<down>", "WizNoteMainFrame")
    vim.map("k", "<up>", "WizNoteMainFrame")
    vim.map("<shift>j", "<Wiz_NextNote>", "WizNoteMainFrame")
    vim.map("<shift>k", "<Wiz_PrevNote>", "WizNoteMainFrame")

    vim.map("h", "<left>", "WizNoteMainFrame")
    vim.map("l", "<right>", "WizNoteMainFrame")
    vim.map("gg", "<home>", "WizNoteMainFrame")
    vim.map("<shift>g", "<end>", "WizNoteMainFrame")

    vim.map("a", "<Wiz_NewNote>", "WizNoteMainFrame")
    vim.map("x", "<Wiz_Delete>", "WizNoteMainFrame")
    vim.map("e", "<Wiz_Edit>", "WizNoteMainFrame")
    vim.map("u", "<Wiz_CloseTab>", "WizNoteMainFrame")
    vim.map("t", "<Wiz_List>", "WizNoteMainFrame")

    ; 定义注释
    vim.comment("<Normal_Mode_WizNoteMainFrame>", "进入normal模式")
    vim.comment("<Insert_Mode_WizNoteMainFrame>", "进入insert模式")
    vim.comment("<Wiz_NewNote>", "新建笔记")
    vim.comment("<Wiz_NewDesktopNote>", "新建桌面便签")
    vim.comment("<Wiz_Delete>", "删除笔记")
    vim.comment("<Wiz_CopyOrMove>", "复制或移动笔记")
    vim.comment("<Wiz_Task>", "打开任务")
    vim.comment("<Wiz_FullScreen>", "全屏")
    vim.comment("<Wiz_Edit>", "编辑笔记")
    vim.comment("<Wiz_CloseTab>", "关闭当前标签")
    vim.comment("<Wiz_NextNote>", "下一个笔记")
    vim.comment("<Wiz_PrevNote>", "上一个笔记")
    vim.comment("<Wiz_List>", "定位到左侧目录")
    vim.comment("<Wiz_PrevNote>", "上一个笔记")
    vim.comment("<Wiz_PrevNote>", "上一个笔记")
return

; 检查模式, 用于检查焦点Edit1这个控件内激活insert模式, 而不用normal模式
; 模式检查，此段代码可以直接复制，但请修改ahk_class的值
WizNote_CheckMode()
{
    ControlGetFocus, ctrl, AHK_CLASS WizNoteMainFrame
    If RegExMatch(ctrl, "Edit1")
        return true
    return false
}

<Normal_Mode_WizNoteMainFrame>:
    vim.mode("normal", "WizNoteMainFrame")
return

<Insert_Mode_WizNoteMainFrame>:
    vim.mode("insert", "WizNoteMainFrame")
return

<Wiz_NewNote>:
    Send, ^n
return

<Wiz_NewDesktopNote>:
    Send, ^!d
return

<Wiz_Delete>:
    Send, {Del}
return

<Wiz_Task>:
    Send, ^!y
return

<Wiz_CopyOrMove>:
    Send, ^m
return

<Wiz_FullScreen>:
    Send, {F11}
return

<Wiz_Edit>:
    Send, ^e
return

<Wiz_CloseTab>:
    Send, ^w
return

<Wiz_List>:
    ControlFocus, WizListCtrl1
return

<Wiz_NextNote>:
    Gosub, <Wiz_List>
    Send, {down}
return

<Wiz_PrevNote>:
    Gosub, <Wiz_List>
    Send, {up}
return

/*
原始快捷键列表:

内容剪辑
Win+PrintScreen 捕捉屏幕到为知
Win+S 保存选中内容
Ctrl+Alt+V 保存剪贴板中内容

全局快捷键
Ctrl+Alt+N 快速新建笔记
Ctrl+Alt+M 打开为知笔记

笔记编辑
Ctrl+E 编辑/保存文档
Ctrl+W 关闭当前标签页
Ctrl+T 笔记选中部分作为标题
Ctrl+G 笔记选中部分作为标签
Alt+E 给当前笔记添加标签
Alt+F 给当前笔记添加附件
Alt+Q 显示当前笔记信息
Ctrl+O 插入清单
Ctrl+1/2... 将设为样式中的标题1/2...
Ctrl+Shift+O 插入或设为数字列表
Ctrl+Shift+U 插入或设为无序列表
ctrl + alt + ] 增大选中文字字体
ctrl + alt + [ 减小选中文字字体
Ctrl+B 笔记选中部分加粗
Ctrl+I 笔记选中部分变斜体
Ctrl+U 笔记选中部分加下划线
Ctrl+L 插入
Ctrl+; 插入当前系统日期或时间
Ctrl+Shift+; 设置插入的日期时间格式

为知界面
Ctrl+N 新建笔记
Ctrl+F 新建子文件夹
Esc 显示/隐藏左侧目录栏
F11 进入或退出全屏模式
Ctrl+Shift+TAB 切换标签页
F7 打开上一条笔记
F8 打开下一条笔记
F9 同步笔记
Ctrl+鼠标滚轮 放大或缩小笔记视图
Alt+D 设置键盘焦点到搜索框

桌面便笺和任务列表
Ctrl+Alt+D 新建桌面便笺
Ctrl+Alt+Y 打开默认任务列表
Ctrl+F3 打开所有任务列表

更多设置
若想更改部分快捷键，可在菜单——选项——热键、网页剪辑器选项卡中进行更多设置
*/

; ref http://git.oschina.net/linxinhong/VimDesktop/raw/master/Plugins/WizNote
;     /WizNote.ahk?dir=0&filepath=Plugins%2FWizNote%2FWizNote.ahk_class
;     &oid=60a9a6ecb68a3ae7ef6c5b5af540e84bc8139fc1
;     &sha=a78a6e18504e5bcecc5926e0176ed89bd661307a

WizNote:
    ; insert模式
    vim.mode("insert", "WizNoteMainFrame")

    vim.map("<esc>", "<Normal_Mode_WizNoteMainFrame>", "WizNoteMainFrame")

    ; normal模式
    vim.mode("normal", "WizNoteMainFrame")

    vim.map("i", "<Insert_Mode_WizNoteMainFrame>", "WizNoteMainFrame")

    vim.map("j", "<down>", "WizNoteMainFrame")
    vim.map("k", "<up>", "WizNoteMainFrame")
    vim.map("<shift>j", "<Wiz_NextNote>", "WizNoteMainFrame")
    vim.map("<shift>k", "<Wiz_PrevNote>", "WizNoteMainFrame")

    vim.map("h", "<left>", "WizNoteMainFrame")
    vim.map("l", "<right>", "WizNoteMainFrame")
    vim.map("gg", "<home>", "WizNoteMainFrame")
    vim.map("<shift>g", "<end>", "WizNoteMainFrame")

    vim.map("a", "<Wiz_NewNote>", "WizNoteMainFrame")
    vim.map("x", "<Wiz_Delete>", "WizNoteMainFrame")
    vim.map("e", "<Wiz_Edit>", "WizNoteMainFrame")
    vim.map("u", "<Wiz_CloseTab>", "WizNoteMainFrame")
    vim.map("t", "<Wiz_List>", "WizNoteMainFrame")

    ; 定义注释
    vim.comment("<Normal_Mode_WizNoteMainFrame>", "进入normal模式")
    vim.comment("<Insert_Mode_WizNoteMainFrame>", "进入insert模式")
    vim.comment("<Wiz_NewNote>", "新建笔记")
    vim.comment("<Wiz_NewDesktopNote>", "新建桌面便签")
    vim.comment("<Wiz_Cal>", "打开日历")
    vim.comment("<Wiz_Delete>", "删除笔记")
    vim.comment("<Wiz_CopyOrMove>", "复制或移动笔记")
    vim.comment("<Wiz_Task>", "打开任务")
    vim.comment("<Wiz_FullScreen>", "全屏")
    vim.comment("<Wiz_Edit>", "编辑笔记")
    vim.comment("<Wiz_CloseTab>", "关闭当前标签")
    vim.comment("<Wiz_NextNote>", "下一个笔记")
    vim.comment("<Wiz_PrevNote>", "上一个笔记")
    vim.comment("<Wiz_List>", "定位到左侧目录")
    vim.comment("<Wiz_PrevNote>", "上一个笔记")
    vim.comment("<Wiz_PrevNote>", "上一个笔记")
return

; 检查模式, 用于检查焦点Edit1这个控件内激活insert模式, 而不用normal模式
; 模式检查，此段代码可以直接复制，但请修改ahk_class的值
WizNote_CheckMode()
{
    ControlGetFocus, ctrl, AHK_CLASS WizNoteMainFrame
    If RegExMatch(ctrl, "Edit1")
        return True
    return False
}

<Normal_Mode_WizNoteMainFrame>:
    vim.mode("normal", "WizNoteMainFrame")
return

<Insert_Mode_WizNoteMainFrame>:
    vim.mode("insert", "WizNoteMainFrame")
return

<Wiz_NewNote>:
{
    send, ^n
    return
}

<Wiz_NewDesktopNote>:
{
    send, ^!d
    return
}

<Wiz_Cal>:
{
    send, {F10}
    send, t
    send, c
    return
}

<Wiz_Delete>:
{
    send, {Del}
    return
}

<Wiz_Task>:
{
    send, ^!y
    return
}

<Wiz_CopyOrMove>:
{
    send, ^m
    return
}

<Wiz_FullScreen>:
{
    send, {F11}
    return
}

<Wiz_Edit>:
{
    send, ^e
    return
}

<Wiz_CloseTab>:
{
    send, ^w
    return
}

<Wiz_List>:
{
    ControlFocus, WizListCtrl1
    return
}

<Wiz_NextNote>:
{
    Gosub, <Wiz_List>
    send {down}
    return
}

<Wiz_PrevNote>:
{
    Gosub, <Wiz_List>
    send {up}
    return
}

/*
原始快捷键列表:

内容剪辑
Win+PrintScreen 捕捉屏幕到为知
Win+S 保存选中内容
Ctrl+Alt+V 保存剪贴板中内容

全局快捷键
Ctrl+Alt+N 快速新建笔记
Ctrl+Alt+M 打开为知笔记

笔记编辑
Ctrl+E 编辑/保存文档
Ctrl+W 关闭当前标签页
Ctrl+T 笔记选中部分作为标题
Ctrl+G 笔记选中部分作为标签
Alt+E 给当前笔记添加标签
Alt+F 给当前笔记添加附件
Alt+Q 显示当前笔记信息
Ctrl+O 插入清单
Ctrl+1/2... 将设为样式中的标题1/2...
Ctrl+Shift+O 插入或设为数字列表
Ctrl+Shift+U 插入或设为无序列表
ctrl + alt + ] 增大选中文字字体
ctrl + alt + [ 减小选中文字字体
Ctrl+B 笔记选中部分加粗
Ctrl+I 笔记选中部分变斜体
Ctrl+U 笔记选中部分加下划线
Ctrl+L 插入
Ctrl+; 插入当前系统日期或时间
Ctrl+Shift+; 设置插入的日期时间格式

为知界面
Ctrl+N 新建笔记
Ctrl+F 新建子文件夹
Esc 显示/隐藏左侧目录栏
F11 进入或退出全屏模式
Ctrl+Shift+TAB 切换标签页
F7 打开上一条笔记
F8 打开下一条笔记
F9 同步笔记
Ctrl+鼠标滚轮 放大或缩小笔记视图
Alt+D 设置键盘焦点到搜索框

桌面便笺和任务列表
Ctrl+Alt+D 新建桌面便笺
Ctrl+Alt+Y 打开默认任务列表
Ctrl+F3 打开所有任务列表

更多设置
若想更改部分快捷键，可在菜单——选项——热键、网页剪辑器选项卡中进行更多设置
*/


Update GKey.ahk

Update GKey.ahk

#SingleInstance, Force
#NoTrayIcon

global msg

@("a", "RunNotepad", "用记事本显示剪切板内容")
@("b", "ShowClipboard", "显示剪切板内容")
@("c", "ShowIp", "显示IP")
@("d", "RunClipboardWithMintty", "运行剪切板的命令")
@("z", "Test", "测试")

MsgBox, %msg%

ExitApp

; AddAction(key, label, info)
@(key, label, info)
{
    global msg
    msg .= key "  =>  " info "`n"
    HotKey, %key%, %label%
}

RunWithBash(command)
{
    shell := ComObjCreate("WScript.Shell")
    exec := shell.Exec("bash -c '" command " | iconv -f utf-8 -t gbk -c")
    return exec.StdOut.ReadAll()
}

RunWithCmd(command)
{
    shell := ComObjCreate("WScript.Shell")
    exec := shell.Exec(ComSpec " /C " command)
    return exec.StdOut.ReadAll()
}

; ===========

RunNotepad:
    Run, notepad
    Send, ^v
    ExitApp
return

ShowClipboard:
    MsgBox, %clipboard%
    ExitApp
return

ShowIp:
    MsgBox, %A_IPAddress1%`n%A_IPAddress2%`n%A_IPAddress3%`n%A_IPAddress4%
    ExitApp
return

RunClipboardWithMintty:
    MsgBox % clipboard
    ;MsgBox % RunWithBash(clipboard)
    ;Run, mintty -e sh -c '%clipboard%; read'
    Run % "mintty -e sh -c '" clipboard "; read'"
    ExitApp
return

Test:
    MsgBox, test
    ExitApp
return

#SingleInstance, Force
#NoTrayIcon

global msg

AddAction("a", "RunNotepad", "用记事本显示剪切板内容")
AddAction("b", "ShowClipboard", "显示剪切板内容")
AddAction("c", "ShowIp", "显示IP")
AddAction("d", "RunClipboardWithMintty", "运行剪切板的命令")
AddAction("z", "test", "测试")

MsgBox, %msg%

ExitApp

AddAction(key, label, info)
{
    msg .= key "  =>  " info "`n"
    HotKey, %key%, %label%
}

RunWithBash(command)
{
    shell := ComObjCreate("WScript.Shell")
    exec := shell.Exec("bash -c '" command " | iconv -f utf-8 -t gbk -c")
    return exec.StdOut.ReadAll()
}

RunWithCmd(command)
{
    shell := ComObjCreate("WScript.Shell")
    exec := shell.Exec(ComSpec " /C " command)
    return exec.StdOut.ReadAll()
}

; ===========

RunNotepad:
    Run, notepad
    Send, ^v
    ExitApp
return

ShowClipboard:
    MsgBox, %clipboard%
    ExitApp
return

ShowIp:
    MsgBox, %A_IPAddress1%`n%A_IPAddress2%`n%A_IPAddress3%`n%A_IPAddress4%
    ExitApp
return

RunClipboardWithMintty:
	MsgBox % clipboard
    ;MsgBox % RunWithBash(clipboard)
    ;Run, mintty -e sh -c '%clipboard%; read'
    Run % "mintty -e sh -c '" clipboard "; read'"
	ExitApp
return

test:
    MsgBox, test
    ExitApp
return


totalcommander: improve uninstallation reliability (part 2)

Improve detection reliability of the last uninstaller window ("Program
removed successfully!") by looking at the window text, in addition to
window title and class.

; default environment
DetectHiddenWindows, off
SetControlDelay, 20

; modified environment
#NoEnv
#NoTrayIcon
DetectHiddenText, off
SetTitleMatchMode, 2  ;contains

; variables
winTitle1 = Uninstall /Repair Total Commander ahk_class #32770
winTitle2 = Uninstall Total Commander ahk_class #32770
winTitle3 = Uninstall ahk_class #32770

WinWait, %winTitle1%, Removes the program, 20
ControlClick, Button1 ; &Uninstall

WinWait, %winTitle2%, Uninstall Program, 20
ControlClick, Button4 ; Remove configuration files
ControlClick, Button5 ; Uninstall

WinWait, %winTitle3%, Warning: This will, 20
ControlClick, Button1 ; Yes

WinWait, %winTitle3%, The following, 20
ControlClick, Button1 ; OK

WinWait, %winTitle3%, Program removed, 20
ControlClick, Button1 ; OK

ExitApp

; default environment
DetectHiddenWindows, off
SetControlDelay, 20

; modified environment
#NoEnv
#NoTrayIcon
DetectHiddenText, off
SetTitleMatchMode, 2  ;contains

; variables
winTitle1 = Uninstall /Repair Total Commander ahk_class #32770
winTitle2 = Uninstall Total Commander ahk_class #32770
winTitle3 = Uninstall ahk_class #32770

WinWait, %winTitle1%, Removes the program, 20
ControlClick, Button1 ; &Uninstall

WinWait, %winTitle2%, Uninstall Program, 20
ControlClick, Button4 ; Remove configuration files
ControlClick, Button5 ; Uninstall

WinWait, %winTitle3%, Warning: This will, 20
ControlClick, Button1 ; Yes

WinWait, %winTitle3%, The following, 20
ControlClick, Button1 ; OK

WinWait, %winTitle3%, , 20
ControlClick, Button1 ; OK

ExitApp