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 можно установить четыре вида перехватов:
Пример перехвата отправляемого пакета 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