API SF | Урок 4 - RakNet

RakNet — это сетевой движок, используемый в SA:MP для обмена данными между игроками и сервером. Клиент отправляет пакет серверу, сервер его обрабатывает и рассылает другим игрокам — таким образом это работает.

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

Пример отправки пакета ID_PLAYER_SYNC:

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 (Remote Procedure Call) — оболочка пакета ID_RPC, предназначенная для удалённого выполнения определенных событий. Все RPC перечислены в RPCEnumeration и ScriptRPCEnumeration.

Пример использования RPC_RequestClass, который запрашивает у сервера сменить наш класс (скин) на сервере:

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
};
 
SF->getSAMP()->registerChatCommand( "setclass", cmd_setclass );	// регистрируем команду

Средствами API можно установить четыре вида перехватов:

  1. RAKHOOK_TYPE_OUTCOMING_RPC — RPC, отправленный серверу;
  2. RAKHOOK_TYPE_OUTCOMING_PACKET — пакет, отправленный серверу;
  3. RAKHOOK_TYPE_INCOMING_RPC — RPC, пришедший от сервера;
  4. RAKHOOK_TYPE_INCOMING_PACKET — пакет, пришедший от сервера.

Пример перехвата отправляемого пакета ID_PLAYER_SYNC:

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

Пример перехвата входящего (пришедшего от сервера) RPC_ScrServerJoin:

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

by urShadow