PDA

Просмотр полной версии : [Урок] Синхронизация в SA:MP (Перевод)



wAx
15.05.2014, 15:59
Всем привет.
Наткнулся я на эту довольно-таки древнюю статью (http://forum.sa-mp.com/showpost.php?p=876854) на официальном форуме и решил перевести. В общем, надеюсь, данная статья поможет новичкам лучше узнать, как работает синхронизация в SA:MP.
Поехали...


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

Содержание:

Скрипт выполняется в одном потоке.
Обновление данных об игроках.
Влияние игрока на функции сервера.
Синхронизация клавиш.
Потеря пакетов.
Смешивание пакетов.


Скрипт выполняется в одном потоке.
Для некоторых это может показаться сложным, поэтому позвольте объяснить: любое действие на сервере происходит только после завершения предыдущего действия.
К примеру, если у вас есть код, который выполняется за 3 секунды в OnPlayerConnect (одна из старых версий плагина GeoIP, например), то сервер будет ждать, пока выполнится этот код, прежде чем начать выполнять что-либо ещё.

Что может занять много времени:
Работа с длинными строками.
Перебор игроков и выполнение действий над ними.
Чтение и запись информации в большое количество файлов.

Серверу придётся ждать, пока выполнятся все эти действия, поэтому старайтесь делать ваш код как можно более производительным.

Преимущества однопоточности:
Преимущество работы сервера в одном потоке (на мой взгляд) - полный контроль над потоком информации на сервере. Вы всегда будете обрабатывать каждое событие на сервере, как только оно произойдёт. Если бы сервер был многопоточным, то информация об игроке могла бы обновиться во время выполнения вашего кода. И, если честно, я не думаю, что так было бы лучше.
[/INDENT]


Обновление данных об игроках.
Когда любой из этих параметров изменяется, клиент будет отправлять информацию на сервер и сервер будет вызывать OnPlayerUpdate:

Здоровье / броня.
HP автомобиля / повреждения / цвет / тюнинг.
Смерть.
Скорость.
Позиция / поворот.
Анимация.
Нажатые клавиши.
Оружие / боеприпасы.
Положение камеры (при наведении, съемке, если такого действия нет, обновляется раз в 2 секунды).

С помощью скриптинга можно отследить практически все изменения при вызове OnPlayerUpdate.

Что происходит, когда игрок перемещает камеру, начинает двигаться или нажимает на клавиши?

Если игрок целился или стрелял, угол поворота камеры и поворота игрока будут отправлены в этом же обновлении и будет вызван коллбэк OnPlayerUpdate. Если же игрок не целился и не стрелял, OnPlayerUpdate может и не вызваться.
OnPlayerUpdate вызывается при нажатии кнопок. OnPlayerKeyStateChange не вызывается, если нажаты клавиши ходьбы/движения (вправо/влево/вперед/назад). Анимация игрока будет обновлена до вызова OnPlayerUpdate, если она была изменена мгновенно.

Примечание: Я не уверен на все 100%, что анимация обновляется в тот же момент, что и статус нажатия клавиш.

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

Что произойдёт, если вернуть 0 в OnPlayerUpdate?
OnPlayerUpdate вызывается до того как сервер посылает обновлённую информацию игрокам. Если в этой функции вернуть 0 (как из фильтрскрипта, так и из мода), обновлённая информация просто не будет передана.

Хороший пример:
Этот код не позволит игроку перемещаться/стрелять, когда он заморожен. Больше не будет "призрачной стрельбы", или как вы там это называли.


public OnPlayerUpdate( playerid )
{
static // Я использую здесь статистические переменные, чтобы они не создавались каждый раз заново
s_Keys,
s_UpDown,
s_LeftRight
;

GetPlayerKeys( playerid, s_Keys, s_UpDown, s_LeftRight ); // Получаем нажатые в настоящее время клавиши

if ( g_IsPlayerFroze[ playerid ] && ( s_Keys || s_UpDown || s_LeftRight ) ) // Если любые клавиши нажаты, не синхронизируем данные
return 0;

return 1;
}

// Используем 2 функции для заморозки/разморозки игрока

stock FreezePlayer( playerid )
{
g_IsPlayerFroze[ playerid ] = true; // Теперь сервер запомнит игрока, как "замороженного"

TogglePlayerControllable( playerid, false );
}

stock UnfreezePlayer( playerid )
{
g_IsPlayerFroze[ playerid ] = false; // То же самое, но наоборот

TogglePlayerControllable( playerid, true );
}



Плохой пример:

Ниже в конце функции нет return 1, из-за чего ни один игрок не будет синхронизироваться.
Примечание (DC): если в функции не возвращать никаких значений, она по умолчанию вернёт 0.


public OnPlayerUpdate( playerid )
{
if ( GetPlayerWeapon( playerid ) == WEAPON_MINIGUN )
Ban( playerid );
}

Лучше сделать так:


public OnPlayerUpdate( playerid )
{
if ( GetPlayerWeapon( playerid ) == WEAPON_MINIGUN )
Ban( playerid );

return 1;
}



Функции сервера, влияюшие на игрока
Такие функции, как SetPlayerHealth, GivePlayerMoney, PutPlayerInVehicle и т.д., передают данные с сервера только тому игроку, которого они затрагивают (за исключением SetPlayerVirtualWorld).

Что же это значит?
К примеру, если у игрока 73 ХП и вы используете SetPlayerHealth для установки здоровья в 100 ХП, происходит следующее:
SetPlayerHealth(playerid, 100.0) -> сервер отправляет игроку сообщение о том, что у него должно обновиться ХП.
Клиент на данный момент приостановлен, поэтому сообщение отправляется в "очередь". Как только клиент продолжит работу, он обработает все сообщения из очереди в порядке их поступления.
Таймер в моде использует GetPlayerHealth на игроке и получает 73.
Теперь клиент возобновлен и все запросы в очереди обрабатываются.
ХП изменено для клиента.
Обновление отправлено на сервер.
Вызвана функция OnPlayerUpdate.
Информация о новом количестве ХП отправлена другим игрокам на сервере, теперь они тоже видят у того игрока 100 ХП.


Синхронизация клавиш

SA:MP синхронизируется путем получения статуса нажатия ваших клаквиш и отправки этого статуса другим клиентам. Если вы нажмете клавиши прицела и стрельбы, данные об этом получат и другие клиенты. Это означает, что, даже если вы стреляете на вашем экране, вы можете стоять перед разсинхронизированной машиной или быть зарезаным/убитым от взрыва автомобиля на экранах других клиентов. Это называется рассинхронизацией.

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

Отправке данных о нажатии клавиш на сервер
Некоторые могут думать, что если нажать клавишу очень быстро, сервер не получит данные о нажатии. Ошибаетесь! Например, если нажать и отпустить клавишу "огонь", сервер получит 2 обновления: вы нажали клавишу и вы отпустили клавишу.


Потеря пакетов
Иногда, когда клиент отправляет обновленные данные на сервер, они теряются. Это может привести к ошибкам и проблемам в скриптах, поэтому нужно быть готовым к потере пакетов!

Как же избежать потери пакетов вызванную ошибкой в скрипте?

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

Игрок 1 имеет 50 брони.
Сервер оснащен супер-новым античитом на броню, основанным на обновлениях игрока!
Античит сработает, если броня игрока изменилась, но деньги его остались прежними (т.е. он не купил её в магазине оружия).


Игрок 1 идет в магазин оружия. Сервер узнает это из-за вызова OnPlayerInteriorChange.
Игрок покупает новую броню, отправляет на сервер обновлённую информацию и платит $240.
Игрок отправляет пакет с информацией на сервер; пакет потерян!
Игрок 1 начинает передвигаться, тем самым отправляя на сервер данные о позиции, но сервер понятия не имеет о броне и о $240.
В игрока 1 стреляют, на сервер отправляются данные о новой броне, но данные о $240 - утеряны.
Античит всё это видит и банит игрока по причине чита на броню.


Предотвращаем бан по ошибке:
Сообщаем админам и начисляем игроку +1 к подозрению в читерстве. Если таких подозрений будет более 3, наказываем.
Даем игроку $1 и ждем новых данных о деньгах игрока. Возможно также снять броню и вернуть ее, только если новые данные о деньгах будут получены в следующих 1-3 обновлениях.


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

Пример такого сценария:
Игрок 1 имеет 85 брони.
Сервер оснащен супер-новым античитом на броню, основанным на обновлениях игрока!
Античит сработает, если броня игрока изменилась, но деньги его остались прежними (т.е. он не купил её в магазине оружия).


Игрок 1 сражается с кем-то.
Игрок 1 дважды получает урон, сначала на 10 hp, потом на 20 hp. Два пакета данных с обновленной броней отправляются на сервер.
Пакеты перемешаны. Сервер сначала получает данные о том, что у игрока есть 55 брони (85 - 10 - 20).
Первый пакет приходит на сервер после второго, там броня игрока - 75.
Сервер думает, что игрок читом восстановил броню и банит его.


Предотвращаем бан по ошибке:
Сообщаем админам и начисляем игроку +1 к подозрению в читерстве. Если таких подозрений будет более 3, наказываем.
Не применяйте наказание в ближайшие 2-3 обновления, только делайте предупреждения, как в предыдущем пункте.
Принимайте меры только если у игрока 100 или более ед. брони.




В заключение, хотелось бы добавить от себя. Данную статью мы с DeimoS перевели специально для того, чтобы расширить ваши общие знания о синхронизации в SA:MP. Несмотря на то, что статья довольно старая, в ней очень много полезной информации. Ну и, конечно, если у вас есть что добавить, можете смело предлагать!


Оригинал: http://forum.sa-mp.com/showthread.php?t=184118
Автор статьи: Slice
Перевод: wAx (http://pro-pawn.ru/member.php?2319), Daniel_Cortez (http://pro-pawn.ru/member.php?100)
Отдельное спасибо DeimoS (http://pro-pawn.ru/member.php?2548) за помощь.

wAx
16.05.2014, 13:28
обновил

Caypen
20.05.2014, 13:45
Предотвращаем это:

- Сообщаем админам, и начисляем игроку +1 к подозрению в читерстве. Если таких подозрений будет более 3, наказываем.
- Не применяйте наказание в ближайшие 2-3 обновления.
- Примените наказание, только в том случае если броня >= 100;

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

vovandolg
11.08.2016, 00:13
if ( g_IsPlayerFroze[ playerid ] && ( s_Keys || s_UpDown || s_LeftRight ) ) // Если любые клавиши нажаты, не синхронизируем данные
return 0;
Я вот тут момента не понял, статус заморозки в проверке лишний?
Ибо нам надо проверять клавиши, а не клавиши+статус нажатия так ведь?
Когда игрок заморожен он и так не че не сделает поэтому и синхрон не нужен там..:scratch_one-s_head:не?
_________________
Так ведь?

if (!s_Keys && !s_UpDown && !s_LeftRight) return 0;

Daniel_Cortez
11.08.2016, 20:25
Провёл ревизию статьи, исправил ошибки и неточности. Кое-где даже были пропущены отрывки из оригинала: например, о том, что может занимать много времени на выполнение, или список параметров, при изменении которых клиент отправит на сервер информацию об обновлении. Эти отрывки я тоже вернул.
Изменений реально много, поэтому если вы читали эту статью ранее, советую на всякий случай прочесть её заново.




Я вот тут момента не понял, статус заморозки в проверке лишний?
Ибо нам надо проверять клавиши, а не клавиши+статус нажатия так ведь?

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

vovandolg
11.08.2016, 21:23
Провёл ревизию статьи, исправил ошибки и неточности. Кое-где даже были пропущены отрывки из оригинала: например, о том, что может занимать много времени на выполнение, или список параметров, при изменении которых клиент отправит на сервер информацию об обновлении. Эти отрывки я тоже вернул.
Изменений реально много, поэтому советую на всякий случай прочесть статью заново.



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

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

- - - Добавлено - - -

Очепятка кстате)


Обновление данных об игроках.
[INDENT]Когда любой из этих параметров изменяется

Elrmrnt-Kritik
05.04.2018, 01:04
То есть, когда срабатывает OnPlayerUpdate, изменения в игроке (например, стало 50 хп) обновляются для других игроков? Таким образом, OnPlayerUpdate обновляет информацию об игроке для других игроков?


Игрок 1 идет в магазин оружия. Сервер узнает это из-за вызова OnPlayerInteriorChange.
Игрок покупает новую броню, отправляет на сервер обновлённую информацию и платит $240.
Игрок отправляет пакет с информацией на сервер; пакет потерян!
Игрок 1 начинает передвигаться, тем самым отправляя на сервер данные о позиции, но сервер понятия не имеет о броне и о $240.
В игрока 1 стреляют, на сервер отправляются данные о новой броне, но данные о $240 - утеряны.
Античит всё это видит и банит игрока по причине чита на броню.

И этот пример какой-то неудачный. Можно, пожалуйста, его разжевать?

Да и смешивание пакетов тоже так себе описано. Вроде смешались, а почему - кто его знает... Потому что пакеты что ли однотипные (изменение одного и того же параметра)?

DeimoS
05.04.2018, 08:45
То есть, когда срабатывает OnPlayerUpdate, изменения в игроке (например, стало 50 хп) обновляются для других игроков? Таким образом, OnPlayerUpdate обновляет информацию об игроке для других игроков?

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




И этот пример какой-то неудачный. Можно, пожалуйста, его разжевать?

Что именно тут не понятно?


Да и смешивание пакетов тоже так себе описано. Вроде смешались, а почему - кто его знает... Потому что пакеты что ли однотипные (изменение одного и того же параметра)?

Нет, причин может быть много, но основные:
1) Плохой интернет-канал, когда игрок поочереди отправил 2 разных пакета, но один где-то затерялся из-за нагруженности канала провайдера игрока, а второй прошёл нормально. Если первый пакет так и не дойдёт до сервера, то это будет потеря пакетов. Если же дойдёт - смешивание.
2) Игрок намеренно смешивает пакеты, дабы получить какой-то профит от этого

