Jump to content

Raknet Bitstream Problems


DigitalHax
 Share

Recommended Posts

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.

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

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.

  • Upvote 1

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

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.

Ryzen 9 RX 6800M ■ 16GB XF8 Windows 11 ■
Ultra ■ LE 2.53DWS 5.6  Reaper ■ C/C++ C# ■ Fortran 2008 ■ Story ■
■ Homepage: https://canardia.com ■

Link to comment
Share on other sites

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?

Link to comment
Share on other sites

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?

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

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.

Ryzen 9 RX 6800M ■ 16GB XF8 Windows 11 ■
Ultra ■ LE 2.53DWS 5.6  Reaper ■ C/C++ C# ■ Fortran 2008 ■ Story ■
■ Homepage: https://canardia.com ■

Link to comment
Share on other sites

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.

  • Upvote 3
Link to comment
Share on other sites

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!

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

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.

Link to comment
Share on other sites

Thanks for the clarification gamecreator. Yes I guess with a major lag the game rapidly becomes unplayable if there is a lot going on!

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

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

Link to comment
Share on other sites

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.

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

Here are some photos.

 

Player spawned on the client and moved.

 

2afx652.jpg

 

 

Server Spawned

 

24dg4d2.jpg

 

Client Moved.

2n9eu4x.jpg

 

So something about having spawned a player on the server. however unnecessary allows the movement to update.

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

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.

Link to comment
Share on other sites

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.

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

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:

  1. 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.).
  2. You can take Ken's advice and have server authorize client's player before they each create it.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

  • Upvote 2
Link to comment
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...