• XSS.stack #1 – первый литературный журнал от юзеров форума

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

OverlordGameDev

RAM
Пользователь
Регистрация
14.07.2024
Сообщения
106
Реакции
131

Предисловие​

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

Конвертация форматов 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
   };
}
Суть тут такая же, как и в предыдущей функции, только наоборот: все значения смещаются не влево, а вправо. Но перед этим удаляется знак # в начале цвета, используя .slice(1), а затем полученное значение преобразуется в целое значение в шестнадцатеричном формате.

Дополнение функций принятия и отправки данных конфига​

После написания функций конвертации цвета из одного формата в другой нужно дополнить функцию 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;
В данном коде берутся значения из трех ключей конфига и конвертируются, используя функцию конвертации из RGB в HEX; затем полученное значение записывается в объект на странице.

Теперь нужно дополнить функцию отправки конфига под названием update_config.
JavaScript:
cs2_box_color: hexToRgb(document.getElementById('cs2_box_color').value),
В данном коде берется значение из объекта на странице, конвертируется в RGB формат, используя ранее написанную функцию, и полученные значения записываются в ключ cs2_box_color.

После этого нужно дополнить функцию 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'])
}
В данном коде берутся значения из ключей r, g, b, которые находятся в ключе cs2_box_color, отправленным запросом с веб-страницы, и затем значения записываются в ключи r, g, b в ключе cs2_box_color уже в конфигурационном файле.

Теперь можно приступить к написанию самой логики отрисовки боксов на экране.

Как будет работать отрисовка боксов​

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

Когда с примерной работой отрисовки все понятно, можно начинать писать сам код. Для начала нужно создать отдельный файл для всей логики отрисовки; в нем будет инициализация конфига, 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)
cs2_screen_width и 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)
Вызывается win32gui.SetWindowLong для изменения стилей окна; в скобках указаны три параметра: дескриптор, флаг, указывающий на изменение именно расширенных стилей окна, и win32gui.GetWindowLong, который получает текущие расширенные стили и добавляет к этим стилям новый стиль 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()

После этого уже можно запустить нейросеть и увидеть результат:
1727108801182.png


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


Возможность отключать отрисовку​

Также, как и упоминалось ранее, данная функция очень ресурсоемкая, и из-за ее скорости обработки скорость работы всей нейросети достаточно сильно падает. Конечно, в таком случае софт все равно дает прирост к вашему аиму, но если хочется максимального профита, то лучше данную функцию отключать. Для этого нужно в конфиге создать новый ключ для включения и отключения отрисовки боксов.
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',
=== 'true' проверяет значение из объекта на то, является ли оно true. Если является, то записывается true; если не является, то false. Это сделано потому, что значение в селекторе по умолчанию текстовое, а данная проверка в любом случае переведет значение в тип булевый. Проще говоря, этот механизм по сути превращает строку 'true' в булевое true, а всё остальное (включая 'false') — в булевое false. Это делается автоматически благодаря сравнению === '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 по x: load_config("cs2_screen_width") // 2 - load_config("cs2_fov_width") // 2
Точка 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()
Проверка с функцией сделана именно в этом месте, а не рядом с вызовом отрисовки боксов, для того чтобы отрисовка FOV работала всегда. Если её сделать рядом с отрисовкой боксов, то FOV будет отрисовываться только когда обнаружен противник, так как отрисовка боксов находится в цикле for, в котором определяются найденные объекты, а цикл находится в if, который проверяет, чтобы объекты в принципе были. На этом с отрисовкой FOV также закончено, и вот какой результат:
1727109959217.png


Функция отрисовки 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)
Сначала из cv2.getTickCount() (текущее значение тиков) вычитается значение тиков при старте таймера (start_timer), затем количество тиков в секунду (cv2.getTickFrequency()) делится на полученное значение. Функция cv2.getTickFrequency() — это количество тактов процессора, то есть количество тиков, происходящих за одну секунду, и оно зависит от частоты процессора.

