From 4e807afa3c9a90cda5686f70723a708997183a51 Mon Sep 17 00:00:00 2001 From: samstalhandske Date: Tue, 16 Dec 2025 17:29:02 +0100 Subject: [PATCH] Multiple local inputs. You can now play with your 3 friends locally, fighting over the same keyboard. --- src/game/instance/game_instance.c | 77 ++++++++++++++--- src/game/instance/game_instance.h | 21 +++-- src/game/presentation/states/state_ingame.c | 95 +++++++++++++++++---- src/game/presentation/states/state_ingame.h | 1 - src/game/session/game_session.c | 4 +- src/game/shared/squeue.c | 5 ++ src/game/shared/squeue.h | 2 + src/main.c | 6 +- 8 files changed, 169 insertions(+), 42 deletions(-) diff --git a/src/game/instance/game_instance.c b/src/game/instance/game_instance.c index 73ab075..91f97bf 100644 --- a/src/game/instance/game_instance.c +++ b/src/game/instance/game_instance.c @@ -2,20 +2,27 @@ #include -#define INPUT_QUEUE_CAPACITY 8 - void game_instance_init(Game_Instance *instance) { - instance->local_player_id = 0xFFFF; - // TODO: SS - Init input-queue. + instance->amount_of_local_players = 0; 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)); + for(uint8_t i = 0; i < MAX_LOCAL_PLAYERS; i++) { + Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[i]; + squeue_init( + &locally_controlled_player->input_queue, + MAX_INPUTS_IN_QUEUE_PER_LOCAL_PLAYER, + sizeof(Simulation_Game_Input) + ); + } } void game_instance_dispose(Game_Instance *instance) { - squeue_free(&instance->local_input_queue); + for(uint8_t i = 0; i < MAX_LOCAL_PLAYERS; i++) { + Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[i]; + squeue_free(&locally_controlled_player->input_queue); + } } bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings) { @@ -33,7 +40,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)); // LEAK + instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session)); if(settings.online) { if(instance->game_networking.api != NULL) { @@ -52,14 +59,25 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett return false; } - instance->local_player_id = host_player_id; + game_instance_clear_local_players(instance); + assert(game_instance_setup_local_player(instance, host_player_id)); } else { + if(settings.max_players > MAX_LOCAL_PLAYERS) { + printf("Failed. Max amount of players (when playing locally) is %u. Max amount of players in the settings is %u.\n", MAX_LOCAL_PLAYERS, settings.max_players); + free(instance->game_session); + instance->game_session = NULL; + return false; + } + game_session_init(instance->game_session, settings); + // When playing locally/offline, create the player(s) here. + game_instance_clear_local_players(instance); for(uint32_t i = 0; i < settings.max_players; i++) { - // When playing locally/offline, create the player(s) here. - assert(game_session_create_player(instance->game_session, &instance->local_player_id)); // TODO: SS - Fix me! Always assigning the newest player to be the active 'local_player_id' which isn't correct. + uint16_t player_id = 0xFFFF; + assert(game_session_create_player(instance->game_session, &player_id)); + assert(game_instance_setup_local_player(instance, player_id)); } printf("Created %u local players.\n", settings.max_players); @@ -116,13 +134,44 @@ void game_instance_stop_session(Game_Instance *instance) { instance->game_session = NULL; } -bool game_instance_push_local_input(Game_Instance *instance, Simulation_Game_Input input) { - return squeue_push(&instance->local_input_queue, (void *)&input); +void game_instance_clear_local_players(Game_Instance *instance) { + instance->amount_of_local_players = 0; } -bool game_instance_pop_local_input(Game_Instance *instance, Simulation_Game_Input *out_input) { +bool game_instance_setup_local_player(Game_Instance *instance, uint16_t player_id) { + if(instance->amount_of_local_players >= MAX_LOCAL_PLAYERS) { + return false; + } + + Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[instance->amount_of_local_players]; + assert(locally_controlled_player != NULL); + instance->amount_of_local_players += 1; + + locally_controlled_player->player_id = player_id; + squeue_clear(&locally_controlled_player->input_queue); + + return true; +} + +bool game_instance_push_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input input) { + assert(local_player_index < MAX_LOCAL_PLAYERS); + assert(local_player_index < instance->amount_of_local_players); + + Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[local_player_index]; + assert(locally_controlled_player != NULL); + + return squeue_push(&locally_controlled_player->input_queue, (void *)&input); +} + +bool game_instance_pop_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input *out_input) { + assert(local_player_index < MAX_LOCAL_PLAYERS); + assert(local_player_index < instance->amount_of_local_players); + + Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[local_player_index]; + assert(locally_controlled_player != NULL); + Simulation_Game_Input input = {0}; - if(!squeue_pop(&instance->local_input_queue, (void *)&input)) { + if(!squeue_pop(&locally_controlled_player->input_queue, (void *)&input)) { return false; } diff --git a/src/game/instance/game_instance.h b/src/game/instance/game_instance.h index bc9feb2..d0823b6 100644 --- a/src/game/instance/game_instance.h +++ b/src/game/instance/game_instance.h @@ -8,9 +8,18 @@ #include "shared/squeue.h" +#define MAX_LOCAL_PLAYERS 4 +#define MAX_INPUTS_IN_QUEUE_PER_LOCAL_PLAYER 4 + typedef struct { - uint16_t local_player_id; // TODO: SS - We need to modify this to support local multiplayer. - SQueue local_input_queue; + uint16_t player_id; + SQueue input_queue; + Simulation_Game_Input prev_input; // TODO: SS - Remove this and just peek the queue? +} Locally_Controlled_Player; + +typedef struct { + Locally_Controlled_Player locally_controlled_players[MAX_LOCAL_PLAYERS]; + uint8_t amount_of_local_players; double simulation_accumulator; @@ -23,10 +32,12 @@ 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); - void game_instance_stop_session(Game_Instance *instance); -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); +void game_instance_clear_local_players(Game_Instance *instance); +bool game_instance_setup_local_player(Game_Instance *instance, uint16_t player_id); + +bool game_instance_push_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input input); +bool game_instance_pop_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input *out_input); #endif \ No newline at end of file diff --git a/src/game/presentation/states/state_ingame.c b/src/game/presentation/states/state_ingame.c index d7a94b7..5ed8750 100644 --- a/src/game/presentation/states/state_ingame.c +++ b/src/game/presentation/states/state_ingame.c @@ -26,7 +26,7 @@ static void state_enter(Presentation_State *state) { .offset = (Vector2) { 0, 0 }, .target = (Vector2) { 0, 0 }, .rotation = 0.0f, - .zoom = 3.0f + .zoom = 2.0f }; // TODO: SS - Maybe put the textures in an array and index them using an enum? @@ -43,6 +43,47 @@ static void state_enter(Presentation_State *state) { assert(IsTextureValid(ctx->texture_snake_body)); } +static Simulation_Game_Input gather_input_for_local_player(uint8_t local_player_index) { + switch(local_player_index) { + case 0: { + return (Simulation_Game_Input) { + .up = IsKeyDown(KEY_W), + .down = IsKeyDown(KEY_S), + .right = IsKeyDown(KEY_D), + .left = IsKeyDown(KEY_A) + }; + } + case 1: { + return (Simulation_Game_Input) { + .up = IsKeyDown(KEY_UP), + .down = IsKeyDown(KEY_DOWN), + .right = IsKeyDown(KEY_RIGHT), + .left = IsKeyDown(KEY_LEFT) + }; + } + case 2: { + return (Simulation_Game_Input) { + .up = IsKeyDown(KEY_I), + .down = IsKeyDown(KEY_K), + .right = IsKeyDown(KEY_L), + .left = IsKeyDown(KEY_J) + }; + } + case 3: { + return (Simulation_Game_Input) { + .up = IsKeyDown(KEY_T), + .down = IsKeyDown(KEY_G), + .right = IsKeyDown(KEY_H), + .left = IsKeyDown(KEY_F) + }; + } + default: { + assert(false); + return (Simulation_Game_Input) {0}; + } + } +} + static void state_tick(Presentation_State *state) { Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; (void)ctx; @@ -53,16 +94,31 @@ static void state_tick(Presentation_State *state) { assert(session != NULL); { // Push local input to queue. - 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(instance->amount_of_local_players == 1) { // If playing alone (either singleplayer or online-multiplayer), more inputs are allowed. + Simulation_Game_Input input = (Simulation_Game_Input) { + .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; + Locally_Controlled_Player *player = &instance->locally_controlled_players[0]; + + if(!simulation_input_equal(input, player->prev_input)) { + game_instance_push_local_input(instance, 0, input); + player->prev_input = input; + } + } + else if(instance->amount_of_local_players > 1) { + for(uint8_t i = 0; i < instance->amount_of_local_players; i++) { + Simulation_Game_Input input = gather_input_for_local_player(i); + Locally_Controlled_Player *player = &instance->locally_controlled_players[i]; + + if(!simulation_input_equal(input, player->prev_input)) { + game_instance_push_local_input(instance, i, input); + player->prev_input = input; + } + } } } @@ -72,12 +128,14 @@ static void state_tick(Presentation_State *state) { 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_id, input); + for(uint16_t i = 0; i < instance->amount_of_local_players; i++) { + // Pop input from local-player's input_queue and set the player's input to it, if we have one. + Simulation_Game_Input input = {0}; + if(game_instance_pop_local_input(instance, i, &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, i, input); + } } game_session_tick(session); @@ -325,6 +383,9 @@ static void state_render(Presentation_State *state) { } } + // TODO: SS - Let the main-camera follow the entity you're controlling? + + // TODO: SS - Switch on 'sim_world->match_state'. if(ctx->debug_draw_session_details) { @@ -362,7 +423,7 @@ static void state_render(Presentation_State *state) { networking->is_host ? "true" : "false", networking->session_id, networking->local_client_id, - instance->local_player_id, + instance->locally_controlled_players[0].player_id, game_session_get_amount_of_active_players(session), sim_world->max_players ); } diff --git a/src/game/presentation/states/state_ingame.h b/src/game/presentation/states/state_ingame.h index b967c60..5fb36a0 100644 --- a/src/game/presentation/states/state_ingame.h +++ b/src/game/presentation/states/state_ingame.h @@ -10,7 +10,6 @@ typedef struct { Game_Instance *game_instance; double simulation_accumulator; - Simulation_Game_Input prev_local_input; Camera2D main_camera; diff --git a/src/game/session/game_session.c b/src/game/session/game_session.c index 7447417..39e9018 100644 --- a/src/game/session/game_session.c +++ b/src/game/session/game_session.c @@ -42,8 +42,8 @@ void game_session_init_default_settings(bool online, Game_Session_Settings *out_ 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->level_width = 128; + out_settings->level_height = 64; out_settings->max_players = online ? 8 : 2; } diff --git a/src/game/shared/squeue.c b/src/game/shared/squeue.c index 21db128..db08a18 100644 --- a/src/game/shared/squeue.c +++ b/src/game/shared/squeue.c @@ -54,4 +54,9 @@ bool squeue_peek(const SQueue *q, void *out) { void *src = (uint8_t*)q->buffer + q->head * q->element_size; memcpy(out, src, q->element_size); return true; +} + +void squeue_clear(SQueue *q) { + void *t = NULL; + while(squeue_pop(q, t)) {} } \ No newline at end of file diff --git a/src/game/shared/squeue.h b/src/game/shared/squeue.h index 7b90bf5..cf69f06 100644 --- a/src/game/shared/squeue.h +++ b/src/game/shared/squeue.h @@ -24,4 +24,6 @@ bool squeue_push(SQueue *q, const void *elem); bool squeue_pop(SQueue *q, void *out); bool squeue_peek(const SQueue *q, void *out); +void squeue_clear(SQueue *q); + #endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 8e1f99b..3df1a03 100644 --- a/src/main.c +++ b/src/main.c @@ -46,9 +46,9 @@ int main() { presentation_state_machine.current->tick(presentation_state_machine.current); } - if(IsKeyPressed(KEY_F)) { - ToggleFullscreen(); - } + // if(IsKeyPressed(KEY_F)) { // TODO: SS - Add this back, but not 'F'. + // ToggleFullscreen(); + // } // Render. BeginDrawing();