Как стать автором
Обновить

Создание графического бота для EVE Online

Время на прочтение34 мин
Количество просмотров18K

В один прекрасный день, стреляя по NPC в космосе, мне стало интересно, а смогу ли я этот рутинный процесс немного автоматизировать.

Выводить в консоль «Привет, мир!» я уже умел.
Теоретическое представление, что нужно делать, так же имелось.
Оставалось дело за малым - реализовать задумку.

В данном начинании, очень подсобила статья на Хабре. (Советую сначала ознакомиться с ней, а уже после вернуться сюда.)

Программу я писал на C# в WinForm, следовательно, и вставки кода будут на нем.
Упор в статье я сделал на визуальную часть, что бы даже «не программист» смог получить представление «что, куда и почему».

Изначальная цель была простой - определять, что дронов в космосе атакуют и собирать их в ангар, после чего выкидывать обратно в космос.

окно EVE Online
окно EVE Online

В EVE Online у всех окон независимо от размера есть некоторые неизменные элементы, на основе которых и получится определить тип окна. 

У дронов есть окно, в котором вы видите:

1) Дроны в корабле (ангар).
2) Дроны в космосе.
3) Значение их HP (структура, броня, щит).

окно дронов
окно дронов

 План будет следующий:

1) Программа должна узнать, что открыто именно окно EVE Online.
2) Нужно найти на экране окно дронов.
3) Проверить, есть ли в космосе дроны.
4) Если дроны в космосе, то нужно проверить их HP, если дроны в ангаре, то выкинуть их в космос.

Дроны

Пункт №1 - программа должна как-то узнать, что открыто именно окно игры eve online

 Для этого понадобится создать класс с импортированием "dll". В данном случае это будет User32.dll.

Описание из гугла - файл библиотеки динамической компоновки. В этом файле Windows хранит инструкции для графических элементов, таких как диалоговые окна.

С помощью него мы будем проверять имя активного окна игры – «exefile».

Важно, что «.exe» отображаемое в диспетчере задач, в коде не используется. Возвращается только имя процесса.

static class Target_wind
    {
        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();  //окно находящееся в фокусе

        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);   //подключение к процессам

        public static bool FocusWindow()   //узнаем имя процесса
        {
            IntPtr hWnd = GetForegroundWindow();    // получаем хендел активного окна
            int pid;    //получаем pid потока активного окна
            GetWindowThreadProcessId(hWnd, out pid);    // узнаем PID процесса

            Process p = Process.GetProcessById(pid);
            string name = String.Format(p.ProcessName);

            if (name == "exefile")
                return true;
            else
                return false;
        }
    }

В нашей программе при запуске определения окна:
1) Ждем 5 секунд (время с запасом, чтобы кликнуть на окно игры).
2) Проверяем имя активного окна.
3) Запускаем логику бота в отдельном потоке.

Thread.Sleep(5000);
if (Target_wind.FocusWindow() == true) //если фокус на игре
{
	Thread potok = new Thread(thread_pve);  //указываем метод, который хотим выполнить
	potok.IsBackground = true;  //делаем поток фоновым – при закрытии основного потока, этот поток тоже будет убит
	potok.Start();  //запускаем поток
}

Пункт №2 - Нужно найти на экране окно дронов

На данном этапе нам понадобится библиотека Drawing.

using System.Drawing;

Далее нужно узнать разрешение экрана и на его основе создать Bitmap, в который будем сохранять снимок экрана. Игру запускаем в оконном режиме, так как в полноэкранном может не работать управление.

Size resolution = Screen.PrimaryScreen.Bounds.Size; //получаем разрешение экрана
Bitmap window_sc = new Bitmap(resolution.Width, resolution.Height); // создаём Bitmap

Чтобы сделать снимок экрана, понадобится переменная типа Graphics.

Graphics GH = Graphics.FromImage(window_sc as Image);   //объявляем изображение
GH.CopyFromScreen(0, 0, 0, 0, window_sc.Size);  //1,2,3,4 цифры - отступы в пикселях, 5 - размер изображения

Таким образом получаем изображение всего экрана. Брать изображение из определенного окна бот не умеет. Далее мы будем вручную делать скришоты окна игры (я использовал Lightshot) и измерять расстояния между пикселями и их разницу в RGB и HCV в графическом редакторе (я использовал Paint.NET).

Подмечу, что в EVE Online можно менять цвет интерфейса, и на большей части цветов программа корректно работала. Но все же была выбрана черная цветовая гамма «Темная материя», так как в ней цвета без всяких «примесей».

Так же ползунок прозрачности и «размытие под окнами» отключены, иначе мы будем получать смешанные цвета.

 Для наглядности:

Видите разницу между серыми уголками? Она есть и значительная.

прозрачное окно / не прозрачное окно
прозрачное окно / не прозрачное окно

1) Теперь нам нужно найти начало окна дронов (верхний левый угол).
2) Так же нам будет полезно знать местоположение нашего курсора на изображении для отсчетов (в пикселях X, Y).

Для поиска пикселей нам понадобится переменная типа Color (содержит цвет пикселя в ARGB) и команда GetPixel(X, Y) для получения пикселя с нашего снимка экрана.

Color color = window_sc.GetPixel(i, j);

Искать начальную точку окна, будем перебором всех пикселей на экране с несколькими проверками. Изначально ищем полосу одного цвета:

1) На одной линии через один пиксель цвета совпадают.
2) В одном столбце через один пиксель цвета совпадают.
3) Первый пиксель не совпадает по цвету с пикселями ниже на 1,3 и 5.
4) Далее от нашей первой точки переходим к проверке уголка по X-6 и Y-7.
5) На одной линии три пикселя находящихся друг за другом совпадают.
6) В одном столбце три пикселя находящихся друг под другом совпадают.
7) Первый пиксель не совпадает по цвету с пикселями по: (+3, 0),(0, +3),(+1, +1).

Если проверки прошли успешно, значит мы нашли панельку дронов. Теперь нужно узнать ее размер. Для этого просто пойдем перебором в право и в низ начиная с пятой клетки. Будем двигаться до тех пор, пока цвет линии не изменится (цвет линии между нашими серыми уголками не меняется).

Пример нижнего левого угла:
1) Когда увидим, что цвет сменился, будем проверять совпадение цвета с верхним уголком, и идти далее, пока этот цвет не пропадет (красный пиксель – точка, где цвет перестал совпадать с верхним углом).
2) Далее по аналогии те же самые проверки, что мы точно нашли уголок (проверяем совпадение центрового и крайних пикселей).
3) Проверяем совпадение ближних пикселей.
4) Проверяем, что центральный пиксель не совпадает с тремя другими (0, -3), (+3, 0), (+1, +1).

Аналогично находим правый верхний угол. А зная уже координаты трех углов, вычисляем координаты нижнего правого угла (взяв значение X от правого верхнего угла и значение Y от левого нижнего).

На данном стало понятно, что перед запуском бота, придется делать отдельный скан экрана на наличие вкладок в окне дронов и указывать, какую будем использовать (в игре горы разных типов дронов, для чего их и разбивают по вкладкам для удобства использования).
Так как при фарме NPC используется обычно какой то конкретный сетап (под конкретный регион, аномальку), то нам нужно найти и запомнить нужную папку, из которой будем этих дронов доставать.

Координаты верхнего левого угла мы знаем, от него и будем вести поиск вкладок. Сама «шапка» вкладок свой размер и положение относительно угла не меняет, благодаря чему можно просто посчитать в паинте нужные отступы.

Выходит, с учетом начального пикселя, расстояние до низа «шапки» 47 пикселей. Переходим на (+5, +47), запоминаем цвет пикселя и идем в низ, пока не встретим другой цвет. Это будет «шапка» дронов в космосе. Запомним ее высоту – дальше пригодится.

Возвращаемся к дронам в ангаре. Перейдя в низ шапки «дроны в отсеке», мы попадаем в рабочую область вкладок. Я разбил область на такие показатели:

Что делаем:

1) Создаем список Bitmap, чтобы хранить изображения вкладок с названиями.