Способы подсчета FPS​

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

Теперь, под последней написанной строкой, нужно вызывать функцию отрисовки, но делать это нужно лишь проверив в конфиге флаг включения и отключения.
Python:
if load_config("cs2_draw_fps_valid"):
   draw_fps(str(int(fps)))
В функцию передается полученное ранее значение FPS. int указан для того, чтобы перевести значение FPS в целое число, так как оно получается изначально дробным. str нужно, чтобы целое число перевести в формат строки.

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

Новая механика наводки и антиотдачи​

Зачем нужен новый режим наводки и антиотдачи​

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

Подготовка проекта​

И первое, что нужно сделать, — это снова добавить новые ключи в конфиг для большей настраиваемости софта.
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,
Первый ключ отвечает за то, за сколько шагов курсор нацелится на объект — за один шаг или за несколько. После каждого шага будет происходить пауза, указанная во втором ключе. Третий ключ отвечает за то, на сколько пикселей будет опускаться курсор по Y при антиотдаче за один шаг. После каждого шага антиотдачи будет происходить пауза длиной в значение из последнего ключа.

После того как ключи были добавлены, нужно, как и раньше, добавить на веб-странице объекты.
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"))
Таким образом, получается, что путь до объекта будет разделен, например, на 2 части, и мышь будет передвигаться 2 раза на разделенное число. Получится, что мышь доходит до объекта не за одно действие, а в несколько, и так как после каждого шага указана пауза, мышь перемещается более плавно, а не телепортируется в конечную точку.

Функция анти отдачи​

Пока что функция наводки пистолетом закончена, и можно начать писать функцию антиотдачи для пистолета. Называться функция будет 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
 
1. RGB to Hex conversion using bit shifts, how .slice(1) works and what uppercase letters mean in Hex values. 101 JS from StackOverflow.
2. <input type="color"> HTML element + processing its value is basic web dev.
3. Flask integration for updating the config is presented like it’s a big deal, but this is the default workflow in any basic web app. Just standard CRUD operations presented in a way to mislead into thinking there’s more complexity.
4. Creating a transparent window with a hacky Windows API call (win32gui.SetWindowLong) to make it overlay the game is clunky + outdated.
5. Pygame for rendering HUD elements?
6. Adding config keys and HTML elements repeatedly (for box drawing, FOV, FPS, etc.) is the same exact process over and over again, but he milks this for extra word count.
7. How to draw FPS is just a copy-paste of the box and FOV sections. Single paragraph would've been enough.
8. Uses a mouse event loop to move the cursor by small amounts != "anti-recoil."
9. Just moving the cursor in steps + time.sleep() == lol
 
And there's no neural network here.
 
And there's no neural network here.
1. RGB to Hex conversion using bit shifts, how .slice(1) works and what uppercase letters mean in Hex values. 101 JS from StackOverflow.
2. <input type="color"> HTML element + processing its value is basic web dev.
3. Flask integration for updating the config is presented like it’s a big deal, but this is the default workflow in any basic web app. Just standard CRUD operations presented in a way to mislead into thinking there’s more complexity.
4. Creating a transparent window with a hacky Windows API call (win32gui.SetWindowLong) to make it overlay the game is clunky + outdated.
5. Pygame for rendering HUD elements?
6. Adding config keys and HTML elements repeatedly (for box drawing, FOV, FPS, etc.) is the same exact process over and over again, but he milks this for extra word count.
7. How to draw FPS is just a copy-paste of the box and FOV sections. Single paragraph would've been enough.
8. Uses a mouse event loop to move the cursor by small amounts != "anti-recoil."
9. Just moving the cursor in steps + time.sleep() == lol
Хочешь до меня что-то донести? Пиши на русском. Я не собираюсь тратить время на перевод
 
