company_banner

Практика использования модели акторов в бэкэнд-платформе игры Quake Champions

    Продолжаю выкладывать доклады с Pixonic DevGAMM Talks — нашего сентябрьского митапа для разработчиков высоконагруженных систем. Много делились опытом и кейсами, и сегодня публикую расшифровку выступления backend-разработчика из Saber Interactive Романа Рогозина. Он рассказывал про практику применения акторной модели на примере управления игроками и их состояниями (другие доклады можно посмотреть в в конце статьи, список дополняется).


    Наша команда работает над backend'ом для игры Quake Champions, и я расскажу о том, что такое акторная модель, и как она используется в проекте.

    Немного о стэке технологий. Мы пишем код на С#, соответственно, все технологии завязаны на нем. Хочу заметить, что будут какие-то специфичные вещи, которые я буду показывать на примере этого языка, но общие принципы останутся неизменными.



    На данный момент мы хостим наши сервисы в Azure. Там есть несколько очень интересных примитивов, от которых мы не хотим отказываться, такие как Table Storage и Cosmos DB (но стараемся сильно на них не завязываться ради кроссплатформенности проекта).

    Сейчас я бы хотел рассказать немного о том, что такое модель актора. И начну с того, что она, как принцип, появилась более 40 лет назад.



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

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

    Немного резюмируя сказанное, представим, что у нас есть клауд, где есть какой-то кластер серверов, и на этом кластере крутятся наши акторы.



    Акторы друг от друга изолированы, общаются посредством асинхронных вызовов, и внутри самих себя акторы потокобезопасны.

    Как это примерно может выглядеть. Допустим, у нас есть несколько пользователей (не очень большая нагрузка), и в какой-то момент мы понимаем, что идет наплыв игроков, и нам нужно срочно сделать upscale.



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

    Таким образом, актор, во-первых, выполняет роль кэша, а во-вторых, это а-ля «умный кэш», который умеет обрабатывать какие-то сообщения, выполнять бизнес-логику. Опять же, если необходимо сделать downscale (к примеру, игроки вышли) — тоже нет никакой проблемы вывести эти акторы из системы.

    Мы в backend’e используем не классическую акторную модель, а на основе Orleans-фреймворк. В чем отличие — я сейчас постараюсь рассказать.



    Во-первых, Orleans вводит понятие virtual-актор или, как он еще называется, грейн (grain). В отличие от классической акторной модели, где какой-либо сервис отвечает за то, чтобы создать этот актор и поместить его на какой-то из серверов, Orleans берёт эту работу на себя. Т.е. если некий user service запрашивает некий грейн, то Orleans поймет, какой из серверов сейчас менее загружен, сам разместит там актора и вернет результат в user service.

    Пример. Для грейна важно знать только тип актора, допустим user states, и ID. Допустим, ID пользователя 777, мы получаем грейн данного юзера и не задумываемся о том, как этот грейн хранить, мы не управляем жизненным циклом грейна. Orleans же внутри себя хранит пути всех акторов очень хитрым образом. Если актора нет, он их создает, если актор живой, он его возвращает, и для юзер-сервисов все выглядит так, что все акторы всегда живые.



    Какие преимущества это нам дает? Во-первых, прозрачную балансировку нагрузки за счет того, что программист не нуждается в том, чтобы самому управлять расположением актора. Он просто говорит Orleans, который развёрнут на нескольких серверах: дай мне из своих серверов такого-то актора.



    При желании можно сделать downscale, если нагрузка на процессор и память небольшая. Опять же, можно сделать и в обратную сторону upscale. Но сервис ничего об этом не знает, он просит грейн, и Orleans дает ему этот грейн. Таким образом, Orleans берет на себя инфраструктурную заботу за жизненный цикл грейнов.

    Во-вторых, Orleans обрабатывает падение серверов.



    Это значит, что если в классической модели программист ответственен за то, чтобы обрабатывать такой случай самостоятельно (разместили актор на каком-то сервере, а этот сервер упал, и мы сами должны поднять этого актора на одном из живых серверов), что добавляет больше механической или сложно-сетевой работы для программиста, то в Orleans это выглядит прозрачно. Мы запрашиваем грейн, Orleans видит, что он недоступен, поднимает его (размещает на каком-то из живых серверов) и возвращает сервису.

    Чтобы стало чуть более понятно, разберем небольшой пример, как пользователь читает некоторый свой state.



    Стейтом может быть его экономическое состояние, которое хранит доспехи, оружие, валюту или чемпионов этого пользователя. Для того, чтобы получить эти стейты, он обращается в PublicUserService, который и обращается к Orleans за стейтом. Что происходит: Orleans видит, что такого актора (т.е. грейна) еще нет, он его создает на свободном сервере, и грейн читает свое состояние из некоторого Persistence-хранилища.

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

    Если у нас несколько клиентов (игровой клиент, гейм-сервер), они могут запросить стейты пользователя, и кто-то из них поднимет этот грейн. Точнее — заставит Orleans поднять его, а затем все вызовы, как мы уже знаем, происходят в нем потокобезопасно, последовательно. Сначала стейт получит клиент, а затем и гейм-сервер.



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



    Зачем здесь нужно сохранение в Persistence? Это отдельная тема и она заключается в том, что иногда нам не особо важно, чтобы грейн постоянно сохранял в Persistence свои стейты. Если это состояние игрока онлайн, мы готовы рискнуть потерять его в угоду производительности, если же это касается экономики, то мы должны быть уверены в том, что его стейтс сохранен.

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

    Небольшой пример того, как это выглядит.



    Как я уже говорил, грейн состоит из типа и какого-то ключа (в данном случае тип это IPlayerState, ключ — IGrainWithGuidKey, что означает, что это Guid). И у нас есть интерфейс, который мы реализуем, т.е. GetStates возвращает какой-то список стейтов и ApplyState, который какой-то стейт применяет. Методы Orleans возвращают Task. Что это значит: Task — это promise, который говорит нам о том, что, когда состояние вернется, promise будет в состоянии resolved. Также у нас есть какой-то PlayerState, который мы получаем с помощью GrainFactory. Т.е. здесь мы получаем ссылку, и ничего не знаем о физическом расположении этого грейна. При вызове GetStates Orleans поднимет наш грейн, прочитает state из Persistence-хранилища себе в память, а при ApplyState применит новый стейт, а также обновит этот стейт и у себя в памяти, и в Persistence.

    Хотелось бы еще разобрать чуть более сложный пример на High level архитектуре нашего UserStates сервиса.



    У нас есть какой-то игровой клиент, который получает свои стейты через OfferSevice. У нас есть GameConfigurationService, ответственный за экономическую модель какой-то группы юзеров, в данном случае и нашего пользователя. И у нас есть оператор, который меняет эту экономическую модель. В соответствии с ней пользователь запрашивает OfferSevice для получения своих стейтов. А OfferSevice уже обращается к UserOrleans сервису, который состоит из этих грейнов, поднимает это состояние пользователя у себя в памяти, возможно, выполняет какую-то бизнес-логику, и возвращает данные обратно пользователю через OfferService.

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

    Здесь я бы хотел разобрать некоторые подводные камни этой модели.



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

    Вторая проблема не так очевидна — это так называемая цепная реакция. Когда пользователь поднимает какой-то грейн, а тот в свою очередь может неявно поднять другие грейны в системе. Как это происходит: пользователь получает свои состояния, а у пользователя есть друзья и он получает состояния своих друзей. Таким образом, вся система держит все свои грейны в памяти и если у нас 1000 пользователей, и у каждого 100 друзей, то 100 000 грейнов могут быть активны просто так. Такого случая тоже нужно избегать — как-то хранить стейты друзей в какой-то общей памяти.



    Ну и какие технологии существуют для реализации модели акторов. Самая, пожалуй, известная — это Akka, которая пришла к нам с Java. Есть ее форк, называется Akka.NET для .NET. Есть Orleans, который open-source и есть в других языках, как реализация. Есть Azure-примитивы, такие как Service Fabric Actor — технологий очень много.

    Вопросы из зала


    — Как вы решаете классические проблемы, как CICD, обновление этих акторов, используете ли Докер и нужен ли он вообще?

    — Докер пока ещё не используем. Вообще, разверткой занимается DevOps, они разворачивают наши сервисы в клауд-сервисе Azure.

    — Непрерывное обновление, без даунтаймов, каким образом происходит? Orleans же сам решает, на какой сервер пойдет грейн, на какой сервер пойдет запрос и каким образом этот сервис обновлять. Т.е. появилась новая бизнес-логика, появилось обновление того же актора — как накатываются эти обновления?

    — Если речь идет о том, чтобы обновить весь сервис целиком, и если мы обновили какую-то бизнес-логику актора, мы можем выкатить под него новый Orleans сервис. Обычно у нас это решается через наши примитивы под названием топология. Мы выкатили какой-то новый сервис Orleans, который еще пока что, предположим, пустой, и без актора, выводим старый сервис и подменяем его новым. Акторов не будет в системе вообще, но при следующем запросе пользователя эти актора уже будут созданы. Будет, возможно, какой-то спайк в начале. В таких случаях обычно обновление проходит утром, так как утром у нас наименьшее число игроков.

    — Как Orleans понимает, что сервак упал? Вот ты рассказывал, что он быстренько акторов перекидывает на другой сервер...

    — У него есть пингатор, который периодически понимает, какие из серваков живые.

    — Он пингует конкретно актор или сервер?

    — Конкретно сервер.

    — Такой вопрос: внутри актора произошла ошибка, ты говоришь он идет шаг за шагом, каждая инструкция. А вот произошла ошибка и что происходит с актором? Допустим такая ошибка, которая не обработана. Актора просто умирает?

    — Нет, Orleans кидает exception в стандартной схеме .NET.

    — Смотри, мы не обработали exception, актор видимо сдох. У игрока я не знаю, как это будет выглядеть, но что потом происходит? Вы пытаетесь как-то этот актор перезапустить или еще что-то сделать в этом духе?

    — Смотря какой кейс, зависит от того, какое поведение. Например retriable или не retriable.

    — Т.е. это все конфигурируется?

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

    — У вас несколько Persistence’ов — это типа базы данных?

    — Persistence, да, база данных с постоянным хранилищем.

    — Допустим, легла база данных, в которой (условно) игровые деньги. Что происходит, если актор не может до нее достучаться? Это вы как обрабатываете?

    — Во-первых, это Storage. На данный момент у нас используется Azure Table Storage и такие проблемы на самом деле случаются — Storage падает. Обычно в этом случае приходится его переконфигурировать.

    — Если актор не смог получить что-то в Storage, у игрока это как выглядит? У него просто этих денег нет или у него игра сразу закрывается?

    — Это критичные изменения для пользователя. Поскольку каждый сервис имеет свою severity, в данном случае, то юзер сервис это состояние terminal, и клиент просто вылетает.

    — Мне показалось, что сообщения акторов происходят через асинхронные очереди. Насколько это оптимизированное решение? Разве это не раздувается, разве это не заставляет игрока подзависнуть? Не лучше ли использовать реактивный подход?

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

    — А как это на игроке отразится?

    — Поскольку юзер-сервис обращается к актору, ему бросят исключение таймаут exception и, если это «критичный» сервис, то клиент выкинет ошибку и закроется. А если менее критично — то подождет.

    — Т.е. у вас есть угроза DDoS? Большое количество мелкий действий может положить игрока? Допустим, кто-то быстро начнет приглашать друзей и т.д.

    — Нет, там стоит request-лимитер, который не позволит слишком часто обращаться к сервисам.

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

    — Хороший вопрос. Во-первых, Orleans 2.0 поддерживает Distributed Actor Transaction — это первый выход. Более точно нужно уже про экономику рассказывать. А как самый простой способ — в последнем Orleans транзакции между акторами без проблем реализуются.

    — Т.е. оно уже умеет гарантировать, что в персистентность данные уйдут целостно?

    — Да.

    Еще доклады с Pixonic DevGAMM Talks


    Pixonic

    285,00

    Разрабатываем и издаем игры с 2009 года

    Поделиться публикацией
    Комментарии 11
      +2
      Интересно конечно.
      Но доклад выглядит как-то странно.
      На все вопросы ответ «Мы просто взяли Orleans и он все делает сам».
        0
        Немного не понятно всё это.

        Если хочется распределенную in-memory базу данных с персистентным бэкендом, можно взять что-то типа Apache Ignite.

        Если нужны распределенные вычисления с использованием модели Акторов, есть Akka.

        Зачем эти два продукта смешивать — я так, к сожалению, и не понял.
          0

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

          0
          Прекрасный пример победы архитектуры над разумом. Говорю исключительно с субъективной точки зрения игрока, гонявшего ещё в Quake 1. Так вот, ни в нём, ни в Quake 3 в 2000-м году не было таких рассинхронизаций, как в QC. Сетевой стек просто отвратителен (я уже не говорю о проблемах на клиенте), а игра в открытом доступе уже почти два года.

          Чтобы не быть голословным, вот пример с прошедшего неделю назад чемпионата. Для тех, кто ничего не понял, поясню: игроки сидели друг напротив друга (т.е. «пинг» как бы не при чём) и игрок rapha прятался за колонной от игрока coolerz, и был убеждён, что он спрятался за колонной и не находится на линии прямой видимости для автоматного огня, однако урон по нему проходил, в результате чего rapha отдал очко и отреагировал вот так (вид того же момента из глаз cooler).

          Как разработчик, я с большим удовольствием читаю о современных технологиях в геймдеве. Как игрок, я совершенно не понимаю, почему игра в 2018 объективно и субъективно намного хуже по качеству игры из той же серии от 2000-го года. Лучше стали две вещи: графика и маркетинг. [sarcasm]Так что я с удовольствием бы послушал доклад о том, как поддерживать репутацию «esports ready» бэкенда при наличии ракет, наносящих 0 урона при прямом попадании[/sarcasm]
            0
            Есть нюанс — игра делается не совсем на классических движках id Tech и может статься вся сетевая часть тоже от движка сейберов. У дума и прочих игр на 5 и 6 айдитехе вроде бы проблем с сетью нет?
              0
              Да, там лютый кадавр id Tech 5 + Saber 3D (proof). В итоге, в угоду красивой на бумаге архитектуре бэкенда, имеем игру, которая ощущается и играется хуже своей предшественницы из 2000-го года. Зато стек технологий современный.
              +1
              Но погодите, ведь речь в докладе идёт именно о backend инфраструктуре (в значении сервисов — matchmaking, «валюта» игрока, рейтинг игрока, что угодно ещё), а не самой игры (если я ошибаюсь — поправьте меня, времени сейчас посмотреть доклад нет, но прочитал статью довольно внимательно). Да и сложно представить, чтобы на акторах кто-то разрабатывал шутер — это не имеет никакого смысла (load balancing в рамках игрового сервера не нужен, отказоустойчивость тоже; это игровой сессионный сервер и все к нему коннектятся и спокойно играют). Традиционные технологии прекрасно справляются — ещё со времён сетевой модели Quake 3 описанной Кармаком ничего нового не изобрели (всё те же механизмы client lag prediction, lag reconciliation, server state rewind, etc). К тому же, при разработке реальных игровых серверов для такого рода игр (fast-paced FPS/TPS), очень остро стоит вопрос о производительности (==цене) серверов. А оверхэд с акторами будет и ещё какой — нарушается cache locality, невозможно производить многие оптимизации, добавляются всевозможные абстракции…

              А вот почему всё не хорошо ощущается в самой игре — хороший вопрос. Неужели действительно не используют сетевую часть idTech, или используют, но породили какие-то баги в ней?

              Кстати, с удовольствием иногда смотрю этот канал www.youtube.com/user/xFPxAUTh0r1ty/videos автор только и занимается, что анализирует network performance ПК-игр, в том числе есть пара видео по Quake Champions.
                0
                Из того, что я видел в анализаторе трафика, игровой клиент коннектится в Азуровские облака. Сетевая модель отличается кардинально от «кармаковской» и причина была озвучена: «Никаких dedicated серверов в QC из соображений безопасности».

                Забота о безопасности вылилась в наличие читеров уже на стадии закрытого бетатеста (можно найти пруфы в ютубе — народ не стесняясь играет с aimbot'ами). А субъективно архитектура ощущается очень микросервисно, т.к. упасть может что-то одно, оставив всех игроков в игре, пока они не отвалятся по таймауту или другим исключениям.
                  +1
                  Я не разработчик QC, но можно сказать эксперт в этой области, и то что выпишите у меня не вяжется в голове. Исходя из того что вы написали, я вижу это так:

                  1. «Никаких dedicated серверов» означает не то, что теперь всё крутится в мифическом облаке на акторах, а то, что dedicated сервера не раздаются всем кому попало в виде exe-файла (что само по себе действительно представляет серьёзную уязвимость — их можно реверс инженерить). Т.е. это совершенно другая модель дистрибьюции игры (когда разработчики сами деплоят сервера, продают доступ к игре по подписке или free-to-play), но это не значит, что как-то изменился сам dedicated сервер игры — это тот же executable файл, который деплоится на виртуальную машину (в Azure) и запускается, и к нему игроков пропускает matchmaking сервис.
                  Сейчас таким образом организовано очень много игр (тот же Fortnite например или Overwatch) — matchmaking собирает игроков и ищет для них рабочий сервер, если такового нет деплоится инстанс на уже доступном железе или арендуется виртуальная машина.
                  К слову, «Никаких dedicated серверов» всё-таки скорее бизнес-решение (иначе нужно менять модель дистрибьюции игры на классическую — когда игроки покупают клиент и могут сами деплоить сервер где хотят, но сейчас мало кто так делает — увы, рынок изменился). Может быть и разрешат запускать dedicated сервера — но за денежку и в рамках их облачной инфраструктуры.

                  2. Ещё раз повторюсь, что нет смысла делать игровые сервера на акторах для такой игры. Это подход построения именно backend архитектур, когда производительностью можно (и нужно) пожертвовать в обмен на fail tolerance, load balancing и т.д… Т.е. ожидать, что dedicated сервер игры написан на Orleans это примерно как ожидать, что в игре для всего используется SQL и хранимые процедуры, или какая-нибудь причудливая технология из сферы энтерпрайз. Akka/Orleans традиционно используются в энтерпрайз бэкенде где нужны вышеозвученные фишки. А в рамках fast-paced игры это просто… как бы сказать, «так игры не пишутся». Там и GC pressure будет космический, т.к. всё это на .NET крутится, что в принципе неприемлимо.
                  А главное, это не имеет смысла, ведь уже есть idTech (огромная выполненная работа), и клиент игры должен прекрасно дружить с сервером за счёт общего стека idTech (общего кода перемещения персонажа и основных игровых систем которые должны симулироваться на клиенте для lag prediction). Всё это реально выбросить и переписывать на чём угодно заново — очень-очень плохое бизнес-решение.
                  А вот использовать для бэкенда Akka/Orleans + Azure это прекрасное бизнес-решение (как и понятно из этой презентации «мы взяли Orleans и он всё делает за нас»).

                  3. Aimbot'ы это чисто клиентская вещь и никоим образом не касается серверной архитектуры. Да, это можно пытаться детектить на сервере разными алгоритмами, но всё равно в индустрии это чаще решается средствами по типу V.A.C. и его аналогами (мониторинг на стороне клиента).

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

                  Опять же, я не разработчик этой игры, но это просто common sense для любого эксперта в индустрии. В эту же пользу говорит и видео-обзор от ютубера о канале которого я писал выше youtu.be/WITA9DpejSg?t=902 игра подключается к конкретному dedicated серверу и обменивается данными именно с ним напрямую в течение всей игровой сессии по протоколу UDP (анализ по Wireshark). Никаких проблем (кроме производительности) автор не заметил, все алгоритмы предикшена и компенсации лага присутствуют как и в остальных шутерах (что не исключает наличия багов о которых вы писали выше). А значит я возвращаюсь к тому же выводу — используется idTech или что-то явно аналогичное, а вот почему всё не хорошо ощущается в самой игре — хороший вопрос. Скорее всего — виноваты баги. Код современных игр очень наворочен в сравнении с тем же оригинальным Q3A, и иногда программисты могут допустить незначительную ошибку в коде приводящую к серьёзным проблемам.
                    0
                    Сам движок — кадавр из idTech 5 + Saber 3D [link]. Причём последнего в итоговом результате гораздо больше. idTech очень узнаваемый, если его открывать PE Explorer'ом — я ковырялся в исполняемых файлах и библиотеках, пока их не обфусцировали просто в надежде найти привычные аргументы командной строки, чтобы хоть немного оптимизировать этого монстра. А про «dedicated сервера» я имел в виду именно невозможность хостить их игрокам самостоятельно, как это делается в Quake Live, серверный код вынесен в ту же степь, куда и QuakeC с возможностью писать свои моды.

                    1. Немного изучил вопрос об акторах, спасибо за эту наводку. Теперь понятно, что бэкенд из доклада не является серверной частью самой игры. Но тем удивительнее, зачем нужен такой предмет для гордости. Микротранзакций в игре немного, сохраняемых данных тоже, аналитика по ним если и ведётся, то на игроков это никак не распространяется — как ММ был отвратителен, так и остался. В том же Quake Live, пока его не прибрала к рукам Bethesda, генерируемые игроками данные использовались и были в открытую доступны по API.

                    2. В сервере Aegis (для игры Ragnarok Online) использовались хранимые процедуры :) а сам сервер крайне упорото взаимодействовал с MS SQL. Один из его билдов спёрли и сообщество написало эмулятор eAthena, который в глобальной ретроспективе работал и кастомизировался лучше и быстрее официальных серверов (привет, Gravity).

                    3. Да, aimbot работает на клиенте. И тем смешнее выглядят официальные заверения, что peer-to-peer, lan и оффлайн вариантов поиграть не будет «из соображений безопасности, чтобы не было aimbot'ов» (пруф на форуме беседки, но ссылку, увы, не сохранил). Боты есть на любой вкус — отрисовка, обрисовка, стрельба, целеуказание на карте. При примерно 1000 живых игроков довольно сложно найти «чистый» паблик.

                    Всё, я завязываю возмущаться о качестве игры на хабре, хорошо, хоть статья уже старая и не нахватаю лещей за оффтопик. Ужасно удручает факт, что игра ±2017 года ощущается намного хуже, чем её предшественница родом из 2000-го. Но, увы, это глобальная тенденция — новый Fallout от той же Bethesda и череда откровенной лажи и вымогательства от Activision и Ко (blizzard, bungie) тому наглядное подтверждение.
              0
              Шёл N-й год разработки, любители крутых бэкендов из Saber так и не смогли научиться сохранять клиентские настройки игры так, чтобы каждый выход/вход не обнулял их.

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

              Самое читаемое