List<Bitmap> bit_dron_list = new List<Bitmap>();

2) перемещаюсь к первой вкладке (с проверкой, не оказались ли мы ниже вкладки дронов в космосе) так же с отсчетом от левого верхнего треугольника на (+29, +53).
3) Создаем для нашей вкладки новый Bitmap:

bit_dron_list.Add(new Bitmap(100, 12));

И перерисовываем в него изображения:

color = window_sc.GetPixel(i, j);
bit_dron_list[bit_dron_list.Count - 1].SetPixel(dron_i, dron_j, color);

4) После первой вкладки будем всегда опускаться на 23 пикселя от верхней левой границы  перерисованной вкладки (зеленый прямоугольник).
5) Повторять, пока не пересечем «шапку» дронов в космосе.

Теперь все это великолепие нужно вывести на экран. Для этого нам нужен picturebox. А также две кнопки (для сканирования экрана и перелистывание списка скриншотов с вкладками дронов), один Label для отображения количества скриншотов в списке. И еще picturebox/кнопка/чекбокс (на свой вкус) для подтверждения выбора вкладки.

Выглядеть будет так.

Отмечу, что данная проверка не гарантирует 100% нахождения окна дронов с первого раза, временами рандомные пиксели в космосе могут успешно пройти проверку и попасть к вам.

Пункт №3 - Проверить, есть ли в космосе дроны

Зная положение шапки «дроны в космосе» и правой границы окна, мы могли бы опуститься от верха шапки на 21 пиксель и оказаться у первого показателя HP. Не забываем сделать новый снимок экрана, для актуализации информации на экране.

Но есть проблема – шапка может изменить свою высоту, если вкладок дронов на борту станет больше/меньше.

Поэтому вести поиск будем снизу-вверх от правого нижнего угла:

  • переходим влево от правой границы на 8 пикселей, так как на этом расстоянии будет находиться последний пиксель на шкале показателей HP дрона

  • переходим вверх на 4 пикселя от нижней границы, чтобы оказаться в нужной нам области показателей HP

Далее идем вверх, пока нам не попадется пиксель иного цвета или мы не пересечем границу «шапки».

Если нашли пиксель другого цвета:

1) Запоминаем количество серых пикселей идя влево на 80 пикселей – сохраняем в список.
2) Переходим на 7 пикселей вверх и продолжаем идти вверх.

Цвет пикселей полосы хитпоинтов - R: 111, G: 111, B: 111.

Именно этот цвет мы и считываем.

if (color.R == 107 && color.G == 107 && color.B == 107)

В идеале, после нахождения первой полосы HP к остальным мы можем сразу переходить на 21 пиксель вверх. Но если вы используете разные группы (выкинули вручную, например), то часть их программа может не найти, так как расстояние между дронами в одной группе 21 пиксель, а расстояние между разными вкладками 23 пикселя.

Пункт №4 - Если дроны в космосе, то нужно проверить их HP, если дроны в ангаре, то выкинуть их

Если дроны в космосе

Предположим, что дронов в космосе мы нашли, значит нужно проверять их HP.
Проверять будем с паузой в 1 секунду. Проверка HP та же самая, мы просто считаем количество серых пикселей в строке. Сравниваем их с прошлым значением и если серых пикселей у кого-то стало меньше, то нужно собрать дронов в ангар.

Собирать дронов мы будем с помощью горячих клавиш и команды SendKeys.SendWait.

        private void Dron_kosmos_ships()   //возвращение дронов
        {
            try
            { 
                SendKeys.SendWait("{r}");   //сейвим дронов (нажатие кнопки)
                Random rand = new Random();
                Thread.Sleep(rand.Next(50, 100));       //время на отклик приложения     
                SendKeys.SendWait("{r}");   //(отпускание кнопки)
                Thread.Sleep(50);
            }
            catch { }
        }

Разберем, как это работает.

Команда try/catch нужны для перестраховки, так как пару раз бот не мог отправить нажатую кнопку в активное окно и крашился.

Далее используем команду:

SendKeys.SendWait("{r}"); 

- SendWait ждет отклика от приложения, поэтому оно предпочтительней, чем Send.
- SendWait в коде используется два раза, а зачем?

Дело в том, как видят нажатые клавиши другие она.

Если речь идет об окне ввода (окно чата в браузере, чат в любой игре - активированный), то туда будут отправлены те клавиши, которые мы прописали.
Пример: если мы напишем:

SendKeys.SendWait("{Hello}");

То и в окне чата появиться слово Hello.

Но вне чатов, логика считывания клавиш немного меняется – по мнению другого окна вы кнопку удерживаете. Поэтому нам и приходится отправлять нажатие одной кнопки дважды, что бы игра засчитала нажатие и отжатие клавиши.

Между нажатием клавиши нужно подождать минимум 50 ms (подобрано опытным путем), иначе второе «нажатие» игра так же может не засчитать. При этом если вы будете что-то писать в окно чата, то там задержки не нужны.

Буду рад, если опытные люди объяснят, как работает данная логика =р

 Так же натыкался на мнения, что нажатие клавиш через обращение к user32.dll работает лучше.

 using System.Runtime.InteropServices;

       [DllImport("user32.dll")]
        static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
 
        private Dron_kosmos_ships ()
        {
            //Нажимаем
            keybd_event(0x01, 0, 0, UIntPtr.Zero);
            //Отпускаем
            keybd_event(0x01, 0, 0x02, UIntPtr.Zero);
        }

Но практическим путем разницы найдено не было. Поэтому для удобства я использовал

SendKeys.SendWait();

Нужно отметить, что должна быть включена ENG раскладка, иначе игра не увидит нажатых клавиш (нужно включать ту раскладку, символы которой вы собираетесь вводить через SendKeys).

Сам метод Dron_kosmos_ships вызываем целых два раза. В EVE Online иногда при сборе дронов, они начинают возвращаться на корабль со своей обычной скоростью, а не на всех парах (МВД).

После всех этих манипуляций, бот снова начинает проверять окно дронов на наличие оных в космосе. До тех пор, пока дронов в космосе не останется.

Если дроны в ангаре

Хорошо, все дроны у нас в ангаре, теперь их нужно выбросить в космос. Тут уже будет сложнее. Горячие клавиши для выброса дронов то же есть. Но по умолчанию выбрасываться будут рандомные дроны из всех вкладок. Это решается назначением приоритетной папки.

Но и здесь есть проблема. Если в папке окажется мало дронов, то будут взяты дроны из других папок.

Например: в основной папке находится два тяжелых дрона, а лимит корабля четыре тяжелых дрона. То в космос будут выброшены два основных тяжелых дрона и еще два тяжелых из других вкладок или три легких/средних дронов – смотря какие есть в наличии.

Данной проблемы мы избежим за счёт использования кликов мышки, так как ранее уже сохранили изображения вкладок с названиями. Снова находим вкладки – опускаемся от верхнего левого угла окна дронов на (+29, +53) и проверяем пиксели в них и в нашем сохранённом списке на соответствие.

Так как названия все уникальные (по крайней мере у меня), то проверял я всего одну полосу пикселей по середине, чтобы определить нужную вкладку.

И здесь получаем два варианта – либо нужная вкладка есть, либо нет.

Логика будет соответствующей, если нужных дронов нет, то выпускаем рандомных.

Для обоих случаев я использовал мышку и радиальное меню в игре, которое появляется при удержании ЛКМ.

Курсор мыши будем плавно перемещать в нужную точку (как это реализовывать, оставлю на ваше усмотрение).

Создаем метод с двумя аргументами – координаты X и Y мыши, где она должна оказаться. Так же получаем нынешние координаты мыши. Передвигаем ее к нашей цели.

private void Mouse_emulation(int targer_x, int targer_y) //передвижение мышки
        {
            int pos_x = Cursor.Position.X;
            int pos_y = Cursor.Position.Y;
            ……………………………………
                //указываем новое положение мыши
            Cursor.Position = new Point(pos_x, pos_y);
        }