Хочешь до меня что-то донести? Пиши на русском. Я не собираюсь тратить время на перевод
Время на перевод (2 секунды) >>> Время на написание "Хочешь до меня что-то донести? Пиши на русском. Я не собираюсь тратить время на перевод" (20 секунд).
И я хочу увидеть, как ты сражаешься технически публично.

1. Преобразование RGB в Hex с использованием битовых сдвигов, как работает .slice(1) и что означают заглавные буквы в шестнадцатеричных значениях. 101 JS со StackOverflow.
2. HTML-элемент <input type="color"> + обработка его значения — это базовая веб-разработка.
3. Интеграция Flask для обновления конфигурации представлена так, как будто это большое дело, но это рабочий процесс по умолчанию в любом базовом веб-приложении. Просто стандартные операции CRUD, представленные таким образом, чтобы ввести в заблуждение и заставить думать, что все сложнее.
4. Создание прозрачного окна с помощью хакерского вызова Windows API (win32gui.SetWindowLong) для наложения его на игру — неуклюже + устарело.
5. Pygame для рендеринга элементов HUD?
6. Повторное добавление ключей конфигурации и HTML-элементов (для рисования рамок, FOV, FPS и т. д.) — это один и тот же процесс снова и снова, но он использует это для дополнительного количества слов.
7. Как рисовать FPS — это просто копипаст разделов box и FOV. Одного абзаца было бы достаточно.
8. Использует цикл событий мыши для перемещения курсора на небольшие расстояния != "anti-recoil".
9. Просто перемещает курсор пошагово + time.sleep() == lol


Как мой безупречный русский?
 
Время на перевод (2 секунды) >>> Время на написание "Хочешь до меня что-то донести? Пиши на русском. Я не собираюсь тратить время на перевод" (20 секунд).
И я хочу увидеть, как ты сражаешься технически публично.

1. Преобразование RGB в Hex с использованием битовых сдвигов, как работает .slice(1) и что означают заглавные буквы в шестнадцатеричных значениях. 101 JS со StackOverflow.
2. HTML-элемент <input type="color"> + обработка его значения — это базовая веб-разработка.
3. Интеграция Flask для обновления конфигурации представлена так, как будто это большое дело, но это рабочий процесс по умолчанию в любом базовом веб-приложении. Просто стандартные операции CRUD, представленные таким образом, чтобы ввести в заблуждение и заставить думать, что все сложнее.
4. Создание прозрачного окна с помощью хакерского вызова Windows API (win32gui.SetWindowLong) для наложения его на игру — неуклюже + устарело.
5. Pygame для рендеринга элементов HUD?
6. Повторное добавление ключей конфигурации и HTML-элементов (для рисования рамок, FOV, FPS и т. д.) — это один и тот же процесс снова и снова, но он использует это для дополнительного количества слов.
7. Как рисовать FPS — это просто копипаст разделов box и FOV. Одного абзаца было бы достаточно.
8. Использует цикл событий мыши для перемещения курсора на небольшие расстояния != "anti-recoil".
9. Просто перемещает курсор пошагово + time.sleep() == lol


Как мой безупречный русский?
Я никогда не говорил что я профессионал, более того, я только начал программировать и все мои статьи это то что я только что изучил. admin говорил что можно выпускать статьи о своем опыте, удачах и неудачах, о том какие трудности были при изучении и т.д. Именно это я и делаю. Вместо того что бы спамить под каждой статьей и слать глупые мемы, ты можешь дать советы по коду.
 
Время на перевод (2 секунды) >>> Время на написание "Хочешь до меня что-то донести? Пиши на русском. Я не собираюсь тратить время на перевод" (20 секунд).
И я хочу увидеть, как ты сражаешься технически публично.

