Files

16 KiB

L2 Market Parser

Кроссплатформенный локальный прототип для захвата нескольких окон Lineage II и сбора предметов из tooltip торгового окна.

Прогресс иконок магазина

Для жёлто-зелёного прогресса дополнительно выдели области Сетка предметов Sell и Сетка предметов Buy. Нужен только прямоугольник 6 × 3 с ячейками предметов, без заголовка и кнопок прокрутки. Поля необязательны, поэтому старые калибровки продолжают распознавать текст без оверлея иконок.

На первом чистом кадре магазина занятые ячейки становятся жёлтыми. После успешного OCR и однозначного сравнения с иконкой из каталога соответствующая ячейка становится зелёной. При одинаково подходящих иконках ячейка остаётся жёлтой; приложение не угадывает слот по положению tooltip или порядку обхода. Выбранный источник разворачивается минимум до 640 CSS px, а экран калибровки показывает исходный скриншот в масштабе 2×, сохраняя координаты в исходных пикселях.

Проверка включает обычные unit-тесты, 17 подписанных игровых скриншотов в Chromium, production build и проверку diff:

npm run check

Запуск

Нужен Node.js 20 или новее и Chrome либо Edge.

npm install
cp .env.local.example .env.local
npm run dev

Открой адрес, который напечатает Vite, обычно http://127.0.0.1:5173.

В основном приложении этот же интерфейс доступен в Рынок → Аудит рынка. Там сохранённая калибровка общая: сервер загружает её при открытии вкладки и заменяет после нажатия «Сохранить».

На macOS браузеру потребуется разрешение System Settings → Privacy & Security → Screen & System Audio Recording. После выдачи разрешения браузер иногда нужно перезапустить.

Как работает поиск

Основное торговое окно считается единым объектом. Пользователь отмечает его нижний постоянный фрагмент, а приложение ищет этот фрагмент по всему кадру. Когда фрагмент найден, его координаты становятся точкой отсчёта для заголовка магазина и зоны tooltip.

Кадр
└─ Маркер магазина найден?
   ├─ нет → заголовок и tooltip не проверяются
   └─ да
      ├─ OCR `Private Store(Buy/Sell) - имя`
      │  ├─ виден → запомнить тип сделки + имя торговца
      │  └─ закрыт tooltip → использовать последний запомненный магазин
      └─ поиск Price : внутри зоны tooltip
         ├─ нет → название и цена не читаются
         └─ да → OCR точной строки названия и точной строки цены

Заголовок и зона tooltip хранятся относительно маркера магазина. Поэтому всё торговое окно можно перемещать. Широкая зона tooltip используется только для поиска Price : и не передаётся целиком в OCR. После нахождения якоря приложение отдельно определяет фактическую ширину строк названия и цены по пикселям текста; для названия учитываются белый, синий и фиолетовый цвета.

Из заголовка Private Store(Buy) - Hikvision получится side: "buy", merchant: "Hikvision"; из Private Store(Sell) - Dwaside: "sell", merchant: "Dwa".

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

Сценарий

  1. В разделе «Экран» нажми «Добавить окно» и выбери окно игры. Приложение сразу откроет «Парсинг».
  2. Нажми «Начать парсинг» и вернись в игру.
  3. Сначала оставь курсор вне предметов, пока не появится блок торговца с типом сделки и именем.
  4. Наводи курсор на каждый предмет. Товары заполняют блок торговца, следующий торговец появляется справа или на новой строке.
  5. Калибровку открывай как дополнительную настройку, если текущая общая конфигурация не распознаёт окно или tooltip.

Новое наблюдение сначала попадает в IndexedDB браузера и сразу отправляется на сервер; 30-секундный повтор остаётся страховкой. При недоступном сервере пакет остаётся локально и повторяется с тем же ID после восстановления связи или перезапуска страницы. Сервер хранит исходный пакет без дублей и для распознанных каталожных предметов обновляет отдельный черновик рынка: Sell становится sell_offer, Buy становится buy_order. Неопознанные товары остаются только в исходном пакете.

Калибровку можно сделать по снимкам разного размера и с торговым окном в разных местах:

  1. Без tooltip выдели нижний маркер и заголовок Private Store(Buy/Sell) - имя.
  2. Добавь второй снимок с tooltip. Калибратор сам найдёт нижний маркер по всему новому кадру и перенесёт относительно него остальные области.
  3. На втором снимке выдели зону tooltip, Price :, название и цену. Уже выбранные области сохраняются.

