/*
*
* Copyright (C) 2009 Daniel Aquino
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see , or write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*/
#ifdef NET_ENET_2
#include "net.h"
#include "enet/enet.h"
#include
#include
#include "xmem.h"
// debug print f
#include "util.h"
// id stuff
typedef unsigned char peer_id_t;
#define NO_ID 0
#define LOWEST_ID 1
#define HIGHEST_ID 255
/*
*
* settings
*
*/
// state flags
static int initialized;
ENetHost* enet_host = NULL; // used in net_tracker.c
// the host player
static ENetPeer* host;
// my settings
static int i_am_host = 0;
static peer_id_t my_id = NO_ID;
char* my_player_name = NULL; // used in net_tracker.c
int my_local_port = 0; // used in net_tracker.c
// settings
static int max_peers = 40;
static int max_channels = 50;
static int system_channel = 0;
/*
*
* Debug Helpers
*
*/
// returns host:port syntax copy data right away as ip is overwritten next call
static char* address_to_str( ENetAddress * address )
{
char temp[INET_ADDRSTRLEN+10] = "";
static char ip[INET_ADDRSTRLEN+10] = "";
enet_address_get_host_ip( address, temp, INET_ADDRSTRLEN );
sprintf( ip, "%s:%d", temp, address->port );
return ip;
}
/*
*
* peer helpers
*
*/
typedef enum {
PLAYING, // peer is valid player in game
UNUSED, // unused peer
CONNECTED, // this must match other enum which defines CONNECTED as well
CONNECTING, // host told us to connect
ACKING, // waiting for peer to ack my id
SYNCHING, // host telling everyone to synch
} network_peer_state_t;
// peer->data =
typedef struct {
peer_id_t id;
network_peer_state_t state;
network_player_t * player;
int connect_port;
ENetPeer ** connected_peers; // used by host to keep track of connections
} network_peer_data_t;
#define PEER_USED( peer )\
( ( (network_peer_data_t*) (peer)->data )->state != UNUSED )
#define PEER_CONNECT_PORT( peer )\
( (network_peer_data_t*) (peer)->data )->connect_port
#define PEER_STATE( peer )\
( (network_peer_data_t*) (peer)->data )->state
#define PEER_ID( peer )\
( (network_peer_data_t*) (peer)->data )->id
#define PLAYER_ID( player )\
PEER_ID( ( (ENetPeer*) (player)->data ) )
static void init_connected_list( network_peer_data_t * peer_data )
{
int x;
for( x = 0; x < max_peers; x++ )
peer_data->connected_peers[x] = NULL;
}
static void init_peer( ENetPeer * peer )
{
network_peer_data_t * data = peer->data;
if( data == NULL ) // first init
{
peer->data = malloc( sizeof(network_peer_data_t) );
data = peer->data;
data->connected_peers = malloc( sizeof(void*) * max_peers );
}
init_connected_list( data );
data->state = UNUSED;
data->player = NULL;
data->connect_port = 0;
data->id = NO_ID;
}
static void init_peers( void )
{
size_t x;
for( x = 0; x < enet_host->peerCount; x++ )
init_peer( &enet_host->peers[x] );
}
static void cleanup_peer( ENetPeer * peer )
{
network_peer_data_t * data = peer->data;
if( ! data ) return;
if( data->connected_peers )
{
free( data->connected_peers );
data->connected_peers = NULL;
}
free( data );
peer->data = NULL;
}
static void cleanup_peers( void )
{
size_t x;
for( x = 0; x < enet_host->peerCount; x++ )
cleanup_peer( &enet_host->peers[x] );
}
static ENetPeer * find_peer_by_id( peer_id_t id )
{
size_t x;
if(!id) return NULL;
for( x = 0; x < enet_host->peerCount; x++ )
{
ENetPeer * peer = &enet_host->peers[x];
if( PEER_USED(peer) && PEER_ID(peer) == id )
return peer;
}
return NULL;
}
static peer_id_t find_free_id( void )
{
peer_id_t id;
static peer_id_t last = LOWEST_ID;
if( last == HIGHEST_ID )
last = LOWEST_ID;
last++;
for( id = last; id <= HIGHEST_ID; id++ )
{
if( id != my_id && ! find_peer_by_id( id ) )
{
last = id;
return id;
}
}
return NO_ID;
}
/*
*
* enet helpers
*
*/
static void enet_cleanup( void )
{
initialized = 0;
enet_host_destroy( enet_host );
enet_host = NULL;
enet_deinitialize();
DebugPrintf("network: enet cleanup finished\n");
}
static network_return_t enet_setup( char* str_address, int port )
{
ENetAddress address;
if ( ! initialized )
{
if (enet_initialize() != 0)
{
return NETWORK_ERROR_INIT;
}
initialized = 1;
}
DebugPrintf("network: enet setup initialized\n");
if ( ! str_address ) address.host = ENET_HOST_ANY;
else enet_address_set_host( &address, str_address );
address.port = (port) ? port : NETWORK_DEFAULT_PORT;
my_local_port = address.port;
DebugPrintf("network: enet setup address %s\n",
address_to_str(&address));
enet_host = enet_host_create( &address, max_peers, 0, 0 );
if ( enet_host == NULL )
{
return NETWORK_ERROR_BIND;
}
DebugPrintf("network: enet setup initializing peers\n");
init_peers();
DebugPrintf("network: enet setup finished\n");
return NETWORK_OK;
}
static int enet_connect( char* str_address, int port )
{
ENetAddress address;
ENetPeer* peer;
network_peer_data_t * peer_data;
if ( ! str_address ) address.host = ENET_HOST_ANY;
else enet_address_set_host( &address, str_address );
address.port = (port) ? port : NETWORK_DEFAULT_PORT;
DebugPrintf("network: enet connect to address %s\n",
address_to_str(&address));
peer = enet_host_connect( enet_host, &address, max_channels );
if (peer == NULL)
{
return -1;
}
init_peer( peer );
peer_data = peer->data;
peer_data->state = CONNECTING;
if( network_state == NETWORK_CONNECTING )
host = peer;
DebugPrintf("network: enet connect pending\n");
return 1;
}
typedef enum {
NO_FLUSH = 0,
FLUSH = 2,
} flush_t;
static void enet_send_packet( ENetPeer* peer, ENetPacket* packet, int channel, flush_t flush )
{
if ( packet == NULL )
{
DebugPrintf("network: enet send packet received null for packet\n");
return;
}
enet_peer_send( peer, channel, packet );
if ( flush )
enet_host_flush( enet_host );
}
static void enet_send(
ENetPeer* peer, void* data, int size,
enet_uint32 type, int channel, flush_t flush
)
{
ENetPacket * packet = enet_packet_create( data, size, type );
enet_send_packet( peer, packet, channel, flush );
}
static network_player_t * find_player_with_lowest_id( void )
{
peer_id_t id = HIGHEST_ID;
network_player_t * lowest = NULL;
network_player_t * player = network_players.first;
if( enet_host == NULL ) return NULL;
while( player )
{
ENetPeer * player_peer = (ENetPeer*) player->data;
peer_id_t player_id = PEER_ID(player_peer);
if( player_id < id && player_id != NO_ID )
{
id = player_id;
lowest = player;
}
player = player->next;
}
if ( my_id < id )
{
return NULL; // I am the lowest
}
return lowest;
}
static void migrate_host( void )
{
network_player_t * player = find_player_with_lowest_id();
if ( ! player )
{
i_am_host = 1;
host = NULL;
player = NULL;
DebugPrintf("network: I am the new host\n");
}
else
{
host = (ENetPeer*) player->data;
DebugPrintf("network: host has been migrated to player %d\n",
PLAYER_ID(player));
}
network_event( NETWORK_HOST, player );
}
/*
*
* internal helpers
*
*/
static enet_uint32 convert_flags( network_flags_t flags )
{
// our default is unreliable and unsequenced
enet_uint32 enet_flags = ENET_PACKET_FLAG_UNSEQUENCED;
if ( flags )
{
// sequenced and unreliable
if ( flags & NETWORK_SEQUENCED )
enet_flags = 0;
// note: enet reliable is always sequenced
if ( flags & NETWORK_RELIABLE )
enet_flags |= ENET_PACKET_FLAG_RELIABLE;
}
return enet_flags;
}
static void update_player( network_player_t* player )
{
ENetPeer * peer = (ENetPeer*) player->data;
if(!peer) return;
if( player->ping != peer->roundTripTime )
{
player->ping = peer->roundTripTime;
//DebugPrintf("network extra: player %s (%d) ping has changed to %d\n",
// player->name, PEER_ID(peer), player->ping);
}
if( player->packet_loss != peer->packetLoss )
{
player->packet_loss = peer->packetLoss;
//DebugPrintf("network extra: player %s (%d) packet loss has changed to %d\n",
// player->name, PEER_ID(peer), player->packet_loss);
}
if( player->packets_lost != peer->packetsLost )
{
player->packets_lost = peer->packetsLost;
//DebugPrintf("network extra: player %s (%d) packets lost has changed to %d\n",
// player->name, PEER_ID(peer), player->packets_lost);
}
}
static void update_players( void )
{
network_player_t * player = network_players.first;
if( enet_host == NULL ) return;
while( player )
{
update_player( player );
player = player->next;
}
}
static void update_player_name( network_player_t * player, char * name )
{
strncpy( player->name, name, NETWORK_MAX_NAME_LENGTH-1 );
}
static network_player_t * create_player( ENetPeer * peer )
{
network_peer_data_t * peer_data = peer->data;
network_player_t * player = malloc( sizeof( network_player_t ) );
// name
update_player_name( player, "NULL" );
// dotted decimal ip
enet_address_get_host_ip( &peer->address, player->ip, sizeof(player->ip) );
// static settings
player->port = peer->address.port;
player->bw_in = peer->incomingBandwidth;
player->bw_out = peer->outgoingBandwidth;
// this is how we maintain relationship between peer and player
player->data = peer;
peer_data->player = player;
// update dynamic data (ping etc..)
update_player( player );
return player;
}
static void append_player( network_player_t * player )
{
// TODO - this should never happen but it does
if(!player)
return;
player->prev = network_players.last;
if(player->prev)
player->prev->next = player;
player->next = NULL;
network_players.last = player;
if( network_players.first == NULL )
network_players.first = player;
network_players.length++;
}
static void destroy_player( network_player_t * player )
{
ENetPeer * peer = player->data;
network_peer_data_t * peer_data = NULL;
if( peer )
peer_data = peer->data;
// join previous and next players together
if( player->prev != NULL ) player->prev->next = player->next;
if( player->next != NULL ) player->next->prev = player->prev;
// reset first and last player
if( player == network_players.first ) network_players.first = player->next;
if( player == network_players.last ) network_players.last = player->prev;
// drop the count
network_players.length--;
// remove peer/player associations
player->data = NULL;
if( peer_data )
peer_data->player = NULL;
// cleanup peer data
if( peer )
init_peer( peer );
// destroy the player
free( player );
}
static void destroy_players( void )
{
network_player_t * player = network_players.first;
while( player )
{
// destroy_player frees player hence player->next will be invalid
network_player_t * next_player = player->next;
destroy_player( player );
player = next_player;
}
network_players.length = 0;
network_players.first = NULL;
network_players.last = NULL;
}
/*
*
* hand shaking helpers
*
*/
static void add_peer_to_connected_list( ENetPeer ** connected_list, ENetPeer * peer )
{
int x;
DebugPrintf("network: adding peer to connected list\n");
for( x = 0; x < max_peers; x++ )
{
ENetPeer * connected = connected_list[x];
if( connected == peer )
{
DebugPrintf("network: peer already on connected list\n");
return; // already added
}
if( connected == NULL )
{
connected_list[x] = peer;
DebugPrintf("network: added peer to connected list\n");
return; // added
}
}
DebugPrintf("network: connected list is already full\n");
}
static int peer_on_connected_list( ENetPeer ** connected_list, ENetPeer * peer )
{
int x;
for( x = 0; x < max_peers; x++ )
if( peer == connected_list[x] )
return 1;
return 0;
}
static int all_players_on_connected_list( ENetPeer ** connected_list, ENetPeer * joiner )
{
size_t x;
for( x = 0; x < enet_host->peerCount; x++ )
{
ENetPeer * peer = &enet_host->peers[x];
if( PEER_STATE(peer) != PLAYING ) continue;
if( peer == joiner ) continue;
if( ! peer_on_connected_list( connected_list, peer ) )
return 0;
}
return 1;
}
/*
*
* event system
*
*/
typedef enum {
// packet types
NAME, // player tells us his name
CONNECT, // host tells player to connect to another player
CONNECTED_TO, // player tells the host they connected to a player
LOST_CONNECTION, // tell host that I lost a connection
DISCONNECT, // host tells players to disconnect someone
NEW_PLAYER, // host tells others that player is now synched
CONNECT_PORT, // player telling host which port people should connect to
YOUR_ID, // host tells you your id when you connect to them
MY_ID, // player tells new player his id on connection
ACK_ID, // player tells another player that he now has his id
ACK_MY_ID, // player tells host that he has his id
// disconnect reasons
FAILED_SYNCH, // host telling you that you failed to synch with someone
HOST_SAID_SO, // host told me to disconnect from you
I_QUIT, // I'm leaving the game
} p2p_event_t;
typedef struct {
p2p_event_t type;
} p2p_packet_t;
typedef struct {
p2p_event_t type;
char name[ NETWORK_MAX_NAME_LENGTH ];
} p2p_name_packet_t;
typedef struct {
p2p_event_t type;
ENetAddress address;
peer_id_t id;
} p2p_connect_packet_t;
typedef struct {
p2p_event_t type;
int number;
} p2p_number_packet_t;
typedef struct {
p2p_event_t type;
peer_id_t id;
} p2p_id_packet_t;
static void disconnect_all( void )
{
size_t x;
for( x = 0; x < enet_host->peerCount; x++ )
{
if ( enet_host->peers[x].state != ENET_PEER_STATE_DISCONNECTED )
enet_peer_disconnect_now( &enet_host->peers[x], I_QUIT );
}
init_peers();
}
static void new_connection( ENetPeer * peer )
{
network_peer_data_t * peer_data = peer->data;
// print debug info
DebugPrintf("network: new connection from address %s\n",
address_to_str(&peer->address));
// some reason connection list was getting dirty
// this makes sure it's clean before we commence
init_connected_list( peer_data );
// create the player structure
peer_data->player = create_player( peer );
// tell player my id
if( peer != host )
{
p2p_id_packet_t packet;
packet.type = MY_ID;
packet.id = my_id;
enet_send( peer, &packet, sizeof(packet),
convert_flags(NETWORK_RELIABLE), system_channel, NO_FLUSH );
DebugPrintf("network: sent my id (%d) to new connection\n", my_id);
}
// tell the player my external port people should connect to
// we need to send this to everyone in case they become host later
{
p2p_number_packet_t packet;
packet.type = CONNECT_PORT;
packet.number = my_local_port;
enet_send( peer, &packet, sizeof(packet),
convert_flags(NETWORK_RELIABLE), system_channel, NO_FLUSH );
DebugPrintf("network: sent my connect port (%d) to new connection\n",
my_local_port);
}
// Send my player name to the new connection
{
p2p_name_packet_t packet;
packet.type = NAME;
strncpy( packet.name, my_player_name, NETWORK_MAX_NAME_LENGTH-1 );
enet_send( peer, &packet, sizeof(packet),
convert_flags(NETWORK_RELIABLE), system_channel, NO_FLUSH );
DebugPrintf("network: sent my name (%s) to new connection\n",
my_player_name);
}
//
if ( i_am_host )
{
// tell the new player his id
p2p_id_packet_t packet;
peer_data->id = find_free_id();
packet.type = YOUR_ID;
packet.id = peer_data->id;
enet_send( peer, &packet, sizeof(packet),
convert_flags(NETWORK_RELIABLE), system_channel, NO_FLUSH );
DebugPrintf("network: sent new player his id %d\n",
peer_data->id);
peer_data->state = CONNECTED;
}
// I'm not the host
else
{
// if we started this connection
if( peer_data->state == CONNECTING )
{
// we connected to the host
if( peer == host )
{
network_state = NETWORK_SYNCHING;
DebugPrintf("network: we have connected to the host...\n");
peer_data->state = CONNECTED;
}
else
{
// we connected to a new player
// we must now wait for them to ack our id
// then we can tell the host that we connected
peer_data->state = ACKING;
}
}
else
{
DebugPrintf("network security: we didn't start this connection\n");
peer_data->state = CONNECTED;
}
}
}
static void lost_connection( ENetPeer * peer, enet_uint32 data )
{
char* reason = &data;
network_peer_data_t * peer_data = peer->data;
// print debug info
{
char * name = "NULL";
if( peer_data->player && (peer_data->player->name[0] != 0) )
name = peer_data->player->name;
DebugPrintf("network security: lost connection from %s %d (%s@%s)\n",
( host==peer ) ? "host" : "player",
peer_data->id,
name,
address_to_str(&peer->address));
switch(reason[0])
{
case FAILED_SYNCH:
DebugPrintf("network: reason was because you failed to synch with player %d\n", reason[1]);
break;
case HOST_SAID_SO:
DebugPrintf("network: reason was because host told me to disconnect from you\n");
break;
case I_QUIT:
DebugPrintf("network: reason was because I'm leaving the game\n");
break;
default:
DebugPrintf("network: reason was unspecified\n");
}
}
// I'm still trying to connect to host
if( network_state == NETWORK_CONNECTING )
{
// we failed to connect to host
if ( host == peer )
network_state = NETWORK_DISCONNECTED;
}
// I'm trying to synch with everyone
if( network_state == NETWORK_SYNCHING )
{
// we failed to connect to host
if ( host == peer )
network_state = NETWORK_SYNCH_FAILED;
}
// we are in the game
if ( network_state == NETWORK_CONNECTED )
{
// backup data so we can kill peer before we broadcast
peer_id_t id = peer_data->id;
// if player was in the game
if( peer_data->state == PLAYING )
{
// we lost a player
network_event( NETWORK_LEFT, peer_data->player );
if( peer_data->player )
destroy_player( peer_data->player );
peer_data->player = NULL;
// host left the game
if ( host == peer )
{
host = NULL;
migrate_host();
}
}
// connection had a valid id
if( id )
{
// if i am host
if( i_am_host )
{
// tell everyone to drop the connection
p2p_id_packet_t packet;
packet.type = DISCONNECT;
packet.id = id;
network_broadcast( &packet, sizeof(packet),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network: telling everyone to drop connection\n");
}
// I am not the host
else
{
// tell host that we lost a connection
network_peer_data_t * host_data = host->data;
p2p_id_packet_t packet;
packet.type = LOST_CONNECTION;
packet.id = id;
network_send( host_data->player, &packet, sizeof(packet),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network: telling host that we lost connection\n");
}
}
}
init_peer( peer ); // cleanup the peer data
}
// other wise multiple players joining at once would block each other
static void tell_peer_to_connect_to_synchers( network_player_t * player )
{
size_t x;
ENetPeer * player_peer = player->data;
DebugPrintf("network: telling new player %d to connect to synchers\n",
PEER_ID(player_peer));
for( x = 0; x < enet_host->peerCount; x++ )
{
ENetPeer * peer = &enet_host->peers[x];
if( PEER_STATE(peer) == SYNCHING && player_peer != peer )
{
p2p_connect_packet_t packet;
packet.type = CONNECT;
packet.id = PEER_ID(peer);
packet.address = peer->address;
packet.address.port = PEER_CONNECT_PORT(peer);
network_send( player, &packet, sizeof(packet),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network: sending connect for syncher %d\n",
PEER_ID(player_peer));
}
}
}
static void send_new_player_event( ENetPeer * peer )
{
p2p_id_packet_t packet;
packet.type = NEW_PLAYER;
packet.id = PEER_ID(peer);
network_broadcast( &packet, sizeof(packet),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network: sent new player event for player (%d) to everyone\n",
PEER_ID(peer));
}
static void send_new_player_event_for_existing_players( network_player_t * joiner )
{
network_player_t * player = network_players.first;
DebugPrintf("network: sending new player events to new player %d for existing players\n",
PLAYER_ID(joiner));
while( player )
{
p2p_id_packet_t packet;
packet.type = NEW_PLAYER;
packet.id = PLAYER_ID(player);
network_send( joiner, &packet, sizeof(packet),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network: sent new player event for existing player %d\n",
PLAYER_ID(player));
player = player->next;
}
// still need to send a new player event for me
{
p2p_id_packet_t packet;
packet.type = NEW_PLAYER;
packet.id = my_id;
network_send( joiner, &packet, sizeof(packet),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network: sent new player event for existing player %d (HOST)\n", my_id );
}
}
static void new_player( ENetPeer * peer )
{
network_peer_data_t * peer_data = peer->data;
// we are not even connected yet...
// only thing that should cause this is host telling us to connect to someone
// but new_connection hasn't fired yet and he's giving us a new player event
if(peer_data->state == CONNECTING || peer_data->player->name[0] == 0)
{
DebugPrintf("network error: "
"got new player message for %hhu "
"but they are still in state=CONNECTING or their name got wiped out! "
"This new player event will be ignored !!!\n", peer_data->id);
return;
}
DebugPrintf("network: player %s joined the game\n",
peer_data->player->name);
if( i_am_host )
{
send_new_player_event( peer ); // player never receives his own new player event
send_new_player_event_for_existing_players( peer_data->player );
tell_peer_to_connect_to_synchers( peer_data->player );
}
// once host starts sending me new player events then I'm in the game
if( network_state != NETWORK_CONNECTED )
{
DebugPrintf("network: we have joined the game\n");
network_state = NETWORK_CONNECTED;
network_event( NETWORK_JOIN, NULL ); // we joined the game
DebugPrintf("network: host will now send us new player events for all existing players\n");
}
append_player( peer_data->player );
peer_data->state = PLAYING;
network_event( NETWORK_JOIN, peer_data->player ); // new player joined
if( peer == host )
network_event( NETWORK_HOST, peer_data->player ); // host has been set
// for simplicity user may desire to rely on only name events to set the name
DebugPrintf("network: firing off name event because we received a new player event\n");
network_event( NETWORK_NAME, peer_data->player );
}
static void peer_connected_to( ENetPeer * peer, peer_id_t id )
{
network_peer_data_t * joiner_data;
ENetPeer * joiner = find_peer_by_id( id );
if( ! joiner )
{
DebugPrintf("network security: failed to find joiner by id %d\n", id);
return;
}
if( joiner->state == PLAYING )
{
network_peer_data_t * peer_data = peer->data;
DebugPrintf("network security: player %d @ %s sent us a succesfully-connected-to-new-player message for player %d but player %d is already in the game!\n",
peer_data->id, address_to_str(&peer->address), id, id);
return;
}
joiner_data = joiner->data;
add_peer_to_connected_list( joiner_data->connected_peers, peer );
if( all_players_on_connected_list( joiner_data->connected_peers, joiner ) )
{
DebugPrintf("network: all players have connected to player %d\n",
joiner_data->id);
new_player( joiner );
}
}
static void pump_synchers( void )
{
size_t x;
for( x = 0; x < enet_host->peerCount; x++ )
{
ENetPeer * peer = &enet_host->peers[x];
network_peer_data_t * peer_data = (network_peer_data_t*) peer->data;
if( PEER_STATE(peer) == SYNCHING )
{
if( all_players_on_connected_list( peer_data->connected_peers, peer ) )
{
DebugPrintf("network: player %d is fully synched firing new player revent\n",
peer_data->id);
new_player( peer );
}
}
}
}
static void new_packet( ENetEvent * event )
{
ENetPeer * peer = event->peer;
network_peer_data_t * peer_data = (network_peer_data_t*) peer->data;
if ( event->channelID == system_channel )
{
p2p_packet_t * packet = ((p2p_packet_t *) event->packet->data);
//DebugPrintf("network extra: new system packet type %d\n",
// packet->type);
switch( packet->type )
{
case YOUR_ID:
{
if( peer == host )
{
p2p_id_packet_t * packet = (p2p_id_packet_t*) event->packet->data;
my_id = packet->id;
DebugPrintf("network: host says my id is %d\n", my_id );
// ack my id
{
p2p_packet_t packet;
packet.type = ACK_MY_ID;
enet_send( peer, &packet, sizeof(packet),
convert_flags(NETWORK_RELIABLE), system_channel, NO_FLUSH );
DebugPrintf("network: acking my id\n");
}
}
else
{
DebugPrintf("network security: %s tried to give me my id but they aren't the host\n",
address_to_str(&peer->address));
}
}
break;
case MY_ID:
{
p2p_id_packet_t * packet = (p2p_id_packet_t*) event->packet->data;
if ( ! i_am_host )
{
peer_data->id = packet->id;
DebugPrintf("network: %s says their id is %d\n",
address_to_str(&peer->address), peer_data->id );
// ack the player id
{
p2p_packet_t packet;
packet.type = ACK_ID;
enet_send( peer, &packet, sizeof(packet),
convert_flags(NETWORK_RELIABLE), system_channel, NO_FLUSH );
DebugPrintf("network: acking his id\n");
}
}
else
{
DebugPrintf("network security: %s is telling me their id (%d) but I'm the host\n",
address_to_str(&peer->address), packet->id);
}
}
break;
case ACK_ID:
DebugPrintf("network: player %d has acked my id\n", peer_data->id);
if( peer_data->state == ACKING ) // we were waiting for him to ack so we could tell host
{
if( peer_data->id == NO_ID )
{
DebugPrintf("network security: %s has ack'd my id before telling me their id!\n",
address_to_str(&peer->address));
}
else // tell the host that we connected successfully
{
network_peer_data_t * host_data = host->data;
p2p_id_packet_t packet;
packet.type = CONNECTED_TO;
packet.id = peer_data->id;
network_send( host_data->player, &packet, sizeof(packet),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network: telling host that we have connected to new player %d\n",
peer_data->id);
peer_data->state = CONNECTED;
}
}
break;
case CONNECT_PORT:
{
p2p_number_packet_t * packet = (p2p_number_packet_t*) event->packet->data;
peer_data->connect_port = packet->number;
DebugPrintf("network: player %d says their connect port is %d\n",
peer_data->id, peer_data->connect_port );
}
break;
case ACK_MY_ID:
if( i_am_host )
{
DebugPrintf("network: player %d has acked their id\n",
peer_data->id);
if( peer_data->connect_port )
{
// tell everyone to connect to new player
p2p_connect_packet_t packet;
packet.type = CONNECT;
packet.id = peer_data->id;
packet.address = peer->address;
packet.address.port = peer_data->connect_port;
network_broadcast( &packet, sizeof(packet),
NETWORK_RELIABLE, system_channel );
peer_data->state = SYNCHING;
DebugPrintf("network: telling everyone to connect to player %d\n",
peer_data->id);
}
else
{
DebugPrintf("network security: %s acked their id before telling me their connect port!\n",
address_to_str(&peer->address));
}
}
else
{
DebugPrintf("network security: %s acked their id but I'm not the host!\n",
address_to_str(&peer->address));
}
break;
case NEW_PLAYER:
if( ! i_am_host )
{
if( peer == host )
{
p2p_id_packet_t * packet = (p2p_id_packet_t*) event->packet->data;
ENetPeer * new_peer = find_peer_by_id( packet->id );
DebugPrintf("network: host sent us new player event for player %d\n",
packet->id);
if( new_peer )
{
new_player( new_peer );
}
else
{
DebugPrintf("network security: failed to find new player by id %d\n",
packet->id);
break;
}
}
// peer is not the host
else
{
DebugPrintf("network security: %s sent us new-player event but they are not the host\n",
address_to_str(&peer->address));
}
}
// I am not host
else
{
DebugPrintf("network security: %s sent us new player event but I am the host\n",
address_to_str(&peer->address));
}
break;
case NAME:
{
p2p_name_packet_t* packet = (p2p_name_packet_t*) event->packet->data;
char* name = packet->name;
DebugPrintf("network: player %d has updated their name from '%s' to '%s'\n",
peer_data->id, peer_data->player->name, name );
update_player_name( peer_data->player, name );
if( peer_data->state == PLAYING )
network_event( NETWORK_NAME, peer_data->player );
}
break;
case DISCONNECT:
{
// host is telling us to disconnect from someone
if( peer == host )
{
p2p_id_packet_t * packet = (p2p_id_packet_t*) event->packet->data;
ENetPeer * bad_peer = find_peer_by_id( packet->id );
// bank if we can't find the peer
if(!bad_peer)
{
DebugPrintf("network security: host told us to disconnect from unknown player %d\n",
packet->id );
break;
}
// if not the host
else
{
DebugPrintf("network: host told us to disconnect from player %d\n",
packet->id);
// dissconnect message will fire and cleanup player
enet_peer_disconnect(bad_peer,HOST_SAID_SO);
}
}
else
{
DebugPrintf("network security: %s told us to disconnect but they are not the host\n",
address_to_str(&peer->address));
}
}
break;
case LOST_CONNECTION:
{
// player is telling the host that he lost connection to someone
p2p_id_packet_t * packet = (p2p_id_packet_t*) event->packet->data;
ENetPeer * bad_peer = find_peer_by_id( packet->id );
if(!bad_peer)
{
DebugPrintf("network security: player %d told us he lost connection from unknown player %d\n",
peer_data->id, packet->id );
break;
}
if( i_am_host )
{
if( PEER_STATE(bad_peer) == SYNCHING )
{
// tell everyone to drop the connection
DebugPrintf("network: dropping player %d because they failed to synch with %d\n",
packet->id, peer_data->id );
// lost connection event will fire
// this will send a DISCONNECT message for us to everyone
// and will cleanup the peer and everything else
enet_peer_disconnect_later(bad_peer,FAILED_SYNCH | peer_data->id << 8);
}
if( PEER_STATE(bad_peer) == PLAYING )
{
// tell them to reconnect cause bad_peer is valid
p2p_connect_packet_t packet2;
packet2.type = CONNECT;
packet2.id = packet->id;
packet2.address = bad_peer->address;
packet2.address.port = peer_data->connect_port;
network_send( peer_data->player, &packet2, sizeof(packet2),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network: telling player %d to reconnect to valid player %d\n",
peer_data->id, packet->id );
}
}
else
{
DebugPrintf("network security: player %d told us he lost-connection but we are not host\n",
peer_data->id);
}
}
break;
case CONNECTED_TO:
{
p2p_id_packet_t * packet = (p2p_id_packet_t*) event->packet->data;
// player tells host that he successfully connected to new connection
if( i_am_host )
{
// if we are also connected to this connection
ENetPeer * target = find_peer_by_id( packet->id );
if( target )
{
DebugPrintf("network: player %d succesfully connected with player %d\n",
peer_data->id, packet->id);
peer_connected_to( peer, packet->id );
}
// if we are not connected to this connection
else
{
// tell the player to drop the connection
p2p_id_packet_t packet2;
packet2.type = DISCONNECT;
packet2.id = packet->id;
network_send( peer_data->player, &packet2, sizeof(packet2),
NETWORK_RELIABLE, system_channel );
DebugPrintf("network security: telling player %d to drop unknown player %d\n",
peer_data->id, packet->id);
}
}
else
{
DebugPrintf("network security: %s told me they connected to someone but I'm not the host\n",
address_to_str(&peer->address));
}
}
break;
case CONNECT:
{
p2p_connect_packet_t * packet = (p2p_connect_packet_t*) event->packet->data;
ENetAddress * address = &packet->address;
// host tells player to connect to a new connection
if( ! i_am_host )
{
ENetPeer* new_peer;
network_peer_data_t * new_peer_data;
DebugPrintf("network: host told us to connect to player %d address %s\n",
packet->id, address_to_str( address ));
new_peer = enet_host_connect( enet_host, address, max_channels );
if(!new_peer)
{
DebugPrintf("network: enet host connect returned NULL.\n");
}
else
{
new_peer_data = new_peer->data;
new_peer_data->state = CONNECTING;
new_peer_data->id = packet->id;
}
}
// I'm the host
else
{
DebugPrintf("network: %s just told me to connect to someone but I am the host!\n",
address_to_str(&peer->address));
}
}
break;
default:
DebugPrintf("network security: received unknown packet type: %d\n",
packet->type);
}
}
// application packet
else
{
// valid player
if( peer_data->state == PLAYING )
{
network_packet_t packet;
packet.size = (int) event->packet->dataLength;
packet.data = (void*) event->packet->data;
packet.from = peer_data->player;
network_event( NETWORK_DATA, &packet );
}
// not a valid player
else
{
DebugPrintf("network security: got application packet from non player at address %s\n",
address_to_str(&peer->address) );
}
}
enet_packet_destroy( event->packet );
}
/*
*
* net.h implementation
*
*/
network_return_t network_setup( char* player_name, int local_port )
{
network_cleanup();
DebugPrintf("network: setup name='%s' connect_port=%d\n",
player_name, local_port);
my_player_name = player_name;
network_state = NETWORK_DISCONNECTED;
return enet_setup( NULL, local_port );
}
int network_join( char* address, int port )
{
if( enet_host == NULL )
return 0;
DebugPrintf("network: join address '%s', local port %d\n",
address,port);
i_am_host = 0;
my_id = NO_ID;
if( network_state == NETWORK_CONNECTING )
{
DebugPrintf("network: join already connecting...\n");
return 1;
}
network_state = NETWORK_CONNECTING;
return enet_connect( address, port );
}
void network_host( void )
{
if( enet_host == NULL ) return;
DebugPrintf("network: starting a host session\n");
i_am_host = 1;
my_id = LOWEST_ID;
host = NULL;
network_state = NETWORK_CONNECTED;
}
void network_cleanup( void )
{
if( enet_host == NULL ) return;
DebugPrintf("network: cleanup\n");
enet_host_flush(enet_host); // send any pending packets
disconnect_all();
destroy_players();
cleanup_peers();
enet_cleanup();
network_state = NETWORK_DISCONNECTED;
my_id = NO_ID;
i_am_host = 0;
}
void network_send(
network_player_t* player, void* data, int size,
network_flags_t flags, int channel
)
{
if( enet_host == NULL ) return;
enet_send( (ENetPeer*) player->data, data, size,
convert_flags(flags), channel, (flags & NETWORK_FLUSH) );
}
void network_broadcast( void* data, int size, network_flags_t flags, int channel )
{
network_player_t * player = network_players.first;
ENetPacket * packet;
if(!data)
{
DebugPrintf("network: broadcast received bad pointer.\n");
return;
}
packet = enet_packet_create( data, size, convert_flags( flags ) );
if( enet_host == NULL ) return;
if( packet == NULL )
{
DebugPrintf("network: broadcast failed to create packet.\n");
return;
}
while(player)
{
ENetPeer * peer = (ENetPeer*) player->data;
if( ! peer ) return;
enet_send_packet( peer, packet, channel, NO_FLUSH );
player = player->next;
}
enet_host_flush( enet_host );
}
void network_pump()
{
ENetEvent event;
if( enet_host == NULL ) return;
while( enet_host_service( enet_host, &event, 0 ) > 0 )
{
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
new_connection( event.peer );
break;
case ENET_EVENT_TYPE_DISCONNECT:
lost_connection( event.peer, event.data );
break;
case ENET_EVENT_TYPE_RECEIVE:
new_packet( &event );
break;
}
}
update_players();
if( i_am_host)
pump_synchers();
}
void network_set_player_name( char* name )
{
network_player_t * player = network_players.first;
ENetPacket * packet;
p2p_name_packet_t name_packet;
if( enet_host == NULL ) return;
DebugPrintf("network: set my player name to %s\n",name);
name_packet.type = NAME;
strncpy( name_packet.name, name, NETWORK_MAX_NAME_LENGTH-1 );
packet = enet_packet_create( &name_packet, sizeof(name_packet),
convert_flags(NETWORK_RELIABLE|NETWORK_FLUSH) );
if( packet == NULL )
{
DebugPrintf("network: set player name failed to create packet\n");
return;
}
while(player)
{
enet_send_packet( (ENetPeer*) player->data, packet, system_channel, NO_FLUSH );
player = player->next;
}
enet_host_flush( enet_host );
}
#endif