L2 Market Parser

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

Запуск

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

npm install
cp .env.local.example .env.local
# Заполни L2_MARKET_IMPORT_TOKEN тем же значением, что HOME_SERVICE_L2_MARKET_IMPORT_TOKEN в Gitea Secrets.
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) - имя`
      │  └─ тип сделки + имя торговца
      └─ поиск Price : внутри зоны tooltip
         ├─ нет → название и цена не читаются
         └─ да → OCR точной строки названия и точной строки цены

Заголовок и зона tooltip хранятся относительно маркера магазина. Поэтому всё торговое окно можно перемещать. Внутри зоны tooltip отдельно ищется Price :, поэтому сам tooltip тоже может двигаться при наведении на разные предметы.

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

Сценарий

  1. В разделе «Источники» нажми «Добавить окно» для каждого окна игры.
  2. В разделе «Калибровка» сделай снимок или загрузи готовый скриншот.
  3. Выдели постоянный нижний элемент торгового окна, например Adena + Confirm, как маркер магазина. По возможности не захватывай изменяемые числа.
  4. Выдели заголовок магазина одной строкой: от Private Store( до конца имени торговца. Рамку и кнопку закрытия не включай.
  5. Выдели широкую область всех возможных положений tooltip, не захватывая нижний Price основного окна.
  6. Внутри tooltip обведи только постоянную надпись Price :.
  7. Отдельно выдели точную строку названия и точную строку цены. Для названия лучше использовать длинный предмет и оставить запас справа.
  8. Сохрани калибровку, нажми «Начать сбор» и вернись в игру.
  9. Наводи курсор на каждый предмет. Уникальные пары название + цена будут накапливаться в таблице, а каждое распознавание — сохраняться с датой и отправляться на сервер.

Наблюдения сначала попадают в IndexedDB браузера, затем отправляются пакетами раз в 30 секунд. При недоступном сервере пакет остаётся локально и повторяется с тем же ID после восстановления связи или перезапуска страницы; сервер не создаёт дубликат.

Для калибровки нужен скриншот, на котором одновременно видны:

  • нижняя часть торгового окна;
  • заголовок Private Store(Buy/Sell) - имя;
  • открытый tooltip предмета;
  • полная строка названия и полная строка Price.

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

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

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

Лучше калиброваться на предмете с длинным названием и большой ценой. Тогда прямоугольники не обрежут более короткие варианты.

Диагностика

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

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

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

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

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

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

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

Проверки

npm test
npm run build
S
Description
No description provided
Readme
11 MiB
Languages
JavaScript 79.5%
CSS 14.2%
HTML 6.3%