Анатомия игры: искусственный интеллект

Анатомия игры: искусственный интеллект

Добрый вечер, дорогие хабражители! Практический материал все еще требует больших аппаратных мощностей, чем у меня есть, поэтому продолжим теоретическое ознакомление. Сегодня мы немножко поговорим об искусственном интеллекте в играх, его формах и проблемах.

Но сначала придется чуть-чуть обмануть ваши ожидания.
Дело в том, что словосочетание «искусственный интеллект» (artificial intelligence) в сознании обывателя сегодня практически всегда ассоциируется с передовыми технологиями нейросетей наподобие ChatGPT и MidJourney. Однако игроки начали употреблять его давным-давно. Практически с тех самых пор, как в играх появились управляемые компьютером враги.

Даже сейчас, просматривая игровые форумы почти на любом языке чуть ли не  за любой год из последних тридцати, вы рано или поздно наткнетесь на такие фразы: «что-то ИИ шалит», «ИИ врагов работает не так, а вот так», «кажется, разрабы поправили ИИ болванчикам». 

Казалось бы, ИИ — это что-то новенькое, разве нет? Откуда геймеры тридцатилетней давности взяли игру с искусственным интеллектом?

Стоит сказать, что нейросети существуют уже давно. Аналоги ChatGPT успешно пугали малообразованных пользователей зарубежных социальных сетей еще в 2012 году, а похожие и аналогичные обучаемые алгоритмы изобрели еще пятьдесят лет назад. Другое дело, что вычислительных мощностей на момент их появления еще не было, как и удобных языков программирования.

Но в играх история немного другая. Впрочем, для понимания вопроса нам сперва нужно определиться, что вообще такое этот ваш загадошный» ИИ.

▎Что такое «Искусственный интеллект»

 

Пожалуй, это один из самых сложных вопросов современной науки и философии наряду с «Вопросом бытия», «Смыслом жизни», «Определением живой материи» и «Происхождением вселенной». Тут вы можете воскликнуть: «Ага! Ученые — мозги моченые! Ничего-то вы не можете определить!» Но подождите. Тут вот какая загвоздка.

У нас есть такое понятие «Интеллект». Словарики в среднем определяют это как «способность оценивать и адекватно реагировать на окружающую действительность». 

Интеллект, -а, м. Ум (в 1 знач.), мыслительная способность, умственное начало у человека. (Ожегов). Интеллект: мыслительная способность человека, его умственное начало. (Ефремова). Интеллект (лат. intellectus — понимание, понятие) (·книж. ). Ум, рассудок, мыслительная способность у человека (в противоп. воле и чувствам). (Ушаков)

Везде фигурирует таинственное «умственное начало» и загадочная «мыслительная способность». Но что это такое — совершенно неясно. Если мы обратимся к другому словарику, скажем, из учебника «Психология и педагогика» Бордовской и Реана, то увидим нечто такое.

Интеллект (от лат. intellectus — понимание, познание) — обобщенная характеристика познавательных (когнитивных, умственных) способностей; способность к приобретению и эффективному использованию знаний.

При этом психологи выделяют три составляющие интеллекта:

  • Когнитивные базовые функции или предпосылки интеллекта — части физиологического процесса восприятия среды на уровне нервной системы (внимание, память, концентрация, логическое мышление, восприятие предметов и речи и т. п.). Сильно зависит от врожденных данных.
  • Архив опыта или «база знаний», т. н. «связанный» интеллект — чувственный и мыслительный опыт, включающий как познание природы, так и все пережитые осознанные и неосознанные события, социальные взаимодействия и т. п. Приобретается и расширяется со временем.
  • «Свободный» или «подвижный» интеллект — способность решать сложные мыслительные задачи, состоящие из цепочек логических задач. Во многом зависит от врожденных данных, но подвержен тренировке под действием опыта.

Все еще не очень понятно, однако тут мы можем выделить уже более четкие параметры — познание, опыт, логические и мыслительные задачи. 

С одной стороны, у нас есть человек, который определенно обладает интеллектом (хотя есть некоторые индивиды, вызывающие сомнения). Есть тесты на коэффициент интеллекта, который показывает скорость и качество абстрактных логических операций вашего мозга. 