А вот для самих кликов снова придется обратиться к user32.dll.

    static class Mouse_Click   //клики мышкой
    {
        [DllImport("user32.dll", SetLastError = true)]
        public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo);
    }

    [Flags]
    public enum MouseEventFlags
    {
        LeftDown = 0x0002,  //нажатие
        LeftUp = 0x0004,    //отпускание
        RightDown = 0x0008, //нажатие
        RightUp = 0x0010,   //отпускание
    }

И теперь:

1) «опускаем» ЛКМ.
2) ждем 1 секунду, что бы появилось радиальное меню.
3) сдвигаем курсор по Y на 75 пикселей вверх – тут находится пункт выброса дронов.
4) ждем еще пол секунды (при значении меньше игра может не среагировать на выбранный пункт).
5) «поднимаем» ЛКМ.

Mouse_Click.mouse_event((uint)MouseEventFlags.Absolute | (uint)MouseEventFlags.LeftDown, 0, 0, 0, (int)UIntPtr.Zero);

Thread.Sleep(1000);
Cursor.Position = new Point(Cursor.Position.X, Cursor.Position.Y + 75);

Thread.Sleep(500);
//отпускание ЛКМ
Mouse_Click.mouse_event((uint)MouseEventFlags.Absolute | (uint)MouseEventFlags.LeftUp, 0, 0, 0, (int)UIntPtr.Zero);

После выпуска дронов ждем 4-5 секунд, пока дроны отобразятся (из-за пинга и опять-таки самой игры, дроны могут отобразиться сразу, а могут через 1-4 секунды).

 УРА! ЦЕЛЬ ВЫПОЛНЕНА!

 Выполнена, да с оговорками.
Сейчас будем исправлять два неприятных момента.

№1 меняющий оттенки интерфейс

Если мы кликнем мышкой по дрону, то он выделиться, а из-за этого перестанут срабатывать проверки на цвета пикселей, так как они у нас фиксированные. Можно конечно задавать диапазон значений, но мной было принято решение просто дронов не выделять. Если все-таки выделили, то придется дрона вернуть в ангар и выкинуть обратно (клики по другим окнам выделение не снимут).

Клики по вкладкам дронов их не выделяют на постоянной основе. Как только указатель мыши покинет границы вкладки, она станет обычного цвета.

В данный момент у нас указатель мыши находится где-то в окне дронов и вполне может перегораживать название вкладки и подсвечивать ее. Так что от указателя нужно избавляться. Самый простой вариант, увести его в центр экрана.

Mouse_emulation(resolution.Width / 2, resolution.Height / 2 + 55);   //убираем мышку в центр экрана

Но в центре экрана находится наш корабль и от мышки над ним будет появляться информация. Так что курсор поднимем немного выше на 55 пикселей.

№2 удобное, но не всегда работающее радиальное меню

Как показала практика, радиальное меню любит открываться с большими задержками, чем должно и не засчитывать выбор действий. Из-за чего от его использования пришлось отказываться.

 В игре есть второй тип меню, появляющийся при нажатии ПКМ. Оно работает идеально.

Поэтому делаем два метода, для ЛКМ и ПКМ соответственно:
1) Нажимаем ПКМ.
2) Ждем 100 мс для отрисовки меню .
3) Наводим мышку на пункт "запустить дронов" (+50, +10).
4) Нажимаем ЛКМ.

Mouse_emulation_right();    //ПКМ
Thread.Sleep(100);
Cursor.Position = new Point(i + 50, j + 10);  //ставим курсор на пункт "запустить дронов"
Mouse_emulation_left();

Вот теперь все работает прекрасно :)
Но кому захочется останавливаться на таком? Верно! НИКОМУ!
Следующая этап, научить дронов атаковать определенные цели, а не всех подряд.

ОБЗОРНАЯ ПАНЕЛЬ

Информация об окружающих нас объектах, с которым можно взаимодействовать у нас находятся в обзорной панели. Будем по ней определять тип врага и атаковать приоритетную цель.

Намечаем план:
1) Найти обзорную панель.
2) Найти иконки NPC, запомнить их место в списке и определить нахождение в «таргете».
3) Найти приоритетную цель и атаковать ее.

Пункт №1 - Найти обзорную панель

Начальный поиск панели будет таким же, как у дронов. Нам нужно найти 4 полосы и угол. Способ проверки будет идентичным с маленьким отличием.

Если в панели дронов угол находился на позиции (-6, -7) от края полосы, то у обзорной панели угол находится на позиции (-7, -6). Далее проходом по краям окна, находим левый нижний и верхний правый углы.

Для проверки углов пользуемся так же логикой из панельки дронов.

Взяв значение "Y" от левого нижнего угла и значение "X" от верхнего правого угла, находим правый нижний угол.

Пункт №2 - Найти иконки NPC, запомнить их место в списке и определить нахождение в «таргете»

Создаем в программе ListBox для выбора 3 типов кораблей (маленькие, средние, большие).

Вообще самых распространённых видов кораблей NPC на аномалиях/миссиях 5 штук.
1) Frigates - Фрегаты.
2) Destroyers - Дестроеры.
3) Cruisers - Крейсеры.
4) Battlecruisers - Баттлкрейсеры.
5) Battleships - Баттлшипы.

Но как можно заметить, Destroyers и Battlecruisers, это те же Frigates и Cruisers соответственно, но с полосочкой снизу. Конечно, если учитывать механики игры, то отличия у этих кораблей хватает, особенно в узкоспециализированных комплексах/миссиях.

Но так как наша цель, это фарм аномалек и не сложных миссий, то хватит всего три типа выбора цели. Что же, остается только этих NPC найти на обзорной панели. В обзорке можно делать разные вкладки под разные задачи, что бы отображались только конкретные типы объектов в космосе. Высота вкладок не меняется, если вы конечно не используете специальное редактирование, с помощью которого можно окрашивать текст и менять размер.

Возвращаемся к левому верхнему углу обзорной панели. Отступаем от него на (+4, +65) пикселей, и попадаем в место отрисовки значков объектов в космосе.

Размер ячейки иконки составляет 21 пиксель в ширину и 18 в высоту. Между собой ячейки отделяет 1 пиксель.

Следовательно, чтобы попадать в ячейку следующего объекта, нужно опускаться на 19 пикселей вниз. Определяем цвет - R:206, G:22, B:22.

И тут нас встречает любимое «НО». Так как мы будем кликать по объектам в обзорной панели, то они будут подсвечиваться, следовательно, менять свой цвет.

Избежать этого мы не сможем, поэтому остается два выбора.
- запомнить оба варианта цвета пикселя.
- искать не конкретный цвет, а диапазон.

 Я выбрал второй вариант с диапазоном. Так как красный цвет имеют только вражеские NPC, то можно не бояться, что своим диапазоном мы сочтем за врага кого-то не того.

Будем проверять:
R > 200
G < 40
G == B
Если проверка прошла, значит мы точно попали на вражеского NPC.

 Остается только вычислить, на какое расстояние нужно перемещаться для нахождения пикселя. По "X" во всех случаях мы будем перемещаться на 10 пикселей вправо. А по "Y" у каждого типа свое расстояние +5, +4, +3. Если мы находим нужный пиксель, то далее ищем координаты нижнего красного пикселя, чтобы определить тип корабля.

Хорошо, поиск целей сделан. 

Следующий «космический камень» который нам мешает, это обязанность взять объект в прицел, который хотим атаковать (ранее дроны в агрессивном режиме сами нападали на тех, кто атаковал нас). С определением цели в «таргете» нам везет, так как он отображается на иконке объекта. И хватит проверки всего одного пикселя.

Но самих цветов снова несколько, так как держать в «таргете» мы можем сразу несколько целей. Поэтому цель, на которую мы «смотрим» сейчас выделена белой галочкой, а не серой.

Снова используем проверку на диапазон:
R > 100
R == G
R == B

Так как цвета R, G, B одинаковы, то проверяем диапазон только у R, а остальных сравниваем с ним. Сам пиксель цели в «таргете» всегда находится на (+5, +8) от начального места проверки типа корабля.

