diff --git a/flight.rdbg b/flight.rdbg index 7436c92..4453720 100644 Binary files a/flight.rdbg and b/flight.rdbg differ diff --git a/gamestate.c b/gamestate.c index a5c79aa..62e74d3 100644 --- a/gamestate.c +++ b/gamestate.c @@ -485,14 +485,13 @@ void box_create(GameState *gs, Entity *new_box, Entity *grid, V2 pos) box_add_to_boxes(gs, grid, new_box); } - V2 box_compass_vector(Entity *box) { - + assert(box->is_box); V2 to_return = (V2){.x = 1.0f, .y = 0.0f}; to_return = V2rotate(to_return, rotangle(box->compass_rotation)); - + return to_return; } @@ -593,12 +592,12 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid) } Entity *newbox = get_entity(gs, box_in_direction); - - if(newbox != NULL && newbox->box_type == BoxMerge && newbox->wants_disconnect && V2equal(V2scale(box_compass_vector(newbox), -1.0f), dir,0.01f)) + + if (newbox != NULL && newbox->box_type == BoxMerge && newbox->wants_disconnect && V2equal(V2scale(box_compass_vector(newbox), -1.0f), dir, 0.01f)) { newbox = NULL; } - + if (newbox != NULL) { box_remove_from_boxes(gs, newbox); @@ -820,6 +819,7 @@ float box_rotation(Entity *box) { return (float)cpBodyGetAngle(cpShapeGetBody(box->shape)); } + V2 entity_pos(Entity *e) { if (e->is_box) @@ -1590,9 +1590,12 @@ bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, uns } } +static THREADLOCAL Entity *grid_to_exclude = NULL; static bool merge_filter(Entity *potential_merge) { - return potential_merge->is_box && potential_merge->box_type == BoxMerge; + assert(grid_to_exclude != NULL); + assert(grid_to_exclude->is_grid); + return potential_merge->is_box && potential_merge->box_type == BoxMerge && box_grid(potential_merge) != grid_to_exclude; } static void cloaking_shield_callback_func(cpShape *shape, cpContactPointSet *points, void *data) @@ -1687,8 +1690,6 @@ static void do_explosion(GameState *gs, Entity *explosion, float dt) cpBodyFree(tmpbody); } - - V2 box_facing_vector(Entity *box) { assert(box->is_box); @@ -1700,6 +1701,40 @@ V2 box_facing_vector(Entity *box) return to_return; } +enum CompassRotation facing_vector_to_compass(Entity *grid_to_transplant_to, Entity *grid_facing_vector_from, V2 facing_vector) +{ + assert(grid_to_transplant_to->body != NULL); + assert(grid_to_transplant_to->is_grid); + + V2 local_to_from = grid_world_to_local(grid_facing_vector_from, V2add(entity_pos(grid_facing_vector_from), facing_vector)); + Log("local %f %f\n", local_to_from.x, local_to_from.y); + + V2 from_target = V2add(entity_pos(grid_to_transplant_to), facing_vector); + V2 local_target = grid_world_to_local(grid_to_transplant_to, from_target); + V2 local_facing = local_target; + + enum CompassRotation dirs[] = { + Right, + Left, + Up, + Down}; + + int smallest = -1; + float smallest_dist = INFINITY; + for (int i = 0; i < ARRLEN(dirs); i++) + { + V2 point = V2rotate((V2){.x = 1.0f}, rotangle(dirs[i])); + float dist = V2dist(point, local_facing); + if (dist < smallest_dist) + { + smallest_dist = dist; + smallest = i; + } + } + assert(smallest != -1); + return dirs[smallest]; +} + V2 thruster_force(Entity *box) { return V2scale(box_facing_vector(box), -box->thrust * THRUSTER_FORCE); @@ -1797,11 +1832,14 @@ V2 box_vel(Entity *box) void create_bomb_station(GameState *gs, V2 pos, enum BoxType platonic_type) { + enum CompassRotation rot = Right; + #define BOX_AT_TYPE(grid, pos, type) \ { \ Entity *box = new_entity(gs); \ box_create(gs, box, grid, pos); \ box->box_type = type; \ + box->compass_rotation = rot; \ box->indestructible = indestructible; \ } #define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece) @@ -1842,14 +1880,7 @@ void create_bomb_station(GameState *gs, V2 pos, enum BoxType platonic_type) void create_hard_shell_station(GameState *gs, V2 pos, enum BoxType platonic_type) { -#define BOX_AT_TYPE(grid, pos, type) \ - { \ - Entity *box = new_entity(gs); \ - box_create(gs, box, grid, pos); \ - box->box_type = type; \ - box->indestructible = indestructible; \ - } -#define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece) + enum CompassRotation rot = Right; bool indestructible = false; Entity *grid = new_entity(gs); @@ -1884,6 +1915,40 @@ void create_initial_world(GameState *gs) create_bomb_station(gs, (V2){-5.0f, 0.0f}, BoxExplosive); create_bomb_station(gs, (V2){0.0f, 5.0f}, BoxGyroscope); create_hard_shell_station(gs, (V2){-5.0f, 5.0f}, BoxCloaking); + + bool indestructible = false; + + float theta = deg2rad(65.0f); + + V2 from = (V2){BOX_SIZE * 4.0f, -1}; + + enum CompassRotation rot = Right; + { + Entity *grid = new_entity(gs); + grid_create(gs, grid); + entity_set_pos(grid, V2add(from, V2rotate((V2){.x = -BOX_SIZE * 9.0f}, theta))); + cpBodySetAngle(grid->body, theta + PI); + entity_ensure_in_orbit(grid); + rot = Left; + BOX_AT_TYPE(grid, ((V2){0.0f, 0.0f}), BoxMerge); + BOX_AT(grid, ((V2){0.0f, -BOX_SIZE})); + BOX_AT_TYPE(grid, ((V2){BOX_SIZE, 0.0f}), BoxMerge); + } + + { + Entity *grid = new_entity(gs); + grid_create(gs, grid); + entity_set_pos(grid, from); + cpBodySetAngle(grid->body, theta); + entity_ensure_in_orbit(grid); + rot = Left; + BOX_AT_TYPE(grid, ((V2){-BOX_SIZE, 0.0f}), BoxMerge); + rot = Down; + BOX_AT_TYPE(grid, ((V2){0.0f, 0.0f}), BoxMerge); + rot = Up; + BOX_AT_TYPE(grid, ((V2){0.0f, BOX_SIZE}), BoxMerge); + cpBodySetVelocity(grid->body, v2_to_cp(V2rotate((V2){-0.4f, 0.0f}, theta))); + } #else create_bomb_station(gs, (V2){-50.0f, 0.0f}, BoxExplosive); create_hard_shell_station(gs, (V2){0.0f, 100.0f}, BoxGyroscope); @@ -2015,7 +2080,6 @@ void process(GameState *gs, float dt) assert(potential_seat->exists); assert(potential_seat->is_box); assert(potential_seat->box_type == BoxMerge); - potential_seat->wants_disconnect = false; } if (potential_seat->box_type == BoxCockpit || potential_seat->box_type == BoxMedbay) // @Robust check by feature flag instead of box type { @@ -2286,28 +2350,62 @@ void process(GameState *gs, float dt) if (e->box_type == BoxMerge) { Entity *from_merge = e; + assert(from_merge != NULL); + + grid_to_exclude = box_grid(from_merge); Entity *other_merge = closest_box_to_point_in_radius(gs, entity_pos(from_merge), MERGE_MAX_DIST, merge_filter); - if (box_grid(from_merge) != box_grid(other_merge)) + + if (other_merge == NULL && from_merge->wants_disconnect) + from_merge->wants_disconnect = false; + + if (!from_merge->wants_disconnect && other_merge != NULL && !other_merge->wants_disconnect) { + assert(box_grid(from_merge) != box_grid(other_merge)); + + Entity *from_grid = box_grid(from_merge); + Entity *other_grid = box_grid(other_merge); + + // the merges are near eachother, but are they facing eachother... bool from_facing_other = V2dot(box_facing_vector(from_merge), V2normalize(V2sub(entity_pos(other_merge), entity_pos(from_merge)))) > 0.8f; bool other_facing_from = V2dot(box_facing_vector(other_merge), V2normalize(V2sub(entity_pos(from_merge), entity_pos(other_merge)))) > 0.8f; - if (from_facing_other && other_facing_from) + + // using this stuff to detect if when the other grid's boxes are snapped, they'll be snapped + // to be next to the from merge box + V2 actual_new_pos = grid_snapped_box_pos(from_grid, entity_pos(other_merge)); + V2 needed_new_pos = V2add(entity_pos(from_merge), V2scale(box_facing_vector(from_merge), BOX_SIZE)); + if (from_facing_other && other_facing_from && V2equal(needed_new_pos, actual_new_pos, 0.01f)) { // do the merge + V2 facing_vector_needed = V2scale(box_facing_vector(from_merge), -1.0f); + V2 current_facing_vector = box_facing_vector(other_merge); + float angle_diff = V2anglediff(current_facing_vector, facing_vector_needed); + if(angle_diff == FLT_MIN) + angle_diff = 0.0f; + assert(!isnan(angle_diff)); + + cpBodySetAngle(other_grid->body, cpBodyGetAngle(other_grid->body) + angle_diff); + + V2 moved_because_angle_change = V2sub(needed_new_pos, entity_pos(other_merge)); + cpBodySetPosition(other_grid->body, v2_to_cp(V2add(entity_pos(other_grid), moved_because_angle_change))); + + // V2 snap_movement_vect = V2sub(actual_new_pos, entity_pos(other_merge)); + V2 snap_movement_vect = (V2){0}; + + Entity *cur = get_entity(gs, other_grid->boxes); - Entity *new_grid = box_grid(from_merge); - Entity *old_grid = box_grid(other_merge); - Entity *cur = get_entity(gs, old_grid->boxes); - old_grid->boxes = (EntityID){0}; + other_grid->boxes = (EntityID){0}; while (cur != NULL) { Entity *next = get_entity(gs, cur->next_box); V2 world = entity_pos(cur); - box_create(gs, cur, new_grid, grid_world_to_local(new_grid, grid_snapped_box_pos(new_grid, world))); // destroys next/prev fields on cur + enum CompassRotation new_rotation = facing_vector_to_compass(from_grid, other_grid, box_facing_vector(cur)); + cur->compass_rotation = new_rotation; + V2 new_cur_pos = grid_snapped_box_pos(from_grid, V2add(snap_movement_vect, world)); + box_create(gs, cur, from_grid, grid_world_to_local(from_grid, new_cur_pos)); // destroys next/prev fields on cur assert(box_grid(cur) == box_grid(from_merge)); cur = next; } - entity_destroy(gs, old_grid); + entity_destroy(gs, other_grid); } } } diff --git a/types.h b/types.h index 7ca9e1e..5641ebe 100644 --- a/types.h +++ b/types.h @@ -6,7 +6,7 @@ #define MAX_PLAYERS 16 #define MAX_ENTITIES 1024 * 25 #define BOX_SIZE 0.25f -#define MERGE_MAX_DIST 0.35f +#define MERGE_MAX_DIST (BOX_SIZE / 2.0f + 0.01f) #define PLAYER_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE}) #define PLAYER_MASS 0.5f #define PLAYER_JETPACK_FORCE 1.5f @@ -17,10 +17,10 @@ #define MISSILE_MASS 1.0f // how many missiles grown per second #define MISSILE_DAMAGE_THRESHOLD 0.2f -#define MISSILE_CHARGE_RATE 0.5f +#define MISSILE_CHARGE_RATE 0.5f // centered on the sprite #define MISSILE_SPRITE_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE}) -#define MISSILE_COLLIDER_SIZE ((V2){.x = BOX_SIZE*0.5f, .y = BOX_SIZE*0.5f}) +#define MISSILE_COLLIDER_SIZE ((V2){.x = BOX_SIZE * 0.5f, .y = BOX_SIZE * 0.5f}) // #define PLAYER_JETPACK_FORCE 20.0f // distance at which things become geostationary and no more solar power! #define PLAYER_JETPACK_ROTATION_ENERGY_PER_SECOND 0.2f @@ -38,7 +38,7 @@ #define GYROSCOPE_ENERGY_USED_PER_SECOND 0.005f #define GYROSCOPE_TORQUE 0.5f #define CLOAKING_ENERGY_USE 0.1f -#define CLOAKING_PANEL_SIZE BOX_SIZE*3.0f +#define CLOAKING_PANEL_SIZE BOX_SIZE * 3.0f #define VISION_RADIUS 12.0f #define MAX_SERVER_TO_CLIENT 1024 * 512 // maximum size of serialized gamestate buffer #define MAX_CLIENT_TO_SERVER 1024 * 10 // maximum size of serialized inputs and mic data @@ -65,10 +65,11 @@ // VOIP #define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2 -#define VOIP_EXPECTED_FRAME_COUNT 480 -#define VOIP_SAMPLE_RATE 48000 +#define VOIP_EXPECTED_FRAME_COUNT 240 +#define VOIP_SAMPLE_RATE (48000 / 2) + // in seconds -#define VOIP_TIME_PER_PACKET (1.0f / ((float)((float)VOIP_SAMPLE_RATE / VOIP_EXPECTED_FRAME_COUNT))) +#define VOIP_TIME_PER_PACKET (1.0f / ((float)((float)VOIP_SAMPLE_RATE / VOIP_EXPECTED_FRAME_COUNT))) #define VOIP_PACKET_MAX_SIZE 4000 #define VOIP_DISTANCE_WHEN_CANT_HEAR (VISION_RADIUS * 0.8f) @@ -156,7 +157,7 @@ typedef sgp_point P2; enum BoxType { - BoxInvalid, // zero initialized box is invalid! + BoxInvalid, // zero initialized box is invalid! BoxHullpiece, BoxThruster, BoxBattery, @@ -236,13 +237,12 @@ typedef struct Entity 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 - - + // players and boxes can be cloaked - // If this is within 2 timesteps of the current game time, the entity is invisible. + // If this is within 2 timesteps of the current game time, the entity is invisible. double time_was_last_cloaked; enum Squad last_cloaked_by_squad; - + // 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 @@ -265,7 +265,7 @@ typedef struct Entity // missile bool is_missile; float time_burned_for; // until MISSILE_BURN_TIME - + // grids bool is_grid; float total_energy_capacity; @@ -274,47 +274,46 @@ typedef struct Entity // boxes bool is_box; enum BoxType box_type; - bool is_platonic; // can't be destroyed, unaffected by physical forces + bool is_platonic; // can't be destroyed, unaffected by physical forces bool always_visible; // always serialized to the player. @Robust check if not used - EntityID next_box; // for the grid! - EntityID prev_box; // doubly linked so can remove in middle of chain + EntityID next_box; // for the grid! + EntityID prev_box; // doubly linked so can remove in middle of chain enum CompassRotation compass_rotation; bool indestructible; - + // merger bool wants_disconnect; // don't serialized, termporary value not used across frames - + // missile launcher float missile_construction_charge; - + // used by medbay and cockpit - EntityID player_who_is_inside_of_me; - + EntityID player_who_is_inside_of_me; + // only serialized when box_type is thruster or gyroscope, used for both. Thrust // can mean rotation thrust! 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 - + // only serialized when box_type is battery - float energy_used; // battery, between 0 battery capacity. You have to look through code to figure out what that is! haha sucker! - + float energy_used; // battery, between 0 battery capacity. You have to look through code to figure out what that is! haha sucker! + // only serialized when box_type is solar panel - float sun_amount; // solar panel, between 0 and 1 - + float sun_amount; // solar panel, between 0 and 1 + // cloaking only float cloaking_power; // 0.0 if unable to be used because no power, 1.0 if fully cloaking! - + // scanner only stuff! EntityID currently_scanning; - float currently_scanning_progress; // when 1.0, scans it! + float currently_scanning_progress; // when 1.0, scans it! BOX_UNLOCKS_TYPE blueprints_learned; // @Robust make this same type as blueprints - float scanner_head_rotate_speed; // not serialized, cosmetic + float scanner_head_rotate_speed; // not serialized, cosmetic float scanner_head_rotate; - V2 platonic_nearest_direction; // normalized + V2 platonic_nearest_direction; // normalized float platonic_detection_strength; // from zero to one } Entity; - typedef struct Player { bool connected; @@ -336,9 +335,9 @@ typedef struct GameState V2 goldpos; Player players[MAX_PLAYERS]; - + V2 platonic_positions[MAX_BOX_TYPES]; // don't want to search over every entity to get the nearest platonic box! - + bool server_side_computing; // some things only the server should know and calculate, like platonic locations // Entity arena @@ -415,14 +414,14 @@ void initialize(struct GameState *gs, void *entity_arena, size_t entity_arena_si void destroy(struct GameState *gs); void process_fixed_timestep(GameState *gs); void process(struct GameState *gs, float dt); // does in place -Entity *closest_box_to_point_in_radius(struct GameState *gs, V2 point, float radius, bool(*filter_func)(Entity*)); +Entity *closest_box_to_point_in_radius(struct GameState *gs, V2 point, float radius, bool (*filter_func)(Entity *)); uint64_t tick(struct GameState *gs); // all of these return if successful or not -bool server_to_client_serialize(struct ServerToClient *msg, unsigned char*bytes, size_t *out_len, size_t max_len, Entity *for_this_player, bool to_disk); -bool server_to_client_deserialize(struct ServerToClient *msg, unsigned char*bytes, size_t max_len, bool from_disk); -bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, unsigned char*bytes, size_t max_len); -bool client_to_server_serialize(GameState *gs, struct ClientToServer *msg, unsigned char*bytes, size_t *out_len, size_t max_len); +bool server_to_client_serialize(struct ServerToClient *msg, unsigned char *bytes, size_t *out_len, size_t max_len, Entity *for_this_player, bool to_disk); +bool server_to_client_deserialize(struct ServerToClient *msg, unsigned char *bytes, size_t max_len, bool from_disk); +bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, unsigned char *bytes, size_t max_len); +bool client_to_server_serialize(GameState *gs, struct ClientToServer *msg, unsigned char *bytes, size_t *out_len, size_t max_len); // entities bool is_burning(Entity *missile); @@ -564,6 +563,14 @@ static V2 V2sub(V2 a, V2 b) }; } +static float sign(float f) +{ + if (f >= 0.0f) + return 1.0f; + else + return -1.0f; +} + static bool V2equal(V2 a, V2 b, float eps) { return V2length(V2sub(a, b)) < eps; @@ -593,6 +600,14 @@ static inline float clamp(float f, float minimum, float maximum) return f; } +static float V2anglediff(V2 a, V2 b) +{ + float acos_input = V2dot(a, b) / (V2length(a) * V2length(b)); + acos_input = clamp(acos_input, -1.0f, 1.0f); + assert(acos_input >= -1.0f && acos_input <= 1.0f); + return acosf(acos_input) * sign(V2dot(a, b)); +} + static float fract(float f) { return f - floorf(f); @@ -711,10 +726,24 @@ static void set_color(Color c) sgp_set_color(c.r, c.g, c.b, c.a); } -#define WHITE \ - (Color) { .r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f } -#define RED \ - (Color) { .r = 1.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f } -#define BLUE \ - (Color) { .r = 0.0f, .g = 0.0f, .b = 1.0f, .a = 1.0f } +static float deg2rad(float deg) +{ + return (deg / 360.0f) * 2.0f * PI; +} + +#define WHITE \ + (Color) \ + { \ + .r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f \ + } +#define RED \ + (Color) \ + { \ + .r = 1.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f \ + } +#define BLUE \ + (Color) \ + { \ + .r = 0.0f, .g = 0.0f, .b = 1.0f, .a = 1.0f \ + } #define GOLD colhex(255, 215, 0)