Compare commits

...

11 Commits

@ -6,7 +6,7 @@
#define SERVER_PORT 2551
// must be unsigned integer
#define GIT_RELEASE_TAG 23
#define GIT_RELEASE_TAG 24
#ifdef DEBUG
@ -15,11 +15,13 @@
// #define SERVER_ADDRESS "207.246.80.160"
// #define PROFILING
#define DEBUG_RENDERING
// #define DEBUG_RENDERING
// #define DEBUG_WORLD
// #define UNLOCK_ALL
#define UNLOCK_ALL
#define TIME_BETWEEN_WORLD_SAVE 1000000.0f
#define INFINITE_RESOURCES
// #define DEBUG_TOOLS
#define DEBUG_TOOLS
#define CHIPMUNK_INTEGRITY_CHECK
// #define FAT_THRUSTERS
// #define NO_GRAVITY
// #define NO_SUNS

Binary file not shown.

@ -288,60 +288,101 @@ cpVect entity_vel(GameState *gs, Entity *e)
return (cpVect){0};
}
static THREADLOCAL double to_face = 0.0;
static THREADLOCAL double nearest_dist = INFINITY;
static THREADLOCAL bool target_found = false;
static void on_missile_shape(cpShape *shape, cpContactPointSet *points, void *data)
typedef struct QueryResult
{
Entity *launcher = (Entity *)data;
Entity *other = cp_shape_entity(shape);
GameState *gs = entitys_gamestate(launcher);
flight_assert(other->is_box || other->is_player || other->is_missile);
cpShape *shape;
cpVect pointA;
cpVect pointB;
} QueryResult;
static THREADLOCAL char query_result_data[128 * sizeof(QueryResult)] = {0};
// the data starts off NULL, on the first call sets it to result data
static THREADLOCAL Queue query_result = {.data_length = 128 * sizeof(QueryResult), .element_size = sizeof(cpShape *)};
cpVect to = cpvsub(entity_pos(other), entity_pos(launcher));
bool should_attack = true;
if (other->is_box && box_grid(other) == box_grid(launcher))
should_attack = false;
if (other->owning_squad == launcher->owning_squad)
should_attack = false;
if (should_attack && cpvlength(to) < nearest_dist)
static void shape_query_callback(cpShape *shape, cpContactPointSet *points, void *data)
{
flight_assert(points->count >= 1); // bad, not exactly sure what the points look like. Just taking the first one for now. @Robust good debug drawing for this and figure it out. Make debug rects fade away instead of only drawn for one frame, makes one off things visible
QueryResult *new = queue_push_element(&query_result);
if (new == NULL)
{
target_found = true;
nearest_dist = cpvlength(to);
// lookahead by their velocity
cpVect rel_velocity = cpvsub(entity_vel(gs, other), entity_vel(gs, launcher));
double dist = cpvdist(entity_pos(other), entity_pos(launcher));
double time_of_travel = sqrt((2.0 * dist) / (MISSILE_BURN_FORCE / MISSILE_MASS));
(void)queue_pop_element(&query_result);
new = queue_push_element(&query_result);
}
new->shape = shape;
new->pointA = points->points[0].pointA;
new->pointB = points->points[0].pointB;
}
cpVect other_future_pos = cpvadd(entity_pos(other), cpvmult(rel_velocity, time_of_travel));
// shapes are pushed to query result
static void shape_query(cpSpace *space, cpShape *shape)
{
query_result.data = query_result_data;
queue_clear(&query_result);
cpSpaceShapeQuery(space, shape, shape_query_callback, NULL);
}
cpVect adjusted_to = cpvsub(other_future_pos, entity_pos(launcher));
// shapes are pushed to query result
static void circle_query(cpSpace *space, cpVect pos, double radius)
{
cpBody *tmp_body = cpBodyNew(0, 0);
cpBodySetPosition(tmp_body, pos);
cpShape *tmp_shape = cpCircleShapeNew(tmp_body, radius, cpv(0, 0));
shape_query(space, tmp_shape);
cpBodyFree(tmp_body);
cpShapeFree(tmp_shape);
}
to_face = cpvangle(adjusted_to);
}
static void rect_query(cpSpace *space, BoxCentered box)
{
cpBody *tmp_body = cpBodyNew(0, 0);
cpBodySetPosition(tmp_body, box.pos);
cpBodySetAngle(tmp_body, box.rotation);
cpShape *tmp_shape = cpBoxShapeNew(tmp_body, box.size.x * 2.0, box.size.y * 2.0, 0.0);
shape_query(space, tmp_shape);
cpBodyFree(tmp_body);
cpShapeFree(tmp_shape);
}
LauncherTarget missile_launcher_target(GameState *gs, Entity *launcher)
{
to_face = 0.0;
cpBody *tmp = cpBodyNew(0.0, 0.0);
cpBodySetPosition(tmp, (entity_pos(launcher)));
cpShape *circle = cpCircleShapeNew(tmp, MISSILE_RANGE, cpv(0, 0));
double to_face = 0.0;
double nearest_dist = INFINITY;
bool target_found = false;
circle_query(gs->space, entity_pos(launcher), MISSILE_RANGE);
QUEUE_ITER(&query_result, QueryResult, res)
{
cpShape *cur_shape = res->shape;
Entity *other = cp_shape_entity(cur_shape);
flight_assert(other->is_box || other->is_player || other->is_missile);
cpVect to = cpvsub(entity_pos(other), entity_pos(launcher));
bool should_attack = true;
if (other->is_box && box_grid(other) == box_grid(launcher))
should_attack = false;
if (other->owning_squad == launcher->owning_squad)
should_attack = false;
if (should_attack && cpvlength(to) < nearest_dist)
{
target_found = true;
nearest_dist = cpvlength(to);
// lookahead by their velocity
cpVect rel_velocity = cpvsub(entity_vel(gs, other), entity_vel(gs, launcher));
double dist = cpvdist(entity_pos(other), entity_pos(launcher)) - MISSILE_SPAWN_DIST;
nearest_dist = INFINITY;
to_face = 0.0;
target_found = false;
cpSpaceShapeQuery(gs->space, circle, on_missile_shape, (void *)launcher);
double time_of_travel = sqrt((2.0 * dist) / (MISSILE_BURN_FORCE / MISSILE_MASS));
cpBodyFree(tmp);
cpShapeFree(circle);
cpVect other_future_pos = cpvadd(entity_pos(other), cpvmult(rel_velocity, time_of_travel));
cpVect adjusted_to = cpvsub(other_future_pos, entity_pos(launcher));
to_face = cpvangle(adjusted_to);
}
}
return (LauncherTarget){.target_found = target_found, .facing_angle = to_face};
}
void on_entity_child_shape(cpBody *body, cpShape *shape, void *data);
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)
@ -437,6 +478,7 @@ EntityID create_sun(GameState *gs, Entity *new_sun, cpVect pos, cpVect vel, doub
new_sun->sun_vel = vel;
new_sun->sun_mass = mass;
new_sun->sun_radius = radius;
new_sun->always_visible = true;
return get_id(gs, new_sun);
}
@ -644,12 +686,8 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
#define MAX_SEPARATE_GRIDS 8
EntityID separate_grids[MAX_SEPARATE_GRIDS] = {0};
int cur_separate_grid_index = 0;
int cur_separate_grid_size = 0;
int processed_boxes = 0;
int biggest_separate_grid_index = 0;
uint32_t biggest_separate_grid_length = 0;
// process all boxes into separate, but correctly connected, grids
while (processed_boxes < num_boxes)
{
@ -679,7 +717,6 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
{
N->next_box = separate_grids[cur_separate_grid_index];
separate_grids[cur_separate_grid_index] = get_id(gs, N);
cur_separate_grid_size++;
processed_boxes++;
if (get_id(gs, N).index > biggest_box_index)
@ -741,14 +778,8 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
}
}
if (biggest_box_index > biggest_separate_grid_length)
{
biggest_separate_grid_length = biggest_box_index;
biggest_separate_grid_index = cur_separate_grid_index;
}
cur_separate_grid_index++;
flight_assert(cur_separate_grid_index < MAX_SEPARATE_GRIDS);
cur_separate_grid_size = 0;
}
// create new grids for all lists of boxes except for the biggest one.
@ -1052,8 +1083,17 @@ SerMaybeFailure ser_data(SerState *ser, char *data, size_t data_len, const char
{
if (ser->write_varnames)
{
// the name
memcpy(ser->bytes + ser->cursor, var_name, var_name_len);
ser->cursor += var_name_len;
SER_ASSERT(ser->cursor < ser->max_size);
// the size, compressed to a short
SER_ASSERT(data_len < 65535); // uh oh stinky!
uint16_t size_to_write = (uint16_t)data_len;
memcpy(ser->bytes + ser->cursor, &size_to_write, sizeof(size_to_write));
ser->cursor += sizeof(size_to_write);
SER_ASSERT(ser->cursor < ser->max_size);
}
for (int b = 0; b < data_len; b++)
{
@ -1096,6 +1136,18 @@ SerMaybeFailure ser_data(SerState *ser, char *data, size_t data_len, const char
// now compare!
SER_ASSERT(strcmp(read_name, name) == 0);
// deserialize and check the size too!
SER_ASSERT(data_len < 65535); // uh oh stinky!
uint16_t expected_size = (uint16_t)data_len;
uint16_t got_size = 0;
for (int b = 0; b < sizeof(got_size); b++)
{
((char *)&got_size)[b] = ser->bytes[ser->cursor];
ser->cursor += 1;
SER_ASSERT(ser->cursor <= ser->max_size);
}
SER_ASSERT(got_size == expected_size);
}
for (int b = 0; b < data_len; b++)
{
@ -1360,6 +1412,8 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
case BoxGyroscope:
SER_MAYBE_RETURN(ser_f(ser, &e->thrust));
SER_MAYBE_RETURN(ser_f(ser, &e->wanted_thrust));
SER_MAYBE_RETURN(ser_f(ser, &e->gyrospin_angle));
SER_MAYBE_RETURN(ser_f(ser, &e->gyrospin_velocity));
break;
case BoxBattery:
SER_MAYBE_RETURN(ser_f(ser, &e->energy_used));
@ -1478,8 +1532,6 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
SER_VAR(&gs->tick);
SER_VAR(&gs->subframe_time);
SER_MAYBE_RETURN(ser_V2(ser, &gs->goldpos));
if (!ser->save_or_load_from_disk) // don't save player info to disk, this is filled on connection/disconnection
{
for (size_t i = 0; i < MAX_PLAYERS; i++)
@ -1521,7 +1573,18 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
{
if (!e->is_box && !e->is_grid)
{
SER_ENTITY();
Entity *cur_entity = e;
bool this_entity_in_range = ser->save_or_load_from_disk;
this_entity_in_range |= ser->for_player == NULL;
this_entity_in_range |= (ser->for_player != NULL && cpvdistsq(entity_pos(ser->for_player), entity_pos(cur_entity)) < VISION_RADIUS * VISION_RADIUS); // only in vision radius
// don't have to check if the entity is cloaked because this is checked above
if (cur_entity->always_visible)
this_entity_in_range = true;
if (this_entity_in_range)
{
SER_ENTITY();
}
}
if (e->is_grid)
{
@ -1549,7 +1612,7 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
EntityID cur_id = get_id(gs, cur_box);
SER_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. @BeforePatreon add debug info in serialization for what size the expected type is, maybe string nameof the type
size_t the_index = (size_t)cur_id.index; // super critical. Type of &i is size_t. Checked when write varnames is true though!
SER_VAR_NAME(&the_index, "&i");
SER_MAYBE_RETURN(ser_entity(ser, gs, cur_box));
}
@ -1709,7 +1772,7 @@ SerMaybeFailure ser_client_to_server(SerState *ser, ClientToServer *msg)
{
size_t to_skip = queue_num_elements(msg->input_data) - num;
size_t i = 0;
QUEUE_ITER(msg->input_data, cur_header)
QUEUE_ITER(msg->input_data, InputFrame, cur)
{
if (i < to_skip)
{
@ -1717,7 +1780,6 @@ SerMaybeFailure ser_client_to_server(SerState *ser, ClientToServer *msg)
}
else
{
InputFrame *cur = (InputFrame *)cur_header->data;
SER_MAYBE_RETURN(ser_inputframe(ser, cur));
}
}
@ -1797,51 +1859,32 @@ static bool merge_filter(Entity *potential_merge)
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)
{
Entity *from_cloaking_box = (Entity *)data;
GameState *gs = entitys_gamestate(from_cloaking_box);
Entity *to_cloak = cp_shape_entity(shape);
to_cloak->time_was_last_cloaked = elapsed_time(gs);
to_cloak->last_cloaked_by_squad = from_cloaking_box->owning_squad;
}
// has to be global var because can only get this information
static THREADLOCAL cpShape *closest_to_point_in_radius_result = NULL;
static THREADLOCAL double closest_to_point_in_radius_result_largest_dist = 0.0;
static THREADLOCAL bool (*closest_to_point_in_radius_filter_func)(Entity *);
static void closest_point_callback_func(cpShape *shape, cpContactPointSet *points, void *data)
{
flight_assert(points->count == 1);
Entity *e = cp_shape_entity(shape);
if (!e->is_box)
return;
if (closest_to_point_in_radius_filter_func != NULL && !closest_to_point_in_radius_filter_func(e))
return;
double dist = cpvlength((cpvsub(points->points[0].pointA, points->points[0].pointB)));
// double dist = -points->points[0].distance;
if (dist > closest_to_point_in_radius_result_largest_dist)
{
closest_to_point_in_radius_result_largest_dist = dist;
closest_to_point_in_radius_result = shape;
}
}
// filter func null means everything is ok, if it's not null and returns false, that means
// exclude it from the selection. This returns the closest box entity!
Entity *closest_box_to_point_in_radius(struct GameState *gs, cpVect point, double radius, bool (*filter_func)(Entity *))
{
closest_to_point_in_radius_result = NULL;
closest_to_point_in_radius_result_largest_dist = 0.0;
closest_to_point_in_radius_filter_func = filter_func;
cpBody *tmpbody = cpBodyNew(0.0, 0.0);
cpShape *circle = cpCircleShapeNew(tmpbody, radius, (point));
cpSpaceShapeQuery(gs->space, circle, closest_point_callback_func, NULL);
cpShape *closest_to_point_in_radius_result = NULL;
double closest_to_point_in_radius_result_largest_dist = 0.0;
circle_query(gs->space, point, radius);
QUEUE_ITER(&query_result, QueryResult, res)
{
cpShape *shape = res->shape;
cpShapeFree(circle);
cpBodyFree(tmpbody);
Entity *e = cp_shape_entity(shape);
if (!e->is_box)
continue;
if (filter_func != NULL && !filter_func(e))
continue;
double dist = cpvlength((cpvsub(res->pointA, res->pointB)));
// double dist = -points->points[0].distance;
if (dist > closest_to_point_in_radius_result_largest_dist)
{
closest_to_point_in_radius_result_largest_dist = dist;
closest_to_point_in_radius_result = shape;
}
}
if (closest_to_point_in_radius_result != NULL)
{
@ -1861,33 +1904,23 @@ static bool scanner_filter(Entity *e)
return true;
}
static double cur_explosion_damage = 0.0;
static cpVect explosion_origin = {0};
static double explosion_push_strength = 0.0;
static void explosion_callback_func(cpShape *shape, cpContactPointSet *points, void *data)
{
GameState *gs = (GameState *)data;
cp_shape_entity(shape)->damage += cur_explosion_damage;
Entity *parent = get_entity(gs, cp_shape_entity(shape)->shape_parent_entity);
cpVect from_pos = entity_pos(cp_shape_entity(shape));
cpVect impulse = cpvmult(cpvnormalize(cpvsub(from_pos, explosion_origin)), explosion_push_strength);
flight_assert(parent->body != NULL);
cpBodyApplyImpulseAtWorldPoint(parent->body, (impulse), (from_pos));
}
static void do_explosion(GameState *gs, Entity *explosion, double dt)
{
cpBody *tmpbody = cpBodyNew(0.0, 0.0);
cpShape *circle = cpCircleShapeNew(tmpbody, explosion->explosion_radius, (explosion_origin));
cur_explosion_damage = dt * EXPLOSION_DAMAGE_PER_SEC;
explosion_origin = explosion->explosion_pos;
explosion_push_strength = explosion->explosion_push_strength;
cpSpaceShapeQuery(gs->space, circle, explosion_callback_func, (void *)gs);
cpShapeFree(circle);
cpBodyFree(tmpbody);
double cur_explosion_damage = dt * EXPLOSION_DAMAGE_PER_SEC;
cpVect explosion_origin = explosion->explosion_pos;
double explosion_push_strength = explosion->explosion_push_strength;
circle_query(gs->space, explosion_origin, explosion->explosion_radius);
QUEUE_ITER(&query_result, QueryResult, res)
{
cpShape *shape = res->shape;
cp_shape_entity(shape)->damage += cur_explosion_damage;
Entity *parent = get_entity(gs, cp_shape_entity(shape)->shape_parent_entity);
cpVect from_pos = entity_pos(cp_shape_entity(shape));
cpVect impulse = cpvmult(cpvnormalize(cpvsub(from_pos, explosion_origin)), explosion_push_strength);
flight_assert(parent->body != NULL);
cpBodyApplyImpulseAtWorldPoint(parent->body, (impulse), (from_pos));
}
}
cpVect box_facing_vector(Entity *box)
@ -2186,11 +2219,12 @@ void create_hard_shell_station(GameState *gs, cpVect pos, enum BoxType platonic_
}
void create_initial_world(GameState *gs)
{
const double mass_multiplier = 10.0;
EntityID suns[] = {
create_sun(gs, new_entity(gs), ((cpVect){800.0, 0.0}), ((cpVect){0.0, 0.0}), 1000000.0, 30.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, 50.0}), ((cpVect){50.0, 0.0}), 10000.0, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, -50.0}), ((cpVect){-50.0, 0.0}), 10000.0, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){-2500.0, -50.0}), ((cpVect){0.0, 0.0}), 100000.0, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, 0.0}), ((cpVect){0.0, 0.0}), 1000000.0 * mass_multiplier, 30.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, 100.0}), ((cpVect){60.0, 0.0}), 10000.0 * mass_multiplier, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, -100.0}), ((cpVect){-60.0, 0.0}), 10000.0 * mass_multiplier, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){-7000.0, -50.0}), ((cpVect){0.0, 0.0}), 100000.0 * mass_multiplier, 20.0),
};
for (int i = 0; i < ARRLEN(suns); i++)
@ -2203,7 +2237,7 @@ void create_initial_world(GameState *gs)
// create_hard_shell_station(gs, (cpVect){800.0, 400.0}, BoxGyroscope);
create_bomb_station(gs, (cpVect){800.0, -800.0}, BoxCloaking);
create_bomb_station(gs, (cpVect){1600.0, 800.0}, BoxMissileLauncher);
create_hard_shell_station(gs, (cpVect){-2500.0, 200.0}, BoxMerge);
create_hard_shell_station(gs, (cpVect){-7000.0, 200.0}, BoxMerge);
#else
Log("Creating debug world\n");
// pos, mass, radius
@ -2255,12 +2289,34 @@ void exit_seat(GameState *gs, Entity *seat_in, Entity *p)
cpBodySetVelocity(p->body, cpBodyGetVelocity(box_grid(seat_in)->body));
}
void shape_integrity_check(cpShape *shape, void *data)
{
flight_assert(cpShapeGetUserData(shape) != NULL);
flight_assert(cp_shape_entity(shape)->exists);
flight_assert(cp_shape_entity(shape)->shape == shape);
}
void body_integrity_check(cpBody *body, void *data)
{
flight_assert(cpBodyGetUserData(body) != NULL);
flight_assert(cp_body_entity(body)->exists);
flight_assert(cp_body_entity(body)->body == body);
}
void process(struct GameState *gs, double dt)
{
PROFILE_SCOPE("Gameplay processing")
{
flight_assert(gs->space != NULL);
#ifdef CHIPMUNK_INTEGRITY_CHECK
PROFILE_SCOPE("Chipmunk Integrity Checks")
{
cpSpaceEachShape(gs->space, shape_integrity_check, NULL);
cpSpaceEachBody(gs->space, body_integrity_check, NULL);
}
#endif
gs->tick++;
PROFILE_SCOPE("sun gravity")
@ -2362,13 +2418,6 @@ void process(struct GameState *gs, double dt)
#ifdef INFINITE_RESOURCES
p->damage = 0.0;
#endif
// update gold win condition
if (cpvlength(cpvsub((cpBodyGetPosition(p->body)), gs->goldpos)) < GOLD_COLLECT_RADIUS)
{
p->goldness += 0.1;
p->damage = 0.0;
gs->goldpos = (cpVect){.x = hash11((float)elapsed_time(gs)) * 20.0, .y = hash11((float)elapsed_time(gs) - 13.6) * 20.0};
}
#if 1
cpVect world_hand_pos = get_world_hand_pos(gs, &player->input, p);
if (player->input.seat_action)
@ -2386,10 +2435,12 @@ void process(struct GameState *gs, double dt)
if (potential_seat->box_type == BoxScanner) // learn everything from the scanner
{
flight_assert(box_interactible(potential_seat->box_type));
player->box_unlocks |= potential_seat->blueprints_learned;
}
if (potential_seat->box_type == BoxMerge) // disconnect!
{
flight_assert(box_interactible(potential_seat->box_type));
potential_seat->wants_disconnect = true;
grid_correct_for_holes(gs, box_grid(potential_seat));
flight_assert(potential_seat->exists);
@ -2398,6 +2449,7 @@ void process(struct GameState *gs, double dt)
}
if (potential_seat->box_type == BoxCockpit || potential_seat->box_type == BoxMedbay)
{
flight_assert(box_interactible(potential_seat->box_type));
// don't let players get inside of cockpits that somebody else is already inside of
if (get_entity(gs, potential_seat->player_who_is_inside_of_me) == NULL)
{
@ -2811,6 +2863,10 @@ void process(struct GameState *gs, double dt)
{
double energy_to_consume = cur_box->wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt;
if (cur_box->wanted_thrust == 0.0)
{
cur_box->thrust = 0.0;
}
if (energy_to_consume > 0.0)
{
cur_box->thrust = 0.0;
@ -2822,12 +2878,29 @@ void process(struct GameState *gs, double dt)
}
if (cur_box->box_type == BoxGyroscope)
{
double energy_to_consume = fabs(cur_box->wanted_thrust * GYROSCOPE_ENERGY_USED_PER_SECOND * dt);
cur_box->gyrospin_velocity = lerp(cur_box->gyrospin_velocity, cur_box->thrust * 20.0, dt * 5.0);
cur_box->gyrospin_angle += cur_box->gyrospin_velocity * dt;
if (cur_box->gyrospin_angle > 2.0 * PI)
{
cur_box->gyrospin_angle -= 2.0 * PI;
}
if (cur_box->gyrospin_angle < -2.0 * PI)
{
cur_box->gyrospin_angle += 2.0 * PI;
}
if (cur_box->wanted_thrust == 0.0)
{
cur_box->thrust = 0.0;
}
double thrust_to_want = cur_box->wanted_thrust;
if (cur_box->wanted_thrust == 0.0)
thrust_to_want = clamp(-cpBodyGetAngularVelocity(grid->body) * GYROSCOPE_PROPORTIONAL_INERTIAL_RESPONSE, -1.0, 1.0);
double energy_to_consume = fabs(thrust_to_want * GYROSCOPE_ENERGY_USED_PER_SECOND * dt);
if (energy_to_consume > 0.0)
{
cur_box->thrust = 0.0;
double energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, energy_to_consume);
cur_box->thrust = (1.0 - energy_unconsumed / energy_to_consume) * cur_box->wanted_thrust;
cur_box->thrust = (1.0 - energy_unconsumed / energy_to_consume) * thrust_to_want;
if (fabs(cur_box->thrust) >= 0.0)
cpBodySetTorque(grid->body, cpBodyGetTorque(grid->body) + cur_box->thrust * GYROSCOPE_TORQUE);
}
@ -2855,15 +2928,23 @@ void process(struct GameState *gs, double dt)
else
{
cur_box->cloaking_power = lerp(cur_box->cloaking_power, 1.0, dt * 3.0);
cpBody *tmp = cpBodyNew(0.0, 0.0);
cpBodySetPosition(tmp, (entity_pos(cur_box)));
cpBodySetAngle(tmp, entity_rotation(cur_box));
// subtract a little from the panel size so that boxes just at the boundary of the panel
// aren't (sometimes cloaked)/(sometimes not) from floating point imprecision
cpShape *box_shape = cpBoxShapeNew(tmp, CLOAKING_PANEL_SIZE - 0.03, CLOAKING_PANEL_SIZE - 0.03, 0.0);
cpSpaceShapeQuery(gs->space, box_shape, cloaking_shield_callback_func, (void *)cur_box);
cpShapeFree(box_shape);
cpBodyFree(tmp);
rect_query(gs->space, (BoxCentered){
.pos = entity_pos(cur_box),
.rotation = entity_rotation(cur_box),
// subtract a little from the panel size so that boxes just at the boundary of the panel
// aren't (sometimes cloaked)/(sometimes not) from floating point imprecision
.size = cpv(CLOAKING_PANEL_SIZE - 0.03, CLOAKING_PANEL_SIZE - 0.03),
});
QUEUE_ITER(&query_result, QueryResult, res)
{
cpShape *shape = res->shape;
Entity *from_cloaking_box = cur_box;
GameState *gs = entitys_gamestate(from_cloaking_box);
Entity *to_cloak = cp_shape_entity(shape);
to_cloak->time_was_last_cloaked = elapsed_time(gs);
to_cloak->last_cloaked_by_squad = from_cloaking_box->owning_squad;
}
}
}
if (cur_box->box_type == BoxMissileLauncher)
@ -2883,8 +2964,7 @@ void process(struct GameState *gs, double dt)
Entity *new_missile = new_entity(gs);
create_missile(gs, new_missile);
new_missile->owning_squad = cur_box->owning_squad; // missiles have teams and attack eachother!
double missile_spawn_dist = sqrt((BOX_SIZE / 2.0) * (BOX_SIZE / 2.0) * 2.0) + MISSILE_COLLIDER_SIZE.x / 2.0 + 0.1;
cpBodySetPosition(new_missile->body, (cpvadd(entity_pos(cur_box), cpvspin((cpVect){.x = missile_spawn_dist, 0.0}, target.facing_angle))));
cpBodySetPosition(new_missile->body, (cpvadd(entity_pos(cur_box), cpvspin((cpVect){.x = MISSILE_SPAWN_DIST, 0.0}, target.facing_angle))));
cpBodySetAngle(new_missile->body, target.facing_angle);
cpBodySetVelocity(new_missile->body, (box_vel(cur_box)));
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 B

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 B

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 B

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 B

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

178
main.c

@ -46,6 +46,10 @@ static int my_player_index = -1;
static bool right_mouse_down = false;
#define MAX_KEYDOWN SAPP_KEYCODE_MENU
static bool keydown[MAX_KEYDOWN] = {0};
static bool piloting_rotation_capable_ship = false;
static double rotation_learned = 0.0;
static double rotation_in_cockpit_learned = 0.0;
static double zoomeasy_learned = 0.0;
typedef struct KeyPressed
{
bool pressed;
@ -55,6 +59,10 @@ static KeyPressed keypressed[MAX_KEYDOWN] = {0};
static cpVect mouse_pos = {0};
static bool fullscreened = false;
static bool picking_new_boxtype = false;
static double exec_time = 0.0; // cosmetic bouncing, network stats
// for network statistics, printed to logs with F3
static uint64_t total_bytes_sent = 0;
static uint64_t total_bytes_received = 0;
static bool build_pressed = false;
static double dilating_time_factor = 1.0;
@ -112,10 +120,15 @@ static sg_image image_itemswitch;
static sg_image image_cloaking_panel;
static sg_image image_missile;
static sg_image image_missile_burning;
static sg_image image_rightclick;
static sg_image image_rothelp;
static sg_image image_zoomeasyhelp;
static sg_image image_gyrospin;
static enum BoxType toolbar[TOOLBAR_SLOTS] = {
BoxHullpiece,
BoxThruster,
BoxGyroscope,
BoxBattery,
BoxCockpit,
BoxMedbay,
@ -477,7 +490,7 @@ void draw_textured_rect(double x, double y, double w, double h)
static void init(void)
{
fopen_s(&log_file, "astris_log.txt", "a");
Log("Another day, another game of astris!\n");
Log("Another day, another game of astris! Git release tag %d\n", GIT_RELEASE_TAG);
queue_init(&packets_to_play, sizeof(OpusPacket), packets_to_play_data,
ARRLEN(packets_to_play_data));
queue_init(&packets_to_send, sizeof(OpusPacket), packets_to_send_data,
@ -634,6 +647,10 @@ static void init(void)
image_cloaking_panel = load_image("loaded/cloaking_panel.png");
image_missile_burning = load_image("loaded/missile_burning.png");
image_missile = load_image("loaded/missile.png");
image_rightclick = load_image("loaded/right_click.png");
image_rothelp = load_image("loaded/rothelp.png");
image_gyrospin = load_image("loaded/gyroscope_spinner.png");
image_zoomeasyhelp = load_image("loaded/zoomeasyhelp.png");
}
// socket initialization
@ -791,6 +808,43 @@ static void ui(bool draw, double dt, double width, double height)
if (draw)
sgp_push_transform();
// helpers
if (draw)
{
// rotation
{
double alpha = 1.0 - clamp01(rotation_learned);
if (piloting_rotation_capable_ship)
alpha = 1.0 - clamp01(rotation_in_cockpit_learned);
set_color_values(1.0, 1.0, 1.0, alpha);
sgp_set_image(0, image_rothelp);
cpVect draw_at = cpv(width / 2.0, height * 0.25);
transform_scope
{
scale_at(1.0, -1.0, draw_at.x, draw_at.y);
pipeline_scope(goodpixel_pipeline)
draw_texture_centered(draw_at, 200.0);
sgp_reset_image(0);
}
}
// zooming zoomeasy
{
double alpha = 1.0 - clamp01(zoomeasy_learned);
set_color_values(1.0, 1.0, 1.0, alpha);
sgp_set_image(0, image_zoomeasyhelp);
cpVect draw_at = cpv(width * 0.1, height * 0.5);
transform_scope
{
scale_at(1.0, -1.0, draw_at.x, draw_at.y);
pipeline_scope(goodpixel_pipeline)
draw_texture_centered(draw_at, 200.0);
sgp_reset_image(0);
}
}
}
// draw pick new box type menu
static double pick_opacity = 0.0;
{
@ -940,10 +994,11 @@ static void ui(bool draw, double dt, double width, double height)
{
pipeline_scope(hueshift_pipeline)
{
set_color_values(1.0, 1.0, 1.0, 1.0);
sgp_set_image(0, image_squad_invite);
setup_hueshift(draw_as_squad);
scale_at(1.0, -1.0, x,
invite_y); // images upside down by default :(
sgp_set_image(0, image_squad_invite);
draw_texture_centered((cpVect){x, invite_y}, size);
sgp_reset_image(0);
}
@ -1372,9 +1427,8 @@ static void draw_dots(cpVect camera_pos, double gap)
void apply_this_tick_of_input_to_player(uint64_t tick_to_search_for)
{
InputFrame *to_apply = NULL;
QUEUE_ITER(&input_queue, cur_header)
QUEUE_ITER(&input_queue, InputFrame, cur)
{
InputFrame *cur = (InputFrame *)cur_header->data;
if (cur->tick == tick(&gs))
{
to_apply = cur;
@ -1411,8 +1465,8 @@ static void frame(void)
PROFILE_SCOPE("frame")
{
double width = (float)sapp_width(), height = (float)sapp_height();
double exec_time = sapp_frame_count() * sapp_frame_duration();
double dt = sapp_frame_duration();
exec_time += dt;
// pressed input management
{
@ -1462,6 +1516,7 @@ static void frame(void)
case ENET_EVENT_TYPE_RECEIVE:
{
total_bytes_received += event.packet->dataLength;
unsigned char *decompressed = malloc(
sizeof *decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
size_t decompressed_max_len = MAX_SERVER_TO_CLIENT;
@ -1602,6 +1657,26 @@ static void frame(void)
local_hand_pos = cpvsub(global_hand_pos, entity_pos(myentity()));
}
// for tutorial text
piloting_rotation_capable_ship = false;
if (myentity() != NULL)
{
Entity *inside_of = get_entity(&gs, myentity()->currently_inside_of_box);
if (inside_of != NULL && inside_of->box_type == BoxCockpit)
{
BOXES_ITER(&gs, cur, box_grid(inside_of))
{
flight_assert(cur->is_box);
if (cur->box_type == BoxGyroscope)
{
piloting_rotation_capable_ship = true;
break;
}
}
}
}
// process player interaction (squad invites)
if (interact_pressed && myplayer() != NULL && myplayer()->squad != SquadNone)
ENTITIES_ITER(cur)
@ -1631,7 +1706,15 @@ static void frame(void)
if (cpvlength(input) > 0.0)
input = cpvnormalize(input);
cur_input_frame.movement = input;
cur_input_frame.rotation = (float)keydown[SAPP_KEYCODE_E] - (float)keydown[SAPP_KEYCODE_Q];
cur_input_frame.rotation = -((float)keydown[SAPP_KEYCODE_E] - (float)keydown[SAPP_KEYCODE_Q]);
if (fabs(cur_input_frame.rotation) > 0.01f)
{
if (piloting_rotation_capable_ship)
rotation_in_cockpit_learned += dt * 0.35;
else
rotation_learned += dt * 0.35;
}
if (interact_pressed)
cur_input_frame.seat_action = interact_pressed;
@ -1659,7 +1742,7 @@ static void frame(void)
reject_invite = false;
}
if (build_pressed && currently_building() != BoxInvalid)
if (build_pressed && !hand_at_arms_length && currently_building() != BoxInvalid)
{
cur_input_frame.dobuild = build_pressed;
cur_input_frame.build_type = currently_building();
@ -1744,6 +1827,10 @@ static void frame(void)
Log("Failed to send packet error %d\n", err);
enet_packet_destroy(packet);
}
else
{
total_bytes_sent += packet->dataLength;
}
last_sent_input_time = stm_now();
}
else
@ -1760,8 +1847,9 @@ static void frame(void)
global_hand_pos =
get_global_hand_pos(world_mouse_pos, &hand_at_arms_length);
Entity *placing_grid = box_grid(closest_box_to_point_in_radius(
&gs, global_hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP, NULL));
Entity *nearest_box = closest_box_to_point_in_radius(&gs, global_hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP, NULL);
Entity *placing_grid = box_grid(nearest_box);
if (placing_grid == NULL)
{
build_preview = (struct BuildPreviewInfo){
@ -1886,15 +1974,14 @@ static void frame(void)
// building preview
if (currently_building() != BoxInvalid && can_build(currently_building()))
{
set_color_values(0.5, 0.5, 0.5,
(sin((float)exec_time * 9.0) + 1.0) / 3.0 + 0.2);
transform_scope
{
sgp_set_image(0, boxinfo(currently_building()).image);
rotate_at(build_preview.grid_rotation +
rotangle(cur_editing_rotation),
global_hand_pos.x, global_hand_pos.y);
sgp_set_image(0, boxinfo(currently_building()).image);
set_color_values(0.5, 0.5, 0.5, (sin((float)exec_time * 9.0) + 1.0) / 3.0 + 0.2);
pipeline_scope(goodpixel_pipeline)
draw_texture_centered(global_hand_pos, BOX_SIZE);
// drawbox(hand_pos, build_preview.grid_rotation, 0.0,
@ -1903,7 +1990,7 @@ static void frame(void)
}
}
double player_scaling_target = zoom < 6.5 ? 100.0 : 1.0;
double player_scaling_target = zoom < 6.5 ? PLAYER_BIG_SCALING/zoom : 1.0;
if (zoom > 50.0)
player_scaling = player_scaling_target; // For press tab zoom shortcut. Bad hack to make zooming in not jarring with the bigger player. Comment this out and press tab to see!
player_scaling = lerp(player_scaling, player_scaling_target, dt * 15.0);
@ -1971,6 +2058,33 @@ static void frame(void)
*/
}
if (box_interactible(b->box_type))
{
if (box_has_point((BoxCentered){
.pos = entity_pos(b),
.rotation = entity_rotation(b),
.size = (cpVect){BOX_SIZE, BOX_SIZE},
},
world_mouse_pos))
{
// set_color_values(1.0, 1.0, 1.0, 0.2);
// draw_color_rect_centered(entity_pos(b), BOX_SIZE);
set_color(WHITE);
draw_circle(entity_pos(b), BOX_SIZE / 1.75 + sin(exec_time * 5.0) * BOX_SIZE * 0.1);
transform_scope
{
pipeline_scope(goodpixel_pipeline)
{
sgp_set_image(0, image_rightclick);
cpVect draw_at = cpvadd(entity_pos(b), cpv(BOX_SIZE, 0));
rotate_at(-entity_rotation(b) - rotangle(b->compass_rotation), draw_at.x, draw_at.y);
draw_texture_centered(draw_at, BOX_SIZE + sin(exec_time * 5.0) * BOX_SIZE * 0.1);
sgp_reset_image(0);
}
}
}
}
sgp_set_image(0, img);
if (b->indestructible)
{
@ -2006,11 +2120,27 @@ static void frame(void)
pipeline_scope(goodpixel_pipeline)
{
rotate_at(b->scanner_head_rotate, entity_pos(b).x, entity_pos(b).y);
set_color(WHITE);
draw_texture_centered(entity_pos(b), BOX_SIZE);
}
}
sgp_reset_image(0);
}
if (b->box_type == BoxGyroscope)
{
sgp_set_image(0, image_gyrospin);
transform_scope
{
pipeline_scope(goodpixel_pipeline)
{
set_color(WHITE);
rotate_at(b->gyrospin_angle, entity_pos(b).x, entity_pos(b).y);
draw_texture_centered(entity_pos(b), BOX_SIZE);
}
}
sgp_reset_image(0);
set_color(WHITE);
}
// scanner range, visualizes what scanner can scan
@ -2123,10 +2253,6 @@ static void frame(void)
}
}
// gold target
set_color(GOLD);
draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1, 0.1);
// instant death
set_color(RED);
draw_circle((cpVect){0}, INSTANT_DEATH_DISTANCE_FROM_CENTER);
@ -2139,8 +2265,14 @@ static void frame(void)
translate(entity_pos(i.sun).x, entity_pos(i.sun).y);
set_color(WHITE);
sgp_set_image(0, image_sun);
draw_texture_centered((cpVect){0}, i.sun->sun_radius * 2.0);
draw_texture_centered((cpVect){0}, i.sun->sun_radius * 8.0);
sgp_reset_image(0);
#ifdef DEBUG_RENDERING
// so you can make sure the sprite is the right size to accurately show when it will burn you
set_color(RED);
draw_circle(cpv(0, 0), i.sun->sun_radius);
#endif
// can draw at 0,0 because everything relative to sun now!
@ -2220,8 +2352,16 @@ void event(const sapp_event *e)
mouse_frozen = !mouse_frozen;
}
#endif
if (e->key_code == SAPP_KEYCODE_F3)
{
// print statistics
double received_per_sec = (double)total_bytes_received / exec_time;
double sent_per_sec = (double)total_bytes_sent / exec_time;
Log("Byte/s received %d byte/s sent %d\n", (int)received_per_sec, (int)sent_per_sec);
}
if (e->key_code == SAPP_KEYCODE_TAB)
{
zoomeasy_learned += 0.2;
if (zoom_target < DEFAULT_ZOOM)
{
zoom_target = DEFAULT_ZOOM;

@ -25,7 +25,7 @@ typedef struct Queue
#define QUEUE_SIZE_FOR_ELEMENTS(element_size, max_elements) ((sizeof(QueueElementHeader) + element_size) * max_elements)
// oldest to newest
#define QUEUE_ITER(q_ptr, cur_header) for (QueueElementHeader *cur_header = (q_ptr)->next; cur_header != NULL; cur_header = cur_header->next)
#define QUEUE_ITER(q_ptr, type, cur) for (QueueElementHeader *cur_header = (q_ptr)->next; cur_header != NULL; cur_header = cur_header->next) for(type *cur = (type*)cur_header->data; cur != NULL; cur = NULL)
void queue_init(Queue *q, size_t element_size, char *data, size_t data_length);
void queue_clear(Queue *q);
void *queue_push_element(Queue *q);

@ -312,9 +312,8 @@ void server(void *info_raw)
CONNECTED_PEERS(enet_host, cur)
{
int this_player_index = (int)(int64_t)cur->data;
QUEUE_ITER(&player_input_queues[this_player_index], cur_header)
QUEUE_ITER(&player_input_queues[this_player_index], InputFrame, cur)
{
InputFrame *cur = (InputFrame *)cur_header->data;
if (cur->tick == tick(&gs))
{
gs.players[this_player_index].input = *cur;

@ -14,7 +14,7 @@ WinActivate, flightbuild
If WinActive("flightbuild")
{
Send, {Enter}
Send, remedybg continue-execution && timeout 1 && remedybg.exe stop-debugging && msbuild && remedybg.exe start-debugging {Enter}
Send, remedybg continue-execution && timeout 1 && remedybg.exe stop-debugging && shadergen.bat && msbuild && remedybg.exe start-debugging {Enter}
}
Send, {Blind} ; So it doesn't hold down ctrl after running! WTF
return

@ -3,7 +3,7 @@
#include "buildsettings.h"
#define MAX_BOX_TYPES 64
#define ZOOM_MIN 0.10 // smaller means you can zoom out more
#define ZOOM_MIN 0.04 // smaller means you can zoom out more
#define ZOOM_MAX 1500.0 // bigger means you can zoom in more
#define MAX_PLAYERS 16
#define MAX_SUNS 8
@ -29,9 +29,10 @@
// centered on the sprite
#define MISSILE_SPRITE_SIZE ((cpVect){.x = BOX_SIZE, .y = BOX_SIZE})
#define MISSILE_COLLIDER_SIZE ((cpVect){.x = BOX_SIZE * 0.5f, .y = BOX_SIZE * 0.5f})
// distance at which things become geostationary and no more solar power!
#define MISSILE_SPAWN_DIST (sqrt((BOX_SIZE / 2.0) * (BOX_SIZE / 2.0) * 2.0) + MISSILE_COLLIDER_SIZE.x / 2.0 + 0.1)
#define PLAYER_JETPACK_ROTATION_ENERGY_PER_SECOND 0.2f
#define PLAYER_JETPACK_SPICE_PER_SECOND 0.08f
#define PLAYER_BIG_SCALING 300.0
#define SCANNER_ENERGY_USE 0.05f
#define MAX_HAND_REACH 1.0f
#define SCANNER_SCAN_RATE 0.5f
@ -43,15 +44,16 @@
#define THRUSTER_FORCE 24.0f
#define THRUSTER_ENERGY_USED_PER_SECOND 0.005f
#define GYROSCOPE_ENERGY_USED_PER_SECOND 0.005f
#define GYROSCOPE_TORQUE 0.5f
#define GYROSCOPE_TORQUE 1.5f
#define GYROSCOPE_PROPORTIONAL_INERTIAL_RESPONSE 0.7 // between 0-1. How strongly responds to rotation, to stop the rotation
#define CLOAKING_ENERGY_USE 0.1f
#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
#define GRAVITY_CONSTANT 0.1f
#define GRAVITY_CONSTANT 0.05f
#define GRAVITY_SMALLEST 0.01f // used to determine when gravity is clamped to 0.0f
#define INSTANT_DEATH_DISTANCE_FROM_CENTER 4000.0f
#define INSTANT_DEATH_DISTANCE_FROM_CENTER 10000.0f
#define SOLAR_ENERGY_PER_SECOND 0.09f
#define DAMAGE_TO_PLAYER_PER_BLOCK 0.1f
#define BATTERY_CAPACITY 1.5f
@ -60,7 +62,9 @@
#define EXPLOSION_DAMAGE_PER_SEC 10.0f
#define EXPLOSION_DAMAGE_THRESHOLD 0.2f // how much damage until it explodes
#define GOLD_UNLOCK_RADIUS 1.0f
#ifndef TIME_BETWEEN_WORLD_SAVE
#define TIME_BETWEEN_WORLD_SAVE 30.0f
#endif
#define MISSILE_EXPLOSION_PUSH 2.5f
#define MISSILE_EXPLOSION_RADIUS 0.4f
@ -102,7 +106,7 @@
#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 <math.h> // sqrt and cos vector functions
#include <math.h> // sqrt and cos vector functions
#include <stdint.h> // tick is unsigned integer
#include <stdio.h> // logging on errors for functions
@ -168,6 +172,20 @@ enum BoxType
BoxLast,
};
static inline bool box_interactible(enum BoxType type)
{
enum BoxType types[] = {
BoxCockpit,
BoxMedbay,
BoxMerge,
BoxScanner,
};
for (int i = 0; i < ARRLEN(types); i++)
if (types[i] == type)
return true;
return false;
}
enum CompassRotation
{
Right,
@ -196,7 +214,7 @@ 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);
}
// when updated, must update serialization, comparison in main.c, and the server
@ -297,6 +315,10 @@ typedef struct Entity
// can mean rotation thrust!
double wanted_thrust; // the thrust command applied to the thruster
double thrust; // the actual thrust it can provide based on energy sources in the grid
// only gyroscope, velocity not serialized. Cosmetic
double gyrospin_angle;
double gyrospin_velocity;
// only serialized when box_type is battery
double energy_used; // battery, between 0 battery capacity. You have to look through code to figure out what that is! haha sucker!
@ -309,9 +331,9 @@ typedef struct Entity
// scanner only stuff!
EntityID currently_scanning;
double currently_scanning_progress; // when 1.0, scans it!
double currently_scanning_progress; // when 1.0, scans it!
BOX_UNLOCKS_TYPE blueprints_learned;
double scanner_head_rotate_speed; // not serialized, cosmetic
double scanner_head_rotate_speed; // not serialized, cosmetic
double scanner_head_rotate;
cpVect platonic_nearest_direction; // normalized
double platonic_detection_strength; // from zero to one
@ -345,8 +367,6 @@ typedef struct GameState
uint64_t tick;
double subframe_time; // @Robust remove this, I don't think it's used anymore
cpVect goldpos;
Player players[MAX_PLAYERS];
EntityID suns[MAX_SUNS]; // can't have holes in it for serialization
@ -535,6 +555,19 @@ static inline double cpvangle(cpVect vec)
return atan2(vec.y, vec.x);
}
typedef struct BoxCentered
{
cpVect pos;
double rotation;
cpVect size; // half width and half height, centered on position
} BoxCentered;
static inline bool box_has_point(BoxCentered box, cpVect point)
{
cpVect local_point = cpvspin(cpvsub(point, box.pos), -box.rotation);
return has_point((AABB){.x = -box.size.x/2.0, .y = -box.size.y/2.0, .width = box.size.x, .height = box.size.y}, local_point);
}
static double sign(double f)
{
if (f >= 0.0f)

Loading…
Cancel
Save