С другой, у нас есть собака, которая, в принципе, в психическом плане может все то же самое, что и человек, но несколько проще или уже. Или обезьяна. Или дельфин. Или ворон. Для всех названных даже есть аналоги тестов на IQ. Но книжки они читать не могут, писать связные тексты тоже, языкам не обучены (хотя внутри отдельных групп что обезьяны, что дельфины, что собаки, что вороны изобретают свои условные сигналы). И вроде бы интеллектом они обладают, хоть и отличающимся от нашего, но разумными мы их почему-то не называем. Хотя одним из свойств разумности обычно называют именно интеллект.

Что мы имеем? Туманность определения и малоизвестный широкой массе механизм и принцип работы. Каждый человек уверен, что у него есть интеллект. Но вряд ли хоть один человек с улицы сможет вам внятно и близко к науке объяснить, как работает то, что всегда с ним и чем он пользуется ежедневно.

Собственно, на этом и спекулируют различные антипрогрессивисты, когда говорят, что какая-нибудь новая технология, будь то 5G, нейросети, промышленные принтеры или ГМО — страшно опасная, вредная, и вообще, ученые нам все врут, скоро всех чипируют и сдадут инопланетянам на опыты.

И вот, чтобы провести дополнительную и более четкую границу, психологи ввели такое понятие, как «Агентность».

Агентность или агентивность (англ. agency) это способность кого бы то ни было совершать осмысленные действия безотносительно естественных потребностей и изменений окружающей среды. Другими словами, если вы почесали укушенную комаром руку — это не агентность. А вот вам наскучило сидеть, и вы решили пройтись или нарисовать смайлик на краю тетрадки — это агентность. 

Другими словами, агентность — это то, что вы делаете усилием воли, по собственному желанию и усмотрению. У животных тоже есть агентность. И чем более развита нервная система, тем больше этой агентности.

У сегодняшних систем искусственного интеллекта, даже у пресловутого ChatGPT, агентности нет, и еще долго не будет. ИИ может только отвечать на запросы пользователя, реагировать на условные сигналы и команды. Но если ему никто не поставил задачу, не написал вопрос в специальном поле, он ничего не будет делать. Он не живой даже с психологической точки зрения, по крайней мере, пока. А значит, любой вред и любая польза от ИИ может быть только на совести людей, его использующих. Однако можно придать ему иллюзию живости.

▎ИИ в играх

 
Итак, чем же характерен ИИ в играх? Мы уже сказали, что сами по себе различные искусственные интеллекты существовали достаточно давно. Однако в игры в своем прямом смысле искусственные интеллекты долго не могли попасть. Из-за этого, кстати, разработчикам первой части игры S.T.A.L.K.E.R. пришлось сильно урезать свои амбиции и функционал игры.

В планах создателей игры было создание полноценного функционального мира, что также известно как «Immersive Sim». Суть его в такой детальной имитации жизни, что животные бегали не по заскриптованным маршрутам, а согласно программным «инстинктам», охотясь друг на друга. Игроку же при этом давали возможность делать все, что ему заблагорассудится.

Банды и сталкеры могли бы устраивать баталии, перекраивая карту зон влияния группировок, а задания, включая необходимые для прохождения основного сюжета, мог взять и выполнить любой NPC. Однако игра получалась слишком тяжеловесной, громоздкой и мало вписывающейся в возможности движка. Постоянно возникали конфликты строчек кода, сбои, вылеты и «softlocks» (мелкие ошибки, мешающие продолжить прохождение). Да и игроки, тестировавшие продукт, были недовольны таким положением вещей. Конечно, в последующих интервью разрабы делали упор именно на «морально неготовых» игроков, но никак не на технические трудности.

Главное преимущество компьютерных игр — интерактивность, то есть способность мира игры реагировать на действия игрока. Именно интерактивность некогда стала определяющим фактором, который помог видеоиграм захватить человеческое внимание в масштабах цивилизации. Прародители всех компьютерных игр — настолки — могли обеспечить интерактивность только при наличии как минимум 2-3 игроков, каждый из которых располагает желанием, знанием правил и, главное, временем.

Можно, конечно, покидать кубик, самому себе придумывая взаимодействие персонажей, но это не особо интересно даже детям. Тем временем самые примитивные игры с приставок начала 80-х годов, во-первых, не требовали участия второго человека, во-вторых, обеспечивали интерактивность. Конечно, в те времена интерактивность заключалась в подсвечивании затемненных частей экрана, открытии запертых дверей и сундуков и побегушками с плохо отрисованными врагами, но она была. И в те времена это был дикий экспириенс, аналогов которому еще не было.


   Doom в представлении нейросети Stable Diffusion (стандартная модель)

