diff --git a/gamestate.c b/gamestate.c index 1e27b64..f4a60b9 100644 --- a/gamestate.c +++ b/gamestate.c @@ -268,6 +268,16 @@ void des_int(char **in, int *i) memread(in, i); } +void ser_uint64(char **out, uint64_t i) +{ + memwrite(out, i); +} + +void des_uint64(char **in, uint64_t *i) +{ + memread(in, i); +} + void ser_bool(char **out, bool b) { **out = (char)b; @@ -403,6 +413,8 @@ void into_bytes(struct ServerToClient *msg, char *bytes, int *out_len, int max_l ser_int(&bytes, msg->your_player); LEN_CHECK(); + ser_uint64(&bytes, gs->tick); + ser_double(&bytes, gs->time); LEN_CHECK(); @@ -444,6 +456,9 @@ void from_bytes(struct ServerToClient *msg, char *bytes, int max_len) des_int(&bytes, &msg->your_player); LEN_CHECK(); + des_uint64(&bytes, &gs->tick); + LEN_CHECK(); + des_double(&bytes, &gs->time); LEN_CHECK(); @@ -523,6 +538,7 @@ void process(struct GameState *gs, float dt) { assert(gs->space != NULL); + gs->tick += 1; gs->time += dt; // process input @@ -569,16 +585,15 @@ void process(struct GameState *gs, float dt) } // don't allow inhabiting a grid that's already inhabited - for(int ii = 0; ii < MAX_PLAYERS; ii++) + for (int ii = 0; ii < MAX_PLAYERS; ii++) { - if(gs->players[ii].currently_inhabiting_index == ship_to_inhabit) + if (gs->players[ii].currently_inhabiting_index == ship_to_inhabit) { Log("Attempted to inhabit already taken ship\n"); ship_to_inhabit = -1; } } - if (ship_to_inhabit == -1) { Log("Couldn't find ship to inhabit even though point collision returned something\n"); diff --git a/main.c b/main.c index 3f4d493..08a39ee 100644 --- a/main.c +++ b/main.c @@ -29,8 +29,9 @@ static KeyPressed keypressed[SAPP_KEYCODE_MENU] = {0}; static V2 mouse_pos = {0}; static bool mouse_pressed = false; static uint64_t mouse_pressed_frame = 0; -static bool mouse_frozen = false; // @BeforeShip make this debug only thing -static float funval = 0.0f; // easy to play with value controlled by left mouse button when held down @BeforeShip remove on release builds +static bool mouse_frozen = false; // @BeforeShip make this debug only thing +static float funval = 0.0f; // easy to play with value controlled by left mouse button when held down @BeforeShip remove on release builds +static struct ClientToServer client_to_server = {0}; // buffer of inputs static ENetHost *client; static ENetPeer *peer; @@ -151,7 +152,7 @@ static void frame(void) { int width = sapp_width(), height = sapp_height(); float ratio = width / (float)height; - float time = sapp_frame_count() * sapp_frame_duration(); + double time = sapp_frame_count() * sapp_frame_duration(); float dt = sapp_frame_duration(); // pressed input management @@ -287,32 +288,54 @@ static void frame(void) // Create and send input packet { // @Robust accumulate total time and send input at rate like 20 hz, not every frame - struct ClientToServer curmsg = {0}; + + struct InputFrame cur_input_frame = {0}; V2 input = (V2){ .x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A], .y = (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S], }; - curmsg.movement = input; - curmsg.inhabit = keypressed[SAPP_KEYCODE_G].pressed; - curmsg.dobuild = mouse_pressed; - curmsg.grid_index = grid_index; - if (curmsg.dobuild) + cur_input_frame.movement = input; + cur_input_frame.inhabit = keypressed[SAPP_KEYCODE_G].pressed; + cur_input_frame.dobuild = mouse_pressed; + cur_input_frame.grid_index = grid_index; + if (cur_input_frame.dobuild) { if (grid_index != -1) { - curmsg.build = grid_world_to_local(&gs.grids[curmsg.grid_index], build_preview.pos); - V2 untransformed = grid_local_to_world(&gs.grids[curmsg.grid_index], curmsg.build); + cur_input_frame.build = grid_world_to_local(&gs.grids[cur_input_frame.grid_index], build_preview.pos); + V2 untransformed = grid_local_to_world(&gs.grids[cur_input_frame.grid_index], cur_input_frame.build); untransformed.x += 5.0f; } else { - curmsg.build = build_preview.pos; + cur_input_frame.build = build_preview.pos; + } + } + + struct InputFrame latest = client_to_server.inputs[0]; + // if they're not the same + if ( + !V2cmp(cur_input_frame.movement, latest.movement, 0.01f) || + cur_input_frame.inhabit != latest.inhabit || + cur_input_frame.dobuild != latest.dobuild || + cur_input_frame.grid_index != latest.grid_index || + !V2cmp(cur_input_frame.build, latest.build, 0.01f)) + { + for (int i = 0; i < INPUT_BUFFER - 1; i++) + { + client_to_server.inputs[i + 1] = client_to_server.inputs[i]; } + cur_input_frame.tick = gs.tick; + client_to_server.inputs[0] = cur_input_frame; } - // @BeforeShip figure out why tf the possess ship key is so unreliable - ENetPacket *packet = enet_packet_create((void *)&curmsg, sizeof(curmsg), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); - enet_peer_send(peer, 0, packet); + static double last_input_sent_time = 0.0; + if (fabs(last_input_sent_time - time) > TIME_BETWEEN_INPUT_PACKETS) + { + ENetPacket *packet = enet_packet_create((void *)&client_to_server, sizeof(client_to_server), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); + enet_peer_send(peer, 0, packet); + last_input_sent_time = time; + } } // @BeforeShip client side prediction and rollback to previous server authoritative state, then replay inputs @@ -434,9 +457,6 @@ static void frame(void) } } - set_color(RED); - sgp_draw_filled_rect(1.0f, 0.5f, 0.3f, 0.3f); - // gold target set_color(GOLD); sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f); @@ -466,21 +486,28 @@ void event(const sapp_event *e) switch (e->type) { case SAPP_EVENTTYPE_KEY_DOWN: - keydown[e->key_code] = true; if (e->key_code == SAPP_KEYCODE_T) { mouse_frozen = !mouse_frozen; } - if (keypressed[e->key_code].frame == 0) + if (!mouse_frozen) { - keypressed[e->key_code].pressed = true; - keypressed[e->key_code].frame = e->frame_count; + keydown[e->key_code] = true; + if (keypressed[e->key_code].frame == 0) + { + keypressed[e->key_code].pressed = true; + keypressed[e->key_code].frame = e->frame_count; + } } break; case SAPP_EVENTTYPE_KEY_UP: - keydown[e->key_code] = false; - keypressed[e->key_code].pressed = false; - keypressed[e->key_code].frame = 0; + if (!mouse_frozen) + { + keydown[e->key_code] = false; + keypressed[e->key_code].pressed = false; + + keypressed[e->key_code].frame = 0; + } break; case SAPP_EVENTTYPE_MOUSE_DOWN: if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) diff --git a/server.c b/server.c index fb2a187..172a911 100644 --- a/server.c +++ b/server.c @@ -56,7 +56,7 @@ void server(void *data) ENetAddress address; ENetHost *server; int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS); - if(sethost != 0) + if (sethost != 0) { Log("Fishy return value from set host: %d\n", sethost); } @@ -78,6 +78,7 @@ void server(void *data) ENetEvent event; uint64_t last_processed_time = stm_now(); float total_time = 0.0f; + uint64_t player_to_latest_tick_processed[MAX_PLAYERS] = {0}; while (true) { // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this @@ -140,34 +141,34 @@ void server(void *data) struct ClientToServer received = {0}; memcpy(&received, event.packet->data, length); int64_t player_slot = (int64_t)event.peer->data; + uint64_t latest_tick = player_to_latest_tick_processed[player_slot]; - // dobuild logging - if (false) + if (received.inputs[0].tick > latest_tick) { - if (received.dobuild) + for (int i = INPUT_BUFFER - 1; i >= 0; i--) { - Log("Received build command\n"); + if (received.inputs[i].tick == 0) // empty input + continue; + if(received.inputs[i].tick <= latest_tick) + continue; // don't reprocess inputs already processed + struct InputFrame cur_input = received.inputs[i]; + gs.players[player_slot].movement = cur_input.movement; + gs.players[player_slot].grid_index = cur_input.grid_index; + + // for these "event" inputs, only modify the game state if the event is true. + // while processing the gamestate, will mark it as false once processed. This + // prevents setting the event input to false before it's been processed. + if (cur_input.inhabit) + { + gs.players[player_slot].inhabit = cur_input.inhabit; + } + if (cur_input.dobuild) + { + gs.players[player_slot].build = cur_input.build; + gs.players[player_slot].dobuild = cur_input.dobuild; + } } - if (gs.players[player_slot].dobuild && !received.dobuild) - { - Log("Received end of build command\n"); - } - } - - gs.players[player_slot].movement = received.movement; - gs.players[player_slot].grid_index = received.grid_index; - - // for these "event" inputs, only modify the game state if the event is true. - // while processing the gamestate, will mark it as false once processed. This - // prevents setting the event input to false before it's been processed. - if (received.inhabit) - { - gs.players[player_slot].inhabit = received.inhabit; - } - if (received.dobuild) - { - gs.players[player_slot].build = received.build; - gs.players[player_slot].dobuild = received.dobuild; + player_to_latest_tick_processed[player_slot] = received.inputs[0].tick; } } diff --git a/types.h b/types.h index 38103e5..123e5fc 100644 --- a/types.h +++ b/types.h @@ -8,7 +8,9 @@ #define MAX_BOXES_PER_GRID 32 #define BOX_MASS 1.0f #define TIMESTEP (1.0f / 60.0f) // not required to simulate at this, but this defines what tick the game is on +#define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f) #define SERVER_PORT 2551 +#define INPUT_BUFFER 4 // must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1" #include "ipsettings.h" // don't leak IP! @@ -61,6 +63,7 @@ struct GameState { cpSpace *space; + uint64_t tick; double time; V2 goldpos; @@ -108,13 +111,17 @@ struct ServerToClient struct ClientToServer { - V2 movement; - bool inhabit; + struct InputFrame + { + uint64_t tick; + V2 movement; + bool inhabit; - // if grid_index != -1, this is in local coordinates to the grid - V2 build; - bool dobuild; - int grid_index; + // if grid_index != -1, this is in local coordinates to the grid + V2 build; + bool dobuild; + int grid_index; + } inputs[INPUT_BUFFER]; }; // server @@ -224,6 +231,11 @@ static V2 V2sub(V2 a, V2 b) }; } +static bool V2cmp(V2 a, V2 b, float eps) +{ + return V2length(V2sub(a, b)) < eps; +} + static inline float clamp01(float f) { return fmax(0.0f, fmin(f, 1.0f));