Размер игрового окна и положение торгового окна могут отличаться. В калибраторе снимок показывается в масштабе 2× для точной ручной разметки, но шаблоны и сохранённые координаты остаются в исходном пиксельном размере Lineage II.

После выбора всех шести областей нажми «Проверить снимки». Они обрабатываются по порядку: сначала кадр с видимым заголовком запоминает магазин, затем кадр с tooltip добавляет предмет. Подключать игровое окно для этого не нужно.

Результат состоит только из блоков торговцев. В каждом блоке показаны тип сделки и последние распознанные цена/количество каждого предмета. Количество читается из числовых скобок в конце названия, например (18) или (5,600); если их нет, используется 1. Повторное распознавание той же комбинации не создаёт новую запись.

Во вкладке приложения OCR-название проходит неточный поиск по активному каталогу. Уверенное совпадение заменяется каноническим названием и показывается с иконкой предмета; слабое совпадение остаётся с вопросительным знаком и пометкой «Не найден в каталоге». Предметы группируются по имени торговца и типу сделки.

Количеством считается только последняя числовая скобка. В Blessed Spiritshot: D-Grade (D) (5,600) часть (D) остаётся в названии, а количество равно 5600.

Если рамки перекрывают соседние элементы, выключи «Показывать выбранные рамки». Сохранённые области не удалятся, а новый пунктирный прямоугольник останется виден во время выделения.

Что именно выделять

Область Правильный выбор Частая ошибка
Маркер магазина Небольшой постоянный фрагмент внизу, например Adena и Confirm Захватить баланс Adena или другое изменяемое число
Заголовок магазина Вся строка Private Store(Sell) - Dwa или Private Store(Buy) - Hikvision Обрезать Buy/Sell или имя; захватить рамку и кнопку закрытия
Зона tooltip Широкая полоса возможных положений всплывающего окна, в том числе за пределами окна трейда Включить нижнюю строку Price основного окна
Якорь Price Только постоянные символы Price : внутри tooltip Добавить цену, которая меняется у каждого предмета
Название предмета Левый край и точная высота верхней строки tooltip; ширина подгоняется автоматически Захватить иконки, фон игры или строку цены
Строка цены Левый край и точная высота Price : 3,000,000 Adena, For Each 400 Adena или числа Захватить дополнительную строку (3 Million Adena)

Калиброваться на самом длинном предмете больше не требуется: после поиска Price : границы названия и цены подгоняются отдельно и останавливаются до удалённого текста интерфейса.

Диагностика

В результате показывается последний обработанный кадр с рамками и каждый этап отдельно:

  • зелёная рамка означает, что шаблон прошёл заданный порог;
  • жёлтая показывает найденную область предмета, которую OCR ещё не разобрал;
  • красная показывает лучшее совпадение, которое не прошло порог;
  • синяя показывает вычисленную область следующего шага;
  • «Пропущено» означает, что предыдущий обязательный этап не прошёл;
  • рядом с OCR показываются фактический crop, текст и уверенность.

Если не находится магазин, смотри crop «Маркер магазина» и процент совпадения. Если тип или имя ещё не определены, убери курсор с предмета и смотри фактический OCR этапа «Заголовок магазина». Надпись «Используется последний магазин» означает, что tooltip закрыл заголовок, но привязка предмета сохранена. Если магазин найден, но предмет не появляется, смотри «Якорь Price :». Если оба якоря найдены, проблема уже в точных crop названия или цены.

Диагностические изображения существуют только в памяти открытой страницы. Они не попадают в JSON результата и не отправляются вместе с рыночными наблюдениями.

В отдельном Vite-прототипе калибровка хранится в localStorage. Во вкладке home-service серверная калибровка является основной, а localStorage остаётся локальной резервной копией. При первом OCR Tesseract.js загружает английскую языковую модель и кеширует её в браузере.

Ограничения прототипа

  • Источники выбираются вручную после каждого обновления страницы.
  • Одна калибровка рассчитана на один масштаб интерфейса Lineage II.
  • Парсинг работает, пока открыта страница прототипа и он не остановлен кнопкой «Остановить парсинг».
  • Курсор перемещает пользователь, приложение не управляет игрой и не эмулирует ввод.
  • Токен импорта хранится только в локальном Vite-процессе; в браузерный bundle он не попадает.

Проверки

npm test
npm run build
# Обновить встроенную копию в соседнем home-service:
npm run build -- --outDir ../home-service/frontend/public/l2/market-audit --emptyOutDir