1. Преобразование RGB в Hex с использованием битовых сдвигов, как работает .slice(1) и что означают заглавные буквы в шестнадцатеричных значениях. 101 JS со StackOverflow.
2. HTML-элемент <input type="color"> + обработка его значения — это базовая веб-разработка.
3. Интеграция Flask для обновления конфигурации представлена так, как будто это большое дело, но это рабочий процесс по умолчанию в любом базовом веб-приложении. Просто стандартные операции CRUD, представленные таким образом, чтобы ввести в заблуждение и заставить думать, что все сложнее.
4. Создание прозрачного окна с помощью хакерского вызова Windows API (win32gui.SetWindowLong) для наложения его на игру — неуклюже + устарело.
5. Pygame для рендеринга элементов HUD?
6. Повторное добавление ключей конфигурации и HTML-элементов (для рисования рамок, FOV, FPS и т. д.) — это один и тот же процесс снова и снова, но он использует это для дополнительного количества слов.
7. Как рисовать FPS — это просто копипаст разделов box и FOV. Одного абзаца было бы достаточно.
8. Использует цикл событий мыши для перемещения курсора на небольшие расстояния != "anti-recoil".
9. Просто перемещает курсор пошагово + time.sleep() == lol


Как мой безупречный русский?
Так же ты делал мне замечания по длинным объяснениям банального кода. Я делал их такими длинными потому что я хочу что бы такие же новички как и я смогли все понять. Я убрал длинные объяснения и стараюсь объяснять кратко, так что я прислушиваясь к советам если они адекватные а не с примесью смехуечков как ты любишь
 
Так же ты делал мне замечания по длинным объяснениям банального кода. Я делал их такими длинными потому что я хочу что бы такие же новички как и я смогли все понять. Я убрал длинные объяснения и стараюсь объяснять кратко, так что я прислушиваясь к советам если они адекватные а не с примесью смехуечков как ты любишь
My young padawan, you have to first understand the concept of https://en.wikipedia.org/wiki/Value_(economics)

The core concept of value in economics rests on this idea of exchange and the relative desires of individuals. It's not just about having something useful, but having something that someone else finds useful and is willing to give you something in return for. This "something" could be money, goods, or services. Now charity has value as well (personal satisfaction, virtue signalling etc), if you think about it.

On our forum, the value is measured by the usefulness of our articles to the forum. You write for the community. And the admin wants to incentivize community engagement with a payment scheme.
Now don't abuse his generosity. If I were the admin for a day, I would have straight up booted you guys permanently with ruthless mocking and disgrace. But the admin didn't.

The articles are trash. And my memes were funny. No debate on either of those points.)

Observe others. Learn. Ask questions. Answer what you know. Be open to technical criticism. Debate. Repeat.
Then once you have something interesting to give back. Give back to the forum.)

And I only came up with technical criticism for the most part. Plus some memes.)

Вот скажи мне, американец, в чём сила?
Brother2.jpg
 
Русский в студию
m12n3.jpg


Pick a textbook, open the editor and study. Forget writing for payments for now. There are other ways to engage: ask, answer, debate.
 
Посмотреть вложение 97666

Pick a textbook, open the editor and study. Forget writing for payments for now. There are other ways to engage: ask, answer, debate.
Я надеялся на адекватный разговор, но ты всё же решил повести себя как клоун 🤡.
Я не перевожу твой текст только потому что это ты завел со мной диалог, если ты хочешь что то мне доказать, общайся на моем языке. Я не собираюсь подстраиваться под тебя
 
Не в картинке сила, американец, а в правде.
Правда вот в том, что твои, как ты считаешь, заумные комментарии, никому, увы, нахуй не сдались не делают лучше.
Ты критикуешь ради критики, но не ради улучшения статьи. Вся твоя позиция строится исключительно на том, чтобы сделать форум лучше, ведь нужно вносить вклад в его развитие. Однако, кроме слов, в которых отсутствует понятие "здравия критика", ты не сделал ничего. Ты не смог задрать планку качества, показав как и о чём пишешь ты, либо бездарен и пользуешься втихую ИИ, чтобы подобрать аргументы поскорее. Ты не покупаешь Премиум, которым ты всё пытался выманить меня на дуэль, ведь для тебя это копейки, как ты говорил, а это один из источников дохода в бизнес модели. Либо общайся без клоунады и псевдоблагородства, помогая, а не пытаясь доносить через претензию, как вы янки это любите. Либо завязывай с комментариями, от них реально скорее уже тянет блевать.
 
