Предисловие
Пока Arduino для переноса на него эмуляции движения курсора доставляется, хочется разобрать еще одну интересную функцию, а именно: визуальная отрисовка объектов на экране, то есть боксов, FOV, FPS, с которой работает нейросеть.Для отрисовки будет использована библиотека Pygame. Как следует из названия, обычно её применяют для создания игр на Python, но игры на Python — в принципе плохая идея, потому что, как минимум, в таких играх сложно реализовать сложные механики, или, по крайней мере, их будет сложнее реализовать, чем на игровом движке. Также игры на Python будут гораздо более ресурсоемкими, чем на тех же движках, как GameMaker или Unity. В связи с этим функция отрисовки на экране также будет ресурсоемкой, и поэтому будет возможность её отключения.
Дополнительно было решено добавить ещё один режим наводки и антиотдачи специально для пистолетов и неавтоматического оружия.
Отрисовка боксов
Подготовка проекта
Перед началом разработки функций отрисовки объектов потребуется подготовить проект. Первое, что будет сделано — это добавление RGB-значений в конфиг для определения цвета отрисованного бокса, а также возможность изменения этого цвета через веб-интерфейс.Вот как выглядит ключ с цветом в конфиге:
JavaScript:
"cs2_box_color": {
"r": 27,
"g": 177,
"b": 22
}
Теперь нужно в веб-интерфейсе (HTML-файле) добавить объект, через который будет меняться цвет. Этот объект будет input типа color.
HTML:
<label for="cs2_box_color">CS2 Box Color:</label>
<input type="color" id="cs2_box_color" name="cs2_box_color"><br>
Конвертация форматов rgb и hex
Функция для конвертации из RGB в HEX и для конвертации HEX в RGB. Нужно это, потому что объект принимает данные в формате HEX, а в конфиге они в формате RGB. Поэтому при получении данных в формате RGB из конфига, данные будут конвертироваться в HEX и записываться в объект, а при обновлении конфига данные из объекта будут конвертироваться в RGB и записываться в конфиг.Функция конвертации RGB в HEX:
JavaScript:
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
}
Пошаговое объяснение:
Шестнадцатеричное представление цвета:Цвет в формате RGB задается тремя компонентами: красный (r), зеленый (g) и синий (b). Диапазон значений каждого цвета начинается от 0 и заканчивается 255.
В формате HEX цвет представляется как строка из шести символов, где каждый компонент RGB кодируется двумя символами: #RRGGBB.
Сдвиг битов и сложение:
1 << 24 — это операция сдвига влево на 24 бита. Результат этого будет 0x1000000, что соответствует 24 битам для использования в шестнадцатеричном формате цвета. Это значение используется для того, чтобы результат всегда имел 6 цифр, даже если RGB имеет значения, которые могут привести к более короткому значению.
(r << 16) — это сдвиг значения красного компонента на 16 битов влево. Это перемещает значение r в старшие 8 битов из 24 битов для цвета.
(g << 8) — это сдвиг значения зеленого компонента на 8 битов влево. Это перемещает значение g в средние 8 битов из 24 битов для цвета.
b — значение синего компонента помещается в младшие 8 битов.
Все эти значения складываются: (1 << 24) + (r << 16) + (g << 8) + b.
Преобразование в строку:
.toString(16) преобразует результат в строку в шестнадцатеричном формате.
.slice(1) удаляет первый символ строки, который является 1 из-за операции 1 << 24. Это делает строку длиной 6 символов, представляющую код цвета без ненужного префикса.
.toUpperCase() переводит все буквы в строке в верхний регистр для соответствия стандартному формату HEX, в котором буквы, как правило, находятся в верхнем регистре.
Теперь можно рассмотреть функцию конвертации из HEX в RGB:
JavaScript:
function hexToRgb(hex) {
const bigint = parseInt(hex.slice(1), 16);
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255
};
}
Дополнение функций принятия и отправки данных конфига
После написания функций конвертации цвета из одного формата в другой нужно дополнить функцию load_config для получения значений из конфига.
JavaScript:
const cs2_boxColor = rgbToHex(config.cs2_box_color.r, config.cs2_box_color.g, config.cs2_box_color.b);
document.getElementById('cs2_box_color').value = cs2_boxColor;
Теперь нужно дополнить функцию отправки конфига под названием update_config.
JavaScript:
cs2_box_color: hexToRgb(document.getElementById('cs2_box_color').value),
После этого нужно дополнить функцию update_config на Python, которая принимает данные с веб-страницы и записывает эти значения в конфигурационный файл.
Python:
config_data['cs2_box_color'] = {
'r': int(request.json['cs2_box_color']['r']),
'g': int(request.json['cs2_box_color']['g']),
'b': int(request.json['cs2_box_color']['b'])
}
Теперь можно приступить к написанию самой логики отрисовки боксов на экране.
Как будет работать отрисовка боксов
Будет создаваться прозрачное окно размерами на весь экран и всегда поверх других окон, и отрисовка будет происходить именно на нем, а не в игре, то есть отрисовка не будет вмешиваться в код игры, как это делают читы.Когда с примерной работой отрисовки все понятно, можно начинать писать сам код. Для начала нужно создать отдельный файл для всей логики отрисовки; в нем будет инициализация конфига, Pygame и настройка окна Pygame, а также сама функция отрисовки.
Варианты реализации
Есть несколько вариантов, как реализовать инициализацию Pygame и настройку окна. Их можно сделать вообще вне функций, но в таком случае, если для других копий нейронной сети сделать такой же код, то при запуске программы весь код будет инициализироваться повторно для каждой игры. Также можно реализовать через функции; таким образом, уже решается проблема с повторной инициализацией во всех копиях нейронной сети.Написание функции отрисовки боксов
Когда новый Python файл создан, в него сразу нужно вписать функцию инициализации конфига, как и в других файлах.
Python:
def load_config(key=None, max_retries=3, delay=1):
attempt = 0
while attempt < max_retries:
try:
with open("config.json", 'r') as file:
config = json.load(file)
if key:
return config.get(key)
return config
except (FileNotFoundError, json.JSONDecodeError) as e:
attempt += 1
if attempt >= max_retries:
raise RuntimeError(f"Не удалось загрузить конфигурацию после {max_retries} попыток. Ошибка: {e}")
else:
print(f"Ошибка при загрузке конфигурации: {e}. Попытка {attempt} из {max_retries}.")
time.sleep(delay) # Задержка перед повторной попыткой
Создание окна для отрисовки
Затем нужно создать функцию, в которой будет инициализироваться Pygame и сразу же настраиваться окно. Функция будет называться pygame_initialization. Далее, в самой функции нужно создать объект инициализации Pygame.
Python:
def pygame_initialization():
pygame.init()
Далее нужно создать окно, в котором как раз будут отрисовываться графические элементы по типу FPS, FOV и боксов противников.
Python:
screen = pygame.display.set_mode((load_config("cs2_screen_width"), load_config("cs2_screen_height")), pygame.HWSURFACE)
Затем нужно получить дескриптор созданного окна.
Python:
hwnd = pygame.display.get_wm_info()["window"]
pygame.display.get_wm_info() получает информацию об окне; затем в полученной информации находит ключ window, в котором хранится дескриптор окна. Таким образом, hwnd = дескриптор окна.
Далее к свойствам окна нужно добавить стиль для того, чтобы в дальнейшем сделать окно прозрачным.
Python:
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) | win32con.WS_EX_LAYERED)
Далее нужно поработать с добавленным стилем, чтобы сделать окно прозрачным.
Python:
win32gui.SetLayeredWindowAttributes(hwnd, win32api.RGB(0, 0, 0), 0, win32con.LWA_COLORKEY)
win32gui.SetLayeredWindowAttributes — это функция, которая позволяет работать с окном, у которого есть стиль WS_EX_LAYERED (этот стиль был добавлен ранее), для изменения прозрачности окна и других визуальных эффектов. hwnd — это дескриптор окна, win32api.RGB(*(0, 0, 0)) — это цвет окна, в данном случае черный, win32con.LWA_COLORKEY делает прозрачным цвет, указанный в win32api.RGB. Таким образом, все черные пиксели, отрисованные в окне, станут прозрачными.
Теперь нужно сделать так, чтобы окно было поверх всех окон.
Python:
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
win32con.HWND_TOPMOST — флаг, указывающий, что окно должно быть поверх других окон. Параметры 0, 0, 0, 0 указывают, что положение и размер окна по умолчанию такие, как при создании окна. win32con.SWP_NOMOVE указывает, что положение окна не должно меняться, а win32con.SWP_NOSIZE — что размер окна не должен меняться.
Отрисовка
Далее нужно написать функцию отрисовки боксов, которая будет называться draw_box. Эта функция должна принимать один параметр, а если точнее, в дальнейшем она будет вызываться в функции детекции, и в нее будут передаваться координаты бокса объекта, то есть x1, y1 и x2, y2, о которых было рассказано в предыдущей теме. То есть, раз функция принимает данные бокса, нужно как-то их обработать, и для начала потребуется каждую из координат назначить в переменную.
Python:
def draw_box(box_cord):
x1, y1, x2, y2 = map(int, box_cord)
Далее нужно вызвать функцию рисования pygame.draw.rect, и в ней первым делом указать окно, на котором нужно рисовать (оно находится в переменной screen). Затем нужно указать цвет, который нужно брать из конфига.
Python:
pygame.draw.rect(
screen,
(load_config("cs2_box_color")['r'], load_config("cs2_box_color")['g'], load_config("cs2_box_color")['b']),
Теперь нужно указать координаты, по которым нужно нарисовать прямоугольник.
Python:
[
x1 + load_config("cs2_screen_width") // 2 - load_config("cs2_fov_width") // 2,
y1 + load_config("cs2_screen_height") // 2 - load_config("cs2_fov_height") // 2,
x2 - x1,
y2 - y1
],
1
Вначале берется начальная координата x1, затем к ней добавляется половина ширины экрана (то есть центр экрана по x) и вычитается половина ширины FOV, чтобы сместить координату относительно центра экрана и FOV. Там, где y1 плюсуется, происходят аналогичные действия. x2 - x1 и y2 - y1 нужны для того, чтобы рассчитать ширину и высоту бокса. Единица в конце указывает на толщину линии, которой будет нарисован бокс.
Теперь с файлом детекции закончено на данный момент, и можно приступать к инициализации и запуску функции отрисовки. Для этого нужно перейти в Python файл с основной логикой нейронной сети и обратиться к функции screenshot; в ней есть бесконечный цикл, в начале этого цикла нужно указать обработку событий с окном Pygame.
Python:
pygame.event.pump()
Далее, в той же функции, но выше цикла, нужно вызвать функцию инициализации Pygame и настройки окна.
Python:
pygame_initialization()
Теперь нужно перейти к функции детекции и найти в ней эту строку:
Python:
if len(results.boxes) > 0:
Над этой строкой нужно вызвать переменную screen, которая хранит в себе данные об окне Pygame, и указать в ней закрашивание всего экрана черным. Если не забыли, то черный в данном случае является прозрачным. Это нужно для того, чтобы после детекции объекта рисовался бокс, и при следующем вызове функции детекции нарисованный ранее бокс стирался, а не оставался на экране.
Python:
screen.fill((0, 0, 0))
Но на этом моменте возникает проблема: эта переменная не глобальная, и для начала ее нужно сделать глобальной. Для этого нужно вернуться в файл с отрисовкой боксов и перейти в функцию pygame_initialization(). В ней нужно указать эту переменную как глобальную:
Python:
global screen
Теперь вне функции нужно назначить этой переменной изначальное значение при запуске программы. None не подойдет, поэтому будет указано то же самое, что и в функции pygame_initialization(), но с нулевыми значениями.
Python:
screen = pygame.display.set_mode((0, 0), pygame.HWSURFACE)
Теперь в главном файле нейросети в функции detection данная переменная успешно используется, и теперь нужно внутри этой функции вызывать функцию отрисовки боксов, передавая туда координаты объекта.
Для этого нужно найти строку:
Python:
if box.cls[0].item() in load_config("cs2_obj_detection"):
И в самом низу вызвать функцию отрисовки.
Python:
draw_box(box_cord)
После этого нужно вызвать функцию Pygame для обновления окна.
Python:
pygame.display.update()
После этого уже можно запустить нейросеть и увидеть результат:
Функция отрисовки будет более полезна для дебага и понимания, на каком расстоянии хорошо или плохо детектит, а также чтобы вычислить, на какие случайные объекты детектит, чтобы в новой обученной модели это исправить. Например, был найден такой забавный момент, где курицу детектит как террориста:
Возможность отключать отрисовку
Также, как и упоминалось ранее, данная функция очень ресурсоемкая, и из-за ее скорости обработки скорость работы всей нейросети достаточно сильно падает. Конечно, в таком случае софт все равно дает прирост к вашему аиму, но если хочется максимального профита, то лучше данную функцию отключать. Для этого нужно в конфиге создать новый ключ для включения и отключения отрисовки боксов.
Python:
"cs2_draw_box_valid": true
Далее нужно добавить на сайте возможность смены этого параметра. Для этого нужно перейти в HTML файл и добавить селектор объекта с выбором true или false.
HTML:
<select id="cs2_draw_box_valid" name="cs2_draw_box_valid">
<option value="true">TRUE</option>
<option value="false">FALSE</option>
</select><br>
Затем в функции загрузки конфига с Python сервера нужно добавить назначение значения из конфига в данный селектор.
JavaScript:
document.getElementById('cs2_draw_box_valid').value = config.cs2_draw_box_valid || '';
После этого нужно дополнить функцию передачи данных с сайта в конфиг, добавив строку сбора данных из объекта в ключ внутри JSON, который будет отправлен на Python-часть, и уже Python-часть добавит это значение в конфиг.
JavaScript:
cs2_draw_box_valid: document.getElementById('cs2_draw_box_valid').value === 'true',
С веб-частью закончено, и теперь нужно дополнить файл, где инициализируется Flask и обновляются значения конфига. Нужно перейти к функции update_config и вписать в нее принятие значения ключа из запроса с веб-части и запись этого значения в ключ в конфиг файле.
Python:
config_data['cs2_draw_box_valid'] = bool(request.json['cs2_draw_box_valid'])
Функция отрисовки FOV
Подготовка проекта
Теперь можно сделать отрисовку зоны FOV, но перед этим нужно сначала добавить ключи в конфиг, а если точнее, ключ с цветом FOV и ключ для отключения и включения данной функции отрисовки.
JavaScript:
"cs2_draw_fov_valid": true,
"cs2_fov_color": {
"r": 255,
"g": 187,
"b": 0
}
Затем нужно, так же как и с настройкой отрисовки боксов, зайти в HTML файл и добавить 2 объекта: объект для включения и выключения функции и объект выбора цвета.
HTML:
<select id="cs2_draw_fov_valid" name="cs2_draw_fov_valid">
<option value="true">TRUE</option>
<option value="false">FALSE</option>
</select><br>
<input type="color" id="cs2_fov_color" name="cs2_fov_color"><br>
В функции, где принимаются данные из конфига, нужно добавить назначение данных из конфига в объекты на странице:
JavaScript:
document.getElementById('cs2_draw_fov_valid').value = config.cs2_draw_fov_valid || '';
const cs2_fovColor = rgbToHex(config.cs2_fov_color.r, config.cs2_fov_color.g, config.cs2_fov_color.b);
document.getElementById('cs2_fov_color').value = cs2_fovColor;
Далее также идентичный код, но в функции для отправки данных из полей в конфиг.
JavaScript:
cs2_draw_fov_valid: document.getElementById('cs2_draw_fov_valid').value === 'true',
cs2_fov_color: hexToRgb(document.getElementById('cs2_fov_color').value),
После этого нужно перейти в Python файл, в котором происходит инициализация Flask, и перейти в функцию update_config. Затем внутри этой функции нужно принимать запрос от веб-части, брать оттуда ключи с параметрами и назначать эти параметры в ключи в конфиг файле.
Python:
config_data['cs2_draw_fov_valid'] = bool(request.json['cs2_draw_fov_valid'])
config_data['cs2_fov_color'] = {
'r': int(request.json['cs2_fov_color']['r']),
'g': int(request.json['cs2_fov_color']['g']),
'b': int(request.json['cs2_fov_color']['b'])
}
Отрисовка fov
С подготовкой интерфейса закончено, теперь нужно перейти в Python файл, где находится функция отрисовки боксов, и добавить новую функцию с названием draw_fov. Внутри функции нужно вызвать pygame.draw.rect для рисования и указать окно, в котором будет это происходить, цвет, которым будет отрисовываться FOV, и затем определить левый верхний угол FOV по x и y, а также указать ширину и высоту FOV.
Python:
def draw_fov():
pygame.draw.rect(
screen,
(load_config("cs2_fov_color")['r'], load_config("cs2_fov_color")['g'], load_config("cs2_fov_color")['b']),
[load_config("cs2_screen_width") // 2 - load_config("cs2_fov_width") // 2,
load_config("cs2_screen_height") // 2 - load_config("cs2_fov_height") // 2,
load_config("cs2_fov_width"), load_config("cs2_fov_height")],
1)
Точка FOV по y: load_config("cs2_screen_height") // 2 - load_config("cs2_fov_height") // 2
Вместе это получается верхний левый угол FOV. Ширина и высота FOV нужны для понимания, каких размеров рисовать объект, то есть рисовать начнет из левого верхнего угла размерами, которые указаны в cs2_fov_width и cs2_fov_height.
Функция отрисовки готова, и теперь нужно ее вызывать.
Варианты вызова функции
У меня было несколько вариантов того, как её вызывать.В отдельном потоке и внутри функции сделать бесконечный цикл, или вызывать её там же, где и отрисовку боксов, и без цикла в функции детекции. Так как в отдельном потоке должно было меньше нагружать систему и не увеличивать скорость работы функции детекции. Но в таком случае строка из функции детекции, которая очищает экран после каждой итерации цикла, ломала отрисовку FOV, и он начинал моргать на экране. Происходило это из-за того, что оба эти действия работали, так сказать, не в такт друг с другом, и после этого остался только второй вариант.
Сделать вызов функции внутри функции detection и не делать саму функцию отрисовки в потоке. В таком случае очистка экрана и вызов функции отрисовки вызываются по порядку, и всё отрисовывается корректно.
В итоге, так как выбран второй вариант, нужно соответственно перейти в функцию детекции в основном файле нейросети и в самом низу, там же где вызывается pygame.display.update(), добавить над ней if для проверки на то, указано ли в конфиге true в ключе cs2_draw_fov_valid. Внутри if нужно вызвать функцию отрисовки FOV.
Python:
if load_config("cs2_draw_fov_valid"):
draw_fov()
pygame.display.update()
Функция отрисовки FPS
Подготовка проекта
Теперь можно написать последнюю функцию, связанную с отрисовкой — это отрисовка FPS нейронной сети. Первое, что будет сделано, это, как и с предыдущими функциями, подготовка конфига и интерфейса для настройки. Останавливаться долго на этом не буду, так как там опять всё делается точно так же.Новые ключи в конфиге:
JavaScript:
"cs2_draw_fps_valid": true,
"cs2_fps_color": {
"r": 140,
"g": 251,
"b": 4
}
Далее нужно перейти в HTML и добавить объект slider для включения и выключения, а также объект input типа color.
HTML:
<select id="cs2_draw_fps_valid" name="cs2_draw_fps_valid">
<option value="true">TRUE</option>
<option value="false">FALSE</option>
</select><br>
<input type="color" id="cs2_fps_color" name="cs2_fps_color"><br>
Теперь в том же HTML дополняем функцию load_config для получения данных из конфига и записи в объект.
JavaScript:
document.getElementById('cs2_draw_fps_valid').value = config.cs2_draw_fps_valid || '';
// Загружаем значение цвета в HEX формате
const cs2_fpsColor = rgbToHex(config.cs2_fps_color.r, config.cs2_fps_color.g, config.cs2_fps_color.b);
document.getElementById('cs2_fps_color').value = cs2_fpsColor;
После этого нужно дополнить функцию обновления конфига, взяв значения из объектов.
JavaScript:
cs2_draw_fps_valid: document.getElementById('cs2_draw_fps_valid').value === 'true',
// Преобразуем цвет обратно в RGB формат
cs2_fps_color: hexToRgb(document.getElementById('cs2_fps_color').value),
Далее нужно перейти в файл инициализации Flask и дополнить функцию update_config.
Python:
config_data['cs2_draw_fps_valid'] = bool(request.json['cs2_draw_fps_valid'])
config_data['cs2_fps_color'] = {
'r': int(request.json['cs2_fps_color']['r']),
'g': int(request.json['cs2_fps_color']['g']),
'b': int(request.json['cs2_fps_color']['b'])
}
Функция отрисовки FPS
После этого можно приступать к написанию самой функции отрисовки. Называться функция будет draw_fps и принимать она будет один аргумент — это количество кадров в секунду.
Python:
def draw_fps(fps):
Далее нужно записать в переменную шрифт для отрисовки и его размер.
Python:
font = pygame.font.Font('freesansbold.ttf', 32)
Затем нужно настроить рендеринг текста, который будет отрисовываться. В его настройках будет 4 параметра: текст для отрисовки, включить или отключить сглаживание текста, цвет текста (будет браться из конфига) и задний фон (по умолчанию черный, то есть прозрачный).
Python:
font_text = font.render(
'FPS: ' + fps,
True,
(load_config("cs2_fps_color")['r'], load_config("cs2_fps_color")['g'], load_config("cs2_fps_color")['b']),
(0, 0, 0))
Далее будет создан прямоугольник вокруг текста, чтобы в дальнейшем было легко изменять положение текста на экране.
Python:
text_rect = font_text.get_rect()
Затем нужна строка для переноса прямоугольника с текстом на конкретные координаты экрана. Не нужно забывать, что нулевые координаты начинаются с левого верхнего угла. Также в указанных координатах будет находиться именно центр прямоугольника, а не его верхний левый угол.
Python:
text_rect.center = (75, 25)
Теперь нужно отрисовывать сам текст в окне, данные которого занесены в переменную screen.
Python:
screen.blit(font_text, text_rect)
С функцией отрисовки закончено, теперь нужно написать логику расчета количества FPS в секунду и вызвать функцию отрисовки, передавая в неё данные. Для этого нужно перейти в Python файл с основной логикой нейронной сети в функцию детекции и в самом её начале создать переменную, в которую запишется текущее количество тиков
Python:
start_timer = cv2.getTickCount()
Далее нужно в конце функции получить количество тиков на данный момент, то есть после всей логики функции. То есть нужно найти этот участок кода:
Python:
if load_config("cs2_draw_fov_valid"):
draw_fov()
И под этим кодом вписать эту строку:
Python:
fps = cv2.getTickFrequency() / (cv2.getTickCount() - start_timer)
Способы подсчета FPS
Можно было использовать обычный таймер, а не cv2. Мной было опробовано оба варианта реализации подсчета, но по моим наблюдениям обычный таймер нагружал систему чуть сильнее, поэтому был выбран метод с cv2. Скорее всего, метод с использованием обычного таймера будет проще в понимании для новичков, и разница в нагрузке не критична, так что вариант с обычным таймером также можно использовать без особой разницы, если вам так будет удобнее.Теперь, под последней написанной строкой, нужно вызывать функцию отрисовки, но делать это нужно лишь проверив в конфиге флаг включения и отключения.
Python:
if load_config("cs2_draw_fps_valid"):
draw_fps(str(int(fps)))
На этом со всеми функциями отрисовки закончено, и было решено также заодно добавить новый режим наводки с антиотдачей, а именно — для пистолетов.
Новая механика наводки и антиотдачи
Зачем нужен новый режим наводки и антиотдачи
Так как обычный режим наводки работает, когда зажата левая кнопка мыши, этот режим не подойдет для пистолетов, так как на пистолетах нужно кликать левой кнопкой мыши, а не зажимать. Кроме того, при зажатии ЛКМ срабатывают макросы для винтовок, а это крайне не подходит к пистолетам. Поэтому и было принято решение сделать дополнительный режим наводки с дополнительной антиотдачей.Подготовка проекта
И первое, что нужно сделать, — это снова добавить новые ключи в конфиг для большей настраиваемости софта.
JavaScript:
"cs2_aim_pistol_step": 1,
"cs2_aim_pistol_time_sleep": 0.001,
"cs2_anti_recoil_pistol_px": 2,
"cs2_anti_recoil_pistol_time_sleep": 0.25,
После того как ключи были добавлены, нужно, как и раньше, добавить на веб-странице объекты.
HTML:
<label for="cs2_aim_target">CS2 Aim Pistol Step:</label>
<input type="number" step="1" id="cs2_aim_pistol_step" name="cs2_aim_pistol_step"><br>
<label for="cs2_aim_target">CS2 Aim Pistol Time Sleep:</label>
<input type="number" step="0.001" id="cs2_aim_pistol_time_sleep" name="cs2_aim_pistol_time_sleep"><br>
<label for="cs2_aim_target">CS2 Anti Recoil Pistol PX:</label>
<input type="number" step="1" id="cs2_anti_recoil_pistol_px" name="cs2_anti_recoil_pistol_px"><br>
<label for="cs2_aim_target">CS2 Anti Recoil Pistol Time Sleep:</label>
<input type="number" step="0.1" id="cs2_anti_recoil_pistol_time_sleep" name="cs2_anti_recoil_pistol_time_sleep"><br>
Затем нужно дополнить на веб-странице функцию загрузки данных из конфига:
JavaScript:
document.getElementById('cs2_aim_pistol_step').value = config.cs2_aim_pistol_step || '';
document.getElementById('cs2_aim_pistol_time_sleep').value = config.cs2_aim_pistol_time_sleep || '';
document.getElementById('cs2_anti_recoil_pistol_px').value = config.cs2_anti_recoil_pistol_px || '';
document.getElementById('cs2_anti_recoil_pistol_time_sleep').value = config.cs2_anti_recoil_pistol_time_sleep || '';
Также нужно дополнить функцию отправки данных со страницы в конфиг:
JavaScript:
cs2_aim_pistol_step: document.getElementById('cs2_aim_pistol_step').value,
cs2_aim_pistol_time_sleep: document.getElementById('cs2_aim_pistol_time_sleep').value,
cs2_anti_recoil_pistol_px: document.getElementById('cs2_anti_recoil_pistol_px').value,
cs2_anti_recoil_pistol_time_sleep: document.getElementById('cs2_anti_recoil_pistol_time_sleep').value,
После этого нужно в Python файле, где происходит инициализация Flask и, в принципе, логика взаимодействия с веб-страницей, дополнить функцию update_config:
Python:
config_data['cs2_aim_pistol_step'] = int(request.json['cs2_aim_pistol_step'])
config_data['cs2_aim_pistol_time_sleep'] = float(request.json['cs2_aim_pistol_time_sleep'])
config_data['cs2_anti_recoil_pistol_px'] = int(request.json['cs2_anti_recoil_pistol_px'])
config_data['cs2_anti_recoil_pistol_time_sleep'] = float(request.json['cs2_anti_recoil_pistol_time_sleep'])
Функция наводки
Первым делом нужно создать функцию aim_pistol, которая будет принимать координаты до объекта dx и dy. Затем нужно добавить проверку if на нажатие боковой кнопки мыши.На какую клавишу включать функции?
Дело в том, что если сделать проверку на нажатие левой кнопки мыши, то внутри if в любом случае потребуется делать эмуляцию нажатия ЛКМ и отпускания. Из-за того что вы будете нажимать левую кнопку мыши, а нейронка будет её отпускать, работать ничего не будет, так как нейронная сеть постоянно будет отменять ваше нажатие. Соответственно, придётся делать на боковую кнопку мыши, чтобы не было конфликтов.
Python:
def aim_pistol(dx, dy):
if 0 > win32api.GetKeyState(win32con.VK_XBUTTON2):
Далее, внутри нужно назначить две переменные, в которых будут храниться данные, полученные от разделения координат до объекта, деленных на шаги из ключа cs2_aim_pistol_step в конфиге.
Python:
aim_step_x = dx / load_config("cs2_aim_pistol_step")
aim_step_y = dy / load_config("cs2_aim_pistol_step")
Далее нужно создать цикл for, который будет повторяться столько раз, сколько указано в ключе с шагами. Внутри цикла нужно выполнять эмуляцию мыши для перемещения на координаты, указанные в переменных, созданных выше.
Python:
for i in range(load_config("cs2_aim_pistol_step")):
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)
time.sleep(load_config("cs2_aim_pistol_time_sleep"))
Функция анти отдачи
Пока что функция наводки пистолетом закончена, и можно начать писать функцию антиотдачи для пистолета. Называться функция будет anti_recoil_pistol. Внутри функции нужно сразу создать глобальную переменную, которая будет использоваться в дальнейшем.
Python:
def anti_recoil_pistol():
global anti_recoil_pistol_y
Далее нужно создать бесконечный цикл while True, в котором нужно добавить проверку if на нажатие боковой кнопки мыши.
Python:
if 0 > win32api.GetKeyState(win32con.VK_XBUTTON2):
Внутри if нужно назначить ранее созданной глобальной переменной значение из ключа cs2_anti_recoil_pistol_px, а после этого установить паузу длиной в значение из ключа cs2_anti_recoil_pistol_time_sleep.
Python:
if 0 > win32api.GetKeyState(win32con.VK_XBUTTON2):
anti_recoil_pistol_y += load_config("cs2_anti_recoil_pistol_px")
"""Пауза"""
time.sleep(load_config("cs2_anti_recoil_pistol_time_sleep"))
Также нужно добавить else, если клавиша не нажата, и внутри него указать обнуление глобальной переменной.
Python:
else:
anti_recoil_pistol_y = 0
Теперь нужно создать эту переменную вне функции и назначить ей стандартное значение 0.
Python:
anti_recoil_pistol_y = 0
После этого нужно дополнить функцию наводки пистолетом, и внутри этой функции, но не внутри if, добавлять значение из глобальной переменной к переменной dy.
Python:
dy += anti_recoil_pistol_y
Для чего передавать значения из функции анти отдачи в функцию наводки?
Работа с глобальной переменной нужна точно для того же, что и глобальная переменная в обычной наводке для игры CS2, а если точнее — чтобы при стрельбе курсор наводился на объект относительно значения из переменной. Это нужно для того, чтобы наводилось не постоянно в голову, а каждый раз все ниже, так как при стрельбе в данной игре курсор всегда в центре, а пули летят выше. Благодаря таким действиям курсор будет стрелять ниже головы, но в итоге пули будут попадать именно в голову.Вызов функций
Теперь нужно вызывать функцию антиотдачи для пистолета и наводку. Функцию наводки нужно вызывать там же, где и обычную наводку, а именно — в функции отрисовки
Python:
aim_pistol(dx, dy)
Функция антиотдачи для пистолета будет вызываться в функции start_game в отдельном потоке, точно так же, как и обычная антиотдача. Именно для этого в функции антиотдачи бесконечный цикл, так как без него функция выполнилась бы один раз при запуске и всё.
Python:
anti_recoil_pistol_thread = threading.Thread(target=anti_recoil_pistol)
anti_recoil_pistol_thread.start()
Функция стрельбы на боковую кнопку мыши
Если вы внимательно читали код, то могли заметить, что в функции антиотдачи для пистолета и наводки нет выполнения нажатия левой кнопки мыши. То есть, на данный момент при нажатии на боковую кнопку мыши будет происходить только наводка и смещение курсора по антиотдаче. Дело в том, что стрельба будет вынесена в отдельную функцию.Почему стрельбу нужно вынести в отдельную функцию?
Из-за того что объекты могут пропадать прямо во время стрельбы, добавление нажатия левой кнопки мыши внутрь функции наводки пистолетом нежелательно, так как стрельба также прекратится, если объект потеряется. Кроме того, в функции наводки находится пауза, которая может меняться в зависимости от настроек. Соответственно, пауза между выстрелами также будет зависеть от этой функции и может не подходить под скорострельность пистолета.По той же причине с неподходящей паузой не получится нормально вставить нажатие левой кнопки в функцию антиотдачи, так как, допустим, в функции антиотдачи пауза составляет 0.25, и данная пауза слишком большая для паузы между выстрелами из пистолета.
Написание функции
Когда было выяснено, зачем нужна отдельная функция для стрельбы, можно приступать к её написанию. Эта функция крайне проста и коротка. В ней нужно создать бесконечный цикл while True и внутри этого цикла добавить проверку на нажатие боковой кнопки мыши. Внутри этой проверки нужно добавить нажатие левой кнопки, отжатие левой кнопки и нужную паузу.
Python:
def shoot_pistol():
while True:
if 0 > win32api.GetKeyState(win32con.VK_XBUTTON2):
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
time.sleep(0.025)
Далее нужно вызывать эту функцию внутри функции start_game в отдельном цикле, чтобы она не блокировала основной поток.
Python:
shoot_pistol_thread = threading.Thread(target=shoot_pistol)
shoot_pistol_thread.start()
Вывод
На этом статья подходит к концу. В ней была реализована новая логика наводки с антиотдачей и отрисовка различных объектов на экране наподобие привычных читов. Статья получилась достаточно маленькой и простой. Следующая статья по данной нейронной сети выйдет, когда мне придёт Arduino для переноса логики эмуляции на него. Перенос на Arduino поможет обойти некоторые античиты, в которых блокируется эмуляция мыши, так что софт станет ещё менее заметным и более безопасным.Также прилагаю видеопрезентацию работы добавленных в этой статье функций: https://rutube.ru/video/062ddf063114057852e7a21d5db39ffc/
Статья в виде документа: https://docs.google.com/document/d/1DlaUk9l3wAcMsmcm3FwmeRLJVGM6ygj0Uu4DBAi9FGM/edit?usp=sharing
Сделано OverlordGameDev специально для форума xss.pro
.