Всем привет.
Наткнулся я на эту довольно-таки древнюю статью на официальном форуме и решил перевести. В общем, надеюсь, данная статья поможет новичкам лучше узнать, как работает синхронизация в SA:MP.
Поехали...
Я заметил, что куча багов и проблем возникает у скриптеров из-за отсутствия знаний в этой области. Надеюсь, у меня получится разъяснить для вас некоторые вещи в этой статье.
Содержание:
- Скрипт выполняется в одном потоке.
- Обновление данных об игроках.
- Влияние игрока на функции сервера.
- Синхронизация клавиш.
- Потеря пакетов.
- Смешивание пакетов.
Скрипт выполняется в одном потоке.
Для некоторых это может показаться сложным, поэтому позвольте объяснить: любое действие на сервере происходит только после завершения предыдущего действия.
К примеру, если у вас есть код, который выполняется за 3 секунды в OnPlayerConnect (одна из старых версий плагина GeoIP, например), то сервер будет ждать, пока выполнится этот код, прежде чем начать выполнять что-либо ещё.
Что может занять много времени:- Работа с длинными строками.
- Перебор игроков и выполнение действий над ними.
- Чтение и запись информации в большое количество файлов.
Серверу придётся ждать, пока выполнятся все эти действия, поэтому старайтесь делать ваш код как можно более производительным.
Преимущества однопоточности:
Преимущество работы сервера в одном потоке (на мой взгляд) - полный контроль над потоком информации на сервере. Вы всегда будете обрабатывать каждое событие на сервере, как только оно произойдёт. Если бы сервер был многопоточным, то информация об игроке могла бы обновиться во время выполнения вашего кода. И, если честно, я не думаю, что так было бы лучше.[/INDENT]
Обновление данных об игроках.
Когда любой из этих параметров изменяется, клиент будет отправлять информацию на сервер и сервер будет вызывать OnPlayerUpdate:
- Здоровье / броня.
- HP автомобиля / повреждения / цвет / тюнинг.
- Смерть.
- Скорость.
- Позиция / поворот.
- Анимация.
- Нажатые клавиши.
- Оружие / боеприпасы.
- Положение камеры (при наведении, съемке, если такого действия нет, обновляется раз в 2 секунды).
С помощью скриптинга можно отследить практически все изменения при вызове OnPlayerUpdate.
Что происходит, когда игрок перемещает камеру, начинает двигаться или нажимает на клавиши?
- Если игрок целился или стрелял, угол поворота камеры и поворота игрока будут отправлены в этом же обновлении и будет вызван коллбэк OnPlayerUpdate. Если же игрок не целился и не стрелял, OnPlayerUpdate может и не вызваться.
- OnPlayerUpdate вызывается при нажатии кнопок. OnPlayerKeyStateChange не вызывается, если нажаты клавиши ходьбы/движения (вправо/влево/вперед/назад). Анимация игрока будет обновлена до вызова OnPlayerUpdate, если она была изменена мгновенно.
Примечание: Я не уверен на все 100%, что анимация обновляется в тот же момент, что и статус нажатия клавиш.
ID, возвращаемый функцией GetPlayerAnimationIndex, меняется так же мгновенно, как и сама анимация обновляется для клиента. Например, когда вы начинаете бегать, анимация будет изменена в тот момент, когда вы нажмете клавишу (хоть вы и можете не заметить этого сразу).
Что произойдёт, если вернуть 0 в OnPlayerUpdate?
OnPlayerUpdate вызывается до того как сервер посылает обновлённую информацию игрокам. Если в этой функции вернуть 0 (как из фильтрскрипта, так и из мода), обновлённая информация просто не будет передана.
Хороший пример:
Этот код не позволит игроку перемещаться/стрелять, когда он заморожен. Больше не будет "призрачной стрельбы", или как вы там это называли.
PHP код:
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.
PHP код:
public OnPlayerUpdate( playerid )
{
if ( GetPlayerWeapon( playerid ) == WEAPON_MINIGUN )
Ban( playerid );
}
Лучше сделать так:
PHP код:
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, Daniel_Cortez
Отдельное спасибо DeimoS за помощь.