Зеленый биом на Планете жуков
Есть пустыня, лесные массивы, горы, снег. А теперь еще и цветущий край.
Спасибо за ваши комментарии.
Вопросы, наблюдения, мнения и советы - все очень важно и играет большую роль для меня.
Стараюсь отвечать всем.
Есть пустыня, лесные массивы, горы, снег. А теперь еще и цветущий край.
Спасибо за ваши комментарии.
Вопросы, наблюдения, мнения и советы - все очень важно и играет большую роль для меня.
Стараюсь отвечать всем.
Сижу и смотрю на криптокошелёк, а там 19.000$ от человека, которого я ни разу в жизни не видел. Это только половина: вторую он пришлёт, когда я отдам ему свою игру со всеми доступами, и никакого договора у нас нет, только переписка. Я два часа думал, отдавать или нет, а чтобы было понятно, почему решение далось так тяжело, расскажу всё с начала.
За три года до этого у меня было 3,5 миллиона рублей долга. Остались они от квест-хаусов: был у меня такой бизнес, он меня разорил, я его закрыл, а кредиты, поставщики, аренда и зарплаты никуда не делись. Потом ковид, локдаун, а у меня на плечах семья.
Тогда я и решил делать игры. Как это работает, я не понимал совершенно, зато очень верил в пассивный доход: сделаешь игру, выложишь, и деньги начнут капать сами. Игры же - это весь мир, думал я, один раз сделаешь крутую, и ты в шоколаде.
Я привлёк деньги инвестора, взял геймдизайнера, нанял программистов и за год и три месяца выпустил шесть игр, раннеры и одну детскую. Вложил в них 1,3 миллиона рублей, а заработал 50$. Не 50 тысяч, а пятьдесят долларов на шесть игр, и ужин в ресторане стоит дороже.
Вечер, когда я всё это осознал, помню хорошо. Сижу, листаю статистику, а там почти нет ни скачиваний, ни покупок, ни отзывов. Я не плакал, просто сидел и пытался понять, что делаю не так, пока долг никуда не девался. Короче, я был в глубокой жопе.
Помощь пришла, откуда я не ждал. Я тогда делал косметический ремонт и нанял для этого работника, Дениса. Скоро я стал замечать, что он всё время залипает в телефон, и однажды подошёл к нему:
— Денис, что ты там делаешь? Покажи.
Он показал мне популярную онлайн-игру: кубики, башни, лезут монстры, и играешь ты против живых людей по сети. Графика простая, почти примитивная, механика странная, но оторваться сложно, это я и сам почувствовал минут через пять.
— Это что, онлайн?
— Ага. Только сервер постоянно отваливается, игра всё время вылетает.
Я полез в отзывы, и там люди жаловались ровно на это. Тут меня и осенило: графика в игре простая, нарисовать такую можно недорого, так почему бы не сделать похожую игру, которой вообще не нужен интернет? Вылетать ей будет нечему, а людей, которых бесят вылеты, в оригинале хватает.
Скажу сразу, игра получилась очень похожей на оригинал, и в комментариях меня за это, конечно, назовут клоноделом, в чём-то справедливо. Но код писал мой программист, графику рисовала моя художница, а главное отличие стояло прямо в названии.
На этот раз я решил не повторять старых ошибок, так что обошёлся без большой команды и без инвесторов. Я наскрёб 30.000 руб. Программисту отдал 20.000 руб и потом доплатил ещё 3.000 руб за доработки. На графику почти ничего не оставалось, зато у меня лежали два набора игровой графики, купленные когда-то за копейки на распродаже, и художница за 7.000 руб переделала их под мою игру. Вышло ровно 30.000 руб и месяц работы, а название я дал такое, чтобы сразу было понятно: игре не нужен интернет.
Вечером я выложил её в Google Play, написал об этом в соцсетях и лёг спать, особо ни на что не рассчитывая. Утром открыл статистику, а там две покупки по 5$. Смешные деньги, но на фоне 50$ за шесть игр и год работы это было очень много. Одну покупку сделал мой подписчик, спасибо ему, а вторую совершенно незнакомый человек, который нашёл игру сам.
Дальше началось безумие. В первый месяц игра принесла 400$, во второй 900$, в третий 1.700$, потом 5.000$, 8.000$ и наконец 10.000$ в месяц. Рекламу я тоже пробовал, но она уходила в минус, так что почти все люди находили игру сами. Как это работает, я тогда толком не понимал, но впервые за три года увидел свет в конце тоннеля.
Свет, правда, быстро потускнел. 10.000$ оказались потолком, и дальше выручка покатилась вниз: 9.000$, 8.000$, 5.000$, 3.000$, пока не упала до 1.300$ в месяц. Команда к тому времени разрослась, я платил зарплаты и нескольким фрилансерам, так что мне самому оставалось, дай бог, 200–300$. Долг висел, семью надо было кормить, и я понимал, что ещё чуть-чуть, и я снова уйду в минус.
Тут мне написал знакомый и рассказал, что продал свою игру за сотни тысяч долларов. Я не понял, как это вообще, продать игру, это же не квартира и не машина. Оказалось, можно: есть специальные площадки, где игры продают как готовый бизнес, почти как квартиры на сайтах объявлений. Я зарегистрировался на одной из них и пошёл выставлять игру.
За десять месяцев игра заработала 48.000$. Просить столько же мне показалось наглостью, поэтому я сделал скидочку и выставил её за 42.000$, хотя сам в такую сумму не очень верил.
Через неделю мне пишет некий Али: «Куплю твою игру за 30.000$». Я смотрю на сообщение и не верю. Игра приносит мне 200–300$ в месяц, на мне ещё 2,5 миллиона рублей долга (миллион я к тому времени погасил), а тут 30.000$. Это же сто месяцев окупаемости за игру, которая умирает.
Сразу соглашаться было нельзя, иначе он бы понял, как сильно мне нужны деньги. Поэтому я с видом человека, у которого всё схвачено, ответил: «Али, спасибо за предложение. Это дёшево. Моя цена 38.000$». Enter я нажимал трясущимися пальцами.
Через час пришёл ответ: «Договорились». ЧТООООО?!
Платить договорились криптой. Али перевёл первые 19.000$, вот так взял и отправил незнакомому мужику такие деньги без всяких гарантий. Теперь была моя очередь отдавать игру, и тут я завис. А вдруг он её получит, взломает и пропадёт, и я останусь с 19.000$ вместо 38.000$?
Потом прикинул: даже если кинет, у меня будет 19.000$ за игру, которая приносит 200–300$ в месяц, и это, если подумать, не так уж плохо. А если не кинет, будет 38.000$ за ту же умирающую игру. Я думал час, потом ещё час и решил, что в худшем случае у меня останутся деньги и исходники, из которых можно собрать игру заново. Хрен с ним, иду ва-банк. Перенёс игру на его аккаунт, отдал все доступы и написал: «Теперь дело за тобой».
Такой нервной недели у меня давно не было, жена подтвердит. Эти деньги закрывали вообще все мои проблемы, и я ходил из угла в угол, постоянно проверяя то переписку, то кошелёк. А потом пришло уведомление: ещё 19.000$. Я продал за 38.000$ игру, которую сделал за 30.000 руб, и закрыл этими деньгами долг, который висел на мне три года.
После этой игры я ещё несколько раз пробовал тот же приём: берёшь популярную игру и делаешь похожую, но без её главной проблемы. Если у большой игры в отзывах все жалуются на одно и то же, для меня это до сих пор лучшая подсказка, что делать, а мне когда-то хватило пяти минут в телефоне у Дениса. Сейчас я делаю игры для Steam, о чём всегда мечтал, и долгов у меня больше нет.
А у вас был свой Денис, человек, который между делом, сам того не зная, развернул вашу жизнь? Расскажите в комментариях, я всё прочитаю.
Я соло-разработчик и архитектор в реальной жизни. Хочу рассказать, как появилась моя первая игра, Mirealle, и почему работа над ней помогла мне снова полюбить профессию, от которой я почти выгорел.
Уже много лет я занимаюсь интерьерами квартир и домов на заказ. Когда-то мне это нравилось, но постепенно работа превратилась в страшную рутину. В какой-то момент я буквально начал чувствовать себя роботом, который выполняет одну и ту же работу снова и снова. Творчества становилось всё меньше, а усталости всё больше, и это стало вгонять меня в депрессию.
Просто уволиться я не мог, поэтому продолжал работать, пока однажды не вспомнил, как в 15 лет делал небольшие Flash-игры. Они были очень простыми, но сам процесс создания, мне тогда безумно нравился.
И я решил попробовать снова.
Так появилась идея Mirealle.
Я начал практически с нуля. Не умел программировать, мало знал о современных игровых движках и геймдизайне. Поэтому вечерами после работы смотрел туториалы, учился, собирал что-то, ломал и переделывал.
Неожиданно я понял, что эта работа меня совсем не утомляет. Наоборот, мне постоянно хотелось продолжать.



