OverlordGameDev
Light Weight
- Депозит
- $0
Предисловие
В этой статье будет показано продолжение разработки нейросети: будет переписана структура проекта, добавлены новые механики и улучшены старые.Нововведения в проекте
В данной статье будет реализована анти-отдача в связке с нейронной сетью, которая была разработана в предыдущих статьях. Анти-отдача будет разделена на несколько типов:- Стандартная — это анти-отдача, которая работает путём смещения курсора по оси Y вниз по прямой.
- Кастомная — этот тип анти-отдачи будет работать, используя точные координаты и смещая курсор как по осям X, так и Y с конкретными таймингами. По сути, данный метод будет аналогичен макросам для мышек X7, и каждый макрос будет настроен на определённую игру и конкретное оружие. В данной статье для примера выбрана игра CS2. В связи с усложнённой логикой отдачи в игре CS2, также потребуется изменить функцию наведения и функцию детекции, чтобы добавить возможность выбора объекта для наведения — например, контр-террориста, террориста или обоих.
Нейронная сеть также будет разделена на две версии с возможностью переключения. Это сделано для повышения модульности и универсальности проекта, поскольку для разных игр существуют разные механики.
Также будет написана логика для функции триггер-бота.
Перенос настроек в конфиг файл
Перед тем как приступить к разработке логики анти-отдачи, необходимо подготовить проект, внести некоторые правки и улучшить его базу. В первую очередь, для удобства следует вынести переменные в отдельный конфигурационный файл. Для этого нужно создать файл config.json и записать в него все переменные, которые ранее назначались в начале кода.Код: Скопировать в буфер обмена
Code:
{
"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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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. Для начала нужно создать два ключа в конфиге, которые будут использоваться в будущей функции антиотдачи.Код: Скопировать в буфер обмена
Code:
"anti_recoil_px": 1,
"anti_recoil_time_sleep": 0.001
После добавления ключей в конфиг можно приступать к написанию функции антиотдачи. Называться она будет anti_recoil, и в ней нужно будет прописать цикл while True. Внутри цикла нужно добавить проверку на нажатие левой кнопки мыши. Если левая кнопка мыши нажата, то двигать курсор по y на количество пикселей, указанных в ключе из конфига anti_recoil_px.
Python: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
def game_initialization():
if load_config("game") == "default":
default.start_game()
После написания функции определения игры нужно вызывать эту функцию при запуске, и для этого в главном файле нужно написать соответствующую логику.
Python: Скопировать в буфер обмена
Code:
if __name__ == "__main__":
game_initialization()
Теперь можно вернуться к default.start_game. Для того чтобы нейросеть для игры запустилась, нужно зайти в файл default и создать функцию start, которая будет запускать функцию создания скриншотов и антиотдачи. Раньше это делала эта часть кода:
Python: Скопировать в буфер обмена
Code:
if __name__ == "__main__":
anti_recoil_thread = threading.Thread(target=anti_recoil)
anti_recoil_thread.start()
screenshot()
Теперь же это будет выглядеть так:
Python: Скопировать в буфер обмена
Code:
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_. Это сделано для того, чтобы абсолютно каждый параметр был отдельно настраиваемым для каждой игры.Код: Скопировать в буфер обмена
Code:
"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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
input_data = """
"""
Python: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
elif parts[0] == 'MoveR':
x, y = map(int, parts[1:])
macros.append((x, y, delay))
После того как в словарь были записаны данные, нужно взять этот словарь и записать его в txt файл, используя file.writelines.
Python: Скопировать в буфер обмена
Code:
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, и, к сожалению, не все из них хорошо работают. Но на первое время этого будет достаточно.
Теперь можно приступить к реализации новой логики анти-отдачи, и первым шагом нужно добавить новые ключи со значениями в конфигурационный файл.
Код: Скопировать в буфер обмена
Code:
"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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
# Основной цикл антиотдачи
while True:
for x, y, delay in macros_dict[load_config("cs2_macros_gun")]:
Затем, внутри цикла for нужно умножать x и y на число, указанное в ключе из конфигурационного файла, в котором указано значение для усиления или уменьшения силы отдачи для конкретного оружия.
Python: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
anti_recoil_x += x
anti_recoil_y += y
После этого нужно указать паузу, используя значение из delay, которое было получено из макроса.
Python: Скопировать в буфер обмена
time.sleep(delay)
Затем, в else нужно обнулять значения переменных. else будет срабатывать, когда левая кнопка мыши будет отпущена.
Python: Скопировать в буфер обмена
Code:
else:
anti_recoil_x = 0
anti_recoil_y = 0
break
Теперь в начале функции нужно сделать эти переменные глобальными.
Python: Скопировать в буфер обмена
global anti_recoil_x, anti_recoil_y
И затем нужно вне функции назначить этим переменным значение 0.
Python: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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: Скопировать в буфер обмена
Code:
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 (перед этим, разумеется, нужно скачать библиотеку Flask через pip install).Python: Скопировать в буфер обмена
from flask import Flask, render_template, request, jsonify
Далее нужно инициализировать Flask.
Python: Скопировать в буфер обмена
app = Flask(__name__)
Теперь нужно указать маршрут для страницы интерфейса.
Python: Скопировать в буфер обмена
Code:
@app.route('/')
def index():
return render_template('index.html')
P.S. Если указать в @app.route('/') слеш, то при открытии обычного адреса сайта откроется конкретно страница index. Если указать /index, то страница интерфейса откроется только при переходе на https://адрес_сайта/index.
Далее нужна функция load_config, точно такая же, как и в файлах нейросетевой логики.
Python: Скопировать в буфер обмена
Code:
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)
Далее нужна функция с маршрутом для Flask, работающая с GET-запросами. Эта функция необходима для того, чтобы в дальнейшем веб-часть могла обращаться к ней и получать значения всех параметров из конфигурационного файла для их отображения в веб-интерфейсе.
Python: Скопировать в буфер обмена
Code:
@app.route('/get_config', methods=['GET'])
def get_config():
return jsonify(load_config()), 200
Теперь нужна функция с маршрутом для Flask, работающая с POST-запросами. В эту функцию будут передаваться значения из веб-интерфейса, и затем эти значения будут заменять собой значения в конфигурационном файле.
Python: Скопировать в буфер обмена
Code:
@app.route('/update_config', methods=['POST'])
def update_config():
Внутри функции нужно инициализировать переменную, которая будет вызывать функцию load_config для открытия конфига и чтения из него данных.
Python: Скопировать в буфер обмена
config_data = load_config()
Затем нужно назначить каждому ключу из конфига новое значение, которое будет получено из веб-части интерфейса.
Python: Скопировать в буфер обмена
Code:
config_data['game'] = request.json['game']
config_data['screen_width'] = int(request.json['screen_width'])
config_data['screen_height'] = int(request.json['screen_height'])
config_data['fov_width'] = int(request.json['fov_width'])
config_data['fov_height'] = int(request.json['fov_height'])
config_data['aim_step'] = int(request.json['aim_step'])
config_data['aim_time_sleep'] = float(request.json['aim_time_sleep'])
config_data['aim_target'] = float(request.json['aim_target'])
config_data['anti_recoil_px'] = int(request.json['anti_recoil_px'])
config_data['anti_recoil_time_sleep'] = float(request.json['anti_recoil_time_sleep'])
config_data['cs2_screen_width'] = int(request.json['cs2_screen_width'])
config_data['cs2_screen_height'] = int(request.json['cs2_screen_height'])
config_data['cs2_fov_width'] = int(request.json['cs2_fov_width'])
config_data['cs2_fov_height'] = int(request.json['cs2_fov_height'])
config_data['cs2_aim_step'] = int(request.json['cs2_aim_step'])
config_data['cs2_aim_time_sleep'] = float(request.json['cs2_aim_time_sleep'])
config_data['cs2_aim_target'] = float(request.json['cs2_aim_target'])
config_data['cs2_obj_detection'] = request.json['cs2_obj_detection']
config_data['cs2_macros_gun'] = request.json['cs2_macros_gun']
config_data['cs2_macros_ak_47_adjustment'] = float(request.json['cs2_macros_ak_47_adjustment'])
config_data['cs2_macros_famas_adjustment'] = float(request.json['cs2_macros_famas_adjustment'])
config_data['cs2_macros_m4a1s_adjustment'] = float(request.json['cs2_macros_m4a1s_adjustment'])
config_data['cs2_macros_m4a4_adjustment'] = float(request.json['cs2_macros_m4a4_adjustment'])
config_data['cs2_macros_mac_10_adjustment'] = float(request.json['cs2_macros_mac_10_adjustment'])
config_data['cs2_macros_sg_553_adjustment'] = float(request.json['cs2_macros_sg_553_adjustment'])
P.S. request.json означает, что берутся данные из запроса с веб-интерфейса. То есть Flask отправляет на Python часть JSON-объекта, внутри которого находятся ключи со значениями для каждой из функций. Допустим, request.json['cs2_aim_time_sleep'] берет из полученного от веб-части JSON ключ с названием cs2_aim_time_sleep, и его значение записывается в ключ в JSON-файле конфига, то есть в config_data['cs2_aim_time_sleep'].
Далее нужно перезаписать данные в конфиге.
Python: Скопировать в буфер обмена
Code:
with open('config.json', 'w') as f:
json.dump(config_data, f, indent=4)
Теперь, после перезаписи данных в конфиге, нужно возвращать серверу ответ. В данном случае возвращаться будут обновленные данные config_data.
Python: Скопировать в буфер обмена
return jsonify(config_data), 200
Веб часть интерфейса
С частью логики на Python закончено, и можно приступить к написанию веб-части на HTML. Так как файл HTML был заготовлен ранее, этап его создания можно пропустить. При открытии файла будет уже базовый шаблон (если файл был создан в среде PyCharm).HTML: Скопировать в буфер обмена
Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Configuration</title>
</head>
<body>
</body>
</html>
Все объекты для управления параметрами будут находиться внутри body, и первое, что нужно сделать, это создать форму, в которой будут находиться все остальные объекты.
HTML: Скопировать в буфер обмена
Code:
<form id="config-form">
</form>
Теперь в этой форме нужно расположить объекты для всех значений из конфига, которые нужно редактировать. Все эти объекты однотипны, но несколько из них стоит рассмотреть подробнее. Например, для объекта с выбором игры будут использоваться выпадающие списки с готовыми вариантами, такие объекты обычно называют селекторами.
HTML: Скопировать в буфер обмена
Code:
<select id="game" name="game">
<option value="cs2">CS2</option>
<option value="default">DEFAULT</option>
</select>
Как видно в коде выше, у селектора также есть id. id в принципе должен быть у каждого объекта, к которому планируется потом обращаться. Также внутри селектора находятся объекты <option>, которые составляют выпадающий список. В value указано значение, а после value находится текст, который виден на странице.
Кроме того, есть объект, отображающий текст на странице.
HTML: Скопировать в буфер обмена
<label for="game">Game:</label>
P.S. Это просто текст; таких объектов в HTML достаточно на любой вкус, и перечислять все эти виды текста нет смысла.
В данной реализации интерфейса также присутствует такой тип объектов, как input типа number. Это числовой объект, в который можно вводить числа. Вводить их можно как с помощью клавиатуры, так и с помощью встроенных в этот объект кнопок повышения и уменьшения значения.
HTML: Скопировать в буфер обмена
<input type="number" step="0.001" id="aim_time_sleep" name="aim_time_sleep">
P.S. Также у этого объекта добавлен параметр step. Этот параметр отвечает за то, на сколько будет увеличиваться или уменьшаться значение, если будут использованы встроенные в этот объект кнопки.
Также можно рассмотреть еще один объект типа селектор. Он абсолютно такой же, как и рассмотренный выше, за исключением значений в value.
HTML: Скопировать в буфер обмена
Code:
<select id="cs2_obj_detection" name="cs2_obj_detection">
<option value="[0]">CT</option>
<option value="[1]">T</option>
<option value="[0, 1]">CT, T</option>
</select>
С разбором объектов закончено, теперь нужно написать логику для взаимодействия веб-части с частью логики на Python. Чтобы в HTML вписать JavaScript-код, нужно указать, что это именно JavaScript.
HTML: Скопировать в буфер обмена
Code:
<script>
</script>
Первой функцией, которая будет написана на JavaScript, будет функция загрузки данных из конфига и запись полученных значений в объекты на странице.
JavaScript: Скопировать в буфер обмена
async function loadConfig() {
P.S. Данная функция асинхронна, так что она не будет блокировать никакую другую логику на веб-странице.
Далее, внутри этой функции нужно отправить запрос к функции get_config, которая находится в Python-файле инициализации Flask (у этой функции указан маршрут /get_config, принимающий запросы типа GET).
JavaScript: Скопировать в буфер обмена
const response = await fetch('/get_config');
Далее нужно получать ответ от этой функции обратно на веб-страницу. Напомню, что в ответ на веб-страницу идут текущие данные из конфига. После получения данных они записываются в переменную config.
JavaScript: Скопировать в буфер обмена
const config = await response.json();
Далее нужно назначать каждому объекту страницы с конкретным id значение из полученного от Python ответа, в котором находится ключ с таким же названием, как и id объекта на странице.
JavaScript: Скопировать в буфер обмена
document.getElementById('game').value = config.game || '';
P.S. || '' означает, что если в полученном ответе значение для нужного объекта пустое, то в объект на странице будет записан пустой массив.
Далее идут однотипные строки, как та, что указана выше, для каждого объекта на странице, за исключением объекта, где находится именно массив чисел.
JavaScript: Скопировать в буфер обмена
document.getElementById('cs2_obj_detection').value = JSON.stringify(config.cs2_obj_detection || []);
P.S. JSON.stringify означает, что данные будут конвертированы в строку, так как для объекта на странице нужна именно строка, а не массив, как в конфиге.
С функцией получения данных из конфига закончено, теперь нужно написать функцию для обновления данных в конфиге.
JavaScript: Скопировать в буфер обмена
async function updateConfig() {
P.S. Функция, как и первая, асинхронна, и поэтому не будет блокировать поток.
Далее внутри функции нужно создать JavaScript-объект с названием data. Затем в конкретные ключи этого объекта назначаются значения из объектов на странице с конкретным id (именно для этого к каждому объекту был назначен id).
JavaScript: Скопировать в буфер обмена
game: document.getElementById('game').value,
Далее идут опять же однотипные строки для каждого объекта на странице, за исключением объекта селектора с массивом данных. Так как эта функция создана для того, чтобы отправлять данные на Python-часть для записи в конфиг, а не наоборот, как было в предыдущей функции, то здесь нужно конвертировать значение не в строку, а наоборот — из строки в массив.
JavaScript: Скопировать в буфер обмена
cs2_obj_detection: JSON.parse(document.getElementById('cs2_obj_detection').value),
Далее нужно отправить JavaScript-объект на Python-часть по маршруту /update_config.
Python: Скопировать в буфер обмена
Code:
await fetch('/update_config', {
});
Внутри нужно указать тип запроса, а именно POST.
JavaScript: Скопировать в буфер обмена
method: 'POST',
Далее нужно указать, что данные отправляются в формате JSON.
JavaScript: Скопировать в буфер обмена
Code:
headers: {
'Content-Type': 'application/json'
},
Теперь нужно указать тело запроса (данные, которые будут отправлены).
JavaScript: Скопировать в буфер обмена
body: JSON.stringify(data)
P.S. JSON.stringify преобразует JavaScript-объект data в строку формата JSON.
С функцией отправки данных на Python-часть закончено. Теперь нужно сделать так, чтобы данные на странице обновлялись при открытии и перезапуске страницы, подхватывая данные из конфига. Для этого нужно создать событие, которое будет вызываться при открытии страницы.
JavaScript: Скопировать в буфер обмена
Code:
document.addEventListener("DOMContentLoaded", function() {
});
Далее нужно вызывать первую написанную в JavaScript функцию, а именно, функцию load_config, чтобы отправлять запрос на Python и получать свежие данные из конфига.
JavaScript: Скопировать в буфер обмена
loadConfig();
Теперь нужно создать константную переменную, в которую будет записан id объекта form.
JavaScript: Скопировать в буфер обмена
const form = document.getElementById("config-form");
Далее нужно вызвать событие, которое будет срабатывать при взаимодействии с объектом input внутри объекта form (const form).
JavaScript: Скопировать в буфер обмена
form.addEventListener('input', updateConfig);
P.S. Если произошло взаимодействие, будет вызываться функция updateConfig из JavaScript.
На этом интерфейс закончен, и нейросеть корректно работает, и управление параметрами также успешно функционирует. Можно было бы закончить статью, но по ходу ее написания пришла идея добавить механику триггер-бота в нейросеть для CS2.
Trigger bot
Для начала нужно обозначить несколько переменных в конфиге со значениями.Код: Скопировать в буфер обмена
Code:
"cs2_trigger_bot": true,
"cs2_trigger_bot_zone": 1,
"cs2_trigger_bot_time_sleep": 0.15,
Теперь нужно создать функцию trigger_bot, в которую будут передаваться координаты бокса объекта из функции детекции.
Python: Скопировать в буфер обмена
def trigger_bot(box_cord):
P.S. box_cord — это box.xyxy[0].tolist().
Как устроена система координат
В предыдущей статье был показан код с работой над этими координатами бокса, но не было объяснено, что это за стороны и как они выглядят. Причина этому проста — я сам не до конца понимал, как это устроено, поэтому не решился высказывать ложные объяснения. Сейчас же все стало понятнее.box.xyxy[0].tolist() — это список координат объекта. Первые xy — это левая верхняя точка бокса, вторые xy — это правая нижняя точка бокса. Я в графики не умею, поэтому нарисовал, как смог:
Что стоит держать в уме, разбираясь в моем недо-графике и в принципе в этих координатах:
- Начальная координата по x и y идет из левого верхнего угла экрана.
- Верх y — это визуально низ экрана, а низ y — это визуально верх экрана.
- Когда речь идет о верхней точке бокса, это означает нижнюю точку y, и аналогично с нижней точкой бокса, которая является верхней точкой y, то есть с большим значением y.
Python: Скопировать в буфер обмена
Code:
if ((box_cord[0] - load_config("cs2_trigger_bot_zone")) < (load_config("fov_width") // 2)
< (box_cord[2] + load_config("cs2_trigger_bot_zone")) and
(box_cord[1] - load_config("cs2_trigger_bot_zone")) < (load_config("fov_height") // 2)
< (box_cord[3] + load_config("cs2_trigger_bot_zone"))):
В данном коде присутствует вычисление центра fov, чтобы он и был внутри объекта при проверке.
Вычисление центра fov по x: load_config("fov_width") // 2
Вычисление центра fov по y: load_config("fov_height") // 2
Также в данном коде используется переменная из конфига для увеличения бокса во все стороны:
- box_cord[0] - load_config("cs2_trigger_bot_zone") смещает левую точку x бокса влево.
- box_cord[2] + load_config("cs2_trigger_bot_zone") смещает правую точку x бокса вправо.
- box_cord[1] - load_config("cs2_trigger_bot_zone") смещает верхнюю точку y бокса вверх, то есть уменьшает значение координат.
- box_cord[3] + load_config("cs2_trigger_bot_zone") смещает нижнюю точку y бокса вниз, то есть увеличивает значение координат, тем самым снижает в нижнюю часть экрана.
Далее в функции идет самое простое — просто эмуляция нажатия левой кнопки мыши и затем отпускание левой кнопки мыши, а затем пауза, значение которой также хранится в переменной, созданной ранее в конфиге. Пауза нужна для того, чтобы при использовании триггер-бота нейронка не зажимала, а тапала по пульке, тем самым гасила отдачу, если это штурмовая винтовка, или не стреляла бесконечно с АВП, пока АВП не вошло по новой в зум.
Python: Скопировать в буфер обмена
Code:
# Нажатие левой кнопки мыши
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
# Отпускание левой кнопки мыши
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
# Задержка
time.sleep(load_config("cs2_trigger_bot_time_sleep"))
Теперь нужно вызвать эту функцию в конце функции детекции при условии, что переменная cs2_trigger_bot в конфиге стоит true.
Для этого можно добавить следующий код в конец функции детекции:
Python: Скопировать в буфер обмена
Code:
if load_config("cs2_trigger_bot"):
trigger_bot(box_cord)
Обновление интерфейса
С функцией триггер бота завершено, и теперь можно вывести новые параметры для редактирования на страницу управления. В Python-файле, в котором происходит инициализация Flask и работа с конфигом, нужно перейти в функцию update_config и вписать переменные триггер бота, которые были внесены в файл конфига.Python: Скопировать в буфер обмена
Code:
config_data['cs2_trigger_bot'] = bool(request.json['cs2_trigger_bot'])
config_data['cs2_trigger_bot_zone'] = int(request.json['cs2_trigger_bot_zone'])
config_data['cs2_trigger_bot_time_sleep'] = float(request.json['cs2_trigger_bot_time_sleep'])
Для начала нужно создать объекты на странице, в которых будут храниться значения.
HTML: Скопировать в буфер обмена
Code:
<select id="cs2_trigger_bot" name="cs2_trigger_bot">
<option value="true">TRUE</option>
<option value="false">FALSE</option>
</select><br>
<label for="cs2_trigger_bot_zone">CS2 Trigger Bot Zone:</label>
<input type="number" step="1" id="cs2_trigger_bot_zone" name="cs2_trigger_bot_zone"><br>
<label for="cs2_trigger_bot_time_sleep">CS2 Trigger Bot Time Sleep:</label>
<input type="number" step="0.001" id="cs2_trigger_bot_time_sleep" name="cs2_trigger_bot_time_sleep"><br>
После того как были добавлены объекты, нужно дополнить функцию загрузки конфига, чтобы передать значения в объекты.
JavaScript: Скопировать в буфер обмена
Code:
document.getElementById('cs2_trigger_bot').value = config.cs2_trigger_bot || '';
document.getElementById('cs2_trigger_bot_zone').value = config.cs2_trigger_bot_zone || '';
document.getElementById('cs2_trigger_bot_time_sleep').value = config.cs2_trigger_bot_time_sleep || '';
Далее нужно дополнить функцию отправки данных из объектов на Python часть кода, чтобы данные записались в конфиг.
JavaScript: Скопировать в буфер обмена
Code:
cs2_trigger_bot: document.getElementById('cs2_trigger_bot').value === 'true',
cs2_trigger_bot_zone: document.getElementById('cs2_trigger_bot_zone').value,
cs2_trigger_bot_time_sleep: document.getElementById('cs2_trigger_bot_time_sleep').value,
На этом обновление веб-части закончено.
Небольшая фишка интерфейса
Хочется также отметить небольшую фишку Flask: чтобы её использовать, нужно перейти в главный файл проекта и в параметрах запуска Flask указать такой IP-адрес.Python: Скопировать в буфер обмена
host="0.0.0.0", port="228"
Вот как выглядит полная строка:
Python: Скопировать в буфер обмена
flask_thread = threading.Thread(target=lambda: app.run(host="0.0.0.0", port="228", debug=True, use_reloader=False))
Теперь, когда IP-адрес страницы установлен на четыре нуля, при запуске нейронной сети в консоль будут выводиться два адреса страницы, например, 127.0.0.1 и 192.168. и т.д. Если ваш мобильный телефон или любое другое устройство с браузером подключено к той же сети, что и ПК, вы сможете зайти на панель софта и редактировать настройки прямо с него. Это полезная фишка, когда нужно настроить конфиг, а каждый раз сворачивать игру не хочется.
Вывод
Пожалуй, на этом код и статья завершены. Возможно, материал получился сложным или я плохо объяснил некоторые моменты, но все же надеюсь, что все понятно, так как я старался донести все простым языком, включая правильный расчет антиотдачи при механике стрельбы в CS2. На том же GitHub я не смог найти проектов с логикой для учета анти отдачи при стрельбе; обычно в проектах антиотдача либо отсутствует, либо реализована простым способом, аналогично нашему проекту. Поэтому надеюсь, что информация окажется полезной.P.S Если кто-то действительно дочитал всю статью и есть замечания по коду, с удовольствием выслушаю ваши предложения по оптимизации, так как считаю, что языки программирования я практически не знаю и с многим не знаком. Буду рад узнать что-то новое и улучшить нейросеть.
О следующей статье
Скорее всего, к следующей статье я куплю Arduino и переведу логику эмуляции мыши на него, чтобы иметь возможность играть с нейросетью в игры, где блокируется эмуляция, например, в Warface.Видео презентация работы софта
Что касается видео-презентации работы софта. У меня возникли некоторые сложности с аккаунтом Google, поэтому залить ролик на YouTube не получится. Было принято решение создать канал, на котором будет опубликован ролик работы софта.Канал, в котором находится ролик с работой софта - https://t.me/DungeonAIChannel
Статья в виде документа:
Также, если кому-то удобнее читать статью в более отредактированном формате, вот ссылка на документ: https://docs.google.com/document/d/14WG2L-3PXS6GS81SLjeTxw9kIxTHoSy7_119dh9twjk/edit?usp=sharingПояснения к эффективности софта
P.S. Отдельно хочется прокомментировать пару моментов из видеоролика.В катке на первом месте, скорее всего, был читер, так как в предыдущей игре он набил 120 килов на 10 смертей, и большинство смертей было именно от него.
Чаще всего промахи были из-за плохих макросов, и они просто не могли полностью законтрить отдачу. На калаше отдача начинает уходить в стороны примерно после 10 патронов.
Также, когда я начинал стрелять, я полностью переставал управлять мышью, чтобы наглядно показать, как нейросеть самостоятельно наводится.
На записи видны моменты, когда мышь доводилась медленно. Это связано с датасетом, который состоит всего из 2000 скриншотов, что крайне мало. Из-за плохого датасета определение объектов прерывалось, в результате чего мышь каждые несколько миллисекунд переставала доводиться и не работала антиотдача.
Вследствие всего вышесказанного, хочу подытожить. Большинство косяков во время стрельбы были связаны с плохим качеством датасета и макросов. Считаю, что в ролике было достаточно моментов, когда было видно, что нейронка зарешала, учитывая что лично я играю намного хуже, чем показано в ролике и явно бы не смог 80% убийств совершить в голову.
P.S. Если у кого-то есть макросы для мышек X7, буду рад, если поделитесь в комментариях.
Сделано OverlordGameDev специально для форума XSS.IS