From: Thomas Perl Date: Tue, 4 Aug 2009 23:27:10 +0000 (+0200) Subject: First cut of multi-player support X-Git-Tag: tennix-1.1~9 X-Git-Url: https://repo.or.cz/w/tennix.git/commitdiff_plain/d59da3a9278f045909f951ec62539776bc3be66a First cut of multi-player support And we are proud of it :) --- diff --git a/README.Multiplayer b/README.Multiplayer new file mode 100644 index 0000000..035d9f0 --- /dev/null +++ b/README.Multiplayer @@ -0,0 +1,19 @@ + +This is done using two computers, the first is the faster one +and the second one is the slower one. If both computers are +equally powerful, just chose the faster one randomly. + +On the faster computer, use: + ./tennix -n REMOTEHOST LOCALPORT REMOTEPORT -m + +On the slower computer, use: + ./tennix -n REMOTEHOST LOCALPORT REMOTEPORT + +So, if your faster computer is called "fast.lan" and the slower +computer is called "slow.lan", you could use the following setup: + + user@fast:~$ ./tennix -n slow.lan 1234 4321 -m + user@slow:~$ ./tennix -n fast.lan 4321 1234 + + -- thp, Wed, 05 Aug 2009 01:26:42 +0200 + diff --git a/game.c b/game.c index cd816ab..5b83a62 100644 --- a/game.c +++ b/game.c @@ -159,7 +159,7 @@ GameState* gamestate_load(const char* filename) } -void gameloop(GameState *s) { +void gameloop(GameState *s, TennixNet* c) { Uint32 ot = SDL_GetTicks(); Uint32 nt; Uint32 dt = GAME_TICKS; @@ -167,6 +167,7 @@ void gameloop(GameState *s) { Uint32 accumulator = 0; bool quit = false; int p; + int i=0; RenderState r = { SOUND_EVENT_NONE, REFEREE_COUNT, @@ -178,6 +179,8 @@ void gameloop(GameState *s) { NULL, }; + assert(c != NULL); + /* Catch-up with existing sound events */ r.sound_events = s->sound_events; @@ -212,7 +215,15 @@ void gameloop(GameState *s) { while( accumulator >= dt) { step(s); - quit = handle_input(s); + + network_receive(c); + network_get_gamestate(c, s); + + if ((i++) % 10 == 0) { + network_send_state(c, s); + } + + quit = handle_input(s, c); accumulator -= dt; } @@ -241,9 +252,6 @@ void gameloop(GameState *s) { void step(GameState* s) { bool ground_event = false; int p; - fprintf(stderr, "ball(%f, %f, %f, %f, %f, %f)\n", - s->ball.x, s->ball.y, s->ball.z, - s->ball.move_x, s->ball.move_y, s->ball.move_z); s->ball.z += s->ball.move_z; if (!s->ball.inhibit_gravity) { @@ -396,8 +404,9 @@ void step(GameState* s) { } } -bool handle_input(GameState* s) { +bool handle_input(GameState* s, TennixNet* c) { static NetworkGameState tmp; + static NetworkInputData net_input; Uint8* keys = NULL; int p; @@ -410,10 +419,18 @@ bool handle_input(GameState* s) { net_unserialize_gamestate(&tmp, s); } + network_get_input(c, &net_input); if (s->winner == WINNER_NONE) { for (p=1; p<=MAXPLAYERS; p++) { if( PLAYER(s, p).type == PLAYER_TYPE_HUMAN) { + if (PLAYER(s, p).input->type == INPUT_TYPE_NETWORK) { + memcpy(&(PLAYER(s, p).input->net), &net_input, + sizeof(NetworkInputData)); + } input_human(s, p); + if (PLAYER(s, p).input->type != INPUT_TYPE_NETWORK) { + network_send_input(c, &(PLAYER(s, p).input->net)); + } } else { input_ai(s, p); } @@ -678,6 +695,9 @@ void input_human(GameState* s, int player) { } PLAYER(s, player).y += move_y; PLAYER(s, player).accelerate *= PLAYER_ACCEL_INCREASE; + if (PLAYER(s, player).accelerate > 190) { + PLAYER(s, player).accelerate = 190; + } } else { PLAYER(s, player).accelerate = PLAYER_ACCEL_DEFAULT; } diff --git a/game.h b/game.h index b76a36d..249d6e1 100644 --- a/game.h +++ b/game.h @@ -30,6 +30,7 @@ #include "graphics.h" #include "input.h" + #define SETS_TO_WIN 3 #define MAXPLAYERS 2 @@ -40,12 +41,6 @@ # define BALL_STATES 4 #endif -typedef unsigned char bool; -enum { - false, - true -}; - typedef unsigned char referee_t; enum { REFEREE_NORMAL, @@ -279,9 +274,9 @@ int gamestate_save(GameState* s, const char* filename); GameState* gamestate_load(const char* filename); /* Game module functions */ -void gameloop(GameState*); +void gameloop(GameState*, TennixNet*); void step(GameState*); -bool handle_input(GameState*); +bool handle_input(GameState*, TennixNet*); void render(const GameState*, RenderState*); float get_move_y( GameState*, unsigned char); void input_human(GameState*, int); diff --git a/input.c b/input.c index 73e3069..9e6ce96 100644 --- a/input.c +++ b/input.c @@ -100,6 +100,12 @@ void init_input() #endif devices_count++; + /* network peer */ + devices[devices_count].type = INPUT_TYPE_NETWORK; + devices[devices_count].icon = GR_INPUT_AI; /* FIXME - network icon! */ + strcpy(devices[devices_count].name, "Network player"); + devices_count++; + /* joysticks */ n = SDL_NumJoysticks(); for (x=0; x= min && v < max); + return (Uint8)((1U<<7) * (v-min) / (max-min)); +} + +float +input_unpack_axis(Uint8 v) +{ + static const float min = -1.2; + static const float max = 1.2; + assert(v < (1U<<7)); + return v * (max-min) / (1U<<7) + min; +} + InputDevice* find_input_devices(unsigned int* count) { @@ -188,21 +212,23 @@ const char* input_device_get_name(InputDevice* d) float input_device_get_axis(InputDevice* d, unsigned const char axis) { Uint8 *keystate; Uint8 mb; + Uint8 net_value; + float result = 0.0; SDL_PumpEvents(); if (d->type == INPUT_TYPE_KEYBOARD) { keystate = SDL_GetKeyState(NULL); if (axis == INPUT_AXIS_X) { - return 1.0*keystate[d->right_key] + -1.0*keystate[d->left_key]; + result = 1.0*keystate[d->right_key] + -1.0*keystate[d->left_key]; } else { - return 1.0*keystate[d->down_key] + -1.0*keystate[d->up_key]; + result = 1.0*keystate[d->down_key] + -1.0*keystate[d->up_key]; } } else if (d->type == INPUT_TYPE_JOYSTICK) { if (axis == INPUT_AXIS_X) { - return JOYSTICK_PERCENTIZE(SDL_JoystickGetAxis(d->joystick, d->x_axis*2)); + result = JOYSTICK_PERCENTIZE(SDL_JoystickGetAxis(d->joystick, d->x_axis*2)); } else { - return JOYSTICK_PERCENTIZE(SDL_JoystickGetAxis(d->joystick, 1+d->y_axis*2)); + result = JOYSTICK_PERCENTIZE(SDL_JoystickGetAxis(d->joystick, 1+d->y_axis*2)); } } else if (d->type == INPUT_TYPE_MOUSE) { mb = SDL_GetMouseState(&d->mx, &d->my); @@ -218,41 +244,66 @@ float input_device_get_axis(InputDevice* d, unsigned const char axis) { } else { if (fabsf(d->my - d->player_y) > PLAYER_MOVE_Y) { if (d->my > d->player_y) { - return 1.0; + result = 1.0; } else if (d->my < d->player_y) { - return -1.0; + result = -1.0; } } } #ifdef TENNIX_PYTHON } else if (d->type == INPUT_TYPE_AI_PYTHON) { - return tennixpy_bot_get_axis(d->py_bot, axis); + result = tennixpy_bot_get_axis(d->py_bot, axis); #endif + } else if (d->type == INPUT_TYPE_NETWORK) { + if (axis == INPUT_AXIS_X) { + result = input_unpack_axis(d->net.x); + } else if (axis == INPUT_AXIS_Y) { + result = input_unpack_axis(d->net.y); + } } else { - /* unimplemented */ + assert(0/*unimplemented*/); } - return 0.0; + + net_value = input_pack_axis(result); + if (axis == INPUT_AXIS_X) { + d->net.x = net_value; + } else if (axis == INPUT_AXIS_Y) { + d->net.y = net_value; + } + + return result; } char input_device_get_key(InputDevice* d, unsigned const char key) { Uint8 mb; + char result = 0; SDL_PumpEvents(); if (d->type == INPUT_TYPE_KEYBOARD) { - return SDL_GetKeyState(NULL)[d->input_keys[key]]; + result = SDL_GetKeyState(NULL)[d->input_keys[key]]; } else if (d->type == INPUT_TYPE_JOYSTICK) { - return SDL_JoystickGetButton(d->joystick, d->input_keys[key]); + result = SDL_JoystickGetButton(d->joystick, d->input_keys[key]); } else if (d->type == INPUT_TYPE_MOUSE) { mb = SDL_GetMouseState(NULL, NULL); - return (mb & d->input_keys[key]) != 0; + result = (mb & d->input_keys[key]) != 0; #ifdef TENNIX_PYTHON } else if (d->type == INPUT_TYPE_AI_PYTHON) { - return tennixpy_bot_get_key(d->py_bot, key); + result = tennixpy_bot_get_key(d->py_bot, key); #endif + } else if (d->type == INPUT_TYPE_NETWORK) { + result = (d->net.keys & (1<net.keys |= (1<net.keys &= ~(1<peer), (const char*)host, 0) == 0); + connection->base_port_local = local_port; + connection->base_port_remote = remote_port; + connection->input_packet = SDLNet_AllocPacket(sizeof(NetworkInputData)); + connection->state_packet = SDLNet_AllocPacket(sizeof(NetworkGameState)); + connection->input_available = false; + connection->state_available = false; + connection->send_input_socket = SDLNet_UDP_Open(0); + connection->send_state_socket = SDLNet_UDP_Open(0); + connection->recv_input_socket = SDLNet_UDP_Open(connection->base_port_local); + connection->recv_state_socket = SDLNet_UDP_Open(connection->base_port_local+1); + connection->master = false; + + return connection; +} + +void +network_disconnect(TennixNet* connection) +{ + if (connection != NULL) + { + SDLNet_UDP_Close(connection->send_input_socket); + SDLNet_UDP_Close(connection->send_state_socket); + SDLNet_UDP_Close(connection->recv_input_socket); + SDLNet_UDP_Close(connection->recv_state_socket); + SDLNet_FreePacket(connection->input_packet); + SDLNet_FreePacket(connection->state_packet); + free(connection); + } +} + +void +network_set_master(TennixNet* connection, bool master) +{ + connection->master = master; +} + +void +network_send_input(TennixNet* connection, NetworkInputData* src) +{ + memcpy(connection->input_packet->data, src, sizeof(NetworkInputData)); + connection->input_packet->address.host = connection->peer.host; + SDLNet_Write16(connection->base_port_remote, &(connection->input_packet->address.port)); + connection->input_packet->channel = -1; + connection->input_packet->len = sizeof(NetworkInputData); + SDLNet_UDP_Send(connection->send_input_socket, -1, connection->input_packet); +} + +void +network_send_state(TennixNet* connection, GameState* src) +{ + if (connection->master) { + net_serialize_gamestate(src, (NetworkGameState*)(connection->state_packet->data)); + connection->state_packet->address.host = connection->peer.host; + SDLNet_Write16(connection->base_port_remote+1, &(connection->state_packet->address.port)); + connection->state_packet->channel = -1; + connection->state_packet->len = sizeof(NetworkGameState); + assert(SDLNet_UDP_Send(connection->send_state_socket, -1, connection->state_packet)!=0); + } +} + +void +network_receive(TennixNet* connection) +{ + connection->input_packet->len = sizeof(NetworkInputData); + while (SDLNet_UDP_Recv(connection->recv_input_socket, connection->input_packet)) { + connection->input_available = true; + } + + if (!(connection->master)) { + connection->state_packet->len = sizeof(NetworkGameState); + while (SDLNet_UDP_Recv(connection->recv_state_socket, connection->state_packet)) { + connection->state_available = true; + } + } +} + +void +network_get_input(TennixNet* connection, NetworkInputData* dest) +{ + if (connection->input_available) { + memcpy(dest, connection->input_packet->data, sizeof(NetworkInputData)); + connection->input_available = false; + } +} + +void +network_get_gamestate(TennixNet* connection, GameState* dest) +{ + if (connection->state_available) { + net_unserialize_gamestate((NetworkGameState*) + (connection->state_packet->data), dest); + connection->state_available = false; + } +} + void net_serialize_ball(const Ball* src, NetworkBall* dest) { - SDLNet_Write32(pack_float(src->x, -WIDTH, WIDTH), &(dest->x)); - SDLNet_Write32(pack_float(src->y, -HEIGHT, HEIGHT), &(dest->y)); + SDLNet_Write32(pack_float(src->x, -WIDTH, WIDTH*2), &(dest->x)); + SDLNet_Write32(pack_float(src->y, -HEIGHT, HEIGHT*2), &(dest->y)); SDLNet_Write32(pack_float(src->z, -50, 50), &(dest->z)); SDLNet_Write32(pack_float(src->move_x, -50, 50), &(dest->move_x)); SDLNet_Write32(pack_float(src->move_y, -50, 50), &(dest->move_y)); @@ -61,8 +177,8 @@ net_serialize_ball(const Ball* src, NetworkBall* dest) void net_unserialize_ball(const NetworkBall* src, Ball* dest) { - dest->x = unpack_float(SDLNet_Read32(&(src->x)), -WIDTH, WIDTH); - dest->y = unpack_float(SDLNet_Read32(&(src->y)), -HEIGHT, HEIGHT); + dest->x = unpack_float(SDLNet_Read32(&(src->x)), -WIDTH, WIDTH*2); + dest->y = unpack_float(SDLNet_Read32(&(src->y)), -HEIGHT, HEIGHT*2); dest->z = unpack_float(SDLNet_Read32(&(src->z)), -50, 50); dest->move_x = unpack_float(SDLNet_Read32(&(src->move_x)), -50, 50); dest->move_y = unpack_float(SDLNet_Read32(&(src->move_y)), -50, 50); @@ -75,29 +191,29 @@ net_unserialize_ball(const NetworkBall* src, Ball* dest) void net_serialize_player(const Player* src, NetworkPlayer* dest) { - SDLNet_Write32(pack_float(src->x, 0, WIDTH), &(dest->x)); - SDLNet_Write32(pack_float(src->y, 0, HEIGHT), &(dest->y)); + SDLNet_Write32(pack_float(src->x, 0, WIDTH*1.2), &(dest->x)); + SDLNet_Write32(pack_float(src->y, 0, HEIGHT*1.2), &(dest->y)); SDLNet_Write32(pack_float(src->power, 0, 110), &(dest->power)); dest->use_power = src->use_power; dest->score = src->score; dest->desire = src->desire; dest->game = src->game; memcpy(dest->sets, src->sets, sizeof(unsigned char)*(SETS_TO_WIN*2)); - SDLNet_Write32(pack_float(src->accelerate, 0, 1), &(dest->accelerate)); + SDLNet_Write32(pack_float(src->accelerate, 0, 200), &(dest->accelerate)); } void net_unserialize_player(const NetworkPlayer* src, Player* dest) { - dest->x = unpack_float(SDLNet_Read32(&(src->x)), 0, WIDTH); - dest->y = unpack_float(SDLNet_Read32(&(src->y)), 0, HEIGHT); + dest->x = unpack_float(SDLNet_Read32(&(src->x)), 0, WIDTH*1.2); + dest->y = unpack_float(SDLNet_Read32(&(src->y)), 0, HEIGHT*1.2); dest->power = unpack_float(SDLNet_Read32(&(src->power)), 0, 110); dest->use_power = src->use_power; dest->score = src->score; dest->desire = src->desire; dest->game = src->game; memcpy(dest->sets, src->sets, sizeof(unsigned char)*(SETS_TO_WIN*2)); - dest->accelerate = unpack_float(SDLNet_Read32(&(src->accelerate)), 0, 1); + dest->accelerate = unpack_float(SDLNet_Read32(&(src->accelerate)), 0, 200); } void diff --git a/network.h b/network.h index b5e0e9a..298187c 100644 --- a/network.h +++ b/network.h @@ -25,6 +25,12 @@ #define __NETWORK_H #include "game.h" +#include "input.h" + +#include + +#define TENNIXNET_STATE_PORT 1448 +#define TENNIXNET_INPUT_PORT 1449 typedef struct { Uint32 x; @@ -45,9 +51,9 @@ typedef struct { bool use_power; unsigned char score; unsigned char desire; - char game; /* score for the current game */ - unsigned char sets[SETS_TO_WIN*2]; /* score for each set */ - Uint32 accelerate; /* a value [0..1] how fast the user accelerates */ + char game; + unsigned char sets[SETS_TO_WIN*2]; + Uint32 accelerate; } NetworkPlayer; typedef struct { @@ -66,6 +72,36 @@ typedef struct { } NetworkGameState; void +init_network(); + +void +uninit_network(); + +TennixNet* +network_connect(const char* host, Uint16 local_port, Uint16 remote_port); + +void +network_disconnect(TennixNet* connection); + +void +network_set_master(TennixNet* connection, bool master); + +void +network_send_input(TennixNet* connection, NetworkInputData* src); + +void +network_send_state(TennixNet* connection, GameState* src); + +void +network_receive(TennixNet* connection); + +void +network_get_input(TennixNet* connection, NetworkInputData* dest); + +void +network_get_gamestate(TennixNet* connection, GameState* dest); + +void net_serialize_ball(const Ball* src, NetworkBall* dest); void diff --git a/tennix.c b/tennix.c index 10cf5a5..5b6d44b 100644 --- a/tennix.c +++ b/tennix.c @@ -36,6 +36,7 @@ #include "graphics.h" #include "sound.h" #include "input.h" +#include "network.h" #include "util.h" #include "animation.h" @@ -105,6 +106,11 @@ int main( int argc, char** argv) { Animation *intro; AnimationState *intro_playback; float wiggle; + TennixNet* connection = NULL; + char* net_host = NULL; + int net_port_local; + int net_port_remote; + bool net_master = false; MenuButton btn_back = { NULL, /* not needed for image-based button */ @@ -194,6 +200,20 @@ int main( int argc, char** argv) { else if (OPTION_SET("--benchmark", "-b")) { benchmark = true; } + else if (OPTION_SET("--network", "-n")) { + net_host = OPTION_VALUE; + assert(OPTION_VALUE != NULL); + OPTION_VALUE_PROCESSED; + net_port_local = atoi(OPTION_VALUE); + assert(OPTION_VALUE != NULL); + OPTION_VALUE_PROCESSED; + net_port_remote = atoi(OPTION_VALUE); + assert(OPTION_VALUE != NULL); + OPTION_VALUE_PROCESSED; + } + else if (OPTION_SET("--master", "-m")) { + net_master = true; + } else { fprintf(stderr, "Ignoring unknown option: %s\n", argv[i]); } @@ -230,6 +250,12 @@ int main( int argc, char** argv) { init_graphics(); init_sound(); init_input(); + init_network(); + + if (net_host != NULL) { + connection = network_connect(net_host, net_port_local, net_port_remote); + network_set_master(connection, net_master); + } menu_button_init(&btn_back); menu_button_init(&btn_start); @@ -268,7 +294,7 @@ int main( int argc, char** argv) { PLAYER(g, 1).type = PLAYER_TYPE_AI; PLAYER(g, 2).type = PLAYER_TYPE_AI; g->location = &(locations[0]); - gameloop(g); + gameloop(g, connection); free(g); exit(0); } @@ -416,7 +442,7 @@ int main( int argc, char** argv) { exit(EXIT_FAILURE); } start_fade(); - gameloop(current_game); + gameloop(current_game, connection); SDL_Delay(150); while(SDL_PollEvent(&e)); #ifdef ENABLE_FPS_LIMIT @@ -757,6 +783,7 @@ int main( int argc, char** argv) { uninit_graphics(); uninit_input(); + uninit_network(); SDL_Quit(); return 0; diff --git a/tennix.h b/tennix.h index b85eea7..5902f5c 100644 --- a/tennix.h +++ b/tennix.h @@ -27,6 +27,33 @@ #include #include +#include + +typedef unsigned char bool; +enum { + false, + true +}; + +typedef struct { + UDPsocket send_input_socket; + UDPsocket recv_input_socket; + UDPsocket send_state_socket; + UDPsocket recv_state_socket; + + UDPpacket* input_packet; + UDPpacket* state_packet; + + IPaddress peer; + + bool input_available; + bool state_available; + + Uint16 base_port_local; + Uint16 base_port_remote; + + bool master; +} TennixNet; #ifdef DELUXE_EDITION # define HAVE_VOICE_FILES