
Не так давно я предавался ностальгии по Star Wars Jedi Knight: Jedi Academy — проекту, в котором в юности провел, пожалуй, с десяток тысяч часов. Углубившись в историю игры, я наткнулся на весьма примечательный эпизод.
Оказалось, что в 2013 году, сразу после поглощения Lucasfilm корпорацией Disney и ликвидации студии LucasArts, разработчики из Raven Software, опасаясь, что результаты их многолетнего труда навсегда канут в Лету, в экстренном порядке опубликовали исходный код Jedi Outcast и Jedi Academy в открытом доступе.
Этот «слив» произошел настолько стремительно, что программисты даже не потрудились удалить внутренние комментарии. В результате код превратился в своего рода капсулу времени, позволяющую прочувствовать все муки и отчаяние разработчиков, пытавшихся «подружить» физику светового меча с движком Quake 3.
Если заглянуть в ключевой модуль боевой системы (bg_saber.c), становится очевидно, что логика сражений держится на «спагетти-коде» — исполинской конструкции switch, насчитывающей около пяти тысяч строк.
Я изучил репозиторий и нашел там совершенно поразительные комментарии:
1. sv_savegame.cpp — ради того, чтобы игрок успел заметить уведомление «Saving», автору пришлось имитировать цикл ожидания.
// Если мне придется еще раз писать этот бред, я, пожалуй, брошусь под автобус.
int startOfFunction = Sys_Milliseconds();
// ...спустя несколько десятков строк...
// Отложенный скрипт первым делом закрывает плашку "Saving",
// но нам критически важно, чтобы она висела хотя бы секунду, поэтому крутимся здесь.
// См. заметку про автобус выше.
2. AI_Jedi.cpp — попытки заставить NPC эффективно использовать Силу и ориентироваться в трехмерном пространстве на процессорах 2003 года явно давались с трудом.
{ // к черту, просто прыгаем
{ // к черту, просто применяем Силу
3. Расчленение (G2_bones.cpp) — для отрисовки отрубаемых конечностей Jedi Academy использовала собственную систему скелетной анимации. Необходимость ручной настройки углов сочленений вызвала у разработчика приступ сарказма:
// зачем это нужно делать — известно одному дьяволу. Но делать это нужно.
4. bg_pmove.cpp — борьба с персонажами, которые упорно скользили по ровным поверхностям, видимо, отняла немало нервов.
{ // мы на земле, стоим неподвижно на плоской поверхности, не надо добавлять сраную гравитацию через clipvelocity!!!
5. NPC_reactions.cpp — логика поведения, когда игрок наводит прицел прямо в лицо дружелюбному NPC.
// спрашиваем, какого черта он творит
6. Математика Quake (q_math.cpp) — знаменитый алгоритм битового хакинга из движка Джона Кармака. Даже спустя годы специалисты Raven признавались, что не понимают, как это вообще работает.
i = 0x5f3759df - ( i >> 1 ); // что за чертовщина?!
7. mhead.c — реакция на поврежденные заголовки в MP3 или WAV файлах.
return 0; // бог знает, что это такое, но явно не наше...
8. Фатальный краш (win_glimp.cpp) — что случается, если графический рендерер категорически отказывается запускаться в среде Windows.
// сообщение об ошибке, к которому я откачусь, если случится нечто непоправимое
9. Ненависть к Win32 (инструмент ModView) — те, кто имел «удовольствие» работать с Win32 API, поймут негодование, скрытое в названии этой функции обновления заголовка окна.
void FuckingWellSetTheDocumentNameAndDontBloodyIgnoreMeYouCunt(LPCSTR psDocName) {
if (gpLastOpenedModViewDoc) {
// чтобы он, черт возьми, сделал именно то, что от него требуется...
gpLastOpenedModViewDoc->SetTitle(psDocName);
}
}
10. wp_saber.cpp — ситуация, когда из-за чьего-то сомнительного архитектурного решения приходится объявлять переменные через extern.
// Приходится прописывать extern вручную. Нельзя сделать include qcommon.h из-за одного конкретного олуха
11. Ограничения скелетных мешей (g_client.cpp) — попытка сделать поворот позвоночника персонажа вслед за мышью, не разрушив при этом хитбокс.
// УХ... ломаем его до основания
// ...18 строк спустя...
// УХ... позвоночник болтается, к чертям всё это
12. Исправление коллизий NPC (g_active.cpp) — самый «эффективный» метод борьбы с багом физики, из-за которого NPC погибали при контакте друг с другом.
Так как движок Quake 3 затачивался под мультиплеер, в котором все объекты — «клиенты», при портировании под синглплеер столкновение NPC на большой скорости приводило к их мгновенной смерти. Разработчики не стали тратить недели на переработку ИИ и просто добавили if, отключающий урон при столкновении NPC между собой.
{// да ну его к черту, клиенты больше не наносят друг другу урон, если только это не игрок
13. Логика транспорта (g_vehicles.c) — обработка инцидентов, когда летающие спидеры врезаются в препятствия.
{// просто катапультируемся
14. Урон от взрыва (g_mover.c) — вычисление урона по целям рядом с разрушаемым объектом.
{// разносим их в щепки
16. Отказ от линейной алгебры (r_surface.cpp). ModView использовался для рендеринга персонажей. Видимо, работа с 3D-матрицами и поверхностями стала для кого-то последней каплей.
// К черту эту математику, она все равно не работает
// #define real_nclip(x0,y0,x1,y1,x2,y2) ( (y1-y0)*(x2-x1) - (x1-x0)*(y2-y1) )
17. Конфликты с «железом» начала нулевых (textures.cpp). Жесткая фильтрация ошибок ради того, чтобы драйверы AMD/ATI не обрушивали утилиту.
if (error && error != GL_STACK_OVERFLOW
/* кривые карты ATI почему-то иногда выкидывают эту ошибку */ )
18. Проблемы с аудио (cl_mp3.org). Чтобы экономить память, движок декодирует звук последовательными блоками, поэтому перемотка аудиобуфера назад невозможна. Если из-за лагов движка аудиопоток «отставал», программист решил просто смириться.
// Чё?! Я что, должен уметь путешествовать во времени? Идите к черту
Поразительно осознавать, что на этом «архитектурном чуде» работает один из лучших мультиплеерных шутеров в истории. Огромная благодарность команде OpenJK за то, что привели этот хаос в порядок и подарили игре вторую жизнь.


