diff --git a/src/instance/game_instance.c b/src/instance/game_instance.c index 0069c2a..cd7917d 100644 --- a/src/instance/game_instance.c +++ b/src/instance/game_instance.c @@ -28,7 +28,7 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett return false; } - instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session)); + instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session)); // LEAK if(settings.online) { if(instance->game_networking.api != NULL) { @@ -57,7 +57,7 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett bool game_instance_join_session(Game_Instance *instance, const char *session_id) { assert(instance != NULL); - printf("Trying to join a session ...\n"); + printf("Trying to join a session '%s' ...\n", session_id); if(instance->game_session != NULL) { printf("Failed. A session is already active.\n"); @@ -68,8 +68,14 @@ bool game_instance_join_session(Game_Instance *instance, const char *session_id) printf("Failed. Network is already active.\n"); return false; } - - return false; + + instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session)); // LEAK + + if(!networking_try_join(&instance->game_networking, instance->game_session, session_id)) { + return false; + } + + return true; } bool game_instance_push_local_input(Game_Instance *instance, Simulation_Game_Input input) { diff --git a/src/presentation/states/state_main_menu.c b/src/presentation/states/state_main_menu.c index b5dfe77..37c7ae1 100644 --- a/src/presentation/states/state_main_menu.c +++ b/src/presentation/states/state_main_menu.c @@ -90,6 +90,14 @@ static void state_render(Presentation_State *state) { } if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "Join")) { ctx->mode = Main_Menu_Mode_Multiplayer_Join; + + memset(&ctx->session_id_input, 0, sizeof(ctx->session_id_input)); + ctx->session_id_input[0] = 'A'; + ctx->session_id_input[1] = 'B'; + ctx->session_id_input[2] = 'C'; + ctx->session_id_input[3] = 'D'; + ctx->session_id_input[4] = 'E'; + ctx->session_id_input[5] = 'F'; } if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 2), 128, BUTTON_HEIGHT }, "Back to Main Menu")) { ctx->mode = Main_Menu_Mode_Home; @@ -126,16 +134,35 @@ static void state_render(Presentation_State *state) { break; } case Main_Menu_Mode_Multiplayer_Join: { - // TODO: SS - Add text-input here so the player can specify what session to try connecting to. + // TODO: SS - Session-browser? Shows all available sessions and you can just press it to join? Maybe be prompted with a password if it's locked? - if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "Connect")) { - printf("TODO: SS - Connect to session id.\n"); - const char *session_id = "TEMP00"; // TEMP - game_instance_join_session(ctx->game_instance, session_id); - } + int pressed_button_index = GuiTextInputBox( + (Rectangle) { 64, 64, 256, 128 }, // Bounds + "Session-ID", // Title + NULL, // Message + "Join", // Buttons + &ctx->session_id_input[0], // Text + sizeof(ctx->session_id_input) + 1, // Text max size + NULL // secret view active + ); - if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 2), 128, BUTTON_HEIGHT }, "Cancel")) { - ctx->mode = Main_Menu_Mode_Multiplayer; + if(pressed_button_index >= 0) { + if(pressed_button_index == 0) { + ctx->mode = Main_Menu_Mode_Multiplayer; + } + else if(pressed_button_index == 1) { + if(game_instance_join_session(ctx->game_instance, &ctx->session_id_input[0])) { + // Ok! + ctx->ingame_ctx->game_instance = ctx->game_instance; + presentation_state_machine_go_to(&presentation_state_ingame); + } + else { + printf("Failed to join session '%s'.\n", &ctx->session_id_input[0]); + } + } + else { + assert(false); + } } break; diff --git a/src/presentation/states/state_main_menu.h b/src/presentation/states/state_main_menu.h index f047cee..19fe807 100644 --- a/src/presentation/states/state_main_menu.h +++ b/src/presentation/states/state_main_menu.h @@ -24,6 +24,8 @@ typedef struct { bool *should_quit_game; Presentation_State_Ingame_Context *ingame_ctx; + + char session_id_input[6]; } Presentation_State_Main_Menu_Context; void presentation_state_main_menu_init(Presentation_State_Main_Menu_Context *ctx); diff --git a/src/session/networking.c b/src/session/networking.c index fed1856..a478d14 100644 --- a/src/session/networking.c +++ b/src/session/networking.c @@ -4,6 +4,19 @@ #define ONVO_IDENTIFIER "c2438167-831b-4bf7-8bdc-abcdefabcd00" +static void listen_callback( + const char *event, /* "joined", "leaved", "game" */ + int64_t messageId, /* sekventiellt meddelande‑ID (från host) */ + const char *clientId, /* avsändarens klient‑ID (eller NULL) */ + json_t *data, /* JSON‑objekt med godtycklig speldata */ + void *context /* godtycklig pekare som skickas vidare */ +) { + printf("#%li :: Event: '%s' from '%s'.\n", messageId, event, clientId); + + Game_Networking *networking = (Game_Networking *)context; + +} + static inline bool setup_api(Game_Networking *networking) { assert(networking != NULL); @@ -22,6 +35,28 @@ static inline bool setup_api(Game_Networking *networking) { return true; } +static bool start_listening(Game_Networking *networking) { + assert(networking != NULL); + assert(networking->api != NULL); + + int listener_id = mp_api_listen(networking->api, listen_callback, (void *)networking); // TODO: SS - Change context. + if(listener_id < 0) { + return false; + } + + networking->listener_id = (uint32_t)listener_id; + + return true; +} + +static bool stop_listening(Game_Networking *networking) { + assert(networking != NULL); + assert(networking->api != NULL); + assert(networking->listener_id >= 0); + + mp_api_unlisten(networking->api, (int)networking->listener_id); +} + bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings) { assert(networking != NULL); @@ -47,6 +82,7 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam ); if(host_result != MP_API_OK) { printf("Failed to host; Got result: %i.\n", host_result); + // TODO: SS - Shutdown API. return false; } @@ -54,7 +90,7 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam networking->session_id = session_id; networking->local_client_id = local_client_id; - // TODO: SS - Start listening. + assert(start_listening(networking)); // Set up session. game_session_init( @@ -70,15 +106,60 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam return true; } -bool networking_try_join(Game_Networking *networking, const char *session_id) { +bool networking_try_join(Game_Networking *networking, Game_Session *session, const char *session_id) { assert(networking != NULL); + + if(strlen(session_id) == 0) { + return false; + } if(!setup_api(networking)) { printf("Failed to join; API is already set up.\n"); return false; } - printf("Trying to join session with id: '%s' ...\n", session_id); + printf("Trying to join session using code: '%s' ...\n", session_id); + + char *result_session_id; + char *local_client_id; + json_t *received_json; + int join_result = mp_api_join( + networking->api, // API. + session_id, // Session-code. + NULL, // JSON-data to send. // TODO: SS - Send information about you as a player (username, ..). + &result_session_id, // Received Session-id. I assume it should be the same as 'session_id'. + &local_client_id, // Received client-id. + &received_json // Received JSON-data. + ); + if(join_result != MP_API_OK) { + // TODO: SS - Shutdown API. + printf("Failed to join; Got result: %i.\n", join_result); + return false; + } + assert(strcmp(result_session_id, session_id) == 0); // TODO: SS - Ask Robin why mp_api_join() returns a session_id. Is it to support 'abcdef' => 'ABCDEF'? + + printf("Joined session '%s', local_client_id is '%s'.\n", result_session_id, local_client_id); + networking->session_id = result_session_id; + networking->local_client_id = local_client_id; + + // TODO: SS - Decide how to network this.. + // Should we just receive the session's settings as well as a long list of commands of what has happened throughout the match - at least one per tick? + // Or, should this client receive the current state of the session? + + if(received_json != NULL) { + // TODO: SS - Read this json because it might contain valuable information. + json_decref(received_json); + } + + // TODO: SS - Stop listening. + assert(start_listening(networking)); + + // TODO: SS - Set up session based on what we got in the json. + // game_session_init( + // session, + // settings + // ); + return true; } @@ -89,14 +170,18 @@ bool networking_stop_hosting(Game_Networking *networking) { return false; } + assert(stop_listening(networking)); + mp_api_destroy(networking->api); networking->api = NULL; if(networking->session_id != NULL) { free(networking->session_id); + networking->session_id = NULL; } if(networking->local_client_id != NULL) { free(networking->local_client_id); + networking->local_client_id = NULL; } printf("Stopped hosting.\n"); diff --git a/src/session/networking.h b/src/session/networking.h index 648aa60..7b77322 100644 --- a/src/session/networking.h +++ b/src/session/networking.h @@ -9,10 +9,12 @@ typedef struct { char *session_id; char *local_client_id; + + uint32_t listener_id; } Game_Networking; bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings); -bool networking_try_join(Game_Networking *networking, const char *session_id); +bool networking_try_join(Game_Networking *networking, Game_Session *session, const char *session_id); bool networking_stop_hosting(Game_Networking *networking);