114 lines
12 KiB
Markdown
114 lines
12 KiB
Markdown
# L2 Market Parser
|
|
|
|
Кроссплатформенный локальный прототип для захвата нескольких окон Lineage II и сбора предметов из tooltip торгового окна.
|
|
|
|
## Запуск
|
|
|
|
Нужен Node.js 20 или новее и Chrome либо Edge.
|
|
|
|
```bash
|
|
npm install
|
|
cp .env.local.example .env.local
|
|
# Сгенерируй один случайный токен, сразу сохрани его и в .env.local,
|
|
# и как новый Gitea Secret HOME_SERVICE_L2_MARKET_IMPORT_TOKEN.
|
|
npm run dev
|
|
```
|
|
|
|
Открой адрес, который напечатает Vite, обычно `http://127.0.0.1:5173`.
|
|
|
|
На macOS браузеру потребуется разрешение **System Settings → Privacy & Security → Screen & System Audio Recording**. После выдачи разрешения браузер иногда нужно перезапустить.
|
|
|
|
## Как работает поиск
|
|
|
|
Основное торговое окно считается единым объектом. Пользователь отмечает его нижний постоянный фрагмент, а приложение ищет этот фрагмент по всему кадру. Когда фрагмент найден, его координаты становятся точкой отсчёта для заголовка магазина и зоны tooltip.
|
|
|
|
```text
|
|
Кадр
|
|
└─ Маркер магазина найден?
|
|
├─ нет → заголовок и tooltip не проверяются
|
|
└─ да
|
|
├─ OCR `Private Store(Buy/Sell) - имя`
|
|
│ ├─ виден → запомнить тип сделки + имя торговца
|
|
│ └─ закрыт tooltip → использовать последний запомненный магазин
|
|
└─ поиск Price : внутри зоны tooltip
|
|
├─ нет → название и цена не читаются
|
|
└─ да → OCR точной строки названия и точной строки цены
|
|
```
|
|
|
|
Заголовок и зона tooltip хранятся относительно маркера магазина. Поэтому всё торговое окно можно перемещать. Внутри зоны tooltip отдельно ищется `Price :`, поэтому сам tooltip тоже может двигаться при наведении на разные предметы.
|
|
|
|
Из заголовка `Private Store(Buy) - Hikvision` получится `side: "buy"`, `merchant: "Hikvision"`; из `Private Store(Sell) - Dwa` — `side: "sell"`, `merchant: "Dwa"`.
|
|
|
|
Контекст хранится отдельно для каждого захватываемого окна. Пока нижний маркер магазина остаётся виден, tooltip относится к последнему успешно распознанному заголовку этого окна. После трёх кадров без маркера контекст сбрасывается, чтобы предметы следующего магазина не попали в предыдущий. Если заголовок ещё ни разу не распознан, предмет из tooltip не сохраняется.
|
|
|
|
## Сценарий
|
|
|
|
1. В разделе «Источники» нажми «Добавить окно» для каждого окна игры.
|
|
2. В разделе «Калибровка» сделай снимок или загрузи готовый скриншот.
|
|
3. Выдели постоянный нижний элемент торгового окна, например `Adena + Confirm`, как маркер магазина. По возможности не захватывай изменяемые числа.
|
|
4. Выдели заголовок магазина одной строкой: от `Private Store(` до конца имени торговца. Рамку и кнопку закрытия не включай.
|
|
5. Выдели широкую область всех возможных положений tooltip, не захватывая нижний `Price` основного окна.
|
|
6. Внутри tooltip обведи только постоянную надпись `Price :`.
|
|
7. Отдельно выдели точную строку названия и точную строку цены. Для названия лучше использовать длинный предмет и оставить запас справа.
|
|
8. Сохрани калибровку, нажми «Начать сбор» и вернись в игру.
|
|
9. Сначала оставь курсор вне предметов, пока в результате не появятся тип сделки и имя торговца.
|
|
10. Наводи курсор на каждый предмет. Даже если tooltip закроет заголовок, предмет будет записан в последний найденный магазин.
|
|
|
|
Наблюдения сначала попадают в IndexedDB браузера, затем отправляются пакетами раз в 30 секунд. При недоступном сервере пакет остаётся локально и повторяется с тем же ID после восстановления связи или перезапуска страницы; сервер не создаёт дубликат.
|
|
|
|
Калибровку можно сделать по снимкам разного размера и с торговым окном в разных местах:
|
|
|
|
1. Без tooltip выдели нижний маркер и заголовок `Private Store(Buy/Sell) - имя`.
|
|
2. Добавь второй снимок с tooltip. Калибратор сам найдёт нижний маркер по всему новому кадру и перенесёт относительно него остальные области.
|
|
3. На втором снимке выдели зону tooltip, `Price :`, название и цену. Уже выбранные области сохраняются.
|
|
|
|
Размер игрового окна и положение торгового окна могут отличаться. Масштаб интерфейса Lineage II должен оставаться тем же: шаблоны ищутся в исходном пиксельном размере.
|
|
|
|
После выбора всех шести областей нажми «Проверить снимки». Они обрабатываются по порядку: сначала кадр с видимым заголовком запоминает магазин, затем кадр с tooltip добавляет предмет. Подключать игровое окно для этого не нужно.
|
|
|
|
Над подробными результатами выводятся уникальные строки вида `Покупка · Lui · Animal Skin · 18 шт. · 400 Adena`. Количество читается из числовых скобок в конце названия, например `(18)` или `(5,600)`. Повторное распознавание той же комбинации торговца, типа сделки, предмета, количества и цены не создаёт новую строку.
|
|
|
|
### Что именно выделять
|
|
|
|
| Область | Правильный выбор | Частая ошибка |
|
|
| --- | --- | --- |
|
|
| Маркер магазина | Небольшой постоянный фрагмент внизу, например `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)` |
|
|
|
|
Лучше калиброваться на предмете с длинным названием и большой ценой. Тогда прямоугольники не обрежут более короткие варианты.
|
|
|
|
## Диагностика
|
|
|
|
В результате показывается последний обработанный кадр с рамками и каждый этап отдельно:
|
|
|
|
- зелёная рамка означает, что шаблон прошёл заданный порог;
|
|
- красная показывает лучшее совпадение, которое не прошло порог;
|
|
- синяя показывает вычисленную область следующего шага;
|
|
- «Пропущено» означает, что предыдущий обязательный этап не прошёл;
|
|
- рядом с OCR показываются фактический crop, текст и уверенность.
|
|
|
|
Если не находится магазин, смотри crop «Маркер магазина» и процент совпадения. Если тип или имя ещё не определены, убери курсор с предмета и смотри фактический OCR этапа «Заголовок магазина». Надпись «Используется последний магазин» означает, что tooltip закрыл заголовок, но привязка предмета сохранена. Если магазин найден, но предмет не появляется, смотри «Якорь Price :». Если оба якоря найдены, проблема уже в точных crop названия или цены.
|
|
|
|
Диагностические изображения существуют только в памяти открытой страницы. Они не попадают в JSON результата и не отправляются вместе с рыночными наблюдениями.
|
|
|
|
Калибровка хранится в `localStorage` текущего браузера. При первом OCR Tesseract.js загружает английскую языковую модель и кеширует её в браузере.
|
|
|
|
## Ограничения прототипа
|
|
|
|
- Источники выбираются вручную после каждого обновления страницы.
|
|
- Одна калибровка рассчитана на один масштаб интерфейса Lineage II.
|
|
- Сбор работает, пока открыта страница прототипа и он не остановлен кнопкой «Остановить сбор».
|
|
- Курсор перемещает пользователь, приложение не управляет игрой и не эмулирует ввод.
|
|
- Токен импорта хранится только в локальном Vite-процессе; в браузерный bundle он не попадает.
|
|
|
|
## Проверки
|
|
|
|
```bash
|
|
npm test
|
|
npm run build
|
|
```
|