Постепенно я начал переносить в игру всё то, чего мне не хватало в реальной архитектуре. Здесь не было бюджетов и надоедливых заказчиков. Я мог строить пространство вокруг одной идеи, создавать невозможную архитектуру и превращать лестницы, перспективу, свет или само здание в часть головоломки.
В какой-то момент я заметил, что снова часами думаю об архитектуре. Только теперь не потому, что должен, а потому что мне снова стало интересно.
Получилось немного иронично: я начал делать игру, потому что устал от архитектуры, а в итоге именно игра напомнила мне, почему когда-то я её полюбил.
Иногда, чтобы снова полюбить своё дело, нужно просто найти другой способ им заниматься.
Давно хотелось и вот добавили новый способ ощутить свободу в полете. Требует еще шлифовки (не хватает угловой инерции при раскрытии крыльев), но в целом уже приятно.
За кадром остались новый зеленый биом, завершенная цепочка стартового квеста, броня и амулеты.
Продолжаем работу.
Спасибо вам за поддержку!
Первая часть: Frostpunk Community Modkit. Или разработка модкита для первого фростпанка
Вторая часть: Как я вскрывал Frostpunk. Часть 2: Картинки, сломанное меню и закон-призрак
К этому моменту я уже умел менять игре настройки, тексты и картинки. Законы, технологии, иконки - всё моё!
Но здания всё равно были чужие. Я мог взять палатку и назвать её "Станция тепловых капсул".
Поменять ей стоимость, количество мест, иконку
А выглядела она всё равно как палатка
Хотелось своё. Свою модель
Ну что ж. Лезем в 3D. Что может пойти не так
Первый вопрос - где вообще лежат модели зданий. Отдельного склада с моделями у игры нет.
Было бы слишком просто
Нашлись они в складе templates, внутри шаблонов. Шаблон здания - это такая коробка, в которую запихано всё: как здание выглядит, какие у него эффекты, сколько стоит, сколько людей влезает. И модель тоже там, внутри, где-то между стоимостью и списком эффектов
Код, который её читает, я нашёл в игре тем же способом, что и в первой части. Файл движка называется meshtemplaterenderingdataserialization.cpp - разработчики, как обычно, забыли путь к исходнику внутри exe. Спасибо, ребята, без вас я бы ничего не нашёл!
Модель в 3D - это набор точек в пространстве, их называют вершинами, и треугольники, натянутые между ними. Любое здание, персонаж, дерево в любой игре - это просто дохуища треугольников
Каждая вершина во фростпанке весит 36 байт. Вот начало модели обычного уличного фонаря:
Первые четыре байта a4 01 00 00 - это 420. Столько вершин у одного сраного фонаря. Следующие четыре, 24 00 00 00 - это 36, байт на вершину. Дальше служебное описание буфера и сами вершины
Разберём одну:
позиция - три числа: x, y, z. Где точка висит в пространстве. Двенадцать байт
нормаль - куда смотрит поверхность в этой точке. По ней игра считает, как падает свет. Сжата до двух байт на ось, потому что жадные
касательная - ещё одно направление, поперёк нормали. Нужна, чтобы на ровной поверхности нарисовать рельеф: кирпичную кладку, доски, заклёпки. Без неё стена была бы гладкой
цвет - у всех ванильных моделей ff ff ff ff, то есть белый. Разработчикам нахуй не пригодился, но место занимает. В каждой вершине. Четыре байта. Умножьте на все модели игры
координаты текстуры - какая точка картинки натягивается на эту вершину. Записаны числами половинной точности, по два байта. Экономят как могут, а на цвете не сэкономили, логика
Дальше треугольники - просто тройки номеров вершин. А в самом конце шаблона - материалы, пути к картинкам и таблица "что и как рисовать"
Лимит - 65 535 вершин на модель. Потому что номер вершины в треугольнике записан двумя байтами, а больше в два байта не влезает хоть ты тресни. Запомните эту цифру, она ещё вылезет и укусит
Модкит научился превращать обычный файл модели OBJ - такие умеет сохранять любая 3D-программа - в формат фростпанка. Переложить вершины, пересчитать нормали и касательные, упаковать, запихнуть в шаблон
Сначала игра послала меня нахуй в принципе. "Compound template element 'mesh' references an invalid template" - мол, ссылаешься на модель, которой не существует. Хотя вот она, лежит, сука, я её сам туда положил
Оказалось, новую модель мало положить в склад, её надо ещё прописать в реестре шаблонов.
Это отдельная история и отдельная боль в жопе, расскажу когда-нибудь потом
В итоге всё собралось. Установил. Запустил игру. Построил
Здание было чёрным
Не тёмным. Не грязным. Не "ну освещение такое"
Чёрным, блядь, как дыра. Как будто кто-то вырезал кусок экрана ножницами и забыл вставить обратно
Вокруг снег, свет, огни генератора, всё красиво. А посреди - чёрная клякса в форме моей станции
Я смотрел на это и думал: ну пиздец. Приехали
Когда здание чёрное, причин может быть миллион. Игра не пишет в журнал "у вас тут чёрное здание, потому что вы долбоёб". Она просто рисует чёрное и молчит. Как партизан
Начал копать. И нашёл не одну причину, а четыре. Одновременно. Каждая сама по себе могла сделать здание чёрным. Это был не баг, это была коллекция
Причина первая. Картинка вверх жопой
В Blender и в формате OBJ координаты картинки считаются снизу вверх. А в DirectX, на котором работает фростпанк, - сверху вниз. Потому что стандарты - это когда каждый делает по-своему
То есть каждая точка модели брала цвет из зеркального места картинки. Доски тянулись с металла, крыша - с камня. Само по себе это не чернота, но каша ещё та
Лечится переворотом одной координаты.
Причина вторая. Чужие таблицы
В хвосте шаблона лежит таблица "с какого треугольника по какой рисовать". И рамка - коробка, в которую влезает модель. По рамке игра решает, видно здание в кадре или нет, и как от него падает тень
Я вставлял свою модель в шаблон палатки, а таблицы и рамка оставались от палатки. Игра честно рисовала столько треугольников, сколько было у палатки. И обрезала по габаритам палатки. А на то, что там теперь совсем другое здание, ей было глубоко все равно
Теперь модкит пересчитывает и таблицы, и рамку сам
Причина третья. Старая знакомая
Помните из второй части историю с 144-м байтом? Как модкит клал пиксели картинки не туда и получалась цветная каша?
Так вот. Эта ошибка жила в коде дольше, чем я думал, и выстрелила здесь второй раз. Текстуры модели съезжали, и игра жрала мусор
Ну охуенно. Одни и те же грабли, только ручка теперь длиннее
Причина четвёртая. Главная
А вот тут начинается самое весёлое
У материала здания во фростпанке две картинки. Первая называется _ALB - это просто цвет. Доски коричневые, металл серый, тут всё понятно даже мне
Вторая называется _NOR. По всем законам 3D-графики это карта нормалей. Та самая картинка, которая рисует рельеф: где выпуклость, где щель между досками. Классическая карта нормалей - сиреневая такая, все её видели
Я сделал обычную карту нормалей. Положил. Чёрное
Сделал заново. Положил. Чёрное
Да ёб твою мать
Тогда я пошёл туда, где правда. В шейдеры
Шейдер - это маленькая программа для видеокарты, которая решает, какого цвета будет каждый пиксель на экране. Как падает свет, как блестит металл, как лежит тень. Все шейдеры игры лежат в складе common, уже скомпилированные
Я вытащил их все и прогнал через дизассемблер от Microsoft. Он превращает скомпилированный шейдер обратно в читаемые команды
Шейдеров оказалось 6962. Шесть тысяч девятьсот шестьдесят два. На одну игру про замерзающих людей
И в них нашлось вот это:
RoughnessSourceMask - "откуда брать шероховатость"
MetalnessSourceMask - "откуда брать металличность"
Маска источника. То есть шейдер берёт шероховатость и металличность не из отдельных картинок, а из какого-то канала какой-то картинки
Какой, блядь? Я полез в ванильные текстуры. Взял карту _NOR обычной палатки и посмотрел, что лежит в каждом цвете:
Если бы это была обычная карта нормалей, красный и зелёный были бы в среднем около 128, а синий - около 255. Синий там всегда почти на максимуме, поэтому карты нормалей такие сиреневые
А тут синий в среднем 32. Почти ноль. Красный 170
Чё за хуйня?
Разложил картинку на четыре канала и посмотрел глазами:
И всё встало на свои места
красный - это вообще не нормаль. Это фактура материала: где дерево, где ткань, где металл. Шероховатость
зелёный - рельеф. Складки ткани, доски. Это половина настоящей нормали
синий - почти весь чёрный. Белые только отдельные детали: крепления, котёл, железяки. Это металличность. Чёрное - не металл, белое - металл
прозрачность - снова рельеф, но под другим углом. Вторая половина нормали
Четыре разные картинки, упакованные в одну. Разработчики сэкономили на текстурах. А я на этой экономии проебал весь день
А теперь смотрим, что я туда пихал. Обычную карту нормалей:
в синем канале 255 - игра читает это как "всё здание сделано из металла. Полированного до блеска"
в прозрачности 255 - игра читает это как "поверхность повёрнута набок"
Здание из полированного металла, у которого все поверхности смотрят вбок. Такое не отражает ничего, кроме пустоты. Поэтому чёрное
Логика железная. Буквально
Весь день я пихал игре сиреневую карту и не понимал, почему здание выглядит как дыра в пространстве
Теперь модкит сам собирает _NOR так, как ждёт игра: шероховатость в красный, наклон по вертикали в зелёный, металличность в синий, наклон по горизонтали в прозрачность. А если у модели нет карты шероховатости - подставляет средние значения, подсмотренные у ванильных зданий. Чтобы новое здание было похоже на остальную игру.
Бонус, который вылез заодно. Прозрачность в первой картинке, _ALB, - это маска свечения. Белым там, где ночью должно светиться. Окна, лампы, раскалённая печь. Про это будет отдельно, потому что окна у меня так и не зажглись. Эта сволочь до сих пор не работает
Ожило
Собрал. Установил. Запустил
Иду строить, а у самого жопа сжимается
Вот оно. Доски деревянные. Металл металлический. Даже снег на крыше лежит
Я минут пять просто крутил камеру вокруг и смотрел. Ну охуенно же, согласитесь
После этого я охуел от собственной крутости и решил, что пора делать что-то серьёзное. Не коробку, а настоящее большое здание. Спуск - огромную шахту-пропасть, вокруг неё постройки, над ней копёр с тросами, на которых вниз спускают людей и еду
Потом всё это нужно "запечь". Модель состоит из сотни отдельных деталей, у каждой свой материал. А игре нужна одна модель и одна картинка на всё. Капризная, блядь
Поэтому Blender разворачивает все детали на одну большую картинку 4096 на 4096 и "запекает" туда цвет, тени, рельеф, шероховатость и металл. Одна кнопка - и пять карт готовы
Модкит забирает это, упаковывает _NOR как положено, и вставляет в игру
Помните лимит в 65 535 вершин? Вот он и вылез
Модель стройки - полупрозрачные леса вокруг будущего здания - модкит генерирует сам. И у Спуска она получилась на 63 981 вершину. Впритык. Ещё один сраный домик сверху - и не влезло бы
Пришлось научить модкит склеивать одинаковые вершины. Стало 40 653. Выдохнул
Вот он, Спуск. В игре. С лифтом, который ездит вверх и вниз по шахте. Лифт, кстати, я взял у ванильного склада - там есть такой эффект, просто его никто не замечает, и он у меня немного поплыл) Потом исправил координату.
Итого
Что модкит умеет после этой части:
брать модель из Blender или любой 3D-программы и превращать её в здание фростпанка
правильно упаковывать материалы, чтобы здание не было чёрной дырой
сам делать модель стройки и снег на крыше
пересчитывать всё служебное: таблицы, рамку, касательные
Продолжение следует
Пишу метроидванию на годот, так выглядит игра на альфа стадии
Бесплатный набор, который идеально подойдёт для платформеров, метроидваний и других 2D-игр. Включает в себя 156 кадров покадровой анимации для персонажа размером 32×32 пикселя.
Внутри есть анимации движения, прыжков, взаимодействия со стенами и платформами, атак, получения урона и смерти. Отдельные анимации можно использовать независимо друг от друга и адаптировать под своего персонажа.
Что входит в набор:
– Основные анимации
1. Бездействие – 8 кадров
2. Ходьба – 10 кадров
3. Бег – 10 кадров
4. Прыжок – 4 кадра
5. Падение – 5 кадров
6. Скольжение – 5 кадров
7. Удержание у стены – 4 кадра
8. Удержание на выступе – 4 кадра
9. Атака 1 – 8 кадров
10. Атака 2 – 8 кадров
11. Широкая атака – 10 кадров
12. Атака вверх – 5 кадров
13. Атака с прыжком вверх – 6 кадров
14. Атака с падением вниз – 7 кадров
15. Отбрасывание – 5 кадров
16. Смерть – 9 кадров
– Переходы
1. Начало бега – 1 кадр
2. Остановка бега – 6 кадров
3. Начало скольжения – 4 кадра
4. Конец скольжения – 5 кадров
5. Прыжок в апексе – 5 кадров
6. Приземление – 4 кадра
7. Приземление с расстояния – 8 кадров
8. Удержание на стене – 3 кадра
9. Удержание на выступе / подъем – 5 кадров
10. Knockback Recover – 7 кадров
Автор разрешает использовать ассеты в коммерческих проектах, а также править всё, что есть внутри под свои задачи.
В прошлый раз мы научились читать игру. Все 51 файл настроек разбираются и собираются обратно байт в байт. Я сидел довольный, как кот на батарее, и думал, что самое сложное позади
Ага
Щас
Дальше я решил сделать первый настоящий мод. Свою ветку законов со своими картинками.
Что может пойти не так?
Всё
Всё может пойти не так
Картинка, которая на самом деле не картинка
Начнём с того, как фростпанк хранит изображения. Иконки, обложки законов, текстуры зданий - всё лежит в том самом складе textures-s3 на 6,8 гигабайта
Каждая картинка там - это не png и не jpg. Это файл .texture, и устроен он так:
[4 байта] ширина
[4 байта] высота
[4 байта] формат сжатия, буквами. Обычно DXT5
[4 байта] сколько в файле копий картинки
дальше таблица на 16 строк: размер каждой копии и сколько она весит
дальше сами картинки, начиная со 144-го байта
Вот начало атласа иконок технологий, вживую:
Разбираем:
00 08 00 00 - ширина. Задом наперёд, помним? 0x0800 = 2048
00 08 00 00 - высота, тоже 2048
44 58 54 35 - буквами "DXT5"
0c 00 00 00 - копий картинки 12
Дальше таблица. Первая строка: 01 00 01 00 и 10 00 00 00 - копия размером 1 на 1 пиксель, весит 16 байт. Следующая 2 на 2. Потом 4 на 4, 8 на 8, 16 на 16... и последняя 2048 на 2048, весит 4 мегабайта
Четыре строки таблицы пустые, одни нули. Таблица всегда на 16 строк, даже если копий меньше. А картинки начинаются ровно с байта 0x90 - это и есть 144
Внимательный читатель спросит: какого хрена одна картинка хранится двенадцать раз?
Это называется мип-уровни. Когда камера далеко и иконка на экране размером со спичечную головку, видеокарте незачем жевать все 2048 на 2048 пикселей. Она берёт уменьшенную копию.
Быстрее, и картинка не рябит. Поэтому в файле лежит та же картинка, каждый раз вдвое меньше, вплоть до одного несчастного пикселя
Ещё внимательнее читатель спросит: почему картинка 1 на 1 пиксель весит 16 байт?
А тут начинается магия сжатия
Обычная картинка хранит каждый пиксель честно: красный, зелёный, синий, прозрачность. 4 байта на пиксель
DXT5 так не делает. Он режет картинку на квадратики 4 на 4 пикселя и для каждого квадратика записывает:
два "главных" цвета
для каждого из 16 пикселей: какой он - первый цвет, второй или что-то между ними
отдельно то же самое для прозрачности
Итого 16 байт на квадратик из 16 пикселей. Вместо 64. В четыре раза меньше
И видеокарта умеет читать это напрямую, ничего не распаковывая. Поэтому все игры так и делают
А картинка 1 на 1 весит 16 байт, потому что меньше одного квадратика не бывает. Хочешь ты один пиксель или шестнадцать - платишь за квадратик целиком. Как такси с минимальным тарифом
Цена - качество. Внутри квадратика всего два настоящих цвета и смесь между ними. Если приблизить иконку, видно, как края идут ступеньками:
Теперь мои первые грабли
Первая версия модкита записывала картинки неправильно. Я клал пиксели сразу за заполненными строками таблицы. Без пустых строк
А игра всегда читает пиксели со 144-го байта. Всегда. Хоть там пусто, хоть нет
У картинки 1024 на 1024 из-за этого всё съезжало на 40 байт. А 40 не делится на 16 - на размер одного квадратика. Значит игра начинала читать квадратики с середины. Каждый квадратик собирался из хвоста одного и головы другого
Результат - цветная каша вместо иконки
Лечилось одной строкой. Искалось... дольше, чем хотелось бы
С заменой картинок разобрались. Берёшь существующую, перерисовываешь, кладёшь на место
Но мне нужны были новые картинки. Обложка для своей ветки законов. Картинка для своего закона. Такого файла в игре никогда не было
И модкит честно ответил: "Не удалось подготовить изображение ui/sociotech/lastshift_charter_cover.dds"
Ну конечно. Как заменить то, чего нет?
Решение напрашивалось: дописать новую коробку в конец склада и внести её в журнал
Дописывать в конец - это важно. Склад весит 6,8 гигабайта. Перепаковывать его целиком ради одной иконки - это минут десять и гигабайты оперативки. А дописать в хвост - секунды. Старые коробки при этом никто не трогает
Но есть нюанс
Журнал отсортирован. Строго. По хэшу имени - тому самому отпечатку из первой части
Зачем? Потому что игра ищет в журнале файлы методом, которым вы ищете слово в бумажном словаре. Открыла посередине. Нужное слово раньше? Открыла середину первой половины. Позже? Середину второй. И так, пока не найдёт
Это называется двоичный поиск, и он быстрый. Среди 4178 записей нужная находится шагов за двенадцать
Но работает он только если словарь отсортирован. Если хоть одна страница вклеена не туда, поиск уходит не в ту половину и возвращается ни с чем
Я дописал новую запись. Отсортировал журнал заново. Собрал мод. Запустил игру
Главное меню выглядело так, будто его постирали с отбеливателем
Бледное. Без текстур. Кнопки висят в пустоте
Я добавил ДВЕ картинки. Для закона. А сломалось вообще всё
Причина - та самая подстава из первой части. Числа задом наперёд
Хэш - это число из четырёх байт. Записан задом наперёд. Игра сортирует журнал по числу
А я сортировал по байтам. Как они лежат в файле
Разница вот:
Слева хэши, как они лежат в файле: d1, 20, cc, d1, 9a... Скачут как хотят. Справа те же хэши как числа: 11970769, 13419040, 13597900... строго по возрастанию
Проверка внизу подтверждает: журнал отсортирован по числу - да. По байтам - нет
То есть я взял правильно отсортированный словарь и пересортировал его по какому-то своему алфавиту. Двоичный поиск игры начал промахиваться. И не только по моим двум картинкам - по всем, кому не повезло оказаться рядом с переставленными страницами. В том числе по картинкам меню
Самое обидное - у меня была проверка. Автоматический тест, который проверял, что журнал после добавления отсортирован
Он проверял. И говорил: всё отлично
Потому что проверял сортировку тем же неправильным способом, каким я сортировал
Проверял свою ошибку своей же ошибкой. Ну охуеть теперь
Переписал тест так, чтобы он сравнивал числа. И добавил специальную ловушку: три придуманных хэша, у которых порядок по байтам и порядок по числу гарантированно разный.
Сначала убедился, что старый код на ловушке падает. Только потом поверил исправлению
Игру вернул в исходное состояние из резервных копий. С тех пор модкит перед любым вмешательством кладёт рядом файлы .orig. А кнопка "Вернуть игру к оригиналу" просто отрезает дописанный хвост и возвращает старый журнал. Шесть с лишним гигабайт никуда копировать не надо
Меню починено. Картинки ложатся. Делаю свою ветку законов, "Хартию линии". В ней несколько законов, среди них "Ночные смены"
Собираю. Запускаю. Открываю книгу законов
"Ночных смен" нет
Нигде
Ни в одной клетке. Ни после какого закона
А закон "Хартия линии" гордо показывает "ЗАКОН ПРИНЯТ"
Хотя я его не подписывал. Я вообще ничего не подписывал, кроме одного закона, "Приоритета".
Это видно в журнале игры
Дальше была неделя позора. Ну ладно, не неделя. Но гипотез было много, и все мимо:
"Игра не поддерживает закон, у которого два родителя". Проверил на сохранении - поддерживает, ванильные законы так устроены
"Движок сам подписал Хартию". Я даже нашёл это в файле сохранения! Оказалось, неправильно расшифровал восемь байт
"Закон дальше определённой клетки не откроется". Не доказал, не опроверг, но причина была не в этом
А причина оказалась до обидного простой
У каждого закона, кроме названия для игрока, есть внутреннее имя. Служебное. Игрок его не видит, по нему игра различает объекты
И это имя игра хранит в однобайтовой кодировке. Где на букву выделен ровно один байт.
Латиница туда влезает, а русских букв там просто нет
И что игра делает с буквой, которой нет? Правильно. Ставит вопрос
"Хартия линии" превращается в "?????? ?????"
"Ночные смены" превращается в "?????? ?????"
Совпадение? Нет. Шесть букв, пробел, пять букв. В обоих
Два разных закона и в придачу сама ветка "Хартия линии" получили ОДНО И ТО ЖЕ имя
Игра смотрит: "так, закон ?????? ?????. Подписан? Ага, подписан... ой, это другой ?????? ?????. Ну, неважно". И рисует одного вместо другого, путает, кто подписан, а кто нет
Три разных объекта с одним паспортом. Представьте, что у вас в классе три Иванова Ивана Ивановича, и учитель ставит двойку одному из них. Кому? А хер его знает. Всем по очереди
Лечение - одна функция. Модкит теперь сам переводит внутреннее имя в латиницу и следит, чтобы оно было уникальным:
Игрок при этом ничего не замечает. Он видит название из файла текстов - нормальное, русское. А внутри у закона теперь "Khartiya linii", и путать его не с кем
Проверил в игре. Ветка заработала полностью. "Ночные смены" нашлись там, где им положено, "Хартия" перестала врать про подписание
Главный урок этой истории, который я выучил очень дорого: не чинить по догадке
Каждая неправильная гипотеза - это сборка мода, запуск игры, клики по меню, разочарование.
Правильнее ставить опыт, который разводит причины по разным законам. Сделал бы сразу два закона с латинскими и два с русскими именами - нашёл бы за один запуск
А потом законы замолчали
Бонусные грабли, коротко
Когда подписываешь закон, горожане его обсуждают. "Опять пайки урезали, суки". Вот это всё
Я написал реплики для своих законов. Все тексты на месте, всё правильно
В игре - тишина
Оказалось, кроме файлов с текстами у игры есть отдельный указатель всех ключей, тоже отсортированный. И реплики она перебирает именно по нему. Мои новые ключи в указатель не попали - значит для игры их не существует
Теперь модкит при сборке дописывает свои ключи и туда. Горожане заговорили. Жалуются. Всё как в жизни
Последнее про картинки. Иконки технологий не лежат каждая в своём файле. Они все на одной большой картинке 2048 на 2048, нарезанной на сетку 20 на 20 клеток. Называется атлас
Технология в настройках говорит не "моя иконка - вот этот файл", а "моя иконка - клетка номер такой-то"
Хочешь свою иконку - нужна свободная клетка
Какая клетка свободна? Логичный ответ: та, на которую не ссылается ни одна технология
Нелогичный, но правильный ответ: та, которая полностью прозрачная
Потому что 65 клеток атласа заняты картинками, которые не принадлежат ни одной технологии.
Какие-то рамки, круги, цифры, значки бонусов. Кто на них ссылается - хрен поймёшь, но они там не просто так. Первая версия модкита могла их затереть. Теперь иконки ложатся только в полностью пустые клетки
Вот он, атлас в модките. Пунктиром отмечены свободные клетки, их 257. Видно, сколько там всего помимо иконок технологий: круги, цифры I, II, III, какая-то табличка DANGER
Коммуналка, в которой половина жильцов не прописана
Итого
Что умеет модкит после этой части:
заменять любую картинку игры
добавлять новые картинки, которых в игре никогда не было
класть свои иконки в свободные клетки атласов, не трогая чужие
откатывать всё обратно одной кнопкой
Продолжение следует