Но время шло, компьютеры становились мощнее, а игроки — искушённее. Теперь внезапно вылетающими из темноты пиксельными монстряками уже мало кого можно было напугать (хотя спрос на хорроры и их летсплеи последних лет удивляет). Игры значительно продвинулись в своем развитии: стало возможным добавлять не одного врага, а пяток, персонажи получили способность взаимодействовать с предметами различным образом, потихоньку в жизнь входила трехмерность (мнимая и настоящая). Теперь у нас есть локации со сложным рельефом, разные области которой заняты разными типами врагов. Кроме того, игры перестали разделяться на изолированные комнаты, при переходе через границу которых враги и их преследование сбрасывалось. И тут встал вопрос: как организовывать преследование?

В примитивных подземельях-лабиринтах монстры просто шли на игрока, пока не настигали его или пока игрок не уничтожит их. Теперь стало очевидно, что нужно озаботиться поведением мобов, так как при прежнем подходе они либо слепо двигались вперед-назад по коридорам, либо замирали, упираясь в препятствие.

В доисторической по нынешним меркам Diablo II локации генерируются случайным образом, точно так же расставляются враги (хотя на самом деле не совсем случайным, но это сейчас не важно). И при старом подходе низкоуровневый игрок, случайно забредший к месту обитания сильных монстров, рисковал собрать за собой хвост из толпы кровожадных чудовищ. Однако если игрок отойдёт от места спавна (появления) врагов на определенное расстояние, враг остановится и вернется на место. Эту же тактику применяли многие игроделы.

Есть два способа ограничить движение мобов. Первый — поставить маркеры вокруг зоны «обитания» мобов. Когда условная черта оказывается пересечена, срабатывает программный триггер, и моб возвращается на место. Второй — поставить маркер в месте спавна или использовать маркер точки спавна как нулевую точку. В таком случае используют отдельный маленький кусочек кода, который через равные промежутки времени проверяет удаленность моба от нулевой точки. Если расстояние превышает заданную строчкой кода величину, срабатывает скрипт возвращения на «базу». Несмотря на то, что оба способа имеют свои преимущества (например, первый экономичен и удобен, когда у вас есть линейная локация с ограниченным числом выходов), наиболее распространен сейчас второй способ, так как подразумевает большую пластичность.

Однако одними побегушками от базы до игрока в большой игре не обойтись. Посему в играх появилась такая вещь, как файлы поведения.

▎Три кита ИИ в играх

 
Кит № 1. В мире дикого геймдева: изучаем поведение мобов

Файлы поведения или behaviours —специальные программные файлы, определяющие то, как тот или иной моб (игровое существо) должен реагировать на некоторые условия: наличие игрока в «зоне видимости», наличие препятствий в виде стен, камней, других мобов, а также на определенные триггеры интерактивных объектов (двери, поднимаемые и сдвигаемые камни и шкафы, переключатели), маркеры состояний и эффектов (огонь, вода, вентили пара), и другие. 

По сути своей, это скрипты, т. е. программы сценария выполнения. Каждое событие регистрируется и запускает соответствующую строчку сценария или соответствующий файл поведения. Строчка или файл, в свою очередь, запускает соответствующую анимацию — спрайт, последовательность картинок или файл скелетной анимации для 3D-моделей.


   Приблизительная схема работы примитивного Behaviours-скрипта

С развитием игропрома расширились возможности игрока для взаимодействия с миром. Усложнились и реакции мобов на его действия. Вот посмотрите на количество и размер файлов поведения DOOM 3: RoE, TES V: Skyrim и Elden Ring.

Файлы анимаций DOOM 3: RoE (слева) содержат в себе некоторые настройки поведения, однако большая часть поведения закодирована в «базовой части» кода игры. Файлы поведения behaviour в Skyrim встречаются далеко не у всех мобов, так как их поведение ссылается на специально выделенные скрипты.


   В Elden Ring также beh-файлы ссылаются на некоторые другие скрипты, включая адреса маркеров, о которых мы поговорим позже.

