Введение
RakNet — это сетевой движок, используемый в SA:MP для обмена данными между игроками и сервером. Клиент отправляет пакет серверу, сервер его обрабатывает и рассылает другим игрокам — таким образом это работает.
В этом уроке я покажу, как отправлять свои пакеты и заносить в них данные, как перехватывать отправляемые/получаемые пакеты и читать и перезаписывать данные.
Отправка пакетов
Пример отправки пакета ID_PLAYER_SYNC:
RPC (Remote Procedure Call) — оболочка пакета ID_RPC, предназначенная для удалённого выполнения определенных событий. Все RPC перечислены в RPCEnumeration и ScriptRPCEnumeration.
Пример использования RPC_RequestClass, который запрашивает у сервера сменить наш класс (скин) на сервере:
Перехват пакетов
Средствами API можно установить четыре вида перехватов:
Пример перехвата входящего (пришедшего от сервера) RPC_ScrServerJoin:
Автор: urShadow
RakNet — это сетевой движок, используемый в SA:MP для обмена данными между игроками и сервером. Клиент отправляет пакет серверу, сервер его обрабатывает и рассылает другим игрокам — таким образом это работает.
В этом уроке я покажу, как отправлять свои пакеты и заносить в них данные, как перехватывать отправляемые/получаемые пакеты и читать и перезаписывать данные.
Отправка пакетов
Пример отправки пакета ID_PLAYER_SYNC:
C++:
stOnFootData sync; // объявляем объект структуры stOnFootData, в которой хранятся данные.
memset( &sync, 0, sizeof( stOnFootData ) ); // обнуляем его.
sync = SF->getSAMP()->getPlayers()->pLocalPlayer->onFootData; // копируем данные из структуры локального игрока.
sync.byteHealth = 100; // записываем значение числа жизней ( к примеру ).
BitStream bsActorSync; // объявляем объект класса BitStream, в котором хранятся пакетные данные.
bsActorSync.Write( ( BYTE ) ID_PLAYER_SYNC ); // записываем ID пакета.
bsActorSync.Write( ( PCHAR ) &sync, sizeof( stOnFootData ) ); // записываем данные из структуры sync
SF->getRakNet()->SendPacket( &bsActorSync ); // отправляем пакет на сервер.
Пример использования RPC_RequestClass, который запрашивает у сервера сменить наш класс (скин) на сервере:
C++:
void CALLBACK cmd_setclass( std::string param ) // объявляем чат-команду /setclass <id>
{
BitStream bsClass; // объявляем объект класса BitStream, в котором хранятся пакетные данные.
bsClass.Write( std::stoi( param ) ); // записываем в него ID переданный в команду.
SF->getRakNet()->SendRPC( RPC_RequestClass, &bsClass ); // отправляем RPC
};
C++:
SF->getSAMP()->registerChatCommand( "setclass", cmd_setclass ); // регистрируем команду
Средствами API можно установить четыре вида перехватов:
- RAKHOOK_TYPE_OUTCOMING_RPC — RPC, отправленный серверу;
- RAKHOOK_TYPE_OUTCOMING_PACKET — пакет, отправленный серверу;
- RAKHOOK_TYPE_INCOMING_RPC — RPC, пришедший от сервера;
- RAKHOOK_TYPE_INCOMING_PACKET — пакет, пришедший от сервера.
C++:
bool CALLBACK outcomingData( stRakNetHookParams *params ) // определение callback-функции, которая будет вызвана при отправке какого либо пакета
{
if( params->packetId == PacketEnumeration::ID_PLAYER_SYNC ) // если отправляемый пакет — это ID_PLAYER_SYNC
{
stOnFootData data; // определяем объект, в который сохраним отправляемые данные
memset( &data, 0, sizeof( stOnFootData ) ); // обнуляем его
byte packet;
params->bitStream->ResetReadPointer(); // на всякий случай устанавливаем оффсет чтения на начало
params->bitStream->Read( packet ); // читаем ID пакета
params->bitStream->Read( (PCHAR)&data, sizeof( stOnFootData ) ); // читаем отправляемые данные
params->bitStream->ResetReadPointer(); // снова обнуляем оффсет чтения
SF->getSAMP()->getChat()->AddChatMessage( D3DCOLOR_XRGB( 255, 255, 0 ), "Наша скорость: %.2f %.2f %.2f",
data.fMoveSpeed[0], data.fMoveSpeed[1], data.fMoveSpeed[2] ); // пишем в чат скорость нашего передвижения, записанную в пакет
data.fMoveSpeed[0] = rand()%10;
data.fMoveSpeed[1] = rand()%10;
data.fMoveSpeed[2] = rand()%10; // перезаписали скорость на случайную; получается эффект, похожий на Pizdarvanka.
params->bitStream->ResetWritePointer(); // обнуляем оффсет записи
params->bitStream->Write( packet ); // пишем ид пакета
params->bitStream->Write( (PCHAR)&data, sizeof( stOnFootData ) ); // пишем обновлённые данные
};
return true; // успешно завершаем отправку пакета
};
SF->getRakNet()->registerRakNetCallback( RakNetScriptHookType::RAKHOOK_TYPE_OUTCOMING_PACKET, outcomingData ); // регистрируем callback
C++:
bool CALLBACK incomingRPC( stRakNetHookParams *params ) // определение callback-функции, которая будет вызвана, если от сервера был получен новый RPC.
{
if( params->packetId == ScriptRPCEnumeration::RPC_ScrServerJoin ) // если это RPC_ScrServerJoin
{
short int sPlayerID;
D3DCOLOR D3DPlayerColor;
byte isNPC, nameLen;
char szPlayerName[25];
params->bitStream->ResetReadPointer(); // обнуляем оффсет чтения.
params->bitStream->Read( sPlayerID ); // читаем ID игрока.
params->bitStream->Read( D3DPlayerColor ); // цвет ника игрока.
params->bitStream->Read( isNPC ); // флаг, говорящий о том, NPC это или нет.
params->bitStream->Read( nameLen ); // длина ника.
params->bitStream->Read( szPlayerName, nameLen ); // ник.
szPlayerName[ nameLen ]= '\0'; // обрезаем, чтоб не было мусора
params->bitStream->ResetReadPointer(); // обнуляем оффсет чтения
SF->getSAMP()->getChat()->AddChatMessage( D3DPlayerColor, "%s[%d] Подключился к серверу.",
szPlayerName, sPlayerID ); // добавляем сообщение в чат.
};
return true; // успешно завершаем обработку RPC.
};
SF->getRakNet()->registerRakNetCallback( RakNetScriptHookType::RAKHOOK_TYPE_INCOMING_RPC, incomingRPC ); // регистрируем callback