Save world and red vignette on damage

main
Cameron Murphy Reikes 2 years ago
parent dd5731eb45
commit de4de50aac

1
.gitignore vendored

@ -1,3 +1,4 @@
debug_world.bin
flight*.zip
flight_server
flight.zip

@ -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)));

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@ -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;

@ -5,13 +5,14 @@
#include <stdio.h>
#include <inttypes.h> // int64 printing
#include <stdlib.h>
#include <string.h> // error string
#include <errno.h>
#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);

@ -5,6 +5,6 @@
int main(int argc, char **argv)
{
server(0);
server("world.bin");
return 0;
}

@ -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);

Loading…
Cancel
Save