5

Вся наша жизнь спираль: сеть

Серия Вся наша жизнь спираль

И снова здравствуйте. Год назад я выкладывал пост, под названием «Вся наша жизнь спираль», в котором немножко рассказал об игре, над которой работаю. Вот он: https://pikabu.ru/story/vsya_nasha_zhizn_spiral_5831544 Этот пост можно считать его продолжением.

Не писал достаточно долго по нескольким причинам. Основная, я опять подзабил на проект и занимался основной работой, также появились некоторые трудности, которые заставили меня снова залезть в дебри Unreal Engine 4 и смотреть, что Эпики там понакрутили. Но я снова в седле, и т.к. очередной этап разработки завершен, то можно и написать статью об этом.

Начну, пожалуй, с новостей. Их всего две:

1) Мы относительно недавно обзавелись сервером в дискорде: https://discordapp.com/invite/aNfW5FQ , т.ч. милости просим.

2) Мы приняли решение часть неигровых модулей выложить в Open Source под BSD лицензий.

Вот об одном (единственном доступном широкой публике на данный момент) open Source модуле, под названием Client Authority Network (CANet) мы и поговорим.

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

• Все игроки должны находится на одной локации в рамках одного сервера. 

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

• Все игроки должны иметь на сервере координаты в глобальной сетке карты.

Что из этого следует: в нашем проекте, чтобы игроки были синхронизированы с картой, нужно будет постоянно держать загруженной всю галактику, со всеми планетами, станциями, итд, а также сохранять глобальные координаты игроков (а это, на минуточку числа, которые современные компьютеры не умеют считать. Для знающих: int256 и выше). И кроме того, невозможно применить стандартные «хаки» (динамическое масштабирование, World Shifting, и прочее) для поджатия мира, т.к. в этом случае будет нарушаться третий закон.

Решение, казалось бы, напрашивается само собой: написать свою сетевую модель, которая не будет столь категорична к позициям игроков. Примерно это, CANEt и делает. Однако, чтобы написать свою сеть в UE4, это нужно переписать туеву кучу кода, и не факт, что после очередного обновления движка все будет работать так, как было задумано. Посему я решили схитрить. Я не стали переделывать всю сеть, а заместо этого перевернул работу оригинального, получив заместо стандартной модели сервер-клиент, модель клиент-роутер-клиент (почти честная PTP). В результате, все три закона стандартной модели стали недействительными. И проблема частично была решена. Почему частично? Осталась проблема синхронизации игроков в отдельно взятых локациях. Эту проблему модуль не решает, но, зато ее способна решить фича, которая перекочевала в UE4 прямо из Фортнайта, а именно Replication Graph, но сегодня речь не о нем.

Итак, как же работает CANet. CANet, как я уже говорил, переворачивает принцип работы сети в UE4 с ног на голову. Начинается все так-же, как и в оригинале. На сервер создаётся Game Mode, которая ждет подключения игрока. Как только игрок подключился, создается Player Controller и Player State, которые реплицируется на клиенты (Player Controller только владельцу подключения), а вот дальше начинаются отличия.

В оригинале, следующим этапом было бы создать на сервере чарактер игрока (ACharacter/APawn) и среплицировать его на клиенты. В случае CANet это происходит немножко иначе:

• На сервере создается специальный класс UClientChannel, который привязан к APlayerState в качестве компонента и реплицируется вместе с ним.

• При первой репликации UClientChannel создается карта всех реплицируемых свойств чарактера игрока, считаются их контрольные суммы и записываются в буфер.

• Если APlayerState привязан к APlayerController, то UClientChannel начинает считаться авторитетным и начинается покадровая проверка свойств из буфера.

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

• В конце кадра, если буфер имеет не нулевую длину, то отправляем его на сервер.

• Сервер сохраняет себе свойства в свой буфер и перенаправляет его остальным клиентам. 

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

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

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

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

На этой ноте, наверно можно и заканчивать. Сам модуль можно найти по адресу: https://github.com/TehnoMag/UE4-Module-CANet , а в следующий раз мы поговорим либо о Replication Graph либо о Wave Function Collapse (WFC) генерации в UE4 и его роли в L.I.M.A. strace_.

Правила сообщества

ОБЩИЕ ПРАВИЛА:

- Уважайте чужой труд и используйте конструктивную критику

- Не занимайтесь саморекламой, пишите качественные и интересные посты

- Никакой политики


СТОИТ ПУБЛИКОВАТЬ:

- Посты о Вашей игре с историей её разработки и описанием полученного опыта

- Обучающие материалы, туториалы

- Интервью с опытными разработчиками

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

НЕ СТОИТ ПУБЛИКОВАТЬ:

- Посты, содержащие только вопрос или просьбу помочь
- Посты, содержащие только идею игры

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

- Посты, не относящиеся к тематике сообщества

Подобные посты по решению администрации могут быть перемещены из сообщества в общую ленту.

ЗАПРЕЩЕНО:

- Публиковать бессодержательные посты с рекламой Вашего проекта (см. следующий пункт), а также все прочие посты, содержащие рекламу/рекламные интеграции

- Выдавать чужой труд за свой

Подобные посты будут перемещены из сообщества в общую ленту, а их авторы по решению администрации могут быть внесены в игнор-лист сообщества.


О РАЗМЕЩЕНИИ ССЫЛОК:

Ссылка на сторонний ресурс, связанный с игрой, допускается только при следующих условиях:

- Пост должен быть содержательным и интересным для пользователей, нести пользу для сообщества

- Ссылка должна размещаться непосредственно в начале или конце поста и только один раз

- Cсылка размещается в формате: "Страница игры в Steam: URL"

0
Автор поста оценил этот комментарий

А как это скажется на производительности? На реддите меня предупредили, что игра на блюпринтах будет сильно лагать на слабых ПК.

раскрыть ветку (1)
2
Автор поста оценил этот комментарий

Ну я бы не сказал, что это прямо будет заметно. Да, в общем случае будет чуть медленнее, чем если бы писалось на чистом C++, но, во первых, всегда можно в настройках проекта выставить опцию Nativize, который странслирует блюпринты в C++, а во вторых, понятие слабого ПК вещь растяжимая.


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

показать ответы
0
Автор поста оценил этот комментарий

@TehnoMagEG,Привет, слушай, хочу тебя кое о чем спросить, как чувака хорошо знакомого с UE4: можно ли сделать игру чисто на блюпринтах? То есть без единой строчки кода?

раскрыть ветку (1)
1
Автор поста оценил этот комментарий

Краткий ответ - да можно. Тот же самый Фортнай на 95% на принтах. ARC - на 100% (но это на сколько я знаю, могу ошибаться)

показать ответы
0
I can fix that
Автор поста оценил этот комментарий

Привет!
Эта задачка в работе, скоро поправим :>

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

o7

0
Автор поста оценил этот комментарий

>> Все игроки должны иметь на сервере координаты в глобальной сетке карты.
Пожскажите, а где такой закон указан?

В целом @TheodorTalion высказал примерно то же самое что думаю и я, но ещё добавлю что складывается ощущение, что вы вместо переопределения логики в примитивов ACharacter, AGameMode, ReplicationGraph (ну и так далее) вносите свою новую абстракцию которая позволяет классам "из коробки" работать по вашим правилам. С очень большим оверхедом.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

@TheodorTalion высказал абсолютно ничем не аргументированное мнение, причем с наездом. Я надеюсь, вы до его уровня не скатитесь. Но прежде чем начинать дискуссию, пожалуйста, ознакомьтесь с кодом.


Пожскажите, а где такой закон указан?
В исходном коде движка, а конкретно функции FRepMovement::Rebase*. Сервер может иметь только одну активную сетку (не учитывая Streaming level). Конечно, к нему можно применить WorldShifting, но он повлияет на всех актеров на сервере. И перед репликацией c сервера будет вызвана функция RebaseOntoZeroOrigin, без учета координат на Streaming уровне.


ReplicatedMovement.Location = FRepMovement::RebaseOntoZeroOrigin(RootComponent->GetComponentLocation(), this);

0
Автор поста оценил этот комментарий

Ты даже не понимаешь разницы репликации и рпц - о чем говорить? Делать мне нечего, кроме как тратить время на то, что в принципе не работает.

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

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

1) Модуль находится все еще в разработке. Я не выкатывал его как релиз. О чем как минимум два раза уже сказал.


2)

Делать мне нечего, кроме как тратить время на то, что в принципе не работает.
Тогда что вы тут делаете?


3)

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


В общем, я закрываю эту ветку. Конструктива с вами, как я вижу, не получится.

0
Автор поста оценил этот комментарий

Реплика гарантирует доставку пакета, но не мгновенно. Опять таки, учи матчасть. Твоя система ляжет за минуты в реальных условиях при нормальном наличии игроков. Тупа забит канал до сервера и от сервера к клиенту. Каждый кадр отправлять релиабл рпц до сервера - это такой бред. Да у тебя и на 2 клиентах чуть-чуть удаленных друг от друга и сервера ляжет все нахер.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

Уважаемый, если у вас есть конструктивные предложения, я готов их выслушать, но, я вас прошу, не надо поливать говном то, что вы, как я вижу, не понимаете и не хотите понять. И смените тон, мы с вами водку не пили.


Хотите помочь? Код открыт. Соберите демку; воспроизведите те проблемы, о которых вы тут с огнем из жопы доказываете и откройте issue на хабе. Воспроизведете? Исправим. А сейчас это пустой треп.

показать ответы
0
Автор поста оценил этот комментарий
ПыСы: RPC работает также как и реплика по большей части. Передается та-же самая информация. ID объекта, смещение по стеку. Добавляется только буфер аргументов, который чуть больше, чем при реплике.

Ахахах. Учи матчасть. Разница между репликацией и рпц огромная.


Ну удачи тебе.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

Ну да, ну да. Костыль в виде tcp over udp, которым закостылины Reliable RPC естественно работает по другому (и в Nix системах частенько приводит к Unaligned memory access). Но вот нюанс, есть еще Unreliable RPC, который использует тот-же подход, что и RepLayer и работает с той-же скорость (хоть и не гарантирует доставки пакета, в прочем, как и Реплика). И черт возьми, все эти ваши движения, коллизии, и евенты, с клиента, внезапно, передаются именно через него.

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

А сразу к двум, трем игрокам на локации она может привязатся?


Да, UClientChannel работает штатным образом. Но дает возможность инвертировать канал передачи, посредством абуза штатного же RPC

Ты посылаешь изменения как RPC? Не как репликацию? Ты убьешь любой сервак. Посылать изменения положения клиента каждый кадр через RPC - это жестоко.


Проблема с читерами, на самом деле, ровно такая-же, как и при использовании оригинальной сетевой модели.

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


Частично, ее решают другие не авторити клиенты (метод круговой поруки),

А это вообще супер лагучее говно.



Лучше делать связку Level streaming и seamless travel и заводить кучу серверов на каждую локацию. Игрок даже не заметит, что он перешел на другой сервер.


Например:
https://www.youtube.com/watch?v=VVMZTFNMidY
https://www.youtube.com/watch?v=DrQ6pSaurgw

раскрыть ветку (1)
0
Автор поста оценил этот комментарий
А сразу к двум, трем игрокам на локации она может привязатся?
Один клиент также выступает в роли авторити. На остальных его реплика. Непись в любой момент времени видит всех игроков, которые есть в зоне его интереса и может работать с ними через тот же авторити. При этом, если сервер посчитает нужным, он может передать авторити на другой клиент.


Ты посылаешь изменения как RPC? Не как репликацию? Ты убьешь любой сервак. Посылать изменения положения клиента каждый кадр через RPC - это жестоко.
Пока так, потом тесты покажут. В худшем случае допишу сокет для потоковой реплики. ПыСы: RPC работает также как и реплика по большей части. Передается та-же самая информация. ID объекта, смещение по стеку. Добавляется только буфер аргументов, который чуть больше, чем при реплике.
В оригинальной сетевой модели нет проблемы с читерами, если руки не из жопы растут.
вы наивны.


Лучше делать связку Level streaming и seamless travel и заводить кучу серверов на каждую локацию. Игрок даже не заметит, что он перешел на другой сервер.
Одна инстанция сервера занимает ~~1Gb оперативной памяти. Для разворачивания игрового мира таким образом, я посчитал, мне потребуется сервер, или кластер серверов с общим объемом оперативной памяти ~~122 Gb, и это без учета игровых механик. Только мир. Такие требования не соответствуют моему желанию раздавать сервера игрокам.

Level straming, я и так активно использую в рамках одной ноды сетки.

показать ответы
1
Автор поста оценил этот комментарий

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

"При проверке, если контрольная сумма свойства в буфере не сошлась с суммой свойства чарактера, то свойство помечается как изменившееся и записывается во второй буфер. В конце кадра, если буфер имеет не нулевую длину, то отправляем его на сервер." - Ты снова изобрел репликацию?

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

Т.е. UClientChannel работает через серверную репликацию, но просто имеет авторитетную роль для клиента, поэтому слушает его изменения? Т.е. этот объект так же существует на сервере и обычным способом реплицирует данные.

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

В общем, считаю что это супер баговая система, которая просто не работает для 90% случаев. Очень подверждена влиянию читеров (потому что поменять память - это 10 секунд), крайне сильно повышает потребляемые ресурсы у клиента в зависимости от кол-ва активных сущностей на сервере, что вообще жопа. Играешь на слабой машине? Ну извини, играй только на сервере в 20 игроков, если 21 зайдет, то у тебя весь проц сожрет и комп начнет лагать. А если ты еще и браузер захочешь запустить, то тут уж извини.


Не понятно для чего ты вообще это изобретал. Если для ММО, то анрил на одном сервере не потянет ММО, если для любой другой игры, то стандартных решений хватит. Хули там тех расчетов для 20-30 игроков.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий
Не понятно для чего ты вообще это изобретал. Если для ММО, то анрил на одном сервере не потянет ММО, если для любой другой игры, то стандартных решений хватит. Хули там тех расчетов для 20-30 игроков.
Перечитайте пост, пожалуйста. А лучше сначала мой первый пост, а потом снова этот.

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

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

"При проверке, если контрольная сумма свойства в буфере не сошлась с суммой свойства чарактера, то свойство помечается как изменившееся и записывается во второй буфер. В конце кадра, если буфер имеет не нулевую длину, то отправляем его на сервер." - Ты снова изобрел репликацию?

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

Т.е. UClientChannel работает через серверную репликацию, но просто имеет авторитетную роль для клиента, поэтому слушает его изменения? Т.е. этот объект так же существует на сервере и обычным способом реплицирует данные.

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

В общем, считаю что это супер баговая система, которая просто не работает для 90% случаев. Очень подверждена влиянию читеров (потому что поменять память - это 10 секунд), крайне сильно повышает потребляемые ресурсы у клиента в зависимости от кол-ва активных сущностей на сервере, что вообще жопа. Играешь на слабой машине? Ну извини, играй только на сервере в 20 игроков, если 21 зайдет, то у тебя весь проц сожрет и комп начнет лагать. А если ты еще и браузер захочешь запустить, то тут уж извини.


Не понятно для чего ты вообще это изобретал. Если для ММО, то анрил на одном сервере не потянет ММО, если для любой другой игры, то стандартных решений хватит. Хули там тех расчетов для 20-30 игроков.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

Я вижу, что вы не разобрались до конца в вопросе. Попробую пояснить.


"Все игроки должны считать логику на сервере, на клиенты транслируется только результаты обработки." - Это неправда. Клиент может сам считать логику и транслировать её на сервер, что ты дальше и описываешь.
Да, клиент может транслировать результаты некоторых расчетов через RPC на сервер. Но для этого необходим опять таки авторити на сервере, который будет принимать RPC и обрабатывать. Однако, основная логика, зашитая в классы самими эпиками, обрабатывается на сервере. Те-же движения и коллизии. Из-за размеров мира этот вариант мне не подходит.


"При проверке, если контрольная сумма свойства в буфере не сошлась с суммой свойства чарактера, то свойство помечается как изменившееся и записывается во второй буфер. В конце кадра, если буфер имеет не нулевую длину, то отправляем его на сервер." - Ты снова изобрел репликацию?
Увы, но стандартный RepLayout работает только в одну сторону. Переписать его без изменения кода движка - нереально.
"Все игроки должны находится на одной локации в рамках одного сервера" - Т.е. ты всегда транслируешь изменения через сервер, а стандартные решения анрила не транслируют изменения, если ты их не можешь увидеть изменение и это плохо? Твое решение вообще плюет на игроков, т.е. если игроков будет больше 1000, то тачки клиентов начнут гореть из-за расчетов , хотя для какого-нибудь серверного проца в 28 ядер это вообще не нагрузка. Ибо колизии тебе все равно нужно считать, ибо что, при пролаге сети в 1000 мс. персонажи стоять должны? Нет, они должны двигаться по предсказываемой траектории, что делает репликация движения в анриле, но раз персонажей на сервере нет, то никакой репликации движения нет).
На производительность клиентов это не скажется ровным счетом никак. А с сервера нагрузка наоборот спадет. Опять же, клиентам не нужно считать всех игроков. Только тех, которые попадают в NetCullDistance и в ноду Replication Grapha (пока не реализовано)
Т.е. UClientChannel работает через серверную репликацию, но просто имеет авторитетную роль для клиента, поэтому слушает его изменения? Т.е. этот объект так же существует на сервере и обычным способом реплицирует данные.
Да, UClientChannel работает штатным образом. Но дает возможность инвертировать канал передачи, посредством абуза штатного же RPC
"на самом сервере, чарактеров и прочих актеров просто не существует" - как мобы будут себя вести? Каждый клиент будем им сам считать логику, куда идти и кого атаковать? Это вообще ерунда. Моб как бы один, а у двоих клиентов ведет себя по разному и находится в абсолютно разных местах (из-за пролага сети и динамических коллизий) и это "типа" нормально. Т.е. из-за пролага сети у одного клиента моб побежит вправо, а у второго влево.
Или это сервер будет считать его и транслировать ему положение? А как, если у сервера нет павнов игроков? Моб откуда узнает куда ему идти и еого бить в таком случае?
Система может спавнить мобов с привязкой к конкретному игроку. В этом случае Авторити становится его клиент. Также никто не отменял авторити на сервере. Свойства объектов на нем существуют и все выборки можно делать по ним. Коллизии, при необходимости, можно считать по CDO объектам.
В общем, считаю что это супер баговая система, которая просто не работает для 90% случаев. Очень подверждена влиянию читеров (потому что поменять память - это 10 секунд), крайне сильно повышает потребляемые ресурсы у клиента в зависимости от кол-ва игроков на сервере, что вообще жопа. Играешь на слабой машине? Ну извини, играй только на сервере в 20 игроков, если 21 зайдет, то у тебя весь проц сожрет и комп начнет лагать. А если ты еще и браузер захочешь запустить, то тут уж извини.
Проблема с читерами, на самом деле, ровно такая-же, как и при использовании оригинальной сетевой модели. Частично, ее решают другие не авторити клиенты (метод круговой поруки), частично, еще не сделанный чит-детектор на сервере. По нагрузкам уже выше отписал.
показать ответы
0
Автор поста оценил этот комментарий

@moderator, поправьте, пожалуйста, если не трудно, переносы в перечислениях. Не могу уже отредактировать пост.

@SupportTech, в превью все нормально отображалось, но при публикации сожрались символы конца строки. Поправить бы =/

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

Мерси

0
Автор поста оценил этот комментарий

@moderator, поправьте, пожалуйста, если не трудно, переносы в перечислениях. Не могу уже отредактировать пост.

@SupportTech, в превью все нормально отображалось, но при публикации сожрались символы конца строки. Поправить бы =/

показать ответы

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества

Недвижимость и ремонт

Теги

Популярные авторы

Сообщества