Кроме того, преимущество beh-файлов в том, что их, подобно анимациям, можно применять сразу к нескольким персонажам. Так, человекоподобные враги, как уже упомянутый выше Кайденский наемник, используют beh-файл c9997, предположительно отвечающий за взбирание в седло — по изначальной задумке разработчиков, кататься верхом могли многие мобы. 

Подобные «общие» файлы расположены в папке Action — Scripts, и помимо поведения по поиску верхового животного, они включают анимации и скрипты разговора, вынимания оружия или броска предметов.


              Action-файлы Elden Ring

Но это нечестно! Сравнивать додревний линейный DOOM, открытый мир Skyrim и новый соулслайк. Разные годы, разный подход к миру, разные форматы!
Окей, уравняем шансы. Beh-файлы игр линейки «Soulsborne» From Software: от оригинальной Dark Souls до свежачка Elden Ring, не минуя все три части Dark Souls и особнячком стоящую Sekiro.

Почти все игры по структуре линейные, хоть и с определенной степенью свободы. Кроме Elden Ring, которая технически — открытый мир, однако сюжетная часть все такая же линейная, да и поведение мобов с программной точки зрения не изменилась. И файловый формат сохранился с допотопных времен, изменилось лишь шифрование.


   Архив модели (вверху) и пакет анимаций (внизу) Dark souls

В первом Dark Souls отдельные beh-файлы есть только у некоторых боссов. В остальных случаях достаточно скриптов игры и файлов с именем c(номер персонажа).hkx, привязывающих к модели скелет и пакет контекстных анимаций.

Со второй частью темного соуса несколько сложнее — ее разрабатывали по отличной от предыдущей игры схеме, и где, что и почему находится, разобраться сложно. Но рискну предположить, что отдельные файлы поведения здесь тоже есть не у всех.


   Beh и анимации босса Призрачная Монахиня в Sekiro

В Sekiro: SDT каждый противник имеет файл поведения. А еще в секире рекордное количество анимаций на каждого врага среди соулс-игр — в среднем, боссы и мини-боссы имеют по 150-200 анимаций, а рядовые противники — по 50.

В Dark Souls 3 в целом все так же, как и в предыдущих играх. Разве что из-за усложнения некоторых механик (добавление стоек, например, и спецнавыков оружия) и расширения комбо врагов, ссылочных beh-файлов у некоторых врагов стало больше. В последующей Elden Ring некоторые вещи упростили, некоторые добавили, но не столько за счет увеличения числа файлов, сколько из-за оптимизации строения самого скрипта.

Вы можете наглядно видеть, как изменялся размер этих файлов и их количество для рядовых противников. А ведь соулс-игры не могут похвастаться великим разнообразием реакции мобов, единственная задача которых — стать препятствием на пути к боссу и пасть от руки героя поневоле.

Кит № 2. Номер паспорта, пожалуйста

Первого кита, файлы поведения, мы уже рассмотрели.
Второй кит — ID или идентификационный номер (identific data, иногда individual data). Файлы поведения — это хорошо. Но в «полевых» условиях «голого» Behaviours недостаточно.
 

Некоторые специфические вещи, наподобие ключевых точек, прописаны в файле поведения. Так, например, у каждой модели оружия в TES V: Skyrim есть свой «инвентарный номер» — ID объекта. Персонажи «понимают» ID объекта, приписанного им в файлах игры, и когда игрок пытается украсть этот объект (ID предмета получает статус «в инвентаре»), файл поведения запускает цепочку скриптов с охотой на вора.


Инвентарь персонажа Кузнец Алвор. Pocket common ссылается на список возможных вещей у обычного горожанина. Инвентарь формируется из 3-4 случайных предметов из списка

Примечание

В случаях, когда объекты находятся не в инвентаре, а являются частью внутреннего убранства дома, их принадлежность определяется отдельным присвоением в таблице объекта мира во вкладке Ownership (принадлежность).

ID также определяют, к какой категории относятся объекты. В Skyrim, например, есть отдельные ключевые списки, каждой позиции которых также присвоен ID. Эти позиции «закрепляют» за вещью, определяя его свойства (броня или одежда, на какую часть тела надевается, можно ли продать и т. п.).


       Параметры одежды кузнеца в Skyrim

