![](https://habrastorage.org/webt/_-/xa/bx/_-xabxneqv1ytoka250ywrqtdvq.jpeg)
Многопользовательская Counter-Strike: Global Offensive наполнена различными раскрасками для оружия разной степени редкости и привлекательности. Некоторые игроки гонятся за уникальными скинами, а другие выбирают на основе субъективного вкуса. Помимо официальной торговой площадки Steam, скины можно купить на сторонних ресурсах, доверие к которым невелико. Но в обоих случаях нет фильтра по цвету.
Вручную перебирать все варианты раскраски для всех видов вооружений очень долго. К счастью, проблему можно автоматизировать. В статье я покажу, как извлечь необходимые ресурсы из игры, и еще раз поговорю про сложность определения схожести цветов.
Мотивация
![](https://habrastorage.org/webt/tg/qt/yr/tgqtyrc_qncprskv-9bydz8ffhk.png)
Феечки почти в сборе
Ранее я уже писал про игровые интеграции CS:GO на Хабр. В примере вывода была информация о нике моего профиля – .:WinX:. Musa. Восемь лет назад я и несколько моих друзей взяли себе в качестве ников имена феечек из мультфильма «Клуб Винкс» и создали закрытый клан в Steam, чтобы иметь общий тег в игре.
Наличие от трех до пяти феечек в команде в соревновательном режиме нередко радовало соперников и становилось поводом для обсуждения, какая феечка самая любимая. За весь игровой стаж мне встречались персонажи из мультика «My Little Pony» и команда продуктов Microsoft Office в полном составе.
После одного из вечерних матчей мне пришла в голову идея дополнить образ феечки. По канону мультфильма у каждой феи есть финальная и самая мощная форма — энчантикс. Мне показалось интересным переименовать самое мощное оружие в игре, снайперскую винтовку AWP, по названию высшей формы фей. Очевидно, что расцветка оружия должна совпадать с цветом феечки.
![](https://habrastorage.org/webt/04/k3/g1/04k3g1vtk_dfpbfh4jeghisuhw8.png)
Если вы не знаете, как они выглядят
Выбирать скины вручную из тысячи вариантов — дело не самое приятное. Процесс хотелось автоматизировать. За день я собрал прототип, который представил своим друзьям. В целом, я решил поставленную проблему, но возникла другая…
Обо всем по порядку.
Список возможных скинов
Первый шаг — получение списка всех раскрасок для каждого типа игрового вооружения. Очевидным вариантом кажется автоматизировано «потыкаться» в торговую площадку Steam или в неофициальные ресурсы. Онлайн-сервисы часто не любят, когда их «парсят», и могут сопротивляться. А торговая площадка и при штатном использовании иногда выдает ошибку, так что способ крайне ненадежный.
Идея посмотреть в файлы игры на первый взгляд казалась сложной, но стабильной. При написании текста про игровые интеграции CS:GO я заметил параметр paintkit в информации по вооружению. Параметр был заполнен явно идентификатором раскраски, например, cu_glock_noir. Вооружившись grep’ом я нашел несколько полезных файлов:
- csgo\scripts\items\items_game.txt — почти 6 МБ различной информации, в том числе список раскрасок и наклеек.
- csgo\scripts\items\items_game_cdn.txt — ссылки на картинки для предпросмотра скинов, эти же картинки используются на торговой площадке.
- csgo\resource\csgo_*.txt — файлы локализации.
Начнем с легкого. Файл со ссылками имеет примитивный формат: каждая строка имеет шаблон «ключ=значение». Например:
weapon_glock_cu_glock_noir=http://media.steampowered.com/apps/730/icons/econ/default_generated/weapon_glock_cu_glock_noir_light_large.c93d0cbfa767d1f822a53ebfca0d57f532088c48.png
Файлы локализации и items_game.txt имеют одинаковый формат, похожий на JSON. Отрывок из файла items_game.txt.
"rarities"
{
"default"
{
"value" "0"
"loc_key" "Rarity_Default"
"loc_key_weapon" "Rarity_Default_Weapon"
"loc_key_character" "Rarity_Default_Character"
"color" "desc_default"
"drop_sound" "EndMatch.ItemRevealRarityCommon"
}
"common"
{
"value" "1"
"loc_key" "Rarity_Common"
"loc_key_weapon" "Rarity_Common_Weapon"
"loc_key_character" "Rarity_Common_Character"
"color" "desc_common"
"weight" "10000000"
"next_rarity" "uncommon"
"drop_sound" "EndMatch.ItemRevealRarityCommon"
}
}
Проведя беглый обзор по файлу, я вывел следующие правила построчного чтения файла конфигурации:
- Если в строке две кавычки, это означает наличие вложенного ассоциативного массива.
- Открывающую фигурную скобку можно игнорировать, а закрывающая фигурная скобка обозначает конец вложенного ассоциативного массива.
- Если в строке четыре кавычки, то это строка ключ-значение. Ключ и значение разделены пробельными символами — двумя табуляциями.
В ходе разбора выяснилось несколько неприятных моментов.
- Ключ и значение разделены не обязательно табуляциями и не обязательно двумя. Разделителем может быть и пробел.
- В некоторых параметрах идентификатор локализации начинается с символа решетки (#).
- Идентификатор локализации может иметь разный регистр в разных файлах. Например, в items_game.txt указано Paintkit_sp_palm, а в файле перевода – PaintKit_sp_palm. Возможно, это актуально для всех идентификаторов, поэтому при разборе на всякий случай все ключи перевожу в нижний регистр.
Файл items_game.txt содержит невероятное количество информации. Помимо информации о непосредственно игровых объектах и их атрибутах, можно найти список киберспортсменов и крупных международных турниров, даже статистику по игрокам для старых матчей.
![](https://habrastorage.org/r/w1560/webt/5w/f6/q5/5wf6q551s0ig-eohrr6hibm19yo.png)
Для моей задачи важна только секция paint_kits, которая содержит список раскрасок. Информация о раскраске включает название, редкость и в некоторых случаях палитру, но не включает информацию о том, где применяется. Я решил убить двух зайцев и в файле items_game_cdn.txt извлечь сразу и ссылку, и название оружия.
Ключ в файле items_game_cdn.txt формируется по такому принципу:
<идентификатор предмета>_<идентификатор раскраски>
Идентификатор скина известен, удаляем его из строки и получаем идентификатор предмета. Для всех вооружений в игре идентификатор предмета начинается с weapon_.
Изображения, на которые ведут ссылки, хранятся на CDN, поэтому массовое скачивание изображений не станет проблемой. Теперь у нас есть изображения и мета-данные о раскрасках. Время поиграться с
Создание библиотеки цветов
![](https://habrastorage.org/webt/bs/b8/pk/bsb8pkon9kcyu6myte0ilcfe-cq.png)
Красивый скин, да?
В метаданных для некоторых раскрасок встречаются параметры color0…color3.
"51"
{
"name" "am_lightning_awp"
"description_string" "#PaintKit_am_lightning_awp"
"description_tag" "#PaintKit_am_lightning_awp_Tag"
"pattern" "lightning_strike"
"wear_default" "0.000000"
"style" "5"
"color0" "7 5 6"
"color1" "65 7 99"
"color2" "100 36 38"
"color3" "108 108 142"
"phongalbedoboost" "40"
"phongexponent" "8"
"pattern_scale" "1"
"pattern_offset_x_start" "0"
"pattern_offset_x_end" "0"
"pattern_offset_y_start" "0"
"pattern_offset_y_end" "0"
"pattern_rotate_start" "0"
"pattern_rotate_end" "0"
"ignore_weapon_size_scale" "1"
"wear_remap_min" "0.000000"
"wear_remap_max" "0.080000"
}
Для быстрого прототипа я использовал эти цвета в качестве палитры. Идея провалилась: не в каждой записи есть такие атрибуты. Так как у нас есть изображения скинов, которые используются на торговой площадке, можно попытаться создать палитру на их основе. Беглый поиск по интернету привел к библиотеке ColorThief, которая создает N кластеров на основе доступных цветов и в качестве палитры предлагает центроиды кластеров.
Решение рабочее, но
- полученные изображения имеют максимальное разрешение 512x512, мелкие детали могут потеряться?
- изображение демонстрирует предмет с одной стороны?
- цвета в атрибутах color* могут не совпадать с цветами на изображении.
Решение для этих недостатков простое: считать палитру не по картинкам торговой площадки, а брать текстуры из игры. К счастью, на официальном сайте игры доступна документация по созданию раскрасок. На данный момент в игре доступны 9 способов нанесения раскраски и наложения текстуры на модель.
![](https://habrastorage.org/webt/ri/zm/ke/rizmkem6gsyi1zezgn6cf6cfqhq.png)
Раскраска по цифрам доступна в игровом клиенте
Рассмотрим основы:
- Покраска по цифрам. Разработчики пронумеровали каждую деталь на оружии цифрой от 1 до 4. Задаем цвет для каждой цифры и получаем новый скин. В этом случае цвета в атрибутах можно принять за палитру скина.
- Ручная работа. Текстура накладывается на модель как есть. Можно считать палитру на файле текстуры.
- Покраска по маске. Четыре цвета наносятся по маске, которая хранится в файле текстур. Рассмотрим этот способ подробнее.
![](https://habrastorage.org/webt/wb/g8/6p/wbg86pzspcrqhaprzywyrs4_zeq.png)
Как работают текстуры в покраске по маске. Источник
Яркая текстура хранит три маски, спрятанных по каналам: красный, зеленый, синий. Цвет в атрибуте color0 задает цвет фона, а color1…color3 — цвета для масок в каналах.
![](https://habrastorage.org/webt/fa/xu/xf/faxuxfrehurtcxrd8msuahqzs6a.png)
Наложение каждого цвета и промежуточные маски
На языке Python наложение текстур реализовать можно с помощью библиотеки PIL.
# Текстура из файлов игры
image = …
channels = image.split()
# Создаем фон
texture = Image.new("RGB", (image.width, image.height), color[0])
for index, mask in enumerate(channels, 1):
# Создаем заполнитель
filler = Image.new("RGB", (image.width, image.height), color[i])
# Объединяем картинки
texture = Image.composite(filler, texture, mask)
Остается вопрос извлечения текстур из игры. Скины хранятся в бинарном файле pak01_dir.vpk. Данный тип файлов открывается с помощью утилиты HLExtractor, которая является примером использования библиотеки HLLib. Сами файлы текстур хранятся в известном формате VTF (Valve Texture Format), для которой тоже есть библиотека и реализация на Python.
После обработки всех 1113 скинов имеем JSON-файл с кратким описанием каждого скина и палитры из четырех цветов в форматах RGB и HSV. Что можно сделать для поиска по цвету?
Поиск по цвету
У феечек может быть несколько цветов, но для упрощения сделаем поиск по одному. Наивное решение: в пространстве RGB посчитать евклидово расстояние от запрошенного цвета до всех цветов в палитре, найти минимум и отсортировать все скины по увеличению найденного расстояния. Второе наивное решение: в пространстве HSV искать по минимальному расстоянию между компонентами Hue.
Интересное решение подсказал мне коллега по науке. Палитра цветов — вектор размерностью 12. В этом случае поисковый запрос — аналогичный вектор. Для упрощения пользователь выбирает один цвет, который заполняет собой вектор запроса. Однако такой подход позволяет добавить кнопку «показать похожие» и искать более схожие скины.
![](https://habrastorage.org/webt/2x/9k/qb/2x9kqbnodjnehxbbojcndyijpms.png)
Веб-интерфейс
Изначально поиск работал только в интерфейсе командной строки, но открывать ссылки было неудобно. Я взял Bootstrap, FastAPI и быстро из примеров соорудил одностраничный сайт с формой для поиска по цвету.
Остался финальный штрих. Поиск скинов был не только ради спортивного интереса, но и для поиска подходящих, но дешевых скинов, а цен в интерфейсе нет. У торговой площадки Steam также нет общедоступного API. Решение пришло спонтанно: открывать страницу с поиском на торговой площадке.
![](https://habrastorage.org/webt/l6/cz/15/l6cz157psbkfajpoh1ba_mdt8hs.png)
Я нашел красивый скин, но его цена меня не устраивает :(
После пары сложных поисковых запросов на торговой площадке Steam идентифицированы важные параметры запроса.
- steamcommunity.com/market/search — базовый URL запроса.
- appid=730 — ограничить поиск по одной игре, 730 — это SteamID игры CS:GO.
- q=Запрос — текст запроса, в нашем случае имя скина.
- category_730_Weapon[]=tag_weapon_glock — тег вида вооружения. Формируется по шаблону tag_%идентификатор_вооружения%. Идентификатор извлекается вместе со ссылками на изображения скинов на CDN.
При переходе по ссылке можно получить больше информации меньшей кровью:
- доступность скинов различной степени потертости и их цену;
- наличие StatTrek-версии скина;
- при желании тут же и купить скин.
Заключение
Я хотел решить проблему подбора скинов суровым, возможно, излишне суровым, техническим методом, и я этого добился. Я поискал по цвету моего персонажа (#A80039) и составил себе вот такой список скинов.
![](https://habrastorage.org/webt/1o/am/qs/1oamqstfyvhvunheeroojdx5aq8.png)
Этот набор мне нравится. Чего не скажешь про его цену. Суммарно он будет стоить от 53 тысяч рублей без ножа или от 93 тысяч рублей с ножом. При этом стоимость возрастает с уменьшением потертостей на скине. Дорогое это удовольствие, быть феечкой!
Исходный код и документация доступны в репозитории на Github.
Комментарии (9)
leorikz
23.08.2022 12:40привет, спасибо за статью. все эти рюшечки из лутбоксов изначально берутся? или сразу за деньгу
Firemoon Автор
23.08.2022 12:55По умолчанию из лутбоксов, но ключи к лутбоксам тоже надо покупать...
DrinkFromTheCup
23.08.2022 13:14Тенически, ключ и есть универсальный лутбокс, в игре появляется строго за деньги - но поменяться с уже купившим его игроком тоже можно.
Наличие коробки всего лишь определяет, что именно из лутбокса выпадет.ubriaco
23.08.2022 14:40Все было бы так, если бы ключ действительно был универсальным, но у каждого вида кейса свой ключ.
shushu
23.08.2022 15:30Да на самом деле нет разницы. Важно то, что ключ надо покупать и нету ни одной возможности получить ключ бесплатно.
Есть возможность получить айтемы бесплатно, но они совсем "унылые"
KiddingBanana
23.08.2022 20:38Можно собирать из дешевых айтемов контракты для получения более дорогих, но это, конечно, адский гринд. Равно как и продажа того, что выпало. Однако, в теории получить желаемые скины бесплатно возможно
drondroncki
Круто