diff --git a/.gitignore b/.gitignore index 4e2cb22..3666051 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +debug_world.bin flight*.zip flight_server flight.zip diff --git a/gamestate.c b/gamestate.c index 6618b6d..404e615 100644 --- a/gamestate.c +++ b/gamestate.c @@ -275,6 +275,7 @@ void create_rectangle_shape(GameState* gs, Entity* e, Entity* parent, V2 pos, V2 void create_player(GameState* gs, Entity* e) { e->is_player = true; + e->no_save_to_disk = true; create_body(gs, e); create_rectangle_shape(gs, e, e, (V2) { 0 }, V2scale(PLAYER_SIZE, 0.5f), PLAYER_MASS); cpShapeSetFilter(e->shape, PLAYER_SHAPE_FILTER); @@ -580,7 +581,7 @@ V2 entity_pos(Entity* e) } else if (e->is_explosion) { return e->explosion_pos; - } + } else { assert(e->body != NULL); return cp_to_v2(cpBodyGetPosition(e->body)); @@ -618,21 +619,24 @@ typedef struct SerState size_t cursor; // points to next available byte, is the size of current message after serializing something size_t max_size; Entity* for_player; + bool write_varnames; + bool save_or_load_from_disk; + + // output + uint32_t version; } SerState; void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name, const char* file, int line) { -#ifdef WRITE_VARNAMES char var_name[512] = { 0 }; snprintf(var_name, 512, "%d%s", line, name); // can't have separator before the name, when comparing names skips past the digit size_t var_name_len = strlen(var_name); -#endif if (ser->serializing) { - -#ifdef WRITE_VARNAMES - memcpy(ser->bytes + ser->cursor, var_name, var_name_len); - ser->cursor += var_name_len; -#endif + if (ser->write_varnames) + { + memcpy(ser->bytes + ser->cursor, var_name, var_name_len); + ser->cursor += var_name_len; + } for (int b = 0; b < var_size; b++) { ser->bytes[ser->cursor] = var_pointer[b]; @@ -642,15 +646,14 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name } else { -#ifdef WRITE_VARNAMES - { + if (ser->write_varnames) { char read_name[512] = { 0 }; for (int i = 0; i < var_name_len; i++) { read_name[i] = ser->bytes[ser->cursor]; ser->cursor += 1; - assert(ser->cursor < ser->max_size); + assert(ser->cursor <= ser->max_size); } read_name[var_name_len] = '\0'; // advance past digits @@ -666,12 +669,11 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name *(char*)NULL = 0; } } -#endif for (int b = 0; b < var_size; b++) { var_pointer[b] = ser->bytes[ser->cursor]; ser->cursor += 1; - assert(ser->cursor < ser->max_size); + assert(ser->cursor <= ser->max_size); } } @@ -679,6 +681,13 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name #define SER_VAR_NAME(var_pointer, name) ser_var(ser, (char*)var_pointer, sizeof(*var_pointer), name, __FILE__, __LINE__) #define SER_VAR(var_pointer) SER_VAR_NAME(var_pointer, #var_pointer) +enum GameVersion { + VInitial, + VAddedTest, + VAddedSerToDisk, + VMax, // this minus one will be the version used +}; + // @Robust probably get rid of this as separate function, just use SER_VAR void ser_V2(SerState* ser, V2* var) { @@ -728,9 +737,21 @@ void ser_player(SerState* ser, Player* p) void ser_entity(SerState* ser, GameState* gs, Entity* e) { + SER_VAR(&e->no_save_to_disk); + if (e->no_save_to_disk && ser->save_or_load_from_disk) + { + return; + } SER_VAR(&e->generation); SER_VAR(&e->damage); + int test; + if (ser->serializing) test = 27; + if (ser->version >= VAddedTest) + SER_VAR(&test); + else test = 27; + assert(test == 27); + bool has_body = ser->serializing && e->body != NULL; SER_VAR(&has_body); @@ -785,6 +806,7 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e) SER_VAR(&e->is_player); if (e->is_player) { + assert(e->no_save_to_disk); ser_entityid(ser, &e->currently_inside_of_box); SER_VAR(&e->goldness); } @@ -824,6 +846,8 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e) void ser_server_to_client(SerState* ser, ServerToClient* s) { + SER_VAR(&ser->version); + GameState* gs = s->cur_gs; int cur_next_entity = 0; @@ -844,11 +868,14 @@ void ser_server_to_client(SerState* ser, ServerToClient* s) ser_V2(ser, &gs->goldpos); - for (size_t i = 0; i < MAX_PLAYERS; i++) + if (!ser->save_or_load_from_disk) { - ser_player(ser, &gs->players[i]); + // @Robust save player data with their ID or something somehow. Like local backup of their account + for (size_t i = 0; i < MAX_PLAYERS; i++) + { + ser_player(ser, &gs->players[i]); + } } - if (ser->serializing) { bool entities_done = false; @@ -869,7 +896,7 @@ void ser_server_to_client(SerState* ser, ServerToClient* s) // are loaded in the parent body is loaded in and can be referenced. BOXES_ITER(gs, cur, e) { - bool this_box_in_range = (ser->for_player == NULL || (ser->for_player != NULL && V2distsqr(entity_pos(ser->for_player), entity_pos(cur)) < VISION_RADIUS*VISION_RADIUS)); + bool this_box_in_range = (ser->for_player == NULL || (ser->for_player != NULL && V2distsqr(entity_pos(ser->for_player), entity_pos(cur)) < VISION_RADIUS * VISION_RADIUS)); if (cur->always_visible) this_box_in_range = true; if (!this_grid_is_visible && this_box_in_range) { @@ -928,7 +955,7 @@ void ser_server_to_client(SerState* ser, ServerToClient* s) } // for_this_player can be null then the entire world will be sent -void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player) +void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player, bool write_varnames) { assert(msg->cur_gs != NULL); assert(msg != NULL); @@ -939,13 +966,24 @@ void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t .cursor = 0, .max_size = max_len, .for_player = for_this_player, + .version = VMax - 1, }; + if (for_this_player == NULL) // @Robust jank + { + ser.save_or_load_from_disk = true; +} + + ser.write_varnames = write_varnames; +#ifdef WRITE_VARNAMES + ser.write_varnames = true; +#endif + ser_server_to_client(&ser, msg); *out_len = ser.cursor + 1; // @Robust not sure why I need to add one to cursor, ser.cursor should be the length.. } -void from_bytes(struct ServerToClient* msg, char* bytes, size_t max_len) +void from_bytes(struct ServerToClient* msg, char* bytes, size_t max_len, bool write_varnames, bool from_disk) { assert(msg->cur_gs != NULL); assert(msg != NULL); @@ -955,10 +993,17 @@ void from_bytes(struct ServerToClient* msg, char* bytes, size_t max_len) .serializing = false, .cursor = 0, .max_size = max_len, + .save_or_load_from_disk = from_disk, }; + ser.write_varnames = write_varnames; + +#ifdef WRITE_VARNAMES + ser.write_varnames = true; +#endif + ser_server_to_client(&ser, msg); -} + } // has to be global var because can only get this information static cpShape* closest_to_point_in_radius_result = NULL; @@ -1083,17 +1128,19 @@ void entity_ensure_in_orbit(Entity* e) EntityID create_spacestation(GameState* gs) { -#define BOX_AT_TYPE(grid, pos, type) { Entity* box = new_entity(gs); box_create(gs, box, grid, pos); box->box_type = type; box->indestructible = indestructible; box->always_visible = true; } +#define BOX_AT_TYPE(grid, pos, type) { Entity* box = new_entity(gs); box_create(gs, box, grid, pos); box->box_type = type; box->indestructible = indestructible; box->always_visible = true; box->no_save_to_disk = true; } #define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece) bool indestructible = false; Entity* grid = new_entity(gs); grid_create(gs, grid); + grid->no_save_to_disk = true; entity_set_pos(grid, (V2) { -150.0f, 0.0f }); entity_ensure_in_orbit(grid); Entity* explosion_box = new_entity(gs); box_create(gs, explosion_box, grid, (V2) { 0 }); explosion_box->is_explosion_unlock = true; + explosion_box->no_save_to_disk = true; BOX_AT_TYPE(grid, ((V2) { BOX_SIZE, 0 }), BoxExplosive); BOX_AT_TYPE(grid, ((V2) { BOX_SIZE * 2, 0 }), BoxHullpiece); BOX_AT_TYPE(grid, ((V2) { BOX_SIZE * 3, 0 }), BoxHullpiece); @@ -1321,8 +1368,7 @@ void process(GameState* gs, float dt) } if (sqdist < (SUN_RADIUS * SUN_RADIUS)) { - entity_destroy(gs, e); - continue; + e->damage += 10.0f * dt; } cpVect g = cpvmult(p, -SUN_GRAVITY_STRENGTH / (sqdist * cpfsqrt(sqdist))); diff --git a/loaded/low_health.png b/loaded/low_health.png new file mode 100644 index 0000000..b4d1c90 Binary files /dev/null and b/loaded/low_health.png differ diff --git a/main.c b/main.c index d008f31..a10b43e 100644 --- a/main.c +++ b/main.c @@ -56,6 +56,7 @@ static sg_image image_sun; static sg_image image_medbay_used; static sg_image image_mystery; static sg_image image_explosion; +static sg_image image_low_health; static int cur_editing_boxtype = -1; static int cur_editing_rotation = 0; @@ -181,6 +182,7 @@ init(void) image_medbay_used = load_image("loaded/medbay_used.png"); image_mystery = load_image("loaded/mystery.png"); image_explosion = load_image("loaded/explosion.png"); + image_low_health = load_image("loaded/low_health.png"); } // socket initialization @@ -460,7 +462,7 @@ frame(void) // @Robust not sure what return_value is, error test on it somehow if (return_value == LZO_E_OK) { - from_bytes(&msg, decompressed, decompressed_max_len); + from_bytes(&msg, decompressed, decompressed_max_len, false, false); myplayer = msg.your_player; } else { @@ -834,7 +836,6 @@ frame(void) sgp_rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y); sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); sgp_set_image(0, image_player); - printf("%f\n", zoom); draw_texture_rectangle_centered(entity_pos(e), V2scale(PLAYER_SIZE, player_scaling)); sgp_reset_image(0); } @@ -870,10 +871,18 @@ frame(void) dbg_drawall(); } // world space transform end - } + // low health + if (myentity() != NULL) + { + sgp_set_color(1.0f, 1.0f, 1.0f, myentity()->damage); + sgp_set_image(0, image_low_health); + draw_texture_rectangle_centered((V2) { width / 2.0f, height / 2.0f }, (V2) { width, height }); + sgp_reset_image(0); + } - // UI drawn in screen space - ui(true, dt, width, height); + // UI drawn in screen space + ui(true, dt, width, height); + } sg_pass_action pass_action = { 0 }; sg_begin_default_pass(&pass_action, (int)width, (int)height); @@ -968,7 +977,7 @@ sokol_main(int argc, char* argv[]) { bool hosting = false; if (argc > 1) { - _beginthread(server, 0, NULL); + _beginthread(server, 0, "debug_world.bin"); hosting = true; } (void)argv; diff --git a/server.c b/server.c index 8c954e1..5c1ffe9 100644 --- a/server.c +++ b/server.c @@ -5,13 +5,14 @@ #include #include // int64 printing #include +#include // error string +#include #include "minilzo.h" // started in a thread from host -void server(void* data) +void server(void* world_save_name) { - (void)data; stm_setup(); @@ -21,6 +22,36 @@ void server(void* data) initialize(&gs, entity_data, entities_size); Log("Allocated %zu bytes for entities\n", entities_size); + if (world_save_name != NULL) + { + size_t read_game_data_buffer_size = entities_size; + char* read_game_data = malloc(read_game_data_buffer_size); + + FILE* file = NULL; + fopen_s(&file, (const char*)world_save_name, "rb"); + if (file == NULL) + { + Log("Could not read from data file %s: errno %d\n", (const char*)world_save_name, errno); + } + else + { + size_t actual_length = fread(read_game_data, sizeof(char), entities_size, file); + if (actual_length <= 1) + { + Log("Could only read %zu bytes, error: errno %d\n", actual_length, errno); + exit(-1); + } + Log("Read %zu bytes from save file\n", actual_length); + ServerToClient msg = (ServerToClient){ + .cur_gs = &gs, + }; + from_bytes(&msg, read_game_data, actual_length, true, true); + fclose(file); + } + + free(read_game_data); + } + #define BOX_AT_TYPE(grid, pos, type) { Entity* box = new_entity(&gs); box_create(&gs, box, grid, pos); box->box_type = type; } #define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece) @@ -43,12 +74,12 @@ void server(void* data) entity_set_rotation(grid, PI / 1.7f); cpBodySetVelocity(grid->body, cpv(-0.1, 0.0)); cpBodySetAngularVelocity(grid->body, 1.0f); - - BOX_AT(grid,((V2) { 0 })); - BOX_AT(grid,((V2) { BOX_SIZE, 0 })); - BOX_AT(grid,((V2) { 2.0*BOX_SIZE, 0 })); - BOX_AT(grid,((V2) { 2.0*BOX_SIZE, BOX_SIZE })); - BOX_AT(grid,((V2) { 0.0*BOX_SIZE, -BOX_SIZE })); + + BOX_AT(grid, ((V2) { 0 })); + BOX_AT(grid, ((V2) { BOX_SIZE, 0 })); + BOX_AT(grid, ((V2) { 2.0 * BOX_SIZE, 0 })); + BOX_AT(grid, ((V2) { 2.0 * BOX_SIZE, BOX_SIZE })); + BOX_AT(grid, ((V2) { 0.0 * BOX_SIZE, -BOX_SIZE })); } if (enet_initialize() != 0) @@ -81,8 +112,10 @@ void server(void* data) Log("Serving on port %d...\n", SERVER_PORT); ENetEvent event; uint64_t last_processed_time = stm_now(); + uint64_t last_saved_world_time = stm_now(); float total_time = 0.0f; size_t player_to_latest_id_processed[MAX_PLAYERS] = { 0 }; + char* world_save_buffer = malloc(entities_size); while (true) { // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this @@ -132,12 +165,12 @@ void server(void* data) break; } - + case ENET_EVENT_TYPE_RECEIVE: { - //Log("A packet of length %zu was received on channel %u.\n", - // event.packet->dataLength, - //event.channelID); + //Log("A packet of length %zu was received on channel %u.\n", + // event.packet->dataLength, + //event.channelID); size_t length = event.packet->dataLength; if (length != sizeof(struct ClientToServer)) @@ -195,7 +228,7 @@ void server(void* data) int player_index = (int)(int64_t)event.peer->data; Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index); Entity* player_body = get_entity(&gs, gs.players[player_index].entity); - if(player_body != NULL) + if (player_body != NULL) { entity_destroy(&gs, player_body); } @@ -220,6 +253,33 @@ void server(void* data) total_time -= TIMESTEP; } + if (world_save_name != NULL && (stm_sec(stm_diff(stm_now(), last_saved_world_time))) > TIME_BETWEEN_WORLD_SAVE) + { + last_saved_world_time = stm_now(); + ServerToClient msg = (ServerToClient){ + .cur_gs = &gs, + }; + size_t out_len = 0; + into_bytes(&msg, world_save_buffer, &out_len, entities_size, NULL, true); + FILE* save_file = NULL; + fopen_s(&save_file, (const char*)world_save_name, "wb"); + if (save_file == NULL) + { + Log("Could not open save file: errno %d\n", errno); + } + else { + size_t data_written = fwrite(world_save_buffer, sizeof(*world_save_buffer), out_len, save_file); + if (data_written != out_len) + { + Log("Failed to save world data, wanted to write %zu but could only write %zu\n", out_len, data_written); + } + else { + Log("Saved game world to %s\n", (const char*)world_save_name); + } + fclose(save_file); + } + } + if (processed) { static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = { 0 }; @@ -233,21 +293,21 @@ void server(void* data) Entity* this_player_entity = get_entity(&gs, gs.players[this_player_index].entity); if (this_player_entity == NULL) continue; // @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again - char* bytes_buffer = malloc(sizeof *bytes_buffer * MAX_BYTES_SIZE); + char* bytes_buffer = malloc(sizeof * bytes_buffer * MAX_BYTES_SIZE); char* compressed_buffer = malloc(sizeof * compressed_buffer * MAX_BYTES_SIZE); struct ServerToClient to_send; to_send.cur_gs = &gs; to_send.your_player = this_player_index; size_t len = 0; - into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE, this_player_entity); + into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE, this_player_entity, false); if (len > MAX_BYTES_SIZE - 8) { Log("Too much data quitting!\n"); exit(-1); } - size_t compressed_len = 0; + size_t compressed_len = 0; lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void*)lzo_working_mem); #ifdef LOG_GAMESTATE_SIZE @@ -268,6 +328,7 @@ void server(void* data) } } + free(world_save_buffer); destroy(&gs); free(entity_data); enet_host_destroy(server); diff --git a/server_main.c b/server_main.c index 689b8ce..d5758cd 100644 --- a/server_main.c +++ b/server_main.c @@ -5,6 +5,6 @@ int main(int argc, char **argv) { - server(0); + server("world.bin"); return 0; } diff --git a/types.h b/types.h index 8f66513..dfffdd9 100644 --- a/types.h +++ b/types.h @@ -31,6 +31,7 @@ #define EXPLOSION_RADIUS 1.0f #define EXPLOSION_DAMAGE_THRESHOLD 0.2f // how much damage until it explodes #define GOLD_UNLOCK_RADIUS 1.0f +#define TIME_BETWEEN_WORLD_SAVE 5.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) @@ -142,6 +143,8 @@ typedef struct Entity EntityID next_free_entity; unsigned int generation; + bool no_save_to_disk; // stuff generated later on, like player's bodies or space stations that respawn. + float damage; // used by box and player cpBody* body; // used by grid, player, and box cpShape* shape; // must be a box so shape_size can be set appropriately, and serialized @@ -263,8 +266,8 @@ void destroy(struct GameState* gs); void process(struct GameState* gs, float dt); // does in place Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius); uint64_t tick(struct GameState* gs); -void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player); -void from_bytes(struct ServerToClient* gs, char* bytes, size_t max_len); +void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player, bool write_varnames); +void from_bytes(struct ServerToClient* msg, char* bytes, size_t max_len, bool write_varnames, bool from_disk); // entities Entity* get_entity(struct GameState* gs, EntityID id);