Pythonic LoadRunner Framework

Высоконагруженный модульный фреймворк для Doomsday: Last Survivors. Синхронный линейный интерфейс скриптов VUser в стиле HP/MicroFocus LoadRunner, оркестрируемый асинхронным ядром конкурентности Trio для параллельного запуска 50+ ботов.

● Trio Structured Concurrency ● Zero-Async VUser Scripts ● VuGen LoadRunner API ● Loguru Per-User Sinks

1. Концепция и разделение конкурентности

Фреймворк устраняет конфликт между высокой нагрузкой (50+ сетевых сокетов) и читаемостью прикладной логики. Автор скрипта пишет линейный синхронный код без единого async или await, а оркестратор Trio распределяет потоки воркеров и контролирует лимиты соединений.

Контроллер сценариев (Trio)

controller/scenario.py

Оркестратор теста на базе структурированной конкурентности trio.open_nursery. Управляет CapacityLimiter (пул до 50 сокетов), поэтапным стартом Ramp-up (по 5 ботов каждые 2 секунды), паузами Pacing между прогонами и автоматической отменой при остановке.

Скрипт VUser (LoadRunner C-Style)

scripts/*.py

Линейный синхронный код. Вся многопоточность изолирована в threading.local(). Любой вызов lr.transaction(), lr.think_time(), lr.save_string() или lr.send_and_wait() автоматически привязан к персональному экземпляру бота.

2. Справочник функций модуля core.lr (LoadRunner VuGen API)

Модуль core.lr полностью воспроизводит интерфейс прикладного программирования MicroFocus LoadRunner (VuGen). Все вызовы изолированы в контексте активного виртуального пользователя (threading.local), поддерживают типизацию и автокомплит в IDE.

SLA & ТАЙМЕРЫ

2.1. Измерение транзакций (Transactions)

Функция Сигнатура Python Поведение и особенности
lr_start_transaction lr.start_transaction(name: str) Запуск таймера высокой точности (time.perf_counter()). Транзакции опциональны и служат только для замера задержек и SLA.
lr_end_transaction lr.end_transaction(name: str, status: str = LR_AUTO) Остановка таймера и фиксация времени отклика. LR_AUTO автоматически выставляет PASS или FAIL. Также поддерживаются LR_PASS, LR_FAIL, LR_STOP.
Контекстный менеджер with lr.transaction(name: str, status: str = LR_AUTO): Синтаксический сахар: автоматически замеряет длительность блока, а при возникновении необработанных ошибок транслирует статус в LR_FAIL.
СЕТЬ & PROTOBUF

2.2. Сетевые вызовы и отправка Protobuf (Network Protocol)

Функция Сигнатура Python Поведение и особенности
lr_send lr.send(command, payload=None, **params) Асинхронная отправка фрейма в сокет без ожидания ответа. Поддерживает имена ("KMsgCl2GspingRequest"), шестнадцатеричные опкоды (0x2716) и нативные параметры.
lr_send_and_wait lr.send_and_wait(command, expected_reply, timeout=5.0, **params) Синхронная отправка с ожиданием ответа сервера. Автоматически сериализует аргументы в бинарный Protobuf по схеме opcodes.json. Выбрасывает TimeoutError при превышении лимита времени.
КОРРЕЛЯЦИЯ

2.3. Корреляция и извлечение данных (Correlation)

Функция Сигнатура Python Поведение и особенности
lr_extract_param lr.extract_param(reply, field_path: str, param_name: str) Извлекает значение поля из ответа сервера (DlsFrame или словарь) и сохраняет в параметр param_name. Автоматически сопоставляет символьные имена ("value") с номерами тегов Protobuf через реестр opcodes.json.
ПАРАМЕТРИЗАЦИЯ

2.4. Параметризация и контекст VUser (Parameter Storage)

Функция Сигнатура Python Поведение и особенности
lr_save_string lr.save_string(name: str, value: str) Сохранение строкового параметра в изолированное локальное хранилище активного бота.
lr_save_int lr.save_int(name: str, value: int) Сохранение целочисленного параметра.
lr_get_param lr.get_param(name: str, default=None) Получение значения сохраненного параметра с поддержкой политики Update value on: Each occurrence.
lr_eval_string lr.eval_string(template: str) Динамическая подстановка маркеров {ParamName} в строку перед отправкой на сервер.
ОШИБКИ & FLOW

2.5. Управление потоком исполнения и обработка ошибок

Функция Сигнатура Python Поведение и особенности
lr_continue_on_error lr.continue_on_error(value: int = 1) Управление реакцией: 1 (продолжать при ошибках) или 0 (прерывать). Имеет наивысший приоритет над RTS. Поддерживается контекстный менеджер: with lr.continue_on_error(1):.
lr_check_error_code lr.check_error_code(reply, allowed: list = [0]) Проверка серверного кода ошибки (поле 99). При недопустимом коде помечает активные транзакции как FAIL и останавливает итерацию при continue_on_error=0.
lr_abort lr.abort(msg: str) Немедленное прерывание текущей итерации VUser с выбросом VUserAbortException.
lr_exit lr.exit(option, status, msg: str) Управляемый выход: LR_EXIT_VUSER (остановить бота насовсем), LR_EXIT_ACTION_AND_CONTINUE (прервать текущее действие), LR_EXIT_ITERATION_AND_CONTINUE (перейти к следующей итерации).
СЕССИЯ & ПОВЕДЕНИЕ

2.6. Управление сессией и паузы размышления (Think Time)

Функция Сигнатура Python Поведение и особенности
lr_login lr.login(user_id=None, email=None, password=None) Полный цикл входа: проверка токенов, TCP-соединение, рукопожатие канала (0x279f → 0x27a0) и вход в мир (0x27a1 → 0x27a2). Вызывается в vuser_init().
lr_logout lr.logout() Штатный выход из игры, закрытие сокета и корректное освобождение потоков. Вызывается в vuser_end().
lr_think_time lr.think_time(min_sec: float, max_sec: float = None) Имитация пауз размышления человека. Подчиняется настройкам RTS: поддерживает отключение (ignore), масштабирование (factor) и ограничение максимума (max_limit_sec).
ЛОГИРОВАНИЕ

2.7. Логирование и вывод сообщений (Logging)

Функция Сигнатура Python Поведение и особенности
lr_output_message lr.output_message(msg: str) Информационное сообщение VUser. Автоматически форматирует плейсхолдеры {Param} через eval_string и пишет в персональный лог бота и общий лог.
lr_error_message lr.error_message(msg: str) Сообщение об ошибке. Записывается в лог бота и системный errors.log. Автоматически помечает открытые транзакции как сбойные при включенной опции fail_open_transactions_on_error.
log lr.log(msg: str) Краткий универсальный синоним для output_message.

3. Жизненный цикл VUser и Run-Time Settings (RTS)

1

Инициализация и вход в игру (vuser_init)

Выполняется ровно один раз при запуске скрипта. Вызывает lr.login(): получение сессионных токенов, TCP-соединение с GameServer, авторизация канала (0x279f → 0x27a0) и вход в игровой мир (0x27a1 → 0x27a2).

2

Основной цикл действий (action)

Основное тело скрипта. Выполняется циклически N итераций (задается в параметре rts["run_logic"]["iterations"] скрипта). Между итерациями выдерживается настраиваемый Pacing (asap, fixed delay, random delay).

3

Завершение работы и выход из игры (vuser_end & teardown)

Выполняется ровно один раз по завершении всех итераций. Вызывает lr.logout(): штатный выход из игрового мира, закрытие сетевого сокета и освобождение системных ресурсов.

Настройки выполнения скрипта — Run-Time Settings (RTS)

В точности как в LoadRunner VuGen, параметры Run Logic (итерации, pacing), Think Time, Error Handling и Logging задаются прямо в коде скрипта:

class DailyFarmScript(VUserScript): rts = { "run_logic": { "iterations": 2, # Число повторов action() за прогон скрипта "pacing": { "type": "random_delay", # Пауза между итерациями: "asap", "fixed", "random_delay" "min_sec": 3.0, "max_sec": 6.0 } }, "think_time": { "mode": "replay", # "replay" — воспроизводить, "ignore" — выключить "factor": 1.0, # Масштабирование времени (0.5x, 2.0x) "max_limit_sec": 10.0 # Верхний предел паузы lr.think_time() }, "error_handling": { "continue_on_error": True, # Продолжать ли следующую итерацию при сбое "fail_open_transactions_on_error": True # Помечать ли открытые транзакции как FAIL }, "logging": { "log_level": "standard" # "standard", "extended", "errors_only" } }

4. Параметризация и датапулы (VuGen & Controller)

Каноническая реализация подсистемы параметров HP/MicroFocus LoadRunner: параметр — это именованный маркер в коде ({UserName}, {UserId}), а датапул — внешний табличный источник данных (напрямую logins.json, либо .dat/.csv). Скрипт оперирует абстрактными именами параметров, а рантайм и контроллер сценария управляют распределением непересекающихся блоков строк и синхронизацией столбцов.

Жизненный цикл параметра

VuGen → Controller → Runtime
  1. Инициализация (старт VUser): Контроллер загружает файл датапула 1 раз и при режиме Unique выделяет боту персональный непересекающийся блок строк: [start_row, end_row).
  2. Подстановка в рантайме: Маркер {ParamName} подставляется движком в строку запроса непосредственно перед отправкой пакета на сервер.
  3. Смена итерации: В начале каждой новой итерации Each iteration сдвигает счетчик строки, Once сохраняет значение, а Each occurrence сбрасывается в начало.
  4. Исчерпание данных: При выходе за границу блока срабатывает политика When out of values (Abort Vuser, Cyclic, Last value).

Синхронизация столбцов (Same Line As)

Схема D (Логин + Пароль)

Если параметры UserId, Email и Password берут данные из единого файла logins.json, настройка same_line_as="UserId" гарантирует, что пароль и почта берутся строго из той же самой записи, исключая рассинхронизацию связок учетных записей.

parameters = { "UserId": { "type": "file", "filename": "logins.json", "column": "UserId", "select_row": "unique", "update_on": "once", "when_out_of_values": "abort" }, "Password": { "type": "file", "filename": "logins.json", "column": "Password", "same_line_as": "UserId" # Строго та же строка! } }
Атрибут Возможные значения Описание поведения
select_row unique, sequential, random, same_line_as Unique — контроллер делит строки на равные блоки между VUsers без пересечений. Sequential — последовательно с 0. Random — случайная строка. Same line as — строка ведущего параметра.
update_on each_iteration, each_occurrence, once Each iteration — смена строки на каждой новой итерации action(). Once — выбор 1 раз при старте VUser (идеально для логинов). Each occurrence — смена при каждом обращении к параметру.
when_out_of_values abort, cyclic, last_value Abort Vuser — завершение бота с ошибкой при нехватке строк. Cyclic — сброс счетчика в начало блока. Last value — использование последнего значения.

Типовые схемы применения в сценариях

  • Схема A (Уникальный логин на бота): Select: Unique + Update: Once + Abort — бот логинится один раз в vuser_init и выполняет итерации под своей учетной записью.
  • Схема B (Новый логин на каждую итерацию): Select: Unique + Update: Each iteration + Abort — требует запаса данных: Строк ≥ VUsers × Итераций.
  • Схема C (Общий пул данных): Select: Random + Update: Each iteration + Cyclic — все боты черпают данные из общего датапула.
  • Схема D (Синхронная пара): Password ссылается на same_line_as="UserId".

5. Анатомия проекта и сетевой стек GameConnection

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

5.1. Полная структура каталогов z:/igg/igg_framework/

z:/igg/igg_framework/ ├── requirements.txt # Зависимости для чистой Ubuntu Linux (trio, loguru) │ ├── docs/ # Документация в формате HTML │ ├── readme.html # Сетевой протокол, разбор WAF и опровержение античита │ └── framework_guide.html # Данное интерактивное руководство по фреймворку │ ├── scenarios/ # Автономные сценарии нагрузки (LoadRunner Scenario) │ └── sc_daily_farm.py # Сценарий ежедневной рутины (прямой запуск python scenarios/sc_daily_farm.py) │ ├── data/ # Датапулы параметров, учетных записей и база команд (JSON) │ ├── logins.json # Единый реестр аккаунтов и датапул (UserId, Email, Password, Group) │ ├── env_config.json # Глобальные игровые константы (GameId, LoginServer, PortOffset) │ ├── opcodes.json # База схем опкодов и полей Protobuf (114 000 строк, 850+ команд) │ └── accounts/ # Сессионный кэш токенов персонажей │ └── visbot01_config.json # Токены Kong Gateway, LoginServer и персонажей │ ├── core/ # Укрупненные библиотеки ядра │ ├── parameters.py # Подсистема параметров: DataFileTable (JSON/CSV/DAT), DataPoolRegistry, VUserParamContext │ ├── config.py # Конфигурация + Loguru логирование (ротация 20MB, zip, очистка) │ ├── login.py # Единый модуль авторизации: Passport Web, WAF Anubis, Gateway, LoginServer │ ├── game.py # Сетевой клиент GameConnection: DlsFrame, Protobuf, ZLIB 0x04b3, Heartbeat 0x2716 │ ├── opcodes.py # Резолвер строковых команд и авто-сериализатор параметров Protobuf │ ├── lr.py # Модуль LoadRunner API: транзакции, think_time, eval_string, get_param │ └── script_base.py # Базовый класс VUserScript (vuser_init -> action -> vuser_end) │ ├── controller/ # Оркестратор нагрузочных сценариев │ ├── scenario.py # Trio Controller: распределение блоков строк Unique, Ramp-up, Pacing │ └── datapool.py # Менеджер выборки пользователей и фильтрации аккаунтов │ ├── scripts/ # Прикладные синхронные линейные скрипты VUser │ └── daily_farm.py # Скрипт: забор подарков альянса (0x2986) + сбор ресурсов базы (0x2785) │ ├── tests/ # Автоматизированные тесты │ ├── test_parameters.py # Тесты логики выборки параметров, блоков строк и исчерпания │ └── test_scenario_datapool.py # Интеграционные тесты DailyFarmScript и Scenario │ ├── tools/ # Инструменты разработки и сниффинга │ └── get_command.py # Интерактивный сниффер команд клиента с GUI-разметкой действий и генерацией кода │ └── logs/ # Ротируемые архивы журналов работы ├── all.log # Полный журнал системы (ротация 20 MB, zip-архивирование, хранение 14 дней) ├── errors.log # Исключительно критические ошибки уровня ERROR └── vusers/ # Персональные изолированные логи по каждому боту ├── vuser_1813568357.log └── vuser_1822500681.log

5.2. Спецификация датапулов (Параметры и поля)

Датапул / Файл Ключевые поля Тип данных Назначение
data/env_config.json game_id, login_server, game_server_port_offset Строки, числа, объекты Глобальные неизменные константы игры. Общие для всех ботов кластера. Смещение портов кластера = 1060.
data/logins.json email, password, group, user_ids Массив объектов Учетные данные для входа. Позволяет связывать аккаунты веб-логина IGG со списками персонажей и группами.
scenarios/sc_*.py ramp_up, duration, pacing, think_time, error_handling Словари Python (RTS) Run-Time Settings сценария. Определяют расписание подачи нагрузки и параметры воспроизведения пауз ботов.
data/accounts/*_config.json PassportToken, AccessKey, LoginSession, NetAddress JWT строки, токены, hex Сессионный кэш персонажей. Хранит актуальные токены сессий и временные метки их действия.

5.3. Пошаговый жизненный цикл сетевого сокета GameConnection

  1. DNS & Трансляция адреса: Принимается внутренний адрес кластера (например 10.246.40.6:11004). Функция resolve_game_server_address() транслирует его во внешний edge-порт: 11004 + 1060 = 12064 на хосте 204.141.172.24.
  2. TCP Connect: Открытие неблокирующего TCP-сокета к порту 12064. Старт потока чтения _recv_loop.
  3. Авторизация канала (0x279f → 0x27a0): Отправка бинарного фрейма MsgCL2GSLoginRequest с токеном LoginSession и аппаратным UDID. Сервер возвращает 0x27a0 со строкой билда сервера и ErrorCode 0.
  4. Цикл Heartbeat (0x2716): После успешной авторизации активируется фоновый поток, каждые 25 секунд отправляющий пинг 0x2716 для удержания канала.
  5. Вход в мир (0x27a1 → 0x27a2): Отправка запроса MsgCL2GSEnterGameRequest. Сервер подтверждает вход и начинает трансляцию состояния базы и мира через бандлы 0x04b3.
  6. Распаковка бандлов 0x04b3: Пакеты с опкодом 0x04b3 распаковываются из ZLIB в последовательность отдельных DlsFrame и маршрутизируются в обработчики.
  7. Graceful Shutdown: При завершении сценария корректно закрывается сокет (SHUT_RDWR) и останавливаются потоки.

5.4. Архитектурная модель конкурентности (Trio + Sync Workers)

[ Trio Main Event Loop ] (controller/scenario.py) │ ├── trio.CapacityLimiter (Лимит 50 одновременных сокетов) ├── Ramp-up Scheduler (Поэтапный запуск групп ботов) │ └── trio.open_nursery() │ ├── Worker 1 ──> trio.to_thread.run_sync() ──> [ VUser 1813568357 Thread ] │ │ │ ├── lr.set_current_vuser(self) │ ├── Linear Sync Script (daily_farm.py) │ ├── GameConnection Socket (204.141.172.24:12064) │ └── Recv Thread + Heartbeat Thread (0x2716) │ ├── Worker 2 ──> trio.to_thread.run_sync() ──> [ VUser 1822500681 Thread ] │ │ │ └── Linear Sync Script ... │ └── Worker N ──> ... (до 50+ параллельных ботов)

6. Развертывание, запуск сценариев и инструментарий

6.1. Развертывание на чистой Ubuntu Linux

Фреймворк полностью автономен и не содержит привязок к платформе Windows или бинарным библиотекам игры. Для выполнения тестов на чистом сервере Ubuntu Linux требуются только Python 3.10+ и легковесные пакеты из requirements.txt:

# 1. Клонирование / копирование репозитория на сервер: cd /opt/igg_framework # 2. Создание виртуального окружения и установка зависимостей: python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # Устанавливает только trio и loguru # 3. Запуск нагрузочного сценария: python scenarios/sc_daily_farm.py

6.2. Управление сценариями нагрузки (LoadRunner Scenario)

Каждый сценарий нагрузки представляет собой автономный исполняемый файл в каталоге scenarios/. Все параметры (Ramp-up, Pacing, длительность, группы ботов) конфигурируются прямо в коде сценария:

# Прямой запуск эталонного сценария ежедневной рутины: python scenarios/sc_daily_farm.py # Запуск массового периодического сценария (раз в 5 часов со шлюзом сессий и скаутом): python scenarios/sc_mass_scrips.py # Создание собственного нового сценария: # 1. Скопируйте scenarios/sc_mass_scrips.py в scenarios/sc_alliance_war.py # 2. Настройте группы ботов, темп и прикладной скрипт внутри файла # 3. Запустите: python scenarios/sc_alliance_war.py

Режимы расписания нагрузки (Duration & Schedules)

Тип расписания (type) Параметры Жизненный цикл VUser Типовое применение
"script" vuser_init → N итераций action() из RTS скрипта → vuser_end Однократные функциональные тесты, калибровка скриптов, автономная отладка.
"interval" interval_sec (сек), repeats (число или None) vuser_init (1 раз) → раунд action() → пауза interval_sec → ... → vuser_end (в конце) Периодическая игровая рутина: пожертвования в технологии альянса каждые 5 часов, сбор шахт, забор наград.
"duration" seconds (длительность теста) vuser_init (1 раз) → непрерывный цикл action() с Pacing → vuser_end Стресс-тестирование, поиск деградации производительности сервера во времени.
"infinite" vuser_init (1 раз) → непрерывный цикл action() до Ctrl+C → vuser_end Непрерывный мониторинг, долговременная фоновая активность.
"iterations" count (число циклов) vuser_init (1 раз) → ровно count итераций action()vuser_end Пакетные тесты фиксированного объёма операций.

Индивидуальные расписания групп: Каждая группа VUserGroup может иметь свой независимый параметр schedule={...}. Это позволяет в рамках одного массового сценария запускать скрипт технологий раз в 5 часов, а скрипт сбора ресурсов — каждые 30 минут.

Архитектура Session Gateway и Scout Bot (Наблюдатель альянса)

В архитектуре Doomsday GameServer одна учётная запись привязывается к одному TCP-сокету. Параллельное подключение со второго сокета приводит к разрыву сессии с кодом ошибки 1013 (KEcplayerLoginOther). Для предотвращения коллизий и безопасной работы рядом с реальным игроком реализованы два взаимодополняющих компонента:

  1. SessionGateway (Шлюз сессий): Удерживает единственный постоянный сокет для каждого UserId. Все скрипты прозрачно направляют вызовы lr.send и lr.send_and_wait в потокобезопасную FIFO-очередь команд, исключая гонки и дублирующие подключения.
  2. AllianceScout (Аккаунт-наблюдатель): Выделенный персонаж (по умолчанию 1820818432 / visbot21), авторизованный в том же альянсе:
    • Периодически считывает состав альянса через 0x27520x2753 (поля IsOnline и LastLogoutTime в Protomsg.GuildMemberData).
    • Перехватывает асинхронные события в реальном времени: 0x2d5e (вход соклановца) и 0x2d5f (выход соклановца).
    • Бесконфликтный возврат (Player Precedence): При входе живого игрока в игру (код 1013) шлюз уступает дорогу и обращается к скауту. Пока скаут видит IsOnline=True, на сокет игрока не отправляется ни единого байта. Человека гарантированно не выбивает из мобильного клиента. Как только скаут фиксирует оффлайн, шлюз безопасно восстанавливает связь.

6.3. Интерактивный сниффер команд и генератор кода (tools/get_command.py)

Для быстрого создания новых скриптов без ручного поиска опкодов разработан интерактивный сниффер. Он запускается на машине с игровым клиентом Doomsday.exe, перехватывает клики и генерирует готовый Python-код:

  1. Запустите игру Doomsday.exe в обычном режиме.
  2. В консоли запустите сниффер: python tools/get_command.py. Он автоматически подключится к процессу через Frida.
  3. Поверх экрана откроется компактное окно: «Doomsday Sniffer — Разметка действий».
  4. Перед кликом в игре введите действие (например: «Нажал кнопку забрать подарки альянса») и нажмите Enter. Комментарий крупно отобразится в консоли и разметит лог-файл.
  5. Кликните кнопку в игре — сниффер мгновенно декодирует команду, сопоставит её со схемой opcodes.json и выведет готовый блок кода:
    # Сгенерированный блок (скопируйте в action(self) вашего скрипта): reply = lr.send_and_wait( command="KMsgCl2GsguildGiftClaimAllRequest", Type=1, expected_reply="KMsgGs2ClguildGiftClaimAllReply", timeout=5.0 )
  6. Все события и сниппеты сохраняются в файлы logs/captured_commands_*.log и logs/captured_snippets_*.py.

7. План развития: Модель состояния игрового мира (In-Memory GameState)

● TODO / Future Roadmap

В текущей архитектуре фреймворк функционирует по канонической Stateless-модели LoadRunner VuGen (Request-Reply): виртуальный пользователь отправляет целевые запросы через lr.send_and_wait(), а входящий поток серверных сообщений вычитывается фоновым потоком _recv_loop для предотвращения переполнения буфера сокета ОС и отбрасывается, если на конкретный опкод не зарегистрировано прямое ожидание.

Однако сразу после завершения рукопожатия авторизации (0x27a1 → 0x27a2) сервер Doomsday выгружает клиенту гигантский пакет данных 0x04b3 (ZLIB-сжатый бандл), который содержит полный исходный слепок всего аккаунта: уровни и координаты всех построек города (MsgGS2CLCityInfoReply 0x272c), текущие запасы ресурсов и балансы валют (0x2801), объёмы добычи на фермах и вышках с таймерами готовности (0x28f0), очереди строительства (BuildQueueInfo), содержимое инвентаря (0x2851) и список героев (0x2871). В процессе активной игры сервер также непрерывно передаёт дельта-патчи (MsgGS2CLDataUpdateNotice 0x298d, MsgGS2CLCollectTimeUpdateNotice 0x28f7).

Цель задачи (Roadmap TODO): Переход от Stateless к Stateful-модели (Smart VUser). Фреймворк будет автоматически перехватывать стартовые данные авторизации и сохранять их в изолированный объект локального состояния vuser.state, непрерывно актуализируя его на лету при приходе серверных уведомлений. Это позволит прикладным скриптам мгновенно принимать решения без спама лишними запросами: проверять свободные очереди стройки (lr.is_build_queue_free()), узнавать текущие остатки ресурсов (lr.get_resource('food')) и запрашивать сбор только с тех зданий, где ресурсы действительно накопились.
🔬 Технические детали реализации: архитектура GameState, шина событий и расширение API lr.* (Нажмите для раскрытия)

7.1. Структура данных локального слепка мира (vuser.state: GameState)

У каждого экземпляра VUserScript создаётся потокобезопасный объект состояния с изолированными словарями сущностей:

Ветка состояния Тип данных Серверный опкод-источник Описание хранимых данных
state.resources Dict[str, int] 0x2801 (ResourceNotice), 0x298d Текущие остатки: нефть, еда, дерево, сталь, алмазы, очки альянса, монеты арены и экспедиций.
state.buildings Dict[int, BuildingInfo] 0x272c (CityInfoReply), 0x271d Словарь всех построек базы по BuildingId: тип (1004, 1007 и т.д.), текущий уровень, координаты ячейки (x, y), статус стройки/улучшения.
state.collect Dict[int, CollectInfo] 0x28f0 (CollectInfoReply), 0x28f3, 0x28f7 Производственные параметры: тип валюты (CurrencyType), накопленный объём в хранилище здания (Store), скорость в час (Speed), максимальная ёмкость (MaxCollection).
state.queues Dict[str, QueueSlot] 0x272c (BuildQueueInfo), 0x2724 Слоты очередей: строители (Queue 1 и 2), исследования в институте, тренировка войск в казармах с таймерами завершения.
state.bag Dict[int, int] 0x2851 (BagInfoNotice) Инвентарь персонажа: ItemId → Count (ускорители, сундуки ресурсов, щиты, телепорты).
state.heroes Dict[int, HeroInfo] 0x2871 (HeroListNotice) Разблокированные герои: ранг, уровень, распределение очков талантов, экипировка.

7.2. Шина событий и автоматическая синхронизация (Event-Driven Auto-Sync)

Синхронизация состояния работает полностью автономно в фоновом потоке _recv_loop через регистрацию системных слушателей:

# Схема работы шины фоновой синхронизации состояния GameState: GameServer ──[TCP поток]──> _recv_loop() │ ┌────────────────────────────┴───────────────────────────┐ ▼ ▼ [ 0x04b3 MsgBigPackage ] [ Обычный DlsFrame ] │ │ ▼ ▼ unpack_compressed_bundle (ZLIB) _dispatch_frame() │ │ └────────────────────────────┬───────────────────────────┘ │ ┌───────────────────┴───────────────────┐ ▼ ▼ [ Входящий опкод ] [ Слушатели GameState ] ├── 0x272c (CityInfoReply) ──> state.buildings.update(...) ├── 0x2801 (ResourceNotice) ──> state.resources.update(...) ├── 0x28f0 / 0x28f7 (Collect) ──> state.collect.update(...) ├── 0x298d (DataUpdateNotice) ──> state.apply_delta_patch(fields) └── 0x2724 (QueueNotice) ──> state.queues.update(...)

7.3. Предлагаемое расширение пользовательского API lr.*

Скрипт освобождается от ручного парсинга Protobuf и получает готовые методы для чтения локального контекста:

# Примеры использования расширенного API в скриптах action(): # 1. Мгновенная проверка доступных ресурсов без сетевого запроса: food_amount = lr.get_resource("food") # по строковому имени oil_amount = lr.get_resource(2) # по CurrencyType if food_amount < 500_000: lr.output_message("Недостаточно еды для апгрейда, открываем коробки ресурсов...") # 2. Проверка свободных очередей строительства: if lr.is_build_queue_free(): # Находим ратушу среди всех зданий: castle = lr.get_building_by_type(1000) # 1000 = MainCastle lr.output_message(f"Ратуша уровня {castle.level} готова к повышению уровня!") lr.send_and_wait("KMsgCl2GscityUpgradeBuidlingRequest", BuildingId=castle.id) # 3. Умный сбор ресурсов (только если реально накопилось > 80% от ёмкости): ready_types = lr.get_collectable_resources(min_fill_pct=0.8) for res_type in ready_types: lr.send_and_wait("KMsgCl2GscollectResourceRequest", Type=res_type)

7.4. Потокобезопасность и изоляция VUser

  • Изоляция экземпляров: Каждый бот (VUser) хранит собственный независимый экземпляр GameState. Утечка данных между ботами исключена.
  • Блокировка чтения/записи (RLock): Обновление состояния из потока _recv_loop и чтение из синхронного скрипта action() защищаются внутренним threading.RLock() с миллисекундным временем удержания, что исключает блокировку сетевого ввода-вывода.