Elrmrnt-Kritik
05.04.2018, 10:23
Ну, купил игрок броню, отправил запрос (пакет) на обновление денежных средств и брони, а пакет-то куда делся?

Nexius_Tailer
05.04.2018, 11:16
Ну, купил игрок броню, отправил запрос (пакет) на обновление денежных средств и брони, а пакет-то куда делся?
Не дошёл. Нужно ж понимать, что передача данных по интернету имеет как задержку (пинг), так и иногда плохое качество связи, из-за которого какие-то данные не доходят. Хз что может быть хорошим примером в этом плане, но попробуй поиграть где-нибудь на очень забитом вайфае в интернет-кафе, я думаю там будет хорошо ощущаться и первое и второе.

DeimoS
05.04.2018, 11:32
https://goodgame.ru/news/26291/
https://goodgame.ru/news/26336/

Вот тебе две статьи касаемо работы мультиплеерных игр, в которых объясняются основы. При желании, можешь нагуглить ещё

Bib
05.04.2018, 15:24
Нужно еще добавить то, что не все пакеты смешиваются и теряются. RakNet имеет несколько уровней надежности отправки пакетов.

Daniel_Cortez
06.04.2018, 22:03
Нужно еще добавить то, что не все пакеты смешиваются и теряются. RakNet имеет несколько уровней надежности отправки пакетов.
Во-первых, это перевод, а не оригинальная статья.
Во-вторых, чтобы точно знать, где какой уровень надёжности, нужно иметь доступ к исходникам, так что большее, что можно было бы добавить в статью - небольшое примечание о существовании этих самых уровней надёжности.