DigitalHax Posted January 20, 2013 Share Posted January 20, 2013 Hey everyone. I have some problems with Raknet, specifically the bitstream, and I thought someone might be able to help, as I know some people have a lot of experience with raknet. The problem I am experiencing is that when I spawn a player on the client before the server, the client player movement doesn't update on the server until the server player is spawned. And by then the players are out of sync. These are my Files //============================================================================= /** * \file Client.cpp * * \brief This file contains all the functions required by the Client class defined in Client.h. Most of these functions are public and they assist in ways such as, creating a network interface, settin up, starting up, connecting and sending & receiving packets. * * \author Kenneth Claassen * * \date 2011-08-05: * * \todo * * \bug * * \version 1.0 ******************************************************************************/ #include "Client.h" #include "CustomNetworkEnums.h" #define DEBUG_BUILD Client::Client(int maxPlay) { maxPlayers = maxPlay; serverPort = "6001"; numPlayers = 0; spawned = false; netLoaded = false; } void Client::CreateClientInterface() { client = RakNet::RakPeerInterface::GetInstance(); if(!client) { MessageBoxA(0,"Error N02: Failed to Create Client Interface", "Error N02:",0); } RakNet::SystemAddress clientID = RakNet::UNASSIGNED_SYSTEM_ADDRESS; } void Client::SetIP() { serverIP = "127.0.0.1"; clientPort = "6000"; } void Client::ClientStartup() { socketDescriptor = RakNet::SocketDescriptor(atoi(clientPort.c_str()),0); socketDescriptor.socketFamily = AF_INET; //an IPV4 internet Protocol client->Startup(8, &socketDescriptor, 1); client->SetOccasionalPing(true); RakNet::ConnectionAttemptResult car = client->Connect(serverIP.c_str(), atoi(serverPort.c_str()),"yoyo", (int) strlen("yoyo")); RakAssert(car==RakNet::CONNECTION_ATTEMPT_STARTED); } void Client::UpdateNetworkKeyboard(){ if(netLoaded==true && spawned==true){ //Moving if(KeyDown(KEY_W)){ player[controlPlayer]->move=3; } else if(KeyDown(KEY_S)){ player[controlPlayer]->move = -3; } else { player[controlPlayer]->move = 0; } //Strafing if(KeyDown(KEY_D)){ player[controlPlayer]->strafe = 1; } else if(KeyDown(KEY_A)){ player[controlPlayer]->strafe = -1; } else { player[controlPlayer]->strafe = 0; } if(MouseDown(1)){ //Camera looking mx=Curve(MouseX()-GraphicsWidth()/2,mx,6); my=Curve(MouseY()-GraphicsHeight()/2,my,6); MoveMouse(GraphicsWidth()/2,GraphicsHeight()/2); camrotation.X=camrotation.X+my/10.0; player[controlPlayer]->turn=player[controlPlayer]->turn-mx/10.0; } player[controlPlayer]->jump=0.0; if(KeyHit(KEY_SPACE)){ player[controlPlayer]->jump=4; } if(player[controlPlayer]->move != player[controlPlayer]->moveCheck){ RakNet::BitStream bsOut; typeID = ID_PLAYER_MOVE; bsOut.Write(typeID); bsOut.Write(player[controlPlayer]->move); bsOut.Write(player[controlPlayer]->GetNetworkID()); client->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); } if(player[controlPlayer]->strafe != player[controlPlayer]->strafeCheck){ RakNet::BitStream bsOut; typeID = ID_PLAYER_STRAFE; bsOut.Write(typeID); bsOut.Write(player[controlPlayer]->strafe); bsOut.Write(player[controlPlayer]->GetNetworkID()); client->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); } if(player[controlPlayer]->turn != player[controlPlayer]->turnCheck){ RakNet::BitStream bsOut; typeID = ID_PLAYER_TURN; bsOut.Write(typeID); bsOut.Write(player[controlPlayer]->turn); bsOut.Write(player[controlPlayer]->GetNetworkID()); client->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); } if(player[controlPlayer]->jump != player[controlPlayer]->jumpCheck){ RakNet::BitStream bsOut; typeID = ID_PLAYER_JUMP; bsOut.Write(typeID); bsOut.Write(player[controlPlayer]->jump); bsOut.Write(player[controlPlayer]->GetNetworkID()); client->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); } player[controlPlayer]->moveCheck = player[controlPlayer]->move; player[controlPlayer]->strafeCheck = player[controlPlayer]->strafe; player[controlPlayer]->turnCheck = player[controlPlayer]->turn; player[controlPlayer]->jumpCheck = player[controlPlayer]->jump; } for(int i=0; i<numPlayers; i++){ player[i]->UpdatePlayer(); } } void Client::ReceivePackets() { for(p=client->Receive(); p; client->DeallocatePacket(p), p=client->Receive()) { packetIdentifier = GetPacketIdentifier(p); RakNet::BitStream bsIn(p->data, p->length, false); RakNet::BitStream bsOut; switch (packetIdentifier) { case ID_DISCONNECTION_NOTIFICATION: //connection lost normally printf("ID_DISCONNECTION_NOTIFICATION from %s\n", p->systemAddress.ToString(true));; break; case ID_ALREADY_CONNECTED: printf("ID_ALREADY_CONNECTED\n"); break; case ID_NEW_INCOMING_CONNECTION: //Somebody connected. We have their IP now printf("ID_NEW_INCOMING_CONNECTION from %s with GUID %s\n", p->systemAddress.ToString(true), p->guid.ToString()); break; case ID_INCOMPATIBLE_PROTOCOL_VERSION: printf("ID_INCOMPATIBLE_PROTOCOL_VERSION\n"); break; case ID_REMOTE_DISCONNECTION_NOTIFICATION: printf("ID_REMOTE_DISCONNECTION_NOTIFICATION\n"); break; case ID_REMOTE_NEW_INCOMING_CONNECTION: printf("ID_REMOTE_NEW_INCOMING_CONNECTION\n"); break; case ID_CONNECTION_BANNED: printf("You are banned from this server\n"); break; case ID_CONNECTION_ATTEMPT_FAILED: printf("Connection attempt failed\n"); break; case ID_CONNECTION_LOST: printf("ID_CONNECTION LOST from %s\n", p->systemAddress.ToString(true));; break; case ID_NO_FREE_INCOMING_CONNECTIONS: printf("Sorry, the server is full\n"); break; case ID_INVALID_PASSWORD: printf("Access Denied: Inccorrect client password\n"); break; case ID_CONNECTION_REQUEST_ACCEPTED: printf("Your connection has been accepted to %s with GUID %s\n", p->systemAddress.ToString(true), p->guid.ToString()); break; case ID_LOAD_CURRENT_PLAYERS: TVec3 pos; TVec3 rot; TVec3 vel; TVec3 omg; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(numPlayers); for(int i=0; i<numPlayers; i++){ bsIn.Read(pos.X); bsIn.Read(pos.Y); bsIn.Read(pos.Z); bsIn.Read(rot.X); bsIn.Read(rot.Y); bsIn.Read(rot.Z); bsIn.Read(vel.X); bsIn.Read(vel.Y); bsIn.Read(vel.Z); bsIn.Read(omg.X); bsIn.Read(omg.Y); bsIn.Read(omg.Z); bsIn.Read(playerNetworkID); player[i] = new Player(pos, rot); player[i]->SetNetworkIDManager(&networkIDManager); player[i]->SetNetworkID(playerNetworkID); SetBodyVelocity(player[i]->pBody, vel); SetBodyOmega(player[i]->pBody, omg); } netLoaded = true; break; case ID_SPAWN_PLAYER: if(netLoaded==true){ TVec3 playerPos; TVec3 playerRot; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(playerPos.X); bsIn.Read(playerPos.Y); bsIn.Read(playerPos.Z); bsIn.Read(playerRot.X); bsIn.Read(playerRot.Y); bsIn.Read(playerRot.Z); bsIn.Read(playerNetworkID); player[numPlayers] = new Player(playerPos, playerRot); player[numPlayers]->SetNetworkIDManager(&networkIDManager); player[numPlayers]->SetNetworkID(playerNetworkID); numPlayers++; } break; case ID_PLAYER_MOVE: if(netLoaded==true){ int move; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(move); bsIn.Read(playerNetworkID); networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID)->move = move; } break; case ID_PLAYER_STRAFE: if(netLoaded==true){ int strafe; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(strafe); bsIn.Read(playerNetworkID); networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID)->strafe = strafe; } break; case ID_PLAYER_TURN: if(netLoaded==true){ int turn; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(turn); bsIn.Read(playerNetworkID); networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID)->turn = turn; } break; case ID_PLAYER_JUMP: if(netLoaded==true){ int jump; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(jump); bsIn.Read(playerNetworkID); networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID)->jump = jump; } break; default: //For sending chat messages break; } } } unsigned char Client::GetPacketIdentifier(RakNet::Packet *p) { if(p==0) return 255; if((unsigned char)p->data[0] == ID_TIMESTAMP) { RakAssert(p->length > sizeof(RakNet::MessageID) + sizeof(RakNet::Time)); return (unsigned char) p->data[sizeof(RakNet::MessageID) + sizeof(RakNet::Time)]; } else return (unsigned char) p->data[0]; } void Client::ShutDownClient() { client->Shutdown(300); RakNet::RakPeerInterface::DestroyInstance(client); } void Client::SpawnPlayer(){ if(spawned==false){ player[numPlayers] = new Player(Vec3(0,0,0), Vec3(0,0,0)); typeID = ID_SPAWN_PLAYER; player[numPlayers]->SetNetworkIDManager(&networkIDManager); playerNetworkID = player[numPlayers]->GetNetworkID(); assert(networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID) == player[numPlayers]); RakNet::BitStream bsOut; bsOut.Write(typeID); bsOut.Write(0); bsOut.Write(0); bsOut.Write(0); bsOut.Write(0); bsOut.Write(0); bsOut.Write(0); bsOut.Write(player[numPlayers]->GetNetworkID()); client->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); controlPlayer = numPlayers; numPlayers++; spawned = true; } } //============================================================================= /** * \file Server.cpp * * \brief This file contains all the functions for the server class which uses RakNet to convey information on a server specified by the user for a game. * * \author Kenneth Claassen * * \date 2011-08-05: * * \todo * * \bug * * \version 1.0 ******************************************************************************/ #include "Server.h" #include "CustomNetworkEnums.h" #define MAX_PLAYERS 10 //========================================================================= /** \brief Initiates an instance of Server object class (Constructor) * * N/A * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ Server::Server(int maxPlay) { numSockets = 1; numIPs = 1; numPlayers = 0; maxPlayers = maxPlay; serverPort = "6001"; spawned = false; } //========================================================================= /** \brief Creates a RakNet networking interface * * This function creates a RakNet networking interface for a server. This is the first step of networking with raknet. We need the networking interface to call RakNet functions from. * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ void Server::CreateServerInterface() { server = RakNet::RakPeerInterface::GetInstance(); server->SetIncomingPassword("yoyo", (int) strlen("yoyo")); server->SetTimeoutTime(30000, RakNet::UNASSIGNED_SYSTEM_ADDRESS); if(!server) { MessageBoxA(0,"Error N01: Failed to Create Server", "Error N01:",0); } } //========================================================================= /** \brief Starts the server * * This function starts the server by setting a port in the socketDescriptor and starting a single IPV4 protocol socket. It then starts the server with the socket and sets the maximum amount of connections to the server. * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ void Server::ServerStartup() { socketDescriptor.port = atoi(serverPort.c_str()); //Set the port for the socket socketDescriptor.socketFamily = AF_INET; //Socket to IPV4 protocol bool b = server->Startup(maxPlayers, &socketDescriptor, 1)==RakNet::RAKNET_STARTED; if(!B) { MessageBoxA(0,"Error N03: Failed to Start Server","Error N03:",0); } server->SetMaximumIncomingConnections(maxPlayers); server->SetOccasionalPing(true); server->SetUnreliableTimeout(1000); DebugServerInfo(); } //========================================================================= /** \brief This function prints server info to the terminal * * The purpose of this function is to obtain information about the server, such as GUID, IP adresses, Ports and then it prints them to the terminal Screen. It is not a vital part of the server class but it is useful for debugging. * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ void Server::DebugServerInfo() { DataStructures::List<RakNet::RakNetSmartPtr < RakNet::RakNetSocket> > sockets; server->GetSockets(sockets); printf("Server Started!\n"); printf("Socket addresses used:\n"); for(unsigned int i=0; i < sockets.Size(); i++) { printf("%i. %s\n", i+1, sockets[i]->boundAddress.ToString(true)); numSockets += 1; } printf("My IP addresses:\n"); for(unsigned int i=0; i < server->GetNumberOfAddresses(); i++) { printf("%i. %s\n", i+1, server->GetLocalIP(i)); numIPs +=1; } printf("My GUID is %s\n", server->GetGuidFromSystemAddress( RakNet::UNASSIGNED_SYSTEM_ADDRESS).ToString()); } //========================================================================= /** \brief * * This * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ void Server::UpdateNetworkKeyboard(){ if(spawned==true){ //cout << batteryLife << endl; //Moving if(KeyDown(KEY_W)){ player[controlPlayer]->move=3; } else if(KeyDown(KEY_S)){ player[controlPlayer]->move = -3; } else { player[controlPlayer]->move = 0; } //Strafing if(KeyDown(KEY_D)){ player[controlPlayer]->strafe = 1; } else if(KeyDown(KEY_A)){ player[controlPlayer]->strafe = -1; } else { player[controlPlayer]->strafe = 0; } if(MouseDown(1)){ //Camera looking mx=Curve(MouseX()-GraphicsWidth()/2,mx,6); my=Curve(MouseY()-GraphicsHeight()/2,my,6); MoveMouse(GraphicsWidth()/2,GraphicsHeight()/2); camrotation.X=camrotation.X+my/10.0; player[controlPlayer]->turn=player[controlPlayer]->turn-mx/10.0; } player[controlPlayer]->jump=0.0; if(KeyHit(KEY_SPACE)){ player[controlPlayer]->jump=4; } if(player[controlPlayer]->move != player[controlPlayer]->moveCheck){ RakNet::BitStream bsOut; typeID = ID_PLAYER_MOVE; bsOut.Write(typeID); bsOut.Write(player[controlPlayer]->move); bsOut.Write(player[controlPlayer]->GetNetworkID()); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); } if(player[controlPlayer]->strafe != player[controlPlayer]->strafeCheck){ RakNet::BitStream bsOut; typeID = ID_PLAYER_STRAFE; bsOut.Write(typeID); bsOut.Write(player[controlPlayer]->strafe); bsOut.Write(player[controlPlayer]->GetNetworkID()); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); } if(player[controlPlayer]->turn != player[controlPlayer]->turnCheck){ RakNet::BitStream bsOut; typeID = ID_PLAYER_TURN; bsOut.Write(typeID); bsOut.Write(player[controlPlayer]->turn); bsOut.Write(player[controlPlayer]->GetNetworkID()); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); } if(player[controlPlayer]->jump != player[controlPlayer]->jumpCheck){ RakNet::BitStream bsOut; typeID = ID_PLAYER_JUMP; bsOut.Write(typeID); bsOut.Write(player[controlPlayer]->jump); bsOut.Write(player[controlPlayer]->GetNetworkID()); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); } player[controlPlayer]->moveCheck = player[controlPlayer]->move; player[controlPlayer]->strafeCheck = player[controlPlayer]->strafe; player[controlPlayer]->turnCheck = player[controlPlayer]->turn; player[controlPlayer]->jumpCheck = player[controlPlayer]->jump; for(int i=0; i<numPlayers; i++){ player[i]->UpdatePlayer(); } } } //========================================================================= /** \brief This function handles receiveing of packets on a server * * This function basically does all the handling and reaction to packets on the server. First it receives a packet, determins what type of information the packet holds and then initiates a reaction to the information. It then cycles through all the packets at that point in time until there are no more packets to read at which point it returns void. Custom user message identifiers are defined in CustomNetworkEnums.h. * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ void Server::ReceivePackets() { for(p=server->Receive(); p; server->DeallocatePacket(p), p=server->Receive()) { packetIdentifier = GetPacketIdentifier(p); RakNet::BitStream bsIn(p->data, p->length, false); RakNet::BitStream bsOut; switch (packetIdentifier) { case ID_DISCONNECTION_NOTIFICATION: //connection lost normally printf("ID_DISCONNECTION_NOTIFICATION from %s\n", p->systemAddress.ToString(true)); break; case ID_ALREADY_CONNECTED: printf("ID_ALREADY_CONNECTED\n"); break; case ID_NEW_INCOMING_CONNECTION: //Somebody connected. We have their IP now printf("ID_NEW_INCOMING_CONNECTION"); typeID = ID_LOAD_CURRENT_PLAYERS; bsOut.Write(typeID); bsOut.Write(numPlayers); for(int i=0; i<numPlayers; i++){ bsOut.Write(EntityPosition(player[i]->pBody).X); bsOut.Write(EntityPosition(player[i]->pBody).Y); bsOut.Write(EntityPosition(player[i]->pBody).Z); bsOut.Write(EntityRotation(player[i]->pBody).X); bsOut.Write(EntityRotation(player[i]->pBody).Y); bsOut.Write(EntityRotation(player[i]->pBody).Z); bsOut.Write(GetBodyVelocity(player[i]->pBody).X); bsOut.Write(GetBodyVelocity(player[i]->pBody).Y); bsOut.Write(GetBodyVelocity(player[i]->pBody).Z); bsOut.Write(GetBodyOmega(player[i]->pBody).X); bsOut.Write(GetBodyOmega(player[i]->pBody).Y); bsOut.Write(GetBodyOmega(player[i]->pBody).Z); bsOut.Write(player[i]->GetNetworkID()); } server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->systemAddress, false); break; case ID_INCOMPATIBLE_PROTOCOL_VERSION: printf("ID_INCOMPATIBLE_PROTOCOL_VERSION\n"); break; case ID_REMOTE_DISCONNECTION_NOTIFICATION: printf("ID_REMOTE_DISCONNECTION_NOTIFICATION\n"); break; case ID_REMOTE_NEW_INCOMING_CONNECTION: printf("ID_REMOTE_NEW_INCOMING_CONNECTION\n"); break; case ID_CONNECTION_BANNED: printf("You are banned from this server\n"); break; case ID_CONNECTION_ATTEMPT_FAILED: printf("Connection attempt failed\n"); break; case ID_CONNECTION_LOST: printf("ID_CONNECTION LOST from %s\n", p->systemAddress.ToString(true)); break; case ID_NO_FREE_INCOMING_CONNECTIONS: printf("Sorry, the server is full\n"); break; case ID_INVALID_PASSWORD: printf("Access Denied: Inccorrect client password\n"); break; case ID_CONNECTION_REQUEST_ACCEPTED: printf("Your connection has been accepted to %s with GUID %s\n", p->systemAddress.ToString(true), p->guid.ToString()); break; case ID_SPAWN_PLAYER: TVec3 playerPos; TVec3 playerRot; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(playerPos.X); bsIn.Read(playerPos.Y); bsIn.Read(playerPos.Z); bsIn.Read(playerRot.X); bsIn.Read(playerRot.Y); bsIn.Read(playerRot.Z); bsIn.Read(playerNetworkID); player[numPlayers] = new Player(playerPos, playerRot); player[numPlayers]->SetNetworkIDManager(&networkIDManager); player[numPlayers]->SetNetworkID(playerNetworkID); typeID = ID_SPAWN_PLAYER; bsOut.Write(typeID); bsOut.Write(playerPos.X); bsOut.Write(playerPos.Y); bsOut.Write(playerPos.Z); bsOut.Write(playerRot.X); bsOut.Write(playerRot.Y); bsOut.Write(playerRot.Z); bsOut.Write(playerNetworkID); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->systemAddress, true); numPlayers++; break; case ID_PLAYER_MOVE: int move; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(move); bsIn.Read(playerNetworkID); networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID)->move = move; typeID = ID_PLAYER_MOVE; bsOut.Write(typeID); bsOut.Write(move); bsOut.Write(playerNetworkID); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->systemAddress, true); break; case ID_PLAYER_STRAFE: int strafe; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(strafe); bsIn.Read(playerNetworkID); networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID)->strafe = strafe; typeID = ID_PLAYER_STRAFE; bsOut.Write(typeID); bsOut.Write(strafe); bsOut.Write(playerNetworkID); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->systemAddress, true); break; case ID_PLAYER_TURN: int turn; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(turn); bsIn.Read(playerNetworkID); networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID)->turn = turn; typeID = ID_PLAYER_TURN; bsOut.Write(typeID); bsOut.Write(turn); bsOut.Write(playerNetworkID); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->systemAddress, true); break; case ID_PLAYER_JUMP: int jump; bsIn.IgnoreBytes(sizeof(RakNet::MessageID)); bsIn.Read(jump); bsIn.Read(playerNetworkID); networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID)->jump = jump; typeID = ID_PLAYER_JUMP; bsOut.Write(typeID); bsOut.Write(jump); bsOut.Write(playerNetworkID); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->systemAddress, true); break; default: break; } } } //========================================================================= /** \brief Removes Timestamp and determines the packet identifier * * This function is used when receiveing packets. It basically strips the packet of its time stamp and returns it to the Receive packeets function for processiing. * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ unsigned char Server::GetPacketIdentifier(RakNet::Packet *p) { if(p==0) return 255; if((unsigned char)p->data[0] == ID_TIMESTAMP) { RakAssert(p->length > sizeof(RakNet::MessageID) + sizeof(RakNet::Time)); return (unsigned char) p->data[sizeof(RakNet::MessageID) + sizeof(RakNet::Time)]; } else return (unsigned char) p->data[0]; } //========================================================================= /** \brief Shuts down the netowork interfaces and free's it from memory * * * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ void Server::ShutDownServer() { server->Shutdown(300); RakNet::RakPeerInterface::DestroyInstance(server); } //========================================================================= /** \brief * * * * \param * * \return void * \return void * * \todo * * \bug * * \version 1.0 **************************************************************************/ void Server::SpawnPlayer(){ if(spawned==false){ player[numPlayers] = new Player(Vec3(0,0,0), Vec3(0,0,0)); typeID = ID_SPAWN_PLAYER; player[numPlayers]->SetNetworkIDManager(&networkIDManager); playerNetworkID = player[numPlayers]->GetNetworkID(); assert(networkIDManager.GET_OBJECT_FROM_ID<Player*>(playerNetworkID) == player[numPlayers]); RakNet::BitStream bsOut; bsOut.Write(typeID); bsOut.Write(0); bsOut.Write(0); bsOut.Write(0); bsOut.Write(0); bsOut.Write(0); bsOut.Write(0); bsOut.Write(player[numPlayers]->GetNetworkID()); server->Send(&bsOut, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); controlPlayer = numPlayers; numPlayers++; spawned = true; } } Not too much is changed from Ken's original tutorial code, but I am requiring this to work otherwise it will cause some further problems. Thanks in advance to anyone who can help me. Quote Win7 64bit, Leadwerks SDK 2.5, Visual Studio 2012, 3DWS, 3ds Max, Photoshop CS5. Life is too short to remove USB safely. Link to comment Share on other sites More sharing options...
Road Kill Kenny Posted January 20, 2013 Share Posted January 20, 2013 From what you explained, this is not a problem with RakNet... it is a general networking problem. When I mention that networking is bloody hard I don't mean the RakNet part. Sending and receiving data is a piece of cake.. It's when you have to sync that things get difficult. Latency is an inevitable reality when it comes to Networking and it impossible to keep them completely in sync without doing some tricks. Firstly you shouldn't spawn the character on the client first. The client should send a request to spawn on the server... then the server spawns the character and sends back data saying to spawn the character on the client. Server needs to be in control. Now you need to go Google networking Linear Interpolation, Client Side Prediction & Correction. My tutorials never covered this kind of problem because they were purely a 'How to' for RakNet. Smoke and mirrors. You could easily make a paper on each of these topics so I'm not gonna try explain them here. 1 Quote STS - Scarlet Thread Studios AKA: Engineer Ken Fact: Game Development is hard... very bloody hard.. If you are not prepared to accept that.. Please give up now! Link to comment Share on other sites More sharing options...
Canardia Posted January 20, 2013 Share Posted January 20, 2013 I tested with winsock that TCP is generally faster than UDP over long distances. I think RakNet supports also TCP, or you can just use winsock, it's cross-platform has all the things built-in for reliable and fast networking. RakNet is essentially just trying to make UDP more reliable, but it's still slower than raw TCP. Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
ChrisMAN Posted January 20, 2013 Share Posted January 20, 2013 I tested with winsock that TCP is generally faster than UDP over long distances. I think RakNet supports also TCP, or you can just use winsock, it's cross-platform has all the things built-in for reliable and fast networking. RakNet is essentially just trying to make UDP more reliable, but it's still slower than raw TCP. I always thought tcp was slower because it has to handshake? Quote Link to comment Share on other sites More sharing options...
DigitalHax Posted January 21, 2013 Author Share Posted January 21, 2013 Thanks for the replies. And yes I am not too sure about the communication between server and client, especially latency. What exactly does the server do, or what does the client do? Can the server just be something that is ran as a separate program, similar to a dedicated server on steam? And doesn't need to have a spawnable player or a 3d graphics window at all? Quote Win7 64bit, Leadwerks SDK 2.5, Visual Studio 2012, 3DWS, 3ds Max, Photoshop CS5. Life is too short to remove USB safely. Link to comment Share on other sites More sharing options...
ChrisMAN Posted January 21, 2013 Share Posted January 21, 2013 Not having done it... I would write an adapter around the transport and do most of the operations in plain ol classes that can run in memory for development/testing/single player. Quote Link to comment Share on other sites More sharing options...
Canardia Posted January 21, 2013 Share Posted January 21, 2013 I always thought tcp was slower because it has to handshake? That's what I thought too, because everyone keeps saying that UDP is faster. But when I tested it, TCP was much faster. I think it's because TCP opens a stream and has permanent connection, while UDP must reconnect and find the route over hundreds of servers each time a tiny packet is sent, which takes time. Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
Road Kill Kenny Posted January 21, 2013 Share Posted January 21, 2013 TCP or UDP... latency is inevitable. You need to use special techniques to make things sync properly. Quote STS - Scarlet Thread Studios AKA: Engineer Ken Fact: Game Development is hard... very bloody hard.. If you are not prepared to accept that.. Please give up now! Link to comment Share on other sites More sharing options...
Josh Posted January 21, 2013 Share Posted January 21, 2013 UDP is faster. TCP is unusable for games. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Canardia Posted January 21, 2013 Share Posted January 21, 2013 UDP is faster. TCP is unusable for games. And this is based on self-verified long distance tests? Quote ■ Ryzen 9 ■ RX 6800M ■ 16GB ■ XF8 ■ Windows 11 ■ ■ Ultra ■ LE 2.5 ■ 3DWS 5.6 ■ Reaper ■ C/C++ ■ C# ■ Fortran 2008 ■ Story ■ ■ Homepage: https://canardia.com ■ Link to comment Share on other sites More sharing options...
Rick Posted January 21, 2013 Share Posted January 21, 2013 What exactly does the server do, or what does the client do? Can the server just be something that is ran as a separate program, similar to a dedicated server on steam? And doesn't need to have a spawnable player or a 3d graphics window at all? Your server generally doesn't have any graphics or even require a graphics card for that matter. However, if you are using LE library on the server side for it's various functionality, then with LE2 you require a decent graphics card to even run your program. This is LE's "fault" though as it doesn't have a feature to run otherwise. The server generally is the truth of everything. It validates and controls everything. The client generally does similar things as the server as in validation but it's not trusted at all. It only does validation and such to give an instant response but can be overruled by the server on everything. So if your character collides with an ammo pickup, locally on the client you can check that collision and instantly give the player the ammo locally, but the server is doing the same checks and will also give the player the ammo on the server and send that it did so or not to the client, and the client will always listen to the server. If you didn't do something like this and the player has a ping of say 250 ms, the delay will be noticeable on the client side and "feel" laggy. You don't want that. You want the client to always feel fast and crisp! You can do that via just doing the action on the client but at the same time sending requests to the server to make sure it's OK to do it, and when getting the response from the server act accordingly OR you can hide the lag in other ways. In some RTS games when you select a unit and tell them to go to a certain place, you might notice they do a little salute animation and say something like "Yes, sir!". This all might take 300ms or so and seem natural on your client, but what it's doing behind the scenes is sending a request to the server, getting a response, then doing the action. Why do you have to do this? Because the client can be hacked. In the above example what if I requested to move into some trees that normally the collisions would not let me do this, but because I hacked the client I was able to skip those collision checks and do it anyway. If the server didn't get involved I could now hide in tree areas that normally I wouldn't. If it was a ranged unit I could potentially be invincible then as other units couldn't get to me if they didn't cheat. With the server it'll send back the response as NO, you can't. Now your local client could be hacked to ignore that response and do it anyway, that would only be happening on YOUR client and not on the server or anyone else's client, which means for everyone else you'd still be where you were before you made the move, and an easy target. As Ken said above, smoke and mirrors. There are many illusions/tricks that happen in an online game. 3 Quote Link to comment Share on other sites More sharing options...
Pixel Perfect Posted January 21, 2013 Share Posted January 21, 2013 So if your character collides with an ammo pickup, locally on the client you can check that collision and instantly give the player the ammo locally, but the server is doing the same checks and will also give the player the ammo on the server and send that it did so or not to the client, and the client will always listen to the server. So if I understand this correctly, the client in this scenario has given the ammo to the player ahead of the sever making the decision in order to mask the lag. So what happens on the client if the sever decides it didn't collide and the player has not received the ammo? Does the client then take the ammo back again and place it back in the pickup? I've never done any networked multi-player design so this is quite intriguing! Quote Intel Core i5 2.66 GHz, Asus P7P55D, 8Gb DDR3 RAM, GTX460 1Gb DDR5, Windows 7 (x64), LE Editor, GMax, 3DWS, UU3D Pro, Texture Maker Pro, Shader Map Pro. Development language: C/C++ Link to comment Share on other sites More sharing options...
gamecreator Posted January 21, 2013 Share Posted January 21, 2013 So if I understand this correctly, the client in this scenario has given the ammo to the player ahead of the sever making the decision in order to mask the lag. So what happens on the client if the sever decides it didn't collide and the player has not received the ammo? Does the client then take the ammo back again and place it back in the pickup? Yes and no. As with Rick's order delay example, you can have a very small delay on the client side from the action being recognized (player reaching ammo) and the action being implemented (ammo disappearing and added to player). It's only a fraction of a second or even a second but it should be enough time to run it by the server so you'd never get to ammo being increased then decreased. On the other hand, with major lag, yes, you'd see the scenario you described but that can't be helped. In that case you'd see a LOT of sudden corrections once connection is reestablished. Quote Link to comment Share on other sites More sharing options...
Pixel Perfect Posted January 21, 2013 Share Posted January 21, 2013 Thanks for the clarification gamecreator. Yes I guess with a major lag the game rapidly becomes unplayable if there is a lot going on! Quote Intel Core i5 2.66 GHz, Asus P7P55D, 8Gb DDR3 RAM, GTX460 1Gb DDR5, Windows 7 (x64), LE Editor, GMax, 3DWS, UU3D Pro, Texture Maker Pro, Shader Map Pro. Development language: C/C++ Link to comment Share on other sites More sharing options...
Rick Posted January 21, 2013 Share Posted January 21, 2013 So if I understand this correctly, the client in this scenario has given the ammo to the player ahead of the sever making the decision in order to mask the lag. Yeah, this plays into some of the predictions that speed up the client. You are predicting on the client that this action is OK and really did happen because you ran the same rules that the server will run. If the server agrees with your client nothing has to be changed, but if the server doesn't agree with your client it'll send a correction. This generally all takes a very short amount of time so it's hard to notice the correction if one needs to happen. In fast paced games if 2 people are going for the same ammo pack and the game has it being picked up on collision instead of doing some kind of animation for picking it up, sometimes you might hear a pickup sound on your end but don't see the ammo added (it probably added it to the ammo count text, but within the 50 ms or so to correct it, our brains wouldn't notice the small flicker of correction to the text. Also, in a fast paced game how often are you staring at your ammo count anyway really.). The odds that both players collide within the 25-100 MS that most pings are these days for these games is rare. In a game I have when the player right clicks on a resource I play a little growing bubble animation, and when it's done the options for that resource come up. Sort of like how The Sims does their actions. Because I want full control of changing these at any time, during this 300ms animation of the bubble growing, I request from the server all actions I can do on this resource. I then display those when the animation is finished. However, I have to account for both ways. The animation may finish before I get a response from the server with lots of lag. So I set flags on both sides. One for the animation and one for the server response. Then in both areas I check if the other flag is true and if so then I setup my action option buttons for display. So there are just really different ways to think about things when adding network code. It's fun and annoying at the same time lol Quote Link to comment Share on other sites More sharing options...
Pixel Perfect Posted January 21, 2013 Share Posted January 21, 2013 Cool, that all makes good sense. Thanks Quote Intel Core i5 2.66 GHz, Asus P7P55D, 8Gb DDR3 RAM, GTX460 1Gb DDR5, Windows 7 (x64), LE Editor, GMax, 3DWS, UU3D Pro, Texture Maker Pro, Shader Map Pro. Development language: C/C++ Link to comment Share on other sites More sharing options...
DigitalHax Posted January 21, 2013 Author Share Posted January 21, 2013 Yes that does clarify things up a bit Thanks for all the replies. Because it is great to understand these things Quote Win7 64bit, Leadwerks SDK 2.5, Visual Studio 2012, 3DWS, 3ds Max, Photoshop CS5. Life is too short to remove USB safely. Link to comment Share on other sites More sharing options...
DigitalHax Posted January 21, 2013 Author Share Posted January 21, 2013 Although I guess that still doesn't help my problem exactly. See I can spawn a player on the client, and it will appear on the server, but when I move the character, the position isn't updating on the server. And yet once I spawn the player on the server (Yes I know a server player is unnecessary) it starts to receive the movement updates. Quote Win7 64bit, Leadwerks SDK 2.5, Visual Studio 2012, 3DWS, 3ds Max, Photoshop CS5. Life is too short to remove USB safely. Link to comment Share on other sites More sharing options...
DigitalHax Posted January 21, 2013 Author Share Posted January 21, 2013 Here are some photos. Player spawned on the client and moved. Server Spawned Client Moved. So something about having spawned a player on the server. however unnecessary allows the movement to update. Quote Win7 64bit, Leadwerks SDK 2.5, Visual Studio 2012, 3DWS, 3ds Max, Photoshop CS5. Life is too short to remove USB safely. Link to comment Share on other sites More sharing options...
Rick Posted January 21, 2013 Share Posted January 21, 2013 The player on the server is very necessary, but it just doesn't require the visuals. You will want a "player" both locally with visuals and on the server without (in LE2 you could just have the controller for the physics). Replicating objects from server to client is a fundamental thing in networking. You will want to do that. I didn't look through your code, but what is the link that you have between characters on client and server? Usually there is some kind of ID that links a client to it's replicated counterpart on the server. Quote Link to comment Share on other sites More sharing options...
Pixel Perfect Posted January 21, 2013 Share Posted January 21, 2013 Although I have no working knowledge of networking and multi-player games I'd have thought that spawning all the players on the server was essential as it needs to simulate the game in its entirety and I can't see how it would do so without actually running the game. True the graphics output would not normally be required because there would be no requirement to view the game on the server, it simply runs the simulation on behalf of the clients and acts as the master sync so to speak. Quote Intel Core i5 2.66 GHz, Asus P7P55D, 8Gb DDR3 RAM, GTX460 1Gb DDR5, Windows 7 (x64), LE Editor, GMax, 3DWS, UU3D Pro, Texture Maker Pro, Shader Map Pro. Development language: C/C++ Link to comment Share on other sites More sharing options...
gamecreator Posted January 22, 2013 Share Posted January 22, 2013 Ideally you simulate on the client side too and use the server as an authenticity check. This way, if the connection is briefly lost, you don't have to immediately stop the game (especially if, for example, you're in an area all by yourself at that point). DH, your code is pretty lengthy for me to look over but you have at least two options: Display the packets being received on the server from your client. Make sure that after the client player spawns, the serrver is receiving player data from the client and you're reacting accordingly (updating x,y, etc.). You can take Ken's advice and have server authorize client's player before they each create it. Quote Link to comment Share on other sites More sharing options...
Rick Posted January 22, 2013 Share Posted January 22, 2013 Like gamecreator says the code is lengthy and not very friendly to break out specific problem areas. Having the code directly in switch statements and even having a switch statement at all is probably not the ideal method for this as you can see it can get very lengthy and hard to read and separate out. You see common things (like ignoring the message byte) in each message. This should be done at a higher level since it needs to be done for all messages. Ideally you would have 1 function for each message type so you can break things out easier and if you like you could even create a Message base type that packages up your network messages into actual types so that those functions that process them take said type that has specific fields for that message. This way you can easily break things out, check that all the data that was expected is there, and see the data easier. If I were you, I'd start small with RakNet and figure out a nice way to organize things. The switch statement is just an example RakNet shows in their tutorials but it's far from ideal in a real situation as you can end up with hundreds of messages and things will get unruly very quickly. My messages generally have a REQUEST/RESPONSE theme to them. The client generally sends REQUEST type messages and the server generally sends RESPONSE message. ie ID_REQUEST_LOGIN, ID_RESPONSE_LOGIN. The way I map messages to class functions is with the event class I have posted here http://www.leadwerks.com/werkspace/files/file/367-c-event/. Then I have a map where the key is the network message and the value is the event type. I usually use the event that takes 2 parameters. The first being the Packet*, the second being the BitStream. Then when I get a packet, I use the first byte (which is the message) and see if it exists in the map. If so, I raise the event, which calls the function for that event. I find this creates a nice separation of messages and makes things easier to understand myself. Quote Link to comment Share on other sites More sharing options...
Pixel Perfect Posted January 22, 2013 Share Posted January 22, 2013 This is proving a very informative thread, useful advice guys! Quote Intel Core i5 2.66 GHz, Asus P7P55D, 8Gb DDR3 RAM, GTX460 1Gb DDR5, Windows 7 (x64), LE Editor, GMax, 3DWS, UU3D Pro, Texture Maker Pro, Shader Map Pro. Development language: C/C++ Link to comment Share on other sites More sharing options...
Rick Posted January 22, 2013 Share Posted January 22, 2013 Also something to note is that as your coding many parts of network games you notice that you end up doing very similar things on both the client and server. There is room for sharing source files between them with possibly some #ifdef stuff in the code to branch on compile if it's client vs server inside said functions. When I first looked at HL source I noticed Valve did this all over the place, and as I started doing network programming myself I saw why. No sense in duplicating code if only 10% of a class is different because it runs on the server vs client. This can save a lot of time and bugs. You can notice this in the example code as well. ID_SPAWN _PLAYER looks very similar on client and server. They both require a Player class, but the insides can be slightly different based on client/server but no real need to make 2 Player classes, one in the server project or 1 in the client project. Just make a separate area for shared classes and include those files in both client & server projects with in #indef checks if you are client or server and then branch off the differences. Since you almost always want the client and server doing the exact same checks for things, this means you only have to code those checks once instead of twice, or copy/paste. 2 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.