Purple Martians
Technical Code Descriptions

Netgame - Join
Overview Client sends CJON Server receives CJON and replies with SJON Client blocks until it receives SJON Client blocks until it gets initial state from server Client chase and lock Packets used for join Setting up netgame
Overview The client requests to join. The server sends a join invitation with all the game details. The client waits for initial state from the server. The client chases until it locks and the server sets it active. Client sends CJON When a client wants to join, it sends a 'CJON' packet to the server, requesting to join.
Packet("CJON");
PacketPut1ByteInt(players[0].color); // requested color
PacketAddString(local_hostname);
ClientSend(packetbuffer, packetsize);
Server receives CJON and replies with SJON The server sets up a client player structure and replies with an 'SJON' packet.
init_player(cn, 1);
players[cn].color = color;
players[cn].control_method = 2; // server client remote view only
players1[cn].who = who;
players1[cn].server_last_sdak_rx_frame_num = frame_num + 200;
players1[cn].game_move_entry_pos = game_move_entry_pos;

Packet("SJON"); // reply with SJON
PacketPut2ByteInt(play_level);
PacketPut4ByteInt(frame_num);
PacketPut4ByteInt(game_move_entry_pos);
PacketPut1ByteInt(frame_speed);
PacketPut1ByteInt(cn);
PacketPut1ByteInt(color);
PacketPut1ByteInt(deathmatch_pbullets);
PacketPut1ByteInt(deathmatch_pbullets_damage);
PacketPut1ByteInt(suicide_pbullets);
Client blocks until it receives SJON When the client receives SJON, it sets up the local and server player structures:
play_level = PacketGet2ByteInt(); 
int server_SJON_frame_num =  PacketGet4ByteInt(); 
players1[cp].game_move_entry_pos =  PacketGet4ByteInt(); 
frame_speed = PacketGet1ByteInt();    
int cp = PacketGet1ByteInt();     // client player number
players[cp].color = PacketGet1ByteInt();  // client player color
deathmatch_pbullets = PacketGet1ByteInt();
deathmatch_pbullets_damage = PacketGet1ByteInt();
suicide_pbullets = PacketGet1ByteInt();

for (int p=0; p < NUM_PLAYERS; p++) init_player(p, 1); // initialize all players

players[0].active = 1;          // server is alway player 0 and active when on a client
players[0].control_method = 2;  // server is remote view when on a client

active_local_player = cp;       // client local player
players[cp].control_method = 4; 
Client blocks until it gets initial state from server On the client in the function 'client_control()':
if (frame_num == 0) client_block_until_initial_state_received();

void client_block_until_initial_state_received(void)
{
   reset_states();
   int done = 0;
   while (!done)
   {
      if ((packetsize = ClientReceive(packetbuffer)) && (PacketRead("stdf"))) done = client_process_stdf_packet();
      proc_controllers();
      if (key[ALLEGRO_KEY_ESCAPE]) fast_exit(64); // in case we get trapped here and need a way out
   }
   set_frame_nums(client_state_dif_dst);
   client_apply_diff();

   // set holdoff 200 frames in future so client won't try to drop while syncing
   players1[p].client_last_sdat_rx_frame_num = frame_num + 200;

   // send ack up to this point so server won't try to send us previous game moves
   Packet("sdak");
   PacketPut1ByteInt(p);
   PacketPut4ByteInt(frame_num);
   PacketPut4ByteInt(players1[p].game_move_entry_pos); // new entry pos
   PacketPut4ByteInt(players1[p].stdf_late);
   PacketPut4ByteInt(players1[p].frames_skipped);
   ClientSend(packetbuffer, packetsize);
}
Client chase and lock After the initial state is received, the client does not block anymore. The client game starts running from the initial state received from the server. The client player is not active yet, but still processes packets from the server. In particular, sdat packets are used to adjust the client's timer until it catches up and locks with the server.
void client_process_sdat_packet(void)
{
   int sdat_frame_num = PacketGet4ByteInt();
   players1[p].client_sync = sdat_frame_num - frame_num;
   int fps_chase = frame_speed + (players1[p].client_sync - server_lead_frames)*2;
   al_set_timer_speed(fps_timer, ( 1 / (float) fps_chase));

   Packet("sdak"); // send acknowledgement of sdat
   PacketPut4ByteInt(frame_num);
   ClientSend(packetbuffer, packetsize);
}
The server monitors the client's sync on its side when it receives the client's acknowledgements. When the client's sync is within limits, the server sets the client active by entering a special game move. This game move is synced back the the client, and when the client executes it, the client becomes active.
if(PacketRead("sdak"))
{
   int client_fn = PacketGet4ByteInt();
   players1[p].server_sync = frame_num - client_fn;
   if ((players[p].active == 0) && (players[p].control_method == 2) && (players1[p].server_sync < 4) && (players1[p].server_sync > -2))
   {
      add_game_move(frame_num + 4, 1, p, players[p].color);
   }
}

Packets used for join
Packet: 'CJON' description: 'client join' direction: client to server - 1 byte (requested color) - 16 bytes (client hostname)
Packet: 'SJON' description: 'server join' direction: server to client - 2 bytes (play level) - 4 bytes (server frame_num) - 4 bytes (server game_move entry position) - 1 byte (frame speed) - 1 byte (player number) - 1 byte (player color) - 1 byte (deathmatch_pbullets) - 1 byte (deathmatch_pbullets_damage) - 1 byte (suicide_pbullets)
Setting up netgame The netgame is only designed to work in a LAN, not the internet. To join a netgame the client needs to know the IP or hostname of the server. The server does not need to know anything about the client. If both the server and the client can ping each other, netgame should work. In some cases a firewall exception may need to be created. (especially for the server) In windows, open a command prompt window and type: - 'ipconfig' to see your local IP address - 'hostname' to see your hostname In linux, open a terminal and type: - 'ifconfig' to see your local IP address - 'hostname' to see your hostname Do this for both the client and the server. Then try to ping each other by typing: - 'ping [IP]' or - 'ping [hostname]' If that works then all you need to do is make sure the client has the server's IP or hostname set. You can do this three different ways: - in the game from 'options menu' -> 'netgame options' - open the config file 'pm.cfg' and edit the entry 'server_IP=' - from a command prompt start the game in client mode with 'pm -c [hostname or IP]' Start the server game running with 'Host Network Game', then start the client(s) with 'Join Network Game' In a few seconds the client will join.