Благодаря такому категорированию, в Skyrim персонажи могут самовольно подбирать выпадающие на землю предметы с врагов. Здесь ID предметов определяет, что именно с этим предметом будет делать персонаж — съест, чтобы пополнить здоровье, припасет для ГГ или будет использовать в битве как оружие, если у него хватит умений. У меня, например, Лидия обожала подбирать магические посохи и орудовать ими, забывая про щит, а иногда и про меч.


       Каст по-македонски, с двух рук

В Elden Ring и других играх From Software некоторые предметы имеют по два идентификационных номера. Сделано это для того, чтобы не только ГГ и человекоподобные враги могли использовать некоторые виды оружия, не привязанные к модельке, но и для того, чтобы работали квестовые цепочки. Кроме того, оружию присваивается новый ID при закалке (т.е. при «зачаровании» и перековке, наделяющей оружие новыми свойствами и эффектами).

Обратите внимание, что номер предмета в файлах игры не является ID предмета, это всего лишь его «название», подчас не имеющее отношения даже к дате добавления.

Кит № 3. Маркеры

Но все же нельзя прописать в beh-файлы совершенно весь контент игры. С играми начала 2000х это еще можно было провернуть, но такие мастодонты, как Elden Ring содержат в себе тонны и килотонны контента, который надо учесть. А уж такие крупные чистокровные РПГ-проекты, как Pathfinder или Baldur’s Gate с их монструозной вариативностью и объемами делают задачу описания ситуаций попросту невозможной.

Дело в том, что местность в играх редко бывает идеально ровной — чаще всего она пересечена различными элементами ландшафта, природными объектами, например, деревьями, зданиями, руинами и т. п. чтобы мобы «понимали», что уперлись в препятствие, это препятствие нужно обозначить. И это не говоря уже о том, что на пути попадаются ловушки, переключатели, указатели, лут и прочее, прочее, прочее.

Тут на помощь геймдизайнеру приходят маркеры.
В одной из предыдущих статей мы уже краешком упоминали маркеры, а теперь расскажем про них подробнее. Обращаем ваше внимание, что маркеры костей для анимации мы здесь рассматривать не будем.

В данном случае, маркер — это специальная метка, при приближении к которой ИИ запускает нужную строчку beh-файла, а та запускает соответствующую анимацию или цепочку скриптов. Юзер в процессе игры не видит их напрямую, однако именно они отвечают за многие возможности взаимодействия с миром игры. И не только игрок, но и внутриигровые мобы.

Маркеры и триггеры
В сообществе геймдизайнеров и мододелов нет единого мнения насчет того, считать ли триггеры маркерами, или это не тождественные понятия. Обычно принимают следующее разделение.

Маркер — то, на что реагирует ИИ моба, от пригодного к поднятию объекта до индикатора лестницы. Пример — маркер стула, на который можно сесть.

Триггер — то, что при взаимодействии запускает действие на стороннем объекте (не герое и не мобе). При этом триггерами могут пользоваться как ГГ, так и мобы. Пример — рычаг открытия дверей.

Как при этом называть маркеры местности в Dragon’s Dogma, запускающие специальные строчки диалога спутников-пешек, каждая из которых может знать или не знать что-то полезное об отдельных аспектах локации, врагов или погоды? Я не буду разделять эти понятия.

Маркеры есть практически во всех видеоиграх. Зачем они нужны? Как мы уже выяснили, для правильной реакции мобов на окружающий мир. Если не будет маркера, то моб застрянет на первом же камне. Или в первом же дверном проеме. 
Да, знаменитые застревания в дверях спутников в Skyrim корнями уходят в косяки расположения маркеров и их восприятия файлами поведения.

Маркеров в играх очень много. Их разнообразие и назначение неконвенционально и зависит только от нужд геймдизайнеров и их личной шизофрении (как вспомню, сколько анимаций для спотыкающегося Портера сделали разработчики Death Stranding, так кажется каждый камушек учитывали. Жаль, с музыкой так же не заморочились). Но всё же основные типы можно осветить.

Маркеры спавна. Места, в которых появляются убитые мобы либо мобы, появляющиеся только днем или ночью. В некоторых играх, например в Dragon’s Dogma, спутники предупреждают о близком расположении точки спавна, произнося что-нибудь наподобие «Ночью здесь бывает опасно». Маркеры возобновляемых ресурсов или лута относятся к этой же категории.