Цель выполнена. Мы нашли нужных нам NPC осталось сохранить их типы, место в списке, и кто взят в «таргет».

Пункт №3 - Найти приоритетную цель и атаковать ее

Логика будет такая. Сначала бот смотрит, взят ли кто-то в «таргет». Если такие есть, то основываясь на типах NPC он ищет подходящий тип для атаки.

Если нужной цели нет, то логика будет следующей:
1)      Если цель Frigates, то идем на повышение – Cruisers, а после Battleships.
2)      Если цель Cruisers, то идем на Frigates, а после на Battleships.
3)      Если цель Battleships, то идем на понижение – Cruisers, а после Frigates.

 Количество целей в «таргете» у каждого корабля свое. Можно взять среднюю цифру 6, либо сделать выбор количества целей.

Метод Mouse_emulation для передвижения мышки у нас уже есть. Нажимать ЛКМ тоже умеем Mouse_emulation_left. Наводим мышку на нашу цель, делаем клик. Далее просто нажимаем/отжимаем кнопку атаки дронов

SendKeys.SendWait("{f}");

Готово, дроны летят к нужной цели.

 Теперь приступаем к логике, если в «таргете» никого нет или меньше, чем мы можем удерживать. Данные о NPC у нас уже есть. Остается перед кликом по цели, сначала нажать ctrl. Ctrl обозначается символом ’^’. Подробнее о всех символах здесь: https://docs.microsoft.com/ru-ru/dotnet/api/system.windows.forms.sendkeys?view=windowsdesktop-5.0

        private void Ctrl_dawn()
        {
            try
            {
                Thread.Sleep(50);
                SendKeys.SendWait("^");   //нажимаем ctrl (берем в лок) 
                Random rand = new Random();
                Thread.Sleep(rand.Next(50, 100));       //время на отклик приложения     
            }
            catch { }
        }

Далее повторяем логику выбора целей - наводим мышку на нашу цель, делаем клик. И отпускаем клавишу в том же методе Ctrl_dawn.

 По какому фактору сортировать NPC в самой обзорной панели мы как игрок решаем сами, просто кликая на нужный пункт. Я сортировал по расстоянию до моего корабля. Близкие цели приоритетнее. В таком подходе есть минус – если появятся NPC, которые будут находиться на одинаковом расстоянии от моего корабля (немного обгоняя друг друга), то бот постоянно будет менять цель атаки. Так сказать, будет стрелять в молоко :(

 На то что бы взять цель в «таргет» нужно время (чем больше ваш корабль по отношению к цели, тем медленнее это будет происходить). У цели в момент взятия в «таргет» иконка начинает мигать – поэтому как-то легко определить, что мы уже ее «лочим» не выйдет.

Поэтому бот просто лочит повторно, если цель еще не попала в «таргет». А время между проверками окна обзорной панели я взял 5 секунд.

if (DateTime.Now > target_attack_timer)
{
	target_attack_timer = DateTime.Now.AddSeconds(5);
	…

Очередная цель выполнена! Так что можем приступать к следующей :)

РАЗВЕЗОНДЫ – ОКНО АНОМАЛИЙ

Фармить аномальки мы научились. Но NPC на них имеют свойство заканчиваться. Почему бы не научить бота самому летать по нужным аномалькам. Отображаются они в окне «разведзонды».

План:
1) Найти панель разведзонды.
2) Найти названия аномалек и запомнить их.
3) Определить, куда лететь.
4) Настроить действия при полете/выходе из варпа.

Пункт №1 - Найти панель разведзонды

Одинаковых полос, как у дронов и обзорной панели здесь нет. За что же тогда можно зацепиться для проверки? Будем искать длинную зеленую полосу, которая отображается в списке аномалек.  Цвет по всей длине у нее не меняется. Но так как мне хотелось того, что бы бот работал на других цветах интерфейса, здесь мы будем сравнивать пиксели не по RGB, а по HSV.

А если быть еще точнее, будем проверять только значение оттенка "H". Потому что он не меняется при смене цвета интерфейса. Его мы узнаем при конвертации значения пикселя.

Color color = window_sc.GetPixel(i, j);
Int hsv_h = (int)color.GetHue();  //значение HSV - "H"

И будем сравнивать пиксели в две строки на 20 пикселей право со значением «120».

if (hsv_h == 120)

Полосы нашли. Теперь нужно найти углы (края) окна разведзондов. 

Слева на 4 пикселя от начала зеленой полосы находится граница окна. Переходим на нее и идем вверх и вниз, пока не попадем на пиксели другого цвета.

Логика определения углов не меняется.

 Размер окна определили. А как нам определять нужные аномальки? Оставить только определённый тип можно, за счёт ручного скрытия «не нужных», но от появления новых аномалек другого типа это не спасет. Поэтому будем запоминать, какой тип аномалек нам нужен.

Пункт №2 - Найти названия аномалек и запомнить их

Возвращаемся на начало найденной нами первой зеленой полосы. И от нее поднимаемся на 21 пиксель вверх.

Проблема в том, что столбцы «расстояние», «код» и другие могут иметь разную ширину в зависимости от настроек пользователя (из-за этого мы не можем заранее перейти на нужный пиксель для сохранения изображения).

Найдем нужный нам столбец подсчетом пройденных линий – так как я знаю, что после третьей линии идет название аномальки. Найдя эту линию, попадем в первый пиксель вкладки «название» (+1, +3).

Высота столбца 18 пикселей, а сколько запоминать в ширину решаете сами.

Либо можно найти следующую линию после «Названия», что бы узнать ширину столбца.

Расстояние между аномальками 20 пикселей. Будем их запоминать, пока не дойдем нижней границы окна.

И так же, как было в дронах – выводим найденные названия в программе.

Проверку на соответствие то же будем проводить по середине – этого достаточно.

Не лишним будет сделать проверку на совпадение названий, чтобы удалять повторяющиеся аномальки из списка.

Пункт №3 - Определить, куда лететь

При запуске бота, он будет определять актуальный список аномалий из окна "разведзонды".

Логика будет опять похожа на дронов.
1) Нажимаем ПКМ.
2) Ждем 100мс для отрисовки меню.
3) Наводим мышку на пункт "перейти в варп-режим" (+40, +14).
4) Нажимаем ЛКМ.

Mouse_emulation_right();    //ПКМ
Thread.Sleep(100);
Cursor.Position = new Point(i + 40, j + 14);  //ставим курсор на пункт "запустить дронов"
Mouse_emulation_left();

И немного меняем пункт №3
- наводим мышку на пункт "перейти в варп-режим" (+400, +34 + ??*??).

 Игра дает выбор из 7 дистанций. По "X" мы просто перемещаемся право на 400 пикселей от места нажатия ПКМ. По "Y" будем опускаться на 34 пикселя, а дальше отсчитывать с умножением на выбор в нашем списке.

Расстояние между выборами расстояния - 19 пикселей, а первый элемент в списке считается нулевым. Следовательно, получаем формулу для Y =+ 34 + 19 * (номер выбранного нами элемента).

Важно подметить, что благодаря взаимодействию со строкой, она будет подсвечена – следовательно, при поиске нужной аномальки, она проверку не пройдет и наш корабль не будет пытаться варпать в свою же аномальку.

Но тут же скрывается и проблема. Когда мы взаимодействуем с любым окном, оно то же меняет свой оттенок, показывая визуально, в каком окне мы находимся. Дизайнерское решение хорошее, но не в нашем случае.

Подсвечено/не подсвечено
Подсвечено/не подсвечено

Получается, что бот больше не сможет найти совпадений. Исправить это легко, просто нужно сделать клик вне этого окна. Можно делать клик в центре экрана, куда мы убираем указатель мыши и проблема решена. Или… нет?

Проблема в том, что под указателем мышки может оказаться какой-то объект. Сами по себе клики по нему нам не мешают, но я столкнулся с проблемой, что иногда игра по понятной ей одной причине засчитывала мне двойной клик и, следовательно, брала разгон на этот объект. А это чревато потерей корабля/дронов/времени.

 Исправим в следующем пункте.

