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

В один прекрасный день, стреляя по 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 можно менять цвет интерфейса, и на большей части цветов программа корректно работала. Но все же была выбрана черная цветовая гамма «Темная материя», так как в ней цвета без всяких «примесей».

7a5102b9d4668cb9cc1c1a190492b009.png

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

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

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

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

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

d9c06c6ff27dd0542f41077ab1ba82ea.png

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

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

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

3c5b33dbd500843827a73b7c1294c36b.png

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

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

02d0e6985d959aa44c247b133d56536b.png

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

a7d6094736bfd0ed9b59bf0cecb89e55.png

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

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

6af9ca3e65efd20174bb5453d2021f16.png

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

6405857744b0cf88b69c8b64586550eb.png

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

90fef62bcb80943e6366ad077e670ca6.png

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

e6a6f3410559daccd7dc79cab74ff066.png

Что делаем:

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

List bit_dron_list = new List();

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/кнопка/чекбокс (на свой вкус) для подтверждения выбора вкладки.

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

6b94a6579edce48a6511971f58604e9b.png

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

888cbac2dda857828de8aa4df56e774d.png

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

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

f0d0a5dfc48268e49a24a48ec406a1aa.png

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

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

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

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

5dda8f08e4b6b72530b3538a5ff56336.png

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

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

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

959ba475fff53de28ef9f1219ca0b332.png

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

7bfd8cd62e31f01110fdaa9102d2bc4f.png

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

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

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

687a1054527f565e5084846f4350146f.png

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

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

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

f67c01e86a6da79c885d870cf79edf02.png

Собирать дронов мы будем с помощью горячих клавиш и команды 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 иногда при сборе дронов, они начинают возвращаться на корабль со своей обычной скорость, а не на всех парах (МВД).

371f3cdfdf22220757cd8cacc64f2d0b.png

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

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

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

6674c9f8766edc1b030bd1486c9a9626.png

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

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

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

bfe4b3106f4763d5eada5b81c28c3a19.png

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

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

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

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

008187e016346b25d49853d6f464b3b8.png

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

Создаем метод с двумя аргументами — координаты 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);

9e62a7bdf660f7a2878eca5ae1c6b0bc.png

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

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

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

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

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

1acd1fc3ad8aee8d6eabff883a71b0a2.png

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

9318c84bef712ab19b0be3bc926aabb2.png

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

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

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

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

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

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

70b2fa9c103bba618940416af0ff12af.png

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

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

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

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

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

5c0087fb9f15c186f2db8ccdd96bb8e8.png

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

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

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

6a102039ef6e83a0222f51a35b01434c.png

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

9531d362f07f0500bc59f0664eef433c.png

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

8be83da6dd2df9aa142eb54297c8346b.png

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

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

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

3c42461b27b30e2a58acb6bd9b790074.png

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

3c50b25eb9eaab08476c4844deffdfc4.png

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

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

97b9d20e478629dc6d9bc08aac210769.png

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

96e4dee696fcb4326ef6321422a4d2bf.png

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

2ca35d514128ab224e9e99e7829099d3.png

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

8bd6ac109b1dc942577097c9fbb813cb.png

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

463ec1d908985aafe3464f378b0a5caf.png

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

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

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

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

f956f9ec79918b6f9c1325655587d957.png

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

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

379e1f5db67abcac67428250b3965c01.png

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

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

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

05edbdadca07742a3d669594a05dbfca.png

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

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

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

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

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

c754604da515799f51ccc4ba4c1e3b07.png

Метод 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, которые будут находиться на одинаковом расстоянии от моего корабля (немного обгоняя друг друга), то бот постоянно будет менять цель атаки. Так сказать, будет стрелять в молоко :(

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

6bba55d258628a17ebb0388d11f724dd.png

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

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

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

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

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

da2adb5f1847f9e2ea3e3d885b7a33c6.png

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

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

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

ef9f9218e66f9ab9e48b1624ea3ef066.png

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

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

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

if (hsv_h == 120)

0a7f109f0c011ad7b076724bca468f65.png

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

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

b021cbd04141313bafcf266baaf9d48d.png

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

7783e488ef6ac659e571a98bbd590db4.png

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

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

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

06756d0ff58c0777a89cce888b407ebc.png

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

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

cf01b6a5f99bebc0a42529e83962a6b5.png

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

8cf96133a8ecf849f57b740a80939a2e.png

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

c075f63bf62fbc2a0543cba6a0eed8a1.png

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

c5f77808600804814fbf9d7492b20aa4.png

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

b8fa3e99f27b26eac9908caad3472d7d.png

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

a31268b19becfa64da0e04784456042d.png

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

Пункт №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();

022b3fbd217b7644494a28270097fa56.png

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

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

44cbce2347ef401450dc1b3fd661e288.png

Расстояние между выборами расстояния — 19 пикселей, а первый элемент в&nbs

© Habrahabr.ru