В Albion Online довольно сложная и развитая экономика с десятками видов предметов. Предметы имеют свои уровни и зачарования, которые влияют на их цену и востребованность на рынке. Однако, отсутствие открытого API затрудняет сбор и анализ этих данных. В статье рассказываю, как с помощью OCR удалось собрать и обработать тысячи строк данных о ценах и продажах предметов, обходя ограничения игры.
Введение
В игре я занимаюсь сбором ресурсов, их переработкой и созданием предметов экипировки. Чтобы зарабатывать на их продаже, нужно понимать, что происходит с рынком ресурсов, их которых эти предметы создаются.
Обычно для решения этой задачи игроки используют информацию, которая есть в карточке предмета справа внизу. Эти данные отражают динамику продаж предмета на рынке за 24 часа, 2 недели и 28 дней:
Зная эти данные по всем позициям на рынке, можно сделать выводы о том, на создании и продаже каких предметов стоит сосредоточиться в первую очередь, чтобы заработать больше серебра.
Проблемы и ограничения игры
Проблема в том, что игра не дает выгрузить эти данные, чтобы продолжить работать с ними в формате электронных таблиц: у игры нет открытого API, а работать с таким большим объемом данных по-другому просто невозможно.
Давайте посчитаем. В игре два типа ресурсов: те, которые можно собрать (необработанные) и те, которые получаются в результате их переработки (обработанные).
Необработанных ресурсов — 5. Это дерево, волокно, шкура, руда и камень. У каждого ресурса 7-8 уровней. У некоторых ресурсов после 4 уровня есть еще 3-4 уровня зачарования, которые повышают его качество.
Вот скриншот, который наглядно показывает систему уровней и зачарований:
Получается, что у каждого из 5 необработанных ресурсов есть от 8 до 23 позиций разного уровня и зачарования.
Обработанных ресурсов тоже 5. Это брусья, ткань, кожа, слитки и блоки. У них, в свою очередь, от 7 до 22 позиций разного уровня и зачарования.
Если сложить вместе необработанные и обработанные ресурсы всех уровней и зачарований, получится 248 позиций:
Для каждой из этих позиций есть карточка на рынке. Если игроку понадобится узнать данные по каждой из карточек за каждый из 28 дней, ему придется собрать и обработать 9212 строк данных.
Сбор и обработка такого количества данных вручную займет очень много времени, поэтому я попробовал найти решение, которое поможет получить эти данные быстро и в автоматическом режиме.
Поиск подходящего решения
В интернете уже есть инструменты вроде Albion Online Data Project, но ни одно из них не подходит для решения этой задачи.
Основной минус AODP в том, что он собирает данные только в том случае, если игрок установит его клиент, а затем загрузит информацию о предмете в игре, открыв его карточку на рынке. Поэтому для части предметов в базе данных AODP нет цен.
Вот один из проектов, который использует данные AODP:
Кроме того, в подобных проектах нет данных о количестве продаж и средней стоимости предмета за день, неделю или месяц.
Чтобы собрать эти данные не вмешиваясь в процесс игры и не используя сторонние клиенты, я решил прочитать их с экрана с помощью системы для оптического распознавания символов (OCR).
взять список предметов, данные о которых нужно собрать;
найти координаты точек на экране, по которым в карточке предметов на рынке отображается нужные данные;
пройти курсором по этим координатам, сделать скриншоты и повторить этот цикл для всех предметов из списка;
кадрировать скриншоты так, чтобы распознавать на них только нужный текст;
распознать текст на скриншотах с помощью OCR и перенести его в таблицы.
Реализация собственного решения
Важно уточнить, что правилами игры запрещена любая автоматизация, которая прямо влияет на игровой процесс. Например, нельзя использовать скрипты, которые автоматизируют ловлю рыбы, убийство мобов и пр.
В моей ситуации прямого влияния на игровой процесс нет, а скрипты я решил писать на Python.
Сперва нужно было узнать координаты курсора, по которым показываются данные в карточке предмета на рынке.
Для этого я написал скрипт, который при нажатии клавиши пишет координаты курсора в консоль, затем переключился на окно игры, открыл карточку предмета и прошелся курсором по всем 28 точкам.
Вот так выглядит вывод координат:
Снятие скриншотов
После того, как были собраны все координаты, я написал скрипт, который последовательно перемещает курсор по ним, делает скриншот каждой позиции, переключается на карточку следующего предмета из списка и повторяет цикл до тех пор, пока не пройдет весь список.
Цикл из 28 скриншотов скрипт прошел примерно за 30 секунд. Чтобы сделать скриншоты всех уровней и зачарований для всех предметов из списка ему понадобилось примерно 9870 секунд (чуть больше 2,5 часов).
В итоге получилось 9212 скриншотов, которые заняли на диске около 8 ГБ:
Дальше нужно было кадрировать получившиеся скриншоты по вертикали и горизонтали, чтобы захватить только часть, на которой нужно распознать текст.
Обработка скриншотов
Тут появилась первая проблема: данные на скриншотах сдвигались вправо вместе с курсором, поэтому пришлось сперва кадрировать скриншоты по вертикали, а затем искать контур самого большого объекта на скриншоте и кадрировать уже по нему.
В итоге получились изображения размером примерно 200х50 пикселей для каждого предмета:
Затем нужно было распознать текст на получившихся изображениях.
Распознавание текста
Сперва я попробовал использовать библиотеку EasyOCR, но из-за несовместимости версий CUDA она никак не хотела запускаться на графическом процессоре, а распознавание на центральном процессоре занимало очень много времени.
Кроме того, результаты распознавания были не самыми лучшими: даже после доп. обработки изображений (увеличение размера, перевод в черно-белое и пр.) модель часто ошибалась и путала цифры, а мне не хотелось заниматься ее дообучением.
Затем я нашел библиотеку PaddleOCR, которая после установки всех зависимостей смогла запуститься на графическом процессоре, а время распознавания текста на всех изображениях для одного предмета (в среднем 770 шт.) сократилось с нескольких минут до нескольких секунд.
Вот результаты распознавания:
Ошибки в распознавании тоже встречались, но они были единичными и на последующую обработку данных я практически не потратил времени. Например, модель не всегда корректно распознавала цифры 0, 6 и 9 и их комбинации на тех изображениях, где было меньше трех цифр (69, 96 и 0).
Перенос текста в таблицы
Затем регулярным выражением вытащил из строк цифры и разделил их по столбцам:
В итоге получились 10 таблиц с данными по продажам каждого предмета:
А вот сводная таблица с данными по продажам всех предметов:
Анализ данных
Вот какие промежуточные выводы можно сделать из полученных данных:
1. За 28 дней в городе Лимхерст было продано 213 млн шт. ресурсов на общую сумму 173 млрд серебра. При стоимости месячной подписки 30 млн серебра этой суммы хватило бы на 481 год подписки.
2. Больше всего было продано древесины (48 млн шт.), меньше всего — руды и слитков (по 8 млн шт.). Причем больше всего продаж древесины пришлось на обычные бревна (44% от общего количества). Несмотря на то, что город находится в лесной локации, такой перекос в сторону бревен 1 уровня выглядит странно.
3. Большая сумма продаж пришлась на ткань (42 млрд серебра), меньшая – на камень (2 млн серебра). Здесь стоит уточнить, что Лимхерст – это город с бонусом переработки волокна в ткань, поэтому такой перекос в сторону продаж ткани вполне понятен.
4. На втором месте после ткани по сумме продаж идет кожа (29 млрд серебра), а на третьем – шкура. Это обусловлено наличием в локациях вокруг города большого количества животных и бонусами к созданию предметов, для производства которых требуется кожа.
5. Несмотря на то, что в городе есть бонус переработки волокна в ткань, само волокно продается в меньшем количестве и на меньшую сумму (12% и 9% от общего количества), поэтому есть смысл перерабатывать этот ресурс перед его продажей.
Планы на будущее
Теперь, когда у меня есть данные по всем ресурсам, я попробую собрать данные по предметам экипировки, чтобы выяснить, какие из них выгоднее создавать и продавать.
Если вы также как и я любите электронные таблицы и хотите самостоятельно поработать с собранными данными — напишите в комментариях, я поделюсь.