Пункт №4 - Настроить действия при полете/выходе из варпа

Мы научились уходить в варп на аномальку. А как бот поймет, где он сейчас – в полете или на аномальке? Для этого нам понадобится найти еще одно окно, а точнее панель корабля.

Найдем на ней минус (остановка корабля), и после плюс (разгон корабля). Цвет всегда белый - R: 255, G: 255, B:255.

 Находим первый белый пиксели, а дальше проверяем:
1) что, следующие 18 пикселей справа тоже белые.
2) что, следующие 4 пикселя снизу тоже белые.
3) что, правый нижний угол минуса тоже белый (+18, +4).

Теперь переходим от первого пикселя минуса на 219 пикселей вправо и проверяем, что пиксель тоже белый.

Проверяем соответствие белых пикселей, делаем проверку на свой вкус.

И проверяем несоответствие.

Далее запоминаем координаты минуса и плюса. Они понадобятся нам в дальнейших улучшениях :) 

Вообще, в данном случае можно было обойтись и без проверки, так как панель корабля всегда находится у нижней границы экрана по центру. Но ее так же, как и другие окна можно перемещать, поэтому лучше находить ее местоположение.

Дальше возвращаемся к первому пикселю минуса и идем от него на (+30, +8), тем самым попав в табло скорости. Проверять скорость будем поиском голубого цвета на протяжении 20 пикселей вниз.

У всех кораблей выход из варпа соответствует одинаковому положению ползунка примерно в этом месте.
У всех кораблей выход из варпа соответствует одинаковому положению ползунка примерно в этом месте.

Цвет пикселей панели не одномерный, поэтому искать будем в разбросе:
70 <R> 100
130 <G> 150
180 <B> 200

if (color.R > 70 && color.R < 100 && color.G > 130 && color.G < 150 && color.B > 180 && color.B < 200) 200)

Если есть совпадение у одного пикселя, значит мы в полете, и бот уходит в сон на 1 секунду, после чего будет повторять проверку. При выходе из варпа корабль сам останавливается.

Теперь можно вернуться к решению проблемы с подсветкой окон в игре. Лучшее место, куда можно кликать не боясь задать кораблю новый маршрут движения – это капаситор. Большой круг с оранжевыми делениями в центре панели. После завершении всех действий (атака, сейв дронов, взятие в таргет, скан аномалек) бот будет делать клик по капаситору. Попадем мы на него от первого пикселя минуса на (+122, +79). После, так же переведя указатель мыши в центр экрана – иначе на капаситоре появится всплывающее меню, которое нам не нужно.

Логика корабля будет следующая:
1) Проверка на нахождение в варпе.
2) Если корабль в варпе, то сон 1 секунда и новая проверка.
3) Если не в полете, то проверяем наличие NPC.
4) Если NPC есть, атакуем.
5) Если NPC нет, то в течении 15 секунд каждую 1 секунду проверяем их наличие.
6) Если спустя 15 секунд NPC не появились, то находим новую аномальку и летим на нее уйдя в сон на 20 секунд (что бы уйти в варп нужно разогнаться, это время мы и будем спать).
7) Если нужной аномальки нет, повторяем действия с первого пункта.

ЗАРЯЖАЙ! ПЛИ! – ВООРУЖЕНИЕ

У нас получился великолепный бот. Но он умеет фармить только на дроновозках :(

Конечно он таким изначально и задумывался, но, этого мне стало уже мало. У кораблей есть пушки, а значит они должны стрелять. Да и активные модули у кораблей есть, неплохо научиться их прожимать.

 Где-то на реализации этого этапа я столкнулся с проблемой, что держать в уме, что я хочу сделать, что уже есть, и в какой последовательности работает, стало сложно. Поэтому я сделал себе блок схему и по мере апгрейдов ее дополнял.

Пример уже полной логики, которую получим в конце статьи.
Пример уже полной логики, которую получим в конце статьи.

Для начала в самой программе сделаем кнопки в виде ячеек, отображающих модули в игре. Можно увидеть, что они отличаются рисункам внутри (верхние, средние и нижние модули).

Также добавил выбор целей для оружия. По умолчанию в Eve online разные типы модулей отображаются на определённых ячейках, но их можно расставлять как удобнее вам. Самих ячеек модулей на экране по 8 штук в каждом ряду. Но такое количество сделано с запасом. Да и активных модулей на кораблях при PVE в среднем имеется 3-5 штук.

У кнопок будет 4 состояния:
1) (серая) выключена.
2) (красная) прожимается при каждой атаке пушками.
3) (желтая)прожимается при каждой атаке дронами.
4) (зеленая) прожимает модуль один раз при включении бота (значимая польза появится немного позже).

В логике атаки было сделано разделение.
Количество целей, которое можно взять в лок (выбрано в программе), делится на 2 и отдается вооружению и дронам (что бы они могли атаковать разные цели). Дронам отдается число с остатком: если было нечетное число 7, то 4 цели будет у дрона и 3 цели у вооружения. Получается, что дроны могут атаковать, например, мелкие цели, а пушки больших, при условии нахождения и тех, и других на панели обзора. Если же целей в обзорке будет мало, всего 3, то сначала дроны найдут себе нужную цель и атакуют, а следом то же сделают пушки.

Остается только нужные модули нажать. Модули в игре можно прожимать как мышкой, так и горячими клавишами: верхний ряд - F1, средний ряд - ctrl+F1, нижний ряд - alt+F1. Для верхнего ряда используем уже знакомый SendKeys.

string num_f = "{F" + (пишем нужный номер кнопки) + "}";
SendKeys.SendWait(num_f);

А вот со средним и нижним рядом возникают проблемы из-за надобности нажимать сочетания клавиш (игра часто нажатий не видит). Если быть точнее, то проблемы возникают только с клавишами F1-F12. Если нажимать сочетания клавиш ctrl/alt с кнопками A-Z, то никаких проблем не появляется.

 Выходом из данной ситуации будет прожимание средних и нижних модулей мышкой. Точкой отсчета будет плюс на панели корабля.
- средний модуль находится на (+105, -35).
- нижний модуль находится на (+78, +12).

Расстояние между модулями в линии равно 52 пикселя. Ну и остается использовать Mouse_emulation и Mouse_emulation_left.

ПРОВЕРКА HP ЩИТА И БРОНИ

К сожалению, из-за разных факторов, иногда NPC могут отправить наш корабль на свалку, из-за чего мы выйдем не в «+», а в «-» по искам (игровая валюта). Хотелось бы таких ситуация избежать.

Реализация:
1) Проверка HP щита и брони.
2) Поиск места для отварпа.
3) Действие при малом HP.

Пункт №1 - Проверка HP щита и брони

Пора сделать проверку на хитпоинты щита и брони корабля. Конечно у корабля еще есть третья ступень защиты, это структура «хул», но в PVE на ней не танкуют. Все, что нам нужно сделать, это проверять наличие красных пикселей на полосах щита и брони.

Цвет пикселей отличается в небольшом диапазоне. Нужно просто определить, в каком месте делать проверку. Я решил проверять цвет с разбросом, чтобы при изменении места проверки пикселя, не нужно было в паинте определять нужный цвет.
R > 240
G < 40
G == B

В EVE online щит в отличии от брони и структуры со временем сам восстанавливается.
Но не равномерно. Максимальное количество хитпоинтов в секунду восстанавливается в районе 30%. Поэтому проверку на HP я сделал ниже 30%, так как если NPC пробили хитпоинты до сюда, то пора  сматываться. Броню решил проверять на тех же значениях.

Точкой отсчет снова будет минус:
- щит (+2, +79).
- броня (+9, +73).

Не забываем сделать чекбоксы в программе для выбора проверки HP корабля.

Пункт №2 - Поиск места для отварпа

Ну что же. Теперь наш бот знает, что нужно уходить в варп при низком HP. Вот только куда? Здесь мы вернемся к нашей обзорной панели, на которой и найдем подходящее место. Логика будет похожа на поиск аномалек в «разведзондах».

