diff --git a/buildsettings.h b/buildsettings.h index d54fd90..875321d 100644 --- a/buildsettings.h +++ b/buildsettings.h @@ -18,15 +18,15 @@ // Intensive profiling means profiling a lot of little tiny stuff. Not always enabled because tanks performance // #define INTENSIVE_PROFILING // #define DEBUG_RENDERING -// #define DEBUG_WORLD -// #define UNLOCK_ALL +#define DEBUG_WORLD +#define UNLOCK_ALL #define TIME_BETWEEN_WORLD_SAVE 1000000.0f // #define TIME_BETWEEN_WORLD_SAVE 1.0f -// #define INFINITE_RESOURCES +#define INFINITE_RESOURCES #define DEBUG_TOOLS #define CHIPMUNK_INTEGRITY_CHECK // #define FAT_THRUSTERS -// #define NO_GRAVITY +#define NO_GRAVITY // #define NO_SUNS #else diff --git a/flight.rdbg b/flight.rdbg index b6e53d5..b273924 100644 Binary files a/flight.rdbg and b/flight.rdbg differ diff --git a/gamestate.c b/gamestate.c index b2f0a53..350e462 100644 --- a/gamestate.c +++ b/gamestate.c @@ -407,70 +407,64 @@ LauncherTarget missile_launcher_target(GameState *gs, Entity *launcher) return (LauncherTarget){.target_found = target_found, .facing_angle = to_face}; } -void on_entity_child_shape(cpBody *body, cpShape *shape, void *data); // declared here bc entity_destroy circular dependency - -// gs is for iterating over all child shapes and destroying those, too -static void destroy_body(GameState *gs, cpBody **body) +void destroy_child_shape(cpBody *body, cpShape *shape, void *data) { - if (*body != NULL) + GameState *gs = (GameState *)data; + if (cp_shape_entity(shape) == NULL) + { + // support the case where no parent entity *SPECIFICALLY* for grid_correct_for_holes, + // where the entities that are part of the old grid are reused so entityids are preserved + cpSpaceRemoveShape(gs->space, shape); + cpShapeFree(shape); + } + else { - cpBodyEachShape(*body, on_entity_child_shape, (void *)gs); - cpSpaceRemoveBody(gs->space, *body); - cpBodyFree(*body); - *body = NULL; + entity_memory_free(gs, cp_shape_entity(shape)); } - *body = NULL; } -// will destroy all shapes which are attached to the body in the entity -void entity_destroy(GameState *gs, Entity *e) +// Destroys the entity and puts its slot back into the free list. Doesn't obey game rules +// like making sure grids don't have holes in them, for that you want entity_destroy. +// *Does* free all owned memory/entities though, e.g grids free the boxes they own. +void entity_memory_free(GameState *gs, Entity *e) { flight_assert(e->exists); if (e->is_grid) { BOXES_ITER(gs, cur, e) - entity_destroy(gs, cur); + { + entity_memory_free(gs, cur); + } } if (e->is_box) { box_remove_from_boxes(gs, e); } - if (e->shape != NULL) { cpSpaceRemoveShape(gs->space, e->shape); cpShapeFree(e->shape); e->shape = NULL; } - destroy_body(gs, &e->body); + if (e->body != NULL) + { + cpBodyEachShape(e->body, destroy_child_shape, (void *)gs); + cpSpaceRemoveBody(gs->space, e->body); + cpBodyFree(e->body); + e->body = NULL; + } Entity *front_of_free_list = get_entity(gs, gs->free_list); if (front_of_free_list != NULL) flight_assert(!front_of_free_list->exists); - int gen = e->generation; + unsigned int gen = e->generation; *e = (Entity){0}; e->generation = gen; e->next_free_entity = gs->free_list; gs->free_list = get_id(gs, e); } -void on_entity_child_shape(cpBody *body, cpShape *shape, void *data) -{ - GameState *gs = (GameState *)data; - if (cp_shape_entity(shape) == NULL) - { - // support the case where no parent entity *SPECIFICALLY* for grid_correct_for_holes, - // where the entities that are part of the old grid are reused so entityids are preserved - cpSpaceRemoveShape(gs->space, shape); - cpShapeFree(shape); - } - else - { - entity_destroy(gs, cp_shape_entity(shape)); - } -} - Entity *new_entity(GameState *gs) { Entity *to_return = NULL; @@ -828,7 +822,7 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid) int num_boxes = grid_num_boxes(gs, grid); if (num_boxes == 0) { - entity_destroy(gs, grid); + entity_memory_free(gs, grid); return; } if (num_boxes == 1) @@ -966,15 +960,7 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid) } // destroys all the box shapes and the entities attached to those shapes - entity_destroy(gs, grid); -} - -static void grid_remove_box(GameState *gs, struct Entity *grid, struct Entity *box) -{ - flight_assert(grid->is_grid); - flight_assert(box->is_box); - entity_destroy(gs, box); - grid_correct_for_holes(gs, grid); + entity_memory_free(gs, grid); } static void on_damage(cpArbiter *arb, cpSpace *space, cpDataPointer userData) @@ -1055,7 +1041,7 @@ void destroy(GameState *gs) { if (gs->entities[i].exists) { - entity_destroy(gs, &gs->entities[i]); + entity_memory_free(gs, &gs->entities[i]); gs->entities[i] = (Entity){0}; } } @@ -1304,6 +1290,7 @@ enum GameVersion { VInitial, VNoGold, + VSafeSun, VMax, // this minus one will be the version used }; @@ -1539,6 +1526,8 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e) SER_MAYBE_RETURN(ser_V2(ser, &e->sun_pos)); SER_MAYBE_RETURN(ser_f(ser, &e->sun_mass)); SER_MAYBE_RETURN(ser_f(ser, &e->sun_radius)); + if (ser->version >= VSafeSun) + SER_VAR(&e->sun_is_safe); } SER_VAR(&e->is_grid); @@ -2390,12 +2379,12 @@ void create_initial_world(GameState *gs) { const double mass_multiplier = 10.0; EntityID suns[] = { - create_sun(gs, new_entity(gs), ((cpVect){500.0, 0.0}), ((cpVect){0.0, 0.0}), 1000000.0 * mass_multiplier, 30.0), - create_sun(gs, new_entity(gs), ((cpVect){500.0, 100.0}), ((cpVect){30.0, 0.0}), 10000.0 * mass_multiplier, 20.0), - create_sun(gs, new_entity(gs), ((cpVect){500.0, -100.0}), ((cpVect){-30.0, 0.0}), 10000.0 * mass_multiplier, 20.0), + create_sun(gs, new_entity(gs), ((cpVect){500.0, 0.0}), ((cpVect){0.0, 0.0}), 1000000.0 * mass_multiplier, 70.0), create_sun(gs, new_entity(gs), ((cpVect){-7000.0, -50.0}), ((cpVect){0.0, 0.0}), 100000.0 * mass_multiplier, 20.0), }; + get_entity(gs, suns[0])->sun_is_safe = true; + for (int i = 0; i < ARRLEN(suns); i++) { gs->suns[i] = suns[i]; @@ -2417,10 +2406,23 @@ void create_initial_world(GameState *gs) create_bomb_station(gs, cpvadd(SUN_POS(0), cpv(0.0, 300.0)), BoxExplosive); create_bomb_station(gs, cpvadd(SUN_POS(0), cpv(0.0, -300.0)), BoxCloaking); create_bomb_station(gs, cpvadd(SUN_POS(0), cpv(300.0, 0.0)), BoxMissileLauncher); - create_bomb_station(gs, cpvadd(SUN_POS(3), cpv(0.0, 300.0)), BoxMerge); + create_bomb_station(gs, cpvadd(SUN_POS(1), cpv(0.0, 300.0)), BoxMerge); #undef SUN_POS #else +#if 1 // basic test + { + bool indestructible = false; + enum CompassRotation rot = Right; + Entity *grid = new_entity(gs); + grid_create(gs, grid); + entity_set_pos(grid, cpv(-1.5, 0.0)); + BOX_AT_TYPE(grid, cpv(0.0, 0.0), BoxHullpiece); + BOX_AT_TYPE(grid, cpv(BOX_SIZE, 0.0), BoxHullpiece); + entity_ensure_in_orbit(gs, grid); + } +#endif + #if 1 // present the stations Log("Creating debug world\n"); @@ -2551,13 +2553,16 @@ void process(struct GameState *gs, double dt) } } #ifndef NO_GRAVITY - from_sun->sun_vel = cpvadd(from_sun->sun_vel, cpvmult(accel, dt)); - from_sun->sun_pos = cpvadd(from_sun->sun_pos, cpvmult(from_sun->sun_vel, dt)); - - if (cpvlength(from_sun->sun_pos) >= INSTANT_DEATH_DISTANCE_FROM_CENTER) + if (!from_sun->sun_is_safe) { - from_sun->sun_vel = cpvmult(from_sun->sun_vel, -0.8); - from_sun->sun_pos = cpvmult(cpvnormalize(from_sun->sun_pos), INSTANT_DEATH_DISTANCE_FROM_CENTER); + from_sun->sun_vel = cpvadd(from_sun->sun_vel, cpvmult(accel, dt)); + from_sun->sun_pos = cpvadd(from_sun->sun_pos, cpvmult(from_sun->sun_vel, dt)); + + if (cpvlength(from_sun->sun_pos) >= INSTANT_DEATH_DISTANCE_FROM_CENTER) + { + from_sun->sun_vel = cpvmult(from_sun->sun_vel, -0.8); + from_sun->sun_pos = cpvmult(cpvnormalize(from_sun->sun_pos), INSTANT_DEATH_DISTANCE_FROM_CENTER); + } } #endif } @@ -2800,9 +2805,8 @@ void process(struct GameState *gs, double dt) Entity *cur_box = cp_shape_entity(maybe_box_to_destroy); if (!cur_box->indestructible && !cur_box->is_platonic) { - Entity *cur_grid = cp_body_entity(cpShapeGetBody(maybe_box_to_destroy)); p->damage -= DAMAGE_TO_PLAYER_PER_BLOCK * ((BATTERY_CAPACITY - cur_box->energy_used) / BATTERY_CAPACITY); - grid_remove_box(gs, cur_grid, cur_box); + cur_box->flag_for_destruction = true; } } else if (box_unlocked(player, player->input.build_type)) @@ -2840,7 +2844,7 @@ void process(struct GameState *gs, double dt) #endif if (p->damage >= 1.0) { - entity_destroy(gs, p); + p->flag_for_destruction = true; player->entity = (EntityID){0}; } @@ -2850,12 +2854,8 @@ void process(struct GameState *gs, double dt) PROFILE_SCOPE("process entities") { - for (size_t i = 0; i < gs->cur_next_entity; i++) + ENTITIES_ITER(gs, e) { - Entity *e = &gs->entities[i]; - if (!e->exists) - continue; - if (e->body != NULL && cpvlengthsq((entity_pos(e))) > (INSTANT_DEATH_DISTANCE_FROM_CENTER * INSTANT_DEATH_DISTANCE_FROM_CENTER)) { #ifdef INTENSIVE_PROFILING @@ -2883,14 +2883,12 @@ void process(struct GameState *gs, double dt) } else { - entity_destroy(gs, e); - continue; + e->flag_for_destruction = true; } - continue; } } - // sun processing for this current entity + // sun processing for this current entity #ifndef NO_SUNS PROFILE_SCOPE("this entity sun processing") { @@ -2899,13 +2897,24 @@ void process(struct GameState *gs, double dt) cpVect pos_rel_sun = (cpvsub(entity_pos(e), (entity_pos(i.sun)))); cpFloat sqdist = cpvlengthsq(pos_rel_sun); + bool is_entity_dangerous = false; + is_entity_dangerous |= e->is_missile; + if (e->is_box) + { + is_entity_dangerous |= e->box_type == BoxExplosive; + } + if (is_entity_dangerous && sqdist < sun_dist_no_gravity(i.sun) * sun_dist_no_gravity(i.sun)) + { + e->flag_for_destruction = true; + break; + } + if (!e->is_grid) // grids aren't damaged (this edge case sucks!) { #ifdef INTENSIVE_PROFILING PROFILE_SCOPE("Grid processing") #endif { - sqdist = cpvlengthsq(cpvsub((entity_pos(e)), (entity_pos(i.sun)))); if (sqdist < (i.sun->sun_radius * i.sun->sun_radius)) { e->damage += 10.0 * dt; @@ -2938,8 +2947,7 @@ void process(struct GameState *gs, double dt) do_explosion(gs, e, dt); if (e->explosion_progress >= EXPLOSION_TIME) { - entity_destroy(gs, e); - continue; + e->flag_for_destruction = true; } } } @@ -2987,7 +2995,7 @@ void process(struct GameState *gs, double dt) explosion->explosion_vel = cpBodyGetVelocity(e->body); explosion->explosion_push_strength = MISSILE_EXPLOSION_PUSH; explosion->explosion_radius = MISSILE_EXPLOSION_RADIUS; - entity_destroy(gs, e); + e->flag_for_destruction = true; continue; } } @@ -2999,11 +3007,6 @@ void process(struct GameState *gs, double dt) PROFILE_SCOPE("Box processing") #endif { - if (e->is_platonic) - { - e->damage = 0.0; - gs->platonic_positions[(int)e->box_type] = entity_pos(e); - } if (e->box_type == BoxExplosive && e->damage >= EXPLOSION_DAMAGE_THRESHOLD) { Entity *explosion = new_entity(gs); @@ -3013,7 +3016,12 @@ void process(struct GameState *gs, double dt) explosion->explosion_push_strength = BOMB_EXPLOSION_PUSH; explosion->explosion_radius = BOMB_EXPLOSION_RADIUS; if (!e->is_platonic) - grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e); + e->flag_for_destruction = true; + } + if (e->is_platonic) + { + e->damage = 0.0; + gs->platonic_positions[(int)e->box_type] = entity_pos(e); } if (e->box_type == BoxMerge) { @@ -3091,7 +3099,7 @@ void process(struct GameState *gs, double dt) flight_assert(box_grid(cur) == box_grid(from_merge)); cur = next; } - entity_destroy(gs, other_grid); + other_grid->flag_for_destruction = true; } } } @@ -3099,7 +3107,7 @@ void process(struct GameState *gs, double dt) } if (e->damage >= 1.0) { - grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e); + e->flag_for_destruction = true; } } } @@ -3160,7 +3168,7 @@ void process(struct GameState *gs, double dt) rect_query(gs->space, (BoxCentered){ .pos = cpvadd(entity_pos(cur_box), cpvmult(box_facing_vector(cur_box), BOX_SIZE)), .rotation = box_rotation(cur_box), - .size = cpv(BOX_SIZE/2.0 - 0.03, BOX_SIZE/2.0 - 0.03), + .size = cpv(BOX_SIZE / 2.0 - 0.03, BOX_SIZE / 2.0 - 0.03), }); QUEUE_ITER(&query_result, QueryResult, res) { @@ -3397,6 +3405,22 @@ void process(struct GameState *gs, double dt) } } + PROFILE_SCOPE("Delete entities") + { + ENTITIES_ITER(gs, e) + { + if (e->flag_for_destruction) + { + Entity *grid = NULL; + if (e->is_box) + grid = box_grid(e); + entity_memory_free(gs, e); + if (grid != NULL) + grid_correct_for_holes(gs, grid); + } + } + } + PROFILE_SCOPE("chipmunk physics processing") { cpSpaceStep(gs->space, dt); diff --git a/main.c b/main.c index 1bdaafc..f7f8e7b 100644 --- a/main.c +++ b/main.c @@ -233,10 +233,6 @@ static struct BoxInfo .image_path = "loaded/merge.png", }, }; -#define ENTITIES_ITER(cur) \ - for (Entity *cur = gs.entities; cur < gs.entities + gs.cur_next_entity; \ - cur++) \ - if (cur->exists) // suppress compiler warning about ^^ above used in floating point context #define ARRLENF(arr) ((float)sizeof(arr) / sizeof(*arr)) static struct SquadMeta @@ -299,7 +295,7 @@ static void new_particle(cpVect pos, cpVect vel) } if (!created) { - Log("TOO MANY PARTICLES"); + Log("TOO MANY PARTICLES\n"); } } @@ -472,6 +468,11 @@ void recalculate_camera_pos() { \ .r = 0.0f, .g = 0.0f, .b = 1.0f, .a = 1.0f \ } +#define GREEN \ + (Color) \ + { \ + .r = 0.0f, .g = 1.0f, .b = 0.0f, .a = 1.0f \ + } #define GOLD colhex(255, 215, 0) typedef struct Color @@ -957,7 +958,7 @@ cpVect pointV2(sgp_point p) static void draw_circle(cpVect point, double radius) { -#define POINTS 64 +#define POINTS 128 sgp_line lines[POINTS]; for (int i = 0; i < POINTS; i++) { @@ -2049,7 +2050,7 @@ static void frame(void) { if (p->alive) { - p->alive_for += dt*1.5; + p->alive_for += dt * 1.5; p->pos = cpvadd(p->pos, cpvmult(p->vel, dt)); if (p->alive_for > 1.0) { @@ -2332,7 +2333,7 @@ static void frame(void) hovering_this_player = (EntityID){0}; // draw all types of entities - ENTITIES_ITER(e) + ENTITIES_ITER(&gs, e) { // draw grid if (e->is_grid) @@ -2734,8 +2735,10 @@ static void frame(void) // can draw at 0,0 because everything relative to sun now! // sun DEATH RADIUS - - set_color(BLUE); + if (i.sun->sun_is_safe) + set_color(GREEN); + else + set_color(BLUE); draw_circle((cpVect){0}, sun_dist_no_gravity(i.sun)); } } diff --git a/server.c b/server.c index e25e22a..2ab05c1 100644 --- a/server.c +++ b/server.c @@ -282,7 +282,7 @@ void server(void *info_raw) Entity *player_body = get_entity(&gs, gs.players[player_index].entity); if (player_body != NULL) { - entity_destroy(&gs, player_body); + entity_memory_free(&gs, player_body); } opus_encoder_destroy(player_encoders[player_index]); player_encoders[player_index] = NULL; diff --git a/types.h b/types.h index b3e00fd..3416536 100644 --- a/types.h +++ b/types.h @@ -130,7 +130,8 @@ #include "cpVect.h" // offers vector functions and types for the structs #include "miniaudio.h" // @Robust BAD. using miniaudio mutex construct for server thread synchronization. AWFUL! -#include // sqrt and cos vector functions +#define _USE_MATH_DEFINES +#include // sqrt and cos vector functions and PI #include // tick is unsigned integer #include // logging on errors for functions @@ -219,8 +220,8 @@ typedef struct EntityID static inline bool entityids_same(EntityID a, EntityID b) { - - return (a.generation == b.generation) && (a.index == b.index); + + return (a.generation == b.generation) && (a.index == b.index); } enum ScannerPointKind @@ -237,7 +238,7 @@ typedef struct InputFrame uint64_t tick; bool been_processed; // not serialized, used by server just to keep track of what inputs have been processed - + cpVect movement; double rotation; @@ -263,6 +264,7 @@ typedef struct PlatonicDetection typedef struct Entity { bool exists; + bool flag_for_destruction; EntityID next_free_entity; unsigned int generation; bool always_visible; // always serialized to the player. @@ -291,7 +293,7 @@ typedef struct Entity enum Squad owning_squad; // also controls what the player can see, because of cloaking! EntityID currently_inside_of_box; enum Squad squad_invited_to; // if squad none, then no squad invite - + // explosion bool is_explosion; cpVect explosion_pos; @@ -306,6 +308,7 @@ typedef struct Entity cpVect sun_pos; double sun_mass; double sun_radius; + bool sun_is_safe; // missile bool is_missile; @@ -322,9 +325,9 @@ typedef struct Entity // boxes bool is_box; enum BoxType box_type; - bool is_platonic; // can't be destroyed, unaffected by physical forces - EntityID next_box; // for the grid! - EntityID prev_box; // doubly linked so can remove in middle of chain + bool is_platonic; // can't be destroyed, unaffected by physical forces + EntityID next_box; // for the grid! + EntityID prev_box; // doubly linked so can remove in middle of chain enum CompassRotation compass_rotation; bool indestructible; @@ -397,6 +400,10 @@ typedef struct SunIter for (SunIter i = {0}; i.i < MAX_SUNS; i.i++) \ if ((i.sun = get_entity(gs_ptr, (gs_ptr)->suns[i.i])) != NULL) +#define ENTITIES_ITER(gs, cur) \ + for (Entity *cur = (gs)->entities; cur < (gs)->entities + (gs)->cur_next_entity; cur++) \ + if (cur->exists) + // gotta update the serialization functions when this changes typedef struct GameState { @@ -413,7 +420,7 @@ typedef struct GameState bool server_side_computing; // some things only the server should know and calculate, like platonic locations // Entity arena - // ent:ity pointers can't move around because of how the physics engine handles user data. + // entity 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; @@ -426,8 +433,8 @@ typedef struct GameState for (Player *cur = players; cur < players + MAX_PLAYERS; cur++) \ if (cur->connected) -#define PI 3.14159f -#define TAU (PI * 2.0f) +#define PI M_PI +#define TAU (M_PI * 2.0) // returns in radians static inline double rotangle(enum CompassRotation rot) @@ -539,7 +546,7 @@ bool could_learn_from_scanner(Player *for_player, Entity *box); void entity_set_pos(Entity *e, cpVect pos); double entity_rotation(Entity *e); void entity_ensure_in_orbit(GameState *gs, Entity *e); -void entity_destroy(GameState *gs, Entity *e); +void entity_memory_free(GameState *gs, 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) typedef struct LauncherTarget @@ -705,5 +712,5 @@ static inline double deg2rad(double deg) return (deg / 360.0f) * 2.0f * PI; } -#define min(X,Y) (((X) < (Y)) ? (X) : (Y)) -#define max(X,Y) (((X) > (Y)) ? (X) : (Y)) \ No newline at end of file +#define min(X, Y) (((X) < (Y)) ? (X) : (Y)) +#define max(X, Y) (((X) > (Y)) ? (X) : (Y)) \ No newline at end of file