diff --git a/.gitignore b/.gitignore
index e6a0f40..9fd6d7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+# profiling results
+*.spall
compile_commands.json
.cache/
enc_temp_folder/
diff --git a/Flight.vcxproj b/Flight.vcxproj
index 8645300..539277f 100644
--- a/Flight.vcxproj
+++ b/Flight.vcxproj
@@ -58,6 +58,7 @@
true
v143
Unicode
+ false
Application
@@ -140,7 +141,7 @@
Level3
true
- _DEBUG;_CONSOLE;%(PreprocessorDefinitions);DEBUG_RENDERING
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions);DEBUG_RENDERING;SERVER_ADDRESS="127.0.0.1"
true
$(ProjectDir)\thirdparty\enet\include;$(ProjectDir)\thirdparty\Chipmunk2D\include\chipmunk;$(ProjectDir)\thirdparty;$(ProjectDir)\thirdparty\Chipmunk2D\include;$(ProjectDir)\thirdparty\minilzo;$(ProjectDir)\thirdparty\opus\include
true
diff --git a/gamestate.c b/gamestate.c
index 3c39378..b575c9b 100644
--- a/gamestate.c
+++ b/gamestate.c
@@ -3,6 +3,8 @@
#include "queue.h"
#include "stdbool.h"
#include "types.h"
+#define PROFILING_IMPL
+#include "profiling.h"
#include "ipsettings.h" // debug/developer settings
@@ -73,7 +75,7 @@ bool cloaking_active(GameState *gs, Entity *e)
{
// cloaking doesn't work for first 1/2 second of game because when initializing
// everything needs to be uncloaked
- return gs->time >= 0.5 && (gs->time - e->time_was_last_cloaked) <= TIMESTEP * 2.0;
+ return time(gs) >= 0.5 && (time(gs) - e->time_was_last_cloaked) <= TIMESTEP * 2.0;
}
bool is_cloaked(GameState *gs, Entity *e, Entity *this_players_perspective)
@@ -391,7 +393,6 @@ void grid_create(GameState *gs, Entity *e)
create_body(gs, e);
}
-
void entity_set_rotation(Entity *e, double rot)
{
assert(e->body != NULL);
@@ -1339,7 +1340,9 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
SER_ASSERT(cur_next_entity <= ser->max_entity_index);
SER_VAR(&s->your_player);
- SER_VAR(&gs->time);
+
+ SER_VAR(&gs->tick);
+ SER_VAR(&gs->subframe_time);
SER_MAYBE_RETURN(ser_V2(ser, &gs->goldpos));
@@ -1661,7 +1664,7 @@ static void cloaking_shield_callback_func(cpShape *shape, cpContactPointSet *poi
GameState *gs = entitys_gamestate(from_cloaking_box);
Entity *to_cloak = cp_shape_entity(shape);
- to_cloak->time_was_last_cloaked = gs->time;
+ to_cloak->time_was_last_cloaked = time(gs);
to_cloak->last_cloaked_by_squad = from_cloaking_box->owning_squad;
}
@@ -1801,7 +1804,13 @@ cpVect thruster_force(Entity *box)
uint64_t tick(GameState *gs)
{
- return (uint64_t)floor(gs->time / ((double)TIMESTEP));
+ // return (uint64_t)floor(gs->time / ((double)TIMESTEP));
+ return gs->tick;
+}
+
+double time(GameState *gs)
+{
+ return ((double)gs->tick * TIMESTEP) + gs->subframe_time;
}
Entity *grid_to_build_on(GameState *gs, cpVect world_hand_pos)
@@ -2110,693 +2119,732 @@ void exit_seat(GameState *gs, Entity *seat_in, Entity *p)
cpBodySetVelocity(p->body, cpBodyGetVelocity(box_grid(seat_in)->body));
}
-void process_fixed_timestep(GameState *gs)
+void process(struct GameState *gs, double dt, bool is_subframe)
{
- process(gs, TIMESTEP);
-}
-
-void process(GameState *gs, double dt)
-{
- assert(gs->space != NULL);
-
- gs->time += dt;
-
- // process sun gravity
- SUNS_ITER(gs)
+ PROFILE_SCOPE("Gameplay processing")
{
- Entity *from_sun = i.sun;
- cpVect accel = {0};
- SUNS_ITER(gs)
- {
- Entity *other_sun = i.sun;
- if (other_sun != from_sun)
- {
- accel = cpvadd(accel, sun_gravity_accel_for_entity(from_sun, other_sun));
- }
- }
-#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));
+ assert(gs->space != NULL);
- if (cpvlength(from_sun->sun_pos) >= INSTANT_DEATH_DISTANCE_FROM_CENTER)
+ PROFILE_SCOPE("subframe stuff")
{
- 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
- }
- // process input
- PLAYERS_ITER(gs->players, player)
- {
- if (player->input.take_over_squad >= 0)
- {
- if (player->input.take_over_squad == SquadNone)
- {
- player->squad = SquadNone;
- }
- else
+ if (is_subframe)
{
- bool squad_taken = false;
- PLAYERS_ITER(gs->players, other_player)
+ gs->subframe_time += dt;
+ while (gs->subframe_time > TIMESTEP)
{
- if (other_player->squad == player->input.take_over_squad)
- {
- squad_taken = true;
- break;
- }
+ gs->subframe_time -= TIMESTEP;
+ gs->tick++;
}
- if (!squad_taken)
- player->squad = player->input.take_over_squad;
}
- player->input.take_over_squad = -1;
- }
-
- // squad invites
- Entity *possibly_to_invite = get_entity(gs, player->input.invite_this_player);
- if (player->input.invite_this_player.generation > 0)
- player->input.invite_this_player = (EntityID){0}; // just in case
- if (player->squad != SquadNone && possibly_to_invite != NULL && possibly_to_invite->is_player)
- {
- possibly_to_invite->squad_invited_to = player->squad;
- }
- Entity *p = get_entity(gs, player->entity);
- // player respawning
- if (p == NULL)
- {
- p = new_entity(gs);
- create_player_entity(gs, p);
- player->entity = get_id(gs, p);
- Entity *medbay = get_entity(gs, player->last_used_medbay);
- entity_ensure_in_orbit(gs, p);
- if (medbay != NULL)
- {
- exit_seat(gs, medbay, p);
- p->damage = 0.95;
- }
- }
- assert(p->is_player);
- p->owning_squad = player->squad;
-
- if (p->squad_invited_to != SquadNone)
- {
- if (player->input.accept_cur_squad_invite)
- {
- player->squad = p->squad_invited_to;
- p->squad_invited_to = SquadNone;
- player->input.accept_cur_squad_invite = false;
- }
- if (player->input.reject_cur_squad_invite)
+ else
{
- p->squad_invited_to = SquadNone;
- player->input.reject_cur_squad_invite = false;
+ assert(gs->subframe_time == 0.0);
+ gs->tick++;
}
}
-#ifdef INFINITE_RESOURCES
- p->damage = 0.0;
-#endif
- // update gold win condition
- if (cpvlength(cpvsub((cpBodyGetPosition(p->body)), gs->goldpos)) < GOLD_COLLECT_RADIUS)
+ PROFILE_SCOPE("sun gravity")
{
- p->goldness += 0.1;
- p->damage = 0.0;
- gs->goldpos = (cpVect){.x = hash11((float)gs->time) * 20.0, .y = hash11((float)gs->time - 13.6) * 20.0};
- }
-#if 1
- cpVect world_hand_pos = get_world_hand_pos(gs, &player->input, p);
- if (player->input.seat_action)
- {
- player->input.seat_action = false; // "handle" the input
- Entity *seat_maybe_in = get_entity(gs, p->currently_inside_of_box);
- if (seat_maybe_in == NULL) // not in any seat
+ SUNS_ITER(gs)
{
- cpPointQueryInfo query_info = {0};
- cpShape *result = cpSpacePointQueryNearest(gs->space, (world_hand_pos), 0.1, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &query_info);
- if (result != NULL)
+ Entity *from_sun = i.sun;
+ cpVect accel = {0};
+ SUNS_ITER(gs)
{
- Entity *potential_seat = cp_shape_entity(result);
- assert(potential_seat->is_box);
-
- if (potential_seat->box_type == BoxScanner) // learn everything from the scanner
+ Entity *other_sun = i.sun;
+ if (other_sun != from_sun)
{
- player->box_unlocks |= potential_seat->blueprints_learned;
- }
- if (potential_seat->box_type == BoxMerge) // disconnect!
- {
- potential_seat->wants_disconnect = true;
- grid_correct_for_holes(gs, box_grid(potential_seat));
- assert(potential_seat->exists);
- assert(potential_seat->is_box);
- assert(potential_seat->box_type == BoxMerge);
- }
- if (potential_seat->box_type == BoxCockpit || potential_seat->box_type == BoxMedbay) // @Robust check by feature flag instead of 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)
- {
- p->currently_inside_of_box = get_id(gs, potential_seat);
- potential_seat->player_who_is_inside_of_me = get_id(gs, p);
- if (potential_seat->box_type == BoxMedbay)
- player->last_used_medbay = p->currently_inside_of_box;
- }
+ accel = cpvadd(accel, sun_gravity_accel_for_entity(from_sun, other_sun));
}
}
- else
+#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)
{
- Log("No seat to get into for a player at point %f %f\n", world_hand_pos.x, world_hand_pos.y);
+ 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);
}
- }
- else
- {
- exit_seat(gs, seat_maybe_in, p);
- seat_maybe_in->player_who_is_inside_of_me = (EntityID){0};
- p->currently_inside_of_box = (EntityID){0};
+#endif
}
}
-#endif
- // process movement
+ PROFILE_SCOPE("input processing")
{
- // no cheating by making movement bigger than length 1
- cpVect movement_this_tick = (cpVect){0};
- double rotation_this_tick = 0.0;
- if (cpvlength(player->input.movement) > 0.0)
- {
- movement_this_tick = cpvmult(cpvnormalize(player->input.movement), clamp(cpvlength(player->input.movement), 0.0, 1.0));
- player->input.movement = (cpVect){0};
- }
- if (fabs(player->input.rotation) > 0.0)
- {
- rotation_this_tick = player->input.rotation;
- if (rotation_this_tick > 1.0)
- rotation_this_tick = 1.0;
- if (rotation_this_tick < -1.0)
- rotation_this_tick = -1.0;
- player->input.rotation = 0.0;
- }
- Entity *seat_inside_of = get_entity(gs, p->currently_inside_of_box);
-
- // strange rare bug I saw happen, related to explosives, but no idea how to
- // reproduce. @Robust put a breakpoint here, reproduce, and fix it!
- if (seat_inside_of != NULL && !seat_inside_of->is_box)
- {
- Log("Strange thing happened where player was in non box seat!\n");
- seat_inside_of = NULL;
- p->currently_inside_of_box = (EntityID){0};
- }
- if (seat_inside_of == NULL)
+ PLAYERS_ITER(gs->players, player)
{
- cpShapeSetFilter(p->shape, PLAYER_SHAPE_FILTER);
- cpBodyApplyForceAtWorldPoint(p->body, (cpvmult(movement_this_tick, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body));
- cpBodySetTorque(p->body, rotation_this_tick * PLAYER_JETPACK_TORQUE);
- p->damage += cpvlength(movement_this_tick) * dt * PLAYER_JETPACK_SPICE_PER_SECOND;
- p->damage += fabs(rotation_this_tick) * dt * PLAYER_JETPACK_ROTATION_ENERGY_PER_SECOND;
- }
- else
- {
- assert(seat_inside_of->is_box);
- cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_NONE); // no collisions while in a seat
- cpBodySetPosition(p->body, (entity_pos(seat_inside_of)));
- cpBodySetVelocity(p->body, (box_vel(seat_inside_of)));
-
- // share cloaking with box
- p->time_was_last_cloaked = seat_inside_of->time_was_last_cloaked;
- p->last_cloaked_by_squad = seat_inside_of->last_cloaked_by_squad;
-
- // set thruster thrust from movement
- if (seat_inside_of->box_type == BoxCockpit)
+ if (player->input.take_over_squad >= 0)
{
- Entity *g = get_entity(gs, seat_inside_of->shape_parent_entity);
-
- cpVect target_direction = {0};
- if (cpvlength(movement_this_tick) > 0.0)
+ if (player->input.take_over_squad == SquadNone)
{
- target_direction = cpvnormalize(movement_this_tick);
+ player->squad = SquadNone;
}
- BOXES_ITER(gs, cur, g)
+ else
{
- if (cur->box_type == BoxThruster)
+ bool squad_taken = false;
+ PLAYERS_ITER(gs->players, other_player)
{
-
- double wanted_thrust = -cpvdot(target_direction, box_facing_vector(cur));
- wanted_thrust = clamp01(wanted_thrust);
- cur->wanted_thrust = wanted_thrust;
- }
- if (cur->box_type == BoxGyroscope)
- {
- cur->wanted_thrust = rotation_this_tick;
+ if (other_player->squad == player->input.take_over_squad)
+ {
+ squad_taken = true;
+ break;
+ }
}
+ if (!squad_taken)
+ player->squad = player->input.take_over_squad;
}
+ player->input.take_over_squad = -1;
}
- }
- }
-
-#if 1 // building
- if (player->input.dobuild)
- {
- 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};
- cpVect world_build = world_hand_pos;
-
- // @Robust sanitize this input so player can't build on any grid in the world
- Entity *target_grid = grid_to_build_on(gs, world_hand_pos);
- cpShape *maybe_box_to_destroy = cpSpacePointQueryNearest(gs->space, (world_build), 0.01, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &info);
- if (maybe_box_to_destroy != NULL)
- {
- Entity *cur_box = cp_shape_entity(maybe_box_to_destroy);
- if (!cur_box->indestructible && !cur_box->is_platonic)
+ // squad invites
+ Entity *possibly_to_invite = get_entity(gs, player->input.invite_this_player);
+ if (player->input.invite_this_player.generation > 0)
+ player->input.invite_this_player = (EntityID){0}; // just in case
+ if (player->squad != SquadNone && possibly_to_invite != NULL && possibly_to_invite->is_player)
{
- 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);
+ possibly_to_invite->squad_invited_to = player->squad;
}
- }
- else if (box_unlocked(player, player->input.build_type))
- {
- // creating a box
- p->damage += DAMAGE_TO_PLAYER_PER_BLOCK;
- cpVect created_box_position;
- if (p->damage < 1.0) // player can't create a box that kills them by making it
+ Entity *p = get_entity(gs, player->entity);
+ // player respawning
+ if (p == NULL)
{
- if (target_grid == NULL)
+ p = new_entity(gs);
+ create_player_entity(gs, p);
+ player->entity = get_id(gs, p);
+ Entity *medbay = get_entity(gs, player->last_used_medbay);
+ entity_ensure_in_orbit(gs, p);
+ if (medbay != NULL)
{
- Entity *new_grid = new_entity(gs);
- grid_create(gs, new_grid);
- entity_set_pos(new_grid, world_build);
- cpBodySetVelocity(new_grid->body, (player_vel(gs, p)));
- target_grid = new_grid;
- created_box_position = (cpVect){0};
+ exit_seat(gs, medbay, p);
+ p->damage = 0.95;
}
- else
- {
- created_box_position = grid_world_to_local(target_grid, world_build);
- }
- Entity *new_box = new_entity(gs);
- box_create(gs, new_box, target_grid, created_box_position);
- new_box->owning_squad = player->squad;
- grid_correct_for_holes(gs, target_grid); // no holey ship for you!
- new_box->box_type = player->input.build_type;
- new_box->compass_rotation = player->input.build_rotation;
- if (new_box->box_type == BoxScanner)
- new_box->blueprints_learned = player->box_unlocks;
- if (new_box->box_type == BoxBattery)
- new_box->energy_used = BATTERY_CAPACITY;
}
- }
- }
-#endif
- if (p->damage >= 1.0)
- {
- entity_destroy(gs, p);
- player->entity = (EntityID){0};
- }
-
- p->damage = clamp01(p->damage);
- }
-
- // process entities
- for (size_t i = 0; i < gs->cur_next_entity; i++)
- {
- Entity *e = &gs->entities[i];
- if (!e->exists)
- continue;
+ assert(p->is_player);
+ p->owning_squad = player->squad;
- // instant death
- {
- cpFloat dist_from_center = cpvlengthsq((entity_pos(e)));
- if (e->body != NULL && dist_from_center > (INSTANT_DEATH_DISTANCE_FROM_CENTER * INSTANT_DEATH_DISTANCE_FROM_CENTER))
- {
- bool platonic_found = false;
- if (e->is_grid)
+ if (p->squad_invited_to != SquadNone)
{
- BOXES_ITER(gs, cur_box, e)
+ if (player->input.accept_cur_squad_invite)
{
- if (cur_box->is_platonic)
- {
- platonic_found = true;
- break;
- }
+ player->squad = p->squad_invited_to;
+ p->squad_invited_to = SquadNone;
+ player->input.accept_cur_squad_invite = false;
+ }
+ if (player->input.reject_cur_squad_invite)
+ {
+ p->squad_invited_to = SquadNone;
+ player->input.reject_cur_squad_invite = false;
}
}
- if (platonic_found)
+
+#ifdef INFINITE_RESOURCES
+ p->damage = 0.0;
+#endif
+ // update gold win condition
+ if (cpvlength(cpvsub((cpBodyGetPosition(p->body)), gs->goldpos)) < GOLD_COLLECT_RADIUS)
{
- cpBody *body = e->body;
- cpBodySetVelocity(body, cpvmult(cpBodyGetVelocity(body), -0.5));
- cpVect rel_to_center = cpvsub(cpBodyGetPosition(body), (cpVect){0});
- cpBodySetPosition(body, cpvmult(cpvnormalize(rel_to_center), INSTANT_DEATH_DISTANCE_FROM_CENTER));
+ p->goldness += 0.1;
+ p->damage = 0.0;
+ gs->goldpos = (cpVect){.x = hash11((float)time(gs)) * 20.0, .y = hash11((float)time(gs) - 13.6) * 20.0};
}
- else
+#if 1
+ cpVect world_hand_pos = get_world_hand_pos(gs, &player->input, p);
+ if (player->input.seat_action)
{
- entity_destroy(gs, e);
- }
- continue;
- }
- }
+ player->input.seat_action = false; // "handle" the input
+ Entity *seat_maybe_in = get_entity(gs, p->currently_inside_of_box);
+ if (seat_maybe_in == NULL) // not in any seat
+ {
+ cpPointQueryInfo query_info = {0};
+ cpShape *result = cpSpacePointQueryNearest(gs->space, (world_hand_pos), 0.1, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &query_info);
+ if (result != NULL)
+ {
+ Entity *potential_seat = cp_shape_entity(result);
+ assert(potential_seat->is_box);
- // sun processing for this current entity
- {
- SUNS_ITER(gs)
- {
- cpVect pos_rel_sun = (cpvsub(entity_pos(e), (entity_pos(i.sun))));
- cpFloat sqdist = cpvlengthsq(pos_rel_sun);
- if (!e->is_grid) // grids aren't damaged (this edge case sucks!)
- {
- sqdist = cpvlengthsq(cpvsub((entity_pos(e)), (entity_pos(i.sun))));
- if (sqdist < (i.sun->sun_radius * i.sun->sun_radius))
+ if (potential_seat->box_type == BoxScanner) // learn everything from the scanner
+ {
+ player->box_unlocks |= potential_seat->blueprints_learned;
+ }
+ if (potential_seat->box_type == BoxMerge) // disconnect!
+ {
+ potential_seat->wants_disconnect = true;
+ grid_correct_for_holes(gs, box_grid(potential_seat));
+ assert(potential_seat->exists);
+ assert(potential_seat->is_box);
+ assert(potential_seat->box_type == BoxMerge);
+ }
+ if (potential_seat->box_type == BoxCockpit || potential_seat->box_type == BoxMedbay) // @Robust check by feature flag instead of 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)
+ {
+ p->currently_inside_of_box = get_id(gs, potential_seat);
+ potential_seat->player_who_is_inside_of_me = get_id(gs, p);
+ if (potential_seat->box_type == BoxMedbay)
+ player->last_used_medbay = p->currently_inside_of_box;
+ }
+ }
+ }
+ else
+ {
+ Log("No seat to get into for a player at point %f %f\n", world_hand_pos.x, world_hand_pos.y);
+ }
+ }
+ else
{
- e->damage += 10.0 * dt;
+ exit_seat(gs, seat_maybe_in, p);
+ seat_maybe_in->player_who_is_inside_of_me = (EntityID){0};
+ p->currently_inside_of_box = (EntityID){0};
}
}
+#endif
- if (e->body != NULL)
+ // process movement
{
- cpVect accel = sun_gravity_accel_for_entity(e, i.sun);
- cpVect new_vel = entity_vel(gs, e);
- new_vel = cpvadd(new_vel, cpvmult(accel, dt));
- cpBodySetVelocity(e->body, (new_vel));
- }
- }
- }
+ // no cheating by making movement bigger than length 1
+ cpVect movement_this_tick = (cpVect){0};
+ double rotation_this_tick = 0.0;
+ if (cpvlength(player->input.movement) > 0.0)
+ {
+ movement_this_tick = cpvmult(cpvnormalize(player->input.movement), clamp(cpvlength(player->input.movement), 0.0, 1.0));
+ player->input.movement = (cpVect){0};
+ }
+ if (fabs(player->input.rotation) > 0.0)
+ {
+ rotation_this_tick = player->input.rotation;
+ if (rotation_this_tick > 1.0)
+ rotation_this_tick = 1.0;
+ if (rotation_this_tick < -1.0)
+ rotation_this_tick = -1.0;
+ player->input.rotation = 0.0;
+ }
+ Entity *seat_inside_of = get_entity(gs, p->currently_inside_of_box);
- if (e->is_explosion)
- {
- e->explosion_progress += dt;
- e->explosion_pos = cpvadd(e->explosion_pos, cpvmult(e->explosion_vel, dt));
- do_explosion(gs, e, dt);
- if (e->explosion_progress >= EXPLOSION_TIME)
- {
- entity_destroy(gs, e);
- }
- }
+ // strange rare bug I saw happen, related to explosives, but no idea how to
+ // reproduce. @Robust put a breakpoint here, reproduce, and fix it!
+ if (seat_inside_of != NULL && !seat_inside_of->is_box)
+ {
+ Log("Strange thing happened where player was in non box seat!\n");
+ seat_inside_of = NULL;
+ p->currently_inside_of_box = (EntityID){0};
+ }
- if (e->is_missile)
- {
- if (is_burning(e))
- {
- e->time_burned_for += dt;
- cpBodyApplyForceAtWorldPoint(e->body, (cpvspin((cpVect){.x = MISSILE_BURN_FORCE, .y = 0.0}, entity_rotation(e))), (entity_pos(e)));
- }
- if (e->damage >= MISSILE_DAMAGE_THRESHOLD && e->time_burned_for >= MISSILE_ARM_TIME)
- {
- Entity *explosion = new_entity(gs);
- explosion->is_explosion = true;
- explosion->explosion_pos = entity_pos(e);
- explosion->explosion_vel = (cpBodyGetVelocity(e->body));
- explosion->explosion_push_strength = MISSILE_EXPLOSION_PUSH;
- explosion->explosion_radius = MISSILE_EXPLOSION_RADIUS;
- entity_destroy(gs, e);
- }
- }
+ if (seat_inside_of == NULL)
+ {
+ cpShapeSetFilter(p->shape, PLAYER_SHAPE_FILTER);
+ cpBodyApplyForceAtWorldPoint(p->body, (cpvmult(movement_this_tick, PLAYER_JETPACK_FORCE)), cpBodyGetPosition(p->body));
+ cpBodySetTorque(p->body, rotation_this_tick * PLAYER_JETPACK_TORQUE);
+ p->damage += cpvlength(movement_this_tick) * dt * PLAYER_JETPACK_SPICE_PER_SECOND;
+ p->damage += fabs(rotation_this_tick) * dt * PLAYER_JETPACK_ROTATION_ENERGY_PER_SECOND;
+ }
+ else
+ {
+ assert(seat_inside_of->is_box);
+ cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_NONE); // no collisions while in a seat
+ cpBodySetPosition(p->body, (entity_pos(seat_inside_of)));
+ cpBodySetVelocity(p->body, (box_vel(seat_inside_of)));
- if (e->is_box)
- {
- 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);
- explosion->is_explosion = true;
- explosion->explosion_pos = entity_pos(e);
- explosion->explosion_vel = grid_vel(box_grid(e));
- 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);
- }
- if (e->box_type == BoxMerge)
- {
- Entity *from_merge = e;
- assert(from_merge != NULL);
+ // share cloaking with box
+ p->time_was_last_cloaked = seat_inside_of->time_was_last_cloaked;
+ p->last_cloaked_by_squad = seat_inside_of->last_cloaked_by_squad;
+
+ // set thruster thrust from movement
+ if (seat_inside_of->box_type == BoxCockpit)
+ {
+ Entity *g = get_entity(gs, seat_inside_of->shape_parent_entity);
- 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);
+ cpVect target_direction = {0};
+ if (cpvlength(movement_this_tick) > 0.0)
+ {
+ target_direction = cpvnormalize(movement_this_tick);
+ }
+ BOXES_ITER(gs, cur, g)
+ {
+ if (cur->box_type == BoxThruster)
+ {
- if (other_merge == NULL && from_merge->wants_disconnect)
- from_merge->wants_disconnect = false;
+ double wanted_thrust = -cpvdot(target_direction, box_facing_vector(cur));
+ wanted_thrust = clamp01(wanted_thrust);
+ cur->wanted_thrust = wanted_thrust;
+ }
+ if (cur->box_type == BoxGyroscope)
+ {
+ cur->wanted_thrust = rotation_this_tick;
+ }
+ }
+ }
+ }
+ }
- if (!from_merge->wants_disconnect && other_merge != NULL && !other_merge->wants_disconnect)
+#if 1 // building
+ if (player->input.dobuild)
{
- assert(box_grid(from_merge) != box_grid(other_merge));
-
- Entity *from_grid = box_grid(from_merge);
- Entity *other_grid = box_grid(other_merge);
+ 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...
- // the merges are near eachother, but are they facing eachother...
- bool from_facing_other = cpvdot(box_facing_vector(from_merge), cpvnormalize(cpvsub(entity_pos(other_merge), entity_pos(from_merge)))) > 0.8;
- bool other_facing_from = cpvdot(box_facing_vector(other_merge), cpvnormalize(cpvsub(entity_pos(from_merge), entity_pos(other_merge)))) > 0.8;
+ cpPointQueryInfo info = {0};
+ cpVect world_build = world_hand_pos;
- // 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
- cpVect actual_new_pos = grid_snapped_box_pos(from_grid, entity_pos(other_merge));
- cpVect needed_new_pos = cpvadd(entity_pos(from_merge), cpvmult(box_facing_vector(from_merge), BOX_SIZE));
- if (from_facing_other && other_facing_from && cpvnear(needed_new_pos, actual_new_pos, 0.01))
+ // @Robust sanitize this input so player can't build on any grid in the world
+ Entity *target_grid = grid_to_build_on(gs, world_hand_pos);
+ cpShape *maybe_box_to_destroy = cpSpacePointQueryNearest(gs->space, (world_build), 0.01, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &info);
+ if (maybe_box_to_destroy != NULL)
{
- // do the merge
- cpVect facing_vector_needed = cpvmult(box_facing_vector(from_merge), -1.0);
- cpVect current_facing_vector = box_facing_vector(other_merge);
- double angle_diff = cpvanglediff(current_facing_vector, facing_vector_needed);
- if (angle_diff == FLT_MIN)
- angle_diff = 0.0;
- assert(!isnan(angle_diff));
-
- cpBodySetAngle(other_grid->body, cpBodyGetAngle(other_grid->body) + angle_diff);
-
- cpVect moved_because_angle_change = cpvsub(needed_new_pos, entity_pos(other_merge));
- cpBodySetPosition(other_grid->body, (cpvadd(entity_pos(other_grid), moved_because_angle_change)));
-
- // cpVect snap_movement_vect = cpvsub(actual_new_pos, entity_pos(other_merge));
- cpVect snap_movement_vect = (cpVect){0};
-
- Entity *cur = get_entity(gs, other_grid->boxes);
-
- other_grid->boxes = (EntityID){0};
- while (cur != NULL)
+ Entity *cur_box = cp_shape_entity(maybe_box_to_destroy);
+ if (!cur_box->indestructible && !cur_box->is_platonic)
{
- Entity *next = get_entity(gs, cur->next_box);
- cpVect world = entity_pos(cur);
- enum CompassRotation new_rotation = facing_vector_to_compass(from_grid, other_grid, box_facing_vector(cur));
- cur->compass_rotation = new_rotation;
- cpVect new_cur_pos = grid_snapped_box_pos(from_grid, cpvadd(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 *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);
}
- entity_destroy(gs, other_grid);
}
- }
- }
- if (e->damage >= 1.0)
- {
- grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e);
- }
- }
- if (e->is_grid)
- {
- Entity *grid = e;
- // calculate how much energy solar panels provide
- double energy_to_add = 0.0;
- BOXES_ITER(gs, cur_box, grid)
- {
- if (cur_box->box_type == BoxSolarPanel)
- {
- cur_box->sun_amount = 0.0;
- SUNS_ITER(gs)
+ else if (box_unlocked(player, player->input.build_type))
{
- double new_sun = clamp01(fabs(cpvdot(box_facing_vector(cur_box), cpvnormalize(cpvsub(entity_pos(i.sun), entity_pos(cur_box))))));
-
- // less sun the farther away you are!
- new_sun *= lerp(1.0, 0.0, clamp01(cpvlength(cpvsub(entity_pos(cur_box), entity_pos(i.sun))) / sun_dist_no_gravity(i.sun)));
- cur_box->sun_amount += new_sun;
+ // creating a box
+ p->damage += DAMAGE_TO_PLAYER_PER_BLOCK;
+ cpVect created_box_position;
+ if (p->damage < 1.0) // player can't create a box that kills them by making it
+ {
+ if (target_grid == NULL)
+ {
+ Entity *new_grid = new_entity(gs);
+ grid_create(gs, new_grid);
+ entity_set_pos(new_grid, world_build);
+ cpBodySetVelocity(new_grid->body, (player_vel(gs, p)));
+ target_grid = new_grid;
+ created_box_position = (cpVect){0};
+ }
+ else
+ {
+ created_box_position = grid_world_to_local(target_grid, world_build);
+ }
+ Entity *new_box = new_entity(gs);
+ box_create(gs, new_box, target_grid, created_box_position);
+ new_box->owning_squad = player->squad;
+ grid_correct_for_holes(gs, target_grid); // no holey ship for you!
+ new_box->box_type = player->input.build_type;
+ new_box->compass_rotation = player->input.build_rotation;
+ if (new_box->box_type == BoxScanner)
+ new_box->blueprints_learned = player->box_unlocks;
+ if (new_box->box_type == BoxBattery)
+ new_box->energy_used = BATTERY_CAPACITY;
+ }
}
- energy_to_add += cur_box->sun_amount * SOLAR_ENERGY_PER_SECOND * dt;
}
- }
-
- // apply all of the energy to all connected batteries
- BOXES_ITER(gs, cur, grid)
- {
- if (energy_to_add <= 0.0)
- break;
- if (cur->box_type == BoxBattery)
+#endif
+ if (p->damage >= 1.0)
{
- double energy_sucked_up_by_battery = cur->energy_used < energy_to_add ? cur->energy_used : energy_to_add;
- cur->energy_used -= energy_sucked_up_by_battery;
- energy_to_add -= energy_sucked_up_by_battery;
+ entity_destroy(gs, p);
+ player->entity = (EntityID){0};
}
- assert(energy_to_add >= 0.0);
- }
- // any energy_to_add existing now can also be used to power thrusters/medbay
- double non_battery_energy_left_over = energy_to_add;
+ p->damage = clamp01(p->damage);
+ }
+ }
- // use the energy, stored in the batteries, in various boxes
- BOXES_ITER(gs, cur_box, grid)
+ PROFILE_SCOPE("process entities")
+ {
+ for (size_t i = 0; i < gs->cur_next_entity; i++)
{
- if (cur_box->box_type == BoxThruster)
- {
+ Entity *e = &gs->entities[i];
+ if (!e->exists)
+ continue;
- double energy_to_consume = cur_box->wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt;
- if (energy_to_consume > 0.0)
+ PROFILE_SCOPE("instant death ")
+ {
+ cpFloat dist_from_center = cpvlengthsq((entity_pos(e)));
+ if (e->body != NULL && dist_from_center > (INSTANT_DEATH_DISTANCE_FROM_CENTER * INSTANT_DEATH_DISTANCE_FROM_CENTER))
{
- 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;
- if (cur_box->thrust >= 0.0)
- cpBodyApplyForceAtWorldPoint(grid->body, (thruster_force(cur_box)), (entity_pos(cur_box)));
+ bool platonic_found = false;
+ if (e->is_grid)
+ {
+ BOXES_ITER(gs, cur_box, e)
+ {
+ if (cur_box->is_platonic)
+ {
+ platonic_found = true;
+ break;
+ }
+ }
+ }
+ if (platonic_found)
+ {
+ cpBody *body = e->body;
+ cpBodySetVelocity(body, cpvmult(cpBodyGetVelocity(body), -0.5));
+ cpVect rel_to_center = cpvsub(cpBodyGetPosition(body), (cpVect){0});
+ cpBodySetPosition(body, cpvmult(cpvnormalize(rel_to_center), INSTANT_DEATH_DISTANCE_FROM_CENTER));
+ }
+ else
+ {
+ entity_destroy(gs, e);
+ }
+ continue;
}
}
- if (cur_box->box_type == BoxGyroscope)
+
+ // sun processing for this current entity
+ PROFILE_SCOPE("this entity sun processing")
{
- double energy_to_consume = fabs(cur_box->wanted_thrust * GYROSCOPE_ENERGY_USED_PER_SECOND * dt);
- if (energy_to_consume > 0.0)
+ SUNS_ITER(gs)
{
- 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;
- if (fabs(cur_box->thrust) >= 0.0)
- cpBodySetTorque(grid->body, cpBodyGetTorque(grid->body) + cur_box->thrust * GYROSCOPE_TORQUE);
+ cpVect pos_rel_sun = (cpvsub(entity_pos(e), (entity_pos(i.sun))));
+ cpFloat sqdist = cpvlengthsq(pos_rel_sun);
+ if (!e->is_grid) // grids aren't damaged (this edge case sucks!)
+ {
+ 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;
+ }
+ }
+
+ if (e->body != NULL)
+ {
+ cpVect accel = sun_gravity_accel_for_entity(e, i.sun);
+ cpVect new_vel = entity_vel(gs, e);
+ new_vel = cpvadd(new_vel, cpvmult(accel, dt));
+ cpBodySetVelocity(e->body, (new_vel));
+ }
}
}
- if (cur_box->box_type == BoxMedbay)
+
+ if (e->is_explosion)
{
- Entity *potential_meatbag_to_heal = get_entity(gs, cur_box->player_who_is_inside_of_me);
- if (potential_meatbag_to_heal != NULL)
+ PROFILE_SCOPE("Explosion")
{
- double wanted_energy_use = fmin(potential_meatbag_to_heal->damage, PLAYER_ENERGY_RECHARGE_PER_SECOND * dt);
- if (wanted_energy_use > 0.0)
+ e->explosion_progress += dt;
+ e->explosion_pos = cpvadd(e->explosion_pos, cpvmult(e->explosion_vel, dt));
+ do_explosion(gs, e, dt);
+ if (e->explosion_progress >= EXPLOSION_TIME)
{
- double energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, wanted_energy_use);
- potential_meatbag_to_heal->damage -= (1.0 - energy_unconsumed / wanted_energy_use) * wanted_energy_use;
+ entity_destroy(gs, e);
}
}
}
- if (cur_box->box_type == BoxCloaking)
+
+ if (e->is_missile)
{
- double energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, CLOAKING_ENERGY_USE * dt);
- if (energy_unconsumed >= CLOAKING_ENERGY_USE * dt)
+ PROFILE_SCOPE("Missile")
{
- cur_box->cloaking_power = lerp(cur_box->cloaking_power, 0.0, dt * 3.0);
- }
- 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);
+ if (is_burning(e))
+ {
+ e->time_burned_for += dt;
+ cpBodyApplyForceAtWorldPoint(e->body, (cpvspin((cpVect){.x = MISSILE_BURN_FORCE, .y = 0.0}, entity_rotation(e))), (entity_pos(e)));
+ }
+ if (e->damage >= MISSILE_DAMAGE_THRESHOLD && e->time_burned_for >= MISSILE_ARM_TIME)
+ {
+ Entity *explosion = new_entity(gs);
+ explosion->is_explosion = true;
+ explosion->explosion_pos = entity_pos(e);
+ explosion->explosion_vel = (cpBodyGetVelocity(e->body));
+ explosion->explosion_push_strength = MISSILE_EXPLOSION_PUSH;
+ explosion->explosion_radius = MISSILE_EXPLOSION_RADIUS;
+ entity_destroy(gs, e);
+ }
}
}
- if (cur_box->box_type == BoxMissileLauncher)
- {
- LauncherTarget target = missile_launcher_target(gs, cur_box);
- if (cur_box->missile_construction_charge < 1.0)
+ if (e->is_box)
+ {
+ PROFILE_SCOPE("Box processing")
{
- double want_use_energy = dt * MISSILE_CHARGE_RATE;
- double energy_charged = want_use_energy - batteries_use_energy(gs, grid, &non_battery_energy_left_over, want_use_energy);
- cur_box->missile_construction_charge += energy_charged;
- }
+ 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);
+ explosion->is_explosion = true;
+ explosion->explosion_pos = entity_pos(e);
+ explosion->explosion_vel = grid_vel(box_grid(e));
+ 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);
+ }
+ if (e->box_type == BoxMerge)
+ {
+ Entity *from_merge = e;
+ assert(from_merge != NULL);
- if (target.target_found && cur_box->missile_construction_charge >= 1.0)
- {
- cur_box->missile_construction_charge = 0.0;
- 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))));
- cpBodySetAngle(new_missile->body, target.facing_angle);
- cpBodySetVelocity(new_missile->body, (box_vel(cur_box)));
+ 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 (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 = cpvdot(box_facing_vector(from_merge), cpvnormalize(cpvsub(entity_pos(other_merge), entity_pos(from_merge)))) > 0.8;
+ bool other_facing_from = cpvdot(box_facing_vector(other_merge), cpvnormalize(cpvsub(entity_pos(from_merge), entity_pos(other_merge)))) > 0.8;
+
+ // 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
+ cpVect actual_new_pos = grid_snapped_box_pos(from_grid, entity_pos(other_merge));
+ cpVect needed_new_pos = cpvadd(entity_pos(from_merge), cpvmult(box_facing_vector(from_merge), BOX_SIZE));
+ if (from_facing_other && other_facing_from && cpvnear(needed_new_pos, actual_new_pos, 0.01))
+ {
+ // do the merge
+ cpVect facing_vector_needed = cpvmult(box_facing_vector(from_merge), -1.0);
+ cpVect current_facing_vector = box_facing_vector(other_merge);
+ double angle_diff = cpvanglediff(current_facing_vector, facing_vector_needed);
+ if (angle_diff == FLT_MIN)
+ angle_diff = 0.0;
+ assert(!isnan(angle_diff));
+
+ cpBodySetAngle(other_grid->body, cpBodyGetAngle(other_grid->body) + angle_diff);
+
+ cpVect moved_because_angle_change = cpvsub(needed_new_pos, entity_pos(other_merge));
+ cpBodySetPosition(other_grid->body, (cpvadd(entity_pos(other_grid), moved_because_angle_change)));
+
+ // cpVect snap_movement_vect = cpvsub(actual_new_pos, entity_pos(other_merge));
+ cpVect snap_movement_vect = (cpVect){0};
+
+ Entity *cur = get_entity(gs, other_grid->boxes);
+
+ other_grid->boxes = (EntityID){0};
+ while (cur != NULL)
+ {
+ Entity *next = get_entity(gs, cur->next_box);
+ cpVect world = entity_pos(cur);
+ enum CompassRotation new_rotation = facing_vector_to_compass(from_grid, other_grid, box_facing_vector(cur));
+ cur->compass_rotation = new_rotation;
+ cpVect new_cur_pos = grid_snapped_box_pos(from_grid, cpvadd(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, other_grid);
+ }
+ }
+ }
+ if (e->damage >= 1.0)
+ {
+ grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e);
+ }
}
}
- if (cur_box->box_type == BoxScanner)
+
+ if (e->is_grid)
{
- // set the nearest platonic solid! only on server as only the server sees everything
- if (gs->server_side_computing)
+ PROFILE_SCOPE("Grid processing")
{
- double energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, SCANNER_ENERGY_USE * dt);
- if (energy_unconsumed >= SCANNER_ENERGY_USE * dt)
+ Entity *grid = e;
+ // calculate how much energy solar panels provide
+ double energy_to_add = 0.0;
+ BOXES_ITER(gs, cur_box, grid)
{
- cur_box->platonic_detection_strength = 0.0;
- cur_box->platonic_nearest_direction = (cpVect){0};
+ if (cur_box->box_type == BoxSolarPanel)
+ {
+ cur_box->sun_amount = 0.0;
+ SUNS_ITER(gs)
+ {
+ double new_sun = clamp01(fabs(cpvdot(box_facing_vector(cur_box), cpvnormalize(cpvsub(entity_pos(i.sun), entity_pos(cur_box))))));
+
+ // less sun the farther away you are!
+ new_sun *= lerp(1.0, 0.0, clamp01(cpvlength(cpvsub(entity_pos(cur_box), entity_pos(i.sun))) / sun_dist_no_gravity(i.sun)));
+ cur_box->sun_amount += new_sun;
+ }
+ energy_to_add += cur_box->sun_amount * SOLAR_ENERGY_PER_SECOND * dt;
+ }
}
- else
+
+ // apply all of the energy to all connected batteries
+ BOXES_ITER(gs, cur, grid)
{
- cpVect from_pos = entity_pos(cur_box);
- cpVect nearest = {0};
- double nearest_dist = INFINITY;
- for (int i = 0; i < MAX_BOX_TYPES; i++)
+ if (energy_to_add <= 0.0)
+ break;
+ if (cur->box_type == BoxBattery)
+ {
+ double energy_sucked_up_by_battery = cur->energy_used < energy_to_add ? cur->energy_used : energy_to_add;
+ cur->energy_used -= energy_sucked_up_by_battery;
+ energy_to_add -= energy_sucked_up_by_battery;
+ }
+ assert(energy_to_add >= 0.0);
+ }
+
+ // any energy_to_add existing now can also be used to power thrusters/medbay
+ double non_battery_energy_left_over = energy_to_add;
+
+ // use the energy, stored in the batteries, in various boxes
+ BOXES_ITER(gs, cur_box, grid)
+ {
+ if (cur_box->box_type == BoxThruster)
+ {
+
+ double energy_to_consume = cur_box->wanted_thrust * THRUSTER_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;
+ if (cur_box->thrust >= 0.0)
+ cpBodyApplyForceAtWorldPoint(grid->body, (thruster_force(cur_box)), (entity_pos(cur_box)));
+ }
+ }
+ if (cur_box->box_type == BoxGyroscope)
+ {
+ double energy_to_consume = fabs(cur_box->wanted_thrust * 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;
+ if (fabs(cur_box->thrust) >= 0.0)
+ cpBodySetTorque(grid->body, cpBodyGetTorque(grid->body) + cur_box->thrust * GYROSCOPE_TORQUE);
+ }
+ }
+ if (cur_box->box_type == BoxMedbay)
{
- cpVect cur_pos = gs->platonic_positions[i];
- if (cpvlength(cur_pos) > 0.0) // zero is uninitialized, the platonic solid doesn't exist (probably) @Robust do better
+ Entity *potential_meatbag_to_heal = get_entity(gs, cur_box->player_who_is_inside_of_me);
+ if (potential_meatbag_to_heal != NULL)
{
- double length_to_cur = cpvdist(from_pos, cur_pos);
- if (length_to_cur < nearest_dist)
+ double wanted_energy_use = fmin(potential_meatbag_to_heal->damage, PLAYER_ENERGY_RECHARGE_PER_SECOND * dt);
+ if (wanted_energy_use > 0.0)
{
- nearest_dist = length_to_cur;
- nearest = cur_pos;
+ double energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, wanted_energy_use);
+ potential_meatbag_to_heal->damage -= (1.0 - energy_unconsumed / wanted_energy_use) * wanted_energy_use;
}
}
}
- if (nearest_dist < INFINITY)
+ if (cur_box->box_type == BoxCloaking)
{
- cur_box->platonic_nearest_direction = cpvnormalize(cpvsub(nearest, from_pos));
- cur_box->platonic_detection_strength = fmax(0.1, 1.0 - fmin(1.0, nearest_dist / 100.0));
+ double energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, CLOAKING_ENERGY_USE * dt);
+ if (energy_unconsumed >= CLOAKING_ENERGY_USE * dt)
+ {
+ cur_box->cloaking_power = lerp(cur_box->cloaking_power, 0.0, dt * 3.0);
+ }
+ 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);
+ }
}
- else
+ if (cur_box->box_type == BoxMissileLauncher)
{
- cur_box->platonic_nearest_direction = (cpVect){0};
- cur_box->platonic_detection_strength = 0.0;
+ LauncherTarget target = missile_launcher_target(gs, cur_box);
+
+ if (cur_box->missile_construction_charge < 1.0)
+ {
+ double want_use_energy = dt * MISSILE_CHARGE_RATE;
+ double energy_charged = want_use_energy - batteries_use_energy(gs, grid, &non_battery_energy_left_over, want_use_energy);
+ cur_box->missile_construction_charge += energy_charged;
+ }
+
+ if (target.target_found && cur_box->missile_construction_charge >= 1.0)
+ {
+ cur_box->missile_construction_charge = 0.0;
+ 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))));
+ cpBodySetAngle(new_missile->body, target.facing_angle);
+ cpBodySetVelocity(new_missile->body, (box_vel(cur_box)));
+ }
}
- }
- }
+ if (cur_box->box_type == BoxScanner)
+ {
+ // set the nearest platonic solid! only on server as only the server sees everything
+ if (gs->server_side_computing)
+ {
+ double energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, SCANNER_ENERGY_USE * dt);
+ if (energy_unconsumed >= SCANNER_ENERGY_USE * dt)
+ {
+ cur_box->platonic_detection_strength = 0.0;
+ cur_box->platonic_nearest_direction = (cpVect){0};
+ }
+ else
+ {
+ cpVect from_pos = entity_pos(cur_box);
+ cpVect nearest = {0};
+ double nearest_dist = INFINITY;
+ for (int i = 0; i < MAX_BOX_TYPES; i++)
+ {
+ cpVect cur_pos = gs->platonic_positions[i];
+ if (cpvlength(cur_pos) > 0.0) // zero is uninitialized, the platonic solid doesn't exist (probably) @Robust do better
+ {
+ double length_to_cur = cpvdist(from_pos, cur_pos);
+ if (length_to_cur < nearest_dist)
+ {
+ nearest_dist = length_to_cur;
+ nearest = cur_pos;
+ }
+ }
+ }
+ if (nearest_dist < INFINITY)
+ {
+ cur_box->platonic_nearest_direction = cpvnormalize(cpvsub(nearest, from_pos));
+ cur_box->platonic_detection_strength = fmax(0.1, 1.0 - fmin(1.0, nearest_dist / 100.0));
+ }
+ else
+ {
+ cur_box->platonic_nearest_direction = (cpVect){0};
+ cur_box->platonic_detection_strength = 0.0;
+ }
+ }
+ }
- // unlock the nearest platonic solid!
- scanner_has_learned = cur_box->blueprints_learned;
- Entity *to_learn = closest_box_to_point_in_radius(gs, entity_pos(cur_box), SCANNER_RADIUS, scanner_filter);
- if (to_learn != NULL)
- assert(to_learn->is_box);
+ // unlock the nearest platonic solid!
+ scanner_has_learned = cur_box->blueprints_learned;
+ Entity *to_learn = closest_box_to_point_in_radius(gs, entity_pos(cur_box), SCANNER_RADIUS, scanner_filter);
+ if (to_learn != NULL)
+ assert(to_learn->is_box);
- EntityID new_id = get_id(gs, to_learn);
+ EntityID new_id = get_id(gs, to_learn);
- if (!entityids_same(cur_box->currently_scanning, new_id))
- {
- cur_box->currently_scanning_progress = 0.0;
- cur_box->currently_scanning = new_id;
- }
+ if (!entityids_same(cur_box->currently_scanning, new_id))
+ {
+ cur_box->currently_scanning_progress = 0.0;
+ cur_box->currently_scanning = new_id;
+ }
- double target_head_rotate_speed = cur_box->platonic_detection_strength > 0.0 ? 3.0 : 0.0;
- if (to_learn != NULL)
- {
- cur_box->currently_scanning_progress += dt * SCANNER_SCAN_RATE;
- target_head_rotate_speed *= 30.0 * cur_box->currently_scanning_progress;
- }
- else
- cur_box->currently_scanning_progress = 0.0;
+ double target_head_rotate_speed = cur_box->platonic_detection_strength > 0.0 ? 3.0 : 0.0;
+ if (to_learn != NULL)
+ {
+ cur_box->currently_scanning_progress += dt * SCANNER_SCAN_RATE;
+ target_head_rotate_speed *= 30.0 * cur_box->currently_scanning_progress;
+ }
+ else
+ cur_box->currently_scanning_progress = 0.0;
- if (cur_box->currently_scanning_progress >= 1.0)
- {
- cur_box->blueprints_learned |= box_unlock_number(to_learn->box_type);
- }
+ if (cur_box->currently_scanning_progress >= 1.0)
+ {
+ cur_box->blueprints_learned |= box_unlock_number(to_learn->box_type);
+ }
- cur_box->scanner_head_rotate_speed = lerp(cur_box->scanner_head_rotate_speed, target_head_rotate_speed, dt * 3.0);
- cur_box->scanner_head_rotate += cur_box->scanner_head_rotate_speed * dt;
- cur_box->scanner_head_rotate = fmod(cur_box->scanner_head_rotate, 2.0 * PI);
+ cur_box->scanner_head_rotate_speed = lerp(cur_box->scanner_head_rotate_speed, target_head_rotate_speed, dt * 3.0);
+ cur_box->scanner_head_rotate += cur_box->scanner_head_rotate_speed * dt;
+ cur_box->scanner_head_rotate = fmod(cur_box->scanner_head_rotate, 2.0 * PI);
+ }
+ }
+ }
}
}
}
- }
- cpSpaceStep(gs->space, dt);
+ PROFILE_SCOPE("chipmunk physics processing")
+ {
+ cpSpaceStep(gs->space, dt);
+ }
+ }
}
diff --git a/main.c b/main.c
index 6a8a40f..99080bd 100644
--- a/main.c
+++ b/main.c
@@ -32,6 +32,9 @@
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
+#include "ipsettings.h"
+#include "profiling.h"
+
// shaders
#include "goodpixel.gen.h"
#include "hueshift.gen.h"
@@ -663,6 +666,7 @@ static void init(void)
ENetEvent event;
enet_address_set_host(&address, SERVER_ADDRESS);
+ Log("Connecting to %s:%d\n", SERVER_ADDRESS, SERVER_PORT);
address.port = SERVER_PORT;
peer = enet_host_connect(client, &address, 2, 0);
if (peer == NULL)
@@ -1386,739 +1390,760 @@ static cpVect get_global_hand_pos(cpVect world_mouse_pos, bool *hand_at_arms_len
}
static void frame(void)
{
- double width = (float)sapp_width(), height = (float)sapp_height();
- double ratio = width / height;
- double time = sapp_frame_count() * sapp_frame_duration();
- double dt = (float)sapp_frame_duration();
-
- // pressed input management
+ PROFILE_SCOPE("frame")
{
- for (int i = 0; i < MAX_KEYDOWN; i++)
+ double width = (float)sapp_width(), height = (float)sapp_height();
+ double ratio = width / height;
+ double exec_time = sapp_frame_count() * sapp_frame_duration();
+ double dt = (float)sapp_frame_duration();
+
+ // pressed input management
{
- if (keypressed[i].frame < sapp_frame_count())
+ for (int i = 0; i < MAX_KEYDOWN; i++)
{
- keypressed[i].pressed = false;
+ if (keypressed[i].frame < sapp_frame_count())
+ {
+ keypressed[i].pressed = false;
+ }
}
- }
- for (int i = 0; i < MAX_MOUSEBUTTON; i++)
- {
- if (mousepressed[i].frame < sapp_frame_count())
+ for (int i = 0; i < MAX_MOUSEBUTTON; i++)
{
- mousepressed[i].pressed = false;
+ if (mousepressed[i].frame < sapp_frame_count())
+ {
+ mousepressed[i].pressed = false;
+ }
}
}
- }
- build_pressed = mousepressed[SAPP_MOUSEBUTTON_LEFT].pressed;
- interact_pressed = mousepressed[SAPP_MOUSEBUTTON_RIGHT].pressed;
+ build_pressed = mousepressed[SAPP_MOUSEBUTTON_LEFT].pressed;
+ interact_pressed = mousepressed[SAPP_MOUSEBUTTON_RIGHT].pressed;
- // networking
- {
- ENetEvent event;
- while (true)
+ // networking
+ static cpVect before_reprediction = {0};
+ PROFILE_SCOPE("networking")
{
- int enet_status = enet_host_service(client, &event, 0);
- if (enet_status > 0)
+ ENetEvent event;
+ while (true)
{
- switch (event.type)
+ int enet_status = enet_host_service(client, &event, 0);
+ if (enet_status > 0)
{
- case ENET_EVENT_TYPE_NONE:
- {
- Log("Wtf none event type?\n");
- break;
- }
- case ENET_EVENT_TYPE_CONNECT:
- {
- Log("New client from host %x\n", event.peer->address.host);
- break;
- }
-
- case ENET_EVENT_TYPE_RECEIVE:
- {
- unsigned char *decompressed = malloc(
- sizeof *decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
- size_t decompressed_max_len = MAX_SERVER_TO_CLIENT;
- assert(LZO1X_MEM_DECOMPRESS == 0);
-
- ma_mutex_lock(&play_packets_mutex);
- double predicted_to_time = gs.time;
- ServerToClient msg = (ServerToClient){
- .cur_gs = &gs,
- .audio_playback_buffer = &packets_to_play,
- };
- int return_value = lzo1x_decompress_safe(
- event.packet->data, event.packet->dataLength, decompressed,
- &decompressed_max_len, NULL);
- if (return_value == LZO_E_OK)
+ switch (event.type)
{
- server_to_client_deserialize(&msg, decompressed,
- decompressed_max_len, false);
- my_player_index = msg.your_player;
- }
- else
+ case ENET_EVENT_TYPE_NONE:
{
- Log("Couldn't decompress gamestate packet, error code %d from "
- "lzo\n",
- return_value);
- }
- ma_mutex_unlock(&play_packets_mutex);
- free(decompressed);
- enet_packet_destroy(event.packet);
-
- double server_current_time = gs.time;
- double difference = predicted_to_time - server_current_time;
- double target_prediction_time =
- (((double)peer->roundTripTime) / 1000.0) + TIMESTEP * 6.0;
-
- // keeps it stable even though causes jumps occasionally
- difference = fmax(difference, target_prediction_time);
-
- double eps = TIMESTEP * 0.1;
- if (predicted_to_time - gs.time < target_prediction_time - eps)
- {
- target_prediction_time_factor = 1.1;
- }
- else if (predicted_to_time - gs.time >
- target_prediction_time + eps * 2.0)
- {
- target_prediction_time_factor = 0.9;
+ Log("Wtf none event type?\n");
+ break;
}
- else
+ case ENET_EVENT_TYPE_CONNECT:
{
- target_prediction_time_factor = 1.0;
+ Log("New client from host %x\n", event.peer->address.host);
+ break;
}
- // re-predict the inputs
- double time_to_repredict = (float)difference;
- uint64_t start_prediction_time = stm_now();
- if (time_to_repredict > 0.0)
+ case ENET_EVENT_TYPE_RECEIVE:
{
- while (time_to_repredict > TIMESTEP)
+ unsigned char *decompressed = malloc(
+ sizeof *decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
+ size_t decompressed_max_len = MAX_SERVER_TO_CLIENT;
+ assert(LZO1X_MEM_DECOMPRESS == 0);
+
+ ma_mutex_lock(&play_packets_mutex);
+ double predicted_to_time = time(&gs);
+ ServerToClient msg = (ServerToClient){
+ .cur_gs = &gs,
+ .audio_playback_buffer = &packets_to_play,
+ };
+ int return_value = lzo1x_decompress_safe(
+ event.packet->data, event.packet->dataLength, decompressed,
+ &decompressed_max_len, NULL);
+ if (return_value == LZO_E_OK)
{
- if (stm_ms(stm_diff(stm_now(), start_prediction_time)) > MAX_MS_SPENT_REPREDICTING)
+ if (myentity() != NULL)
+ before_reprediction = entity_pos(myentity());
+ PROFILE_SCOPE("Deserializing data")
+ {
+ server_to_client_deserialize(&msg, decompressed,
+ decompressed_max_len, false);
+ }
+ my_player_index = msg.your_player;
+ }
+ else
+ {
+ Log("Couldn't decompress gamestate packet, error code %d from "
+ "lzo\n",
+ return_value);
+ }
+ ma_mutex_unlock(&play_packets_mutex);
+ free(decompressed);
+ enet_packet_destroy(event.packet);
+
+ PROFILE_SCOPE("Repredicting inputs")
+ {
+
+ double server_current_time = time(&gs);
+ double difference = predicted_to_time - server_current_time;
+ double target_prediction_time =
+ (((double)peer->roundTripTime) / 1000.0) + TIMESTEP * 6.0;
+
+ // keeps it stable even though causes jumps occasionally
+ difference = fmax(difference, target_prediction_time);
+
+ double eps = TIMESTEP * 0.1;
+ if (predicted_to_time - time(&gs) < target_prediction_time - eps)
{
- Log("Reprediction took longer than %f milliseconds", MAX_MS_SPENT_REPREDICTING);
+ target_prediction_time_factor = 1.1;
}
- QUEUE_ITER(&input_queue, cur_header)
+ else if (predicted_to_time - time(&gs) >
+ target_prediction_time + eps * 2.0)
{
- InputFrame *cur = (InputFrame *)cur_header->data;
- if (cur->tick == tick(&gs))
+ target_prediction_time_factor = 0.9;
+ }
+ else
+ {
+ target_prediction_time_factor = 1.0;
+ }
+
+ // re-predict the inputs
+ double time_to_repredict = (float)difference;
+ Log("Repredicting %f\n", time_to_repredict);
+
+ uint64_t start_prediction_time = stm_now();
+ if (time_to_repredict > 0.0)
+ {
+ while (time_to_repredict > TIMESTEP)
{
- myplayer()->input = *cur;
- break;
+ if (stm_ms(stm_diff(stm_now(), start_prediction_time)) > MAX_MS_SPENT_REPREDICTING)
+ {
+ Log("Reprediction took longer than %f milliseconds, could only predict %f\n", MAX_MS_SPENT_REPREDICTING, time_to_repredict);
+ break;
+ }
+ QUEUE_ITER(&input_queue, cur_header)
+ {
+ InputFrame *cur = (InputFrame *)cur_header->data;
+ if (cur->tick == tick(&gs))
+ {
+ myplayer()->input = *cur;
+ break;
+ }
+ }
+ process(&gs, TIMESTEP, false);
+ time_to_repredict -= TIMESTEP;
}
+ process(&gs, time_to_repredict, true);
+ time_to_repredict = 0.0;
}
- process(&gs, TIMESTEP);
- time_to_repredict -= TIMESTEP;
+
+ current_time_ahead_of_server = time(&gs) - server_current_time;
}
- process(&gs, time_to_repredict);
- time_to_repredict = 0.0;
+ break;
}
- current_time_ahead_of_server = gs.time - server_current_time;
- break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ {
+ fprintf(stderr, "Disconnected from server\n");
+ exit(-1);
+ break;
+ }
+ }
}
-
- case ENET_EVENT_TYPE_DISCONNECT:
+ else if (enet_status == 0)
{
- fprintf(stderr, "Disconnected from server\n");
- exit(-1);
break;
}
+ else if (enet_status < 0)
+ {
+ fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
+ break;
}
}
- else if (enet_status == 0)
- {
- break;
- }
- else if (enet_status < 0)
- {
- fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
- break;
- }
}
- }
- // gameplay
- ui(false, dt, width, height); // if ui button is pressed before game logic, set the pressed to
- // false so it doesn't propagate from the UI modal/button
- cpVect build_target_pos = {0};
- double build_target_rotation = 0.0;
- struct BuildPreviewInfo
- {
- cpVect grid_pos;
- double grid_rotation;
- } build_preview = {0};
- cpVect global_hand_pos = {0}; // world coords! world star!
- bool hand_at_arms_length = false;
- recalculate_camera_pos();
- cpVect world_mouse_pos = screen_to_world(width, height, mouse_pos);
- {
- // interpolate zoom
- zoom = lerp(zoom, zoom_target, dt * 12.0);
-
- // calculate build preview stuff
- cpVect local_hand_pos = {0};
- global_hand_pos =
- get_global_hand_pos(world_mouse_pos, &hand_at_arms_length);
+ // dbg_rect(before_reprediction);
- if (myentity() != NULL)
+ // gameplay
+ ui(false, dt, width, height); // if ui button is pressed before game logic, set the pressed to
+ // false so it doesn't propagate from the UI modal/button
+ cpVect build_target_pos = {0};
+ double build_target_rotation = 0.0;
+ struct BuildPreviewInfo
{
- local_hand_pos = cpvsub(global_hand_pos, entity_pos(myentity()));
- }
-
- // process player interaction (squad invites)
- if (interact_pressed && myplayer() != NULL && myplayer()->squad != SquadNone)
- ENTITIES_ITER(cur)
- {
- if (cur != myentity() && cur->is_player &&
- has_point(centered_at(entity_pos(cur), cpvmult(PLAYER_SIZE, player_scaling)), world_mouse_pos))
- {
- maybe_inviting_this_player = get_id(&gs, cur);
- interact_pressed = false;
- }
- }
-
- // Create and send input packet, and predict a frame of gamestate
- static InputFrame cur_input_frame = {
- 0}; // keep across frames for high refresh rate screens
- static size_t last_input_committed_tick = 0;
+ cpVect grid_pos;
+ double grid_rotation;
+ } build_preview = {0};
+ cpVect global_hand_pos = {0}; // world coords! world star!
+ bool hand_at_arms_length = false;
+ recalculate_camera_pos();
+ cpVect world_mouse_pos = screen_to_world(width, height, mouse_pos);
+ PROFILE_SCOPE("gameplay and prediction")
{
- // prepare the current input frame, such that when processed next,
- // every button/action the player has pressed will be handled
- // without frustration by the server. Resulting in authoritative game
- // state that looks and feels good.
-
- cpVect input = (cpVect){
- .x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A],
- .y = (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S],
- };
- 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];
+ // interpolate zoom
+ zoom = lerp(zoom, zoom_target, dt * 12.0);
- if (interact_pressed)
- cur_input_frame.seat_action = interact_pressed;
+ // calculate build preview stuff
+ cpVect local_hand_pos = {0};
+ global_hand_pos =
+ get_global_hand_pos(world_mouse_pos, &hand_at_arms_length);
- cur_input_frame.hand_pos = local_hand_pos;
- if (take_over_squad >= 0)
- {
- cur_input_frame.take_over_squad = take_over_squad;
- take_over_squad = -1;
- }
- if (confirm_invite_this_player)
- {
- cur_input_frame.invite_this_player = maybe_inviting_this_player;
- maybe_inviting_this_player = (EntityID){0};
- confirm_invite_this_player = false;
- }
- if (accept_invite)
- {
- cur_input_frame.accept_cur_squad_invite = true;
- accept_invite = false;
- }
- if (reject_invite)
+ if (myentity() != NULL)
{
- cur_input_frame.reject_cur_squad_invite = true;
- reject_invite = false;
+ local_hand_pos = cpvsub(global_hand_pos, entity_pos(myentity()));
}
- if (build_pressed && currently_building() != BoxInvalid)
- {
- cur_input_frame.dobuild = build_pressed;
- cur_input_frame.build_type = currently_building();
- cur_input_frame.build_rotation = cur_editing_rotation;
- }
+ // process player interaction (squad invites)
+ if (interact_pressed && myplayer() != NULL && myplayer()->squad != SquadNone)
+ ENTITIES_ITER(cur)
+ {
+ if (cur != myentity() && cur->is_player &&
+ has_point(centered_at(entity_pos(cur), cpvmult(PLAYER_SIZE, player_scaling)), world_mouse_pos))
+ {
+ maybe_inviting_this_player = get_id(&gs, cur);
+ interact_pressed = false;
+ }
+ }
- // "commit" the input. each input must be on a successive tick.
- if (tick(&gs) > last_input_committed_tick)
+ // Create and send input packet, and predict a frame of gamestate
+ static InputFrame cur_input_frame = {
+ 0}; // keep across frames for high refresh rate screens
+ static size_t last_input_committed_tick = 0;
{
- cur_input_frame.tick = tick(&gs);
- last_input_committed_tick = tick(&gs);
+ // prepare the current input frame, such that when processed next,
+ // every button/action the player has pressed will be handled
+ // without frustration by the server. Resulting in authoritative game
+ // state that looks and feels good.
+
+ cpVect input = (cpVect){
+ .x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A],
+ .y = (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S],
+ };
+ 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];
- InputFrame *to_push_to = queue_push_element(&input_queue);
- if (to_push_to == NULL)
+ if (interact_pressed)
+ cur_input_frame.seat_action = interact_pressed;
+
+ cur_input_frame.hand_pos = local_hand_pos;
+ if (take_over_squad >= 0)
+ {
+ cur_input_frame.take_over_squad = take_over_squad;
+ take_over_squad = -1;
+ }
+ if (confirm_invite_this_player)
{
- InputFrame *to_discard = queue_pop_element(&input_queue);
- (void)to_discard;
- to_push_to = queue_push_element(&input_queue);
- assert(to_push_to != NULL);
+ cur_input_frame.invite_this_player = maybe_inviting_this_player;
+ maybe_inviting_this_player = (EntityID){0};
+ confirm_invite_this_player = false;
+ }
+ if (accept_invite)
+ {
+ cur_input_frame.accept_cur_squad_invite = true;
+ accept_invite = false;
+ }
+ if (reject_invite)
+ {
+ cur_input_frame.reject_cur_squad_invite = true;
+ reject_invite = false;
}
- *to_push_to = cur_input_frame;
+ if (build_pressed && currently_building() != BoxInvalid)
+ {
+ cur_input_frame.dobuild = build_pressed;
+ cur_input_frame.build_type = currently_building();
+ cur_input_frame.build_rotation = cur_editing_rotation;
+ }
- if (myplayer() != NULL)
- myplayer()->input =
- cur_input_frame; // for the client side prediction!
+ // "commit" the input. each input must be on a successive tick.
+ if (tick(&gs) > last_input_committed_tick)
+ {
+ cur_input_frame.tick = tick(&gs);
+ last_input_committed_tick = tick(&gs);
- cur_input_frame = (InputFrame){0};
- cur_input_frame.take_over_squad =
- -1; // @Robust make this zero initialized
- }
+ InputFrame *to_push_to = queue_push_element(&input_queue);
+ if (to_push_to == NULL)
+ {
+ InputFrame *to_discard = queue_pop_element(&input_queue);
+ (void)to_discard;
+ to_push_to = queue_push_element(&input_queue);
+ assert(to_push_to != NULL);
+ }
- // in client side prediction, only process the latest in the queue, not
- // the one currently constructing.
- static double prediction_time_factor = 1.0;
- prediction_time_factor = lerp(prediction_time_factor,
- target_prediction_time_factor, dt * 3.0);
- process(&gs, dt * prediction_time_factor);
+ *to_push_to = cur_input_frame;
- static int64_t last_sent_input_time = 0;
- if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) >
- TIME_BETWEEN_INPUT_PACKETS)
- {
- ma_mutex_lock(&send_packets_mutex);
- ClientToServer to_send = {
- .mic_data = &packets_to_send,
- .input_data = &input_queue,
- };
- unsigned char serialized[MAX_CLIENT_TO_SERVER] = {0};
- size_t out_len = 0;
- if (client_to_server_serialize(&gs, &to_send, serialized, &out_len,
- MAX_CLIENT_TO_SERVER))
- {
- unsigned char compressed[MAX_CLIENT_TO_SERVER] = {0};
- char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = {0};
- size_t compressed_len = 0;
-
- lzo1x_1_compress(serialized, out_len, compressed, &compressed_len,
- (void *)lzo_working_mem);
-
- ENetPacket *packet =
- enet_packet_create((void *)compressed, compressed_len,
- ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
- enet_peer_send(peer, 0, packet); // @Robust error check this
- last_sent_input_time = stm_now();
+ if (myplayer() != NULL)
+ myplayer()->input =
+ cur_input_frame; // for the client side prediction!
+
+ cur_input_frame = (InputFrame){0};
+ cur_input_frame.take_over_squad =
+ -1; // @Robust make this zero initialized
}
- else
+
+ // in client side prediction, only process the latest in the queue, not
+ // the one currently constructing.
+ static double prediction_time_factor = 1.0;
+ prediction_time_factor = lerp(prediction_time_factor,
+ target_prediction_time_factor, dt * 3.0);
+ process(&gs, dt * prediction_time_factor, true);
+
+ static int64_t last_sent_input_time = 0;
+ if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) >
+ TIME_BETWEEN_INPUT_PACKETS)
{
- Log("Failed to serialize client to server!\n");
+ ma_mutex_lock(&send_packets_mutex);
+ ClientToServer to_send = {
+ .mic_data = &packets_to_send,
+ .input_data = &input_queue,
+ };
+ unsigned char serialized[MAX_CLIENT_TO_SERVER] = {0};
+ size_t out_len = 0;
+ if (client_to_server_serialize(&gs, &to_send, serialized, &out_len,
+ MAX_CLIENT_TO_SERVER))
+ {
+ unsigned char compressed[MAX_CLIENT_TO_SERVER] = {0};
+ char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = {0};
+ size_t compressed_len = 0;
+
+ lzo1x_1_compress(serialized, out_len, compressed, &compressed_len,
+ (void *)lzo_working_mem);
+
+ ENetPacket *packet =
+ enet_packet_create((void *)compressed, compressed_len,
+ ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
+ enet_peer_send(peer, 0, packet); // @Robust error check this
+ last_sent_input_time = stm_now();
+ }
+ else
+ {
+ Log("Failed to serialize client to server!\n");
+ }
+ ma_mutex_unlock(&send_packets_mutex);
}
- ma_mutex_unlock(&send_packets_mutex);
}
- }
- // calculate world position and camera
- recalculate_camera_pos();
- world_mouse_pos = screen_to_world(width, height, mouse_pos);
- global_hand_pos =
- get_global_hand_pos(world_mouse_pos, &hand_at_arms_length);
+ // calculate world position and camera
+ recalculate_camera_pos();
+ world_mouse_pos = screen_to_world(width, height, mouse_pos);
+ 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));
- if (placing_grid == NULL)
- {
- build_preview = (struct BuildPreviewInfo){
- .grid_pos = global_hand_pos,
- .grid_rotation = 0.0,
- };
- }
- else
- {
- global_hand_pos = grid_snapped_box_pos(placing_grid, global_hand_pos);
- build_preview = (struct BuildPreviewInfo){
- .grid_pos = entity_pos(placing_grid),
- .grid_rotation = entity_rotation(placing_grid),
- };
- }
- }
-
- // drawing
- {
- sgp_begin((int)width, (int)height);
- sgp_viewport(0, 0, (int)width, (int)height);
- sgp_project(0.0f, (float)width, 0.0f, (float)height);
- sgp_set_blend_mode(SGP_BLENDMODE_BLEND);
-
- // Draw background color
- set_color(colhexcode(0x000000));
- // set_color_values(0.1, 0.1, 0.1, 1.0);
- sgp_clear();
-
- // WORLD SPACE
- // world space coordinates are +Y up, -Y down. Like normal cartesian coords
- transform_scope
- {
- translate(width / 2, height / 2);
- scale_at(zoom, -zoom, 0.0, 0.0);
-
- // parllax layers, just the zooming, but not 100% of the camera panning
-#if 1 // space background
- transform_scope
+ Entity *placing_grid = box_grid(closest_box_to_point_in_radius(
+ &gs, global_hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP, NULL));
+ if (placing_grid == NULL)
{
- cpVect scaled_camera_pos = cpvmult(
- camera_pos, 0.0005); // this is how strong/weak the parallax is
- translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
- set_color(WHITE);
- sgp_set_image(0, image_stars);
- double stars_height_over_width =
- (float)sg_query_image_info(image_stars).height /
- (float)sg_query_image_info(image_stars).width;
- const double stars_width = 35.0;
- double stars_height = stars_width * stars_height_over_width;
- pipeline_scope(goodpixel_pipeline)
- draw_textured_rect(-stars_width / 2.0, -stars_height / 2.0, stars_width, stars_height);
- // draw_textured_rect(0, 0, stars_width, stars_height);
- sgp_reset_image(0);
+ build_preview = (struct BuildPreviewInfo){
+ .grid_pos = global_hand_pos,
+ .grid_rotation = 0.0,
+ };
}
- transform_scope
+ else
{
- cpVect scaled_camera_pos = cpvmult(
- camera_pos, 0.005); // this is how strong/weak the parallax is
- translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
- set_color(WHITE);
- sgp_set_image(0, image_stars2);
- double stars_height_over_width =
- (float)sg_query_image_info(image_stars).height /
- (float)sg_query_image_info(image_stars).width;
- const double stars_width = 35.0;
- double stars_height = stars_width * stars_height_over_width;
- pipeline_scope(goodpixel_pipeline)
- draw_textured_rect(-stars_width / 2.0, -stars_height / 2.0, stars_width, stars_height);
- // draw_textured_rect(0, 0, stars_width, stars_height);
- sgp_reset_image(0);
+ global_hand_pos = grid_snapped_box_pos(placing_grid, global_hand_pos);
+ build_preview = (struct BuildPreviewInfo){
+ .grid_pos = entity_pos(placing_grid),
+ .grid_rotation = entity_rotation(placing_grid),
+ };
}
-#endif
+ }
-#if 1 // parallaxed dots
- transform_scope
- {
- cpVect scaled_camera_pos = cpvmult(camera_pos, 0.25);
- translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
- set_color(WHITE);
- draw_dots(scaled_camera_pos, 3.0);
- }
+ // drawing
+ PROFILE_SCOPE("drawing")
+ {
+ sgp_begin((int)width, (int)height);
+ sgp_viewport(0, 0, (int)width, (int)height);
+ sgp_project(0.0f, (float)width, 0.0f, (float)height);
+ sgp_set_blend_mode(SGP_BLENDMODE_BLEND);
+
+ // Draw background color
+ set_color(colhexcode(0x000000));
+ // set_color_values(0.1, 0.1, 0.1, 1.0);
+ sgp_clear();
+
+ // WORLD SPACE
+ // world space coordinates are +Y up, -Y down. Like normal cartesian coords
transform_scope
{
- cpVect scaled_camera_pos = cpvmult(camera_pos, 0.5);
- translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
- set_color(WHITE);
- draw_dots(scaled_camera_pos, 2.0);
- }
+ translate(width / 2, height / 2);
+ scale_at(zoom, -zoom, 0.0, 0.0);
+
+ // parllax layers, just the zooming, but not 100% of the camera panning
+#if 1 // space background
+ transform_scope
+ {
+ cpVect scaled_camera_pos = cpvmult(
+ camera_pos, 0.0005); // this is how strong/weak the parallax is
+ translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
+ set_color(WHITE);
+ sgp_set_image(0, image_stars);
+ double stars_height_over_width =
+ (float)sg_query_image_info(image_stars).height /
+ (float)sg_query_image_info(image_stars).width;
+ const double stars_width = 35.0;
+ double stars_height = stars_width * stars_height_over_width;
+ pipeline_scope(goodpixel_pipeline)
+ draw_textured_rect(-stars_width / 2.0, -stars_height / 2.0, stars_width, stars_height);
+ // draw_textured_rect(0, 0, stars_width, stars_height);
+ sgp_reset_image(0);
+ }
+ transform_scope
+ {
+ cpVect scaled_camera_pos = cpvmult(
+ camera_pos, 0.005); // this is how strong/weak the parallax is
+ translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
+ set_color(WHITE);
+ sgp_set_image(0, image_stars2);
+ double stars_height_over_width =
+ (float)sg_query_image_info(image_stars).height /
+ (float)sg_query_image_info(image_stars).width;
+ const double stars_width = 35.0;
+ double stars_height = stars_width * stars_height_over_width;
+ pipeline_scope(goodpixel_pipeline)
+ draw_textured_rect(-stars_width / 2.0, -stars_height / 2.0, stars_width, stars_height);
+ // draw_textured_rect(0, 0, stars_width, stars_height);
+ sgp_reset_image(0);
+ }
#endif
- // camera go to player
- translate(-camera_pos.x, -camera_pos.y);
+#if 1 // parallaxed dots
+ transform_scope
+ {
+ cpVect scaled_camera_pos = cpvmult(camera_pos, 0.25);
+ translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
+ set_color(WHITE);
+ draw_dots(scaled_camera_pos, 3.0);
+ }
+ transform_scope
+ {
+ cpVect scaled_camera_pos = cpvmult(camera_pos, 0.5);
+ translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
+ set_color(WHITE);
+ draw_dots(scaled_camera_pos, 2.0);
+ }
+#endif
- draw_dots(camera_pos, 1.5); // in plane dots
+ // camera go to player
+ translate(-camera_pos.x, -camera_pos.y);
- // hand reached limit circle
- if (myentity() != NULL)
- {
- static double hand_reach_alpha = 1.0;
- hand_reach_alpha = lerp(hand_reach_alpha,
- hand_at_arms_length ? 1.0 : 0.0, dt * 5.0);
- set_color_values(1.0, 1.0, 1.0, hand_reach_alpha);
- draw_circle(entity_pos(myentity()), MAX_HAND_REACH);
- }
+ draw_dots(camera_pos, 1.5); // in plane dots
- // vision circle, what player can see
- if (myentity() != NULL)
- {
- set_color(colhexcode(0x4685e3));
- draw_circle(entity_pos(myentity()), VISION_RADIUS);
- }
+ // hand reached limit circle
+ if (myentity() != NULL)
+ {
+ static double hand_reach_alpha = 1.0;
+ hand_reach_alpha = lerp(hand_reach_alpha,
+ hand_at_arms_length ? 1.0 : 0.0, dt * 5.0);
+ set_color_values(1.0, 1.0, 1.0, hand_reach_alpha);
+ draw_circle(entity_pos(myentity()), MAX_HAND_REACH);
+ }
- double halfbox = BOX_SIZE / 2.0;
+ // vision circle, what player can see
+ if (myentity() != NULL)
+ {
+ set_color(colhexcode(0x4685e3));
+ draw_circle(entity_pos(myentity()), VISION_RADIUS);
+ }
- // mouse frozen, debugging tool
- if (mouse_frozen)
- {
- set_color_values(1.0, 0.0, 0.0, 0.5);
- draw_filled_rect(world_mouse_pos.x, world_mouse_pos.y, 0.1, 0.1);
- }
+ double halfbox = BOX_SIZE / 2.0;
- // building preview
- if (currently_building() != BoxInvalid && can_build(currently_building()))
- {
- set_color_values(0.5, 0.5, 0.5,
- (sin((float)time * 9.0) + 1.0) / 3.0 + 0.2);
+ // mouse frozen, debugging tool
+ if (mouse_frozen)
+ {
+ set_color_values(1.0, 0.0, 0.0, 0.5);
+ draw_filled_rect(world_mouse_pos.x, world_mouse_pos.y, 0.1, 0.1);
+ }
- transform_scope
+ // building preview
+ if (currently_building() != BoxInvalid && can_build(currently_building()))
{
- 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);
- pipeline_scope(goodpixel_pipeline)
- draw_texture_centered(global_hand_pos, BOX_SIZE);
- // drawbox(hand_pos, build_preview.grid_rotation, 0.0,
- // cur_editing_boxtype, cur_editing_rotation);
- sgp_reset_image(0);
+ 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);
+ pipeline_scope(goodpixel_pipeline)
+ draw_texture_centered(global_hand_pos, BOX_SIZE);
+ // drawbox(hand_pos, build_preview.grid_rotation, 0.0,
+ // cur_editing_boxtype, cur_editing_rotation);
+ sgp_reset_image(0);
+ }
}
- }
- player_scaling =
- lerp(player_scaling, zoom < 6.5 ? 100.0 : 1.0, dt * 7.0);
- ENTITIES_ITER(e)
- {
- // draw grid
- if (e->is_grid)
+ player_scaling =
+ lerp(player_scaling, zoom < 6.5 ? 100.0 : 1.0, dt * 7.0);
+ ENTITIES_ITER(e)
{
- Entity *g = e;
- BOXES_ITER(&gs, b, g)
+ // draw grid
+ if (e->is_grid)
{
- set_color_values(1.0, 1.0, 1.0, 1.0);
- if (b->box_type == BoxBattery)
- {
- double cur_alpha = sgp_get_color().a;
- Color from = WHITE;
- Color to = colhex(255, 0, 0);
- Color result =
- Collerp(from, to, b->energy_used / BATTERY_CAPACITY);
- set_color_values(result.r, result.g, result.b, cur_alpha);
- }
- transform_scope
+ Entity *g = e;
+ BOXES_ITER(&gs, b, g)
{
- rotate_at(entity_rotation(g) + rotangle(b->compass_rotation),
- entity_pos(b).x, entity_pos(b).y);
-
- if (b->box_type == BoxThruster)
+ set_color_values(1.0, 1.0, 1.0, 1.0);
+ if (b->box_type == BoxBattery)
{
- transform_scope
+ double cur_alpha = sgp_get_color().a;
+ Color from = WHITE;
+ Color to = colhex(255, 0, 0);
+ Color result =
+ Collerp(from, to, b->energy_used / BATTERY_CAPACITY);
+ set_color_values(result.r, result.g, result.b, cur_alpha);
+ }
+ transform_scope
+ {
+ rotate_at(entity_rotation(g) + rotangle(b->compass_rotation),
+ entity_pos(b).x, entity_pos(b).y);
+
+ if (b->box_type == BoxThruster)
{
- set_color_values(1.0, 1.0, 1.0, 1.0);
- sgp_set_image(0, image_thrusterburn);
- double scaling = 0.95 + lerp(0.0, 0.3, b->thrust);
- scale_at(scaling, 1.0, entity_pos(b).x, entity_pos(b).y);
- pipeline_scope(goodpixel_pipeline)
+ transform_scope
{
- draw_texture_centered(entity_pos(b), BOX_SIZE);
+ set_color_values(1.0, 1.0, 1.0, 1.0);
+ sgp_set_image(0, image_thrusterburn);
+ double scaling = 0.95 + lerp(0.0, 0.3, b->thrust);
+ scale_at(scaling, 1.0, entity_pos(b).x, entity_pos(b).y);
+ pipeline_scope(goodpixel_pipeline)
+ {
+ draw_texture_centered(entity_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->player_who_is_inside_of_me) != NULL)
+ img = image_cockpit_used;
+ }
+ if (b->box_type == BoxMedbay)
+ {
+ if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
+ img = image_medbay_used;
+ }
+ if (b->box_type == BoxSolarPanel)
+ {
+ sgp_set_image(0, image_solarpanel_charging);
+ set_color_values(1.0, 1.0, 1.0, b->sun_amount);
+ pipeline_scope(goodpixel_pipeline)
+ draw_texture_centered(entity_pos(b), BOX_SIZE);
sgp_reset_image(0);
+ set_color_values(1.0, 1.0, 1.0, 1.0 - b->sun_amount);
+ /* Color to_set = colhexcode(0xeb9834);
+ to_set.a = b->sun_amount * 0.5;
+ set_color(to_set);
+ draw_color_rect_centered(entity_pos(b), BOX_SIZE);
+ */
}
- }
- sg_image img = boxinfo(b->box_type).image;
- if (b->box_type == BoxCockpit)
- {
- if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
- img = image_cockpit_used;
- }
- if (b->box_type == BoxMedbay)
- {
- if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
- img = image_medbay_used;
- }
- if (b->box_type == BoxSolarPanel)
- {
- sgp_set_image(0, image_solarpanel_charging);
- set_color_values(1.0, 1.0, 1.0, b->sun_amount);
- pipeline_scope(goodpixel_pipeline)
+
+ sgp_set_image(0, img);
+ if (b->indestructible)
+ {
+ set_color_values(0.2, 0.2, 0.2, 1.0);
+ }
+ else if (b->is_platonic)
+ {
+ set_color(GOLD);
+ }
+
+ // all of these box types show team colors so are drawn with the hue shifting shader
+ // used with the player
+ if (b->box_type == BoxCloaking || b->box_type == BoxMissileLauncher)
+ {
+ pipeline_scope(hueshift_pipeline)
+ {
+ setup_hueshift(b->owning_squad);
draw_texture_centered(entity_pos(b), BOX_SIZE);
+ }
+ }
+ else
+ {
+ pipeline_scope(goodpixel_pipeline)
+ draw_texture_centered(entity_pos(b), BOX_SIZE);
+ }
sgp_reset_image(0);
- set_color_values(1.0, 1.0, 1.0, 1.0 - b->sun_amount);
- /* Color to_set = colhexcode(0xeb9834);
- to_set.a = b->sun_amount * 0.5;
- set_color(to_set);
+
+ if (b->box_type == BoxScanner)
+ {
+ sgp_set_image(0, image_scanner_head);
+ transform_scope
+ {
+ pipeline_scope(goodpixel_pipeline)
+ {
+ rotate_at(b->scanner_head_rotate, 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
+ if (b->box_type == BoxScanner)
+ {
+ set_color(BLUE);
+ draw_circle(entity_pos(b), SCANNER_RADIUS);
+ set_color(WHITE);
+ }
+ set_color_values(0.5, 0.1, 0.1, b->damage);
draw_color_rect_centered(entity_pos(b), BOX_SIZE);
- */
- }
- sgp_set_image(0, img);
- if (b->indestructible)
- {
- set_color_values(0.2, 0.2, 0.2, 1.0);
- }
- else if (b->is_platonic)
- {
- set_color(GOLD);
+ if (b->box_type == BoxCloaking)
+ {
+ set_color_values(1.0, 1.0, 1.0, b->cloaking_power);
+ sgp_set_image(0, image_cloaking_panel);
+ pipeline_scope(goodpixel_pipeline)
+ draw_texture_centered(entity_pos(b), CLOAKING_PANEL_SIZE);
+ sgp_reset_image(0);
+ }
}
- // all of these box types show team colors so are drawn with the hue shifting shader
- // used with the player
- if (b->box_type == BoxCloaking || b->box_type == BoxMissileLauncher)
+ // outside of the transform scope
+ if (b->box_type == BoxScanner)
{
- pipeline_scope(hueshift_pipeline)
+ if (b->platonic_detection_strength > 0.0)
{
- setup_hueshift(b->owning_squad);
- draw_texture_centered(entity_pos(b), BOX_SIZE);
+ set_color(colhexcode(0xf2d75c));
+ cpVect to = cpvadd(entity_pos(b), cpvmult(b->platonic_nearest_direction, b->platonic_detection_strength));
+ dbg_rect(to);
+ dbg_rect(entity_pos(b));
+ draw_line(entity_pos(b).x, entity_pos(b).y, to.x, to.y);
}
}
- else
+ if (b->box_type == BoxMissileLauncher)
{
- pipeline_scope(goodpixel_pipeline)
- draw_texture_centered(entity_pos(b), BOX_SIZE);
- }
- sgp_reset_image(0);
+ set_color(RED);
+ draw_circle(entity_pos(b), MISSILE_RANGE);
- if (b->box_type == BoxScanner)
- {
- sgp_set_image(0, image_scanner_head);
+ // draw the charging missile
transform_scope
{
- pipeline_scope(goodpixel_pipeline)
+ rotate_at(missile_launcher_target(&gs, b).facing_angle, entity_pos(b).x, entity_pos(b).y);
+ sgp_set_image(0, image_missile);
+ pipeline_scope(hueshift_pipeline)
{
- rotate_at(b->scanner_head_rotate, entity_pos(b).x, entity_pos(b).y);
+ set_color_values(1.0, 1.0, 1.0, b->missile_construction_charge);
+ setup_hueshift(b->owning_squad);
draw_texture_centered(entity_pos(b), BOX_SIZE);
}
+ sgp_reset_image(0);
}
- sgp_reset_image(0);
- set_color(WHITE);
}
+ }
+ }
- // scanner range, visualizes what scanner can scan
- if (b->box_type == BoxScanner)
+ // draw missile
+ if (e->is_missile)
+ {
+ transform_scope
+ {
+ rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
+ set_color_values(1.0, 1.0, 1.0, 1.0);
+
+ if (is_burning(e))
{
- set_color(BLUE);
- draw_circle(entity_pos(b), SCANNER_RADIUS);
- set_color(WHITE);
+ sgp_set_image(0, image_missile_burning);
}
- set_color_values(0.5, 0.1, 0.1, b->damage);
- draw_color_rect_centered(entity_pos(b), BOX_SIZE);
-
- if (b->box_type == BoxCloaking)
+ else
{
- set_color_values(1.0, 1.0, 1.0, b->cloaking_power);
- sgp_set_image(0, image_cloaking_panel);
- pipeline_scope(goodpixel_pipeline)
- draw_texture_centered(entity_pos(b), CLOAKING_PANEL_SIZE);
- sgp_reset_image(0);
+ sgp_set_image(0, image_missile);
}
- }
- // outside of the transform scope
- if (b->box_type == BoxScanner)
- {
- if (b->platonic_detection_strength > 0.0)
+ pipeline_scope(hueshift_pipeline)
{
- set_color(colhexcode(0xf2d75c));
- cpVect to = cpvadd(entity_pos(b), cpvmult(b->platonic_nearest_direction, b->platonic_detection_strength));
- dbg_rect(to);
- dbg_rect(entity_pos(b));
- draw_line(entity_pos(b).x, entity_pos(b).y, to.x, to.y);
+ setup_hueshift(e->owning_squad);
+ draw_texture_rectangle_centered(entity_pos(e), MISSILE_SPRITE_SIZE);
}
+
+ sgp_reset_image(0);
}
- if (b->box_type == BoxMissileLauncher)
+ }
+
+ // draw player
+ if (e->is_player &&
+ get_entity(&gs, e->currently_inside_of_box) == NULL)
+ {
+ transform_scope
{
- set_color(RED);
- draw_circle(entity_pos(b), MISSILE_RANGE);
+ rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
+ set_color_values(1.0, 1.0, 1.0, 1.0);
- // draw the charging missile
- transform_scope
+ pipeline_scope(hueshift_pipeline)
{
- rotate_at(missile_launcher_target(&gs, b).facing_angle, entity_pos(b).x, entity_pos(b).y);
- sgp_set_image(0, image_missile);
- pipeline_scope(hueshift_pipeline)
- {
- set_color_values(1.0, 1.0, 1.0, b->missile_construction_charge);
- setup_hueshift(b->owning_squad);
- draw_texture_centered(entity_pos(b), BOX_SIZE);
- }
+ setup_hueshift(e->owning_squad);
+ sgp_set_image(0, image_player);
+ draw_texture_rectangle_centered(
+ entity_pos(e), cpvmult(PLAYER_SIZE, player_scaling));
sgp_reset_image(0);
}
}
}
- }
-
- // draw missile
- if (e->is_missile)
- {
- transform_scope
+ if (e->is_explosion)
{
- rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
- set_color_values(1.0, 1.0, 1.0, 1.0);
-
- if (is_burning(e))
- {
- sgp_set_image(0, image_missile_burning);
- }
- else
- {
- sgp_set_image(0, image_missile);
- }
-
- pipeline_scope(hueshift_pipeline)
- {
- setup_hueshift(e->owning_squad);
- draw_texture_rectangle_centered(entity_pos(e), MISSILE_SPRITE_SIZE);
- }
-
+ sgp_set_image(0, image_explosion);
+ set_color_values(1.0, 1.0, 1.0,
+ 1.0 - (e->explosion_progress / EXPLOSION_TIME));
+ draw_texture_centered(e->explosion_pos, e->explosion_radius * 2.0);
sgp_reset_image(0);
}
}
- // draw player
- if (e->is_player &&
- get_entity(&gs, e->currently_inside_of_box) == NULL)
+ // 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);
+
+ // the SUN
+ SUNS_ITER(&gs)
{
transform_scope
{
- rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
- set_color_values(1.0, 1.0, 1.0, 1.0);
+ 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);
+ sgp_reset_image(0);
- pipeline_scope(hueshift_pipeline)
- {
- setup_hueshift(e->owning_squad);
- sgp_set_image(0, image_player);
- draw_texture_rectangle_centered(
- entity_pos(e), cpvmult(PLAYER_SIZE, player_scaling));
- sgp_reset_image(0);
- }
+ // can draw at 0,0 because everything relative to sun now!
+
+ // sun DEATH RADIUS
+
+ set_color(BLUE);
+ draw_circle((cpVect){0}, sun_dist_no_gravity(i.sun));
}
}
- if (e->is_explosion)
- {
- sgp_set_image(0, image_explosion);
- set_color_values(1.0, 1.0, 1.0,
- 1.0 - (e->explosion_progress / EXPLOSION_TIME));
- draw_texture_centered(e->explosion_pos, e->explosion_radius * 2.0);
- sgp_reset_image(0);
- }
- }
- // 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);
+ set_color_values(1.0, 1.0, 1.0, 1.0);
+ dbg_drawall();
+ } // world space transform end
- // the SUN
- SUNS_ITER(&gs)
+ // low health
+ if (myentity() != NULL)
{
- transform_scope
- {
- 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);
- sgp_reset_image(0);
-
- // can draw at 0,0 because everything relative to sun now!
-
- // sun DEATH RADIUS
-
- set_color(BLUE);
- draw_circle((cpVect){0}, sun_dist_no_gravity(i.sun));
- }
+ set_color_values(1.0, 1.0, 1.0, myentity()->damage);
+ sgp_set_image(0, image_low_health);
+ draw_texture_rectangle_centered((cpVect){width / 2.0, height / 2.0},
+ (cpVect){width, height});
+ sgp_reset_image(0);
}
- set_color_values(1.0, 1.0, 1.0, 1.0);
- dbg_drawall();
- } // world space transform end
-
- // low health
- if (myentity() != NULL)
- {
- set_color_values(1.0, 1.0, 1.0, myentity()->damage);
- sgp_set_image(0, image_low_health);
- draw_texture_rectangle_centered((cpVect){width / 2.0, height / 2.0},
- (cpVect){width, height});
- sgp_reset_image(0);
+ // UI drawn in screen space
+ ui(true, dt, width, height);
}
- // UI drawn in screen space
- ui(true, dt, width, height);
+ sg_pass_action pass_action = {0};
+ sg_begin_default_pass(&pass_action, (int)width, (int)height);
+ sgp_flush();
+ sgp_end();
+ sg_end_pass();
+ sg_commit();
}
-
- sg_pass_action pass_action = {0};
- sg_begin_default_pass(&pass_action, (int)width, (int)height);
- sgp_flush();
- sgp_end();
- sg_end_pass();
- sg_commit();
}
void cleanup(void)
@@ -2130,6 +2155,9 @@ void cleanup(void)
ma_mutex_unlock(&server_info.info_mutex);
WaitForSingleObject(server_thread_handle, INFINITE);
+ end_profiling_mythread();
+ end_profiling();
+
ma_mutex_uninit(&send_packets_mutex);
ma_mutex_uninit(&play_packets_mutex);
@@ -2248,6 +2276,8 @@ sapp_desc sokol_main(int argc, char *argv[])
stm_setup();
ma_mutex_init(&server_info.info_mutex);
server_info.world_save = "debug_world.bin";
+ init_profiling("astris.spall");
+ init_profiling_mythread(0);
if (argc > 1)
{
server_thread_handle =
diff --git a/profiling.h b/profiling.h
new file mode 100644
index 0000000..8f692ca
--- /dev/null
+++ b/profiling.h
@@ -0,0 +1,98 @@
+#include "types.h"
+#include // malloc the profiling buffer
+
+#ifdef PROFILING_H
+#error only include profiling.h once
+#endif
+#define PROFILING_H
+
+#ifdef PROFILING
+#define PROFILING_BUFFER_SIZE (1 * 1024 * 1024)
+
+#ifdef PROFILING_IMPL
+#define SPALL_IMPLEMENTATION
+#pragma warning(disable : 4996) // spall uses fopen
+#include "spall.h"
+
+#define WIN32_LEAN_AND_MEAN
+#define VC_EXTRALEAN
+#define NOMINMAX
+#include
+// This is slow, if you can use RDTSC and set the multiplier in SpallInit, you'll have far better timing accuracy
+double get_time_in_micros()
+{
+ static double invfreq;
+ if (!invfreq)
+ {
+ LARGE_INTEGER frequency;
+ QueryPerformanceFrequency(&frequency);
+ invfreq = 1000000.0 / frequency.QuadPart;
+ }
+ LARGE_INTEGER counter;
+ QueryPerformanceCounter(&counter);
+ return counter.QuadPart * invfreq;
+}
+SpallProfile spall_ctx;
+THREADLOCAL SpallBuffer spall_buffer;
+THREADLOCAL unsigned char *buffer_data = NULL;
+THREADLOCAL uint32_t my_thread_id = 0;
+
+void init_profiling(const char *filename)
+{
+ spall_ctx = SpallInit(filename, 1);
+}
+
+void init_profiling_mythread(uint32_t id)
+{
+ my_thread_id = id;
+ if (buffer_data != NULL)
+ {
+ *(int *)0 = 0;
+ }
+ buffer_data = malloc(PROFILING_BUFFER_SIZE);
+ spall_buffer = (SpallBuffer){
+ .length = PROFILING_BUFFER_SIZE,
+ .data = buffer_data,
+ };
+ SpallBufferInit(&spall_ctx, &spall_buffer);
+}
+
+void end_profiling_mythread()
+{
+ SpallBufferFlush(&spall_ctx, &spall_buffer);
+ SpallBufferQuit(&spall_ctx, &spall_buffer);
+ free(buffer_data);
+}
+
+void end_profiling()
+{
+ SpallQuit(&spall_ctx);
+}
+
+#endif // PROFILING_IMPL
+
+#include "spall.h"
+
+extern SpallProfile spall_ctx;
+extern THREADLOCAL SpallBuffer spall_buffer;
+extern THREADLOCAL uint32_t my_thread_id;
+
+double get_time_in_micros();
+void init_profiling(const char *filename);
+// you can pass anything to id as long as it's different from other threads
+void init_profiling_mythread(uint32_t id);
+void end_profiling();
+void end_profiling_mythread();
+
+#define PROFILE_SCOPE(name) DeferLoop(SpallTraceBeginLenTidPid(&spall_ctx, &spall_buffer, name, sizeof(name) - 1, my_thread_id, 0, get_time_in_micros()), SpallTraceEndTidPid(&spall_ctx, &spall_buffer, my_thread_id, 0, get_time_in_micros()))
+
+#else // PROFILING
+
+void inline init_profiling(const char *filename) { (void)filename; }
+// you can pass anything to id as long as it's different from other threads
+void inline init_profiling_mythread(uint32_t id) { (void)id; }
+void inline end_profiling() {}
+void inline end_profiling_mythread() {}
+
+#define PROFILE_SCOPE(name)
+#endif
diff --git a/server.c b/server.c
index d8818d8..da8b945 100644
--- a/server.c
+++ b/server.c
@@ -1,5 +1,5 @@
+#include "chipmunk.h" // initializing bodies
#include "sokol_time.h"
-#include // initializing bodies
#include "types.h"
#include
#include
@@ -20,40 +20,7 @@
for (ENetPeer *cur = host->peers; cur < host->peers + host->peerCount; cur++) \
if (cur->state == ENET_PEER_STATE_CONNECTED)
-#ifdef PROFILING
-
-#define SPALL_IMPLEMENTATION
-#pragma warning(disable : 4996) // spall uses fopen
-#include "spall.h"
-
-#define WIN32_LEAN_AND_MEAN
-#define VC_EXTRALEAN
-#define NOMINMAX
-#include
-// This is slow, if you can use RDTSC and set the multiplier in SpallInit, you'll have far better timing accuracy
-double get_time_in_micros()
-{
- static double invfreq;
- if (!invfreq)
- {
- LARGE_INTEGER frequency;
- QueryPerformanceFrequency(&frequency);
- invfreq = 1000000.0 / frequency.QuadPart;
- }
- LARGE_INTEGER counter;
- QueryPerformanceCounter(&counter);
- return counter.QuadPart * invfreq;
-}
-
-static SpallProfile spall_ctx;
-static SpallBuffer spall_buffer;
-
-#define PROFILE_SCOPE(name) DeferLoop(SpallTraceBeginLenTidPid(&spall_ctx, &spall_buffer, name, sizeof(name) - 1, 0, 0, get_time_in_micros()), SpallTraceEndTidPid(&spall_ctx, &spall_buffer, 0, 0, get_time_in_micros()))
-#else // PROFILING
-
-#define PROFILE_SCOPE(name)
-
-#endif
+#include "profiling.h"
// started in a thread from host
void server(void *info_raw)
@@ -61,14 +28,6 @@ void server(void *info_raw)
ServerThreadInfo *info = (ServerThreadInfo *)info_raw;
const char *world_save_name = info->world_save;
#ifdef PROFILING
-#define BUFFER_SIZE (1 * 1024 * 1024)
- spall_ctx = SpallInit("server.spall", 1);
- unsigned char *buffer = malloc(BUFFER_SIZE);
- spall_buffer = (SpallBuffer){
- .length = BUFFER_SIZE,
- .data = buffer,
- };
- SpallBufferInit(&spall_ctx, &spall_buffer);
#endif
@@ -79,8 +38,10 @@ void server(void *info_raw)
gs.server_side_computing = true;
Log("Allocated %zu bytes for entities\n", entities_size);
+ init_profiling_mythread(1);
+
create_initial_world(&gs);
-
+
// inputs
Queue player_input_queues[MAX_PLAYERS] = {0};
size_t input_queue_data_size = QUEUE_SIZE_FOR_ELEMENTS(sizeof(InputFrame), INPUT_QUEUE_MAX);
@@ -95,9 +56,9 @@ void server(void *info_raw)
OpusEncoder *player_encoders[MAX_PLAYERS] = {0};
OpusDecoder *player_decoders[MAX_PLAYERS] = {0};
- #ifdef DEBUG_WORLD
+#ifdef DEBUG_WORLD
world_save_name = NULL;
- #endif
+#endif
if (world_save_name != NULL)
{
size_t read_game_data_buffer_size = entities_size;
@@ -199,333 +160,336 @@ void server(void *info_raw)
double audio_time_to_send = 0.0;
double total_time = 0.0;
unsigned char *world_save_buffer = malloc(entities_size);
- while (true)
+ PROFILE_SCOPE("Serving")
{
- ma_mutex_lock(&info->info_mutex);
- if (info->should_quit)
- {
- ma_mutex_unlock(&info->info_mutex);
- break;
- }
- ma_mutex_unlock(&info->info_mutex);
-
- // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
while (true)
{
- int ret = enet_host_service(enet_host, &event, 0);
- if (ret == 0)
- break;
- if (ret < 0)
+ ma_mutex_lock(&info->info_mutex);
+ if (info->should_quit)
{
- fprintf(stderr, "Enet host service error %d\n", ret);
+ ma_mutex_unlock(&info->info_mutex);
+ break;
}
- if (ret > 0)
+ ma_mutex_unlock(&info->info_mutex);
+
+ // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
+ while (true)
{
- switch (event.type)
+ int ret = enet_host_service(enet_host, &event, 0);
+ if (ret == 0)
+ break;
+ if (ret < 0)
{
- case ENET_EVENT_TYPE_CONNECT:
+ fprintf(stderr, "Enet host service error %d\n", ret);
+ }
+ if (ret > 0)
{
- Log("A new client connected from %x:%u.\n",
- event.peer->address.host,
- event.peer->address.port);
-
- int64_t player_slot = -1;
- for (int i = 0; i < MAX_PLAYERS; i++)
+ switch (event.type)
{
- if (!gs.players[i].connected)
+ case ENET_EVENT_TYPE_CONNECT:
+ {
+ Log("A new client connected from %x:%u.\n",
+ event.peer->address.host,
+ event.peer->address.port);
+
+ int64_t player_slot = -1;
+ for (int i = 0; i < MAX_PLAYERS; i++)
{
- player_slot = i;
- break;
+ if (!gs.players[i].connected)
+ {
+ player_slot = i;
+ break;
+ }
}
- }
- if (player_slot == -1)
- {
- enet_peer_disconnect_now(event.peer, 69);
- }
- else
- {
- event.peer->data = (void *)player_slot;
- gs.players[player_slot] = (struct Player){0};
- gs.players[player_slot].connected = true;
- create_player(&gs.players[player_slot]);
-
- int error;
- player_encoders[player_slot] = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
- if (error != OPUS_OK)
- Log("Failed to create encoder: %d\n", error);
- player_decoders[player_slot] = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
- if (error != OPUS_OK)
- Log("Failed to create decoder: %d\n", error);
+ if (player_slot == -1)
+ {
+ enet_peer_disconnect_now(event.peer, 69);
+ }
+ else
+ {
+ event.peer->data = (void *)player_slot;
+ gs.players[player_slot] = (struct Player){0};
+ gs.players[player_slot].connected = true;
+ create_player(&gs.players[player_slot]);
+
+ int error;
+ player_encoders[player_slot] = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
+ if (error != OPUS_OK)
+ Log("Failed to create encoder: %d\n", error);
+ player_decoders[player_slot] = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
+ if (error != OPUS_OK)
+ Log("Failed to create decoder: %d\n", error);
+ }
}
- }
- break;
+ break;
- case ENET_EVENT_TYPE_RECEIVE:
- {
- // Log("A packet of length %zu was received on channel %u.\n",
- // event.packet->dataLength,
- // event.channelID);
- if (event.packet->dataLength == 0)
+ case ENET_EVENT_TYPE_RECEIVE:
{
- Log("Wtf an empty packet from enet?\n");
- }
- else
- {
- int64_t player_slot = (int64_t)event.peer->data;
- size_t length = event.packet->dataLength;
+ // Log("A packet of length %zu was received on channel %u.\n",
+ // event.packet->dataLength,
+ // event.channelID);
+ if (event.packet->dataLength == 0)
+ {
+ Log("Wtf an empty packet from enet?\n");
+ }
+ else
+ {
+ int64_t player_slot = (int64_t)event.peer->data;
+ size_t length = event.packet->dataLength;
#define VOIP_QUEUE_DECL(queue_name, queue_data_name) \
Queue queue_name = {0}; \
char queue_data_name[QUEUE_SIZE_FOR_ELEMENTS(sizeof(OpusPacket), VOIP_PACKET_BUFFER_SIZE)] = {0}; \
queue_init(&queue_name, sizeof(OpusPacket), queue_data_name, QUEUE_SIZE_FOR_ELEMENTS(sizeof(OpusPacket), VOIP_PACKET_BUFFER_SIZE))
- VOIP_QUEUE_DECL(throwaway_buffer, throwaway_buffer_data);
- Queue *buffer_to_fill = &player_voip_buffers[player_slot];
- if (get_entity(&gs, gs.players[player_slot].entity) == NULL)
- buffer_to_fill = &throwaway_buffer;
+ VOIP_QUEUE_DECL(throwaway_buffer, throwaway_buffer_data);
+ Queue *buffer_to_fill = &player_voip_buffers[player_slot];
+ if (get_entity(&gs, gs.players[player_slot].entity) == NULL)
+ buffer_to_fill = &throwaway_buffer;
- queue_clear(&player_input_queues[player_slot]);
- struct ClientToServer received = {.mic_data = buffer_to_fill, .input_data = &player_input_queues[player_slot]};
- unsigned char decompressed[MAX_CLIENT_TO_SERVER] = {0};
- size_t decompressed_max_len = MAX_CLIENT_TO_SERVER;
- assert(LZO1X_MEM_DECOMPRESS == 0);
+ queue_clear(&player_input_queues[player_slot]);
+ struct ClientToServer received = {.mic_data = buffer_to_fill, .input_data = &player_input_queues[player_slot]};
+ unsigned char decompressed[MAX_CLIENT_TO_SERVER] = {0};
+ size_t decompressed_max_len = MAX_CLIENT_TO_SERVER;
+ assert(LZO1X_MEM_DECOMPRESS == 0);
- int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL);
+ int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL);
- if (return_value == LZO_E_OK)
- {
- if (!client_to_server_deserialize(&gs, &received, decompressed, decompressed_max_len))
+ if (return_value == LZO_E_OK)
+ {
+ if (!client_to_server_deserialize(&gs, &received, decompressed, decompressed_max_len))
+ {
+ Log("Bad packet from client %d\n", (int)player_slot);
+ }
+ }
+ else
{
- Log("Bad packet from client %d\n", (int)player_slot);
+ Log("Couldn't decompress player packet, error code %d from lzo\n", return_value);
}
}
- else
+ /* Clean up the packet now that we're done using it. */
+ enet_packet_destroy(event.packet);
+ }
+ break;
+
+ case ENET_EVENT_TYPE_DISCONNECT:
+ {
+ int player_index = (int)(int64_t)event.peer->data;
+ Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
+ Entity *player_body = get_entity(&gs, gs.players[player_index].entity);
+ if (player_body != NULL)
{
- Log("Couldn't decompress player packet, error code %d from lzo\n", return_value);
+ entity_destroy(&gs, player_body);
}
+ opus_encoder_destroy(player_encoders[player_index]);
+ player_encoders[player_index] = NULL;
+ opus_decoder_destroy(player_decoders[player_index]);
+ player_decoders[player_index] = NULL;
+ gs.players[player_index].connected = false;
+ queue_clear(&player_voip_buffers[player_index]);
+ event.peer->data = NULL;
}
- /* Clean up the packet now that we're done using it. */
- enet_packet_destroy(event.packet);
- }
- break;
+ break;
- case ENET_EVENT_TYPE_DISCONNECT:
- {
- int player_index = (int)(int64_t)event.peer->data;
- Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
- Entity *player_body = get_entity(&gs, gs.players[player_index].entity);
- if (player_body != NULL)
+ case ENET_EVENT_TYPE_NONE:
{
- entity_destroy(&gs, player_body);
}
- opus_encoder_destroy(player_encoders[player_index]);
- player_encoders[player_index] = NULL;
- opus_decoder_destroy(player_decoders[player_index]);
- player_decoders[player_index] = NULL;
- gs.players[player_index].connected = false;
- queue_clear(&player_voip_buffers[player_index]);
- event.peer->data = NULL;
- }
- break;
-
- case ENET_EVENT_TYPE_NONE:
- {
- }
- break;
+ break;
+ }
}
}
- }
- total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time));
- last_processed_time = stm_now();
- // @Robost @BeforeShip if can't process quick enough will be stuck being lagged behind, think of a solution for this...
- const double max_time = 5.0 * TIMESTEP;
- if (total_time > max_time)
- {
- Log("Abnormally large total time %f, clamping\n", total_time);
- total_time = max_time;
- }
+ total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time));
+ last_processed_time = stm_now();
+ // @Robost @BeforeShip if can't process quick enough will be stuck being lagged behind, think of a solution for this...
+ const double max_time = 5.0 * TIMESTEP;
+ if (total_time > max_time)
+ {
+ Log("Abnormally large total time %f, clamping\n", total_time);
+ total_time = max_time;
+ }
- PROFILE_SCOPE("World Processing")
- {
while (total_time > TIMESTEP)
{
- CONNECTED_PEERS(enet_host, cur)
+ PROFILE_SCOPE("World Processing")
{
- int this_player_index = (int)(int64_t)cur->data;
- QUEUE_ITER(&player_input_queues[this_player_index], cur_header)
+ CONNECTED_PEERS(enet_host, cur)
{
- InputFrame *cur = (InputFrame *)cur_header->data;
- if (cur->tick == tick(&gs))
+ int this_player_index = (int)(int64_t)cur->data;
+ QUEUE_ITER(&player_input_queues[this_player_index], cur_header)
{
- gs.players[this_player_index].input = *cur;
- break;
+ InputFrame *cur = (InputFrame *)cur_header->data;
+ if (cur->tick == tick(&gs))
+ {
+ gs.players[this_player_index].input = *cur;
+ break;
+ }
}
}
- }
- process(&gs, TIMESTEP);
- total_time -= TIMESTEP;
+ process(&gs, TIMESTEP, false);
+ total_time -= TIMESTEP;
+ }
}
- }
- if (world_save_name != NULL && (stm_sec(stm_diff(stm_now(), last_saved_world_time))) > TIME_BETWEEN_WORLD_SAVE)
- {
- PROFILE_SCOPE("Save World")
+ if (world_save_name != NULL && (stm_sec(stm_diff(stm_now(), last_saved_world_time))) > TIME_BETWEEN_WORLD_SAVE)
{
- last_saved_world_time = stm_now();
- ServerToClient msg = (ServerToClient){
- .cur_gs = &gs,
- };
- size_t out_len = 0;
- if (server_to_client_serialize(&msg, world_save_buffer, &out_len, entities_size, NULL, true))
+ PROFILE_SCOPE("Save World")
{
- FILE *save_file = NULL;
- fopen_s(&save_file, (const char *)world_save_name, "wb");
- if (save_file == NULL)
- {
- Log("Could not open save file: errno %d\n", errno);
- }
- else
+ last_saved_world_time = stm_now();
+ ServerToClient msg = (ServerToClient){
+ .cur_gs = &gs,
+ };
+ size_t out_len = 0;
+ if (server_to_client_serialize(&msg, world_save_buffer, &out_len, entities_size, NULL, true))
{
- size_t data_written = fwrite(world_save_buffer, sizeof(*world_save_buffer), out_len, save_file);
- if (data_written != out_len)
+ FILE *save_file = NULL;
+ fopen_s(&save_file, (const char *)world_save_name, "wb");
+ if (save_file == NULL)
{
- Log("Failed to save world data, wanted to write %zu but could only write %zu\n", out_len, data_written);
+ Log("Could not open save file: errno %d\n", errno);
}
else
{
- Log("Saved game world to %s\n", (const char *)world_save_name);
+ size_t data_written = fwrite(world_save_buffer, sizeof(*world_save_buffer), out_len, save_file);
+ if (data_written != out_len)
+ {
+ Log("Failed to save world data, wanted to write %zu but could only write %zu\n", out_len, data_written);
+ }
+ else
+ {
+ Log("Saved game world to %s\n", (const char *)world_save_name);
+ }
+ fclose(save_file);
}
- fclose(save_file);
}
- }
- else
- {
- Log("URGENT: FAILED TO SAVE WORLD FILE!\n");
+ else
+ {
+ Log("URGENT: FAILED TO SAVE WORLD FILE!\n");
+ }
}
}
- }
- if (stm_sec(stm_diff(stm_now(), last_sent_gamestate_time)) > TIME_BETWEEN_SEND_GAMESTATE)
- {
- last_sent_gamestate_time = stm_now();
- PROFILE_SCOPE("send_data")
+ if (stm_sec(stm_diff(stm_now(), last_sent_gamestate_time)) > TIME_BETWEEN_SEND_GAMESTATE)
{
- static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = {0};
+ last_sent_gamestate_time = stm_now();
+ PROFILE_SCOPE("send_data")
+ {
+ static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = {0};
- audio_time_to_send += (float)stm_sec(stm_diff(stm_now(), last_sent_audio_time));
- last_sent_audio_time = stm_now();
- int num_audio_packets = (int)floor(1.0 / (VOIP_TIME_PER_PACKET / audio_time_to_send));
+ audio_time_to_send += (float)stm_sec(stm_diff(stm_now(), last_sent_audio_time));
+ last_sent_audio_time = stm_now();
+ int num_audio_packets = (int)floor(1.0 / (VOIP_TIME_PER_PACKET / audio_time_to_send));
#define MAX_AUDIO_PACKETS_TO_SEND 12
- if (num_audio_packets > MAX_AUDIO_PACKETS_TO_SEND)
- {
- Log("Wants %d, this is too many packets. Greater than the maximum %d\n", num_audio_packets, MAX_AUDIO_PACKETS_TO_SEND);
- num_audio_packets = MAX_AUDIO_PACKETS_TO_SEND;
- }
+ if (num_audio_packets > MAX_AUDIO_PACKETS_TO_SEND)
+ {
+ Log("Wants %d, this is too many packets. Greater than the maximum %d\n", num_audio_packets, MAX_AUDIO_PACKETS_TO_SEND);
+ num_audio_packets = MAX_AUDIO_PACKETS_TO_SEND;
+ }
- opus_int16 decoded_audio_packets[MAX_PLAYERS][MAX_AUDIO_PACKETS_TO_SEND][VOIP_EXPECTED_FRAME_COUNT] = {0};
+ opus_int16 decoded_audio_packets[MAX_PLAYERS][MAX_AUDIO_PACKETS_TO_SEND][VOIP_EXPECTED_FRAME_COUNT] = {0};
- audio_time_to_send -= num_audio_packets * VOIP_TIME_PER_PACKET;
+ audio_time_to_send -= num_audio_packets * VOIP_TIME_PER_PACKET;
- // decode what everybody said
- CONNECTED_PEERS(enet_host, cur)
- {
- int this_player_index = (int)(int64_t)cur->data;
- for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
+ // decode what everybody said
+ CONNECTED_PEERS(enet_host, cur)
{
- opus_int16 *to_dump_to = decoded_audio_packets[this_player_index][packet_i];
- OpusPacket *cur_packet = (OpusPacket *)queue_pop_element(&player_voip_buffers[this_player_index]);
- if (cur_packet == NULL)
- opus_decode(player_decoders[this_player_index], NULL, 0, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
- else
- opus_decode(player_decoders[this_player_index], cur_packet->data, cur_packet->length, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
+ int this_player_index = (int)(int64_t)cur->data;
+ for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
+ {
+ opus_int16 *to_dump_to = decoded_audio_packets[this_player_index][packet_i];
+ OpusPacket *cur_packet = (OpusPacket *)queue_pop_element(&player_voip_buffers[this_player_index]);
+ if (cur_packet == NULL)
+ opus_decode(player_decoders[this_player_index], NULL, 0, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
+ else
+ opus_decode(player_decoders[this_player_index], cur_packet->data, cur_packet->length, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
+ }
}
- }
- // send gamestate to each player
- CONNECTED_PEERS(enet_host, cur)
- {
- int this_player_index = (int)(int64_t)cur->data;
- Entity *this_player_entity = get_entity(&gs, gs.players[this_player_index].entity);
- if (this_player_entity == NULL)
- continue;
- // @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
- unsigned char *bytes_buffer = malloc(sizeof *bytes_buffer * MAX_SERVER_TO_CLIENT);
- unsigned char *compressed_buffer = malloc(sizeof *compressed_buffer * MAX_SERVER_TO_CLIENT);
-
- // mix audio to be sent
- VOIP_QUEUE_DECL(buffer_to_play, buffer_to_play_data);
+ // send gamestate to each player
+ CONNECTED_PEERS(enet_host, cur)
{
- for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
+ int this_player_index = (int)(int64_t)cur->data;
+ Entity *this_player_entity = get_entity(&gs, gs.players[this_player_index].entity);
+ if (this_player_entity == NULL)
+ continue;
+ // @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
+ unsigned char *bytes_buffer = malloc(sizeof *bytes_buffer * MAX_SERVER_TO_CLIENT);
+ unsigned char *compressed_buffer = malloc(sizeof *compressed_buffer * MAX_SERVER_TO_CLIENT);
+
+ // mix audio to be sent
+ VOIP_QUEUE_DECL(buffer_to_play, buffer_to_play_data);
{
- opus_int16 to_send_to_cur[VOIP_EXPECTED_FRAME_COUNT] = {0}; // mix what other players said into this buffer
- CONNECTED_PEERS(enet_host, other_player)
+ for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
{
- if (other_player != cur)
+ opus_int16 to_send_to_cur[VOIP_EXPECTED_FRAME_COUNT] = {0}; // mix what other players said into this buffer
+ CONNECTED_PEERS(enet_host, other_player)
{
- int other_player_index = (int)(int64_t)other_player->data;
- Entity *other_player_entity = get_entity(&gs, gs.players[other_player_index].entity);
- if (other_player_entity != NULL)
+ if (other_player != cur)
{
- double dist = cpvdist(entity_pos(this_player_entity), entity_pos(other_player_entity));
- double volume = lerp(1.0, 0.0, clamp01(dist / VOIP_DISTANCE_WHEN_CANT_HEAR));
- if (volume > 0.01)
+ int other_player_index = (int)(int64_t)other_player->data;
+ Entity *other_player_entity = get_entity(&gs, gs.players[other_player_index].entity);
+ if (other_player_entity != NULL)
{
- for (int frame_i = 0; frame_i < VOIP_EXPECTED_FRAME_COUNT; frame_i++)
+ double dist = cpvdist(entity_pos(this_player_entity), entity_pos(other_player_entity));
+ double volume = lerp(1.0, 0.0, clamp01(dist / VOIP_DISTANCE_WHEN_CANT_HEAR));
+ if (volume > 0.01)
{
- to_send_to_cur[frame_i] += (opus_int16)((float)decoded_audio_packets[other_player_index][packet_i][frame_i] * volume);
+ for (int frame_i = 0; frame_i < VOIP_EXPECTED_FRAME_COUNT; frame_i++)
+ {
+ to_send_to_cur[frame_i] += (opus_int16)((float)decoded_audio_packets[other_player_index][packet_i][frame_i] * volume);
+ }
}
}
}
}
- }
- OpusPacket *this_packet = (OpusPacket *)queue_push_element(&buffer_to_play);
- opus_int32 ret = opus_encode(player_encoders[this_player_index], to_send_to_cur, VOIP_EXPECTED_FRAME_COUNT, this_packet->data, VOIP_PACKET_MAX_SIZE);
- if (ret < 0)
- {
- Log("Failed to encode audio packet for player %d: opus error code %d\n", this_player_index, ret);
- }
- else
- {
- this_packet->length = ret;
+ OpusPacket *this_packet = (OpusPacket *)queue_push_element(&buffer_to_play);
+ opus_int32 ret = opus_encode(player_encoders[this_player_index], to_send_to_cur, VOIP_EXPECTED_FRAME_COUNT, this_packet->data, VOIP_PACKET_MAX_SIZE);
+ if (ret < 0)
+ {
+ Log("Failed to encode audio packet for player %d: opus error code %d\n", this_player_index, ret);
+ }
+ else
+ {
+ this_packet->length = ret;
+ }
}
}
- }
- ServerToClient to_send = (ServerToClient){
- .cur_gs = &gs,
- .your_player = this_player_index,
- .audio_playback_buffer = &buffer_to_play,
- };
-
- size_t len = 0;
- if (server_to_client_serialize(&to_send, bytes_buffer, &len, MAX_SERVER_TO_CLIENT, this_player_entity, false))
- {
- if (len > MAX_SERVER_TO_CLIENT - 8)
+ ServerToClient to_send = (ServerToClient){
+ .cur_gs = &gs,
+ .your_player = this_player_index,
+ .audio_playback_buffer = &buffer_to_play,
+ };
+
+ size_t len = 0;
+ if (server_to_client_serialize(&to_send, bytes_buffer, &len, MAX_SERVER_TO_CLIENT, this_player_entity, false))
{
- Log("Too much data quitting!\n");
- exit(-1);
- }
+ if (len > MAX_SERVER_TO_CLIENT - 8)
+ {
+ Log("Too much data quitting!\n");
+ exit(-1);
+ }
- size_t compressed_len = 0;
- lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void *)lzo_working_mem);
+ size_t compressed_len = 0;
+ lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void *)lzo_working_mem);
#ifdef LOG_GAMESTATE_SIZE
- Log("Size of gamestate packet before comrpession: %zu | After: %zu\n", len, compressed_len);
+ Log("Size of gamestate packet before comrpession: %zu | After: %zu\n", len, compressed_len);
#endif
- ENetPacket *gamestate_packet = enet_packet_create((void *)compressed_buffer, compressed_len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
- int err = enet_peer_send(cur, 0, gamestate_packet);
- if (err < 0)
+ ENetPacket *gamestate_packet = enet_packet_create((void *)compressed_buffer, compressed_len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
+ int err = enet_peer_send(cur, 0, gamestate_packet);
+ if (err < 0)
+ {
+ Log("Enet failed to send packet error %d\n", err);
+ enet_packet_destroy(gamestate_packet);
+ }
+ }
+ else
{
- Log("Enet failed to send packet error %d\n", err);
- enet_packet_destroy(gamestate_packet);
+ Log("Failed to serialize data for client %d\n", this_player_index);
}
+ free(bytes_buffer);
+ free(compressed_buffer);
}
- else
- {
- Log("Failed to serialize data for client %d\n", this_player_index);
- }
- free(bytes_buffer);
- free(compressed_buffer);
}
}
}
@@ -547,10 +511,9 @@ void server(void *info_raw)
enet_host_destroy(enet_host);
enet_deinitialize();
+ end_profiling_mythread();
printf("Cleanup\n");
#ifdef PROFILING
- SpallBufferQuit(&spall_ctx, &spall_buffer);
- SpallQuit(&spall_ctx);
#endif
}
\ No newline at end of file
diff --git a/server.spall b/server.spall
deleted file mode 100644
index 19d342b..0000000
Binary files a/server.spall and /dev/null differ
diff --git a/types.h b/types.h
index c677054..f565583 100644
--- a/types.h
+++ b/types.h
@@ -337,7 +337,9 @@ typedef struct GameState
// @Robust for the integer tick, also store a double for how much time has been processed.
// Like a whole timestep then a double for subtimestep
- double time; // @Robust separate tick integer not prone to precision issues. Could be very large as is saved to disk!
+ uint64_t tick;
+ double subframe_time;
+
cpVect goldpos;
@@ -421,9 +423,12 @@ void create_initial_world(GameState *gs);
void initialize(struct GameState *gs, void *entity_arena, size_t entity_arena_size);
void destroy(struct GameState *gs);
void process_fixed_timestep(GameState *gs);
-void process(struct GameState *gs, double dt); // does in place
+// if is subframe, doesn't always increment the tick. When enough
+// subframe time has been processed, increments the tick
+void process(struct GameState *gs, double dt, bool is_subframe); // does in place
Entity *closest_box_to_point_in_radius(struct GameState *gs, cpVect point, double radius, bool (*filter_func)(Entity *));
uint64_t tick(struct GameState *gs);
+double time(GameState *gs);
double sun_dist_no_gravity(Entity *sun);
// all of these return if successful or not