diff --git a/src/instance/game_instance.c b/src/instance/game_instance.c new file mode 100644 index 0000000..0069c2a --- /dev/null +++ b/src/instance/game_instance.c @@ -0,0 +1,87 @@ +#include "game_instance.h" + +#include + +#define INPUT_QUEUE_CAPACITY 8 + +void game_instance_init(Game_Instance *instance) { + instance->local_player_index = 0xFFFF; + // TODO: SS - Init input-queue. + instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context. + instance->game_session = NULL; + memset(&instance->game_networking, 0, sizeof(Game_Networking)); + + squeue_init(&instance->local_input_queue, 4, sizeof(Simulation_Game_Input)); +} + +void game_instance_dispose(Game_Instance *instance) { + squeue_free(&instance->local_input_queue); +} + +bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings) { + assert(instance != NULL); + + printf("Trying to host an %s session ...\n", settings.online ? "online" : "offline"); + + if(instance->game_session != NULL) { + printf("Failed. A session is already active.\n"); + return false; + } + + instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session)); + + if(settings.online) { + if(instance->game_networking.api != NULL) { + printf("Failed. Network is already active.\n"); + free(instance->game_session); + instance->game_session = NULL; + return false; + } + + if(!networking_try_host(&instance->game_networking, instance->game_session, settings)) { + printf("Failed to host session.\n"); + free(instance->game_session); + instance->game_session = NULL; + return false; + } + } + else { + game_session_init(instance->game_session, settings); + } + + assert(game_session_create_player(instance->game_session, &instance->local_player_index)); // Create the host (you!). + + return true; +} + +bool game_instance_join_session(Game_Instance *instance, const char *session_id) { + assert(instance != NULL); + + printf("Trying to join a session ...\n"); + + if(instance->game_session != NULL) { + printf("Failed. A session is already active.\n"); + return false; + } + + if(instance->game_networking.api != NULL) { + printf("Failed. Network is already active.\n"); + return false; + } + + return false; +} + +bool game_instance_push_local_input(Game_Instance *instance, Simulation_Game_Input input) { + return squeue_push(&instance->local_input_queue, (void *)&input); +} + +bool game_instance_pop_local_input(Game_Instance *instance, Simulation_Game_Input *out_input) { + Simulation_Game_Input input = {0}; + if(!squeue_pop(&instance->local_input_queue, (void *)&input)) { + return false; + } + + *out_input = input; + return true; +} \ No newline at end of file diff --git a/src/instance/game_instance.h b/src/instance/game_instance.h new file mode 100644 index 0000000..22cdcfc --- /dev/null +++ b/src/instance/game_instance.h @@ -0,0 +1,32 @@ +#ifndef GAME_INSTANCE_H +#define GAME_INSTANCE_H + +#include + +#include "session/networking.h" +#include "session/game_session.h" + +#include "shared/squeue.h" + +typedef struct { + uint16_t local_player_index; + SQueue local_input_queue; + + double simulation_accumulator; + + Game_Networking game_networking; + Game_Session *game_session; +} Game_Instance; + +void game_instance_init(Game_Instance *instance); +void game_instance_dispose(Game_Instance *instance); + +bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings); +bool game_instance_join_session(Game_Instance *instance, const char *session_id); + +// TODO: SS - stop_session() + +bool game_instance_push_local_input(Game_Instance *instance, Simulation_Game_Input input); +bool game_instance_pop_local_input(Game_Instance *instance, Simulation_Game_Input *out_input); + +#endif \ No newline at end of file diff --git a/src/jansson/dump.c b/src/jansson/dump.c index e1b6bfa..96687ff 100644 --- a/src/jansson/dump.c +++ b/src/jansson/dump.c @@ -205,7 +205,7 @@ static int compare_keys(const void *key1, const void *key2) static int loop_check(hashtable_t *parents, const json_t *json, char *key, size_t key_size) { - snprintf(key, key_size, "%p", json); + snprintf(key, key_size, "%p", (void *)json); if (hashtable_get(parents, key)) return -1; diff --git a/src/main.c b/src/main.c index e5ae826..30da9d2 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,9 @@ #include "presentation/states/state_main_menu.h" #include "raylib.h" +#include "session/networking.h" + +#include "instance/game_instance.h" int main() { InitWindow(1280, 720, "snejk"); @@ -14,10 +17,18 @@ int main() { bool should_quit_game = false; - Presentation_State_Ingame_Context presentation_state_ingame_ctx = (Presentation_State_Ingame_Context) { - }; + Game_Instance local_game_instance; + memset(&local_game_instance, 0, sizeof(Game_Instance)); + + game_instance_init(&local_game_instance); + + Presentation_State_Ingame_Context presentation_state_ingame_ctx; + memset(&presentation_state_ingame_ctx, 0, sizeof(Presentation_State_Ingame_Context)); + Presentation_State_Main_Menu_Context presentation_state_main_menu_ctx = { .should_quit_game = &should_quit_game, + .game_instance = &local_game_instance, + .ingame_ctx = &presentation_state_ingame_ctx }; presentation_state_ingame_init(&presentation_state_ingame_ctx); @@ -34,6 +45,10 @@ int main() { if(presentation_state_machine.current != NULL && presentation_state_machine.current->tick != NULL) { presentation_state_machine.current->tick(presentation_state_machine.current); } + + if(IsKeyPressed(KEY_F)) { + ToggleFullscreen(); + } // Render. BeginDrawing(); @@ -47,6 +62,8 @@ int main() { assert(presentation_state_machine_go_to(NULL)); CloseWindow(); + + game_instance_dispose(&local_game_instance); return 0; } \ No newline at end of file diff --git a/src/presentation/states/state_ingame.c b/src/presentation/states/state_ingame.c index 2f8805f..79b534f 100644 --- a/src/presentation/states/state_ingame.c +++ b/src/presentation/states/state_ingame.c @@ -20,24 +20,6 @@ static void state_enter(Presentation_State *state) { (void)ctx; printf("Entered ingame.\n"); - - printf("Setting up session ...\n"); - - assert(g_current_session != NULL); - Game_Session_Settings *settings = &g_current_session->settings; - printf( - "Singleplayer? %i.\n" - "Settings:\n" - "- Seed: %u\n" - "- Level width: %u, height: %u\n" - "- Max players: %u\n" - , - g_current_session->is_singleplayer, - settings->seed, - settings->level_width, settings->level_height, - settings->max_players - ); - printf("Setting up context ...\n"); ctx->main_camera = (Camera2D) { @@ -65,23 +47,46 @@ static void state_tick(Presentation_State *state) { Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; (void)ctx; - Game_Session *session = g_current_session; + Game_Instance *instance = ctx->game_instance; + assert(instance != NULL); + Game_Session *session = instance->game_session; + assert(session != NULL); { // Push local input to queue. - game_session_enqueue_player_input(session, session->local_player_index, (Simulation_Game_Input) { + Simulation_Game_Input input = (Simulation_Game_Input) { // NOTE: SS - This needs to be slightly modified to support N local players. .up = IsKeyDown(KEY_UP) || IsKeyDown(KEY_W), .down = IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S), .right = IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D), .left = IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A) - }); + }; + + if(!simulation_input_equal(input, ctx->prev_local_input)) { + game_instance_push_local_input(instance, input); + ctx->prev_local_input = input; + } + } + + const double delta_time = GetFrameTime(); + ctx->simulation_accumulator += delta_time; + + const double sim_dt = 1.0f / session->settings.tickrate; + + while (ctx->simulation_accumulator >= sim_dt) { + // Pop input from instance's local_input_queue and set the local player's input to it, if we have one. + Simulation_Game_Input input = {0}; + if(game_instance_pop_local_input(instance, &input)) { + // TODO: SS - We should probably check if this input is invalid for the snake. (pressing "go right" when going left, for example.) + // If it is invalid, the next input in the queue should be tried. + game_session_set_player_input(session, instance->local_player_index, input); + } + + game_session_tick(session); + ctx->simulation_accumulator -= sim_dt; } - double delta_time = GetFrameTime(); - game_session_update(session, delta_time); - { // TEMP: SS if(IsKeyPressed(KEY_TAB)) { - ctx->debug_render_match_state = !ctx->debug_render_match_state; + ctx->debug_draw_session_details = !ctx->debug_draw_session_details; } if(IsKeyPressed(KEY_ESCAPE)) { @@ -102,7 +107,9 @@ static void state_render(Presentation_State *state) { ClearBackground((Color) { 240, 236, 226, 255 }); - Simulation_World *sim_world = &g_current_session->simulation_world; + Game_Instance *instance = ctx->game_instance; + Game_Session *session = instance->game_session; + Simulation_World *sim_world = &session->simulation_world; assert(sim_world != NULL); Game_World *world = sim_world->game_world; @@ -222,7 +229,7 @@ static void state_render(Presentation_State *state) { // TODO: SS - Don't draw player-name if playing by yourself. // TODO: SS - Don't draw your own player-name, only others. { // Draw player-name. - const char *player_name = "mirakel"; // NOTE: SS - Hardcoded. + const char *player_name = "player"; // NOTE: SS - Hardcoded. const uint32_t font_size = 8; int text_width = MeasureText(player_name, font_size); DrawText(player_name, pres_x - (float)text_width/2.0f + 4, pres_y - 16, font_size, (Color) { 255, 255, 255, 128 }); @@ -255,7 +262,7 @@ static void state_render(Presentation_State *state) { }, (Rectangle) { // Destination. pres_x + origin.x, - pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - ((sin(GetTime() * 12) + 1)/2) * 1, + pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - ((sin((GetTime() + (x + y)) * 12) + 1)/2) * 1, GRID_CELL_SIZE, GRID_CELL_SIZE }, @@ -271,11 +278,33 @@ static void state_render(Presentation_State *state) { // TODO: SS - Switch on 'sim_world->match_state'. - if(ctx->debug_render_match_state) { - char buf[512]; - snprintf(&buf[0], sizeof(buf), "Match-state: %i. Tick: %lu.", sim_world->match_state, sim_world->tick); + if(ctx->debug_draw_session_details) { + char buf[1024]; + snprintf(&buf[0], sizeof(buf), + "Match-state: %i\n" + "Tick: %lu\n" + "\n" + "Seed: %u\n" + "Level: %ux%u\n" + "Tickrate: %.2f\n" + "\n" - DrawText(buf, 16, 16, 8, RED); + // TODO: SS - This should only be shown when playing online. Other things should be shown in the cases below; + // When playing offline/singleplayer you can only be one player. + // When playing local-multiplayer, you control multiple players from the same computer/client/instance. + "Local player: %u (host? %s)\n" + "Player count: %u / %u\n" + , + sim_world->match_state, sim_world->tick, + sim_world->game_world->seed, + sim_world->game_world->grid.width, sim_world->game_world->grid.height, + session->settings.tickrate, + instance->local_player_index, instance->local_player_index == 0 ? "true" : "false", // NOTE: SS - You're the host if you're player 0. + game_session_get_amount_of_active_players(session), sim_world->max_players + ); + + DrawText(buf, 17, 17, 8, BLACK); + DrawText(buf, 16, 16, 8, WHITE); } } EndMode2D(); @@ -292,7 +321,7 @@ static void state_exit(Presentation_State *state) { UnloadTexture(ctx->texture_snake_head); UnloadTexture(ctx->texture_snake_body); - game_session_destroy(); + ctx->game_instance = NULL; } Presentation_State presentation_state_ingame; diff --git a/src/presentation/states/state_ingame.h b/src/presentation/states/state_ingame.h index 21ab765..1633fea 100644 --- a/src/presentation/states/state_ingame.h +++ b/src/presentation/states/state_ingame.h @@ -5,8 +5,13 @@ #include "simulation/simulation_world.h" #include "raylib.h" +#include "instance/game_instance.h" typedef struct { + Game_Instance *game_instance; + double simulation_accumulator; + Simulation_Game_Input prev_local_input; + Camera2D main_camera; // Textures. @@ -17,8 +22,8 @@ typedef struct { Texture2D texture_snake_body; // Debug. - bool debug_render_match_state; - + bool debug_draw_session_details; + } Presentation_State_Ingame_Context; void presentation_state_ingame_init(Presentation_State_Ingame_Context *ctx); diff --git a/src/presentation/states/state_main_menu.c b/src/presentation/states/state_main_menu.c index ce78973..b5dfe77 100644 --- a/src/presentation/states/state_main_menu.c +++ b/src/presentation/states/state_main_menu.c @@ -2,39 +2,58 @@ #include #include +#include #include "raylib.h" #include "raygui.h" +#include "session/networking.h" + static void state_enter(Presentation_State *state) { Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context; (void)ctx; ctx->mode = Main_Menu_Mode_Home; + + // if(ctx->game_session != NULL) { + // if(ctx->game_session->is_singleplayer) { + + // } + // else { + // if(ctx->game_session->is_host) { + // networking_stop_hosting(ctx->game_networking); + // } + // else { + + // } + // } + + // game_session_dispose(ctx->game_session); + // ctx->game_session = NULL; + // } } static void state_tick(Presentation_State *state) { Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context; - { // DEBUG - if(IsKeyPressed(KEY_P)) { - ctx->is_singleplayer = true; - ctx->mode = Main_Menu_Mode_Game_Setup; - game_session_init_default_settings(ctx->is_singleplayer, &ctx->session_settings); + // { // DEBUG + // if(IsKeyPressed(KEY_P)) { + // game_session_init_default_settings(true, &ctx->session_settings); - game_session_create( - ctx->is_singleplayer, - !ctx->is_singleplayer, - ctx->session_settings - ); + // assert(ctx->game_session == NULL); + // ctx->game_session = (Game_Session *)calloc(1, sizeof(Game_Session)); - presentation_state_machine_go_to(&presentation_state_ingame); - } - } + // game_session_init( + // ctx->game_session, + // ctx->session_settings + // ); + + // ctx->ingame_ctx->game_instance = ctx->game_instance; + // presentation_state_machine_go_to(&presentation_state_ingame); + // } + // } } -bool editing_width = false; - static void state_render(Presentation_State *state) { Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context; @@ -59,17 +78,15 @@ static void state_render(Presentation_State *state) { break; } case Main_Menu_Mode_Singleplayer: { - ctx->is_singleplayer = true; ctx->mode = Main_Menu_Mode_Game_Setup; - game_session_init_default_settings(ctx->is_singleplayer, &ctx->session_settings); + game_session_init_default_settings(false, &ctx->session_settings); break; } case Main_Menu_Mode_Multiplayer: { if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 0), 128, BUTTON_HEIGHT }, "Host")) { - ctx->is_singleplayer = false; ctx->mode = Main_Menu_Mode_Game_Setup; - game_session_init_default_settings(ctx->is_singleplayer, &ctx->session_settings); + game_session_init_default_settings(true, &ctx->session_settings); } if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "Join")) { ctx->mode = Main_Menu_Mode_Multiplayer_Join; @@ -94,18 +111,16 @@ static void state_render(Presentation_State *state) { // Modify 'ctx->session_settings'. if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 0), 128, BUTTON_HEIGHT }, "Play")) { - // Set up the ingame-context and transition to the ingame-state. - // NOTE: SS - We might need to wait before transitioning when playing multiplayer. - game_session_create( - ctx->is_singleplayer, - !ctx->is_singleplayer, - ctx->session_settings - ); - - presentation_state_machine_go_to(&presentation_state_ingame); + if(game_instance_host_session(ctx->game_instance, ctx->session_settings)) { // Settings indicate whether or not this is an online or offline game. + ctx->ingame_ctx->game_instance = ctx->game_instance; + presentation_state_machine_go_to(&presentation_state_ingame); + } + else { + printf("Failed to play.\n"); + } } if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "Cancel")) { - ctx->mode = ctx->is_singleplayer ? Main_Menu_Mode_Home : Main_Menu_Mode_Multiplayer; + ctx->mode = ctx->session_settings.max_players == 1 ? Main_Menu_Mode_Home : Main_Menu_Mode_Multiplayer; } break; @@ -115,6 +130,8 @@ static void state_render(Presentation_State *state) { 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); } if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 2), 128, BUTTON_HEIGHT }, "Cancel")) { diff --git a/src/presentation/states/state_main_menu.h b/src/presentation/states/state_main_menu.h index 9ecf7ac..f047cee 100644 --- a/src/presentation/states/state_main_menu.h +++ b/src/presentation/states/state_main_menu.h @@ -2,7 +2,8 @@ #define PRES_STATE_MAIN_MENU_H #include "states.h" -#include "session/game_session.h" +#include "state_ingame.h" +#include "instance/game_instance.h" typedef enum { Main_Menu_Mode_Home, @@ -16,10 +17,13 @@ typedef enum { typedef struct { Main_Menu_Mode mode; - bool is_singleplayer; Game_Session_Settings session_settings; - + + Game_Instance *game_instance; + bool *should_quit_game; + + Presentation_State_Ingame_Context *ingame_ctx; } Presentation_State_Main_Menu_Context; void presentation_state_main_menu_init(Presentation_State_Main_Menu_Context *ctx); diff --git a/src/session/game_session.c b/src/session/game_session.c index 3ae1910..051dbe4 100644 --- a/src/session/game_session.c +++ b/src/session/game_session.c @@ -6,22 +6,14 @@ #include #include -#include "shared/squeue.h" +void game_session_init(Game_Session *session, Game_Session_Settings settings) { + assert(session != NULL); -#define INPUT_QUEUE_CAPACITY 8 - -Game_Session *g_current_session = NULL; - -void game_session_create(bool is_singleplayer, bool is_host, Game_Session_Settings settings) { - assert(g_current_session == NULL); - - Game_Session *session = (Game_Session *)calloc(1, sizeof(Game_Session)); - session->is_singleplayer = is_singleplayer; - session->is_host = is_host; session->settings = settings; session->simulation_world = simulation_create_world( session->settings.seed, + session->settings.online, session->settings.max_players, session->settings.level_width, session->settings.level_height @@ -29,66 +21,36 @@ void game_session_create(bool is_singleplayer, bool is_host, Game_Session_Settin session->players = (Game_Session_Player *)calloc(session->settings.max_players, sizeof(Game_Session_Player)); session->players_prev = (Game_Session_Player *)calloc(session->settings.max_players, sizeof(Game_Session_Player)); - - for(uint16_t i = 0; i < session->settings.max_players; i++) { - assert(squeue_init( - &session->players[i].input_queue, - INPUT_QUEUE_CAPACITY, - sizeof(Simulation_Game_Input) - )); - - // Intentionally not initializing players_prev's input_queue. - } - - { // TEMP - session->local_player_index = 0; - assert(game_session_create_player(session, &session->local_player_index)); - } - - session->simulation_accumulator = 0.0f; - - g_current_session = session; - - printf("New Game_Session created.\n"); } -void game_session_destroy() { - Game_Session *session = g_current_session; - +void game_session_dispose(Game_Session *session) { if(session == NULL) { return; } simulation_destroy_world(&session->simulation_world); - for(uint16_t i = 0; i < session->settings.max_players; i++) { - squeue_free(&session->players[i].input_queue); - - // Intentionally not freeing players_prev's input_queue. - } - free(session->players); session->players = NULL; free(session->players_prev); session->players_prev = NULL; - free(session); - g_current_session = NULL; + memset(session, 0, sizeof(Game_Session)); } -void game_session_init_default_settings(bool is_singleplayer, Game_Session_Settings *out_settings) { +void game_session_init_default_settings(bool online, Game_Session_Settings *out_settings) { out_settings->seed = 1337; // TODO: SS - Randomize. + out_settings->online = online; out_settings->tickrate = 10.0; out_settings->level_width = 64; out_settings->level_height = 32; - out_settings->max_players = is_singleplayer ? 1 : 8; + out_settings->max_players = online ? 8 : 2; } -static void game_session_tick(Game_Session *session) { - // Update input. +void game_session_tick(Game_Session *session) { for(uint16_t i = 0; i < session->settings.max_players; i++) { Game_Session_Player *session_player = &session->players[i]; - + { // Check if any players have joined/disconnected by comparing against the previous session_player. Game_Session_Player *session_player_prev = &session->players_prev[i]; @@ -107,21 +69,17 @@ static void game_session_tick(Game_Session *session) { }); } } - + if(!session_player->active) { continue; } - - Simulation_Game_Input input = {0}; - if(squeue_pop(&session_player->input_queue, &input)) { - // We got an input. - - simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) { - .type = Simulation_Command_Type_Set_Player_Input, - .player_id = i, - .player_input = input, - }); - } + + // Update input. + simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) { + .type = Simulation_Command_Type_Set_Player_Input, + .player_id = i, + .player_input = session_player->input, + }); } // Tick. @@ -131,17 +89,6 @@ static void game_session_tick(Game_Session *session) { memcpy(session->players_prev, session->players, sizeof(Game_Session_Player) * session->settings.max_players); } -void game_session_update(Game_Session *session, const double delta_time) { - session->simulation_accumulator += delta_time; - - const double sim_dt = 1.0 / session->settings.tickrate; - - while (session->simulation_accumulator >= sim_dt) { - game_session_tick(session); - session->simulation_accumulator -= sim_dt; - } -} - static Game_Session_Player *game_session_get_player_from_id(Game_Session *session, uint16_t player_index) { assert(session != NULL); if(player_index >= session->settings.max_players) { @@ -151,11 +98,19 @@ static Game_Session_Player *game_session_get_player_from_id(Game_Session *sessio return &session->players[player_index]; } -Game_Session_Player *game_session_get_local_player(Game_Session *session) { +uint16_t game_session_get_amount_of_active_players(Game_Session *session) { assert(session != NULL); - return game_session_get_player_from_id(session, session->local_player_index); -} + uint16_t amount = 0; + for(uint16_t i = 0; i < session->settings.max_players; i++) { + Game_Session_Player *player = &session->players[i]; + if(player->active) { + amount += 1; + } + } + + return amount; +} bool game_session_create_player(Game_Session *session, uint16_t *out_player_index) { assert(session != NULL); @@ -191,23 +146,10 @@ void game_session_destroy_player(Game_Session *session, uint16_t player_index) { memset(player, 0, sizeof(Game_Session_Player)); } -bool game_session_enqueue_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input) { +bool game_session_set_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input) { Game_Session_Player *player = game_session_get_player_from_id(session, player_index); assert(player != NULL); - if(simulation_input_equal(player->most_recent_input, input)) { - // Ignore 'input' if it's equal to the most recent input. This way we avoid duplicates. - return false; - } - - // TODO: SS - Check if 'input' is a valid "snake-move". - // Inputting right when going left will sadly be "okay" here which will make the game - // feel less responsive. - - if(!squeue_push(&player->input_queue, (void *)&input)) { - return false; - } - - player->most_recent_input = input; + player->input = input; return true; } \ No newline at end of file diff --git a/src/session/game_session.h b/src/session/game_session.h index c399ceb..137f405 100644 --- a/src/session/game_session.h +++ b/src/session/game_session.h @@ -13,54 +13,46 @@ typedef struct { bool active; - SQueue input_queue; - Simulation_Game_Input most_recent_input; // Exclusively used to make sure that the input-queue doesn't get filled with duplicated inputs. + Simulation_Game_Input input; } Game_Session_Player; typedef struct { uint32_t seed; - double tickrate; - + bool online; + uint16_t level_width; uint16_t level_height; + + double tickrate; uint8_t max_players; - + // .. } Game_Session_Settings; typedef struct { - bool is_singleplayer; - bool is_host; - Game_Session_Settings settings; - - Simulation_World simulation_world; - double simulation_accumulator; - + Game_Session_Player *players; Game_Session_Player *players_prev; - uint16_t local_player_index; - // TODO: SS - Local + remote input-queue. + Simulation_World simulation_world; } Game_Session; -extern Game_Session *g_current_session; +void game_session_init(Game_Session *session, Game_Session_Settings settings); +void game_session_dispose(Game_Session *session); -void game_session_create(bool is_singleplayer, bool is_host, Game_Session_Settings settings); -void game_session_destroy(); +void game_session_init_default_settings(bool online, Game_Session_Settings *out_settings); -void game_session_init_default_settings(bool is_singleplayer, Game_Session_Settings *out_settings); +void game_session_tick(Game_Session *session); -void game_session_update(Game_Session *session, const double delta_time); - -Game_Session_Player *game_session_get_local_player(Game_Session *session); +uint16_t game_session_get_amount_of_active_players(Game_Session *session); bool game_session_create_player(Game_Session *session, uint16_t *out_player_index); void game_session_destroy_player(Game_Session *session, uint16_t player_index); -bool game_session_enqueue_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input); +bool game_session_set_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input); #endif \ No newline at end of file diff --git a/src/MultiplayerApi.c b/src/session/multiplayer_api.c similarity index 90% rename from src/MultiplayerApi.c rename to src/session/multiplayer_api.c index 1510cb9..82c14da 100644 --- a/src/MultiplayerApi.c +++ b/src/session/multiplayer_api.c @@ -1,4 +1,4 @@ -#include "MultiplayerApi.h" +#include "multiplayer_api.h" #include #include @@ -15,18 +15,19 @@ typedef struct ListenerNode { int id; MultiplayerListener cb; - void *user_data; + void *context; struct ListenerNode *next; } ListenerNode; typedef struct ListenerSnapshot { MultiplayerListener cb; - void *user_data; + void *context; } ListenerSnapshot; struct MultiplayerApi { char *server_host; uint16_t server_port; + char identifier[37]; int sockfd; char *session_id; @@ -48,12 +49,19 @@ static void *recv_thread_main(void *arg); static void process_line(MultiplayerApi *api, const char *line); static int start_recv_thread(MultiplayerApi *api); -MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port) { - MultiplayerApi *api = (MultiplayerApi *)calloc(1, sizeof(MultiplayerApi)); +MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port, const char *identifier) +{ + int len = strlen(identifier); + if (len != 36) { + return NULL; + } + + MultiplayerApi *api = (MultiplayerApi *)calloc(1, sizeof(MultiplayerApi)); if (!api) { return NULL; } + if (server_host) { api->server_host = strdup(server_host); } else { @@ -66,6 +74,9 @@ MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port) { } api->server_port = server_port; + + strncpy(api->identifier, identifier, 37); + api->sockfd = -1; api->session_id = NULL; api->recv_thread_started = 0; @@ -117,6 +128,7 @@ void mp_api_destroy(MultiplayerApi *api) { } int mp_api_host(MultiplayerApi *api, + json_t *data, char **out_session, char **out_clientId, json_t **out_data) { @@ -129,9 +141,16 @@ int mp_api_host(MultiplayerApi *api, json_t *root = json_object(); if (!root) return MP_API_ERR_IO; - json_object_set_new(root, "session", json_null()); + json_object_set_new(root, "identifier", json_string(api->identifier)); json_object_set_new(root, "cmd", json_string("host")); - json_object_set_new(root, "data", json_object()); + + json_t *data_copy; + if (data && json_is_object(data)) { + data_copy = json_deep_copy(data); + } else { + data_copy = json_object(); + } + json_object_set_new(root, "data", data_copy); rc = send_json_line(api, root); if (rc != MP_API_OK) { @@ -214,6 +233,7 @@ int mp_api_list(MultiplayerApi *api, json_t **out_list) json_t *root = json_object(); if (!root) return MP_API_ERR_IO; + json_object_set_new(root, "identifier", json_string(api->identifier)); json_object_set_new(root, "cmd", json_string("list")); rc = send_json_line(api, root); @@ -276,7 +296,8 @@ int mp_api_join(MultiplayerApi *api, json_t *root = json_object(); if (!root) return MP_API_ERR_IO; - + + json_object_set_new(root, "identifier", json_string(api->identifier)); json_object_set_new(root, "session", json_string(sessionId)); json_object_set_new(root, "cmd", json_string("join")); @@ -371,16 +392,20 @@ int mp_api_join(MultiplayerApi *api, return joinAccepted ? MP_API_OK : MP_API_ERR_REJECTED; } -int mp_api_game(MultiplayerApi *api, json_t *data) { +int mp_api_game(MultiplayerApi *api, json_t *data, const char* destination) { if (!api || !data) return MP_API_ERR_ARGUMENT; if (api->sockfd < 0 || !api->session_id) return MP_API_ERR_STATE; json_t *root = json_object(); if (!root) return MP_API_ERR_IO; + json_object_set_new(root, "identifier", json_string(api->identifier)); json_object_set_new(root, "session", json_string(api->session_id)); json_object_set_new(root, "cmd", json_string("game")); + if(destination) + json_object_set_new(root, "destination", json_string(destination)); + json_t *data_copy; if (json_is_object(data)) { data_copy = json_deep_copy(data); @@ -394,14 +419,14 @@ int mp_api_game(MultiplayerApi *api, json_t *data) { int mp_api_listen(MultiplayerApi *api, MultiplayerListener cb, - void *user_data) { + void *context) { if (!api || !cb) return -1; ListenerNode *node = (ListenerNode *)malloc(sizeof(ListenerNode)); if (!node) return -1; node->cb = cb; - node->user_data = user_data; + node->context = context; pthread_mutex_lock(&api->lock); node->id = api->next_listener_id++; @@ -505,6 +530,8 @@ static int send_json_line(MultiplayerApi *api, json_t *obj) { return MP_API_ERR_IO; } + printf("Sending JSON: %s\n", text); // Debug print + size_t len = strlen(text); int fd = api->sockfd; @@ -640,7 +667,7 @@ static void process_line(MultiplayerApi *api, const char *line) { while (node) { if (node->cb) { snapshot[idx].cb = node->cb; - snapshot[idx].user_data = node->user_data; + snapshot[idx].context = node->context; idx++; } node = node->next; @@ -648,7 +675,7 @@ static void process_line(MultiplayerApi *api, const char *line) { pthread_mutex_unlock(&api->lock); for (int i = 0; i < count; ++i) { - snapshot[i].cb(cmd, (int64_t)msgId, clientId, data_obj, snapshot[i].user_data); + snapshot[i].cb(cmd, (int64_t)msgId, clientId, data_obj, snapshot[i].context); } free(snapshot); @@ -728,4 +755,4 @@ static int start_recv_thread(MultiplayerApi *api) { api->recv_thread_started = 1; return MP_API_OK; -} +} \ No newline at end of file diff --git a/src/MultiplayerApi.h b/src/session/multiplayer_api.h similarity index 84% rename from src/MultiplayerApi.h rename to src/session/multiplayer_api.h index 7b20665..5c70216 100644 --- a/src/MultiplayerApi.h +++ b/src/session/multiplayer_api.h @@ -4,10 +4,6 @@ #include #include "jansson/jansson.h" -#ifdef __cplusplus -extern "C" { -#endif - typedef struct MultiplayerApi MultiplayerApi; /* Callback‑typ för inkommande events från servern. */ @@ -16,7 +12,7 @@ typedef void (*MultiplayerListener)( 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 *user_data /* godtycklig pekare som skickas vidare */ + void *context /* godtycklig pekare som skickas vidare */ ); /* Returkoder */ @@ -31,7 +27,7 @@ enum { }; /* Skapar en ny API‑instans. Returnerar NULL vid fel. */ -MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port); +MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port, const char *identifier); /* Stänger ner anslutning, stoppar mottagartråd och frigör minne. */ void mp_api_destroy(MultiplayerApi *api); @@ -41,9 +37,10 @@ void mp_api_destroy(MultiplayerApi *api); anroparen ansvarar för att free:a. out_data (om ej NULL) får ett json_t* med extra data från servern (anroparen ska json_decref när klart). */ int mp_api_host(MultiplayerApi *api, - char **out_session, - char **out_clientId, - json_t **out_data); + json_t *data, + char **out_session, + char **out_clientId, + json_t **out_data); /* Hämtar en lista över tillgängliga publika sessioner. @@ -70,19 +67,15 @@ int mp_api_join(MultiplayerApi *api, json_t **out_data); /* Skickar ett "game"‑meddelande med godtycklig JSON‑data till sessionen. */ -int mp_api_game(MultiplayerApi *api, json_t *data); +int mp_api_game(MultiplayerApi *api, json_t *data, const char* destination); /* Registrerar en lyssnare för inkommande events. Returnerar ett positivt listener‑ID, eller −1 vid fel. */ int mp_api_listen(MultiplayerApi *api, MultiplayerListener cb, - void *user_data); + void *context); /* Avregistrerar lyssnare. Listener‑ID är värdet från mp_api_listen. */ void mp_api_unlisten(MultiplayerApi *api, int listener_id); -#ifdef __cplusplus -} -#endif - -#endif /* MULTIPLAYER_API_H */ +#endif /* MULTIPLAYER_API_H */ \ No newline at end of file diff --git a/src/session/networking.c b/src/session/networking.c new file mode 100644 index 0000000..fed1856 --- /dev/null +++ b/src/session/networking.c @@ -0,0 +1,104 @@ +#include "networking.h" + +#include + +#define ONVO_IDENTIFIER "c2438167-831b-4bf7-8bdc-abcdefabcd00" + +static inline bool setup_api(Game_Networking *networking) { + assert(networking != NULL); + + if(networking->api != NULL) { + printf("Failed to set up API. Already active.\n"); + return false; + } + + MultiplayerApi *mp_api = mp_api_create("kontoret.onvo.se", 9001, ONVO_IDENTIFIER); + if(mp_api == NULL) { + printf("Failed to set up API. Could not initialize.\n"); + return false; + } + + networking->api = mp_api; + return true; +} + +bool networking_try_host(Game_Networking *networking, Game_Session *session, Game_Session_Settings settings) { + assert(networking != NULL); + + if(!settings.online) { + printf("Failed to host; Game_Session_Settings.online == false, expected it to be true.\n"); + return false; + } + + if(!setup_api(networking)) { + printf("Failed to host; API is already set up.\n"); + return false; + } + + char *session_id = NULL; + char *local_client_id = NULL; + json_t *response_data = NULL; + int host_result = mp_api_host( + networking->api, + NULL, // TODO: SS - Send data to server that contains the game-session's settings. + &session_id, + &local_client_id, + &response_data + ); + if(host_result != MP_API_OK) { + printf("Failed to host; Got result: %i.\n", host_result); + return false; + } + + printf("Started hosting session '%s', local_client_id is '%s'.\n", session_id, local_client_id); + networking->session_id = session_id; + networking->local_client_id = local_client_id; + + // TODO: SS - Start listening. + + // Set up session. + game_session_init( + session, + settings + ); + + if(response_data != NULL) { + // TODO: SS - Handle JSON-response. + json_decref(response_data); + } + + return true; +} + +bool networking_try_join(Game_Networking *networking, const char *session_id) { + assert(networking != NULL); + + 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); + return true; +} + +bool networking_stop_hosting(Game_Networking *networking) { + assert(networking != NULL); + if(networking->api == NULL) { + printf("Failed to stop hosting; API is not set up.\n"); + return false; + } + + mp_api_destroy(networking->api); + networking->api = NULL; + + if(networking->session_id != NULL) { + free(networking->session_id); + } + if(networking->local_client_id != NULL) { + free(networking->local_client_id); + } + + printf("Stopped hosting.\n"); + return true; +} \ No newline at end of file diff --git a/src/session/networking.h b/src/session/networking.h new file mode 100644 index 0000000..648aa60 --- /dev/null +++ b/src/session/networking.h @@ -0,0 +1,19 @@ +#ifndef NETWORKING_H +#define NETWORKING_H + +#include "multiplayer_api.h" +#include "game_session.h" + +typedef struct { + MultiplayerApi *api; + + char *session_id; + char *local_client_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_stop_hosting(Game_Networking *networking); + +#endif \ No newline at end of file diff --git a/src/shared/game_world.h b/src/shared/game_world.h index 4228280..888a8d5 100644 --- a/src/shared/game_world.h +++ b/src/shared/game_world.h @@ -25,6 +25,8 @@ void game_world_destroy(Game_World *world); bool game_world_create_entity(Game_World *world, Entity_Type type, uint16_t x, uint16_t y, Entity_ID *out_entity_id); void game_world_destroy_entity(Game_World *world, Entity_ID entity_id); +// TODO: SS - "void game_world_spawn_player(Game_World *world, ..)" + Entity *game_world_try_get_entity_by_id(Game_World *world, Entity_ID id); #endif \ No newline at end of file diff --git a/src/simulation/simulation_world.c b/src/simulation/simulation_world.c index 843193d..fbc9364 100644 --- a/src/simulation/simulation_world.c +++ b/src/simulation/simulation_world.c @@ -6,12 +6,14 @@ #define SIM_COMMANDS_PER_PLAYER 4 -Simulation_World simulation_create_world(uint32_t seed, uint8_t max_players, uint16_t width, uint16_t height) { +Simulation_World simulation_create_world(uint32_t seed, bool online, uint8_t max_players, uint16_t width, uint16_t height) { Simulation_World w; memset(&w, 0, sizeof(Simulation_World)); w.tick = 0; + w.online = online; + w.game_world = game_world_create(seed, width, height); assert(w.game_world != NULL); @@ -97,9 +99,7 @@ void simulation_world_tick(Simulation_World *simulation_world) { switch(simulation_world->match_state) { case Simulation_Match_State_Waiting_To_Start: { - bool is_singleplayer = simulation_world->max_players == 1; - - if(is_singleplayer) { + if(!simulation_world->online) { // If not online, just go to Counting_Down. simulation_world->match_state = Simulation_Match_State_Counting_Down; } else { @@ -230,7 +230,6 @@ void simulation_world_tick(Simulation_World *simulation_world) { // Figure out what cell we're on and what cell we want to go to. Grid_Cell *current_cell = grid_get_cell(&gw->grid, entity->x, entity->y); - Grid_Cell *start_cell = current_cell; assert(current_cell != NULL); Grid_Cell *target_cell = grid_get_cell(&gw->grid, entity->x + dx, entity->y + dy); diff --git a/src/simulation/simulation_world.h b/src/simulation/simulation_world.h index 5054663..c0def65 100644 --- a/src/simulation/simulation_world.h +++ b/src/simulation/simulation_world.h @@ -2,8 +2,10 @@ #define SIMULATION_WORLD_H #include +#include #include "shared/game_world.h" +#include "shared/random.h" // Deterministic random. #include "player.h" #include "command.h" @@ -17,6 +19,8 @@ typedef enum { typedef struct { uint64_t tick; + bool online; + Game_World *game_world; Simulation_Match_State match_state; @@ -29,7 +33,7 @@ typedef struct { uint16_t max_commands; } Simulation_World; -Simulation_World simulation_create_world(uint32_t seed, uint8_t max_players, uint16_t width, uint16_t height); +Simulation_World simulation_create_world(uint32_t seed, bool online, uint8_t max_players, uint16_t width, uint16_t height); void simulation_destroy_world(Simulation_World *simulation_world); void simulation_world_tick(Simulation_World *simulation_world);