Last active
September 16, 2022 17:34
-
-
Save kawashirov/440937360bfa0020805cd40323deb6ca to your computer and use it in GitHub Desktop.
Kawa's routines macro for Tower of Fantasy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TOF_MACRO_VERSION := "2022.09.17.1" | |
/* | |
Это сложный макрос для автоматизации различных рутин в Tower of Fantasy. | |
Он использует популярную и универсальную программу Auto Hot Key | |
для того, что бы скриншотить окно игры, смотреть что там происходит, | |
исходя из этого нажимать кнопки за игрока. | |
Работает аналогично программам Genshin Overlay и Genshin Auto Fish, | |
ими и вдохновлена вся эта автоматизация. | |
Дом макроса: https://gist.github.com/kawashirov/440937360bfa0020805cd40323deb6ca | |
Там можно найти актуальную версию. | |
Текущую версию этого фала смотри выше в TOF_MACRO_VERSION | |
Написан by kawashirov | |
Discord: kawashirov#8363 | |
!!! ВНИМАНИЕ !!! | |
ИСПОЛЬЗУЙТЕ МАКРОС НА СВОЙ СТРАХ И РИСК! | |
НЕ ДОКОНЦА ПОНЯТНО, СООТВЕТСТВУЕТ ЛИ МАКРОС ПРАВИЛАМ ИГРЫ И МОЖНО ЛИ ЗА НЕГО | |
ПОЛУЧИТЬ Б А Н. | |
На данный момент адекватно автоматизировано: | |
- Assist-походы во Frontier Clash за экспой, деталями и саппорт поинтами. | |
Описание работы макроса внизу по тексту, ищи Macro_AutoFarmFrontierClashCycle | |
- Агресивный бегающий автофарм за бегающими мобами | |
с помошью Венуса, Косы, Абсолют Зеро, Магнетик бомбы и Холограм прожектора. | |
Описание работы макроса внизу по тексту, ищи Macro_AutoFarmOpenWorldActive | |
- Точечный стационарный автофарм мобов | |
с помошью Молотка, Абсолют Зеро, Магнетик бомбы и Холограм прожектора. | |
Описание работы макроса внизу по тексту, ищи Macro_AutoFarmOpenWorldPassive | |
Немного скриншотов с демонстрацией: https://imgur.com/a/36KMt1k | |
ОСТАНОВКА ЛЮБЫХ ЗАПУЩЕНЫХ МАКРОСОВ: Ctrl + Numpad Del | |
ВЫХОД ИЗ ПРОГРАММЫ В (почти) ЛЮБОЙ МОМЕНТ ВРЕМЕНИ: Ctrl + Alt + Numpad 0 | |
Может не работать в игре, в таком случае сделай Alt-Tab в другое окно и прожми там. | |
Для работы макроса НЕОБХОДИМО соблюдение следующих условий: | |
- Auto Hot Key (или просто AHK) версии 2.0+ (beta). В версииях 1.х НЕ РАБОТАЕТ. | |
Собственно этот макрос написан на языке этой программы и исполнеяется ею же. | |
Скачать можно тут: https://www.autohotkey.com/download/ahk-v2.zip | |
Документация на англ: https://lexikos.github.io/v2/docs/AutoHotkey.htm | |
Установка: https://lexikos.github.io/v2/docs/Program.htm#install | |
Туториала по установке и пользованию AHK не будет, но самый простой варик это: | |
- Скачать portable версию (ссылка на архив выше) и распаковать куда-нибудь | |
- Сделать ярлык на AutoHotkey64.exe (имеет плоско-зелёную иконку "H") | |
- Изменить ярлык (ПКМ -> Свойства) и поменять его так, что бы было что-то типа | |
"C:\путь\к\программе\AutoHotkey64.exe" "D:\путь\к\макросу\ToF.ahk" | |
Должно получиться так: https://i.imgur.com/sAceTDo.png | |
- Запускать ярлык нажимая по нему ПКМ -> Запустить от имени администратора. | |
- Макрос никак не взаимодействует с самой игрой на прямую. Он опирается исключительно на возможности системы Windows, | |
что бы забирать информацию через API GDI/WDM и отправлять события клавиатуры и мыши в окно игры. | |
По этому встроеный в игру античит не должен никак реагировать на макрос. | |
- Макрос следует запускать ПОСЛЕ игры. | |
Макрос смотрит на окно игры и в момент запуска макроса окно игры уже должно быть "готово" к взаимодействиям. | |
Если игра закрылась, то макрос сломается и его следует перезапустить. | |
- Макрос ДОЛЖЕН быть запущен от имени АДМИНИСТРАТОРА. | |
Tower of Fantasy тоже работает под админом и система Windows защищает и запрещает | |
взаимодействие с админскими окнами из под обычных програм, требуя повышения привелегий. | |
Никаких иных взаимодействий с системой, кроме клавы, мыши, окна ToF, статус-окна | |
и системного таймера макрос не делает. | |
- Макрос полагается на нестандартные бинды (Settings -> Controller). | |
Я делаю макрос в первую очередь под себя и считаю стандартные бинды в ToF уебанскими. | |
Смотри описания конкретных макросов, что бы понять, что с биндами. | |
- После запуска макроса должно появиться окошко "Kawa's ToF AHK status". | |
Если окно не появилось, проверьте системный трей (иконки возле часов) на наличие зеленой "H". | |
Если зеленая "H" есть, а окошка нету, значит что-то забаговалось - попробуйте перезапустить: ПКМ -> Reload Script. | |
Если зеленой "H" нету, значит оно не запустилось - возможно, что-то наебнулось, | |
внимательно проверь, что бы не было никаких ошибок. | |
- Окошко "Kawa's ToF AHK status" носит информационный характер. | |
В нем отображается отладочная информация, что бы понимать, что сейчас происходит. | |
Его можно закрыть, если оно мешает, но макрос НЕ закроется, а будет сидеть в трее. | |
Что бы закрыть макрос на совсем, нужно либо прожать Ctrl + Alt + Numpad 0, либо ПКМ по иконке в трее -> Exit. | |
- Tower of Fantasy ДОЛЖЕН иметь соотношение 16:9, а разрешение не должно изменяться. | |
Тестировано 1920х1080 и 1280х720, рекомендуется запускать в окне. | |
Если разрешение изменится во вемя работы макроса, то он может наебнуться. | |
Лучше не менять разрешение во время работы макроса. | |
- Макрос сделан из расчета на стабильные 25 fps (Зафиксировать можно в настройках графики). | |
При большом fps макрос тоже будет работать, но в этом нет необходимости - зачем лишний раз нагружать комп? | |
При низком fps (специально заниженом драйверами или прочими прогами) могут быть существенные проблемы: | |
игра может пропускать нажатия кнопок и мыши, анимации UI замедляться, макрос не обнаружит события на экране, | |
по этому тебе следует обеспечить стабильные 25+ fps. | |
- Графика в игре ДОЛЖЕНА быть на минималках, а "стиль" стоять Default. | |
Антиалиасинги и пост-процесинги размывают контуры элементов и их сложнее обнаруживать. | |
- Необходимо отключить все "улучшения рендера" на уровне системы и драйверов. | |
Все "оптимизации" от NVIDIA и RADEON идут нахуй, т.к. они меняют цветность ключевых пикселей в игре. | |
Макрос очень сильно опирается на обнаружение UI на экране ToF, | |
если макрос не увидит какую-нибудь кнопочку - зависнет. | |
- Игра должна быть полностью в пространстве экрана, т.е. окно игры: | |
- не должно быть задвинуто за пределы экрана (даже чуть-чуть) | |
- не должно быть частично на нескольких мониторах сразу | |
- не должно перекрываться другими окнами или оверлеями | |
Если будут какие-то помехи окну игры, то макрос жёстко обдрищится и может даже накликать лишнего за пределами игры. | |
Это необходимо, что бы макрос мог нормально скриншотить, сканировать и нажимать в окно игры. | |
- Окно игры должно быть активно во время работы макроса. | |
Любое переключение на другие окна (Alt-Tab) или оверлеи (скриншотик сделать) приостановит (поставит на паузу) макрос. | |
Макрос попытается продолжить работу при возвращении фокуса в ToF, | |
но может сломаться, если в игре что-то происходило, пока макрос спал. Будь внимателен. | |
- Не следует никак взаимодействовать с компом (клавиатурой и мышью), если макрос тоже что-то нажимает или двигает мышь. | |
Осообенно в такие моменты не стоит двигать окно игры. | |
Макрос расчитан на полное АФК - ушел спать, а игра играет сама в себя. По этому лучше лишний раз не мешать макросу. | |
Макрос распространяется под лицензией MIT: | |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated | |
documentation files (the "Software"), to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and | |
to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO | |
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* * * * * | |
Первичная инициализация Auto Hot Key | |
*/ | |
if SubStr(A_AhkVersion, 1, 2) != "2." { | |
; #Requires AutoHotkey v2.0 обсирается | |
Err1 := Format("Wrong AHK version: required 2.x+, got {}. Please use correct one.", A_AhkVersion) | |
Err2 := Format("Неверная версия AHK: нужна 2.x+, а сейчас {}. Пожалуйста, используй указаную версию.", A_AhkVersion) | |
MsgBox( Err1 . "`n`n" . Err2 ) | |
ExitApp 1 | |
} | |
if A_IsAdmin != 1 { | |
MsgBox "Admin privileges required! Please, run macro as Administrator.`n`nМакросу нужны права админа, запусти от имени админа." | |
ExitApp 1 | |
} | |
#WinActivateForce | |
ProcessSetPriority "High" | |
InstallMouseHook true, true | |
InstallKeybdHook true, true | |
A_AllowMainWindow := 0 | |
/* * * * * | |
Утилитарные функции общего назначения (не имеют отношения к ToF) | |
*/ | |
; Функции системного таймера | |
; потому что я рот ебал тайм-менеджмент в AHK | |
; https://docs.microsoft.com/en-us/windows/win32/api/profileapi/ | |
Sys_TimerFreq := 0 | |
DllCall("QueryPerformanceFrequency", "Int64*", &Sys_TimerFreq) | |
Sys_TimerStampGet() { | |
Sys_Counter := 0 | |
DllCall("QueryPerformanceCounter", "Int64*", &Sys_Counter) | |
return Sys_Counter | |
} | |
Sys_TimerStampDiffMs(StampA, StampB) { | |
; Возращает разницу A - B в мс. | |
global Sys_TimerFreq | |
return (StampA - StampB) / Sys_TimerFreq * 1000 | |
} | |
Sys_TimerStampDiffSec(StampA, StampB) { | |
; Возращает разницу A - B в сек. | |
global Sys_TimerFreq | |
return (StampA - StampB) / Sys_TimerFreq | |
} | |
Sys_TimerStampAddMs(StampA, TimeMs) { | |
; Возращает разницу A - B в мс. | |
global Sys_TimerFreq | |
return StampA + Round(TimeMs / 1000 * Sys_TimerFreq) | |
} | |
Sys_TimerStampAddSec(StampA, TimeSec) { | |
; Возращает разницу A - B в сек. | |
global Sys_TimerFreq | |
return StampA + Round(TimeSec * Sys_TimerFreq) | |
} | |
Util_SecondsFormat(S) { | |
S := Round(S) | |
M := S // 60 | |
S := Mod(S, 60) | |
H := M // 60 | |
M := Mod(M, 60) | |
return Format("{:02d}:{:02d}:{:02d}", H, M, S) | |
} | |
Util_BoxToPoint(Box) { | |
X := Box[1] * 0.5 + Box[3] * 0.5 | |
Y := Box[2] * 0.5 + Box[4] * 0.5 | |
return [X, Y] | |
} | |
/* * * * * | |
Инициализация GUI и связывание с ToF | |
*/ | |
ToF_Hwnd := "" | |
ToF_Window := "" | |
ToF_Gui := {} | |
ToF_ScreenWidth := -1 | |
ToF_ScreenHeight := -1 | |
ToF_ShouldStop := false | |
__Setup() { | |
global ToF_Hwnd | |
global ToF_Window | |
global ToF_Gui | |
global ToF_ScreenWidth | |
global ToF_ScreenHeight | |
Title := Format("Kawa's ToF AHK Macro ver. {}", TOF_MACRO_VERSION) | |
ToF_Gui.Window := Gui("+Resize +MinSize480x", Title) | |
ToF_Gui.Window.MarginY := ToF_Gui.Window.MarginY * 0.5 | |
ToF_Gui.Hwnd := ToF_Gui.Window.AddText("w420 xp", "Window Info") | |
ToF_Gui.Hwnd.SetFont("cGreen bold") | |
ToF_Gui.StopInfo := ToF_Gui.Window.AddText("w420 xp", "Press Ctrl+NumDel to stop any macro.") | |
ToF_Gui.StopInfo.SetFont("cMaroon bold") | |
ToF_Gui.Loop := ToF_Gui.Window.AddText("w420 xp", "Loop Progress Line") | |
ToF_Gui.Loop.SetFont("bold") | |
ToF_Gui.Flow := ToF_Gui.Window.AddText("w420 xp", "Flow Line") | |
ToF_Gui.Flow.SetFont("bold") | |
ToF_Gui.Debug := ToF_Gui.Window.AddText("w420 xp", "Debug Line") | |
ToF_Gui.Error := ToF_Gui.Window.AddText("w420 xp", "LastError Line") | |
ToF_Gui.Error.SetFont("cMaroon") | |
ToF_Gui.Spacing1 := ToF_Gui.Window.AddText("w420 xp", " ") | |
ToF_Gui.Status1 := ToF_Gui.Window.AddText("w420 xp", "Status1 Line") | |
ToF_Gui.Status2 := ToF_Gui.Window.AddText("w420 xp", "Status2 Line") | |
ToF_Gui.Status3 := ToF_Gui.Window.AddText("w420 xp", "Status3 Line") | |
ToF_Gui.Status4 := ToF_Gui.Window.AddText("w420 xp", "Status4 Line") | |
ToF_Gui.Status5 := ToF_Gui.Window.AddText("w420 xp", "Status5 Line") | |
ToF_Gui.Window.Show() | |
A_TrayMenu.Insert("1&", "Show Status Window", (ItemName, ItemPos, MyMenu) => ToF_Gui.Window.Show()) | |
ToF_Gui.Hwnd.Value := "Searching ToF window..." | |
ToF_Gui.Flow.Value := ToF_Gui.Hwnd.Value | |
Sleep 50 | |
ToF_Hwnd := WinExist("ahk_exe QRSL.exe") | |
if !ToF_Hwnd { | |
MsgA := "Tower of Fantasy (QRSL.exe) not found!" | |
ToF_Gui.Hwnd.Value := MsgA | |
MsgBox MsgA . "`n`nСначала запусти игру!" | |
ExitApp 1 | |
} | |
ToF_Window := {Hwnd: ToF_Hwnd} | |
WinMoveTop ToF_Window | |
WinActivate ToF_Window | |
WinWaitActive ToF_Window | |
WinGetClientPos &WinX, &WinY, &ToF_ScreenWidth, &ToF_ScreenHeight, ToF_Window | |
ToF_Gui.Hwnd.Value := Format("Detected ToF window №{} @ X:{} Y:{} {}x{}", ToF_Hwnd, WinX, WinY, ToF_ScreenWidth, ToF_ScreenHeight) | |
if WinX > -8000 and WinY > -8000 | |
ToF_Gui.Window.Show(Format("X{} Y{}", WinX - 384, WinY + 16)) | |
Sleep 50 | |
WinMoveTop ToF_Window | |
WinActivate ToF_Window | |
WinWaitActive ToF_Window | |
Sleep 50 | |
; Тесты таймера | |
ToF_Gui.Flow.Value := "Testing system timer..." | |
ToF_Gui.Status1.Value := Format("System timer precision: {}t/ms", Sys_TimerFreq // 1000) | |
CurrentStamp := Sys_TimerStampGet() ; Первый холостой вызов, что бы расперделось | |
ToF_Gui.Status2.Value := Format("Current system counter: {} ~ {}", CurrentStamp, Util_SecondsFormat(CurrentStamp / Sys_TimerFreq)) | |
TestSleepA := Sys_TimerStampGet() | |
Loop 50 { | |
Sleep 1 | |
} | |
TestSleepB := Sys_TimerStampGet() | |
TestSleepMs := Sys_TimerStampDiffMs(TestSleepB, TestSleepA) / 50 | |
TestSleepFps := 1000 / TestSleepMs | |
ToF_Gui.Status3.Value := Format("AHK approx min sleep: {:0.3f}ms = {:0.3f}fps.", TestSleepMs, TestSleepFps) | |
ToF_Gui.Flow.Value := "Ready to launch macro!" | |
} | |
__Setup() | |
SilentError(Exc, Mode) { | |
ToF_Gui.Error.Value := Format("{} Error: {} @ {}", Mode, Exc.Message, Exc.Line) | |
return 1 | |
} | |
OnError SilentError | |
/* * * * * | |
Базовые функции для проверки корректности состояния окна ToF | |
*/ | |
ToF_RequestUserStop() { | |
Critical "On" | |
global ToF_ShouldStop := true | |
Msg := "Stop by user requested... (Ctrl+NumDel)" | |
ToF_Gui.Flow.Value := Msg | |
ToF_Gui.Debug.Value := Msg | |
} | |
ToF_CheckStopByUser() { | |
; Кршит текущий поток (выбрасывает Error), если стоит флаг ToF_ShouldStop. | |
global ToF_ShouldStop | |
if !ToF_ShouldStop | |
return | |
Critical "Off" | |
ToF_ShouldStop := false | |
Msg := "Stopped by user! (Ctrl+NumDel)" | |
ToF_Gui.Flow.Value := Msg | |
ToF_Gui.Debug.Value := Msg | |
Sleep 1000 | |
throw Error(Msg) | |
} | |
ToF_CheckExist() { | |
; Проверяет существование окна ToF. Если окна нет, выключает макрос. | |
if !WinExist(ToF_Window) { | |
MsgBox "Tower of Fantasy (QRSL.exe) not found! `n`nИгра пропала! Она закрылась?`nЯ умираю тоже :(" | |
ExitApp 1 | |
} | |
return true | |
} | |
ToF_CheckActive() { | |
; Проверяет активно ли окно ToF. | |
IsActive := ToF_CheckExist() and WinActive(ToF_Window) | |
if IsActive { | |
ToF_Gui.Hwnd.Value := Format("ToF window №{} is active, macro available.", ToF_Hwnd) | |
ToF_Gui.Hwnd.SetFont("cGreen bold") | |
} else { | |
ToF_Gui.Hwnd.Value := Format("ToF window №{} is inactive, macro paused.", ToF_Hwnd) | |
ToF_Gui.Hwnd.SetFont("cMaroon bold") | |
} | |
return IsActive | |
} | |
ToF_WinWaitActive(Timeout := 5) { | |
; Зависает макрос, пока окно ToF не станет активным. | |
Loop { | |
if ToF_CheckActive() | |
break | |
ToF_Gui.Debug.Value := Format("Awaiting for ToF window... {}s", (A_Index - 1) * Timeout) | |
WinWaitActive(ToF_Window,,Timeout) | |
} | |
return true | |
} | |
ToF_CheckResolution() { | |
; Проверяет, что бы у ToF было корректное разрешение. | |
global ToF_ScreenWidth | |
global ToF_ScreenHeight | |
ToF_CheckExist() | |
WinGetClientPos &WinX, &WinY, &Width, &Height, ToF_Window | |
/* | |
if ToF_ScreenWidth != Width or ToF_ScreenHeight != Height { | |
ErrRes := Format("{}x{} -> {}x{}", ToF_ScreenWidth, ToF_ScreenHeight, Width, Height) | |
Err1 := Format("ToF resolution mismatch ({}), please do not change resolution while running. You must restart macro.", ErrRes) | |
Err2 := Format("Разрешение ToF сбилось ({}), пожалуйста не меняйте разрешение пока макрос запущен. Перезапустите.", ErrRes) | |
MsgBox( Err1 . "`n`n" . Err2 ) | |
ExitApp 1 | |
} | |
*/ | |
if Width/16 != Height/9 { | |
Err1 := Format("ToF resolution is not 16:9 ({}x{}), please switch the game to 16:9 format", Width, Height) | |
Err2 := Format("Разрешение ToF не 16:9 ({}x{}), пожалуйста смените разрешение на формат 16:9", Width, Height) | |
MsgBox( Err1 . "`n`n" . Err2 ) | |
throw TargetError(Err1 . "`n`n" . Err2) | |
} | |
} | |
ToF_CheckUserIdle() { | |
; Возвращает true, когда с последней активности пользователя прошло больше 1 секунды | |
LoopBegin := Sys_TimerStampGet() | |
Loop { | |
AwaitTime := Sys_TimerStampDiffSec(Sys_TimerStampGet(), LoopBegin) | |
if A_TimeIdlePhysical > 1000 | |
return true | |
ToF_Gui.Debug.Value := Format("Awaiting user idle... {}, {}ms, {:0.1f}sec", A_Index, A_TimeIdlePhysical, AwaitTime) | |
Sleep 200 | |
} | |
} | |
ToF_Check() { | |
; Все проверки сразу. | |
ToF_CheckStopByUser() | |
ToF_WinWaitActive() | |
ToF_CheckResolution() | |
; ToF_CheckUserIdle() | |
ToF_CheckStopByUser() | |
} | |
/* * * * * | |
Базовые функции для работы с экраном ToF | |
*/ | |
ToF_PixelSearch(&FoundX, &FoundY, Box, ColorID, Variation) { | |
; Box = [X1, Y1, X2, Y2] | |
; Ищет пиксель указаного цвета и вариации на экране ToF в относительных координатах от 0 до 1 | |
; ToF_Gui.Debug.Value := Format("Looking for pixel {} {} at [{:0.3f}, {:0.3f}, {:0.3f}, {:0.3f}]", ColorID, Variation, Box[1], Box[2], Box[3], Box[4]) | |
FoundX := "" | |
FoundY := "" | |
ToF_Check() | |
WinGetClientPos &WinX, &WinY, &Width, &Height, ToF_Window | |
if WinX == "" or WinY == "" or Width < 10 or Height < 10 | |
return | |
X1 := Floor(Box[1] * Width) | |
Y1 := Floor(Box[2] * Height) | |
X2 := Ceil(Box[3] * Width) | |
Y2 := Ceil(Box[4] * Height) | |
; ToF_Gui.Debug.Value := Format("Looking for pixel {} {} at {}, {}, {}, {}", ColorID, Variation, X1, Y1, X2, Y2) | |
CoordMode "Pixel", "Client" | |
PixelSearch &PixelX, &PixelY, X1, Y1, X2, Y2, ColorID, Variation | |
if PixelX == "" or PixelY == "" | |
return | |
FoundX := PixelX / Width | |
FoundY := PixelY / Height | |
ToF_Gui.Debug.Value := Format("Found pixel {:0.3f} ({}), {:0.3f} ({})", FoundX, PixelX, FoundY, PixelY) | |
} | |
ToF_PixelCheck(Box, ColorID, Variation) { | |
; Box = [X1, Y1, X2, Y2] | |
; Тоже, что и ToF_PixelSearch, но булевое. | |
ToF_PixelSearch(&FoundX, &FoundY, Box, ColorID, Variation) | |
return IsNumber(FoundX) and IsNumber(FoundY) | |
} | |
ToF_PixelCheckAny(Box, Colors, SleepT?, Context := "No Context") { | |
; Box = [X1, Y1, X2, Y2] | |
; Colors = [{C, V}, ...] | |
if !IsSet(SleepT) | |
SleepT := 40 ; ~1 кадр при 25 фпс | |
if !IsObject(Box) or Box.Length < 1 or !IsObject(Colors) or Colors.Length < 1 | |
return true ; Ничего ожидать не нужно. | |
BoxS := Format("[{:0.4f}, {:0.4f}, {:0.4f}, {:0.4f}]", Box[1], Box[2], Box[3], Box[4]) | |
For C in Colors { | |
ToF_Gui.Debug.Value := Format("ToF_PixelCheckAny {} {} {} {} {}", BoxS, A_Index, C.C, C.V, Context) | |
if ToF_PixelCheck(Box, C.C, C.V) | |
return true | |
Sleep SleepT | |
} | |
return false | |
} | |
ToF_SleepForPixel(Box, Colors, SleepT := 100, Context := "") { | |
; Box = [X1, Y1, X2, Y2] | |
; Colors = [{C, V}, ...] | |
; Ожидание, пока в указаном Box не появится один из указаных пикселей Colors | |
if !Box or Box.Length < 1 or !Colors or Colors.Length < 1 | |
return true ; Ничего ожидать не нужно. | |
Loop { | |
C_Index := Mod(A_Index - 1, Colors.Length) + 1 | |
C := Colors[C_Index] | |
BoxS := Format("[{:0.3f}, {:0.3f}, {:0.3f}, {:0.3f}]", Box[1], Box[2], Box[3], Box[4]) | |
ToF_Gui.Debug.Value := Format("ToF_SleepForPixel {} {} {} {} {} {}", BoxS, A_Index, C_Index, C.C, C.V, Context) | |
if ToF_PixelCheck(Box, C.C, C.V) | |
return true | |
Sleep SleepT | |
} | |
} | |
ToF_CoordsRelativeToAbsolute(&AbsX, &AbsY, RelX, RelY) { | |
ToF_Check() | |
WinGetClientPos &WX, &WY, &Width, &Height, ToF_Window | |
; Добавляем +1, т.к. координаты в AHK от 1, а не от 0 | |
AbsX := Round(WX + RelX * Width + 1) | |
AbsY := Round(WY + RelY * Height + 1) | |
} | |
ToF_MouseMove(Point, SleepT?, Speed?) { | |
; Point = [X, Y] куда двигать мышь в 0..1 окна | |
; SleepT задержки между попытками | |
; Speed скорость движения мыши | |
if !IsSet(SleepT) | |
SleepT := 40 ; ~1 кадр при 25 фпс | |
if !IsSet(Speed) | |
Speed := 5 | |
Loop { | |
ToF_CoordsRelativeToAbsolute(&TX, &TY, Point[1], Point[2]) | |
ToF_Gui.Debug.Value := Format("ToF_MouseMove to X:{:0.4f}|{}, Y:{:0.4f}|{}...", Point[1], TX, Point[2], TY) | |
CoordMode "Mouse", "Screen" | |
MouseGetPos &XC, &YC | |
if Abs(TX - XC) + Abs(TY - YC) < 3 | |
break | |
SendMode "Event" | |
SetMouseDelay 20 | |
MouseMove TX, TY, Speed | |
Sleep SleepT | |
} | |
return true | |
} | |
ToF_MouseDrag(FromPoint, ToPoint, SleepT?, Speed?) { | |
if !IsSet(SleepT) | |
SleepT := 40 ; ~1 кадр при 25 фпс | |
FromFormat := Format("[{:0.4f}, {:0.4f}]", FromPoint[1], FromPoint[2]) | |
ToFormat := Format("[{:0.4f}, {:0.4f}]", ToPoint[1], ToPoint[2]) | |
ToF_Gui.Debug.Value := Format("ToF_MouseDrag {} -> {}...", FromFormat, ToFormat) | |
Sleep SleepT | |
ToF_MouseMove(FromPoint, SleepT, Speed?) | |
ToF_Check() | |
Send "{LButton down}" | |
Sleep SleepT | |
ToF_MouseMove(ToPoint, SleepT, Speed?) | |
Sleep SleepT | |
ToF_Check() | |
Send "{LButton up}" | |
Sleep SleepT | |
/* | |
ToF_CoordsRelativeToAbsolute(&FromX, &FromY, FromPoint[1], FromPoint[2]) | |
ToF_CoordsRelativeToAbsolute(&ToX, &ToY, ToPoint[1], ToPoint[2]) | |
FromFormat := Format("X:{:0.4f}|{}, Y:{:0.4f}|{}", FromPoint[1], FromX, FromPoint[2], FromY) | |
ToFormat := Format("X:{:0.4f}|{}, Y:{:0.4f}|{}", ToPoint[1], ToX, ToPoint[2], ToY) | |
ToF_Gui.Debug.Value := Format("ToF_MouseDrag {} -> {}...", FromFormat, ToFormat) | |
CoordMode "Mouse", "Screen" | |
MouseClickDrag "L", FromX, FromY, ToX, ToY, Speed | |
*/ | |
} | |
ToF_Click(Point, SleepT?, Speed?) { | |
; Point = [X, Y] куда кликать | |
; SleepT и Speed передаются в ToF_MouseMove | |
ToF_MouseMove(Point, SleepT?, Speed?) | |
ToF_CoordsRelativeToAbsolute(&TX, &TY, Point[1], Point[2]) | |
CoordMode "Mouse", "Screen" | |
SendMode "Event" | |
SetMouseDelay 40 | |
Click TX, TY | |
return true | |
} | |
ToF_ClickSafe(Point, CheckFunc, SleepT?, Speed?, LoopN?) { | |
/* | |
Point = [X, Y] куда кликать в 0..1 окна | |
CheckFunc функция проверки, должна возвращать true если можно жать. | |
SleepT и Speed передаются в ToF_Click | |
Безопасно нажимает на экран ToF по относительным координатам от 0 до 1. | |
До нажатия на экран, вызывает CheckFunc и нажимает только если она вернула true. | |
Иначе, спит SleepT мс и пробует снова до LoopN раз. | |
В случае удачи CheckFunc вернет результат ToF_Click, иначе false | |
Это защищает от ошибочных нажатий, если что-то сломалось. | |
Alt, если необходимо, следует нажимать отдельно. | |
*/ | |
Counter := 3 | |
if !IsSet(LoopN) | |
LoopN := 1000 | |
if !IsSet(SleepT) | |
SleepT := 40 ; ~1 кадр при 25 фпс | |
Loop LoopN { | |
ToF_MouseMove(Point, SleepT?, Speed?) | |
if !CheckFunc() { | |
; Если условие проверки не выполнено, подождать, повторить | |
Sleep SleepT | |
continue | |
} | |
if Counter > 0 { | |
; Условие проверки должно выполниться несколько раз подряд. | |
Counter -= 1 | |
Sleep SleepT | |
continue | |
} | |
return ToF_Click(Point?, Speed?, SleepT?) | |
} | |
return false | |
} | |
ToF_ClickSafeAnyColor(Box, AnyColors, Point?, SleepT?, Speed?, LoopN?) { | |
/* | |
Box = [X1, Y1, X2, Y2] | |
AnyColors = [{C, V}, ...] массив цветов | |
Point = [X, Y] или ничего | |
SleepT, Speed и LoopN передаются в ToF_ClickSafe | |
Безопасно нажимает на экран ToF по относительным координатам от 0 до 1. | |
До нажатия на экран, проверяет зону кнопки Box на наличие какого либо из цветов в AnyColors. | |
Это защищает от ошибочных нажатий, если что-то сломалось. | |
Alt, если необходимо, следует нажимать отдельно. | |
*/ | |
if !IsSet(Point) or !IsObject(Point) | |
Point := Util_BoxToPoint(Box) | |
F := CheckFunc() => ToF_PixelCheckAny(Box, AnyColors, SleepT?) | |
return ToF_ClickSafe(Point, F, SleepT?, Speed?, LoopN?) | |
} | |
/* * * * * | |
Базовые функции для взаимодействия с UI ToF | |
*/ | |
; Loading Screen | |
ToF_IsLoading() { | |
; Поиск синего прогресбарра на участке от 0% до <32% | |
; TODO Не работает на всех разрешениях | |
static BoxBar1 := [0.140, 0.940, 0.145, 0.970] | |
static BoxBar2 := [0.255, 0.940, 0.260, 0.970] | |
ToF_PixelSearch &Bar1X, &Bar1Y, BoxBar1, 0x23b4f7, 1 | |
ToF_PixelSearch &Bar2X, &Bar2Y, BoxBar2, 0x23b4f7, 1 | |
ToF_Gui.Debug.Value := Format("ToF_IsLoading: Bar1:{:0.3f},{:0.3f}, Bar2:{:0.3f},{:0.3f}", Bar1X, Bar1Y, Bar2X, Bar2Y) | |
return IsNumber(Bar1X) and IsNumber(Bar1Y) and IsNumber(Bar2X) and IsNumber(Bar2Y) | |
} | |
ToF_AwaitLoadingEnd(SleepT := 500) { | |
; Зависнуть пока ToF_IsLoading | |
While ToF_IsLoading() { | |
ToF_Gui.Debug.Value := Format("ToF_AwaitLoadingEnd: {} {}", SleepT, A_Index) | |
Sleep SleepT | |
} | |
return true | |
} | |
; OpenWorld | |
ToF_IsOpenWorld() { | |
; Поиск синего кружочка рядом с каналом в левом верхнем углу или иконки солнышка рядом с миникартой. | |
; Синяя точка гарантирует опенворлд, но при низком ХП она перекрывается красными эффектами и не детектится. | |
; Иконка солнышка есть и в других режимах, например рифте или вормхоле. Такие режимы будут считаться ложно-открытыми. | |
; Имеет смысл детектить и кнопку выхода из инстанса, но она тоже может перекрываться эффектами. | |
static BlueDotBox := [0.007, 0.053, 0.015, 0.065] | |
static MinimapSunBox := [0.136, 0.131, 0.143, 0.142] | |
ResultDot := ToF_PixelCheck(BlueDotBox, 0x3ea6ed, 5) | |
ResultSun := ToF_PixelCheck(MinimapSunBox, 0xffee94, 1) | |
ToF_Gui.Debug.Value := Format("ToF_IsOpenWorld: Dot:{}, ResultSun:{}", ResultDot, ResultSun) | |
return ResultDot or ResultSun | |
} | |
ToF_AwaitOpenWorld(SleepT := 500) { | |
; Зависнуть пока не выполнится ToF_IsOpenWorld | |
While !ToF_IsOpenWorld() { | |
ToF_Gui.Flow.Value := Format("ToF_AwaitOpenWorld: {} {}", SleepT, A_Index) | |
Sleep SleepT | |
} | |
return true | |
} | |
ToF_CheckOpenWorldStartup(Context := "") { | |
if ToF_IsOpenWorld() | |
return true | |
M1 := Format("You are not in open world. Macro ({}) must startup in open world.", Context) | |
M2 := Format("Ты не в открытом мире. Макро ({}) должен начинать свою работу в открытом мире.", Context) | |
MsgBox M1 "`n`n" M2 | |
return false | |
} | |
; Weapons & Skills | |
ToF_CanSwitchWeapon(&CurrentSlot, NextSlot, &NextSwitchStamp) { | |
; Можем ли мы переключить оружие используя ToF_TrySwitchWeapon? | |
; Да, если оно уже переключено или кд на переключение отсутствует. | |
Switch_TimeMs := Sys_TimerStampDiffSec(NextSwitchStamp, Sys_TimerStampGet()) | |
return CurrentSlot == NextSlot or Switch_TimeMs < 0 | |
} | |
ToF_TrySwitchWeapon(&CurrentSlot, NextSlot, &NextSwitchStamp, DischargeAwait := 1500, SwitchCD := 3) { | |
; Хелпер для переключения пушек и синхронизации состояния. | |
; CurrentSlot - Необходимо отслеживать какая поушка сейчас активна | |
; NextSlot - Кнопка оружия, на которую мы хотим переключиться | |
; NextSwitchStamp - Когда в следующий раз можно будет переключиться? | |
; DischargeAwait - Задержка после переключения, что бы успел отработать дисчардж. | |
; Эль проблема: если переключение во время, когда нам дают пездюлей, то переключения не будет. | |
; Возвращает true, если переключено или false, если нужно ждать отката. | |
if CurrentSlot == NextSlot | |
return true | |
Switch_TimeMs := Sys_TimerStampDiffSec(NextSwitchStamp, Sys_TimerStampGet()) | |
if Switch_TimeMs > 0 | |
return false | |
ToF_Gui.Flow.Value := Format("Switching weapon №{} -> №{}...", CurrentSlot, NextSlot) | |
Loop 5 { | |
Sleep 50 | |
ToF_Check() | |
Send "{" . NextSlot . " down}" | |
Sleep 50 | |
Send "{" . NextSlot . " up}" | |
} | |
NextSwitchStamp := Sys_TimerStampAddSec(Sys_TimerStampGet(), SwitchCD) | |
CurrentSlot := NextSlot | |
Sleep DischargeAwait ; На случай ульты | |
return true | |
} | |
ToF_IsWeaponSkillAvailable() { | |
; Проверяет доступен ли скилл оружия. | |
; Проверяются зоны над и под цифрой счетчика отката скилла на пердмет белого цвета. | |
; Доступная кнопка скила обычно имеет близко-белый цвет, а не активная затенена. | |
; Но всю кнопку проверять нельзя, т.к. счетчик отката тоже белый. | |
; Т.к. кнопка полупрозрачная, из-за фона вероятны ложно-положительные результаты, но не ложно-отрицательные. | |
; Лучше проверять этой функцией ушел ли скилл в откат (был ли он использован?) | |
static BoxUp := [0.8969, 0.8888, 0.9375, 0.8972] | |
static BoxDown := [0.8969, 0.9263, 0.9375, 0.9347] | |
ResultUp := ToF_PixelCheck(BoxUp, 0xffffff, 10) | |
ResultDown := ToF_PixelCheck(BoxDown, 0xffffff, 10) | |
ToF_Gui.Debug.Value := Format("ToF_IsSkillAvailable: Up: {}, Down: {}", ResultUp, ResultDown) | |
return ResultUp or ResultDown | |
} | |
ToF_ActivateWeaponSkill(Context, SkillButton, MaxLoops := 40) { | |
; Долбим кнопку активации скилла оружия до тех пор, пока не кнопка не уйдет в откат. | |
; По-умолчанию до 40 * (50+50)мс = 4сек. | |
ToF_Gui.Flow.Value := Format("Activating Skill ({})...", Context) | |
Loop MaxLoops { | |
ToF_Gui.Debug.Value := Format("Activating Skill ({})... {}", Context, A_Index) | |
ToF_Check() | |
Send "{" . SkillButton . " down}" | |
Sleep 50 | |
Send "{" . SkillButton . " up}" | |
Sleep 50 | |
if !ToF_IsWeaponSkillAvailable() { | |
ToF_Gui.Flow.Value := Format("Activated Skill ({}). (Success? {})", Context, A_Index) | |
return true | |
} | |
} | |
ToF_Gui.Flow.Value := Format("Activated Skill ({}). (Failed?)", Context) | |
return false | |
} | |
; Fighting | |
ToF_IsGettingDamage() { | |
; Поиск иконки мечей слева внизу над хп баром. | |
; Иконка анимированая и переливается красным по этому ищем белый цвет и 2 оттенка красного из 4ех. | |
; Если эта иконка есть, значит сейчас мы в сражении с кем-то, не важно в инстансе или в отткрытом мире. | |
; Природные эффекты вроде горения также активируют этот эффект! | |
static Box := [0.385, 0.891, 0.402, 0.920] | |
if !ToF_PixelCheck(Box, 0xffffff, 0) | |
return false | |
C := 0 | |
if ToF_PixelCheck(Box, 0xb90000, 10) | |
C += 1 | |
if ToF_PixelCheck(Box, 0x6c1407, 10) | |
C += 1 | |
if ToF_PixelCheck(Box, 0x4a210e, 10) | |
C += 1 | |
if ToF_PixelCheck(Box, 0x82261f, 10) | |
C += 1 | |
return C >= 2 | |
} | |
ToF_IsInFightWithMiniBoss() { | |
; Когда с кем-то дерешься, в левой верхней секции экрана появляется маленький красныый хпбар | |
; Попытка найти этот хпбар. | |
static Box := [0.222, 0.222, 0.232, 0.238] | |
return ToF_PixelCheck(Box, 0xff5252, 1) | |
} | |
ToF_IsInFightWithBigBoss() { | |
; Проверка есть ли сейчас бой с большим боссом. | |
; У большого босса большой многослоынй ХПбар в верху экрана, | |
; имя босса и хп-бар цели на которую он агрится | |
static HPBox := [0.3977, 0.0931, 0.6016, 0.0972] | |
static HPColors := [ | |
{C:0xff5252, V:1}, ; Красный | |
{C:0xffd391, V:1}, ; Оранжевый | |
{C:0x8bd4ff, V:1}, ; Бирюзовый | |
{C:0xff8ced, V:1}, ; Розовый | |
] | |
static NameBox := [0.5 - 0.04, 0.0417, 0.5 + 0.06, 0.0556] | |
static AgroBox := [0.6359, 0.1417, 0.7117, 0.1444] | |
RsultHP := ToF_PixelCheckAny(HPBox, HPColors) | |
ResultName := ToF_PixelCheck(NameBox, 0xf5f5f5, 1) ; Почти белый | |
ResultAgro := ToF_PixelCheck(AgroBox, 0x2fd1a1, 1) ; Зеленый | |
ToF_Gui.Debug.Value := Format("ToF_IsInFightWithBigBoss: HP:{}, N:{}, A:{}", RsultHP, ResultName, ResultAgro) | |
return RsultHP and ResultName and ResultAgro | |
} | |
ToF_AutoFightIconBox := [0.5930, 0.8361, 0.6156, 0.8764] | |
ToF_IsAutoFightEnabled() { | |
; Пороверяет есть ли крутящаяся бирюзовая иконка автобоя. | |
; Если false, это не значит, что автобой отключен. Кнопка просто может отсутствовать. | |
return ToF_PixelCheck(ToF_AutoFightIconBox, 0xace2fd, 5) | |
} | |
ToF_IsAutoFightDisabled() { | |
; Пороверяет есть ли статичная серая иконка автобоя. | |
; Если false, это не значит, что автобой включен. Кнопка просто может отсутствовать. | |
return ToF_PixelCheck(ToF_AutoFightIconBox, 0xb3b9c1, 5) | |
} | |
ToF_ClickAutoFight() { | |
; Нажать на Auto справа внизу возле хп бара игрока. | |
; Работает только в инстансах, включает (выключает) авто бой. | |
static Point := Util_BoxToPoint(ToF_AutoFightIconBox) | |
ToF_Gui.Flow.Value := "Enabling auto fight..." | |
ToF_Check() | |
Send "{alt down}" | |
Sleep 100 | |
ToF_Click(Point) | |
Sleep 100 | |
Send "{alt up}" | |
} | |
ToF_TryKeepAutoFightEnabled(&AutoFightCounter, MaxCounter := 10) { | |
AF_On := ToF_IsAutoFightEnabled() | |
AF_Off := ToF_IsAutoFightDisabled() | |
if (AF_On != AF_Off) { | |
; Если AF_On == AF_Off, то это либо ошибка распознования, | |
; либо кнопка не доступна (кат-сцена, ...), игнорируем | |
if (AF_On) { | |
AutoFightCounter := 0 | |
} | |
if (AF_Off) { | |
AutoFightCounter += 1 | |
} | |
if (AutoFightCounter >= MaxCounter) { | |
; Если мы задетектили более MaxCounter раз подряд, что автобой выкл, | |
; не обнаруживая, что он включен, то кликаем туда. | |
ToF_ClickAutoFight() | |
AutoFightCounter := 0 | |
} | |
} | |
} | |
ToF_GetApproxDashes() { | |
; Примерное число доступных дэшей. | |
; 0, 1, 2 или 3. | |
; Может быть чуть чуть меньше. | |
; Также может перекрываться эффектами низкого хп. | |
static DashColor := 0x39c3fe | |
static Boxes := [ | |
[0.9508, 0.6556, 0.9539, 0.6597], ; Конец первого деша | |
[0.9711, 0.6556, 0.9743, 0.6597], ; Конец второго деша | |
[0.9907, 0.6556, 0.9937, 0.6597], ; Конец третьего деша | |
] | |
if ToF_PixelCheck(Boxes[3], DashColor, 3) | |
return 3 | |
if ToF_PixelCheck(Boxes[2], DashColor, 3) | |
return 2 | |
if ToF_PixelCheck(Boxes[1], DashColor, 3) | |
return 1 | |
return 0 | |
} | |
/* | |
Бесполезная информация: цвет хп бара в зависимости от того сколько хп. | |
221/221 100% HP = 0x96e065 (150 224 101) | |
214/221 97% HP = 0xa2dc65 (162 220 101) | |
197/221 89% HP = 0xb8d464 (184 212 100) | |
188/221 85% HP = 0xc2d063 (194 208 99) | |
177/221 80% HP = 0xcdcb63 (205 203 99) | |
163/221 74% HP = 0xe4a353 (228 163 83) | |
148/221 67% HP = 0xe0b95d (224 185 93) | |
124/221 56% HP = 0xe49e51 (228 158 81) | |
97/221 44% HP = 0xe87f42 (232 127 66) | |
73/221 33% HP = 0xeb6336 (235 99 54) | |
66/221 30% HP = 0xeb5c33 (235 92 51) | |
Где-то тут ниже хп бар начинает мигать и становиться полупрозрачным! | |
*/ | |
ToF_GetApproxHP() { | |
; Попытка измерить длину хпбара и понять сколько у игрока ХП. | |
; Испльзуется бинарный поиск по красному каналу. | |
; Известно, что заполненый бар всегда ярче 145. | |
; Может обсираться, если под ХП-баром светлый фон, и показывать ложные 100% ХП. | |
; Возвращает 0...1 но может быть и за пределами из-за погрешностей. | |
static HpY := 0.953 | |
static HpXLeft := 0.4195 | |
static HpXRight := 0.5898 | |
ToF_Check() | |
WinGetClientPos &WinX, &WinY, &Width, &Height, ToF_Window | |
Y := Round(HpY * Height) | |
XLeft := Round(HpXLeft * Width) | |
XRight := Round(HpXRight * Width) | |
Red := -1 | |
Loop { | |
; ToF_Gui.Debug.Value := Format("Scaning HP at [{},{}] @ Loop:{}, R:{}...", XLeft, XRight, A_Index, Red) | |
XCenter := Round(XLeft * 0.5 + XRight * 0.5) | |
if XCenter == XLeft or XCenter == XRight | |
break | |
CoordMode "Pixel", "Client" | |
Color := PixelGetColor(XCenter, Y) | |
Red := Number("0x" . SubStr(Color, 3, 2)) | |
if Red > 145 { | |
XLeft := XCenter | |
} else { | |
XRight := XCenter | |
} | |
} | |
HpXCenter := XLeft / Width | |
Hp := (HpXCenter - HpXLeft) / (HpXRight - HpXLeft) | |
ToF_Gui.Debug.Value := Format("ToF_GetApproxHP: {:0.1f}% @ {:0.4f} ({}) R:{}", Hp * 100, HpXCenter, XLeft, Red) | |
return Hp | |
} | |
ToF_IsHPFull() { | |
; ХП-бар игрока внизу экрана, проверка заполнен лиего кончик салатовым. | |
static Box := [0.585, 0.947, 0.593, 0.960] | |
return ToF_PixelCheck(Box, 0x96e065, 1) | |
} | |
ToF_IsHPGreen() { | |
; проверка ХП-бар заполнен салатовым. | |
static Box := [0.417, 0.947, 0.593, 0.960] | |
return ToF_PixelCheck(Box, 0x96e065, 2) | |
} | |
ToF_ClickExitInstance() { | |
; Нажать на иконку выхода из инстанса в левом верхнем углу рядом с таймером и таблицей игроков | |
; Не факт, что работает в воид рифте, там другой лайаут интерфейса. | |
static Point := [0.138, 0.061] | |
ToF_Check() | |
Send "{alt down}" | |
Sleep 100 | |
ToF_Click(Point) | |
Sleep 100 | |
Send "{alt up}" | |
Sleep 100 | |
} | |
ToF_IsSomeoneFightingInInstance() { | |
; Поиск прогрессбара дамага на таблице игроков в левом верхнем углу инстанса | |
static BoxA := [0.0085, 0.1152, 0.1446, 0.1237] | |
static BoxB := [0.0085, 0.1611, 0.1446, 0.1695] | |
static BoxC := [0.0085, 0.2069, 0.1446, 0.2153] | |
static BoxD := [0.0085, 0.2527, 0.1446, 0.2612] | |
CheckA := ToF_PixelCheck(BoxA, 0x3ea6ed, 3) | |
CheckB := ToF_PixelCheck(BoxB, 0x3ea6ed, 3) | |
CheckC := ToF_PixelCheck(BoxC, 0x3ea6ed, 3) | |
CheckD := ToF_PixelCheck(BoxD, 0x3ea6ed, 3) | |
ToF_Gui.Debug.Value := Format("ToF_IsSomeoneFightingInInstance: {},{},{},{}", CheckA, CheckB, CheckC, CheckD) | |
return CheckA or CheckB or CheckC or CheckD | |
} | |
ToF_IsChallengeFailedScreen() { | |
; Поиск экрана Challenge Failed и проверка находимся ли мы сейчас на этом экране. | |
; Во Frontier Clash он вылазит, если никто не запустил данж за 1:30 | |
; Также вылазит не только во Frontier Clash, должно работать универсально. | |
; Ниже и выше Challenge Failed есть 2 полоски серо-синего цвета, | |
; а внизу надписать "Нажмите где-нибудь и идите нахуй" | |
; Детектим эти полосы и серую надпись внизу. | |
static BoxLine1 := [0.470, 0.252, 0.530, 0.259] | |
static BoxLine2 := [0.470, 0.379, 0.530, 0.385] | |
static BoxGrayBottom := [0.470, 0.881, 0.530, 0.912] | |
Line1 := ToF_PixelCheck(BoxLine1, 0xa4b1c6, 2) | |
Line2 := ToF_PixelCheck(BoxLine2, 0xa4b1c6, 2) | |
GrayBottom := ToF_PixelCheck(BoxGrayBottom, 0x323438, 5) | |
ToF_Gui.Debug.Value := Format("ToF_IsChallengeFailedScreen: Line1: {}, Line2: {}, GrayBottom: {}", Line1, Line2, GrayBottom) | |
return Line1 and Line2 and GrayBottom | |
} | |
ToF_IsYouFailedDeadScreen() { | |
; Экран, который появляется в инстансах, когда ты умер. | |
; Проверка находимся ли мы сейчас на этом экране. | |
static ColorTriangle := 0xd4d4d4 | |
static ColorGray := 0x5e6e81 | |
static BoxTringleTop:= [0.5 - 0.005, 0.225, 0.5 + 0.005, 0.248] | |
static BoxTringleLeft := [0.5 - 0.079 - 0.005, 0.468, 0.5 - 0.079 + 0.005, 0.491] | |
static BoxTringleRight := [0.5 + 0.079, 0.468, 0.5 + 0.079 + 0.005, 0.491] | |
static BoxGrayBottom := [0.5 - 0.010, 0.468, 0.5 + 0.010, 0.491] | |
TT := ToF_PixelCheck(BoxTringleTop, ColorTriangle, 2) | |
TL := ToF_PixelCheck(BoxTringleLeft, ColorTriangle, 2) | |
TR := ToF_PixelCheck(BoxTringleRight, ColorTriangle, 2) | |
GB := ToF_PixelCheck(BoxGrayBottom, ColorGray, 2) | |
ToF_Gui.Debug.Value := Format("ToF_IsYouFailedDeadScreen: Top: {}, Left: {}, Right: {}, Bottom: {}", TT, TL, TR, GB) | |
return TT and TL and TR and GB | |
} | |
ToF_IsFrontierClashGrayChestsScreen() { | |
; Поиск экрана в конце Frontier Clash с 5ю серыми сундуками. | |
; Детект по серому днищу сундука №2 и №4 и синей кнопке Select внизу | |
; Детект по среднему сундуку обсирается т.к. там спавнится курсор | |
; Также оттенок серого частво встречается в игре | |
; по этому также детектим синюю кнопку skip внизу экрана | |
static BoxChest2 := [0.283, 0.515, 0.369, 0.535] | |
static BoxChest4 := [0.635, 0.515, 0.729, 0.535] | |
static BoxBtnSelect := [0.428, 0.484, 0.464, 0.925] | |
Chest2 := ToF_PixelCheck(BoxChest2, 0x7c7e80, 5) | |
Chest4 := ToF_PixelCheck(BoxChest4, 0x7c7e80, 5) | |
BtnSelect := ToF_PixelCheck(BoxBtnSelect, 0x1589f6, 1) | |
ToF_Gui.Debug.Value := Format("ToF_IsFrontierClashGrayChestsScreen: Chest2: {}, Chest4: {}, BtnSelect: {}", Chest2, Chest4, BtnSelect) | |
return Chest2 and Chest4 and BtnSelect | |
} | |
ToF_IsRedAlertShown() { | |
; В таких режимах как Frontier Clash может появляться оповещение с инофрмацией | |
; обычно инфа о волнах и о том, что нужно запустить данж. | |
; TODO, пока не используется | |
} | |
/* * * * * | |
Типовые взаимодействия с UI ToF | |
*/ | |
; Match-making | |
ToF_IsMatchApproveConfirmScreen() { | |
; Поиск кнопки Approve Confirm во время матч мейкинга, когда надо дать свое согласие на дпредложенный подбор. | |
; Поиск по синим ромбикам на кнопке Deny и оранжевой палке рядом с кнопкой Approve Confirm | |
; Возвращаем координаты оранжевых пикселей, т.е. реальная кпонка где-то в стороне. | |
static BoxOrange := [0.642, 0.666, 0.650, 0.723] | |
static DashesBlue := [0.357, 0.694, 0.368, 0.743] | |
IsOrange := ToF_PixelCheck(BoxOrange, 0xf39800, 1) | |
IsBlue := ToF_PixelCheck(DashesBlue, 0x00a0e9, 1) | |
ToF_Gui.Debug.Value := Format("ToF_IsMatchApproveConfirmScreen: Orange: {}, Blue: {}", IsOrange, IsBlue) | |
return IsOrange and IsBlue | |
} | |
ToF_TryAssistMatchApproveConfirm() { | |
; Пытается поставить галочку Assist во время экрана подтверждения матча. | |
; Возвращает true, если галочка поставлена и это проверено. | |
; Возвращает false, если что-то не так и зона для галочки не обнаружена. | |
static AssistMarkBox := [0.4390, 0.7944, 0.4579, 0.8264] | |
static AssistMarkPoint := Util_BoxToPoint(AssistMarkBox) | |
static AssistAsidePoint := [AssistMarkPoint[1] + 0.05, AssistMarkPoint[2]] | |
IsAssistMatchMarkUnset() { | |
; Когда галочка НЕ стоит, при наведении на квадрат, она НЕ меняет цвет. | |
return ToF_PixelCheck(AssistMarkBox, 0x80b3cc, 1) | |
} | |
IsAssistMatchMarkSet() { | |
; Когда галочка стоит, при наведении на квадрат, она меняет цвет. | |
UnHoverFrame := ToF_PixelCheck(AssistMarkBox, 0x008bca, 3) | |
UnHoverMark := ToF_PixelCheck(AssistMarkBox, 0xdcdcdc, 3) | |
HoverFrame := ToF_PixelCheck(AssistMarkBox, 0x006494, 3) | |
HoverMark := ToF_PixelCheck(AssistMarkBox, 0xa2a2a2, 3) | |
return (UnHoverFrame and UnHoverMark) or (HoverFrame and HoverMark) | |
} | |
LoopBegin := Sys_TimerStampGet() | |
Loop { | |
LoopTime := Sys_TimerStampDiffSec(Sys_TimerStampGet(), LoopBegin) | |
ToF_Gui.Flow.Value := Format("Setting matching Assist mark... {} {0.3f}s", A_Index, LoopTime) | |
; Отодвигаем мышь рядом в сторону, да бы не мешать распознованию квадрата. | |
ToF_MouseMove(AssistAsidePoint) | |
; Если галочка уже стоит, то ничего делать не нужно. | |
if IsAssistMatchMarkSet() | |
return true | |
; Если и пустого квадратика нет, значит что-то нетак | |
if !IsAssistMatchMarkUnset() | |
return false | |
ToF_Click(AssistMarkPoint) | |
; Здесь не нужны Sleep, т.к. они уже есть в движении мышью | |
} | |
} | |
ToF_ClickMatchApproveConfirm() { | |
; Нажать на кнопку Approve Confirm во время матч мейка | |
static ApproveBox := [0.5445, 0.6778, 0.6383, 0.7139] | |
static ApproveColor := [{C:0x2a3547, V:1}] | |
ToF_Gui.Flow.Value := "Pressing matching Approve Confirm..." | |
return ToF_ClickSafeAnyColor(ApproveBox, ApproveColor) | |
} | |
; Adventure UI | |
ToF_ClickSwordsAdventure() { | |
; Нажать на мечи (Adventure) в верхнем правом углу | |
; static Box := [0.891, 0.035] | |
ToF_Gui.Flow.Value := "Pressing Adventure Swords..." | |
Sleep 100 | |
ToF_Check() | |
Send "{alt down}" | |
Sleep 100 | |
ToF_Check() | |
Send "{3 down}" | |
Sleep 200 | |
Send "{3 up}" | |
Sleep 100 | |
Send "{alt up}" | |
Sleep 100 | |
} | |
ToF_AdventureLeftTabColors := [ | |
{C:0xbba597, V:1}, ; Цвет активной вкладки | |
{C:0xe4ecf4, V:1}, ; Цвет не активной вкладки | |
] | |
ToF_AdventureColumnsGoButtonColors := [ | |
{C: 0x346fad, V:2}, ; Цвет синей активной кнопки | |
{C: 0x344c6b, V:2}, ; Цвет синей не активной кнопки | |
] | |
ToF_ClickAdventureSelect() { | |
; Нажать на вкладку SELECT слева в окне Adventure | |
static Box := [0.130 - 0.05, 0.438 - 0.05, 0.130 + 0.05, 0.438 + 0.05] | |
ToF_Gui.Flow.Value := "Switching to Select..." | |
return ToF_ClickSafeAnyColor(Box, ToF_AdventureLeftTabColors) | |
} | |
ToF_ClickAdventureChallange() { | |
; Нажать на вкладку CHALLANGE слева в окне Adventure | |
static Box := [0.130 - 0.05, 0.543 - 0.05, 0.130 + 0.05, 0.543 + 0.05] | |
ToF_Gui.Flow.Value := "Pressing Challange..." | |
return ToF_ClickSafeAnyColor(Box, ToF_AdventureLeftTabColors) | |
} | |
ToF_ConfirmAdventureChallange() { | |
static TabBox := [0.0961, 0.5167, 0.1164, 0.5583] | |
static ShopBox := [0.1266, 0.8778, 0.1680, 0.9167] | |
ResultTab := ToF_PixelCheck(TabBox, 0x5da8ff, 5) ; Голубая буква C на вкладке Challange | |
ResultShop := ToF_PixelCheck(ShopBox, 0x584193, 1) ; Кнопка магазина внизу менюшки | |
ToF_Gui.Debug.Value := Format("ToF_ConfirmAdventureChallange: Tab: {}, Shop: {}", ResultTab, ResultShop) | |
return ResultTab and ResultShop | |
} | |
ToF_AwaitClickAdventureChallange(SleepT := 500) { | |
; Нажать ToF_ClickAdventureChallange | |
; Зависнуть пока не выполнится ToF_ConfirmAdventureChallange | |
Loop { | |
ToF_ClickAdventureChallange() | |
Sleep SleepT | |
if ToF_ConfirmAdventureChallange() | |
break | |
} | |
return true | |
} | |
ToF_DragAdventureModes(Direction) { | |
static LeftX := 0.26 | |
static RightX := 0.79 | |
static FromPoint := [0, 0.5] | |
static ToPoint := [0, 0.5] | |
FromPoint[1] := Mod(Direction, 2) == 1 ? LeftX : RightX | |
ToPoint[1] := Mod(Direction, 2) == 1 ? RightX : LeftX | |
ToF_MouseDrag(FromPoint, ToPoint) | |
ToF_MouseDrag(FromPoint, ToPoint) | |
} | |
ToF_FindWhereFrontierClashAndPress() { | |
; Кнопка Frontier Clash может располагаться в разных местах и имет разную расцветку. | |
; По этому поиск и нажатие этойкнопки - сложная задача. | |
; На 50+ уровнях кнопка Frontier Clash имеет отличительную особенность: | |
; Предметы внизу столбца над кнопкой Go - там есть и синий, и фиолетовый и золотой. | |
; На других кнопках нету сразу 3 класса редкости. | |
; Это может не работать на уровнях ниже 50 или выше 60. | |
; Нужно исследовать. | |
ToF_Gui.Flow.Value := "Searching Frontier Clash button..." | |
static ColorsActive := { | |
Gold: {C: 0xe4bd7e, V: 10}, | |
Purple: {C: 0xb692ca, V: 10}, | |
Blue: {C: 0x63a5d5, V: 5} | |
} | |
static ColorsInactive := { | |
Gold: {C: 0x585c61, V: 3}, | |
Purple: {C: 0x4f5372, V: 3}, | |
Blue: {C: 0x3e5774, V: 2} | |
} | |
; Столбцы с режимами игры, среди которых нужно искать фронтир клеш. | |
static Y1 := 0.691 | |
static Y2 := 0.696 | |
static BoxA := [0.467, Y1, 0.585, Y2] | |
static BoxB := [0.599, Y1, 0.716, Y2] | |
static BoxC := [0.730, Y1, 0.846, Y2] | |
static Has3Colors(Box, Colors) { | |
ToF_PixelSearch &Sq1X, &Sq1Y, Box, Colors.Gold.C, Colors.Gold.V | |
ToF_PixelSearch &Sq2X, &Sq2Y, Box, Colors.Purple.C, Colors.Purple.V | |
ToF_PixelSearch &Sq3X, &Sq3Y, Box, Colors.Blue.C, Colors.Blue.V | |
if IsNumber(Sq1X) and IsNumber(Sq2X) and IsNumber(Sq3X) | |
return Sq1X/3 + Sq2X/3 + Sq3X/3 | |
return "" | |
} | |
FindBox(Colors) { | |
BoxData := {} | |
Sleep 50 | |
BoxData.AX := Has3Colors(BoxA, Colors) | |
BoxData.BX := Has3Colors(BoxB, Colors) | |
BoxData.CX := Has3Colors(BoxC, Colors) | |
BoxData.Counter := 0 | |
BoxData.X := "" | |
if IsNumber(BoxData.AX) { | |
BoxData.Counter += 1 | |
BoxData.X := BoxData.AX | |
} | |
if IsNumber(BoxData.BX) { | |
BoxData.Counter += 1 | |
BoxData.X := BoxData.BX | |
} | |
if IsNumber(BoxData.CX) { | |
BoxData.Counter += 1 | |
BoxData.X := BoxData.CX | |
} | |
return BoxData | |
} | |
Data := FindBox(ColorsActive) | |
if Data.Counter == 0 { | |
Data := FindBox(ColorsInactive) | |
} | |
ToF_Gui.Debug.Value := Format("FC button: C:{}, AX:{:.3f}, BX:{:.3f}, CX:{:.3f}", Data.Counter, Data.AX, Data.BX, Data.CX) | |
if Data.Counter != 1 { | |
return false | |
} | |
ButtonGoBox := [Data.X - 0.04, 0.757 - 0.03, Data.X + 0.04, 0.757 + 0.03] | |
return ToF_ClickSafeAnyColor(ButtonGoBox, ToF_AdventureColumnsGoButtonColors) | |
} | |
ToF_ClickFrontierClashDiffNormal() { | |
; Выбрать Сложность 1 во Frontier Clash | |
static Box := [0.752 - 0.04, 0.565 - 0.03, 0.752 + 0.04, 0.565 + 0.03] | |
static Colors := [{C:0x9a4155, V:5}, {C:0x9f6377, V:5}] | |
ToF_Gui.Flow.Value := "Pressing Frontier Clash Diff Normal..." | |
return ToF_ClickSafeAnyColor(Box, Colors) | |
} | |
ToF_ClickFrontierClashInstanceGo() { | |
; Нажать GO во Frontier Clash | |
static Box := [0.773 - 0.03, 0.729 - 0.02, 0.773 + 0.03, 0.729 + 0.02] | |
static Colors := [{C:0x158bf9, V:4}] | |
ToF_Gui.Flow.Value := "Pressing Frontier Clash Instance GO..." | |
return ToF_ClickSafeAnyColor(Box, Colors) | |
} | |
ToF_ClickDimTrials() { | |
; Нажать на столбец Dimensional Trials | |
; Подразумеваем, что Dimensional Trials всегда во 2ом столбце. | |
static Box := [0.398 - 0.04, 0.757 - 0.03, 0.398 + 0.04, 0.757 + 0.03] | |
ToF_Gui.Flow.Value := "Selecting Dimensional Trials..." | |
return ToF_ClickSafeAnyColor(Box, ToF_AdventureColumnsGoButtonColors) | |
} | |
ToF_ClickDimTrialsWeaponDrill() { | |
; Выбрать Weapon Drill в Dimensional Trials | |
static Box := [0.250, 0.465] | |
ToF_Gui.Flow.Value := "Selecting Weapon Drill..." | |
; TODO Safe | |
return ToF_Click(Box) | |
} | |
ToF_ClickDimTrialsDiff1() { | |
; Выбрать Сложность 1 в Dimensional Trials | |
static Box := [0.250, 0.563] | |
ToF_Gui.Flow.Value := "Selecting Difficity 1..." | |
; TODO Safe | |
return ToF_Click(Box) | |
} | |
ToF_ClickDimTrialsInstanceGo() { | |
; Нажать GO в Dimensional Trials | |
static Box := [0.773, 0.729] | |
ToF_Gui.Flow.Value := "Pressing Instance GO..." | |
; TODO Safe | |
return ToF_Click(Box) | |
} | |
; Стандартный диалог с двумя выборами | |
ToF_DialogButtonY := 0.528 | |
ToF_DialogButtonColors := [ | |
{C:0x2f2f2f, V:3}, ; Серая кнопка | |
{C:0x1589f6, V:5}, ; Синяя кнопка | |
] | |
ToF_ClickDialogLeft(Context := "No Context") { | |
; Нажать ЛЕВУЮ кнопку из двух во всплывающем диалоге-вопросе. | |
; Анимации могут подвисать, по этому делаем задержку в пол-секунды до и после. | |
static Box := [0.336 - 0.05, ToF_DialogButtonY - 0.03, 0.336 + 0.05, ToF_DialogButtonY + 0.03] | |
ToF_Gui.Flow.Value := Format("Pressing LEFT dialog button ({})...", Context) | |
return ToF_ClickSafeAnyColor(Box, ToF_DialogButtonColors) | |
} | |
ToF_ClickDialogRight(Context := "No Context") { | |
; Нажать ЛЕВУЮ кнопку из двух во всплывающем диалоге-вопросе. | |
; Анимации могут подвисать, по этому делаем задержку в пол-секунды до и после. | |
static Box := [0.664 - 0.05, ToF_DialogButtonY - 0.03, 0.664 + 0.05, ToF_DialogButtonY + 0.03] | |
ToF_Gui.Flow.Value := Format("Pressing RIGHT dialog button ({})...", Context) | |
return ToF_ClickSafeAnyColor(Box, ToF_DialogButtonColors) | |
} | |
/* * * * * | |
Код непосредственно макросов ToF | |
*/ | |
Macro_Helper_ActivateHologramProjector(&HoloProjNext, HoloProjCD, HoloProjButton) { | |
Loop 5 { | |
Sleep 50 | |
ToF_Check() | |
Send "{" . HoloProjButton . " down}" | |
Sleep 50 | |
Send "{" . HoloProjButton . " up}" | |
} | |
HoloProjNext := Sys_TimerStampAddSec(Sys_TimerStampGet(), HoloProjCD) | |
return true | |
} | |
Macro_Helper_ActivateMagneticPulse(&MagPulseNext, MagPulseCD, MagPulseButton) { | |
Loop 3 { | |
Sleep 50 | |
ToF_Check() | |
Send "{" . MagPulseButton . " down}" | |
Sleep 50 | |
Send "{" . MagPulseButton . " up}" | |
} | |
MagPulseNext := Sys_TimerStampAddSec(Sys_TimerStampGet(), MagPulseCD) | |
return true | |
} | |
Macro_AutoFarmOpenWorldPassive() | |
{ | |
static KEY_WEAPON_ENE_PUMMELER := "2" | |
static KEY_WEAPON_COCORITTER_ABSOLUTEZERO := "3" | |
static KEY_WEAPON_SKILL := "e" | |
static KEY_RELIC_MAGNETIC_PULSE := "q" | |
static KEY_RELIC_HOLOGRAM_PROJECTOR := "r" | |
/* | |
Макрос для соло AFK-авто-фарма мало-мобильных мобов ближнего боя, которые пытаются прибежать к игроку. | |
НЕОБХОДИМАЯ КОНФИГУРАЦИЯ | |
Кнопка "2" - Ene Pummeler | |
Кнопка "3" - Cocoritter Absolute Zero (или Pepper Staff of the Scars) | |
Кнопка "E" - должна быть забинжена на скилл оружия. | |
Кнопка "Q" - Relic Magnetic Pulse (Желательно C2+, можно получить куски с Barbarossa) | |
Кнопка "R" - Relic Hologram Projector (Можно получить бесплатно за исследование Warren) | |
Можно перописать свои бинды отредактировав константы чуть выше. | |
КАК РАБОТАЕТ | |
- Макрос в АФК-режиме быстро стучит молотком на месте и не дает игроку сдвинуться. | |
- Когда враг попадает под АФК-атаку начинается бой, | |
что регистрируется макросом по мини хп-бару в левой верхней части экрана. | |
- В бою автоматически активируются релики на вспомогательный дамаг: | |
- сначала активируется Magnetic Pulse для защиты от контроля на время анимации активации Hologram Projector. | |
- затем активируется Hologram Projector, который не плохо так ускоряет взлом щита врага, даже без конст. | |
- Не рекомендуется ставить другие релики, | |
т.к. в макрос вшиты откаты именно этих реликов, да бы прожимать их постоянно по кд. | |
- В бою молотком переключается на режим долгих (полностью заряженых) аттак молотком, | |
которые хорошо шаттарят щиты и опракидывают врага. | |
- Если хп в бою меньше 40% или в афк меньше 80%, то включается скилл хилл-жезла. При этом хилл в бою активируется | |
только используя (доступный) Magnetic Pulse, это защищает от срыва анимации переключения оружия и | |
анимации скилла хилл-жезлов. Но в АФК-режиме релик не используется. | |
ДОСТОИНТСВА И НЕДОСТАТКИ | |
- Макрос абсолютно не пригоден против врагов дальнего боя, которые сохраняют дистанцию от игрока. | |
Этот макрос сохраняет максимальную стационарность по сравнению | |
с Macro_AutoFarmOpenWorldActive и не пытается приблизиться к врагу. | |
- Макрос слабо пригоден против врагов с резистом на крио-урон. | |
- Несмотря на Hyperbody и Magnetic Pulse, анимации всеравно могут быть сорованы, | |
а игрок может откинуться в сторону и потерять стационарность. Но при удачном исходе, | |
игрок не будет сдвигаться вообще. Главное быть достаточно сильным, что бы выдержать удары по лицу от врага. | |
- Есть шанс, что в битве враг получит больно по лицу и отлетит довольно далеко, что молоток не будет доставать, | |
либо в придачу умрет на растоянии, тогда дроп не будет подобран, но такое происходит не часто. | |
- Используется только два слота оружия, один слот не используется вообще, в него можно положить любое оружие | |
для подходящего Weapon Resonance (Fortitude, Attack, Benediction) | |
- Макрос не понимает когда у него есть ульта, а когда нету, также не понимает успешно | |
ли переключилось оружие, по этому при переключении макро паузится и может рассинхраниваться, | |
но пушки часто переключаются и обычно чинятся сами. | |
- Если у тебя мало ХП, то вокруг экрана появятся красные эффекты, которые частично перекрывают HUD. | |
Из-за этого макрос не всегда может увидеть нужное на экране и виснет. Пока-что чёткого решения этой проблемы нет. | |
- Неустойчив против посторонних всплывашек: если произойдет дисконнект от сервера или вылезет | |
белая лента (Reward Recovery) в начале нового дня, то макрос не поймет, что происходит и повиснет. | |
ДЛЯ ЧЕГО ПОДХОДИТ ИСПОЛЬЗОВАТЬ | |
Идеально для гринда деталей крафта машин: | |
- Monocross (Кибер лошадь): | |
https://toweroffantasy.info/mounts/monocross | |
Дроп с Devotees. | |
Молот хорошо снимает щит и опрокидывает их. | |
Рекомендуется фармить на платформе рядом с Tower of Fantasy, т.к. у домиков под Omnium Tower есть пездюки, | |
которые обстреливают из луков, против которых стационарная стойка и молот бессильны. | |
А на платформе над шахтами, если правильно встать, другие мобы агриться не будут. | |
- Chaser (Метла, робот-пылесос): | |
https://toweroffantasy.info/mounts/chaser | |
Дроп с The Vermin Brothers. | |
У них слабость к холоду, чпокаются молотом как орешки. | |
Всякие дурачки могут кидать в тебя валяющиеся рядом бомбы-бочки. | |
- Voyager (Летающее такси) | |
https://toweroffantasy.info/mounts/voyager | |
Дроп с мех из Four Powers, против собачниц не подходит. | |
Лучше всего фармить на платформе северо-западнее под летающим островом. | |
Эта меха может больно бить электрошоком и срывать атаки, следует проявлять осторожность. | |
ЗАПУСК МАКРОСА: Ctrl + Numpad 8 (в Open World) | |
ОСТАНОВКА МАКРОСА: Ctrl + Numpad Del | |
ВЫХОД ИЗ ПРОГРАММЫ: Ctrl + Alt + Numpad 0 | |
*/ | |
if !ToF_CheckActive() or !ToF_CheckOpenWorldStartup("AutoFarmOpenWorldPassive") | |
return false | |
ToF_Gui.Flow.Value := "Preparing Macro_AutoFarmOpenWorldPassive..." | |
static Fight_CD := 5 ; Сколько секунд считать что мы в битве после того как битва была задетекчекена. | |
static AbsoluteZeroE_CD := 60 ; Откат скилла Абсолют Зеро | |
static RelicMagneticPulseQ_CD := 35 ; Откат релика магнетик бомба | |
static RelicHologramProjectorR_CD := 180 ; Откат релика голограма | |
; Момнты времени, в которые произойдут откаты скиллов и реликов. | |
StampDummy := Sys_TimerStampAddSec(Sys_TimerStampGet(), -1) | |
AbsoluteZeroE_Next := StampDummy | |
RelicMagneticPulseQ_Next := StampDummy | |
RelicHologramProjectorR_Next := StampDummy | |
; Блокировки от частого переклучения оружий. | |
WeaponNextSwitch := StampDummy | |
WeaponCurrentSlot := -1 | |
; Детект режима боя и %хп. | |
; Хпбар врага может мигать, по этому бой длится еще не менее Fight_CD секунд. | |
FightLast := Sys_TimerStampAddSec(Sys_TimerStampGet(), -Fight_CD) | |
SmoothHp := ToF_GetApproxHP() | |
Loop { | |
ToF_Gui.Loop.Value := Format("Loop: {}", A_Index) | |
ToF_Gui.Flow.Value := ToF_Gui.Loop.Value | |
if !ToF_IsOpenWorld() { | |
ToF_Gui.Flow.Value := "Not in Open World, awaiting..." | |
Sleep 500 | |
continue | |
} | |
StampNow := Sys_TimerStampGet() | |
FightNow := ToF_IsInFightWithMiniBoss() | |
FightLast := FightNow ? StampNow : FightLast | |
FightSmooth := Sys_TimerStampDiffSec(StampNow, FightLast) < Fight_CD | |
AbsoluteZeroE_T := Sys_TimerStampDiffSec(AbsoluteZeroE_Next, StampNow) | |
RelicMagneticPulseQ_T := Sys_TimerStampDiffSec(RelicMagneticPulseQ_Next, StampNow) | |
RelicHologramProjectorR_T := Sys_TimerStampDiffSec(RelicHologramProjectorR_Next, StampNow) | |
WeaponSwitch_T := Sys_TimerStampDiffSec(WeaponNextSwitch, StampNow) | |
RoughHp := ToF_GetApproxHP() | |
SmoothHp := SmoothHp * 0.8 + RoughHp * 0.2 | |
ToF_Gui.Status1.Value := Format("Fight Now: {}, Fight Smooth: {}", FightNow, FightSmooth) | |
ToF_Gui.Status2.Value := Format("Current Weapon: {}, Weapon Time: {:0.1f}s", WeaponCurrentSlot, WeaponSwitch_T) | |
ToF_Gui.Status3.Value := Format("AbsZeroE: {:0.1f}s, MagPulseQ: {:0.1f}s, HoloProjR: {:0.1f}s", AbsoluteZeroE_T, RelicMagneticPulseQ_T, RelicHologramProjectorR_T) | |
ToF_Gui.Status4.Value := Format("Rough HP: {:0.1f}%, Smooth HP: {:0.1f}%", RoughHp * 100, SmoothHp * 100) | |
; Абсолют зеро если не полное хп. | |
ShouldHeal := SmoothHp < (FightSmooth ? 0.4 : 0.8) | |
CanHeal := AbsoluteZeroE_T < 0 and ToF_CanSwitchWeapon(&WeaponCurrentSlot, KEY_WEAPON_COCORITTER_ABSOLUTEZERO, &WeaponNextSwitch) and (!FightSmooth or RelicMagneticPulseQ_T < 0) | |
if ShouldHeal and CanHeal { | |
if (FightSmooth) { | |
; Подхил в бою возможен только совместно с реликом магнетик бомб, что бы обезопасить переключение от срыва атаки. | |
ToF_Gui.Flow.Value := "Activating Magnetic Pulse for Absolute Zero..." | |
Macro_Helper_ActivateMagneticPulse(&RelicMagneticPulseQ_Next, RelicMagneticPulseQ_CD, KEY_RELIC_MAGNETIC_PULSE) | |
Sleep 50 | |
} | |
ToF_Gui.Flow.Value := "Activating Absolute Zero!" | |
ToF_TrySwitchWeapon(&WeaponCurrentSlot, KEY_WEAPON_COCORITTER_ABSOLUTEZERO, &WeaponNextSwitch, 50) | |
; Малое ожидание после переключения, т.к. дальнейший код устойчив на дисчардж. | |
ToF_ActivateWeaponSkill("Absolute Zero", KEY_WEAPON_SKILL) | |
AbsoluteZeroE_Next := Sys_TimerStampAddSec(Sys_TimerStampGet(), AbsoluteZeroE_CD) | |
continue | |
} | |
; Голограм прожектор | |
if FightSmooth and (RelicHologramProjectorR_T < 0) and (RelicMagneticPulseQ_T < 0) { | |
; Включаем голограмму, но что бы ее не сорвали сначала включаем мину-антиконтроль | |
ToF_Gui.Flow.Value := "Activating Magnetic Pulse for Hologram Projector..." | |
Macro_Helper_ActivateMagneticPulse(&RelicMagneticPulseQ_Next, RelicMagneticPulseQ_CD, KEY_RELIC_MAGNETIC_PULSE) | |
ToF_Gui.Flow.Value := "Activating Hologram Projector..." | |
Sleep 100 | |
Macro_Helper_ActivateHologramProjector(&RelicHologramProjectorR_Next, RelicHologramProjectorR_CD, KEY_RELIC_HOLOGRAM_PROJECTOR) | |
continue | |
} | |
ToF_Gui.Flow.Value := "Activating Hammer!" | |
if ToF_TrySwitchWeapon(&WeaponCurrentSlot, KEY_WEAPON_ENE_PUMMELER, &WeaponNextSwitch, 200) { | |
; Малое ожидание после переключения, т.к. дальнейший код устойчив на дисчардж. | |
if FightSmooth { | |
ToF_Gui.Flow.Value := "Hammer active slow attack!" | |
ToF_Check() | |
Send "{LButton down}" | |
Sleep 3150 | |
Send "{LButton up}" | |
Sleep 50 | |
} else { | |
ToF_Gui.Flow.Value := "Hammer passive fast attack!" | |
ToF_Check() | |
Send "{LButton down}" | |
Sleep 500 | |
Send "{LButton up}" | |
Sleep 100 | |
} | |
} else { | |
ToF_Gui.Flow.Value := "Awaiting for hammer!" | |
Sleep 100 | |
} | |
} | |
} | |
Macro_AutoFarmOpenWorldActive() | |
{ | |
static KEY_WEAPON_NEMESIS_VENUS := "1" | |
static KEY_WEAPON_KING_SCYTHE := "2" | |
static KEY_WEAPON_COCORITTER_ABSOLUTEZERO := "3" | |
static KEY_WEAPON_SKILL := "e" | |
static KEY_RELIC_MAGNETIC_PULSE := "q" | |
static KEY_RELIC_HOLOGRAM_PROJECTOR := "r" | |
static I_DONT_OWN_VENUS := 0 | |
/* | |
Макрос для соло AFK-авто-фарма мобов дальнего боя или активно перемещающихся. | |
Может также атаковать группу мобов. | |
НЕОБХОДИМАЯ КОНФИГУРАЦИЯ | |
Кнопка "1" - Nemesis Venus, 1-звезда или выше (Нужно, что бы скилл ставил электрод.) | |
Кнопка "2" - KING Scythe of the Crow | |
Кнопка "3" - Cocoritter Absolute Zero (или Pepper Staff of the Scars) | |
Кнопка "E" - должна быть забинжена на скилл оружия. | |
Кнопка "Q" - Relic Magnetic Pulse (Желательно C2+, можно получить куски с Barbarossa) | |
Кнопка "R" - Relic Hologram Projector (Можно получить бесплатно за исследование Warren) | |
Можно перописать свои бинды отредактировав константы чуть выше. | |
КАК РАБОТАЕТ | |
- Макрос в АФК-режиме машет косой без комбо шагая на месте вперед-назад и переустанавливая электрод скиллом. | |
Электрод бьет далеко и агрит врага, если тот стоит спиной, | |
вдалеке или бродит в округе, подхиливает, а также помогает во время боя. | |
- Когда враг попадает под АФК-атаку начинается бой, что регистрируется макросом по мини хп-бару в левой верхней части экрана. | |
- В бою автоматически активируются релики на вспомогательный дамаг: | |
- сначала активируется Magnetic Pulse для защиты от контроля на время анимации активации Hologram Projector. | |
- затем активируется Hologram Projector, который не плохо так ускоряет взлом щита врага, даже без конст. | |
- Не рекомендуется ставить другие релики, т.к. в макрос вшиты откаты именно этих реликов, да бы прожимать их постоянно по кд. | |
- В бою коса на постоянку прокликивает обычниые авто-атаки, т.к. коса постоянно гонится за врагом | |
и на последних итерациях станит врага и хорошо шаттерит. | |
- Если хп в бою меньше 40% или в афк меньше 80%, то включается скилл хилл-жезла. При этом хилл в бою | |
активируется только используя (доступный) Magnetic Pulse, это защищает от срыва анимации переключения оружия | |
и анимации скилла хилл-жезлов. Но в АФК-режиме релик не используется. | |
ДОСТОИНТСВА И НЕДОСТАТКИ | |
- В отличие от Macro_AutoFarmOpenWorldPassive, этот макрос не сохраняет стационарность вообще | |
и с помощью косы постоянно гонится за врагами. | |
- Макрос не пригоден, если вокруг цели близко находятся другие враги - | |
коса легко может переключиться на других врагов и воевать не туда. | |
- Есть шанс, что в битве враг отступит и умрет далеко от места респавна, тогда вы будете АФКшить далеко от врага | |
и цикл фарма встанет. К сожалению, эту проблему сложно решить. Следует переодически проверять работу макроса, но | |
по личному опыту могу сказать, что отлет происходит не часто, если правильно встать в начале. | |
- Если у тебя нету Nemesis Venus, то найди чуть выше в коде I_DONT_OWN_VENUS и поменяй цифру 0 на 1 | |
и перезагрузи макрос. В таком случае Nemesis Venus не будет прожиматься, а в её слот можно положить | |
любое оружие для подходящего Weapon Resonance (Fortitude, Attack, Benediction) | |
- Макрос не понимает когда у него есть ульта, а когда нету, также не понимает успешно ли переключилось оружие, | |
по этому при переключении макро паузится и может рассинхраниваться, но пушки часто переключаются и обычно чинятся. | |
- Если у тебя мало ХП, то вокруг экрана появятся красные эффекты, которые частично перекрывают HUD. | |
Из-за этого макрос не всегда может увидеть нужное на экране и виснет. Пока-что чёткого решения этой проблемы нет. | |
- Неустойчив против посторонних всплывашек: если произойдет дисконнект от сервера или вылезет | |
белая лента (Reward Recovery) в начале нового дня, то макрос не поймет, что происходит и повиснет. | |
ДЛЯ ЧЕГО ПОДХОДИТ ИСПОЛЬЗОВАТЬ | |
Идеально для гринда деталей крафта машин: | |
- Omnium Beast (Носатый шагоход): | |
https://toweroffantasy.info/mounts/omnium-beast-vii | |
Дроп с жиробасов-аберрантов Behemoths, у них слабость к огню. | |
Коса хорошо снимает щит и притягивает игрока к нему. | |
- Voyager (Летающее такси): | |
https://toweroffantasy.info/mounts/voyager | |
Дроп с собачниц из Four Powers. | |
Они постоянно отходят и держат дистанцию от игрока, но коса хорошо притягивает игрока. | |
ЗАПУСК МАКРОСА: Ctrl + Numpad 9 (в Open World) | |
ОСТАНОВКА МАКРОСА: Ctrl + Numpad Del | |
ВЫХОД ИЗ ПРОГРАММЫ: Ctrl + Alt + Numpad 0 | |
*/ | |
if !ToF_CheckActive() or !ToF_CheckOpenWorldStartup("AutoFarmOpenWorldActive") | |
return false | |
ToF_Gui.Flow.Value := "Preparing Macro_AutoFarmOpenWorldActive..." | |
static Fight_CD := 5 ; Сколько секунд считать что мы в битве после того как битва была задетекчекена. | |
static AbsoluteZeroE_CD := 60 ; Откат скилла Абсолют Зеро | |
static ScytheE_CD := 45 ; Откат скилла косы Кинга | |
static VenusE_CD := 25 ; Откат скилла Венуса Немезиды | |
static RelicMagneticPulseQ_CD := 35 ; Откат релика магнетик бомба | |
static RelicHologramProjectorR_CD := 180 ; Откат релика голограма | |
; Момнты времени, в которые произойдут откаты скиллов и реликов. | |
StampDummy := Sys_TimerStampAddSec(Sys_TimerStampGet(), -1) | |
AbsoluteZeroE_Next := StampDummy | |
ScytheE_Next := StampDummy | |
VenusE_Next := StampDummy | |
RelicMagneticPulseQ_Next := StampDummy | |
RelicHologramProjectorR_Next := StampDummy | |
; Блокировки от частого переклучения оружий. | |
WeaponNextSwitch := StampDummy | |
WeaponCurrentSlot := -1 | |
; Детект режима боя и колво хп. | |
; Хпбар врага может мигать, по этому бой длится еще не менее Fight_CD секунд. | |
FightLast := Sys_TimerStampAddSec(Sys_TimerStampGet(), -Fight_CD) | |
SmoothHp := ToF_GetApproxHP() | |
Loop { | |
ToF_Gui.Loop.Value := Format("Loop: {}", A_Index) | |
ToF_Gui.Flow.Value := ToF_Gui.Loop.Value | |
if !ToF_IsOpenWorld() { | |
ToF_Gui.Flow.Value := "Not in Open World, awaiting..." | |
Sleep 500 | |
continue | |
} | |
StampNow := Sys_TimerStampGet() | |
FightNow := ToF_IsInFightWithMiniBoss() | |
FightLast := FightNow ? StampNow : FightLast | |
FightSmooth := Sys_TimerStampDiffSec(StampNow, FightLast) < Fight_CD | |
AbsoluteZeroE_T := Sys_TimerStampDiffSec(AbsoluteZeroE_Next, StampNow) | |
ScytheE_T := Sys_TimerStampDiffSec(ScytheE_Next, StampNow) | |
VenusE_T := Sys_TimerStampDiffSec(VenusE_Next, StampNow) | |
RelicMagneticPulseQ_T := Sys_TimerStampDiffSec(RelicMagneticPulseQ_Next, StampNow) | |
RelicHologramProjectorR_T := Sys_TimerStampDiffSec(RelicHologramProjectorR_Next, StampNow) | |
WeaponSwitch_T := Sys_TimerStampDiffSec(WeaponNextSwitch, StampNow) | |
RoughHp := ToF_GetApproxHP() | |
SmoothHp := SmoothHp * 0.8 + RoughHp * 0.2 | |
ToF_Gui.Status1.Value := Format("Fight Now: {:0.1f}, Fight Smooth: {:0.1f}", FightNow, FightSmooth) | |
ToF_Gui.Status2.Value := Format("Current Weapon: {}, Weapon Switch Time: {:0.1f}s", WeaponCurrentSlot, WeaponSwitch_T) | |
ToF_Gui.Status3.Value := Format("ScytheE: {:0.1f}s, VenusE: {:0.1f}s, AbsZeroE: {:0.1f}s", ScytheE_T, VenusE_T, AbsoluteZeroE_T) | |
ToF_Gui.Status4.Value := Format("MagPulseQ: {:0.1f}s, HoloProjR: {:0.1f}s", RelicMagneticPulseQ_T, RelicHologramProjectorR_T) | |
ToF_Gui.Status5.Value := Format("RoughHP: {:0.1f}%, SmoothHP: {:0.1f}%", RoughHp * 100, SmoothHp * 100) | |
; Абсолют зеро если не полное хп. | |
ShouldHeal := SmoothHp < (FightSmooth ? 0.4 : 0.8) | |
CanHeal := AbsoluteZeroE_T < 0 and ToF_CanSwitchWeapon(&WeaponCurrentSlot, KEY_WEAPON_COCORITTER_ABSOLUTEZERO, &WeaponNextSwitch) and (!FightSmooth or RelicMagneticPulseQ_T < 0) | |
if ShouldHeal and CanHeal { | |
if (FightSmooth) { | |
; Подхил в бою возможен только совместно с реликом магнетик бомб, что бы обезопасить переключение от срыва атаки. | |
ToF_Gui.Flow.Value := "Activating Magnetic Pulse for Absolute Zero..." | |
Macro_Helper_ActivateMagneticPulse(&RelicMagneticPulseQ_Next, RelicMagneticPulseQ_CD, KEY_RELIC_MAGNETIC_PULSE) | |
Sleep 50 | |
} | |
ToF_Gui.Flow.Value := "Activating Absolute Zero!" | |
ToF_TrySwitchWeapon(&WeaponCurrentSlot, KEY_WEAPON_COCORITTER_ABSOLUTEZERO, &WeaponNextSwitch, 50) | |
; Малое ожидание после переключения, т.к. дальнейший код устойчив на дисчардж. | |
ToF_ActivateWeaponSkill("Absolute Zero", KEY_WEAPON_SKILL) | |
AbsoluteZeroE_Next := Sys_TimerStampAddSec(Sys_TimerStampGet(), AbsoluteZeroE_CD) | |
continue | |
} | |
; Голограм прожектор | |
if FightSmooth and (RelicHologramProjectorR_T < 0) and (RelicMagneticPulseQ_T < 0) { | |
; Включаем голограмму, но что бы ее не сорвали сначала включаем мину-антиконтроль | |
ToF_Gui.Flow.Value := "Activating Magnetic Pulse for Hologram Projector..." | |
Macro_Helper_ActivateMagneticPulse(&RelicMagneticPulseQ_Next, RelicMagneticPulseQ_CD, KEY_RELIC_MAGNETIC_PULSE) | |
ToF_Gui.Flow.Value := "Activating Hologram Projector..." | |
Sleep 100 | |
Macro_Helper_ActivateHologramProjector(&RelicHologramProjectorR_Next, RelicHologramProjectorR_CD, KEY_RELIC_HOLOGRAM_PROJECTOR) | |
continue | |
} | |
; Постоянно обновляем электрод | |
if !I_DONT_OWN_VENUS and VenusE_T < 0 and ToF_TrySwitchWeapon(&WeaponCurrentSlot, KEY_WEAPON_NEMESIS_VENUS, &WeaponNextSwitch, 50) { | |
ToF_Gui.Flow.Value := "Activating Venus..." | |
ToF_ActivateWeaponSkill("Venus", KEY_WEAPON_SKILL) | |
VenusE_Next := Sys_TimerStampAddSec(Sys_TimerStampGet(), VenusE_CD) | |
continue | |
} | |
ToF_TrySwitchWeapon(&WeaponCurrentSlot, KEY_WEAPON_KING_SCYTHE, &WeaponNextSwitch, 50) | |
if FightNow { | |
if ScytheE_T < 0 and WeaponCurrentSlot == KEY_WEAPON_KING_SCYTHE { | |
; Активируем скилл косы. | |
ToF_Gui.Flow.Value := "Fight with Scythe skill!" | |
ToF_ActivateWeaponSkill("Scythe", KEY_WEAPON_SKILL) | |
ScytheE_Next := Sys_TimerStampAddSec(Sys_TimerStampGet(), ScytheE_CD) | |
continue | |
} else { | |
ToF_Gui.Flow.Value := "Fight with usual attack!" | |
; Тупо кликаем по врагу текущим оружием. | |
Sleep 100 | |
ToF_Check() | |
Send "{LButton down}" | |
Sleep 100 | |
Send "{LButton up}" | |
Sleep 100 | |
continue | |
} | |
} | |
if WeaponCurrentSlot == KEY_WEAPON_KING_SCYTHE { | |
; AFK-шаги влево-вправо | |
; Работает со всеми оружками цикла: Косой, Венусом, Абсолют | |
; Но с Венусом и Абсолют шаг получается длинее и не стабильнее. | |
Direction := Mod(A_Index, 2) == 0 | |
DirectionButton := Direction ? "a" : "d" | |
ToF_Gui.Flow.Value := Direction ? "AFK to the LEFT!" : "AFK to the RIGHT!" | |
Sleep 720 | |
ToF_Check() | |
Send "{" . DirectionButton . " down}" | |
Sleep 80 | |
Send "{LButton down}" | |
Sleep 80 | |
Send "{LButton up}" | |
Sleep 80 | |
Send "{" . DirectionButton . " up}" | |
Sleep 40 | |
} | |
} | |
} | |
Macro_AutoFarmFrontierClashCycle() { | |
/* | |
Макрос для автоматических Assist-походов в Frontier Clash на сложности Normal | |
для фарма опыта, деталюшек машин, и саппорт-поинтов. | |
НЕОБХОДИМАЯ КОНФИГУРАЦИЯ | |
Никаких специальный биндов не требуется. Почти всё прожимается мышкой. | |
КАК РАБОТАЕТ | |
- Ты не должен быть в команде изначально. Допускается команда, но 2ого игрока не должно быть. | |
- При запуске макроса будет всплывашка, которая спросит, сколько циклов надо сделать. По-умолчанию 100 (много). | |
- Кнопка с Frontier Clash ездит в менюшке Adventure/Challange по разным столбцам, | |
по этому макрос ее ищет по некоторым признакам (распознование текста в AHK еще не завезли). | |
Если кнопка не найдена, то макрос начнёт дрыгать столбики с режимами игры туда-сюда, пытаясь найти | |
столбик с Frontier Clash. Макрос должен найти даже затененную кнопку, когда у тебя 0/3 походок. | |
Также икноки на конопках меняются, в зависимости от доступных наград в зависимости от уровня. Макрос сделан | |
из расчёта на работу на 50+ уровнях, на уровнях ниже возможно работать не будет из-за других иконок. | |
Вообще поиск кнопки Frontier Clash на экране - самое сложное в этом макросе и я охуел его делать. | |
- Затем макрос заходит в меню Frontier Clash и запускает матч-мейкинг, подтверждая его с галочкой "Assist". | |
Макрос пытается проверить и подтвердить наличие галочки в квадратике "Assist", | |
по этому может совершать "ритуалы" с двиганьем мышкой рядом. Это необходимо по тех. причинам. | |
- Макрос ждет фактического начала битвы, т.е. кто-то должен запустить арену и ударить первого моба. | |
- Если все АФК, то вылезет экран Challange Failed, макрос его обнаружит и выйдет с инстанса. | |
- В начале битвы макрос нажимает кнопку автобоя (справа внизу над хп-баром) и далее ToF играет сам в себя. | |
Если макрос заметит, что автобой отключен, то он попытается его включить. Макрос всегда пытается его включить. | |
- Если по каким-то причинам ты умерешь, макрос должен увидеть экран смерти и начать прожимать место | |
кнопки респавна в углу. Может нажаться кнопка "Spectate", но это ничего страшного. | |
- В конце должен вылести экран с 5ю сундуками, макрос его тоже должен | |
обнаружить и прокликать все другие экраны, что бы выйти с инстанса. | |
- При выходе в открытый мир, макрос спит 5 секунд и терпит возможные подлаги и запускает цикл сначала. | |
ДОСТОИНТСВА И НЕДОСТАТКИ | |
- Полный АФК, не требующий никакого контроля и вмешательства. | |
- Почти всё управляется мышкой, по этому не стоит мешать с макросу, когда тот пытается двигать мышь. | |
- Если у тебя мало ХП, то вокруг экрана появятся красные эффекты, которые частично перекрывают HUD. | |
Из-за этого макрос не всегда может увидеть нужное на экране и виснет. Пока-что чёткого решения этой проблемы нет. | |
Как правило макрос нормално отрабатывает цикл, но не включает авто-файт на арене. | |
Ты либо простоишь всю битву, либо отлагаешь когда кто-то тебя подхилит и эффект пропадет. | |
Рекомендуется брать с собой хилл-пушки и включать Trait Cocoritter, что бы всегда иметь 20%+ хп. | |
- Необходим не-АФК игрок в пачке, который запустит игру. Переодически в авто-подборе случайно попадаются | |
все игроки-АФКшники, из которых никто не включает арену и катка завершается. | |
- Тяжелые мобы вроде Хабаки могут прокинуть тебя через пол арены и ты будешь плавать в воде, а автобой встанет. | |
Это исправляется в Settings -> Unstuck, но это никак не обнаружить со стороны макроса, | |
по этому ты просто будешь висеть там пока не кончится арена. | |
- Неустойчив против посторонних всплывашек: если произойдет дисконнект от сервера или вылезет | |
белая лента (Reward Recovery) в начале нового дня, то макрос не поймет, что происходит и повиснет. | |
ЗАПУСК МАКРОСА: Ctrl + Numpad 1 (в Open World) | |
ОСТАНОВКА МАКРОСА: Ctrl + Numpad Del | |
ВЫХОД ИЗ ПРОГРАММЫ: Ctrl + Alt + Numpad 0 | |
*/ | |
ToF_ClickSwordsAdventure() | |
ToF_AwaitClickAdventureChallange() | |
Loop { | |
static UpperIdlePoint := [0.55, 0.1] | |
ToF_MouseMove(UpperIdlePoint) | |
if ToF_FindWhereFrontierClashAndPress() | |
break | |
ToF_DragAdventureModes(A_Index) | |
} | |
ToF_ClickFrontierClashDiffNormal() | |
ToF_ClickFrontierClashInstanceGo() | |
ToF_ClickDialogRight("Match") ; Матчмейкинг | |
; ToF_ClickDialogLeft("Join") ; Только пачка в мире | |
; Подтверждать найденый подбор | |
LoopBegin := Sys_TimerStampGet() | |
Loop { | |
LoopTime := Sys_TimerStampDiffSec(Sys_TimerStampGet(), LoopBegin) | |
ToF_Gui.Flow.Value := Format("Frontier Clash matching Loop... {} {:0.1f}s", A_Index, LoopTime) | |
Sleep 200 | |
if ToF_IsOpenWorld() { | |
ToF_Gui.Flow.Value := "Open World Detected!" | |
return | |
} | |
Sleep 200 | |
if ToF_IsLoading() { | |
ToF_Gui.Flow.Value := "Loading..." | |
break | |
} | |
Sleep 200 | |
if ToF_IsSomeoneFightingInInstance() { | |
ToF_Gui.Flow.Value := "Fighting detected! Are we already in the instance..?" | |
Sleep 500 | |
break | |
} | |
Sleep 200 | |
if ToF_IsMatchApproveConfirmScreen() { | |
Sleep 40 | |
if ToF_TryAssistMatchApproveConfirm() { | |
Sleep 40 | |
ToF_ClickMatchApproveConfirm() | |
} | |
} | |
} | |
; Ждем окончания загрузочного экрана, если он есть. | |
; Это необходимо, что бы иметь возможность нормально ломать цикл боя. | |
; Иначе бой будет ломаться до завершения загрузки входа в инстанс | |
ToF_Gui.Flow.Value := "Awaiting loading end (Open World -> Frontier Clash)... " | |
ToF_AwaitLoadingEnd() | |
AutoFightCounter := 0 | |
FightLoopStart := Sys_TimerStampGet() | |
Loop { | |
FightLoopTime := Util_SecondsFormat(Sys_TimerStampDiffSec(Sys_TimerStampGet(), FightLoopStart)) | |
ToF_Gui.Flow.Value := Format("Frontier Clash fight loop... №{} {}", A_Index, FightLoopTime) | |
ToF_Gui.Status1.Value := Format("AutoFightCounter:{}", AutoFightCounter) | |
; Опрашиваем чаще, если обнаружен отключеный автофайт. | |
CheckDelay := (AutoFightCounter > 0) ? 50 : 200 | |
Sleep CheckDelay | |
if ToF_IsOpenWorld() { | |
ToF_Gui.Flow.Value := "Open World Detected!" | |
break | |
} | |
Sleep CheckDelay | |
if ToF_IsLoading() { | |
ToF_Gui.Flow.Value := "Loading Screen Detected!" | |
break | |
} | |
Sleep CheckDelay | |
if ToF_IsFrontierClashGrayChestsScreen() { | |
; Нажать Skip | |
ToF_Gui.Flow.Value := "Fighting finished, Skip chests..." | |
ToF_Click([0.500, 0.897]) | |
Sleep 1000 | |
; Закрыть Success | |
ToF_Gui.Flow.Value := "Fighting finished, Skip Success..." | |
ToF_Click([0.100, 0.500]) | |
Sleep 500 | |
; Закрыть Combat Record | |
ToF_Gui.Flow.Value := "Fighting finished, Combat Record..." | |
ToF_Click([0.120, 0.045]) | |
Sleep 500 | |
; Выйти из инстанса | |
ToF_ClickExitInstance() | |
Sleep 500 | |
; Подтвердить, что хотим потратить попытку, хотя мы ее и не тратили | |
; Бессмысленный вопрос. | |
ToF_ClickDialogRight("OK") | |
; Не брейкаем боевой цикл. | |
; Он должен закончиться сам загрузкой или опен-ворлдом | |
;break | |
continue | |
} | |
Sleep CheckDelay | |
if ToF_IsChallengeFailedScreen() { | |
; Закрыть Challenge Failed | |
ToF_Gui.Flow.Value := "Fighting finished, Skip Challenge Failed..." | |
ToF_Click([0.100, 0.500]) | |
Sleep 1000 | |
; Закрыть Combat Record | |
ToF_Gui.Flow.Value := "Fighting finished, Combat Record..." | |
ToF_Click([0.120, 0.045]) | |
Sleep 500 | |
; Выйти из инстанса | |
ToF_ClickExitInstance() | |
Sleep 500 | |
; Подтвердить, что хотим потратить попытку, хотя мы ее и не тратили | |
; Бессмысленный вопрос. | |
ToF_ClickDialogRight("OK") | |
; Не брейкаем боевой цикл. | |
; Он должен закончиться сам загрузкой или опен-ворлдом | |
;break | |
continue | |
} | |
Sleep CheckDelay | |
if ToF_IsYouFailedDeadScreen() { | |
; Реснуться | |
ToF_Gui.Flow.Value := "Dead in fighting, pressing Resurrect..." | |
ToF_Click([0.831, 0.831]) | |
Sleep CheckDelay | |
continue | |
} | |
Sleep CheckDelay | |
ToF_TryKeepAutoFightEnabled(&AutoFightCounter) | |
} | |
ToF_Gui.Flow.Value := "Awaiting loading end (Frontier Clash -> Open World)... " | |
ToF_AwaitLoadingEnd() | |
ToF_AwaitOpenWorld() | |
ToF_Gui.Flow.Value := Format("Frontier Clash loop {} done.", A_Index) | |
} | |
Macro_AutoFarmFrontierClash() { | |
if !ToF_CheckActive() or !ToF_CheckOpenWorldStartup("AutoFarmFrontierClash") | |
return false | |
global ToF_ShouldStop := false | |
static PromptMsg := "Enter number of Frontier Clash cycles to do:`n`n" | |
static PromptMsg .= "Введи сколько циклов Frontier Clash нужно выполнить:`n" | |
static PromptMsg .= "Через 30 сек окно автоматически закроется и запустит процесс." | |
Prompt := InputBox(PromptMsg, "Frontier Clash", "T30", 100) | |
if Prompt.Result == "Cancel" or !IsInteger(Prompt.Value) | |
return | |
LoopsN := Prompt.Value * 1 | |
Sleep 50 | |
ToF_Gui.Window.Show() | |
Sleep 50 | |
WinActivate ToF_Window | |
Sleep 50 | |
Time_Start := Sys_TimerStampGet() | |
Loop LoopsN { | |
Time_Spent := Util_SecondsFormat(Sys_TimerStampDiffSec(Sys_TimerStampGet(), Time_Start)) | |
ToF_Gui.Loop.Value := Format("Loop {}/{} {}", A_Index, LoopsN, Time_Spent) | |
Macro_AutoFarmFrontierClashCycle() | |
ToF_Gui.Flow.Value := "Awaiting open world lags is over for 3sec..." | |
Sleep 3000 | |
} | |
ToF_Gui.Flow.Value := "Frontier Clash Finished." | |
} | |
Macro_AutoFarmSupportWeaponDrillCycle() { | |
ToF_ClickSwordsAdventure() | |
ToF_ClickAdventureSelect() | |
ToF_ClickDimTrials() | |
ToF_ClickDimTrialsWeaponDrill() | |
ToF_ClickDimTrialsDiff1() | |
ToF_ClickDimTrialsInstanceGo() | |
ToF_ClickDialogLeft("Continue no VIT") | |
ToF_ClickDialogRight("Match") | |
; Подтверждать найденый подбор пока не началась загрузка | |
Loop { | |
ToF_Gui.Flow.Value := Format("Dimensional Trials matching loop... {}", A_Index) | |
Sleep 300 | |
if ToF_IsOpenWorld() { | |
ToF_Gui.Flow.Value := "Open World Detected!" | |
return | |
} | |
Sleep 50 | |
if ToF_IsLoading() { | |
ToF_Gui.Flow.Value := "Loading..." | |
break | |
} | |
Sleep 50 | |
if ToF_IsSomeoneFightingInInstance() { | |
ToF_Gui.Flow.Value := "Fighting detected! Are we already in the instance..?" | |
Sleep 500 | |
break | |
} | |
Sleep 50 | |
if ToF_IsMatchApproveConfirmScreen() { | |
Sleep 50 | |
ToF_ClickMatchApproveConfirm() | |
} | |
} | |
; Ждем окончания загрузочного экрана, если он есть. | |
; Это необходимо, что бы иметь возможность нормально ломать цикл боя. | |
; Иначе бой будет ломаться до завершения загрузки входа в инстанс | |
ToF_Gui.Flow.Value := "Awaiting loading end (Open World -> Dimensional Trials)... " | |
ToF_AwaitLoadingEnd() | |
AutoFightCounter := 0 | |
FightLoopStart := Sys_TimerStampGet() | |
Loop { | |
FightLoopTime := Util_SecondsFormat(Sys_TimerStampDiffSec(Sys_TimerStampGet(), FightLoopStart)) | |
ToF_Gui.Flow.Value := Format("Dimensional Trials fight loop... №{} {}", A_Index, FightLoopTime) | |
ToF_Gui.Status1.Value := Format("AutoFightCounter:{}", AutoFightCounter) | |
; Опрашиваем чаще, если обнаружен отключеный автофайт. | |
CheckDelay := (AutoFightCounter > 0) ? 50 : 200 | |
Sleep CheckDelay | |
if ToF_IsOpenWorld() { | |
ToF_Gui.Flow.Value := "Open World Detected!" | |
break | |
} | |
Sleep CheckDelay | |
if ToF_IsLoading() { | |
ToF_Gui.Flow.Value := "Loading Screen Detected!" | |
break | |
} | |
; TODO exit checks | |
Sleep CheckDelay | |
if ToF_IsYouFailedDeadScreen() { | |
; Реснуться | |
ToF_Gui.Flow.Value := "Dead in fighting, pressing Resurrect..." | |
ToF_Click([0.831, 0.831]) | |
Sleep CheckDelay | |
continue | |
} | |
Sleep CheckDelay | |
ToF_TryKeepAutoFightEnabled(&AutoFightCounter) | |
} | |
ToF_Gui.Flow.Value := "Awaiting loading end (Dimensional Trials -> Open World)... " | |
ToF_AwaitLoadingEnd() | |
ToF_AwaitOpenWorld() | |
ToF_Gui.Flow.Value := Format("Dimensional Trials loop {} done.", A_Index) | |
} | |
Macro_AutoFarmSupportWeaponDrill() | |
{ | |
Prompt := InputBox("Number of Weapon Drill cycles", "Number of cycles", "T30", 10) | |
if Prompt.Result == "Cancel" or !IsInteger(Prompt.Value) | |
return | |
LoopsN := Prompt.Value * 1 | |
ToF_Gui.Window.Show() | |
Sleep 100 | |
WinActivate ToF_Window | |
Sleep 100 | |
Loop LoopsN { | |
ToF_Gui.Loop.Value := Format("Loop {}/{}", A_Index, LoopsN) | |
Macro_AutoFarmSupportWeaponDrillCycle() | |
ToF_Gui.Flow.Value := "Awaiting open world lags is over..." | |
Sleep 5000 | |
} | |
ToF_Gui.Flow.Value := "Weapon Drill farm done!" | |
} | |
/* * * * * | |
Бинды макросов | |
*/ | |
~^Numpad9::Macro_AutoFarmOpenWorldActive | |
~^Numpad8::Macro_AutoFarmOpenWorldPassive | |
~^Numpad1::Macro_AutoFarmFrontierClash | |
#MaxThreadsBuffer True | |
~^!Numpad0::ExitApp | |
*~^NumpadDot::ToF_RequestUserStop |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment