Pull to refresh

Comments 77

ну FPS и PING понятно как влияют на это, а что скажете на счет TICKRATE?
В нашей реализации мы позволили себе отойти от описанного в статье и внедрили интерполяцию на сервере при выстреле. Так что даже при TickRate = 1 и простой траектории врага попадание было идеальным (хоть конечно и выглядело это отвратительно).
Я пожалуй опишу эту технику в этой статье.
Как обрабатывается ситуация, когда другой игрок (2), уже «забежавший за угол» стреляет в игрока (1) и, вроде бы, тоже в голову? У него на экране «прошлое» (т.е. он видит игрока (1), который ещё не выстрелил), он попадает в игрока(1). Игрок (1) видит на экране «прошлое» (игрок(2) бежит за угол) стреляет, попадает.
Во-первых, обычно анимация смерти начинается только после получения стейта с сервера, где говорится, что игрок умер (обратите внимание, в нашей демке сделано именно так.)
А далее разные игры обрабатывают такие ситуации по разному:
  • Оба игрока умирают(т.к. оба игрока совершили выстрел пока еще были живы и оба попали в своего врага).
  • Игрок, команда которого дошла до сервера первой, остается вживых. Т.е. когда команда на выстрел доходит до сервера, сначала сервер проверяет, жив ли игрок в данный момент времени, и только если жив проводит рейкаст в прошлом.
в том же overwatch часто бывают ситуации «выстрелили друг в друга и оба умерли»…
а вот в CS:GO именно так как описал автор. Иногда и поражает, почему ты вроде стрельнул первый, а даже не попал
Если не ошибаюсь, в Overwatch просто есть Ханзо с медленными стрелами.
это понятно, но я имел в ивду солдата и вот это все, с пулями.
Смею предположить, что все дело в скорости полета пули.
В CS:GO выстрел настигает жертву мгновенно
В Overwatch почти у всех персонажей есть скорость полета «пули», кроме Трейсер, DV-a Солдата-76 и похоже Вдовы. Выстрелам остальных героев требуется время, чтобы добраться до конечной точки координат.
В Overwatch и уже за углом от выстрела вдовы помирал много раз
Тут еще имеют место быть хитбоксы. В Overwatch они довольно широкие.
У «пули» как правило в шутерах тоже есть ограничение скорости.
Вы живы, вы выстрелили, пуля полетела, соперник жив.
Соперник жив, соперник выстрелил, пуля полетела, вы тоже живы.

Тут «пули» долетают до целей и оба погибают.
Обычно пули наоборот не существуют в физическом мире)
Вместо полета пули выполняется один рейкаст, как бы предполагая что скорость пули бесконечна.
А в батле реализовано в виде пуль из колизии или пускают рэйкаст и считают время и баллистику?:) (Особенно в танках)
Не знаю как для пуль(хоть скорее всего для них рейкаст пускают), а долго летящие проджектайлы, такие как баллистические снаряды, прогнозируются с помощью экстраполяции. Valve например вообще не заморачиваются с лаг компенсацией проджектайлов. пруф
Т.к. они и так не мгновенно летят, а значит каждый человек допускает возможность того, что он промахнулся)
А не подскажете хороший материал по мультиплееру в гонках? особенно о столкновениях?)
Я бы заглянул на Gaffer On Games чтобы про физику прочитать.
Но пока никаких годных материалов специализирующихся на гонках не видел.
Тут все зависит от реализации «пули» в конкретном движке, в том же кс пуля — это инстахит (как его еще называют хитскан), то есть, по факту — вектор, и сервер просто проверяет прохождение вектора через интересующую точку. Могу даже более конкретный пример привести — unreal tournament 2003/2004 — там вся игровая логика написана на джавоподобном unrealscript который легко разбирается в исходники, где конкретно видна такая реализация. В батлфилде же, пуля — это прожектаил который имеет свою скорость и на него действует глобальная физика, кстати, в батлфилде стрельба рассчитывается частично на клиенте, дабы снизить нагрузку с сервера по моделированию всех физических взаимодействий.
Так как на клиенте мы используем интерполяцию для отображения позиций врагов, было бы не вполне корректно применять конкретное состояние сервера, которое якобы отрисовывал клиент, ведь на самом деле игрок стрелял во врага, который находился между двумя какими-то состояниями.
Раз уж мы применяем механизм компенсации лага, с его помощью можно найти два состояния мира, между которыми произошел выстрел и проинтерполировать их.


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


А вот интересно, при довольно сложном мире, где много объектов, которые могут на что-то влиять, как это осуществляют? Меняют ли состояние всех объектов? Или как-то выбирают те, которые могут влиять?
Сервер должен должен интерполировать состояние между кадрами, между которыми интерполирует игрок. Это не вполне одно и то же, так как буфер кадров для интерполяции может быть больше чем на два кадра:)

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

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

Откатывать тоже просто — просто применять по очереди все изменения мира.

Для тех, кто первый раз слышит о дельте — это разность двух состояний.

К примеру, есть состояние А, в котором у игрока П1 100хп, у игрока П2 — 80хп. И вот игрок П2 наносит урон игроку П1 в размере 30единиц.

Теперь состояние мира Б выглядит так:
П1 — 70хп
П2 — 80хп

Дельта двух состояний:
П1 -30хп

Можно отправить состояние Б с данными:
П1 — 70хп
П2 — 80хп

Или отправить дельту А и Б:
П1 -30хп

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

Для огромных миров, например как майнкрафт, это экономит огромное количество трафика (там еще всякие чанки и компрессии)
Верно. Единственная проблема дельта-компрессии в том, что для отображения n-ного состояния мира, нужно получить все состояния от 0 до n, а значит надо посылать состояния через reliable протокол, построенный над udp)
не совсем понял зачем. Для нового игрока на серваке всегда лежит последний n-ый снимок мира как раз для этого случая. А больше вроде бы случаев и нет, разве что откат на несколько состояний назад для просчета физики/etc. Всем остальным игрокам можно слать дельты
Я говорю о том, что до клиента так или иначе должны доходить ВСЕ дельты. Это обязательно, иначе будет расхождение в мире.
Пусть изначальное состояние
X = 0.

Delta 0
X += 1
Delta 1
X -= 1
Delta 2
X += 1

Если, например, Delta1 не дойдет до клиента, у него X будет всегда на единицу больше чем надо.
Значит надо чтобы все дельты дошли. Так как UDP не гарантирует доставки пакетов, а использовать TCP — самоубийство, надо строить reliable протокол над UDP.

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

Что значит «Интерполирует предыдущие состояния других игроков»? Клиент получает положение и вектора других игроков и расчитывает новое положение?

Может быть такая ситуация. Стрелок целится в голову жертвы по версии стрелка. По версии жертвы она успела уйти в сторону. Как будет разруливаться такая ситуация? Тот кто раньше сообщил свое состояние (малый пинг) тот и полбедил?
Что значит «Интерполирует предыдущие состояния других игроков»? Клиент получает положение и вектора других игроков и расчитывает новое положение?

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

Может быть такая ситуация. Стрелок целится в голову жертвы по версии стрелка. По версии жертвы она успела уйти в сторону. Как будет разруливаться такая ситуация? Тот кто раньше сообщил свое состояние (малый пинг) тот и полбедил?

Прочитайте и первую часть тоже. Сервер авторитарный. Что говорит сервер — то и правда.
Сервер для определения попадания откатывает время назад, так что если стрелок с точки зрения его клиента попал, он скорее всего попадет.
В той же Destiny упоминался некий «судья» (точный термин не помню, в голове перевел как «судья» или «модератор»), которого они постоянно там фиксят, что бы сбалансировать game experience игроков с разными пингами. Судя по статье тут такого нет, чисто математика и никакой эвристики или чего то подобного.
Можно предположить что если сервер/клиент занимаются интерполяциями, то большой пинг в «правильные» моменты вполне может быть преимуществом, т.к. «мозгом» становится тоже проще интерполировать. Можно даже чит написать который будет по хоткею какой-то неведомой магией пытаться поднять пинг на 100-200мс.

Это все чисто предположения, но да: по хорошему нужен судья который будет не только откатывать/интерполировать, но и учитывать текущие пинги обоих игроков
Ну да, так и есть. Было в Дестини, сейчас не знаю. У людей с высоким пингом было приемущество. В большинстве случаев они из стычек один-на-один выходили победителями. Были даже читеры, специально лагали, что бы выжить. Стоит такой лагающий, в него все лупят, а ему хоть бы хны. Внезаптно он оживает и все умерли.
Да, неприятная ситуация, когда персонажа убивают, когда ты уже далеко из-за одного снайпера с огромным пингом (играл в TF2). Скажите, а почему иногда, когда бежишь по коридору, то может внезапно телепортировать на то место, где ты был в прошлом: пакеты от клиента не доходят до сервера и при получении состояния от сервера клиент меняет положение на то, что сказал сервер, а не сам клиент предположил на основе интерполяции?
Скорее всего слишком много ваших пакетов потерялось.
Интерполяция — это нахождение промежуточного положения между двумя крайними точками, так что она тут никак не поможет.
А экстраполяция не применяется в шутерах для положений персонажей.
Прочитайте предыдущую часть для более подробного объяснения.
Можно ли получить доступ к демке для исследований? Не нашел ссылку в статьях и спрашиваю с надеждой, что вы просто забыли добавить ее :)
Код демки находится в не самом лучшем состоянии. Так что его выкладывать не будем, а то научится кто-нибудь на свою голову. А вот вопрос о хостинге WebGL примера думаем.
По опыту игры в Q3 через dial-up и пингом 200...400 предполагал именно такое положение дел.
Интересно, какие еще есть приемы для повышения играбельности при высокой и нестабильной задержке.
А вам не хватает указанных техник, при которых игра играбельна даже при пинге в 2 секунды и обновлениях сервера раз в секунду?:)
Или вы имеете в виду альтернативы?
Я такой алгоритм предвидел, ещё когда вы первую часть статьи перевели. И проблема, которую я тогда сформулировал актуальна и в этой реализации. Что если два игрока читят совместно, Петя имеет намеренно большой лаг, Вася — минимальный, между собой у них связь через быструю локальную сеть. Тогда Вася живёт в будущем относительно Пети. И может в прошлое Пети посылать информацию о том, что только будет происходить. А значит Петя будет обладать информацией о будущем, и сможет использовать её для читинга. Например, невидимка нападает на персонажа Васи. Вася передаёт информацию о невидимке в прошлое и Петя реагирует на неё, применяя защитное заклинание на персонажа Васи.

И как будет вести себя сервер, если таких читеров не один и не два, а полсервера? Какие причудливые бои будут разворачиваться.
Да, разумеется так можно обмануть систему. Правда профита на практике немного выйдет. Максимальный пинг, который бы я поддерживал у себя на сервере — секунда. И то кратковременно. Как показывает практика, с пингом 500 играть просто нереально.
Вы успеете понять что надо делать по команде вашего друга за полсекунды? А ведь ваш друг еще должен тратить свой самый ценный ресурс — внимание на то чтобы следить за происходящим вокруг вас. На закуску. Скорость реакции среднего человека на внезапное событие 250 мс. Можете проверить вашу скорость реакции вот тут. Таким образом пока ваш друг отреагирует на событие + пока вы отреагируете на сообщение друга, пройдет в лучшем случае 500 мс. Знакомое число? И это мы считаем, что друг вас уколол иголкой, а не словами сказал.
А еще с пингом 500 играть банально не удобно. Обычно если два игрока отправляют команды на убийства друг друга, в живых остается тот, чья команда дошла до сервера быстрее.
Очевидно, что реагировать будет компьютер. Помните, в стандартный мапхак диаблы включалась функциональность мгновенного дисконнекта при опускание хитов ниже предела? Так и здесь, будет целый программный набор читера. Например, можно транслировать картинку от Васи Пете непрерывно. Вдобавок к перехвату пакетов, которые позволяют видеть всю in-game информацию, потому что редко кто озабочивается, чтобы невидимое для игрока делать невидимым на стороне сервера, а не клиента.

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

Разве? Давайте посчитаем. Пусть Семён играет с реальным лагом в 1 секунду и виртуальным лагом в 5 секунд, а Иван с настоящим лагом в 1 секунду. Петя помогает семёну и тоже имеет лаг в 1 секунду. На 2 секунде Иван атакует Семёна, на третьей секунде команда семёна дошла до сервера и сервер «убил» Семёна. На четвёртой секунде об этом узнал Петя и немедленно уведомил об этом Семёна. Тот тут же послал сигнал «убить Ивана», который дошёл до сервера на 5 секунде. Сервер вычел эмулируемый лаг в 5 секунд и посчитал, что семён стрелял на нулевой секунде, а значит успел первым. Семён оживает, Иван умирает.
Вот всей этой канители с оживаниями никто не делает. Если человек мертв в настоящий момент на сервере, пришедшая команда на выстрел уже не сработает.
Если игрок убит, он не будет воскрешен даже если выяснится, что его убийцу «убили» раньше.

А если рассматривать программные хаки, так гораздо удобнее для реализации предоставленных уязвимостей, скажем, отображать всех врагов вне зависимости от того, видны ли они. Все равно эта информация скорее всего доходит до клиента.
Или классический AutoAim, который по зажатию кнопки будет целиться в голову врага.
А это не имеет никакого отношения к механизму лагокомпенсации:)
> Вот всей этой канители с оживаниями никто не делает.
То есть реальность отличается от изложенной в статье теории? Но допустим, но ведь это нечестно. Для настоящих лагающих, а не фейкающих.
Нет, не отличается. В статье описан механизм компенсации лагов следующим образом:
Сервер оставляет стрелка в том положении, в котором он находится; а весь остальной мир меняет на то состояние, которое клиент отображал в момент выстрела, т.е. возвращает назад во времени.
Сервер обрабатывает выстрел и получает его результат.
Сервер возвращает весь мир в текущее состояние времени.
Сервер применяет результат выстрела и рассылает обновления по клиентам как обычно.

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

Если хотите, можете применять выстрелы в прошлом, флаг вам в руки разбираться с применением ввода остальных игроков, потому что там нехилые временные парадоксы возникнуть могут. И иногда получаются настоящие петли времени, из которых не очень понятно как выходить.
Тоже про AutoAim подумал в процессе чтения статьи. Я правильно понимаю что с такой техникой нарушается авторитарность сервера? Похоже что клиент теперь может сказать серверу: а вот когда враг пробегал через щель, я ему прям в нос выстрелил!
Авторитарность сервера не так-то сильно и нарушается. Для AutoAim не нужна лагокомпенсация. Клиент знает положение врага?
Знает.
Значит может выстрелить ему в нос в реальном времени. Зачем для этого в прошлое возвращаться?
Кс, стоит добавить про откат мира в прошлое сервером — когда играешь с пинггм 90 в кс, то замечаешь такой прикол — можно стрелять не ровно в голову, а рядом, и тебе очень часто засчитывается попадание.
Это значит, что сервер округляет положение игроков и если траектория более-менее попадает под округления — зачет
Я бы грешил на хитбоксы если честно. Ну и еще напомню что в КС есть такое понятие как
разброс
image

Поверьте моему опыту, это не разброс. Это замечаешь сразу, пересаживаешься с пинга 30 на пинг 90. Стиль игры и навыки не меняются за один день, и я абсолютно точно вижу, что стрелял чуть выше головы, сидя и с прицела sg553(b-4-4), а попадание засчитывается. При пинге 30 такого ни разу не случалось.
Естественно, я не говорю про спрей, когда выпускается вся обойма
Забавно. Может быть они действительно увеличивали хитбоксы для игроков с плохим пингом)
Я помню, в КС версии так 1.3 был нечестный прием — можно было искусственно задерживать отправку пакетов до сервера, и при этом, поскольку все выстрелы просчитывались на сервере и сервер «верил» времени и положению, которое прислал клиент, в вас практически невозможно было попасть, поскольку вы на самом деле уже были в другом месте и сервер это учитывал. В Quake 3 вроде бы нужно как раз стрелять туда, где находится соперник у тебя на экране, а в CS было так, что стрелять надо с некоторым «упреждением», именно из-за другого алгоритма расчета попаданий на сервере.
Я гарантирую что в Quake надо стрелять на опережение)
Подскажите, а есть такое явление что в некоторых играх скорость игрока с большим пингом(например 150мс) медленнее чем обычно для игрока с пингом 10мс, тоесть именно плавное замедление а не скачками?
Как применимы ли ваши алгоритмы к компенсации скорости передвижения?
Медленнее?? Это очень неожиданное явление. Сомневаюсь, что это возможно, речь обычно идет о задержке, а не замедлении. Возможно вам начинают посылать пакеты более редко, так что движение визуально получается более гладкое, из-за этого и появляется ощущение замедленности.
Довольно интересная статья. Идет ли речь о «настоящем» откате времени для всего мира или это просто расчет точки попадания на мониторе первого игрока и отправка численного урона убежавшему второму игроку?

Давайте представим ситуацию, когда выстрел не убивает. Первый игрок стреляет и попадает. У второго брызжет кровь, выпадает из рук оружие и он прихрамывая бежит дальше. Допустим, второй игрок в своем времени убежал на полсекунды от места столкновения. Кровь и снаряжение остались позади. Но он не убит (допустим был в шлеме), а его физические характеристики должны были измениться. Спрашивается, с какого момента? Раненный он не мог бежать с той же скоростью и за полсекунды пробегает уже меньше.

Допустим, это не выстрел, а удар в рукопашной с последующей анимацией броска через спину с прогиба. Второго игрока надо телепортировать? «Эй, парень, ты тут уже полсекунды на земле лежишь, нажми кнопку встать!»

Второй игрок имеет щит, которым может на 400мс прикрываться от летящих в него пуль с кулдауном 400мс. Пинг первого 500мс, второго — 0. Что делать?

В общем, каковы ограничения физической модели игры и каковы ограничения отката времени?
Обычно речь идет о применении в настоящем времени, но рассчете в прошлом. Что плохого будет в противоположном случае?
Пусть есть игроки A, B и C. А выстрелил в B, но у него большой пинг. B выстрелил в C, его сигнал мгновенно дошел и игрок C умер, что и отобразилось на клиенте. Потом дошел сигнал от A, игрок B умер в прошлом, а значит C ожил. Выглядит как странный лаг.

Допустим, это не выстрел, а удар в рукопашной с последующей анимацией броска через спину с прогиба. Второго игрока надо телепортировать? «Эй, парень, ты тут уже полсекунды на земле лежишь, нажми кнопку встать!»

Поэтому обычно такое мощное взаимодействие не делают)

Глобально правила для многопользовательских игр такие: на один физически объект(положение и вращение) единовременно может влиять не более 1 игрока.
В противном случае пинг от 50 ощущается просто отвратительно сильно.
«Например если хочется разнести врагу бошку!» — ошибочка, слово «башка» через 'а' пишется. Выделено жирным и бросается в глаза.
Спасибо. Обычно для таких уведомлений используются ЛС. У меня вроде и контакты даны если ридонли не могут пользоваться ЛС на хабре)
Справедливости ради — есть разговорный вариант «разнести бóшку» (с ударением на «о»). Хотя не знаю, на сколько он правилен.
Для уверенности (перед тем как написать) проверил поиском в инете. «Башка́ — разговорное название головы, заимствовано из тюркских языков: ср. Туркменбаши («глава туркмен»), кызылбаши («красноголовые»).»
Вы все правильно скорректировали, я даже поправил статью) Я думаю, единственное за что вам минус влепили — нарушение традиций хабра.
Поиск в инете выдаёт гору страниц с обсуждением этого вопроса, в часности ссылки на словари
В целом мнения расходятся, однако часто пишут, что во множественном, особенно в разговорной речи вполне нормально через О
Смутило только что такой вариант, возможно, пошел из тюремного жаргона.
Интересно было бы узнать мнение автора о мультиплеерной составляющей игры Dark Souls 3.
Так большой пинг несомненно является читом.

Увы, не играл и не разбирал мультиплеер DS3)
Кстати интересный тип мультиплеера — разрабы DS одними из первых стали встраивать мультиплеерные вторжения других игроков в синглеплеер игрока.

Позже подобный тип мультиплеера пришел в ватч догс и мгс 5.

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


Интересно вот что: там многие игровые значения рассчитываются исходя из FPS. Иногда это выглядит забавно, например, оружие при 60 FPS изнашивается куда быстрее, чем при 30. Вы не знаете, зачем так делается, какие плюсы и минусы у такого подхода и т.д.? Интересно было бы почитать.

О_О привязка к фпс звучит очень странно.
Все обычно наоборот стараются абстрагироваться от FPS. А чтобы привязывали износ к FPS — это знатный изврат)
На тему рукопашки. Рукопашный бой — это вообще больная тема. С точки зрения игрока A, A и B находятся рядом, а с точки зрения игрока B, A и B рядом никогда не были. И что тут делать?
Это изначально консольная игра была. Учитывая, что конкретные модели консоли все одинаковые, то FPS вполне себе был константой.
Да, в этом есть логика. Видимо там в принципе время неправильно рассчитывалось.
В первой части DS FPS был фиксированым на 30. По сути все неприятности изначально поправили энтузиасты.

Как уже сказали, игра изначально была консольная, так что железо было одинаковым. При низком ФПС на компе отрубается мультиплеер. Так и пишет: "недостаточно кадровой частоты для игры по сети".


В игре есть всякие моменты касательно расчёта столкновений. Например, враг бьёт мечом, и ты попадаешь в зону, которую этот меч накрывает при ударе. Но можно сделать подкат и избежать урона. т.к. при подкате ты какое-то время неуязвим. В зависимости от различных параметров игрового персонажа, эта неуязвимость может быть от 0.13 до 0.53 секунд (вот тут есть некоторая инфа).
Вот интересно, как всё это обсчитывается с учётом имультиплеера.

Это звучит действительно интересно!
Я на выходных покопаюсь, может найду что-нибудь.
Интересно было бы разобрать такой вопрос:
Что именно подразумевается под задержкой(пингом) от игрока до сервера в статьях и в целом в индустрии. Является ли это число в мc разницей между отправкой и получением ICMP пакета от игрока до сервера, или же всегда подразумевается задержка, с кототорой отправляются UDP пакеты с полезной игровой информацией от игрока до сервера в одну сторону?
Влияет ли как-то количество отображаемых FPS у клиента на получение более свежего состояния мира от сервера до этого клиента?

спасибо! )
В контексте данной статьи корректно было бы использовать время от отправки UDP пакета на сервер до получения ответа от него.
FPS как на клиенте так и на сервере безусловно влияет на получение более свежего состояния. Не сложно посчитать, что в худшем случае пингг возрастает на время равное 1/FPS, причем это верно как для FPS сервера, так и игровой машины.

Теперь я знаю, как это правильно делать, спасибо!

Пожалуйста) Я старался чтобы было максимально понятно)
Но хочу вас разочаровать: я не знаю. И автор не знает. Никто не знает) Потому что правильного варианта вообще нет, есть только грязные приемчики, дающие приемлемый результат)
Sign up to leave a comment.

Articles