Я надеялся на адекватный разговор, но ты всё же решил повести себя как клоун 🤡.
Я не перевожу твой текст только потому что это ты завел со мной диалог, если ты хочешь что то мне доказать, общайся на моем языке. Я не собираюсь подстраиваться под тебя
Не в картинке сила, американец, а в правде.
Правда вот в том, что твои, как ты считаешь, заумные комментарии, никому, увы, нахуй не сдались не делают лучше.
Ты критикуешь ради критики, но не ради улучшения статьи. Вся твоя позиция строится исключительно на том, чтобы сделать форум лучше, ведь нужно вносить вклад в его развитие. Однако, кроме слов, в которых отсутствует понятие "здравия критика", ты не сделал ничего. Ты не смог задрать планку качества, показав как и о чём пишешь ты, либо бездарен и пользуешься втихую ИИ, чтобы подобрать аргументы поскорее. Ты не покупаешь Премиум, которым ты всё пытался выманить меня на дуэль, ведь для тебя это копейки, как ты говорил, а это один из источников дохода в бизнес модели. Либо общайся без клоунады и псевдоблагородства, помогая, а не пытаясь доносить через претензию, как вы янки это любите. Либо завязывай с комментариями, от них реально скорее уже тянет блевать.
С чего ты взял, что мне есть что тебе доказывать? Я хочу доказать свою техническую критику администратору. Меня интересует только следующее:
1. если вы искренне заинтересованы в техническом разговоре. Я с удовольствием займусь.
2. если вы или кто-либо пишет мусорные статьи за платежи. Я выскажу свое мнение, беспощадно.

12knk.jpg



PS.
Debate pro tip: pause for a second and let the opponents shoot themselves in the foot.

1asd2na.jpg
 
Последнее редактирование:
Не в картинке сила, американец, а в правде.
Правда вот в том, что твои, как ты считаешь, заумные комментарии, никому, увы, нахуй не сдались не делают лучше.
Ты критикуешь ради критики, но не ради улучшения статьи. Вся твоя позиция строится исключительно на том, чтобы сделать форум лучше, ведь нужно вносить вклад в его развитие. Однако, кроме слов, в которых отсутствует понятие "здравия критика", ты не сделал ничего. Ты не смог задрать планку качества, показав как и о чём пишешь ты, либо бездарен и пользуешься втихую ИИ, чтобы подобрать аргументы поскорее. Ты не покупаешь Премиум, которым ты всё пытался выманить меня на дуэль, ведь для тебя это копейки, как ты говорил, а это один из источников дохода в бизнес модели. Либо общайся без клоунады и псевдоблагородства, помогая, а не пытаясь доносить через претензию, как вы янки это любите. Либо завязывай с комментариями, от них реально скорее уже тянет блевать.
Он мне там какуют статью скинул, что-то об выгоде для обеих сторон, в плане как автору и читателю. Но суть в том что лично он на форум принес только срачи и негатив. Я уже убедился что он не способен на адекватный диалог, он может только кричать, но не слушать других.
Расписывать и спорить конечно можно долго, но это абсолютно бессмысленно
 
С чего ты взял, что мне есть что тебе доказывать? Я хочу доказать свою техническую критику администратору. Меня интересует только следующее:
1. если вы искренне заинтересованы в техническом разговоре. Я с удовольствием займусь.
2. если вы или кто-либо пишет мусорные статьи за платежи. Я выскажу свое мнение, беспощадно.

