From cb891afae7677f1f0ecfd45a89b1ca32c65310fa Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Thu, 3 Nov 2022 20:20:58 -0700 Subject: [PATCH] Complete cockpit implementation, VS format --- Flight.vcxproj | 3 +- Flight.vcxproj.filters | 112 ++++---- gamestate.c | 378 ++++++++++++-------------- loaded/cockpit.png | Bin 0 -> 727 bytes loaded/cockpit_used.png | Bin 0 -> 840 bytes main.c | 585 +++++++++++++++++++--------------------- server.c | 20 +- types.h | 373 +++++++++++++------------ 8 files changed, 721 insertions(+), 750 deletions(-) create mode 100644 loaded/cockpit.png create mode 100644 loaded/cockpit_used.png diff --git a/Flight.vcxproj b/Flight.vcxproj index 91cd780..4b78c28 100644 --- a/Flight.vcxproj +++ b/Flight.vcxproj @@ -105,6 +105,7 @@ _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true C:\Users\Cameron\Documents\flight\thirdparty\enet\include;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include\chipmunk;C:\Users\Cameron\Documents\flight\thirdparty;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include + true Console @@ -192,4 +193,4 @@ - + \ No newline at end of file diff --git a/Flight.vcxproj.filters b/Flight.vcxproj.filters index 1820211..8c2885d 100644 --- a/Flight.vcxproj.filters +++ b/Flight.vcxproj.filters @@ -15,174 +15,172 @@ - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Header Files - + Header Files - + Header Files - + Header Files - + Header Files - + Header Files - + Header Files - + Header Files - + Header Files - - Source Files - + \ No newline at end of file diff --git a/gamestate.c b/gamestate.c index b6a74a3..18504f9 100644 --- a/gamestate.c +++ b/gamestate.c @@ -37,7 +37,7 @@ static cpVect v2_to_cp(V2 v) return cpv(v.x, v.y); } -bool was_entity_deleted(struct GameState* gs, EntityID id) +bool was_entity_deleted(GameState* gs, EntityID id) { if (id.generation == 0) return false; // generation 0 means null entity ID, not a deleted entity Entity* the_entity = &gs->entities[id.index]; @@ -45,7 +45,7 @@ bool was_entity_deleted(struct GameState* gs, EntityID id) } // may return null if it doesn't exist anymore -Entity* get_entity(struct GameState* gs, EntityID id) +Entity* get_entity(GameState* gs, EntityID id) { if (id.generation == 0) { @@ -59,18 +59,18 @@ Entity* get_entity(struct GameState* gs, EntityID id) return to_return; } -EntityID get_id(struct GameState* gs, Entity* e) +EntityID get_id(GameState* gs, Entity* e) { if (e == NULL) return (EntityID) { 0 }; - size_t index = e - gs->entities; + size_t index = (e - gs->entities); assert(index >= 0); assert(index < gs->cur_next_entity); return (EntityID) { .generation = e->generation, - .index = index, + .index = (unsigned int)index, }; } @@ -84,12 +84,12 @@ static Entity* cp_body_entity(cpBody* body) return (Entity*)cpBodyGetUserData(body); } -static struct GameState* cp_space_gs(cpSpace* space) +static GameState* cp_space_gs(cpSpace* space) { - return (struct GameState*)cpSpaceGetUserData(space); + return (GameState*)cpSpaceGetUserData(space); } -int grid_num_boxes(struct GameState* gs, Entity* e) +int grid_num_boxes(GameState* gs, Entity* e) { assert(e->is_grid); int to_return = 0; @@ -122,6 +122,20 @@ void box_remove_from_boxes(GameState* gs, Entity* box) } void on_entity_child_shape(cpBody* body, cpShape* shape, void* data); + +// gs is for iterating over all child shapes and destroying those, too +static void destroy_body(GameState* gs, cpBody** body) +{ + if (*body != NULL) + { + cpBodyEachShape(*body, on_entity_child_shape, (void*)gs); + cpSpaceRemoveBody(gs->space, *body); + cpBodyFree(*body); + *body = NULL; + } + *body = NULL; +} + void entity_destroy(GameState* gs, Entity* e) { assert(e->exists); @@ -142,15 +156,7 @@ void entity_destroy(GameState* gs, Entity* e) cpShapeFree(e->shape); e->shape = NULL; } - if (e->body != NULL) - { - cpBodyEachShape(e->body, on_entity_child_shape, (void*)gs); - cpSpaceRemoveBody(gs->space, e->body); - cpBodyFree(e->body); - e->body = NULL; - } - e->body = NULL; - e->shape = NULL; + destroy_body(gs, &e->body); Entity* front_of_free_list = get_entity(gs, gs->free_list); if (front_of_free_list != NULL) @@ -167,7 +173,7 @@ void on_entity_child_shape(cpBody* body, cpShape* shape, void* data) entity_destroy((GameState*)data, cp_shape_entity(shape)); } -Entity* new_entity(struct GameState* gs) +Entity* new_entity(GameState* gs) { Entity* to_return = NULL; if (get_entity(gs, gs->free_list) != NULL) @@ -188,7 +194,7 @@ Entity* new_entity(struct GameState* gs) return to_return; } -void create_body(struct GameState* gs, Entity* e) +void create_body(GameState* gs, Entity* e) { assert(gs->space != NULL); @@ -204,7 +210,20 @@ void create_body(struct GameState* gs, Entity* e) cpBodySetUserData(e->body, (void*)e); } -void grid_create(struct GameState* gs, Entity* e) +V2 player_vel(GameState* gs, Entity* player) +{ + assert(player->is_player); + Entity* potential_seat = get_entity(gs, player->currently_piloting_seat); + if (potential_seat != NULL) + { + return cp_to_v2(cpBodyGetVelocity(get_entity(gs, potential_seat->shape_parent_entity)->body)); + } + else { + return cp_to_v2(cpBodyGetVelocity(player->body)); + } +} + +void grid_create(GameState* gs, Entity* e) { e->is_grid = true; create_body(gs, e); @@ -243,7 +262,7 @@ void create_rectangle_shape(GameState* gs, Entity* e, Entity* parent, V2 pos, V2 cpSpaceAddShape(gs->space, e->shape); } -void create_player(struct GameState* gs, Entity* e) +void create_player(GameState* gs, Entity* e) { e->is_player = true; create_body(gs, e); @@ -253,7 +272,7 @@ void create_player(struct GameState* gs, Entity* e) // box must be passed as a parameter as the box added to chipmunk uses this pointer in its // user data. pos is in local coordinates. Adds the box to the grid's chain of boxes -void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos) +void box_create(GameState* gs, Entity* new_box, Entity* grid, V2 pos) { new_box->is_box = true; assert(gs->space != NULL); @@ -276,7 +295,7 @@ void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos) // removes boxes from grid, then ensures that the rule that grids must not have // holes in them is applied. -static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct Entity* box) +static void grid_remove_box(GameState* gs, struct Entity* grid, struct Entity* box) { assert(grid->is_grid); assert(box->is_box); @@ -341,17 +360,17 @@ static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct En V2 cur_local_pos = entity_shape_pos(N); const V2 dirs[] = { (V2) { -.x = -1.0f, .y = 0.0f -}, - (V2) { -.x = 1.0f, .y = 0.0f -}, - (V2) { -.x = 0.0f, .y = 1.0f -}, - (V2) { -.x = 0.0f, .y = -1.0f -}, + .x = -1.0f, .y = 0.0f + }, + (V2) { + .x = 1.0f, .y = 0.0f + }, + (V2) { + .x = 0.0f, .y = 1.0f + }, + (V2) { + .x = 0.0f, .y = -1.0f + }, }; int num_dirs = sizeof(dirs) / sizeof(*dirs); @@ -364,7 +383,7 @@ static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct En EntityID box_in_direction = (EntityID){ 0 }; BOXES_ITER(gs, cur, grid) { - if (V2cmp(entity_shape_pos(cur), wanted_local_pos, 0.01f)) + if (V2equal(entity_shape_pos(cur), wanted_local_pos, 0.01f)) { box_in_direction = get_id(gs, cur); break; @@ -428,18 +447,6 @@ static void grid_remove_box(struct GameState* gs, struct Entity* grid, struct En } } -static void postStepRemove(cpSpace* space, void* key, void* data) -{ - cpShape* b = (cpShape*)key; - Entity* e = cp_shape_entity(b); - // @Robust why not just do these deletions in the update loop? save on a lot of complexity - if (e->damage > 1.0f) - { - if (e->is_box) - grid_remove_box(cp_space_gs(space), cp_body_entity(cpShapeGetBody(b)), e); - } -} - static cpBool on_damage(cpArbiter* arb, cpSpace* space, cpDataPointer userData) { cpShape* a, * b; @@ -458,28 +465,28 @@ static cpBool on_damage(cpArbiter* arb, cpSpace* space, cpDataPointer userData) } // b must be the key passed into the post step removed, the key is cast into its shape - cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL); - cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL); + //cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL); + //cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL); return true; // keep colliding } -void initialize(struct GameState* gs, void* entity_arena, int entity_arena_size) +void initialize(GameState* gs, void* entity_arena, size_t entity_arena_size) { - *gs = (struct GameState){ 0 }; + *gs = (GameState){ 0 }; memset(entity_arena, 0, entity_arena_size); // SUPER critical. Random vals in the entity data causes big problem gs->entities = (Entity*)entity_arena; - gs->max_entities = entity_arena_size / sizeof(Entity); + gs->max_entities = (unsigned int)(entity_arena_size / sizeof(Entity)); gs->space = cpSpaceNew(); cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler cpCollisionHandler* handler = cpSpaceAddCollisionHandler(gs->space, 0, 0); // @Robust limit collision type to just blocks that can be damaged handler->postSolveFunc = on_damage; } -void destroy(struct GameState* gs) +void destroy(GameState* gs) { // can't zero out gs data because the entity memory arena is reused // on deserialization - for (int i = 0; i < gs->max_entities; i++) + for (size_t i = 0; i < gs->max_entities; i++) { if (gs->entities[i].exists) entity_destroy(gs, &gs->entities[i]); @@ -494,9 +501,11 @@ V2 grid_com(Entity* grid) return cp_to_v2(cpBodyLocalToWorld(grid->body, cpBodyGetCenterOfGravity(grid->body))); } -V2 entity_pos(Entity* grid) +V2 entity_pos(Entity* e) { - return cp_to_v2(cpBodyGetPosition(grid->body)); + assert(!e->is_box); + // @Robust merge entity_pos with box_pos + return cp_to_v2(cpBodyGetPosition(e->body)); } V2 grid_vel(Entity* grid) { @@ -579,7 +588,7 @@ void update_from(cpBody* body, struct BodyData* data) cpBodySetAngularVelocity(body, data->angular_velocity); } -#define WRITE_VARNAMES // debugging feature horrible for network +//#define WRITE_VARNAMES // debugging feature horrible for network typedef struct SerState { char* bytes; @@ -587,10 +596,11 @@ typedef struct SerState size_t cursor; // points to next available byte, is the size of current message after serializing something size_t max_size; } SerState; -void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name) +void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name, const char* file, int line) { - const char* var_name = name; #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) @@ -611,8 +621,8 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name { #ifdef WRITE_VARNAMES { - char read_name[1024] = { 0 }; - + char read_name[512] = { 0 }; + for (int i = 0; i < var_name_len; i++) { read_name[i] = ser->bytes[ser->cursor]; @@ -620,11 +630,18 @@ void ser_var(SerState* ser, char* var_pointer, size_t var_size, const char* name assert(ser->cursor < ser->max_size); } read_name[var_name_len] = '\0'; - if (strcmp(read_name, var_name) != 0) + // advance past digits + char* read = read_name; + char* var = var_name; + while (*read >= '0' && *read <= '9') + read++; + while (*var >= '0' && *var <= '9') + var++; + if (strcmp(read, var) != 0) { - printf("%s:%d | Expected variable %s but got %sn\n", __FILE__, __LINE__, var_name, read_name); + printf("%s:%d | Expected variable %s but got %sn\n", file, line, var_name, read_name); *(char*)NULL = 0; - } +} } #endif for (int b = 0; b < var_size; b++) @@ -636,7 +653,7 @@ 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) +#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) void ser_V2(SerState* ser, V2* var) @@ -661,16 +678,20 @@ void ser_entityid(SerState* ser, EntityID* id) void ser_inputframe(SerState* ser, struct InputFrame* i) { + SER_VAR(&i->tick); SER_VAR(&i->movement); - SER_VAR(&i->inhabit); - SER_VAR(&i->build); + + SER_VAR(&i->seat_action); + ser_entityid(ser, &i->seat_to_inhabit); + SER_VAR(&i->hand_pos); + SER_VAR(&i->dobuild); SER_VAR(&i->build_type); SER_VAR(&i->build_rotation); ser_entityid(ser, &i->grid_to_build_on); } -void ser_player(SerState* ser, struct Player* p) +void ser_player(SerState* ser, Player* p) { SER_VAR(&p->connected); if (p->connected) @@ -680,7 +701,7 @@ void ser_player(SerState* ser, struct Player* p) } } -void ser_entity(SerState* ser, struct GameState* gs, Entity* e) +void ser_entity(SerState* ser, GameState* gs, Entity* e) { SER_VAR(&e->generation); SER_VAR(&e->damage); @@ -754,13 +775,15 @@ void ser_entity(SerState* ser, struct GameState* gs, Entity* e) ser_entityid(ser, &e->prev_box); SER_VAR(&e->compass_rotation); SER_VAR(&e->thrust); + SER_VAR(&e->wanted_thrust); SER_VAR(&e->energy_used); + ser_entityid(ser, &e->piloted_by); } } void ser_server_to_client(SerState* ser, ServerToClient* s) { - struct GameState* gs = s->cur_gs; + GameState* gs = s->cur_gs; int cur_next_entity = 0; if (ser->serializing) @@ -780,20 +803,22 @@ void ser_server_to_client(SerState* ser, ServerToClient* s) ser_V2(ser, &gs->goldpos); - for (int i = 0; i < MAX_PLAYERS; i++) + for (size_t i = 0; i < MAX_PLAYERS; i++) { ser_player(ser, &gs->players[i]); } if (ser->serializing) { - for (int i = 0; i < gs->cur_next_entity; i++) + bool entities_done = false; + for (size_t i = 0; i < gs->cur_next_entity; i++) { Entity* e = &gs->entities[i]; if (e->exists) { if (e->is_player) { + SER_VAR(&entities_done); SER_VAR(&i); ser_entity(ser, gs, e); } @@ -801,35 +826,41 @@ void ser_server_to_client(SerState* ser, ServerToClient* s) { // serialize boxes always after bodies, so that by the time the boxes // are loaded in the parent body is loaded in and can be referenced. + SER_VAR(&entities_done); SER_VAR(&i); ser_entity(ser, gs, e); BOXES_ITER(gs, cur, e) { EntityID cur_id = get_id(gs, cur); - SER_VAR_NAME(&cur_id.index, "&i"); + assert(cur_id.index < gs->max_entities); + SER_VAR(&entities_done); + size_t the_index = (size_t)cur_id.index; // super critical. Type of &i is size_t. @Robust add debug info in serialization for what size the expected type is, maybe string nameof the type + SER_VAR_NAME(&the_index, "&i"); ser_entity(ser, gs, cur); } } } } - int end_of_entities = -1; - SER_VAR_NAME(&end_of_entities, "&i"); + entities_done = true; + SER_VAR(&entities_done); } else { while (true) { - int next_index; - SER_VAR_NAME(&next_index, "&i"); - if (next_index == -1) + bool entities_done = false; + SER_VAR(&entities_done); + if (entities_done) break; + size_t next_index; + SER_VAR_NAME(&next_index, "&i"); assert(next_index < gs->max_entities); Entity* e = &gs->entities[next_index]; e->exists = true; ser_entity(ser, gs, e); - gs->cur_next_entity = max(gs->cur_next_entity, next_index + 1); + gs->cur_next_entity = (unsigned int)max(gs->cur_next_entity, next_index + 1); } - for (int i = 0; i < gs->cur_next_entity; i++) + for (size_t i = 0; i < gs->cur_next_entity; i++) { Entity* e = &gs->entities[i]; if (!e->exists) @@ -841,7 +872,7 @@ void ser_server_to_client(SerState* ser, ServerToClient* s) } } -void into_bytes(struct ServerToClient* msg, char* bytes, size_t * out_len, size_t max_len) +void into_bytes(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len) { assert(msg->cur_gs != NULL); assert(msg != NULL); @@ -889,7 +920,7 @@ static void closest_point_callback_func(cpShape* shape, cpContactPointSet* point } } -Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius) +Entity* closest_to_point_in_radius(GameState* gs, V2 point, float radius) { closest_to_point_in_radius_result = NULL; closest_to_point_in_radius_result_largest_dist = 0.0f; @@ -926,12 +957,12 @@ V2 thruster_force(Entity* box) return V2scale(thruster_direction(box), -box->thrust * THRUSTER_FORCE); } -uint64_t tick(struct GameState* gs) +uint64_t tick(GameState* gs) { return (uint64_t)floor(gs->time / ((double)TIMESTEP)); } -void process(struct GameState* gs, float dt) +void process(GameState* gs, float dt) { assert(gs->space != NULL); @@ -956,70 +987,42 @@ void process(struct GameState* gs, float dt) // update gold win condition if (V2length(V2sub(cp_to_v2(cpBodyGetPosition(p->body)), gs->goldpos)) < GOLD_COLLECT_RADIUS) { - p->goldness += 0.1; + p->goldness += 0.1f; p->spice_taken_away = 0.0f; - gs->goldpos = (V2){ .x = hash11(gs->time) * 20.0f, .y = hash11(gs->time - 13.6f) * 20.0f }; - } - - if (get_entity(gs, p->currently_piloting_seat) == NULL) - { - p->currently_piloting_seat = (EntityID){ 0 }; + gs->goldpos = (V2){ .x = hash11((float)gs->time) * 20.0f, .y = hash11((float)gs->time - 13.6f) * 20.0f }; } - // @Todo do getting inside pilot seat -#if 0 - if (p->input.inhabit) +#if 1 + if (player->input.seat_action) { - p->input.inhabit = false; // "handle" the input - if (p->currently_inhabiting_index == -1) + player->input.seat_action = false; // "handle" the input + Entity* the_seat = get_entity(gs, p->currently_piloting_seat); + if (the_seat == NULL) // not piloting any seat { - - // @Robust mask to only ship boxes of things the player can inhabit cpPointQueryInfo query_info = { 0 }; - cpShape* result = cpSpacePointQueryNearest(gs->space, v2_to_cp(p->pos), 0.1f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, CP_ALL_CATEGORIES), &query_info); + cpShape* result = cpSpacePointQueryNearest(gs->space, v2_to_cp(player->input.hand_pos), 0.1f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &query_info); if (result != NULL) { - // result is assumed to be a box shape - struct Grid* g = (struct Grid*)cpBodyGetUserData(cpShapeGetBody(result)); - int ship_to_inhabit = -1; - for (int ii = 0; ii < MAX_GRIDS; ii++) - { - SKIPNULL(gs->grids[ii].body); - if (&gs->grids[ii] == g) - { - ship_to_inhabit = ii; - break; - } - } - - // don't allow inhabiting a grid that's already inhabited - for (int ii = 0; ii < MAX_PLAYERS; ii++) + Entity* potential_seat = cp_shape_entity(result); + assert(potential_seat->is_box); + if (potential_seat->box_type == BoxCockpit) { - 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"); - } - else - { - p->currently_inhabiting_index = ship_to_inhabit; + p->currently_piloting_seat = get_id(gs, potential_seat); + potential_seat->piloted_by = get_id(gs, p); } } else { - Log("No ship above player at point %f %f\n", p->pos.x, p->pos.y); + Log("No ship above player at point %f %f\n", player->input.hand_pos.x, player->input.hand_pos.y); } } else { - p->vel = grid_vel(&gs->grids[p->currently_inhabiting_index]); - p->currently_inhabiting_index = -1; + V2 pilot_seat_exit_spot = V2add(box_pos(the_seat), V2rotate((V2) { .x = BOX_SIZE }, rotangle(the_seat->compass_rotation))); + cpBodySetPosition(p->body, v2_to_cp(pilot_seat_exit_spot)); + cpBodySetVelocity(p->body, v2_to_cp(player_vel(gs, p))); + the_seat->piloted_by = (EntityID){ 0 }; + p->currently_piloting_seat = (EntityID){ 0 }; } } #endif @@ -1032,46 +1035,38 @@ void process(struct GameState* gs, float dt) { player->input.movement = V2scale(V2normalize(player->input.movement), clamp(V2length(player->input.movement), 0.0f, 1.0f)); } - cpBodyApplyForceAtWorldPoint(p->body, v2_to_cp(V2scale(player->input.movement, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body)); - p->spice_taken_away += movement_strength * dt * PLAYER_JETPACK_SPICE_PER_SECOND; - // @Todo do pilot seat -#if 0 + Entity* piloting_seat = get_entity(gs, p->currently_piloting_seat); + + if (piloting_seat == NULL) { - struct Grid* g = &gs->grids[p->currently_inhabiting_index]; - V2 target_new_pos = V2lerp(p->pos, grid_com(g), dt * 20.0f); - p->vel = V2scale(V2sub(target_new_pos, p->pos), 1.0f / dt); // set vel correctly so newly built grids have the correct velocity copied from it + cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_ALL); + cpBodyApplyForceAtWorldPoint(p->body, v2_to_cp(V2scale(player->input.movement, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body)); + p->spice_taken_away += movement_strength * dt * PLAYER_JETPACK_SPICE_PER_SECOND; + } + else + { + cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_NONE); // no collisions while going to pilot seat + cpBodySetPosition(p->body, v2_to_cp(box_pos(piloting_seat))); // set thruster thrust from movement { - float energy_available = g->total_energy_capacity; + Entity* g = get_entity(gs, piloting_seat->shape_parent_entity); V2 target_direction = { 0 }; - if (V2length(p->input.movement) > 0.0f) + if (V2length(player->input.movement) > 0.0f) { - target_direction = V2normalize(p->input.movement); + target_direction = V2normalize(player->input.movement); } - for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++) + BOXES_ITER(gs, cur, g) { - SKIPNULL(g->boxes[ii].shape); - if (g->boxes[ii].type != BoxThruster) + if (cur->box_type != BoxThruster) continue; - - float wanted_thrust = -V2dot(target_direction, thruster_direction(&g->boxes[ii])); + float wanted_thrust = -V2dot(target_direction, thruster_direction(cur)); wanted_thrust = clamp01(wanted_thrust); - - float needed_energy = wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt; - energy_available -= needed_energy; - - if (energy_available > 0.0f) - g->boxes[ii].thrust = wanted_thrust; - else - g->boxes[ii].thrust = 0.0f; + cur->wanted_thrust = wanted_thrust; } } - // cpBodyApplyForceAtWorldPoint(g->body, v2_to_cp(V2scale(p->input.movement, 5.0f)), v2_to_cp(grid_com(g))); - // bigger the ship, the more efficient the spice usage } -#endif } #if 1 // building @@ -1080,14 +1075,10 @@ void process(struct GameState* gs, float dt) player->input.dobuild = false; // handle the input. if didn't do this, after destruction of hovered box, would try to build on its grid with grid_index... cpPointQueryInfo info = { 0 }; - V2 world_build = player->input.build; + V2 world_build = player->input.hand_pos; // @Robust sanitize this input so player can't build on any grid in the world Entity* target_grid = get_entity(gs, player->input.grid_to_build_on); - if (target_grid != NULL) - { - world_build = grid_local_to_world(target_grid, player->input.build); - } cpShape* nearest = cpSpacePointQueryNearest(gs->space, v2_to_cp(world_build), 0.01f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &info); if (nearest != NULL) { @@ -1107,7 +1098,7 @@ void process(struct GameState* gs, float dt) box_create(gs, new_box, new_grid, (V2) { 0 }); new_box->box_type = player->input.build_type; new_box->compass_rotation = player->input.build_rotation; - cpBodySetVelocity(new_grid->body, cpBodyGetVelocity(p->body)); + cpBodySetVelocity(new_grid->body, v2_to_cp(player_vel(gs, p))); } else { @@ -1128,59 +1119,42 @@ void process(struct GameState* gs, float dt) p->spice_taken_away = clamp01(p->spice_taken_away); } - // @Todo add thrust from thruster blocks -#if 0 - for (int i = 0; i < MAX_GRIDS; i++) - { - SKIPNULL(gs->grids[i].body); + // process grids + for (size_t i = 0; i < gs->cur_next_entity; i++) { + Entity* e = &gs->entities[i]; + if (!e->exists) + continue; - struct Box* batteries[MAX_BOXES_PER_GRID] = { 0 }; - int cur_battery = 0; - for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++) + if (e->is_box) { - SKIPNULL(gs->grids[i].boxes[ii].shape); - if (gs->grids[i].boxes[ii].type == BoxBattery) + if (e->damage >= 1.0f) { - assert(cur_battery < MAX_BOXES_PER_GRID); - batteries[cur_battery] = &gs->grids[i].boxes[ii]; - cur_battery++; + grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e); } } - int batteries_len = cur_battery; - - float thruster_energy_consumption_per_second = 0.0f; - for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++) + if (e->is_grid) { - SKIPNULL(gs->grids[i].boxes[ii].shape); - if (gs->grids[i].boxes[ii].type == BoxThruster) + + float thruster_energy_consumption_per_second = 0.0f; + BOXES_ITER(gs, cur, e) { - float energy_to_consume = gs->grids[i].boxes[ii].thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt; - struct Box* max_capacity_battery = NULL; - float max_capacity_battery_energy_used = 1.0f; - for (int iii = 0; iii < batteries_len; iii++) + if (cur->box_type == BoxThruster) { - if (batteries[iii]->energy_used < max_capacity_battery_energy_used) + float energy_to_consume = cur->wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt; + cur->thrust = 0.0f; + BOXES_ITER(gs, possible_battery, e) { - max_capacity_battery = batteries[iii]; - max_capacity_battery_energy_used = batteries[iii]->energy_used; + if (possible_battery->box_type == BoxBattery && (1.0f - possible_battery->energy_used) > energy_to_consume) + { + possible_battery->energy_used += energy_to_consume; + cur->thrust = cur->wanted_thrust; + cpBodyApplyForceAtWorldPoint(e->body, v2_to_cp(thruster_force(cur)), v2_to_cp(box_pos(cur))); + } } } - - if (max_capacity_battery != NULL && (1.0f - max_capacity_battery->energy_used) > energy_to_consume) - { - max_capacity_battery->energy_used += energy_to_consume; - cpBodyApplyForceAtWorldPoint(gs->grids[i].body, v2_to_cp(thruster_force(&gs->grids[i].boxes[ii])), v2_to_cp(box_pos(&gs->grids[i].boxes[ii]))); - } } } - - gs->grids[i].total_energy_capacity = 0.0f; - for (int ii = 0; ii < batteries_len; ii++) - { - gs->grids[i].total_energy_capacity += 1.0f - batteries[ii]->energy_used; - } } -#endif cpSpaceStep(gs->space, dt); } \ No newline at end of file diff --git a/loaded/cockpit.png b/loaded/cockpit.png new file mode 100644 index 0000000000000000000000000000000000000000..64c4d4ce81d099239189f7f520dad7dbcc0ffc22 GIT binary patch literal 727 zcmV;|0x127P)EX>4Tx04R}tkv&MmP!xqvQ>CI62RjsT$WR@`f~bh2RIvyaN?V~-2a}inL6e3g z#l=x@EjakISaoo5*44pP5Cnff9G#pLU8KbOl0u6ZA3Wa2dG8$VyB81|6{eb96M(8& zMk*c?v$<6<@QOb80gNFmF;h>Z7c=l&U-$6w^DfG>yx;w~`jx!N0G~)a$8^IY-XNaY zv~C;;dF`tW_ufVKA?)q`6Le7;!8ifh0u8sG*DsEJW$lNHLM3`PFL_LKB_ zTZ~UpV63dz~C*=yXN+-b&t~rAVa-cz5xyn zfzcvmuY3Hvr?YSWzG?OE2UiSooQTGetN;K224YJ`L;wH)0002_L%V+f000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&X{04pU_*yZj3000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}00024NklEX>4Tx04R}tkv&MmP!xqvQ>CI62RjsT$WR@`f~bh2RIvyaN?V~-2a}inL6e3g z#l=x@EjakISaoo5*44pP5Cnff9G#pLU8KbOl0u6ZA3Wa2dG8$VyB81|6{eb96M(8& zMk*c?v$<6<@QOb80gNFmF;h>Z7c=l&U-$6w^DfG>yx;w~`jx!N0G~)a$8^IY-XNaY zv~C;;dF`tW_ufVKA?)q`6Le7;!8ifh0u8sG*DsEJW$lNHLM3`PFL_LKB_ zTZ~UpV63dz~C*=yXN+-b&t~rAVa-cz5xyn zfzcvmuY3Hvr?YSWzG?OE2UiSooQTGetN;K224YJ`L;wH)0002_L%V+f000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&X{04y0a_7AQA000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0003WNkl_{zntuWqH?#EdZvc7epjF1ycu+5-Be8Cc6Y8atiD-Pstuc zP$DgM;H?4F{TQz6s!bfoy%q25&jtX1u=DR=o&q;10Rm|t-+bX?l_3ehU(Ka3L`2Rx zbZf%!h{j-k08%onEs@o*Qt_*(I=mN;hKX9Ii)k%3Vf8gqYcc5Roh= zr; +#include // starting server thread + +#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert() +#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert() #include "sokol_app.h" +#include "sokol_gfx.h" #include "sokol_glue.h" +#include "sokol_gp.h" #include "sokol_time.h" -#include -#include // starting server thread +#pragma warning ( default: 33010 ) +#pragma warning ( disable: 6262 ) // warning about using a lot of stack, lol that's how stb image is #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" - #include "types.h" -#include // errno error message on file open #include +#include // errno error message on file open static struct GameState gs = { 0 }; static int myplayer = -1; static bool right_mouse_down = false; static bool keydown[SAPP_KEYCODE_MENU] = { 0 }; -typedef struct KeyPressed -{ +typedef struct KeyPressed { bool pressed; uint64_t frame; } KeyPressed; @@ -32,8 +35,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; @@ -43,15 +47,16 @@ static sg_image image_itemframe; static sg_image image_itemframe_selected; static sg_image image_thrusterburn; static sg_image image_player; +static sg_image image_cockpit_used; static int cur_editing_boxtype = -1; static int cur_editing_rotation = 0; -static struct BoxInfo -{ +static struct BoxInfo { enum BoxType type; const char* image_path; sg_image image; -} boxes[] = { // if added to here will show up in toolbar, is placeable +} boxes[] = { + // if added to here will show up in toolbar, is placeable { .type = BoxHullpiece, .image_path = "loaded/hullpiece.png", @@ -63,13 +68,18 @@ static struct BoxInfo { .type = BoxBattery, .image_path = "loaded/battery.png", - } }; + }, + { + .type = BoxCockpit, + .image_path = "loaded/cockpit.png", + }, +}; const int boxes_len = sizeof(boxes) / sizeof(*boxes); -struct BoxInfo boxinfo(enum BoxType type) +struct BoxInfo + boxinfo(enum BoxType type) { - for (int i = 0; i < boxes_len; i++) - { + for (int i = 0; i < boxes_len; i++) { if (boxes[i].type == type) return boxes[i]; } @@ -77,7 +87,8 @@ struct BoxInfo boxinfo(enum BoxType type) return (struct BoxInfo) { 0 }; } -static sg_image load_image(const char* path) +static sg_image +load_image(const char* path) { sg_image to_return = sg_alloc_image(); @@ -87,65 +98,66 @@ static sg_image load_image(const char* path) const int desired_channels = 4; stbi_set_flip_vertically_on_load(true); stbi_uc* image_data = stbi_load(path, &x, &y, &comp, desired_channels); - if (!image_data) - { + if (!image_data) { fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason()); exit(-1); } - sg_init_image(to_return, &(sg_image_desc){ - .width = x, - .height = y, - .pixel_format = SG_PIXELFORMAT_RGBA8, - .min_filter = SG_FILTER_NEAREST, - .mag_filter = SG_FILTER_NEAREST, - .data.subimage[0][0] = { - .ptr = image_data, - .size = (size_t)(x * y * desired_channels), - }}); + sg_init_image(to_return, + &(sg_image_desc) {.width = x, + .height = y, + .pixel_format = SG_PIXELFORMAT_RGBA8, + .min_filter = SG_FILTER_NEAREST, + .mag_filter = SG_FILTER_NEAREST, + .data.subimage[0][0] = { + .ptr = image_data, + .size = (size_t)(x * y * desired_channels), + } }); stbi_image_free(image_data); return to_return; } -static void init(void) +static void +init(void) { - // @BeforeShip make all fprintf into logging to file, warning dialog grids on failure instead of exit(-1), replace the macros in sokol with this as well, like assert + // @BeforeShip make all fprintf into logging to file, warning dialog grids on + // failure instead of exit(-1), replace the macros in sokol with this as well, + // like assert Entity* entity_data = malloc(sizeof * entity_data * MAX_ENTITIES); initialize(&gs, entity_data, sizeof * entity_data * MAX_ENTITIES); sg_desc sgdesc = { .context = sapp_sgcontext() }; sg_setup(&sgdesc); - if (!sg_isvalid()) - { + if (!sg_isvalid()) { fprintf(stderr, "Failed to create Sokol GFX context!\n"); exit(-1); } sgp_desc sgpdesc = { 0 }; sgp_setup(&sgpdesc); - if (!sgp_is_valid()) - { - fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error())); + if (!sgp_is_valid()) { + fprintf(stderr, + "Failed to create Sokol GP context: %s\n", + sgp_get_error_message(sgp_get_last_error())); exit(-1); } // image loading { - for (int i = 0; i < boxes_len; i++) - { + for (int i = 0; i < boxes_len; i++) { boxes[i].image = load_image(boxes[i].image_path); } image_thrusterburn = load_image("loaded/thrusterburn.png"); image_itemframe = load_image("loaded/itemframe.png"); image_itemframe_selected = load_image("loaded/itemframe_selected.png"); image_player = load_image("loaded/player.png"); + image_cockpit_used = load_image("loaded/cockpit_used.png"); } // socket initialization { - if (enet_initialize() != 0) - { + if (enet_initialize() != 0) { fprintf(stderr, "An error occurred while initializing ENet.\n"); exit(-1); } @@ -154,9 +166,9 @@ static void init(void) 2 /* allow up 2 channels to be used, 0 and 1 */, 0 /* assume any amount of incoming bandwidth */, 0 /* assume any amount of outgoing bandwidth */); - if (client == NULL) - { - fprintf(stderr, + if (client == NULL) { + fprintf( + stderr, "An error occurred while trying to create an ENet client host.\n"); exit(-1); } @@ -166,20 +178,16 @@ static void init(void) enet_address_set_host(&address, SERVER_ADDRESS); address.port = SERVER_PORT; peer = enet_host_connect(client, &address, 2, 0); - if (peer == NULL) - { + if (peer == NULL) { fprintf(stderr, "No available peers for initiating an ENet connection.\n"); exit(-1); } // the timeout is the third parameter here - if (enet_host_service(client, &event, 5000) > 0 && - event.type == ENET_EVENT_TYPE_CONNECT) - { + if (enet_host_service(client, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { Log("Connected\n"); } - else - { + else { /* Either the 5 seconds are up or a disconnect event was */ /* received. Reset the peer in the event the 5 seconds */ /* had run out without any significant event. */ @@ -190,44 +198,54 @@ static void init(void) } } -#define DeferLoop(start, end) for (int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end)) +#define DeferLoop(start, end) \ + for (int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end)) #define transform_scope DeferLoop(sgp_push_transform(), sgp_pop_transform()) -static void draw_color_rect_centered(V2 center, float size) +static void +draw_color_rect_centered(V2 center, float size) { float halfbox = size / 2.0f; sgp_draw_filled_rect(center.x - halfbox, center.y - halfbox, size, size); } -static void draw_texture_rectangle_centered(V2 center, V2 width_height) +static void +draw_texture_rectangle_centered(V2 center, V2 width_height) { V2 halfsize = V2scale(width_height, 0.5f); - sgp_draw_textured_rect(center.x - halfsize.x, center.y - halfsize.y, width_height.x, width_height.y); + sgp_draw_textured_rect(center.x - halfsize.x, + center.y - halfsize.y, + width_height.x, + width_height.y); } -static void draw_texture_centered(V2 center, float size) +static void +draw_texture_centered(V2 center, float size) { draw_texture_rectangle_centered(center, (V2) { size, size }); } -static void draw_circle(V2 point, float radius) +static void +draw_circle(V2 point, float radius) { #define POINTS 64 sgp_line lines[POINTS]; - for (int i = 0; i < POINTS; i++) - { + for (int i = 0; i < POINTS; i++) { float progress = (float)i / (float)POINTS; float next_progress = (float)(i + 1) / (float)POINTS; - lines[i].a = (V2){ .x = cos(progress * 2.0f * PI) * radius, .y = sin(progress * 2.0f * PI) * radius }; - lines[i].b = (V2){ .x = cos(next_progress * 2.0f * PI) * radius, .y = sin(next_progress * 2.0f * PI) * radius }; + lines[i].a = (V2){ .x = cosf(progress * 2.0f * PI) * radius, + .y = sinf(progress * 2.0f * PI) * radius }; + lines[i].b = (V2){ .x = cosf(next_progress * 2.0f * PI) * radius, + .y = sinf(next_progress * 2.0f * PI) * radius }; lines[i].a = V2add(lines[i].a, point); lines[i].b = V2add(lines[i].b, point); } sgp_draw_lines(lines, POINTS); } -static Entity* myentity() +static Entity* +myentity() { if (myplayer == -1) return NULL; @@ -237,100 +255,107 @@ static Entity* myentity() return to_return; } -static void ui(bool draw, float dt, float width, float height) +static void +ui(bool draw, float dt, float width, float height) { static float cur_opacity = 1.0f; cur_opacity = lerp(cur_opacity, myentity() != NULL ? 1.0f : 0.0f, dt * 5.0f); - if (cur_opacity <= 0.01f) - { + if (cur_opacity <= 0.01f) { return; } - // draw spice bar if (draw) - { + sgp_push_transform(); + + // if(draw) sgp_scale(1.0f, -1.0f); + // draw spice bar + if (draw) { static float spice_taken_away = 0.5f; - if (myentity() != NULL) - { + if (myentity() != NULL) { spice_taken_away = myentity()->spice_taken_away; } sgp_set_color(0.5f, 0.5f, 0.5f, cur_opacity); - float margin = width * 0.1; + float margin = width * 0.1f; float bar_width = width - margin * 2.0f; sgp_draw_filled_rect(margin, 80.0f, bar_width, 30.0f); sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity); - sgp_draw_filled_rect(margin, 80.0f, bar_width * (1.0f - spice_taken_away), 30.0f); + sgp_draw_filled_rect( + margin, 80.0f, bar_width * (1.0f - spice_taken_away), 30.0f); } // draw item toolbar { - int itemframe_width = sg_query_image_info(image_itemframe).width * 2.0f; - int itemframe_height = sg_query_image_info(image_itemframe).height * 2.0f; - int total_width = itemframe_width * boxes_len; - float item_width = itemframe_width * 0.75; - float item_height = itemframe_height * 0.75; + float itemframe_width = (float)sg_query_image_info(image_itemframe).width * 2.0f; + float itemframe_height = (float)sg_query_image_info(image_itemframe).height * 2.0f; + float total_width = itemframe_width * boxes_len; + float item_width = itemframe_width * 0.75f; + float item_height = itemframe_height * 0.75f; float item_offset_x = (itemframe_width - item_width) / 2.0f; float item_offset_y = (itemframe_height - item_height) / 2.0f; - float x = width / 2.0 - total_width / 2.0; - float y = height - itemframe_height * 1.5; - for (int i = 0; i < boxes_len; i++) - { - if (has_point((AABB) { + float x = width / 2.0f - total_width / 2.0f; + float y = height - itemframe_height * 1.5f; + for (int i = 0; i < boxes_len; i++) { + if (has_point( + (AABB) { .x = x, .y = y, .width = itemframe_width, .height = itemframe_height, }, - mouse_pos) && - mouse_pressed) - { + mouse_pos) + && mouse_pressed) { // "handle" mouse pressed mouse_pressed = false; cur_editing_boxtype = i; } - if (draw) + + if (draw) { + sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity); + if (cur_editing_boxtype == i) { + sgp_set_image(0, image_itemframe_selected); + } + else { + sgp_set_image(0, image_itemframe); + } + sgp_draw_textured_rect(x, y, itemframe_width, itemframe_height); + sgp_set_image(0, boxinfo((enum BoxType)i).image); + transform_scope { - sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity); - if (cur_editing_boxtype == i) - { - sgp_set_image(0, image_itemframe_selected); - } - else - { - sgp_set_image(0, image_itemframe); - } - sgp_draw_textured_rect(x, y, itemframe_width, itemframe_height); - sgp_set_image(0, boxinfo((enum BoxType)i).image); - sgp_draw_textured_rect(x + item_offset_x, y + item_offset_y, item_width, item_height); - sgp_reset_image(0); + float item_x = x + item_offset_x; + float item_y = y + item_offset_y; + sgp_scale_at(1.0f, -1.0f, item_x + item_width / 2.0f, item_y + item_height / 2.0f); + //sgp_scale(1.0f, -1.0f); + sgp_draw_textured_rect(item_x, item_y, item_width, item_height); } - - x += itemframe_width; + sgp_reset_image(0); + } + x += itemframe_width; } } + + if (draw) + sgp_pop_transform(); } -static void frame(void) +static void +frame(void) { - int width = sapp_width(), height = sapp_height(); - float ratio = width / (float)height; + float width = (float)sapp_width(), height = (float)sapp_height(); + float ratio = width / height; double time = sapp_frame_count() * sapp_frame_duration(); - float dt = sapp_frame_duration(); + float dt = (float)sapp_frame_duration(); // pressed input management { - for (int i = 0; i < SAPP_KEYCODE_MENU; i++) - { - if (keypressed[i].frame < sapp_frame_count()) - { + for (int i = 0; i < SAPP_KEYCODE_MENU; i++) { + if (keypressed[i].frame < sapp_frame_count()) { keypressed[i].pressed = false; } } - if (mouse_pressed_frame < sapp_frame_count()) - { + if (mouse_pressed_frame < sapp_frame_count()) { mouse_pressed = false; } } @@ -338,25 +363,24 @@ static void frame(void) // networking { ENetEvent event; - while (true) - { + while (true) { int enet_status = enet_host_service(client, &event, 0); - if (enet_status > 0) - { - switch (event.type) - { - case ENET_EVENT_TYPE_CONNECT: - { + if (enet_status > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_CONNECT: { Log("New client from host %x\n", event.peer->address.host); break; } - case ENET_EVENT_TYPE_RECEIVE: - { - // @Robust @BeforeShip use some kind of serialization strategy that checks for out of bounds - // and other validation instead of just casting to a struct - // "Alignment of structure members can be different even among different compilers on the same platform, let alone different platforms." - // ^^ need serialization strategy that accounts for this if multiple platforms is happening https://stackoverflow.com/questions/28455163/how-can-i-portably-send-a-c-struct-through-a-network-socket + case ENET_EVENT_TYPE_RECEIVE: { + // @Robust @BeforeShip use some kind of serialization strategy that + // checks for out of bounds and other validation instead of just + // casting to a struct "Alignment of structure members can be + // different even among different compilers on the same platform, + // let alone different platforms." + // ^^ need serialization strategy that accounts for this if multiple + // platforms is happening + // https://stackoverflow.com/questions/28455163/how-can-i-portably-send-a-c-struct-through-a-network-socket ServerToClient msg = (ServerToClient){ .cur_gs = &gs, }; @@ -367,21 +391,17 @@ static void frame(void) break; } - case ENET_EVENT_TYPE_DISCONNECT: - { + case ENET_EVENT_TYPE_DISCONNECT: { fprintf(stderr, "Disconnected from server\n"); exit(-1); break; } - } } - else if (enet_status == 0) - { + else if (enet_status == 0) { break; } - else if (enet_status < 0) - { + else if (enet_status < 0) { fprintf(stderr, "Error receiving enet events: %d\n", enet_status); break; } @@ -392,14 +412,15 @@ static void frame(void) ui(false, dt, width, height); // handle events V2 build_target_pos = { 0 }; float build_target_rotation = 0.0f; - static V2 camera_pos = { 0 }; // keeps camera at same position after player death - V2 world_mouse_pos = mouse_pos; - struct BuildPreviewInfo - { + static V2 camera_pos = { + 0 + }; // keeps camera at same position after player death + V2 world_mouse_pos = mouse_pos; // processed later in scope + struct BuildPreviewInfo { V2 grid_pos; float grid_rotation; - V2 pos; } build_preview = { 0 }; + V2 hand_pos = { 0 }; bool hand_at_arms_length = false; { // interpolate zoom @@ -407,9 +428,7 @@ static void frame(void) // calculate world position and camera { - if (myentity() != NULL) - { - + if (myentity() != NULL) { camera_pos = entity_pos(myentity()); } world_mouse_pos = V2sub(world_mouse_pos, (V2) { .x = width / 2.0f, .y = height / 2.0f }); @@ -420,106 +439,108 @@ static void frame(void) // calculate build preview stuff EntityID grid_to_build_on = (EntityID){ 0 }; - if (myentity() != NULL) - { - V2 hand_pos = V2sub(world_mouse_pos, entity_pos(myentity())); + if (myentity() != NULL) { + hand_pos = V2sub(world_mouse_pos, entity_pos(myentity())); float hand_len = V2length(hand_pos); - if (hand_len > MAX_HAND_REACH) - { + if (hand_len > MAX_HAND_REACH) { hand_at_arms_length = true; hand_len = MAX_HAND_REACH; } - else - { + else { hand_at_arms_length = false; } hand_pos = V2scale(V2normalize(hand_pos), hand_len); hand_pos = V2add(hand_pos, entity_pos(myentity())); Entity* placing_grid = closest_to_point_in_radius(&gs, hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP); - if (placing_grid == NULL) - { + if (placing_grid == NULL) { build_preview = (struct BuildPreviewInfo){ .grid_pos = hand_pos, .grid_rotation = 0.0f, - .pos = hand_pos, }; } - else - { + else { grid_to_build_on = get_id(&gs, placing_grid); - V2 pos = grid_snapped_box_pos(placing_grid, hand_pos); - build_preview = (struct BuildPreviewInfo){ - .grid_pos = entity_pos(placing_grid), + hand_pos = grid_snapped_box_pos(placing_grid, hand_pos); + build_preview = (struct BuildPreviewInfo){ .grid_pos = entity_pos(placing_grid), .grid_rotation = entity_rotation(placing_grid), - .pos = pos }; + }; } } // Create and send input packet { - // @Robust accumulate total time and send input at rate like 20 hz, not every frame + // @Robust accumulate total time and send input at rate like 20 hz, not + // every frame + static size_t last_frame_id = 0; struct InputFrame cur_input_frame = { 0 }; + cur_input_frame.id = last_frame_id; 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], }; + if (V2length(input) > 0.0) + input = V2normalize(input); cur_input_frame.movement = input; - cur_input_frame.inhabit = keypressed[SAPP_KEYCODE_G].pressed; - if (mouse_pressed && cur_editing_boxtype != -1) - { + cur_input_frame.seat_action = keypressed[SAPP_KEYCODE_G].pressed; + cur_input_frame.hand_pos = hand_pos; + if (mouse_pressed && cur_editing_boxtype != -1) { cur_input_frame.dobuild = mouse_pressed; cur_input_frame.build_type = cur_editing_boxtype; cur_input_frame.build_rotation = cur_editing_rotation; cur_input_frame.grid_to_build_on = grid_to_build_on; - Entity* grid = get_entity(&gs, grid_to_build_on); - if (grid != NULL) - { - cur_input_frame.build = grid_world_to_local(grid, build_preview.pos); - } - else - { - 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_to_build_on.generation != latest.grid_to_build_on.generation || - cur_input_frame.grid_to_build_on.index != latest.grid_to_build_on.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]; + // @Robust split this into separate lines and be very careful about testing for inequality + + bool input_differs = false; + input_differs = input_differs || !V2equal(cur_input_frame.movement, latest.movement, 0.01f); + + input_differs = input_differs || cur_input_frame.seat_action != latest.seat_action; + input_differs = input_differs || !entityids_same(cur_input_frame.seat_to_inhabit, latest.seat_to_inhabit); + input_differs = input_differs || !V2equal(cur_input_frame.hand_pos, latest.hand_pos, 0.01f); + + input_differs = input_differs || cur_input_frame.dobuild != latest.dobuild; + input_differs = input_differs || cur_input_frame.build_type != latest.build_type; + input_differs = input_differs || cur_input_frame.build_rotation != latest.build_rotation; + input_differs = input_differs || !entityids_same(cur_input_frame.grid_to_build_on, latest.grid_to_build_on); + + if (input_differs) { + struct InputFrame last_frame = client_to_server.inputs[0]; + for (int i = 0; i < INPUT_BUFFER - 1; i++) { + struct InputFrame last_last_frame = last_frame; + last_frame = client_to_server.inputs[i + 1]; + client_to_server.inputs[i + 1] = last_last_frame; } cur_input_frame.tick = tick(&gs); client_to_server.inputs[0] = cur_input_frame; + last_frame_id += 1; } + dbg_rect(client_to_server.inputs[0].hand_pos); 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); + 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 - // no need to store copies of game state, just player input frame to frame. Then know how many frames ago the server game state arrived, it's that easy! - // process(&gs, (float)sapp_frame_duration()); + // @BeforeShip client side prediction and rollback to previous server + // authoritative state, then replay inputs no need to store copies of game + // state, just player input frame to frame. Then know how many frames ago + // the server game state arrived, it's that easy! process(&gs, + // (float)sapp_frame_duration()); } // drawing { - sgp_begin(width, height); - sgp_viewport(0, 0, width, height); + sgp_begin((int)width, (int)height); + sgp_viewport(0, 0, (int)width, (int)height); sgp_project(0.0f, width, 0.0f, height); sgp_set_blend_mode(SGP_BLENDMODE_BLEND); @@ -529,7 +550,8 @@ static void frame(void) // sokol drawing library draw in world space // world space coordinates are +Y up, -Y down. Like normal cartesian coords - transform_scope{ + transform_scope + { sgp_translate(width / 2, height / 2); sgp_scale_at(zoom, -zoom, 0.0f, 0.0f); @@ -537,10 +559,9 @@ static void frame(void) sgp_translate(-camera_pos.x, -camera_pos.y); // hand reached limit circle - if (myentity() != NULL) - { + if (myentity() != NULL) { static float hand_reach_alpha = 1.0f; - hand_reach_alpha = lerp(hand_reach_alpha, hand_at_arms_length ? 1.0f : 0.0f, dt * 5.0); + hand_reach_alpha = lerp(hand_reach_alpha, hand_at_arms_length ? 1.0f : 0.0f, dt * 5.0f); sgp_set_color(1.0f, 1.0f, 1.0f, hand_reach_alpha); draw_circle(entity_pos(myentity()), MAX_HAND_REACH); } @@ -548,10 +569,8 @@ static void frame(void) // stars sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); const int num = 30; - for (int x = -num; x < num; x++) - { - for (int y = -num; y < num; y++) - { + for (int x = -num; x < num; x++) { + for (int y = -num; y < num; y++) { sgp_draw_point((float)x * 0.1f, (float)y * 0.1f); } } @@ -559,30 +578,29 @@ static void frame(void) float halfbox = BOX_SIZE / 2.0f; // mouse - if (mouse_frozen) - { + if (mouse_frozen) { sgp_set_color(1.0f, 0.0f, 0.0f, 0.5f); sgp_draw_filled_rect(world_mouse_pos.x, world_mouse_pos.y, 0.1f, 0.1f); } // building preview - if (cur_editing_boxtype != -1) - { - sgp_set_color(0.5f, 0.5f, 0.5f, (sin(time * 9.0f) + 1.0) / 3.0f + 0.2); + if (cur_editing_boxtype != -1) { + sgp_set_color(0.5f, 0.5f, 0.5f, (sinf((float)time * 9.0f) + 1.0f) / 3.0f + 0.2f); transform_scope { - sgp_set_image(0, boxinfo(cur_editing_boxtype).image); - sgp_rotate_at(build_preview.grid_rotation + rotangle(cur_editing_rotation), build_preview.pos.x, build_preview.pos.y); - draw_texture_centered(build_preview.pos, BOX_SIZE); - // drawbox(build_preview.pos, build_preview.grid_rotation, 0.0f, cur_editing_boxtype, cur_editing_rotation); + sgp_rotate_at(build_preview.grid_rotation + rotangle(cur_editing_rotation), + hand_pos.x, + hand_pos.y); + draw_texture_centered(hand_pos, BOX_SIZE); + // drawbox(hand_pos, build_preview.grid_rotation, 0.0f, + // cur_editing_boxtype, cur_editing_rotation); sgp_reset_image(0); } } - for (int i = 0; i < gs.cur_next_entity; i++) - { + for (size_t i = 0; i < gs.cur_next_entity; i++) { Entity* e = &gs.entities[i]; if (!e->exists) continue; @@ -594,45 +612,51 @@ static void frame(void) sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); // debug draw force vectors for thrusters #if 0 - { - if (b->type == BoxThruster) - { - dbg_rect(box_pos(b)); - dbg_line(box_pos(b), V2add(box_pos(b), V2scale(thruster_force(b), -1.0f))); - } - } + { + if (b->type == BoxThruster) + { + dbg_rect(box_pos(b)); + dbg_line(box_pos(b), V2add(box_pos(b), V2scale(thruster_force(b), -1.0f))); + } + } #endif - if (b->box_type == BoxBattery) - { + if (b->box_type == BoxBattery) { float cur_alpha = sgp_get_color().a; Color from = WHITE; Color to = colhex(255, 0, 0); Color result = Collerp(from, to, b->energy_used); sgp_set_color(result.r, result.g, result.b, cur_alpha); } - transform_scope { - - sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation), box_pos(b).x, box_pos(b).y); + transform_scope + { + sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation), + box_pos(b).x, + box_pos(b).y); - if (b->box_type == BoxThruster) - { + if (b->box_type == BoxThruster) { transform_scope { sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); sgp_set_image(0, image_thrusterburn); - // float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0, 0.07, b->thrust); - // printf("%f\n", b->thrust); - float scaling = 0.95 + lerp(0.0, 0.3, b->thrust); + // float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0, + // 0.07, b->thrust); printf("%f\n", b->thrust); + float scaling = 0.95f + lerp(0.0f, 0.3f, b->thrust); // float scaling = 1.1; // sgp_translate(-(scaling*BOX_SIZE - BOX_SIZE), 0.0); // sgp_scale(scaling, 1.0); - sgp_scale_at(scaling, 1.0, box_pos(b).x, box_pos(b).y); + sgp_scale_at(scaling, 1.0f, box_pos(b).x, box_pos(b).y); draw_texture_centered(box_pos(b), BOX_SIZE); sgp_reset_image(0); } } + sg_image img = boxinfo(b->box_type).image; + if (b->box_type == BoxCockpit) + { + if (get_entity(&gs, b->piloted_by) != NULL) + img = image_cockpit_used; + } - sgp_set_image(0, boxinfo(b->box_type).image); + sgp_set_image(0, img); draw_texture_centered(box_pos(b), BOX_SIZE); sgp_reset_image(0); @@ -645,61 +669,33 @@ static void frame(void) V2 to = V2add(grid_com(g), vel); sgp_draw_line(grid_com(g).x, grid_com(g).y, to.x, to.y); } - if (e->is_player) - { - transform_scope - { - sgp_rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y); - sgp_set_color(1.0f, 0.5f, 0.5f, 1.0f); - sgp_set_image(0, image_player); - draw_texture_rectangle_centered(entity_pos(e), PLAYER_SIZE); - sgp_reset_image(0); - } - } - } - } - - // player - for (int i = 0; i < MAX_PLAYERS; i++) - { - struct Player* player = &gs.players[i]; - if (!player->connected) - continue; - Entity* p = get_entity(&gs, player->entity); - if (p == NULL) - continue; - static float opacities[MAX_PLAYERS] = { 1.0f }; - static V2 positions[MAX_PLAYERS] = { 0 }; - opacities[i] = lerp(opacities[i], p != NULL ? 1.0f : 0.1f, dt * 7.0f); - Color col_to_draw = Collerp(WHITE, GOLD, p->goldness); - col_to_draw.a = opacities[i]; - - if (p != NULL) - { - positions[i] = entity_pos(p); - } - - set_color(col_to_draw); - transform_scope - { - float psize = 0.1f; - sgp_draw_filled_rect(positions[i].x - psize / 2.0f, positions[i].y - psize / 2.0f, psize, psize); + if (e->is_player && get_entity(&gs, e->currently_piloting_seat) == NULL) { + + transform_scope + { + 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); + draw_texture_rectangle_centered(entity_pos(e), PLAYER_SIZE); + sgp_reset_image(0); + } + } } - } + // gold target + set_color(GOLD); + sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f); - // gold target - set_color(GOLD); - sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f); + sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); + dbg_drawall(); + } // world space transform end - sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); - dbg_drawall(); } // UI drawn in screen space ui(true, dt, width, height); sg_pass_action pass_action = { 0 }; - sg_begin_default_pass(&pass_action, width, height); + sg_begin_default_pass(&pass_action, (int)width, (int)height); sgp_flush(); sgp_end(); sg_end_pass(); @@ -717,38 +713,31 @@ void cleanup(void) void event(const sapp_event* e) { - switch (e->type) - { + switch (e->type) { case SAPP_EVENTTYPE_KEY_DOWN: - if (e->key_code == SAPP_KEYCODE_T) - { + if (e->key_code == SAPP_KEYCODE_T) { mouse_frozen = !mouse_frozen; } - if (e->key_code == SAPP_KEYCODE_R) - { + if (e->key_code == SAPP_KEYCODE_R) { cur_editing_rotation += 1; cur_editing_rotation %= RotationLast; } int key_num = e->key_code - SAPP_KEYCODE_0; int target_box = key_num - 1; - if (target_box < BoxLast) - { + if (target_box < BoxLast) { cur_editing_boxtype = target_box; } - if (!mouse_frozen) - { + if (!mouse_frozen) { keydown[e->key_code] = true; - if (keypressed[e->key_code].frame == 0) - { + 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: - if (!mouse_frozen) - { + if (!mouse_frozen) { keydown[e->key_code] = false; keypressed[e->key_code].pressed = false; @@ -760,34 +749,28 @@ void event(const sapp_event* e) zoom_target = clamp(zoom_target, 0.5f, 900.0f); break; case SAPP_EVENTTYPE_MOUSE_DOWN: - if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) - { + if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) { mouse_pressed = true; mouse_pressed_frame = e->frame_count; } - if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) - { + if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) { right_mouse_down = true; } break; case SAPP_EVENTTYPE_MOUSE_UP: - if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) - { + if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) { mouse_pressed = false; mouse_pressed_frame = 0; } - if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) - { + if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) { right_mouse_down = false; } break; case SAPP_EVENTTYPE_MOUSE_MOVE: - if (!mouse_frozen) - { + if (!mouse_frozen) { mouse_pos = (V2){ .x = e->mouse_x, .y = e->mouse_y }; } - if (right_mouse_down) - { + if (right_mouse_down) { funval += e->mouse_dx; Log("Funval %f\n", funval); } @@ -795,10 +778,10 @@ void event(const sapp_event* e) } } -sapp_desc sokol_main(int argc, char* argv[]) +sapp_desc +sokol_main(int argc, char* argv[]) { - if (argc > 1) - { + if (argc > 1) { _beginthread(server, 0, NULL); } (void)argv; diff --git a/server.c b/server.c index 172a14d..b0f4da1 100644 --- a/server.c +++ b/server.c @@ -60,7 +60,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 }; + size_t player_to_latest_id_processed[MAX_PLAYERS] = { 0 }; while (true) { // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this @@ -123,36 +123,36 @@ 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]; + size_t latest_id = player_to_latest_id_processed[player_slot]; - if (received.inputs[0].tick > latest_tick) + if (received.inputs[0].id > latest_id) { for (int i = INPUT_BUFFER - 1; i >= 0; i--) { if (received.inputs[i].tick == 0) // empty input continue; - if (received.inputs[i].tick <= latest_tick) + if (received.inputs[i].id <= latest_id) continue; // don't reprocess inputs already processed struct InputFrame cur_input = received.inputs[i]; gs.players[player_slot].input.movement = cur_input.movement; + gs.players[player_slot].input.hand_pos = cur_input.hand_pos; // for these "event" inputs, only modify the current input 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) + if (cur_input.seat_action) { - gs.players[player_slot].input.inhabit = cur_input.inhabit; + gs.players[player_slot].input.seat_action = cur_input.seat_action; } if (cur_input.dobuild) { gs.players[player_slot].input.grid_to_build_on = cur_input.grid_to_build_on; - gs.players[player_slot].input.build = cur_input.build; gs.players[player_slot].input.dobuild = cur_input.dobuild; gs.players[player_slot].input.build_type = cur_input.build_type; gs.players[player_slot].input.build_rotation = cur_input.build_rotation; } } - player_to_latest_tick_processed[player_slot] = received.inputs[0].tick; + player_to_latest_id_processed[player_slot] = received.inputs[0].id; } } @@ -165,7 +165,7 @@ void server(void* data) case ENET_EVENT_TYPE_DISCONNECT: { - int player_index = (int64_t)event.peer->data; + int player_index = (int)(int64_t)event.peer->data; Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index); gs.players[player_index].connected = false; // box_destroy(&gs.players[player_index].box); @@ -203,7 +203,7 @@ void server(void* data) to_send.cur_gs = &gs; to_send.your_player = (int)(int64_t)server->peers[i].data; - int len = 0; + size_t len = 0; into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE); ENetPacket* gamestate_packet = enet_packet_create((void*)bytes_buffer, len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); diff --git a/types.h b/types.h index 01b8b5f..cdce4e8 100644 --- a/types.h +++ b/types.h @@ -17,7 +17,7 @@ #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 +#define INPUT_BUFFER 6 // must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1" #include "ipsettings.h" // don't leak IP! @@ -36,7 +36,7 @@ void sgp_set_color(float, float, float, float); // somehow automatically or easily cast to floats typedef struct sgp_vec2 { - float x, y; + float x, y; } sgp_vec2; typedef sgp_vec2 sgp_point; @@ -68,102 +68,117 @@ typedef sgp_point P2; enum BoxType { - BoxHullpiece, - BoxThruster, - BoxBattery, - BoxCockpit, - BoxLast, + BoxHullpiece, + BoxThruster, + BoxBattery, + BoxCockpit, + BoxLast, }; enum CompassRotation { - Right, - Down, - Left, - Up, - RotationLast, + Right, + Down, + Left, + Up, + RotationLast, }; // when generation is 0, invalid ID typedef struct { - unsigned int generation; // if 0 then EntityID points to nothing, generation >= 1 - unsigned int index; // index into the entity arena + unsigned int generation; // if 0 then EntityID points to nothing, generation >= 1 + unsigned int index; // index into the entity arena } EntityID; +static bool entityids_same(EntityID a, EntityID b) +{ + return (a.generation == b.generation) && (a.index == b.index); +} + +// when updated, must update serialization, AND comparison +// function in main.c struct InputFrame { - uint64_t tick; - V2 movement; - bool inhabit; + uint64_t tick; + size_t id; // each input has unique, incrementing, I.D, so server doesn't double process inputs. Inputs to server should be ordered from 0-max like biggest id-smallest. This is done so if packet loss server still processes input + V2 movement; - // if grid_index != -1, this is in local coordinates to the grid - V2 build; - bool dobuild; - enum BoxType build_type; - enum CompassRotation build_rotation; - EntityID grid_to_build_on; + bool seat_action; + EntityID seat_to_inhabit; + V2 hand_pos; // world coords, world star! + + bool dobuild; + enum BoxType build_type; + enum CompassRotation build_rotation; + EntityID grid_to_build_on; }; typedef struct Entity { - bool exists; - EntityID next_free_entity; - unsigned int generation; - - 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 - - // for serializing the shape - EntityID shape_parent_entity; // can't be zero if shape is nonzero - V2 shape_size; - - // player - bool is_player; - EntityID currently_piloting_seat; - float spice_taken_away; // at 1.0, out of spice - float goldness; // how much the player is a winner - - // grids - bool is_grid; - float total_energy_capacity; - EntityID boxes; - - // boxes - bool is_box; - enum BoxType box_type; - EntityID next_box; - EntityID prev_box; // doubly linked so can remove in middle of chain - enum CompassRotation compass_rotation; - float thrust; // must be between 0 and 1 - float energy_used; // battery + bool exists; + EntityID next_free_entity; + unsigned int generation; + + 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 + + + // for serializing the shape + // @Robust remove shape_parent_entity from this struct, use the shape's body to figure out + // what the shape's parent entity is + EntityID shape_parent_entity; // can't be zero if shape is nonzero + V2 shape_size; + + // player + bool is_player; + EntityID currently_piloting_seat; + float spice_taken_away; // at 1.0, out of spice + float goldness; // how much the player is a winner + + // grids + bool is_grid; + float total_energy_capacity; + EntityID boxes; + + // boxes + bool is_box; + enum BoxType box_type; + EntityID next_box; + EntityID prev_box; // doubly linked so can remove in middle of chain + enum CompassRotation compass_rotation; + float wanted_thrust; // the thrust command applied to the thruster + float thrust; // the actual thrust it can provide based on energy sources in the grid + float energy_used; // battery + EntityID piloted_by; } Entity; +typedef struct Player +{ + bool connected; + EntityID entity; + struct InputFrame input; +} Player; // gotta update the serialization functions when this changes typedef struct GameState { - cpSpace *space; + cpSpace* space; - double time; + double time; - V2 goldpos; + V2 goldpos; - struct Player - { - bool connected; - EntityID entity; - struct InputFrame input; - } players[MAX_PLAYERS]; + Player players[MAX_PLAYERS]; - // Entity arena - // ent:ity pointers can't move around because of how the physics engine handles user data. - // if you really need this, potentially refactor to store entity IDs instead of pointers - // in the shapes and bodies of chipmunk. Would require editing the library I think - Entity *entities; - unsigned int max_entities; // maximum number of entities possible in the entities list - unsigned int cur_next_entity; // next entity to pass on request of a new entity if the free list is empty - EntityID free_list; + // Entity arena + // ent:ity pointers can't move around because of how the physics engine handles user data. + // if you really need this, potentially refactor to store entity IDs instead of pointers + // in the shapes and bodies of chipmunk. Would require editing the library I think + Entity* entities; + unsigned int max_entities; // maximum number of entities possible in the entities list + unsigned int cur_next_entity; // next entity to pass on request of a new entity if the free list is empty + EntityID free_list; } GameState; #define PI 3.14159f @@ -171,80 +186,80 @@ typedef struct GameState // returns in radians static float rotangle(enum CompassRotation rot) { - switch (rot) - { - case Right: - return 0.0f; - break; - case Down: - return -PI / 2.0f; - break; - case Left: - return -PI; - break; - case Up: - return -3.0f * PI / 2.0f; - break; - default: - Log("Unknown rotation %d\n", rot); - return -0.0f; - break; - } + switch (rot) + { + case Right: + return 0.0f; + break; + case Down: + return -PI / 2.0f; + break; + case Left: + return -PI; + break; + case Up: + return -3.0f * PI / 2.0f; + break; + default: + Log("Unknown rotation %d\n", rot); + return -0.0f; + break; + } } typedef struct ServerToClient { - struct GameState *cur_gs; - int your_player; + struct GameState* cur_gs; + int your_player; } ServerToClient; struct ClientToServer { - struct InputFrame inputs[INPUT_BUFFER]; + struct InputFrame inputs[INPUT_BUFFER]; }; // server -void server(void *data); // data parameter required from thread api... +void server(void* data); // data parameter required from thread api... // gamestate -void initialize(struct GameState *gs, void *entity_arena, int entity_arena_size); -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 *gs, char *out_bytes, size_t * out_len, size_t max_len); -void from_bytes(struct ServerToClient *gs, char *bytes, size_t max_len); +void initialize(struct GameState* gs, void* entity_arena, size_t entity_arena_size); +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* gs, char* out_bytes, size_t* out_len, size_t max_len); +void from_bytes(struct ServerToClient* gs, char* bytes, size_t max_len); // entities -Entity *get_entity(struct GameState *gs, EntityID id); -Entity *new_entity(struct GameState *gs); -EntityID get_id(struct GameState *gs, Entity *e); -V2 entity_pos(Entity *e); -void entity_set_pos(Entity *e, V2 pos); -float entity_rotation(Entity *e); +Entity* get_entity(struct GameState* gs, EntityID id); +Entity* new_entity(struct GameState* gs); +EntityID get_id(struct GameState* gs, Entity* e); +V2 entity_pos(Entity* e); +void entity_set_pos(Entity* e, V2 pos); +float entity_rotation(Entity* e); #define BOX_CHAIN_ITER(gs, cur, starting_box) for (Entity *cur = get_entity(gs, starting_box); cur != NULL; cur = get_entity(gs, cur->next_box)) #define BOXES_ITER(gs, cur, grid_entity_ptr) BOX_CHAIN_ITER(gs, cur, (grid_entity_ptr)->boxes) // player -void player_destroy(struct Player *p); -void player_new(struct Player *p); +void player_destroy(struct Player* p); +void player_new(struct Player* p); // grid -void grid_create(struct GameState *gs, Entity *e); -void box_create(struct GameState *gs, Entity *new_box, Entity *grid, V2 pos); -V2 grid_com(Entity *grid); -V2 grid_vel(Entity *grid); -V2 grid_local_to_world(Entity *grid, V2 local); -V2 grid_world_to_local(Entity *grid, V2 world); -V2 grid_snapped_box_pos(Entity *grid, V2 world); // returns the snapped pos in world coords -float entity_angular_velocity(Entity *grid); -V2 entity_shape_pos(Entity *box); -V2 box_pos(Entity *box); -float box_rotation(Entity *box); +void grid_create(struct GameState* gs, Entity* e); +void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos); +V2 grid_com(Entity* grid); +V2 grid_vel(Entity* grid); +V2 grid_local_to_world(Entity* grid, V2 local); +V2 grid_world_to_local(Entity* grid, V2 world); +V2 grid_snapped_box_pos(Entity* grid, V2 world); // returns the snapped pos in world coords +float entity_angular_velocity(Entity* grid); +V2 entity_shape_pos(Entity* box); +V2 box_pos(Entity* box); // returns in world coords +float box_rotation(Entity* box); // thruster -V2 thruster_direction(Entity *box); -V2 thruster_force(Entity *box); +V2 thruster_direction(Entity* box); +V2 thruster_force(Entity* box); // debug draw void dbg_drawall(); @@ -260,154 +275,154 @@ void dbg_rect(V2 center); typedef struct AABB { - float x, y, width, height; + float x, y, width, height; } AABB; static bool has_point(AABB aabb, V2 point) { - return point.x > aabb.x && point.x < aabb.x + aabb.width && point.y > aabb.y && point.y < aabb.y + aabb.height; + return point.x > aabb.x && point.x < aabb.x + aabb.width && point.y > aabb.y && point.y < aabb.y + aabb.height; } static V2 V2add(V2 a, V2 b) { - return (V2){ - .x = a.x + b.x, - .y = a.y + b.y, - }; + return (V2) { + .x = a.x + b.x, + .y = a.y + b.y, + }; } static V2 V2scale(V2 a, float f) { - return (V2){ - .x = a.x * f, - .y = a.y * f, - }; + return (V2) { + .x = a.x * f, + .y = a.y * f, + }; } static float V2length(V2 v) { - return sqrtf(v.x * v.x + v.y * v.y); + return sqrtf(v.x * v.x + v.y * v.y); } static V2 V2normalize(V2 v) { - return V2scale(v, 1.0f / V2length(v)); + return V2scale(v, 1.0f / V2length(v)); } static float V2dot(V2 a, V2 b) { - return a.x * b.x + a.y * b.y; + return a.x * b.x + a.y * b.y; } static float V2projectvalue(V2 vec, V2 onto) { - float length_onto = V2length(onto); - return V2dot(vec, onto) / (length_onto * length_onto); + float length_onto = V2length(onto); + return V2dot(vec, onto) / (length_onto * length_onto); } static V2 V2project(V2 vec, V2 onto) { - return V2scale(onto, V2projectvalue(vec, onto)); + return V2scale(onto, V2projectvalue(vec, onto)); } static V2 V2rotate(V2 vec, float theta) { - return (V2){ - .x = vec.x * cosf(theta) - vec.y * sinf(theta), - .y = vec.x * sinf(theta) + vec.y * cosf(theta), - }; + return (V2) { + .x = vec.x * cosf(theta) - vec.y * sinf(theta), + .y = vec.x * sinf(theta) + vec.y * cosf(theta), + }; } // also known as atan2 static float V2angle(V2 vec) { - return atan2f(vec.y, vec.x); + return atan2f(vec.y, vec.x); } static V2 V2sub(V2 a, V2 b) { - return (V2){ - .x = a.x - b.x, - .y = a.y - b.y, - }; + return (V2) { + .x = a.x - b.x, + .y = a.y - b.y, + }; } -static bool V2cmp(V2 a, V2 b, float eps) +static bool V2equal(V2 a, V2 b, float eps) { - return V2length(V2sub(a, b)) < eps; + return V2length(V2sub(a, b)) < eps; } static inline float clamp01(float f) { - return fmaxf(0.0f, fminf(f, 1.0f)); + return fmaxf(0.0f, fminf(f, 1.0f)); } static inline float clamp(float f, float minimum, float maximum) { - if (f < minimum) - return minimum; - if (f > maximum) - return maximum; - return f; + if (f < minimum) + return minimum; + if (f > maximum) + return maximum; + return f; } static float fract(float f) { - return f - floorf(f); + return f - floorf(f); } static float lerp(float a, float b, float f) { - return a * (1.0f - f) + (b * f); + return a * (1.0f - f) + (b * f); } static V2 V2lerp(V2 a, V2 b, float factor) { - V2 to_return = {0}; - to_return.x = lerp(a.x, b.x, factor); - to_return.y = lerp(a.y, b.y, factor); + V2 to_return = { 0 }; + to_return.x = lerp(a.x, b.x, factor); + to_return.y = lerp(a.y, b.y, factor); - return to_return; + return to_return; } // for random generation static float hash11(float p) { - p = fract(p * .1031f); - p *= p + 33.33f; - p *= p + p; - return fract(p); + p = fract(p * .1031f); + p *= p + 33.33f; + p *= p + p; + return fract(p); } typedef struct Color { - float r, g, b, a; + float r, g, b, a; } Color; static Color colhex(int r, int g, int b) { - return (Color){ - .r = (float)r / 255.0f, - .g = (float)g / 255.0f, - .b = (float)b / 255.0f, - .a = 1.0f, - }; + return (Color) { + .r = (float)r / 255.0f, + .g = (float)g / 255.0f, + .b = (float)b / 255.0f, + .a = 1.0f, + }; } static Color Collerp(Color a, Color b, float factor) { - Color to_return = {0}; - to_return.r = lerp(a.r, b.r, factor); - to_return.g = lerp(a.g, b.g, factor); - to_return.b = lerp(a.b, b.b, factor); - to_return.a = lerp(a.a, b.a, factor); + Color to_return = { 0 }; + to_return.r = lerp(a.r, b.r, factor); + to_return.g = lerp(a.g, b.g, factor); + to_return.b = lerp(a.b, b.b, factor); + to_return.a = lerp(a.a, b.a, factor); - return to_return; + return to_return; } static void set_color(Color c) { - sgp_set_color(c.r, c.g, c.b, c.a); + sgp_set_color(c.r, c.g, c.b, c.a); } #define WHITE \