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) - Dwa — side: "sell", merchant: "Dwa".
Сценарий
- В разделе «Источники» нажми «Добавить окно» для каждого окна игры.
- В разделе «Калибровка» сделай снимок или загрузи готовый скриншот.
- Выдели постоянный нижний элемент торгового окна, например
Adena + Confirm, как маркер магазина. По возможности не захватывай изменяемые числа. - Выдели заголовок магазина одной строкой: от
Private Store(до конца имени торговца. Рамку и кнопку закрытия не включай. - Выдели широкую область всех возможных положений tooltip, не захватывая нижний
Priceосновного окна. - Внутри tooltip обведи только постоянную надпись
Price :. - Отдельно выдели точную строку названия и точную строку цены. Для названия лучше использовать длинный предмет и оставить запас справа.
- Сохрани калибровку, нажми «Начать сбор» и вернись в игру.
- Наводи курсор на каждый предмет. Уникальные пары
название + ценабудут накапливаться в таблице, а каждое распознавание — сохраняться с датой и отправляться на сервер.
Наблюдения сначала попадают в 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