Посмотреть вложение 97667
Перед тем как я выпускаю статью, я спрашиваю мнение админа. Если он одобряет, я начинаю писать статью. Я тебе уже сказал, есть претензии по реализации кода? Покажи свою реализацию, ту которая будет лучше.
 
С чего ты взял, что мне есть что тебе доказывать? Я хочу доказать свою техническую критику администратору. Меня интересует только следующее:
1. если вы искренне заинтересованы в техническом разговоре. Я с удовольствием займусь.
2. если вы или кто-либо пишет мусорные статьи за платежи. Я выскажу свое мнение, беспощадно.

Посмотреть вложение 97667
Так для чего кому-либо твоё мнение? Знаешь что выдаёт твоё желание хайпануть, а не якобы желание сделать лучше? Ты не идёшь в личку к автору, в попытках исправить качество. Ты ни разу не попытался сделать структурный анализ, не попытался сделать акцент на том, чтобы сделать правки по коду, как мегакрутой разработчик. Вся твоя кампания по хайпу заключается в том, чтобы чаще тегать админа и поскорее обратить на себя внимания в топике, где не идёт даже обсуждение статей. Ты правда думаешь, что те 50-150$, которые объективно стоят статьи написанные в таком объёме, нарушат баланс силы во Вселенной? Тогда поздравляю, Шарик, ты балбес. Займись чем-то полезным, ведь у админа уже есть объективные критерии оценок, которые вряд ли изменятся из-за беспонтового выскочки.
 
С чего ты взял, что мне есть что тебе доказывать? Я хочу доказать свою техническую критику администратору. Меня интересует только следующее:
1. если вы искренне заинтересованы в техническом разговоре. Я с удовольствием займусь.
2. если вы или кто-либо пишет мусорные статьи за платежи. Я выскажу свое мнение, беспощадно.

Посмотреть вложение 97667
Так же, admin говорил что качество кода не учитывается в подобных статьях. Важна идея. Например моя идея о написании нейросети для игр, статья уникальна так как никто подобных статей не делал. Да, качество кода хромает, но статья не об этом
 
Так для чего кому-либо твоё мнение? Знаешь что выдаёт твоё желание хайпануть, а не якобы желание сделать лучше? Ты не идёшь в личку к автору, в попытках исправить качество. Ты ни разу не попытался сделать структурный анализ, не попытался сделать акцент на том, чтобы сделать правки по коду, как мегакрутой разработчик. Вся твоя кампания по хайпу заключается в том, чтобы чаще тегать админа и поскорее обратить на себя внимания в топике, где не идёт даже обсуждение статей. Ты правда думаешь, что те 50-150$, которые объективно стоят статьи написанные в таком объёме, нарушат баланс силы во Вселенной? Тогда поздравляю, Шарик, ты балбес. Займись чем-то полезным, ведь у админа уже есть объективные критерии оценок, которые вряд ли изменятся из-за беспонтового выскочки.
Забей, я предлагаю просто игнорировать абсолютно все его сообщения. Он как капризный ребенок которому не дали игрушку. Если не обращать на его истерики, он смирится и утихнет
 
Ты ни разу не попытался сделать структурный анализ, не попытался сделать акцент на том, чтобы сделать правки по коду, как мегакрутой разработчик
Так ему же писал один из авторов, мол хочешь что-то поправить, объяснить или подискутировать, напиши в ЛС а не устраивай срачи в теме. Ну и что ты думаешь?) он проигнорировал и продолжил срач в теме) Считаю он уже давно пойман за руку как дешёвка
 
Забей, я предлагаю просто игнорировать абсолютно все его сообщения. Он как капризный ребенок которому не дали игрушку. Если не обращать на его истерики, он смирится и утихнет
Я и не обращал. Долгое время в целом. Да и продолжу эту практику после сегодняшнего нашего с ним "диалога". Однако, иногда и мне хочется поиграться и понять вообще посыл, логику и мотивацию "задиры", "буллера" ;)

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


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх