D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Предисловие
В этой статье будет показано продолжение разработки нейросети: будет переписана структура проекта, добавлены новые механики и улучшены старые.Нововведения в проекте
В данной статье будет реализована анти-отдача в связке с нейронной сетью, которая была разработана в предыдущих статьях. Анти-отдача будет разделена на несколько типов:- Стандартная — это анти-отдача, которая работает путём смещения курсора по оси Y вниз по прямой.
- Кастомная — этот тип анти-отдачи будет работать, используя точные координаты и смещая курсор как по осям X, так и Y с конкретными таймингами. По сути, данный метод будет аналогичен макросам для мышек X7, и каждый макрос будет настроен на определённую игру и конкретное оружие. В данной статье для примера выбрана игра CS2. В связи с усложнённой логикой отдачи в игре CS2, также потребуется изменить функцию наведения и функцию детекции, чтобы добавить возможность выбора объекта для наведения — например, контр-террориста, террориста или обоих.
Нейронная сеть также будет разделена на две версии с возможностью переключения. Это сделано для повышения модульности и универсальности проекта, поскольку для разных игр существуют разные механики.
Также будет написана логика для функции триггер-бота.
Перенос настроек в конфиг файл
Перед тем как приступить к разработке логики анти-отдачи, необходимо подготовить проект, внести некоторые правки и улучшить его базу. В первую очередь, для удобства следует вынести переменные в отдельный конфигурационный файл. Для этого нужно создать файл config.json и записать в него все переменные, которые ранее назначались в начале кода.Код: Скопировать в буфер обмена
Код:
{
"screen_width": 1366,
"screen_height": 768,
"fov_width": 500,
"fov_height": 500,
"aim_step": 2,
"aim_time_sleep": 0.001,
"aim_target": 0.2
}
Теперь вместо переменных в коде нужно написать новую функцию с названием load_config, которая будет принимать три аргумента:
- Первый аргумент: ключ (в нём будет находиться ключ из файла конфига, например aim_step).
- Второй аргумент: max_retries (это максимальное количество попыток обращения к конфигу. Это нужно для будущего интерфейса, если в интерфейсе заменилось значение и передалось в конфиг, в этот момент программа может запросить доступ к значению из конфига, а интерфейс не успел его передать. Поэтому может возникнуть ошибка и краш всей программы. max_retries позволит при неудачной попытке попробовать снова определённое количество раз).
- Третий аргумент: delay (это пауза перед следующей попыткой обращения к конфигу после неудачной попытки).
def load_config(key=None, max_retries=3, delay=1):
Далее нужно назначить переменную attempt, которая будет равняться нулю. Это нужно также для предотвращения краша программы. Затем нужно создать цикл while, который будет срабатывать, если attempt меньше max_retries.
Python: Скопировать в буфер обмена
while attempt < max_retries:
Внутри цикла нужно установить блок try-except. В блоке try будет попытка открыть и загрузить в переменную config данные из файла конфига. После этого, внутри блока try, должно быть условие if, которое будет проверять, если в переменную load_config был передан ключ, то функция должна возвращать данные конкретного ключа из переменной config, в которую ранее были загружены все данные из файла конфига. Если же ключ не был передан при запросе к переменной, функция должна возвращать все данные из переменной config, а не конкретный ключ.
Python: Скопировать в буфер обмена
Код:
try:
with open("config.json", 'r') as file:
config = json.load(file)
if key:
return config.get(key)
return config
В except, если выдало ошибку о неудачном получении данных из конфига или файл конфига был не найден, сначала добавляется +1 к attempt. Затем следует проверка if на то, что если attempt больше или равен max_retries, то выдается ошибка RuntimeError с уведомлением о том, что было много попыток использовать конфиг-файл. В else (если attempt не больше max_retries) выводится обычный print о том, что не удалось использовать конфиг. После этого следует пауза длиной в значение из переменной delay. Затем будет новая попытка подключения, и так будет продолжаться, пока не будет успешный запрос или количество попыток не превысит максимально разрешенное количество.
Python: Скопировать в буфер обмена
Код:
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) # Задержка перед повторной попыткой
С переменной обращения к конфигу закончено. Теперь в коде множество ошибок из-за того, что не найдены переменные, так как они были удалены ранее. Чтобы исправить это, вместо вызова переменных нужно вызывать функцию обращения к конфигу, передавая в неё ключ, из которого нужно получить данные. Например, если есть ошибка в строке, которая получает центр экрана по оси X путем деления ширины экрана на 2, нужно заменить эту строку на вызов функции load_config, передавая ключ для ширины экрана и вычисляя центр экрана.
Python: Скопировать в буфер обмена
center_x = screen_width // 2
Нужно заменить screen_width на вызов функции load_config с ключом screen_width.
Python: Скопировать в буфер обмена
center_x = load_config("screen_width") // 2
Так нужно проделать со всеми ошибками в коде, связанными с отсутствием переменных. Но на самом деле вычисление center_x, а также center_y и fov_x и fov_y также не понадобятся в коде, так как в дальнейшем в коде нужно будет получать значения из этих переменных. Даже если вызывать, допустим, ту же переменную center_x в каком-нибудь цикле, её значение не будет изменяться, даже если в конфиге по факту уже другое значение. Это связано с тем, что при обращении к переменной center_x она не будет каждый раз вызывать load_config, а просто будет использовать значение, которое получило впервые. Поэтому ещё раз, нужно удалить это:
Python: Скопировать в буфер обмена
Код:
center_x = screen_width // 2
center_y = screen_height // 2
fov_x = center_x - fov_width // 2
fov_y = center_y - fov_height // 2
После удаления нужно заменить все использования center_x на следующее:
Python: Скопировать в буфер обмена
load_config("screen_width") // 2
center_y на это:
Python: Скопировать в буфер обмена
load_config("screen_height") // 2
fov_x на это:
Python: Скопировать в буфер обмена
load_config("screen_width") // 2 - load_config("fov_width") // 2
fov_y на это:
Python: Скопировать в буфер обмена
load_config("screen_height") // 2 - load_config("fov_height") // 2
Таким образом, если менять данные в конфиге во время игры, все данные в самой нейросети также будут обновляться. Сейчас это лишь небольшое улучшение, но в дальнейшем это будет основой функциональности интерфейса.
Базовая система анти отдачи
Теперь, когда все переменные заменены на вызов получения данных из конфига, можно приступать к первой версии антиотдачи, которая будет просто смещать курсор вниз по y. Для начала нужно создать два ключа в конфиге, которые будут использоваться в будущей функции антиотдачи.Код: Скопировать в буфер обмена
Код:
"anti_recoil_px": 1,
"anti_recoil_time_sleep": 0.001
После добавления ключей в конфиг можно приступать к написанию функции антиотдачи. Называться она будет anti_recoil, и в ней нужно будет прописать цикл while True. Внутри цикла нужно добавить проверку на нажатие левой кнопки мыши. Если левая кнопка мыши нажата, то двигать курсор по y на количество пикселей, указанных в ключе из конфига anti_recoil_px.
Python: Скопировать в буфер обмена
Код:
def anti_recoil():
while True:
if 0 > win32api.GetKeyState(win32con.VK_LBUTTON):
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, 0, int(load_config("anti_recoil_px")), 0, 0)
time.sleep(load_config("anti_recoil_time_sleep"))
- Выход: убрать цикл и вызывать функцию каждый раз в конце функции детекции. В таком случае функция не будет занимать поток на постоянной основе; будет происходить итерация цикла детекции, вызов функции наводки, затем вызов функции антиотдачи. После этого антиотдача сместит курсор на указанное количество пикселей по y, и начнется новая итерация цикла детекции. Данный метод плох тем, что пока будет работать функция антиотдачи, цикл функции детекции не начнется, и хотя смещение занимает не так много времени, все же это лишняя задержка, которой можно избежать. Кроме того, постоянный вызов функции или постоянная проверка на нажатие в потоке детекции также вредят скорости работы нейронной сети.
- Выход: оставить цикл while True, и вызвать функцию антиотдачи в отдельном потоке при запуске игры. Таким образом, не потребуется постоянно вызывать функцию и занимать поток детекции. Этот способ хорош тем, что функция будет вызвана лишь один раз, и соответственно реакция на нажатие будет быстрее, нежели в первом способе, где придется ждать, пока дойдет очередь до вызова функции. Поскольку будет использоваться отдельный поток, функция антиотдачи не будет конфликтовать с функцией детекции.
Python: Скопировать в буфер обмена
Код:
if __name__ == "__main__":
anti_recoil_thread = threading.Thread(target=anti_recoil)
anti_recoil_thread.start()
screenshot()
На этом этапе нейросеть с простой антиотдачей закончена, и её вполне хватит для некоторых игр с несложной отдачей.
Нейросеть для CS2
Теперь можно начинать разработку нейросети для CS2, и есть два пути её реализации:- Путь: Нейросеть для CS2 будет отличаться от текущей версии полностью переписанной логикой антиотдачи, переписанной логикой наведения и переписанной логикой детекции. В связи с этими изменениями можно просто добавлять условие if в текущем коде, в котором будет проверяться выбранная игра. Таких условий придётся делать достаточно много, и некоторые из них нужно будет делать в цикле, что, хотя и немного, но скажется на скорости работы программы.
- Путь: Вынести обе версии нейросети в отдельные файлы, а в основном файле проекта написать функцию для проверки выбранной игры. Допустим, если выбрана игра CS2, то запускать нейросеть из файла с логикой для игры CS2. Если выбрана другая игра, то соответственно запускать логику из файла для этой другой игры. С таким способом суммарное количество кода увеличится по сравнению с первым вариантом разработки, но учитывая, что логика для каждой игры будет разделена на отдельные файлы, это является плюсом, так как позволит в дальнейшем намного проще добавлять логику для отдельных игр с различными механиками. Также количество строк без использования условий в каждой функции значительно повысит читаемость кода. Плюсом будет также то, что если одна из версий нейросети сломается при изменении, это не затронет остальные логики. Таким образом, я считаю, что данный вариант разработки позволит сделать код более структурированным и модульным, поэтому выбран именно он.
Изменение структуры проекта
Перед созданием логики нейросети конкретно для игры CS2 потребуется изменить структуру проекта. Все версии нейросетей будут разделены на отдельные файлы и не будут храниться в основном файле. Поэтому для начала нужно создать папку для файлов с логикой для каждой игры. Основная папка для файлов нейросетей будет называться game_modules. В этой папке для каждой логики будет своя папка: для игры CS2 будет одноимённая папка, для уже реализованной версии нейросети будет папка с названием default, поскольку это стандартная версия, которая подойдёт для многих игр. Теперь внутри папок cs2 и default нужно создать одноимённые Python-файлы.Возможность выбора версии нейросети
После подготовки папок и файлов можно приступать к написанию кода. Для начала нужно скопировать весь код и перенести его в файл default.py, а в основном файле нужно удалить весь код и добавить такую же функцию загрузки конфига, как и в стандартной нейросети, которая только что была перенесена в файл default.py.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) # Задержка перед повторной попыткой
Далее нужно добавить в конфиг новый ключ, который потребуется в дальнейшем для определения игры.
Код: Скопировать в буфер обмена
"game": "default",
Теперь нужно вернуться обратно в главный файл и написать ещё одну функцию. Эта функция будет проверять ключ из конфига, и в зависимости от того, какое значение будет указано, будет запускать нужную копию нейросети. Пока что реализована только простая версия, поэтому проверка будет только для неё.
Python: Скопировать в буфер обмена
Код:
def game_initialization():
if load_config("game") == "default":
default.start_game()
После написания функции определения игры нужно вызывать эту функцию при запуске, и для этого в главном файле нужно написать соответствующую логику.
Python: Скопировать в буфер обмена
Код:
if __name__ == "__main__":
game_initialization()
Теперь можно вернуться к default.start_game. Для того чтобы нейросеть для игры запустилась, нужно зайти в файл default и создать функцию start, которая будет запускать функцию создания скриншотов и антиотдачи. Раньше это делала эта часть кода:
Python: Скопировать в буфер обмена
Код:
if __name__ == "__main__":
anti_recoil_thread = threading.Thread(target=anti_recoil)
anti_recoil_thread.start()
screenshot()
Теперь же это будет выглядеть так:
Python: Скопировать в буфер обмена
Код:
def start_game():
screenshot_thread = threading.Thread(target=screenshot)
screenshot_thread.start()
anti_recoil_thread = threading.Thread(target=anti_recoil)
anti_recoil_thread.start()
Подготовка нейросети для cs2
Теперь можно начать писать логику для нейросети cs2. Для начала можно скопировать код стандартной нейросети и вставить его в Python-файл cs2.py. Затем нужно добавить новые ключи со значениями в конфиг. Половина ключей и их значения идентичны стандартной версии нейронной сети, поэтому можно просто скопировать их и дописать в начале каждого ключа приписку cs2_. Это сделано для того, чтобы абсолютно каждый параметр был отдельно настраиваемым для каждой игры.Код: Скопировать в буфер обмена
Код:
"cs2_screen_width": 1366,
"cs2_screen_height": 768,
"cs2_fov_width": 250,
"cs2_fov_height": 250,
"cs2_aim_step": 1,
"cs2_aim_time_sleep": 0.001,
"cs2_aim_target": 0.38,
После добавления ключей в конфиг нужно в самом файле cs2.py изменить все упоминания старых ключей на новые с припиской cs2, но не считая ключей anti_recoil_px и anti_recoil_time_sleep, так как в дальнейшем они не понадобятся в функции антиотдачи. Тем не менее, для проверки нейронной сети они пока должны использовать значения для простой нейронной сети.
После изменения вызываемых из конфига ключей нужно перейти в основной файл проекта и добавить в функцию инициализации игры еще одно условие для проверки ключа на название cs2.
Python: Скопировать в буфер обмена
Код:
def game_initialization():
if load_config("game") == "default":
default.start_game()
if load_config("game") == "cs2":
cs2.start_game()
Теперь можно указать в конфиге внутри ключа game значение cs2 и запустить нейросеть. При запуске нейросети в консоль дважды будет выводиться уведомление об инициализации CUDA. Это связано с тем, что инициализация CUDA и модели обучения присутствуют в обеих версиях нейронной сети и находятся вне какой-либо функции. Если код находится вне функции, он инициализируется при запуске программы, что приводит к повторной инициализации. Из этой дилеммы есть два выхода:
- Перенести инициализацию CUDA и инициализацию модели обучения в отдельный файл, в котором перед инициализацией будет проверяться ключ game, и инициализировать конкретный файл с обученной моделью для конкретной игры.
- Оставить в каждой из версий нейронной сети инициализацию CUDA и модели обучения для конкретной игры без проверок ключей, но вынести это в отдельную функцию, которую можно вызывать в самом начале функции скриншота.
Python: Скопировать в буфер обмена
Код:
def cuda_initialization():
global model
# Определение устройства (GPU или CPU)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
if torch.cuda.is_available():
print("Используется CUDA")
else:
print("Используется CPU")
model = YOLO(os.path.abspath("models\\cs2_model.pt"))
model.to(device)
print("Запущена нейронная сеть для игры CS2")
def screenshot():
cuda_initialization()
camera = dxcam.create()
while True:
frame = camera.grab(region=(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_screen_width") // 2 - load_config("cs2_fov_width") // 2 +
load_config("cs2_fov_width"), load_config("cs2_screen_height") // 2 -
load_config("cs2_fov_height") // 2 + load_config("cs2_fov_height")))
if frame is None:
continue
detection(frame)
По аналогии нужно сделать то же самое и с простой версией нейронной сети. После этого можно запустить программу через главный файл и убедиться в том, что инициализация происходит только один раз и для указанной в конфиге игры.
На этом очередные правки перед самым интересным внесены, и скоро можно будет начать писать логику антиотдачи, которая использует макросы по типу тех, что применяются для мышек X7. Но перед тем как начать писать логику антиотдачи, нужно где-то достать сами макросы. Найти их можно достаточно легко, но не все они имеют приемлемое качество, или же можно купить готовые. В любом случае, в том виде, в котором они используются в мышках, их нельзя будет использовать в текущем проекте. Поэтому было принято решение написать небольшую программу для конвертации формата макросов от мышек X7 в формат для нейросети.
Программа для конвертации макросов
Python: Скопировать в буфер обмена
Код:
input_data = """
"""
Python: Скопировать в буфер обмена
Код:
input_data = """Delay 20 ms
LeftUp 1
Delay 20 ms
LeftDown 1
Delay 20 ms
MoveR 0 6
Delay 20 ms
MoveR 0 6
Delay 20 ms
MoveR 0 6
Delay 20 ms
MoveR 0 6
"""
Затем нужно создать пустой список, в котором в дальнейшем будет находиться конвертированный макрос, а также создать переменную Delay = 0 (в дальнейшем значение будет меняться).
Python: Скопировать в буфер обмена
Код:
macros = []
delay = 0
После этого нужно создать цикл for, который будет проходить по каждой строке в переменной с макросом x7.
Python: Скопировать в буфер обмена
for line in input_data.split('\n'):
Внутри цикла нужно создать проверку на то, что строка из переменной не пустая.
Python: Скопировать в буфер обмена
if line.strip():
Далее, внутри if нужно брать полученную строку и разделить её на элементы, используя пробелы. Затем добавить проверку на то, что если первый элемент — это строка Delay, то нужно взять второй элемент (второй элемент — это число, например 20 ms) и разделить его на 1000, чтобы получить миллисекунды в формате, который понимает Python, и записать полученное значение в переменную.
Затем нужно добавить похожую проверку, но на слово MoveR. Если первый элемент — это MoveR, то остальные элементы — это координаты. Нужно взять эти координаты и преобразовать их в int, затем записать их в переменные x и y. Далее нужно записать получившиеся переменные x, y, и delay в пустой словарь macros.
Python: Скопировать в буфер обмена
Код:
elif parts[0] == 'MoveR':
x, y = map(int, parts[1:])
macros.append((x, y, delay))
После того как в словарь были записаны данные, нужно взять этот словарь и записать его в txt файл, используя file.writelines.
Python: Скопировать в буфер обмена
Код:
with open('макрос.txt', 'w') as file:
file.writelines(str(macros))
На этом написание конвертера макросов закончено. В итоге, если конвертировать макрос x7, пример которого был выше, получится что-то подобное:
[(0, 8, 0.098), (5, 21, 0.097), (-5, 26, 0.098), (0, 23, 0.1)]
Теперь нужно хранить этот макрос, чтобы в дальнейшем вызывать его в коде. Для этого был создан файл с названием cs2_macros_list.py, который помещен в папку cs2, где находится логика нейросети для одноименной игры. Внутри этого файла нужно создать переменную, в которой будет находиться макрос. Название переменной будет соответствовать оружию, для которого этот макрос предназначен.
ak_47 = [(0, 8, 0.098), (5, 21, 0.097), (-5, 26, 0.098), (0, 23, 0.1)]
Мною было конвертировано множество макросов для разного оружия. Показывать этапы конвертации каждого из них нет смысла, и скидывать макросы тоже не вижу смысла, так как использовались макросы, скачанные с первого сайта в Google, и, к сожалению, не все из них хорошо работают. Но на первое время этого будет достаточно.
Теперь можно приступить к реализации новой логики анти-отдачи, и первым шагом нужно добавить новые ключи со значениями в конфигурационный файл.
Код: Скопировать в буфер обмена
Код:
"cs2_macros_gun": "ak_47",
"cs2_macros_ak_47_adjustment": 0.25,
"cs2_macros_famas_adjustment": 0.4,
"cs2_macros_m4a1s_adjustment": 0.3,
"cs2_macros_m4a4_adjustment": 0.3,
"cs2_macros_mac_10_adjustment": 0.35,
"cs2_macros_sg_553_adjustment": 0.5
P.S. Так как макросы являются довольно примитивными, результат их использования на разных системах с разным DPI, разрешением и чувствительностью мыши в игре может сильно варьироваться. У кого-то макрос будет стрелять в точку, у кого-то выше, у кого-то ниже. Поэтому будет возможность уменьшать или увеличивать силу смещения по координатам, что позволит каждому настроить макросы под себя и достичь хорошего результата.
Продвинутая анти отдача
Теперь можно переписывать функцию анти-отдачи. Для начала нужно удалить все из функции и первым делом создать пустой словарь, в котором в дальнейшем будут храниться макросы. Затем нужно создать цикл for, который будет получать все переменные и их значения из cs2_macros_list.py и записывать их в переменную weapon.Python: Скопировать в буфер обмена
Код:
macros_dict = {}
for weapon in dir(cs2_macros_list):
Затем нужно записывать эти данные (переменные и их значения в виде координат макроса) в ранее созданный словарь.
Python: Скопировать в буфер обмена
macros_dict[weapon] = getattr(cs2_macros_list, weapon)
P.S. getattr(cs2_macros_list, weapon) возвращает значение переменной weapon из файла cs2_macros_list, то есть возвращает координаты и записывает их в словарь, где ключом является название переменной weapon.
После этого нужно создать цикл while, чтобы функция анти-отдачи работала постоянно. Внутри цикла while нужно создать цикл for, в котором будет браться ключ из словаря macros_dict с названием оружия, указанным в конфигурационном файле из cs2_macros_gun (названия ключей в файле с макросами такие же, как названия из конфигурационного файла). Из этого ключа в файле с макросами будут браться значения x, y, и delay.
Python: Скопировать в буфер обмена
Код:
# Основной цикл антиотдачи
while True:
for x, y, delay in macros_dict[load_config("cs2_macros_gun")]:
Затем, внутри цикла for нужно умножать x и y на число, указанное в ключе из конфигурационного файла, в котором указано значение для усиления или уменьшения силы отдачи для конкретного оружия.
Python: Скопировать в буфер обмена
Код:
x = round(x * load_config(f"cs2_macros_{load_config('cs2_macros_gun')}_adjustment"))
y = round(y * load_config(f"cs2_macros_{load_config('cs2_macros_gun')}_adjustment"))
Далее, в том же цикле for нужно добавить проверку на нажатие левой кнопки мыши.
Python: Скопировать в буфер обмена
if 0 > win32api.GetKeyState(win32con.VK_LBUTTON):
Далее нужно создать 2 переменные, к которым будут прибавляться x и y (позже будет объяснено, для чего это нужно).
Python: Скопировать в буфер обмена
Код:
anti_recoil_x += x
anti_recoil_y += y
После этого нужно указать паузу, используя значение из delay, которое было получено из макроса.
Python: Скопировать в буфер обмена
time.sleep(delay)
Затем, в else нужно обнулять значения переменных. else будет срабатывать, когда левая кнопка мыши будет отпущена.
Python: Скопировать в буфер обмена
Код:
else:
anti_recoil_x = 0
anti_recoil_y = 0
break
Теперь в начале функции нужно сделать эти переменные глобальными.
Python: Скопировать в буфер обмена
global anti_recoil_x, anti_recoil_y
И затем нужно вне функции назначить этим переменным значение 0.
Python: Скопировать в буфер обмена
Код:
anti_recoil_x = 0
anti_recoil_y = 0
def anti_recoil():
global anti_recoil_x, anti_recoil_y
Объяснение логики стрельбы в cs2 и новой анти отдачи
Для чего нужна вся эта работа с переменной? Дело в том, что стрельба в CS2 не совсем стандартная. Обычно в шутерах при стрельбе прицел уходит вверх вслед за пулями, но в CS2 прицел всегда остается в центре, а пули летят выше. То есть, если бы использовалась система анти-отдачи и наводки, как в стандартной нейросети, то нейросеть бы наводила прицел на объект, а пули летели бы выше, над головой игрока. Поэтому в версии нейросети для CS2 прицел не будет просто наводиться на центр объекта; он будет наводиться на центр объекта, который будет смещаться относительно координат из макроса. Допустим, чтобы центр объекта оказался в нужной позиции, нужно передвинуть мышь на 5 пикселей по x и на 1 пиксель по y. При нажатии левой кнопки мыши курсор смещается на эти координаты, и происходит выстрел. Затем, не отпуская левую кнопку мыши, происходит второй выстрел, но курсор остается в центре объекта, а пуля летит на 5 пикселей выше центра объекта. Чтобы пули летели в центр объекта, а не выше на 5 пикселей, нужно сместить центр объекта по y на 5 пикселей ниже. Таким образом, центр объекта по y для программы будет ниже на 5 пикселей от реального центра, но пули будут стрелять в реальный центр.Учитывая все вышесказанное, переменные нужны для передачи координат макроса при нажатии левой кнопки мыши. Эти координаты будут прибавляться к чистым координатам для перемещения прицела. Если левая кнопка мыши отпущена, функция наведения не будет работать, а значения переменных, передаваемых из анти-отдачи, обнуляются. При повторном нажатии левой кнопки мыши цикл повторится, и макрос начнет передавать свои координаты по новой, начиная сначала.
Также текущая функция анти-отдачи отличается от стандартной тем, что в самой функции нет перемещения курсора. Если курсор пытался бы смещаться по координатам в функции анти-отдачи и также смещаться на эти же координаты в функции наводки, это бы ухудшило точность. Поэтому в текущей реализации анти-отдача работает только тогда, когда обнаружен объект, так как функция наводки вызывается именно тогда, когда есть обнаруженные объекты. Бывает так, что вдалеке нейросеть не распознает объекты, и в итоге даже анти-отдача не сработает, но в CS2 это не критично, так как графика в игре не сложная, эффектов мало, а карты не самые длинные. Поэтому такое бывает очень редко и не критично.
По сути, в нейросетях, где стандартная отдача, а не как в CS2, когда при стрельбе прицел поднимается, анти-отдача не нужна, так как при поднятии прицела выше центра объекта нейросеть сама его вернет назад. Однако проблема в том, что скорость стрельбы, как правило, в играх выше скорости обработки и детекции скриншотов нейросетью. Соответственно, пока нейросеть поймет, что прицел стал выше, уже успеет произойти выстрел и после него еще один выстрел. Добавив функцию анти-отдачи в отдельном потоке, скорость обработки данных нейросетью компенсируется скоростью работы отдельной функции, задачей которой является просто перемещение курсора по заготовленным координатам. Использовать такой метод, как в CS2, для других нейросетей считаю бессмысленным, так как во-первых, в других играх случаев, когда нейросеть не определяет объект, может быть гораздо больше, и в такие моменты выручает хотя бы анти-отдача, а во-вторых, скорость срабатывания анти-отдачи отдельно от функции наводки компенсирует скорость детекции.
После того как была закончена функция анти-отдачи и пояснено, для чего и почему вся её логика устроена именно так, можно перейти к изменению функции наводки. Вот её текущая реализация, которая ничем не отличается от функции наводки из первой статьи:
Python: Скопировать в буфер обмена
Код:
def aim(dx, dy):
if 0 > win32api.GetKeyState(win32con.VK_LBUTTON):
aim_step_x = dx / load_config("cs2_aim_step")
aim_step_y = dy / load_config("cs2_aim_step")
for i in range(load_config("cs2_aim_step")):
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)
time.sleep(load_config("cs2_aim_time_sleep"))
А вот её новая реализация:
Python: Скопировать в буфер обмена
Код:
def aim(dx, dy):
dx += anti_recoil_x
dy += anti_recoil_y
if 0 > win32api.GetKeyState(win32con.VK_LBUTTON):
aim_step_x = dx / load_config("cs2_aim_step")
aim_step_y = dy / load_config("cs2_aim_step")
for i in range(load_config("cs2_aim_step")):
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)
time.sleep(load_config("cs2_aim_time_sleep"))
На этом написание логики наводки и анти-отдачи готово, и теперь можно добавить возможность переключения объектов для детекции, то есть контр-террористов или террористов.
Выбор объектов для детекции
Первым делом понадобится создать новый ключ со значениями в конфиге.Код: Скопировать в буфер обмена
"cs2_obj_detection": [0],
Если значение [0], то будет наводиться на объект, который был пронумерован в датасете первым. Если [1], то на второй объект. Если [0,1], то будет наводиться на первый и второй объект, и если будут еще объекты в датасете, то получится [0,1,2,3] и т.д. Далее нужно перейти к функции детекции и немного её дополнить. Раньше в функции детекции брался первый обнаруженный объект, и далее шли вычисления центра объекта и т.д. Теперь будут браться все обнаруженные объекты, используя цикл for, и внутри цикла каждый из объектов будет проверяться через условие if на соответствие id объекта с id, указанным в конфиге. Если объект соответствует id, указанному в конфиге, тогда будет происходить логика вычисления центра объекта относительно экрана и т.д.
Было:
Python: Скопировать в буфер обмена
Код:
def detection(frame):
results = model.predict(frame, verbose=False)[0]
if len(results.boxes) > 0:
box = results.boxes[0]
box_cord = box.xyxy[0].tolist()
center_x_obj = (box_cord[0] + box_cord[2]) / 2
center_y_obj = (box_cord[1] + box_cord[3]) / 2
center_x_obj += load_config("cs2_screen_width") // 2 - load_config("cs2_fov_width") // 2
center_y_obj += load_config("cs2_screen_height") // 2 - load_config("cs2_fov_height") // 2
center_y_obj -= (box_cord[3] - box_cord[1]) * load_config("cs2_aim_target")
dx = center_x_obj - load_config("cs2_screen_width") // 2
dy = center_y_obj - load_config("cs2_screen_height") // 2
aim(dx, dy)
Стало:
Python: Скопировать в буфер обмена
Код:
def detection(frame):
results = model.predict(frame, verbose=False)[0]
if len(results.boxes) > 0:
for box in results[0].boxes:
if box.cls[0].item() in load_config("cs2_obj_detection"):
box_cord = box.xyxy[0].tolist()
center_x_obj = (box_cord[0] + box_cord[2]) / 2
center_y_obj = (box_cord[1] + box_cord[3]) / 2
center_x_obj += load_config("cs2_screen_width") // 2 - load_config("cs2_fov_width") // 2
center_y_obj += load_config("cs2_screen_height") // 2 - load_config("cs2_fov_height") // 2
center_y_obj -= (box_cord[3] - box_cord[1]) * load_config("cs2_aim_target")
dx = center_x_obj - load_config("cs2_screen_width") // 2
dy = center_y_obj - load_config("cs2_screen_height") // 2
aim(dx, dy)
На этом написание самой нейросети закончено, и что получается в итоге:
- Переписана структура всего проекта, что делает код более модульным и лёгким в плане добавления нового функционала.
- Возможность менять значения прямо на лету, не перезапуская каждый раз проект.
- Добавлена простая анти-отдача.
- Добавлена более продвинутая механика анти-отдачи для игры CS2.
- Возможность переключать детекцию с одного объекта на другой.
- Дополнительная программа для конвертации макросов от мышек x7 в формат для текущего проекта.
Написание интерфейса
Теперь можно реализовать интерфейс для управления настройками под каждую игру, чтобы это было ещё проще и быстрее. На Python я знаком только с Eel и Flask, также одним из вариантов разработки для меня был Windows Forms на C#. Eel мне показался более сложным, нежели Flask, а писать интерфейс на Windows Forms в принципе показалось плохой идеей, потому что, ну, потому что зачем? Поэтому был выбран Flask.Что такое Flask
Что такое Flask, мне кажется, знают многие, и особо рассказывать об этом не вижу смысла, поэтому скажу кратко: это фреймворк для Python, который позволяет писать веб-часть приложения на HTML+JS и взаимодействовать с Python частью.Подготовка проекта
Перед началом написания кода нужно подготовить проект. Для этого нужно создать папку templates, в которой будут храниться HTML-файлы. В ней нужно создать файл index.html. Затем нужно создать Python-файл с названием flask_initialization.py, в котором будет происходить инициализация Flask, а также замена значений из конфига значениями с веб-части интерфейса.Python часть интерфейса
После подготовки проекта можно приступать к написанию кода. Сначала будет написана логика на Python, а затем уже веб-часть интерфейса. Для начала нужно зайти в файл flask_initialization.py и импортировать Flask (перед этим, разумеется, нужно Скачать
View hidden content is available for registered users!