Нам нужно найти в строке раздел с названием:
1) Точка отсчета будет в том же месте, где мы смотрим иконки NPC.
2) Столбик с иконками не меняет свой размер, поэтому можем сразу перейти вправо на 22 пикселя и окажемся в следующем разделе.
3) Идем право, пока не наткнемся на пиксель иного цвета (разделяющая линия).

Запоминаем эту точку и снова идем вправо, пока не наткнемся на границу этого раздела.

Возвращаемся в начало нашей вкладки «название». Вспоминаем, что расстояние (высота) между строками 19 пикселей. Я сохранял изображение названий на 18 пикселей в высоту и до упора в правую границу раздела.

Так же делаем меню в программе с отображением сохраненных пунктов.

Пункт №3 - Действие при малом HP

Представим, что наш корабль теряет щит ниже 30% и бот это увидел. Каковы будут действия?

Сначала нужно проверить, использует ли корабль дронов. Если да, то нужно собрать их в ангар. До тех пор, пока дроны не будут собраны, бот просто повторяет проверку. Когда все дроны оказались в ангаре, бот ищет нужный объект для отварпа. Сравнивать разделы будем так же в одной горизонтальной линии (этого достаточно, для нахождения уникального названия).

P.S. На скрине выше первая строка и третья совпадают по названию. Так что, если размер строк в «названии» небольшой, то проверки в один пиксель не хватит.
P.S. На скрине выше первая строка и третья совпадают по названию. Так что, если размер строк в «названии» небольшой, то проверки в один пиксель не хватит.

Нажимает на него ПКМ, передвигаем курсор на (+45, +14) и нажимает ЛКМ.

Теперь бот умеет отварпывать. При уходе в варп, бот засыпает на 20 секунд (если конечно этому не помешают - блокировки варп прыжка от NPC или невозможность для разгона из-за объекта на пути к цели). Далее бот проверяет, что он вышел из варпа (отсутствие голубых пикселей на шкале скорости). И ждет ? минут.

В программе сделан выбор по времени ожидания.

После ? минут, бот начинает свою работу по новой. Проверка своих HP, наличие NPC, аномалек. Здесь нас будут поджидать две проблемы. Одну мы решили ранее – подсветка окна, когда мы с ним взаимодействуем. Наш бот кликает по капаситору и выделение с окон снимает.

А вот вторая проблема в том, что выделение с объекта, на который мы кликнули не пропадет. В окне аномалек нам это играло на руку, так как бот не будет пытаться лететь на аномальку, на которой он и находится. Хотя и тут получается ситуация, что если на аномальке нас продавили по хитпоинтам, то бот не сможет вернуться на ту же аномальку и дофармить ее. Ему сначала придется варпнуть на другую аномальку того же типа.


Следовательно, в окне обзорной панели бот не сможет найти объект для отварпа и скорей всего умрет на аномальке. Конечно на аномальке бот стреляет по NPC, то есть выделение висит на них, но наш бот не идеален и в момент проверки целей, NPC может быть в условной 8 строке, а вот в момент клика он может успеть умереть и там окажется наш объект для отварпа.

Что бы этого избежать, бот будет выделять рандомную строку, если он не нашел объект для отварпа. И повторять проверку на поиск объекта.

ЛОКАЛЬНЫЙ ЧАТ

Логику взаимодействия с NPC мы настроили. Самое время вспомнить, что EVE online - PVP ориентированная игра и убить вас могут везде (исключение – нахождение в доке станции). Да и фарм аномалек в основном происходит в «нулевых» системах, где нет помощи в виде дружественных NPC. Значит нужно научиться проверять локальный чат на наличие в нем посторонних.

План:
1) Найти окно чата.
2) Найти игроков в локале и определить их опасность.
3) Логика действий.

Пункт №1 - Найти окно чата

Окно чата мы будем искать по наличию шестеренки вверхнем левом углу.

Будем проверять пиксели по краям воображаемых квадратов:
1) Пиксели с внутренней стороны шестеренки одинаковые (сторона 5 пикселей).
2) Пиксели с внешней стороны шестеренки одинаковые (сторона 7 пикселей).
3) Пиксель в шестеренке не соответствует пикселям вне шестеренки внутри и снаружи.
4) Пиксели внутри шестеренки одинаковые (сторона 3 пикселя).
5) Пиксели снаружи шестеренки одинаковые (сторона 11 пикселей).

Если проверки прошли, то отступаем на (-8, -32) от нашего первого проверочного пикселя шестеренки.

Попадаем таким образом в уже знакомый нам уголок. Логика проверки тут точно такая же:
- Проверяем верхний угол.
- После идем вниз и вправо для нахождения еще двух углов.

 И вот мы уже знаем размер окна чата.

Пункт №2 - Найти игроков в локале и определить их опасность

Список игроков у нас находится справа. Найдем его правый угол сделав отступ (-3, +45) от верхнего права угла окна чата (это расстояние неизменно). И от правого угла списка игроков просто идем влево, пока не попадем на пиксель иного цвета.

Верхние левый и правый углы мы нашли. После так же идем уже вниз, пока не упремся в иной пиксель.

Не забудьте поставить краткий список участников, так как работать будем с ним.

Размер поля никнейма в высоту 18 пикселей. Держим во внимании, что справа может появиться ползунок, если пилотов в чате много. Поэтому как минимум не доходим до правой границы на 8 пикселей.

Далее проверяем среднюю линию, если там есть пиксели, отличающиеся от фона, значит тут никнейм и можно его сохранить.

Хорошо. Определить наличие пилотов в локале мы можем. Осталось понять Who is who?

В этом нам поможет сама EVE online, которая помечает пилотов разными «статусами» в зависимости от их поступков и наших взаимоотношений (война альянсов, нахождение в одном флоте и т.п.).

Разве что у нейтралов по умолчанию иконки нет, но она может быть «=», если пилоту/альянсу ее поставили вручную. 

Вот наш теоретический возможный чат:

Первым делом будем проверять наличие стандартного нейтрала. Перейдя от начального пикселя строки с никнеймом на (+2, +5). И проверим их соответствие с со следующим пикселем справа. Если они одинаковые, значит нейтрал.

Если не одинаковые, то смотрим цвет пикселя иконки. Здесь снова воспользуемся значением HSV - "H".

color = window_sc.GetPixel(i, j);
hsv_h = (int)color.GetHue();  //значение HSV - "H"

Далее мы будем вести подсчет цветов вправо на 10 пикселей. Найдя 4 пикселя одного значения, можем определить пилота - друг/нейтрал/враг.

Так как для друзей и врагов используются определённые оттенки, то с помощью пипетки был найден их диапазон :)

Друг, если: 60<hsv && hsv != 180.
Враг, если: 60> hsv && hsv>0.

Но, у красных и серых иконок hsv равен 0.

Здесь нам поможет второе значение HSV - "S".

max = Math.Max(color.R, Math.Max(color.G, color.B)); 
min = Math.Min(color.R, Math.Min(color.G, color.B)); 
double saturation = (max == 0) ? 0 : 1d - (1d * min / max); //значение HSV - "S"

И проверяем:
saturation > 0.5 - значит враг, так как у красного оттенка значение всегда больше 0.5.
saturation < 0.5 - значит нейтрал, так как у серого оттенка значение всегда меньше 0.5.

Таким нехитрым способом мы определили Who is who.

Пункт №3 - Логика действий

Первое, что нам нужно определить – как отображается наш собственный никнейм. С этим все выходит просто, наш никнейм всегда является нейтральным и отображается без иконок. Следовательно, при сканировании списка пилотов там будет как минимум один нейтрал и это мы. Значит изначально количество нейтралов = -1, врагов = 0, друзей = 0. И при сканировании пилотов в чате, эти значения будем увеличивать.

Так как скан чата довольна полезная вещь не только в афк PVE, то ее я вынес отдельной кнопкой в программе, что бы можно было запускать отдельно. Кнопочка для поиска окна чата и поле для вывода первого никнейма тоже добавляем. Что бы быть уверенным в правильном нахождении окна.

И еще пришла мысль, что бы бот сообщал голосом увеличение/уменьшение пилотов определенного типа в чате. Для данной цели был взят гугл переводчик, туда воткнут нужный краткий текст на все случаи и добавлен в программу. Лучше добавить возможность отключать эти уведомления, если они вам не нужны. Приоритет на уведомление будет у врагов и нейтралов (если вдруг за раз начнет резко увеличиваться локал). И так же у этих уведомлений больший приоритет имеет увеличение локала.

 Добавляем чекбокс на отварп из аномальки при нейтралах и врагах (так как в зависимости от системы и аномальки, из нее не всегда нужно улетать при нейтрале, но желательно о госте знать).

Логика отварпа с аномальки будет точно та же, что и при низком уровне HP.

 На этом этапе мы получили самостоятельного бота, за которым не нужно следить. Но остается парочка вещей, которые можно улучшить. Этим и займемся :)

ОТВАРП В ДОК СТАНЦИИ/ЦИТАДЕЛИ

В данный момент бот умеет отварпывать на безопасные объекты, но при этом он остается в космосе. И если NPC за вами не полетят, то пилоты с радостью полетят в ту же точку и расстреляют вас в ней.

План:
1) Док – варп в док станции.
2) Андок – выход из дока в космос.

Пункт №1 – Док – варп в док станции

Кстати, по мимо безопасности - если войти в док, то будет восстановлен весь щит. Теперь при отварпе нам нужно будет выбрать пункт «войти в док». В программе сделаем для этого дополнительный чекбокс, что бы бот знал, какой именно отварп нужно использовать.

И в случае, если нужно докнутся на объекте, выбираем 4 пункт ПКМ. Понадобится опуститься на 71 пиксель вниз, а не на 14.

После ухода в варп корабль так же засыпает на 20 секунд, далее ждет выхода и варпа, и уже ждет ? минут.

Пункт №2 - Андок – выход из дока в космос

Что же теперь нужно определить, где мы находимся. Стоит отметить, что бот не всегда может зайти в док при отварпе. Например враждебный пилот успел по вам выстрелить в аномалии, то в течении минуты в док зайти будет нельзя, а вы скорей всего до него доварпаете раньше этого времени. А еще вы сами можете отменить вход в док или наоборот туда зайди, пока бот находился в ожидании ? минут из за нейтрала.

Для определения, где мы, можно проверять наличие минуса и плюса на панели корабля, так как их местоположение уже известно.

Если их нашли, значит мы в космосе. Тогда ничего нового в логике действий не появляется. Разве что на всякий случай будем кликать на минус, что бы корабль остановился, если он вдруг куда-то двигался. Дальше уже проверяем наличие NPC и поиск аномальки.

Если оказалось, что мы находимся в доке, то будем искать кнопку выхода и дока.

Кнопка желтого цвета. Искать ее будем по верхнему левому углу. У угла данной кнопки все пиксели одного цвета (186, 137, 0), кроме «центрального» (191, 140, 0).

Поэтому определение угла начинается со второго пикселя справа:
1) Проверяем 5 пикселей вправо, что они одного цвета.
2) Переходим на (-1,-1) и проверяем, что 5 пикселей вниз одного цвета.
3) Проверяем, что центральный пиксель отличается от верхнего, нижнего, правого и левого
4) Проверяем, что центральный пиксель отличается от смежных с ним пикселей по диагоналям.

Если проверки прошли, значит мы нашли кнопку выхода из дока. Переходим от верхнего левого угла на (+80, +22) центр кнопки и нажимаем ее.

Засыпаем на 15 секунд, после чего прожимаем минус для остановки корабля (при выходе из дока, корабль всегда движется вперед на максимальной скорости).

ВАЖНО: при входе в док, все модули выключаются. Поэтому нужно снова включить те, которые должны работать постоянно (наши зеленые ячейки модулей в программе). Дальше проверяем наличие NPC и поиск аномальки.

ИСПОЛЬЗОВАНИЕ РАЗГОНЫХ ВРАТ

Помимо обычных аномалек, если специализированные. И не забываем про миссии. В большинстве случаев, они состоят из нескольких отдельных сегментов, между которых нужно перемещаться с помощью «елок» разгонных врат. Пока не будет убито все, что шевелится, дальше вас не пустят.

Приварпывать на первую елку вам придется руками, а дальше все сделает бот. Находить елку будем по иконке в окне обзорной панели.

Первый пиксель находится на (+5, +5) от начала иконки объекта.

Искать будем серые пиксели по HSV - "H", так как значение у серых пикселей здесь всегда равняется 60.

Будем проверять:
1) что, 13 пикселей вправо одинаковые.
2) что, начальный пиксель и на 9 пикселей ниже одинаковые.
3) что, 13 пикселей вправо одинаковые.
4) что, начальный пиксель не совпадает с пикселями слева, снизу, сверху, а также на (+14, 0) и (+6, +4).

Не забываем сделать чекбокс в программе.

Логика бота простая - если он не находит NPC, то ищет елку. Найдя ее, нажимает ПКМ, переходит на (+90, +70) «активировать ворота» и нажимает ЛКМ. Далее ждет остановки корабля и сканирует NPC по стандартному алгоритму.

Игнорирование скорости

Здесь же мне захотелось научить корабль двигаться во время фарма, так как до елки корабль может ползти 5+ минут. Лучше, если бот сразу полетит к елке, постреливая попутно NPC. А когда все зачистит, отварпает.

К сожалению, легкой возможности отличить нахождение в варпе и обычный полет к данному пункту не появилось. Поэтому бот просто перестает проверять нахождение в варпе. Чем это грозит?

При таком фарме бот просто до выхода из варпа будет пытаться атаковать и лочить NPC, чего игра сделать не позволит. Получается бот на костылях, но кроме эстетических страданий, никаких других проблем данный способ не приносит.

Использование пеленгатора

Как писалось ранее, на некоторых аномальках не нужно сразу отварпывать при появлении нейтралов. В основном это касается наших елок, так как к нам напрямую гости приварпать не смогут. В EVE Online есть замечательное окно «Пеленгатор», оно показывает нужные вам типы объектов в определённом радиусе от корабля. Полезное для нас – это тип корабля.

Бот здесь ничего не поймет, а вот мы при полу афк фарме – да (если будем находится рядом с компьютером). В программе просто делаем отдельную кнопку, которая будет включать сканирование кнопкой «V» через задаваемый промежуток.

SendKeys.SendWait("{v}");   //кликаем подскан (нажатие кнопки)
Thread.Sleep(50);       //время на отклик приложения     
SendKeys.SendWait("{v}");   //кликаем подскан (отпускание кнопки)

БОЛЬШОЙ И УЖАСНЫЙ ДРЕДНОУТ

При фарме аномалек в нулевых системах есть шанс встретить дредноут. Это очень вкусная цель, вот только он очень толстый и больно бьет, если вы фармите на Battleship. В одиночку его не убить.

Следовательно получаем два варианта:
1) Он откачивает свой щит быстрее, чем мы наносим урон, но и он по нам плохо наносит урон. Тогда бот просто потратит весь боекомплект и будет попусту сидеть на аномальке.
2) Он наносит вам тонну дамага и взрывает ваш корабль.

По механике игры, через 2 часа после появления, дредноут исчезнет – если с ним все это время взаимодействовать, иначе он исчезнет примерно через 15 минут
По механике игры, через 2 часа после появления, дредноут исчезнет – если с ним все это время взаимодействовать, иначе он исчезнет примерно через 15 минут

Появляется он всегда на последнем спавне NPC в аномальке. А самих аномалек в системе обычно находится больше двух. Так что будем отправлять бота на другие аномальки в случае появления такого чуда.

Добавляем для него отдельный чекбокс и звуковое уведомление, что бы можно было среагировать самому.

Верхний пиксель дредноута находится на (+10, +4) от первого пикселя иконки в обзорной панели. А нижний пиксель находится на (0, +11).

Логика действий будет следующая:
1) Если не нужно улетать при дредноуте, то бот просто его игнорирует и уничтожает NPC.
А когда становится мало HP, отварпывает в безопасное место. То есть, при наличии корабля, который не лопается от дредноута, вы просто зачистите аномальку и полетите на следующую (если на аномальке только дредноут, она перестанет отображаться уже через пару минут и начнется спавн следующей аномальки).

2) Если нужно улетать, то приоритет на новой аномальке. Если нужной аномальки нет, либо вы ее не указывали, то начинается поиск объекта для отварпа.

ВЫБОР ДЕЙСТВИЙ ДРОНОВ

Последнее, что мне захотелось немного улучшить, это выбор действий с дронами. Сделать чекбоксы, указывающие – нужно ли вытаскивать дронов из папки, или нужно просто выкидывать любых дронов и нужно ли сейвить дронов при низком показателе HP/наличии нейтралов в системе.

С сейвом дронов все легко, просто дополнительная проверка до отварпа.

С запуском любых дронов дело обстоит не сильно сложнее:
1) От левого верхнего угла переходим на (+96, +35), либо от начала пунктов с дронами на (+92, -13).
2) Нажимаем ПКМ.
3) Переходим на (+50, +15).
4) Нажимаем ЛКМ.

На этом можно заканчивать.

РЕЗУЛЬТАТ

Ну что же. Теперь вместо закликивания неписи на аномальках, можно почитать книгу или выйти на улицу потрогать траву.

В программе я добавил лампочки, чтобы видеть, этап работы:
Красная – поток не работает/завершился.
Желтая – поток ждет клика по окну евы, либо проверяет наличие нужных окон.
Зеленая – поток успешно работает.

Сама программа по умолчанию отображается поверх всех окон. Настройки были спрятаны за шестеренками.

И самое важное…

А вдруг вся статья, это один большой обман?

Привожу вам наглядное доказательство работоспособности сего чуда:

БОНУСНАЯ ЧАСТЬ

Пускай идеал не достижим, но мы будем к нему стремиться!

НУЖНАЯ РАСКЛАДКА

Так как прожимание клавиш работает на английской раскладке, то бот сам будет ее проверять при старте. Для этого снова потребуется обращение к user32.dll.

static class Handle_Language
{
 [DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();  //окно находящееся в фокусе

	 [DllImport("user32.dll", CharSet = CharSet.Auto)]
	public static extern bool PostMessage(IntPtr hWnd, int Msg, int wParam, int lParam);    //отправка запроса в приложение
}

И собственно сама проверка языка.

if (InputLanguage.CurrentInputLanguage.Culture.Name != "en-US") //проверка английской раскладки
{
	InputLanguage.CurrentInputLanguage = InputLanguage.FromCulture(new System.Globalization.CultureInfo("en-US"));  //включаем англ. раскладку
	
	//0x0050 - код сообщения WM_INPUTLANGCHANGEREQUEST
	Handle_Language.PostMessage(Handle_Language.GetForegroundWindow(), 0x0050, 2, 0);   //сообщаем приложению о смене раскладки
}

Причем мало просто сменить язык. Так как сами приложения раскладку проверяют только при активации окна. А если оно уже активно, то нужно самим сообщить о сменившемся языке ввода.

ВЫБОР ДРОНОВ

В запоминании нужной вкладки с дронами есть проблема. В названии указывается количество. Следовательно, если их станет больше/меньше, что проверка на совпадение пикселей не пройдет. Что бы это избежать, я ее «вручную» обрезаю по ширине, оставляя только название и в таком виде сохраняю. По понятной причине проверка на совпадение будет проходить ширину «обрезанной» строки.

ПРОБЛЕМА ОТВАРПА

При отварпе на объект может случиться казус. Мы уже можем быть рядом с ним – например, корабль вышел из дока и в этом момент появился нейтрал. Следовательно, нужно обратно докнуться. А сделать это не получится, так как пункты в сплывающем меню меняют свое положение в зависимости от объекта и расстояния до него.

Решить эту проблему можно через радиальное меню, которое мы и будем использовать. Найдя объект для отварпа удерживаем ЛКМ одну секунду и переводим указатель мыши на нужный пункт и отпускаем ЛКМ.
- отварп (-55, -60).
- войти в док (-5, -80).

И если нельзя уйти в варп, то пункт будет недоступен – следовательно никаких действий не последует. В случае надобности дока, бот прожмет нужную кнопку.

Так же при на отварпе на безопасный объект бот стал сразу брать разгон (делает два клика по объекту) и только после проверяет наличие дронов в космосе.

Зачем это нужно: дроны могу точень долго к вам лететь, из за чего овтарпать бот может уже и не успеть. А если он стоит в разгоне на объект, то уйдет в варп мгновенно.

ОДНО ОКНО ХОРОШО, А ДВА ЕЩЕ ЛУЧШЕ

Ну и вишенка на торте.
Я всегда играл в одно окно. Но мне так захотелось реализовать работу бота в двух и более окнах, что пришлось заводить второй аккаунт. В программе нам понадобится несколько новых кнопок.

1 - ждет, когда активным окном станет EVE Online и запоминает его.
2 - показывает успешность нахождения окна игры.
3 - Список окон.
4 - Добавить окно.
5 - Удалить окно.

Все сделанные настройки в программе теперь нужно запоминать, так как при создании нового или смене выбора в них, должны показываться актуальные настройки выбранного окна.

Для работы с окнами нам понадобится запомнить их Pid. Это мы уже делали ранее в Target_wind.FocusWindow. Возьмем нужный код от туда.

public static int FocusWindow_pid()    //узнаем pid процесса
{
	IntPtr hWnd = GetForegroundWindow();    // получаем хендел активного окна
	int pid;    //получаем pid потока активного окна
	GetWindowThreadProcessId(hWnd, out pid);    // узнаем PID процесса
	return pid;
}

И теперь при работе бота помимо проверки на название окна, мы еще проверяем pid этого окна. Осталось окна как-то переключать.

Сама логика простая:
1) при старте бот запускает отдельные потоки для каждого окна.
2) каждый поток проверяет фокус на окне игры.
3) поток, чей pid совпал с окном начинает свою работу – проверка HP, скан дронов, аномалек и т.д.
4) пройдя весь цикл, поток так же уходит в ожидание, но перед этим переключает фокус на следующее окно игры (если такое конечно было сохранено в настройках).

Для реализации 4 пункта, на понадобится помимо pid, сохранять еще и дескриптор окна hWnd. Так как смена окна происходит по нему.

public static IntPtr FocusWindow_hWnd()    //узнаем hWnd процесса
{
	IntPtr hWnd = GetForegroundWindow();
	return hWnd;
}

Далее нас снова нужно обратиться к user32.dll.

 [DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);    //открытие, скрытие окна приложения

private const int SW_SHOWNORMAL = 1;
private const int SW_SHOW = 5;
private const int SW_MINIMIZE = 6;

Метод смены окна выглядит так:

public static void Switch_handle(IntPtr hid) //переводим фокус на нужное окно
{
	ShowWindow(hid, SW_MINIMIZE);
	ShowWindow(hid, SW_SHOWNORMAL);
	ShowWindow(hid, SW_SHOW);
}

В теории должно быть достаточно команды ShowWindow(hid, SW_SHOW). Но на практике оказалось иначе. Окно может не стать активным и даже не перейти на передний план. Либо перейти на передний план, но активным останется прошлое окно.

Опытным путем была найдена рабочая комбинация действий для смены окна - нужное окно мы сначала сворачиваем, далее разворачиваем, и напоследок выводим на передний план.

Не забываем изменить логику сна при выбросе дронов, уходе в варп и нахождении в доке. Бот должен проверять не стало ли его кон активным, если да, то он переключает его на следующее. Что бы другой поток мог в это время работать.

 Подтверждение работы так же имеется :)

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 22: ↑22 и ↓0+22
Комментарии36

Публикации

Истории

Работа

Ближайшие события