Маркеры ландшафта. Эти маркеры нужны для того, чтобы мобы (да и вспомогательные файлы игрока) знали, что впереди камень, и его можно обойти или перепрыгнуть. В нормальных завершенных играх у всех объектов мира, включая скалы, дороги, реки и деревья, есть коллизия (физика столкновения), которая не позволяет игроку и персонажам проваливаться сквозь них. И во многих случаях для их преодоления есть специальные анимации — их тоже запускает beh-скрипт, опираясь на соответствующий маркер. И да, для некоторых больших лестниц тоже используют маркеры подъема. Но с этим заморачиваются не все — в большинстве своем, коллизии с уклоном достаточно. 


Иногда маркеры ландшафта и маркеры траектории — это о-о-очень близкие или тождественные понятия. Например, в играх, заточенных под паркур.

Маркеры траектории и маркеры ареала. Маркеры ареала — это область, внутри которой моб обитает и передвигается. Для персонажей это обычно дом родной, для животных — область, по которой они будут бегать в поисках вас или от вас. Маркеры траектории — нечто похожее, но более простое, реализованное за счет контрольных точек и более-менее прямых отрезков между ними. Иногда в траектории есть петли и перемычки, обеспечивающие вариативность. Какой из типов встречается чаще, сказать сложно. В зависимости от движка и целей, их можно комбинировать. Плюсы первого типа — гибкость и вариативность движений. Плюсы второго — четкость и контролируемость, а также экономия ресурсов вычисления.


       Создание ареала на движке Unreal Engine

Маркеры взаимодействия. Самая широкая категория. Может включать как метку стула, на который можно сесть, так и кнопку открывания двери. Триггеры запуска ловушек и кнопки поворота обелисков-загадок, в принципе, можно отнести сюда же.


Балкон в Вайтране. Синие человечки — маркеры сидения, желтые драконы — места возможного приземления дракона

Функциональные маркеры. Это вещи, не только невидимые игроком, но и в принципе практически лишенные взаимодействия и нужные лишь для работы отдельных элементов игры. Это маркеры света, маркеры фракции, маркеры эффектов погоды или природы, музыки и т. п.


Если включить видимость сразу ВСЕХ маркеров на локации, можно схватить приступ. Просмотр локации «Руины Нового Лондо» из Dark Souls в программе DSmodT v0.4

▎Настоящий ИИ

 
Но все же beh-файлы — всего лишь имитация искусственного интеллекта. Да, за счет расширения набора функций и скриптов можно реализовать очень сложные поведенческие реакции и взаимодействия, но это все еще не искусственный интеллект в прямом смысле слова.

До недавнего времени игр с настоящим ИИ не было. Да и сейчас, несмотря на всю продвинутость технологий, именно в игровом процессе настоящий искусственный интеллект, близкий к пресловутым чат-ботам, практически нигде не используется. Исключение составляют одиночные нишевые проекты, наподобие Доки-Доки , но это совсем не тот ИИ, которого мы ожидаем при этих словах.

Был опыт с парой игр, даже от крупных студий (например, Ubisoft), но он оказался провальным. В основном некоторые нейронки попроще используются в играх с открытым миром, выживалках с иммерсив сим и не очень удачных проектах наподобие No Man’s Sky, которой, помимо процедурной генерации мира, похвастаться больше нечем.

Есть несколько проектов в разработке, и некоторые энтузиасты собственноручно пилят аддоны к уже существующим играм. Самый знаменитый — мод, добавляющий ChatGPT в Skyrim. Умельцы умудрились пришить не только сам чат-бот, но и мини-AI-шку, озвучивающую то, что нагенерировал чат голосами персонажей Скайрима. Теперь они могут более-менее адекватно реагировать на происходящее вокруг, обмундирование и поведение ГГ и на те самые маркеры на локации, а также общаться с игроком на незаскриптованные темы. Краткий ролик с данной новостью на русском оставлю тут.

Однако возможно, я чего-то не знаю, и какой-нибудь крупный проект с почти настоящим ИИ прошел мимо меня. Дайте знать, и я обязательно ознакомлюсь и напишу поподробнее и о других применениях ИИ в играх.

С вами была Людмила Хигерович, всего хорошего и не болейте!

НЛО прилетело и оставило здесь промокод для читателей нашего блога:
15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS

 

Источник

Читайте также