Comments 52
Во всяком случае для дебага было бы логично сериализовать весь всё состояние игры каждый ход в json или посчитать от него хеш и сравнивать между всеми игроками, странно что опции это включить нет
Было много идей, как отслеживать рассинхроны, как пофиксить их все сразу, но все идеи требовали огромное количество времени на реализацию. Изкоробки там есть примитивный механизм отслеживания, но он покрывает только определенный набор переменных, а их там огромное и огромное количество, поэтому работал он так себе.
Какой-то вариант наподобие того, что Вы предложили, тоже думали, в принципе с небольшими корректировками его можно было бы реализовать, но, опять же, вопрос времени. Просто так все переменные не имеет смысла считать, там же много и тех, которые актуальны только для текущего игрока. Как-то если разделить все важное от неважного, разработать какой-то механизм работы с этим ... Ну, короче, все можно, но заниматься некому )
В Циве же есть сейвы, наверняка даже бинарные. Вот это и есть необходимая сериализация. Почистить их только от переменных игроков, и можно сравнивать.
Я про цивилизацию помню только, что когда мы сохраняли сессию (с друзьями), а потом загружали её в другой день - за нас 1 ход ходил почему то ИИ и сбрасывал нам постройки (особенно обидно за чудеса света). И я помню аналогичные жалобы в стиме от игроков.
Интересная статья, спасибо =)
Когда я ридал это вондерфул текст, я прямо трейне, ибо такой эинфаче решение это просто гранде
Нет тестов.
А почему бы...не принят что текущий код в какой то минимальной версии - работает и не попробовать эти чертовы тесты завайбкодить. а потом выкинуть уже явно кривые тесты. Чтобы хотя бы не сделать еще хуже.
Что я не понимаю?
Некому. Моды пишут энтузиасты, на голом энтузиазме.
Ну, кстати, насчёт завайбкодить идея хорошая. Происходило бы это все сегодня, думаю, попробовали бы.
Если есть свободное время и желание, можно всегда заслать PR, требования к коду там достаточно лояльные, все будут только рады )
Тесты (юнит, игровые, "сценарии" для мультиплеера) были бы отличным подспорьем, если бы команда видела стабильное будущее на 3-4 года вперед.
Тот факт, что в Linux версии исполняемого файла видны имена функций - не удивителен. По умолчанию компиляторы под эту ОС оставляют эти данные в исполняемом файле. Зачем это нужно, я так и не понял, лично я всегда выставляю опции компиляции, выкидывающие эту информацию. В проприетарных проектах такое вообще должно быть обязательным.
Как заядлому игроку во все версии Civilization, регулярно ловящему рассинхроны в сетевой игре, было очень интересно прочитать, спасибо!
Интересно, в VI и VII версиях логика работы сетевой игры такая же? Аналогичные проблемы подсказывают что да, но вдруг...
И интересно конечно почему Firaxis была выбрана именно такая баго-ориентированная архитектура
Насчет последующих частей особо не вкурсе; когда интересовался, где-то читал, что для VI части нет DLLника открытого, как для V, подозреваю, его до сих пор тоже нет и, наверное, никогда и не будет. Так что с моддингом там по идее похуже и в целом кишочки уже и не поковыряешь, как в V, чтобы понять в деталях, как оно все работает.
А насчет подхода - ну, можно с уверенностью сказать, что:
- такой подход в теории может быть менее требователен к интернет-ресурсам
- и запилить его было явно намного проще и быстрее, чем какой-либо другой
Лично я подозреваю, что второй пункт оказался решающим, потому что по сути в такой схеме логика для МП практически не отличается от логики СП. Вот прям если DLL посмотреть, то логика МП от СП отличается разве что редкими ифчиками по типу `if isMultiplayer`. В другой схеме переписывать с виду там прям очень много пришлось бы.
В нормальных играх, в которых мультиплеер планировался изначально, разработку с самого начала ведут на клиент-серверной архитектуре. Сингл в таком случае выглядит как мультиплеер на локальном сервере с одним клиентом.
Да, это изначально заметно сложнее реализуется, но все окупается при реализации мультиплеера. Который прикручивать в изначально не подготовленный для этого код это просто леденящий душу пиздец.
В Циве почему-то на этом сэкономили. Хотя казалось бы подход широко известный. Заодно еще сэкономили на синхронизации нормальной, хотя все подводные камни и методы синхронизации и лагокомпенсации известны еще с бородатых лет в шутерах и прочих экшенах. Видимо, разрабы решили что если игра походовая, риалтайма нет, задержки сетевые не важны, то можно не напрягаться.
Согласен, но жалко что так. Когда понимаешь что у real-time шутеров нет проблем с синхронизацией многопользовательских игр под сотню игроков, а у пошаговой стратегии на 2-3 человека рассинхроны и вылеты.
Но это была проблема всех игр серии. Причем с одной стороны у мультиплеера проблема с синхронизацией, а у синглплеера - с глупыми ИИ. Make your choise так сказать)
Главные фишки подхода - автоматическая устойчивость к читам, "бесплатные" реплеи и малый объем передаваемой информации между игроками. Запилить его совсем не проще, чем сохранять всю игру и перекидывать каждый ход между игроками.
А логика, вероятно, не отличается потому, что просто DRY. Как ни крути, но после получения инпута (локально или по сети) и состояния (если бы перекидывали сейвы), симуляция одинаковая.
К читам устойчивости нет совсем никакой, потому что ничего не запрещает:
поставить мод, который открывает всю карту, показывает все ресурсы и тд и тп
как хочешь слать какие хочешь бродкасты, их валидность практически никак не проверяется (ну, кроме базового "существует ли такой юнит", но не "может ли этот юнит ходить так далеко")
Вообще, вот сейчас, после коммента челика выше, мне кажется, что даже не стоит тут искать намеренно какие-то плюсы и причины, почему так было сделано, потому что код там действительно выглядит так, будто они на имеющуюся синглплеерную базу натягивали мультиплеер ))
Собственно только эти читы (посмотреть что делает соперник) и возможны при параллельной симуляции. Нельзя добавить себе ни 20 танков, ни неубиваемого юнита, ни бесконечные ходы юниту, ни отстроеный город, ни исследования, ни плодородных земель, ничего такого.
Почему, можно если протокол плохо продуман (клиент отсылает результаты действий, а не запросы на действия), а валидации нет.
То есть вопрос в том, как работает протокол. Вот игрок кликает в игре на кнопку "купить танк". Что отошлется другим игрокам? "Игрок нажал на кнопку купить танк"? Или "Игрок купил танк в клетке [15,20]"? Во втором случае читерам вообще раздолье.
Класс, всегда было интересно почему постоянные рассинхроны в игре происходят. Играю с самого релиза ваниллу мультиплеер, каждую игру несколько перезагрузок стабильно, надеялся, что в может 7 циве это починят..
P.S. как в мультиплеере вычисляется, кто построит чудо света?
Когда чудо строится, у тебя есть показатель "производство" в городе + кол-во уже вложенного "производства" в чудо. Каждый ход "производство" из города вкладывается в прогресс постройки чуда. Если в один ход оба игрока заканчивают одно и то же чудо, то построится оно у того, у кого было по итогу вложено больше "производства".
Например:
- Игрок А строит чудо размером в 15 производства со скоростью 5 производства за ход; ход 1: 5/15, ход 2: 10/15; ход 3: 15/15
- Игрок Б строит чудо со скоростью 6 производства за ход: ход 1: 6/15, ход 2: 12/15, ход 3: 18/15
По итогу чудо появится у игрока Б. Это не только в МП так, в СП против ботов логика та же.
Если в один ход оба игрока заканчивают одно и то же чудо, то построится оно у того, у кого было по итогу вложено больше "производства".
Это в вашем моде так? В ваниле это завит от слота: строит тот игрок, который находится выше.
А в турнирном патче, например, сделали псевдорандом.
Интересная статья. С удовольствием прочитал и лайкнул.
Firaxis выложила в открытый доступ Windows DLL с логикой всей игры
Наверное, всё-таки, исходники DLL-библиотеки, а не сам скомпилированный DLL-файл.
Я не играл в 5ую часть и не знаю как там устроен мультиплеер. Там ходы делают по очереди или одновременно?
Спасибо за прочтение! =)
Там на выбор 3 варианта:
- одновременные
- по очереди
- "гибридные", т.е. одновременные в мирное время и по очереди если объявлена война
Дополню, в оригинальной версии если вариант ходов "по очереди", то стабильность сетевой игры сохраняется на более долгое время. Разница в 100-150 ходов на "нормальной" скорости в сравнении с одновременной игрой и гибридом.
Это видимо следствие описанного в статье - если ходы одновременные, то вероятность того, что два игрока будут пушить разнонаправленные действия, выше.
По такой логике игра с поочередными ходами, без ботов, варваров и городов-государств, а только с игроками, должна ещё сильнее повысить стабильность.
Вообще, конечно, далеко не факт, что дело в одновременных/поочередных ходах. Уже просто не помню, как они там обрабатываются, поэтому не буду утверждать наверняка, но есть вероятность, что это везение, либо эффект плацебо))
Это как раньше, еще до того как поняли почему VP вылетает в мультиплеере, были всякие "туториалы как пофиксить МП", где советовали графику попроще поставить, какие-то еще там настройки в игре подкрутить, и якобы оно становится стабильней)) По факту это всё, конечно, не меняет того, что где-то в коде в логике работы "чего-то" есть реальный десинк по типу "рабочий, который установлен в автоматический режим обработки клеток, при нахождении клеток-кандидатов с одинаковым Score, выбирает из них рандомную, потому что в коде использован sort
вместо stable_sort
".
Но если вот обрезать функционал (оффнуть ботов, варваров, руины, конгресс и т.д.), то, конечно, шанс словить десинк явно меньше, просто потому что меньше уникальной логики будет раниться. Просто не уверен, что это относится к одновременным/поочередным ходам, потому что если логика с багом, то она все равно рассинхронит игру вне зависимости от того, в каком режиме ходов ее отранили.
У нас основные проблемы с синхронизацией были от того, что не все действия игрока (инпуты) проходили через планировщик. Некоторые действия могли применяться в зависимости от состояния тумана войны, без учета того что туман у игрока А другой нежели у игрока Б. OpenAL нам жизнь попортил, оказывается он иногда втихаря переставлял флаги 8087CW и округление начинало работать по разному на разных клиентах. Иногда, конечно, и random портил всё, когда по ошибке вызывали его, а не игровой детерминированный рандом. Но у нас RTS. Как можно столько косяков оставить в TBS мне сложно представить ..
Наверное надо вернуться в циву, это знак
Я давно уже думаю, что 4X-стратегии идеальны для сегодняшних нейросетей! Придумать MCP сервер (я уже спрашивал в Humankind на этот счёт), и пусть рассуждающая нейронка в контексте игрока (ни больше, ни меньше) думает, что́ распланировать на следующий ход. Думаю, это был бы идеальный соперник! А что, если бы их было все 15?
Вон, для Diplomacy ведь запилили партию между ИИшками.
давно неероночка есть в старкрафте и она играет вроде даже
Можно запускать нейросеть тупо как stateless-модель в данном шаге. На промпт давать краткую сводку своей стратегии, историю взаимодействий, долгосрочные планы (которые сама же модель и написала для себя) и идентичность и через MCP совершать нужные действия. Детально - последние 10 ходов. Это не сильно проблема, я считаю. При таком подходе серьёзных отклонений в согласованности шагов и общей адекватности быть не должно.
Титаническая работа.
Я - разработчик игр, и с трудом представляю, какой объем терпения и упорства нужен, чтобы так дебажить чужой код. Респект.
Что касается архитектуры - это на самом деле типичное решение для стратегий, хотя прежде всего для реалтаймовых (хотя пошаговым тоже никто не запретит так делать)
Это основано прежде всего на том, что состояние игры слишком комплексное, поэтому его просто дорого передавать по сети. Банально играть будет очень болезненно, потому что развертывание состояния будет сравнимо с загрузкой сейва (ну не всего, там еще графика и ассеты грузятся).
Для РТС это вообще невозможно - сеть захлебнется передать состояние 10000 юнитов (а такие стратегии есть).
Поэтому вместо отправки состояния его считают локально и оно детерминировано, а синхронизируют лишь команды от игроков - в кадр номер A выбрать юниты в области от XY 1 до XY 2, отправить в точку XY 3. Если одинаковые команды применяются детерминировано на состояние, то оно остается синхронным. Для проверки, что состояния не разошлись, можно отправлять вместе с командами хеш состояния и сравнивать.
Таким образом через сеть требуется отправлять только ваши клики мышкой и нагрузка на сеть зависит только от CPM.
Изящная архитектура, но накладывает серьезные ограничения на логику.
Помимо приведенных вами кейсов (sort, random, словари) типичная проблема так же разная реализация float и тригонометрии на разных архитектурах. Поэтому часто используют целочисленную арифметику, в том же StarCraft используется разбиение пространства на полигоны, но с целочисленными координатами.
Дебажить - одно удовольствие.
Блин, а мы тут уже успели накомментить, что это всё костыли и что так вообще не делается )) Ну, тогда задумка, как говорится, хорошая, но реализация так себе.
Ну это инструмент.
Думаю игру типа BAR или Supreme Commander просто не сделать иначе.
Но проблем с этим действительно много и без хороших инструментов дебага это очень больно разбирать.
С другой стороны, если правильно организовать, можно дешево делать записи симуляций и воспроизводить места с проблемами - запись игры будет всего лишь списком примененных команд от всех игроков.
Можно получить срез любого состояния в любое время.
В теории
Как разработчик RTS, хотел бы уточнить, а чем именно проблематична нестабильная сортировка и словари в разрезе С/С++ и детерминированности? Вроде как и то и другое сортирует и раскладывает по бакетам всё равно детерминировано (только нестабильно), там же нету random внутри, или есть какой-то изъян?
В теории - да, если действия применяют одно за другим.
Если ты добавил 3 элемента в словарь А и такие же 3 элемента в словарь Б, они "как бы идентичны".
Но во первых порядок операций может нарушаться, а во вторых есть скрытые параметры, которые ты не контролируешь.
Например, клиент собирает команды (отправки юнитов по клику мыши) и добавляет их в словарь/список, в конце кадра - отправляет их другим клиентам, а через Х кадров они все применяют эти команды.
Но клиент А собирает команды в свой словарь сам, один за другим. Словарь растет на ходу.
Потом словарь сериализуется, отправляется по сети, десериализуется на клиенте Б.
И тут элементы добавляются не так - словарь создается сразу нужно длины, у него из-за этого другой размер и конфигурация бакетов. Плюс сама сериализация/десериализация может изменить порядок, и даже добавление элемента одного за одним даст разный порядок.
Формально после операции у вас 2 одинаковых словаря, но если вы попробуете итерировать по ним - получите разные списки.
Аналогично список - к примеру команды должны выполнятся от дальнего юнита к ближнему. Поэтому каждый раз когда команда попадает в список на следующий кадр - ее нужно отсортировать. Клиент А получит Клик - сортировка - клик - сортировка - клик - сортировка. Клиент Б получит - получение команды - сортировка.
Все это может усугубляться сложными архитектурами, многопоточностью и т.д.
Формально - ты добавил 2 объекта и они одинаковые. По факту - ты добавил их в разном порядке и хэш не будет тем же самым.
Там может быть все что угодно - случайно использовал детерминированную операцию в эффекте, а эффект не работает на Low параметрах графики.
У одного игрока лист с сакуры упал, а у другого - нет - рассинхрон (реальная история)
Случайно использовал недетерминированную операцию (рандом внутри) - рассинхрон.
Результат сериализации/десериализации разный - рассинхрон.
У нас была ошибка из-за того, что разные имплементации Unity для windows и android/ios давали разный результат сериализации float - с точкой и с запятой.
Разные клиенты используют разные версии общих библиотек - рассинхрон.
У тебя JIT компиляция и 2 устройства используют разные архитектуры -> разные оптимизации для хэшей -> разный порядок в словаре.
Звучит очень тупо, но только после того, как найдешь проблему.
Хороший материал для еще одной статьи )
Спасибо за развернутый ответ!
Попробую разобраться по шагам. (спойлер, многоплатформенность не упоминается до пункта 11).
Не уверен, как именно в детерменированном мире порядок операций может нарушаться. Только что если какая-то многопоточность участвует, но тогда она и так всё поломает из-за конкурентности (или сделет всё в разы сложнее).
Если мы собираем команды в словарь у игрока А, а потом его разматываем в список, чтобы этот список снова положить в словарь только у игрока Б, то тут да, мы сами себе злобные Буратины, т.к. операции (собрать-всписок-выполнить у А, и собрать-всписок-передать-собрать-всписок-выполнить у Б) получаются не тождествены.
Однако, если операции делать идентично, то непонятно откуда тут возьмется недетерминированность, т.к. и размер бакета и хэш-метод одинаковые и детерминированные. Де-сериализация идентична если её выполнять у всех идентично.
Вывод, словари нельзя сравнивать просто по составу элементов. У словаря больше внутреннего состояния. Его тоже надо сравнивать для настоящей одинаковости.
Получение команд в список и их выполнение кусками у А и передача к Б для выполнения пачкой - опять же косяк подхода и стабильность сортировки тут не при чем и ситуацию не спасет. Всё банально сведется к тому, что А обработает отсортированных юнитов кусками типа [2,7]+[8]+[1,3], а Б обработает точно так же отсортированных всех [1,2,3,7,8].
Сложность архитектур не нарушает детерминированности, если только в ней не замешаны недетерминированные элементы, или не нарушена идентичность симуляций у клиентов.
Многопоточность - это да, тут надо прям осторожно со словарями и вообще (но если учесть пункт 4 то всё ок). Но тогда и корнем сложностей надо называть её, а не сортировки и словари.
Кстати, стабильность сортировки тут не поможет, она же всего лишь гарантирует неизменность порядка элементов от начального, а не то, что "одинаковые" элементы будут отсортированы идентично. Тут тогда компаратор надо докручивать.
Графику надо отделять от логики, это практически первая заповедь. В идеале, игра должна работать вообще без дисплея (что, кстати, открывает отличные просторы для автотестов, тренировки ИИ и прочих симуляций. Люто рекомендую).
Про лист сакуры - красиво. Я тоже такие проблемы ловил )) Но это всё же скорее личные заслуги того кто это накосячил )))
Мультиплатформенность это проблема. Обычно из-за тонкостей реализации IEEE-754, 8087CW, и всякой тривиальщины типа настроек локали (разделителя разрядов по умолчанию итп). Но крови попьёт немало.
Внешние библиотеки надо применять только для общения с внешним миром (сеть, звук, инпут, графика, итп). Игровая логика должна быть от них отделена строго-настрого. Кстати да, мы тоже горели на том, что например использовали Виндовую "умную" сортировку имен в списках вместо своей.
Jit и словари, вот тут похоже на корень проблемы, но опять же он в разных реализациях, а не в словаре как таковом.
Получается, что внутренние проблемы как обычно в нарушении идентичности из-за некоторых промахов архитектуры (выполнять кусками vs выполнять пачкой), в выполнении чего-то с привязкой к графике/звуку итп. А внешние - в неподконтрольных разнородных библиотеках и различиях их реализаций. Условно, но если использовать внешние Sin/Cos, то и на них можно погореть. Словари из библиотек лишь пример того, что на разных платформах может нередко работать по разному (а сортировки как-бы совсем не при делах). Будете носить с собой свой словарь - будет идентичен. Так же и с int вместо float.
В общем интересная тема, такое лучше за пивом обсуждать.
Вы правы, но это ведь звучит как "нормально делай - нормально будет".
1 Детерминированность - это не имманентное свойство системы, это абстракция, ее создает программист. Одна ошибка - и ты ошибся.
Конечно, если за всем следить - все будет хорошо, но чем больше мы используем сложных типов, чужих типов и т.д - тем больше шанс косяка.
2. Да, но п3
3. Да, но MS явно в документации говорит, к примеру, что реализация хеш функции - не гарантированная и использовать его как стойкий хеш, сравнивать ее между разными машинами и архитектурами нельзя, она может быть разной. Отсюда и словари для каких то типов данных могут иметь разный порядок. Это кажется несущественным, если игра под windows, но может оказаться следующая версия dot net, сборка под другую платформу, серверные функции на linux серверах - и приехали. Представьте, все идеально работает, а через пол года портируешь на Mac и ломается при каждом использовании предмета.
4. Как раз противоположный - словари нужно сравнивать по явному стейту, приводя его в нормализованное состояние. И не использовать итерацию по всем (как раз ее порядок может быть разным). У MS есть криптографические реализации для стабильной сериализации данных. Ну или OrderedDict использовать.
5. 6. 7. 8. Все так
9. Конечно, но есть и эффекты на стыке. К примеру где то полет стрелы - просто эффект, а где то симулируют полет снарядов - значит недостаточно только показывать его, частица честно должна лететь и искать коллизии. Иногда отделить одно от другого сложно. Но да, в общем случае это ошибка скорее.
10. Да.
Вообще есть чисто техническая проблема - для реализации такой штуки, нужно сделать модель без зависимостей, чтобы гарантировать, что она не потащит что-то недетерминированное. И использовать в коде view - обычный рандом, а в коде модели - специальный детерминированный. Не все языки позволяют легко это сделать. Например сделать отдельную dll (assembly definition). Но даже в этом случае легко случайно импортировать что-то недетерминированное неявно или не заметить. Или наоборот - использовать детерминированный рандом в визуальной части - ты его использовал в коде 10 механик, легко перепутать. Собственно думаю пример с сакурой (сакура поэтичная была, в статье был пример про эффекты дерева, но случай вроде реальный)
11. Да. Непосвященному может показаться, что float работает хорошо и детерминированно, пока внезапно он не начнет плохо работать на прод сервере, потому что он на другой плотформы от тестового. А уж мобилки зачастую имеют просто другие реализации тех же библиотек.
12. Да, но иногда идея писать свою реализацию поиска пути и триангуляции делоне для разбиения полей может быть плохой.
13. Вычисление хеша может быть разным. Словарь использует хеш.
В общем да - часто сам себе злобный буратино, но и игры ведь сложные, и людей много разных над ними работает, и много чего под капотом неявного.
Я именно сетевых РТС таких не делал, но у нас сейчас ровно такая архитектура для стейта (команды применяются локально и отправляются на сервер, где он применяет их независимо на стейт и проверяет корректность).
И у нас были и проблемы со словарями, и с сериализацией и самописными хешами, и с порядком операций, и с подписками (на клиенте view часть и несколько логических подписана, а на сервере - только логические и порядок из-за этого разный).
В другом проекте была проблема с точкой и запятой при передаче float по сети.
Большая часть ошибок максимально тупые, но от этого не менее ошибки.
Отличный ответ, спасибо!
Как бы да, "нормально делай - нормально будет". Просто хочется копнуть и разобраться на шаг поглубже, понять чем именно те или иные концепции ущербны и выяснить что деталями реализации, а не концепциями.
Просто в какой-то момент (например, при мультиплатформе), понимаешь, что float-операции это тоже внешняя реализация и приходится делать свою, на int-ах, но не отказываться от неё совсем. Так же как и хэш и словари - если внешнее решение имеет расхождения - пора их анализировать и может быть переходить на своё.
Я так-то больше на десктопе работаю, в Делфи (то же самое что Плюсы, только помногословнее). Поэтому для меня иметь свою реализацию словаря это нечто само-собой разумеющееся и легко выполнимое. А дальше я тащу его на Мак или Линукс/Андройд и он работает идентично. Проблемы со внешними зависимостями от внешних факторов обычно (xml сериализатор вот системный использовали, пока не погорели с ним и перешли на "свой" попроще). А так, да, много чего сам писал в образовательных целях, и поиски пути, и Делоне, и Венгерский, и генетику, и всяко-разное. Самостоятельно посчитать хэш - тоже не так сложно, тем более сейчас, в эпоху LLM.
Тонну косяков на отладке тоже собрал, конечно. Хорошо помогает инструментарий, TDD и автоматизация. Например симулировать час игры, а потом его реплей и сверять на идентичность, и так 50 раз. Или иметь пару сотен игровых тестов. Пока вот только к мультиплеер-тестам не придумал как подступиться - заводить отдельного Overseer-а и им рулить пачкой псевдо-игроков ..
А в чем заключается проблема обновить проект на свежие версии компиляторов? Там вот MSVC v90 прибит гвоздями, что принуждает к пляске с бубнами вокруг установки студий разных версий в нужном порядке (без гарантий на успех), причем, ссылки на скачивание постепенно дохнут.
Почему vcxproj не обновить до современной версии, входящей в поставку 22 студии?
Честно, не в курсе нюансов и вообще далек от C++ разработки и всего что с ней связано, но когда я туда контрибьютил, вроде как основной проблемой здесь было то, что закрытые либы (которые есть только в виде DLLей) юзают старый тулкит и поэтому ядро тоже как бы завязано на старом тулките. Ну, что-то такое, хз, может это и бред всё и просто не нашлось шарящего чела, который все это завез бы ))
Мультиплеер в Цивилизации 5