|
|
@ -58,6 +58,8 @@ static bool fullscreened = false;
|
|
|
|
static bool picking_new_boxtype = false;
|
|
|
|
static bool picking_new_boxtype = false;
|
|
|
|
|
|
|
|
|
|
|
|
static bool build_pressed = false;
|
|
|
|
static bool build_pressed = false;
|
|
|
|
|
|
|
|
static double dilating_time_factor = 1.0;
|
|
|
|
|
|
|
|
static double time_to_process = 0.0;
|
|
|
|
static bool interact_pressed = false;
|
|
|
|
static bool interact_pressed = false;
|
|
|
|
#define MAX_MOUSEBUTTON (SAPP_MOUSEBUTTON_MIDDLE + 1)
|
|
|
|
#define MAX_MOUSEBUTTON (SAPP_MOUSEBUTTON_MIDDLE + 1)
|
|
|
|
static bool mousedown[MAX_MOUSEBUTTON] = {0};
|
|
|
|
static bool mousedown[MAX_MOUSEBUTTON] = {0};
|
|
|
@ -86,8 +88,6 @@ static ENetPeer *peer;
|
|
|
|
static double zoom_target = 300.0;
|
|
|
|
static double zoom_target = 300.0;
|
|
|
|
static double zoom = 300.0;
|
|
|
|
static double zoom = 300.0;
|
|
|
|
static enum Squad take_over_squad = (enum Squad) - 1; // -1 means not taking over any squad
|
|
|
|
static enum Squad take_over_squad = (enum Squad) - 1; // -1 means not taking over any squad
|
|
|
|
static double target_prediction_time_factor = 1.0;
|
|
|
|
|
|
|
|
static double current_time_ahead_of_server = 0.0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// images
|
|
|
|
// images
|
|
|
|
static sg_image image_itemframe;
|
|
|
|
static sg_image image_itemframe;
|
|
|
@ -1330,6 +1330,19 @@ static void ui(bool draw, double dt, double width, double height)
|
|
|
|
sgp_pop_transform();
|
|
|
|
sgp_pop_transform();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// returns zero vector if no player
|
|
|
|
|
|
|
|
static cpVect my_player_pos()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (myentity() != NULL)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return entity_pos(myentity());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return (cpVect){0};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void draw_dots(cpVect camera_pos, double gap)
|
|
|
|
static void draw_dots(cpVect camera_pos, double gap)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
set_color_values(1.0, 1.0, 1.0, 1.0);
|
|
|
|
set_color_values(1.0, 1.0, 1.0, 1.0);
|
|
|
@ -1368,6 +1381,23 @@ static void draw_dots(cpVect camera_pos, double gap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void apply_this_tick_of_input_to_player(uint64_t tick_to_search_for)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
InputFrame *to_apply = NULL;
|
|
|
|
|
|
|
|
QUEUE_ITER(&input_queue, cur_header)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
InputFrame *cur = (InputFrame *)cur_header->data;
|
|
|
|
|
|
|
|
if (cur->tick == tick(&gs))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
to_apply = cur;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (to_apply != NULL && myplayer() != NULL)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
myplayer()->input = *to_apply;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
static cpVect get_global_hand_pos(cpVect world_mouse_pos, bool *hand_at_arms_length)
|
|
|
|
static cpVect get_global_hand_pos(cpVect world_mouse_pos, bool *hand_at_arms_length)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (myentity() == NULL)
|
|
|
|
if (myentity() == NULL)
|
|
|
@ -1395,7 +1425,7 @@ static void frame(void)
|
|
|
|
double width = (float)sapp_width(), height = (float)sapp_height();
|
|
|
|
double width = (float)sapp_width(), height = (float)sapp_height();
|
|
|
|
double ratio = width / height;
|
|
|
|
double ratio = width / height;
|
|
|
|
double exec_time = sapp_frame_count() * sapp_frame_duration();
|
|
|
|
double exec_time = sapp_frame_count() * sapp_frame_duration();
|
|
|
|
double dt = (float)sapp_frame_duration();
|
|
|
|
double dt = sapp_frame_duration();
|
|
|
|
|
|
|
|
|
|
|
|
// pressed input management
|
|
|
|
// pressed input management
|
|
|
|
{
|
|
|
|
{
|
|
|
@ -1422,6 +1452,9 @@ static void frame(void)
|
|
|
|
PROFILE_SCOPE("networking")
|
|
|
|
PROFILE_SCOPE("networking")
|
|
|
|
{
|
|
|
|
{
|
|
|
|
ENetEvent event;
|
|
|
|
ENetEvent event;
|
|
|
|
|
|
|
|
uint64_t predicted_to_tick = tick(&gs); // modified on deserialization of game state
|
|
|
|
|
|
|
|
cpVect where_i_thought_id_be = my_player_pos();
|
|
|
|
|
|
|
|
bool applied_gamestate_packet = false;
|
|
|
|
while (true)
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int enet_status = enet_host_service(client, &event, 0);
|
|
|
|
int enet_status = enet_host_service(client, &event, 0);
|
|
|
@ -1448,7 +1481,6 @@ static void frame(void)
|
|
|
|
assert(LZO1X_MEM_DECOMPRESS == 0);
|
|
|
|
assert(LZO1X_MEM_DECOMPRESS == 0);
|
|
|
|
|
|
|
|
|
|
|
|
ma_mutex_lock(&play_packets_mutex);
|
|
|
|
ma_mutex_lock(&play_packets_mutex);
|
|
|
|
double predicted_to_time = time(&gs);
|
|
|
|
|
|
|
|
ServerToClient msg = (ServerToClient){
|
|
|
|
ServerToClient msg = (ServerToClient){
|
|
|
|
.cur_gs = &gs,
|
|
|
|
.cur_gs = &gs,
|
|
|
|
.audio_playback_buffer = &packets_to_play,
|
|
|
|
.audio_playback_buffer = &packets_to_play,
|
|
|
@ -1462,6 +1494,7 @@ static void frame(void)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
server_to_client_deserialize(&msg, decompressed,
|
|
|
|
server_to_client_deserialize(&msg, decompressed,
|
|
|
|
decompressed_max_len, false);
|
|
|
|
decompressed_max_len, false);
|
|
|
|
|
|
|
|
applied_gamestate_packet = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
my_player_index = msg.your_player;
|
|
|
|
my_player_index = msg.your_player;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1475,83 +1508,83 @@ static void frame(void)
|
|
|
|
free(decompressed);
|
|
|
|
free(decompressed);
|
|
|
|
enet_packet_destroy(event.packet);
|
|
|
|
enet_packet_destroy(event.packet);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
fprintf(stderr, "Disconnected from server\n");
|
|
|
|
|
|
|
|
exit(-1);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (enet_status == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (enet_status < 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// only repredict inputs on the most recent server authoritative packet
|
|
|
|
PROFILE_SCOPE("Repredicting inputs")
|
|
|
|
PROFILE_SCOPE("Repredicting inputs")
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
if (applied_gamestate_packet)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
uint64_t server_current_tick = tick(&gs);
|
|
|
|
|
|
|
|
|
|
|
|
double server_current_time = time(&gs);
|
|
|
|
uint64_t ticks_should_repredict = predicted_to_tick - server_current_tick;
|
|
|
|
double difference = predicted_to_time - server_current_time;
|
|
|
|
|
|
|
|
double target_prediction_time =
|
|
|
|
uint64_t healthy_num_ticks_ahead = (uint64_t)ceil((((double)peer->roundTripTime) / 1000.0) / TIMESTEP) + 6;
|
|
|
|
(((double)peer->roundTripTime) / 1000.0) + TIMESTEP * 6.0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// keeps it stable even though causes jumps occasionally
|
|
|
|
// keeps it stable even though causes jumps occasionally
|
|
|
|
difference = fmax(difference, target_prediction_time);
|
|
|
|
uint64_t ticks_to_repredict = ticks_should_repredict;
|
|
|
|
|
|
|
|
|
|
|
|
double eps = TIMESTEP * 0.1;
|
|
|
|
if (ticks_should_repredict < healthy_num_ticks_ahead - 1)
|
|
|
|
if (predicted_to_time - time(&gs) < target_prediction_time - eps)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
target_prediction_time_factor = 1.1;
|
|
|
|
dilating_time_factor = 1.1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (predicted_to_time - time(&gs) >
|
|
|
|
else if (ticks_should_repredict > healthy_num_ticks_ahead + 1)
|
|
|
|
target_prediction_time + eps * 2.0)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
target_prediction_time_factor = 0.9;
|
|
|
|
dilating_time_factor = 0.1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
target_prediction_time_factor = 1.0;
|
|
|
|
dilating_time_factor = 1.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// re-predict the inputs
|
|
|
|
// snap in dire cases
|
|
|
|
double time_to_repredict = (float)difference;
|
|
|
|
if (ticks_should_repredict < healthy_num_ticks_ahead - TICKS_BEHIND_DO_SNAP)
|
|
|
|
Log("Repredicting %f\n", time_to_repredict);
|
|
|
|
{
|
|
|
|
|
|
|
|
Log("Snapping\n");
|
|
|
|
|
|
|
|
ticks_to_repredict = healthy_num_ticks_ahead;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint64_t start_prediction_time = stm_now();
|
|
|
|
uint64_t start_prediction_time = stm_now();
|
|
|
|
if (time_to_repredict > 0.0)
|
|
|
|
while (ticks_to_repredict > 0)
|
|
|
|
{
|
|
|
|
|
|
|
|
while (time_to_repredict > TIMESTEP)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (stm_ms(stm_diff(stm_now(), start_prediction_time)) > MAX_MS_SPENT_REPREDICTING)
|
|
|
|
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);
|
|
|
|
Log("Reprediction took longer than %f milliseconds, needs to repredict %llu more ticks\n", MAX_MS_SPENT_REPREDICTING, ticks_to_repredict);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QUEUE_ITER(&input_queue, cur_header)
|
|
|
|
apply_this_tick_of_input_to_player(tick(&gs));
|
|
|
|
{
|
|
|
|
process(&gs, TIMESTEP);
|
|
|
|
InputFrame *cur = (InputFrame *)cur_header->data;
|
|
|
|
ticks_to_repredict -= 1;
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
current_time_ahead_of_server = time(&gs) - server_current_time;
|
|
|
|
cpVect where_i_am = my_player_pos();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
|
|
double reprediction_error = cpvdist(where_i_am, where_i_thought_id_be);
|
|
|
|
|
|
|
|
InputFrame *biggest_frame = (InputFrame *)queue_most_recent_element(&input_queue);
|
|
|
|
|
|
|
|
if (reprediction_error >= 0.1)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Disconnected from server\n");
|
|
|
|
Log("Big reprediction error %llu\n", biggest_frame->tick);
|
|
|
|
exit(-1);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (enet_status == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (enet_status < 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1647,6 +1680,14 @@ static void frame(void)
|
|
|
|
cur_input_frame.build_rotation = cur_editing_rotation;
|
|
|
|
cur_input_frame.build_rotation = cur_editing_rotation;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// in client side prediction, only process the latest input in the queue, not
|
|
|
|
|
|
|
|
// the one currently constructing.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
time_to_process += dt * dilating_time_factor;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cpVect before = my_player_pos();
|
|
|
|
|
|
|
|
do
|
|
|
|
|
|
|
|
{
|
|
|
|
// "commit" the input. each input must be on a successive tick.
|
|
|
|
// "commit" the input. each input must be on a successive tick.
|
|
|
|
if (tick(&gs) > last_input_committed_tick)
|
|
|
|
if (tick(&gs) > last_input_committed_tick)
|
|
|
|
{
|
|
|
|
{
|
|
|
@ -1669,16 +1710,23 @@ static void frame(void)
|
|
|
|
cur_input_frame; // for the client side prediction!
|
|
|
|
cur_input_frame; // for the client side prediction!
|
|
|
|
|
|
|
|
|
|
|
|
cur_input_frame = (InputFrame){0};
|
|
|
|
cur_input_frame = (InputFrame){0};
|
|
|
|
cur_input_frame.take_over_squad =
|
|
|
|
cur_input_frame.take_over_squad = -1; // @Robust make this zero initialized
|
|
|
|
-1; // @Robust make this zero initialized
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// in client side prediction, only process the latest in the queue, not
|
|
|
|
if (time_to_process >= TIMESTEP)
|
|
|
|
// the one currently constructing.
|
|
|
|
{
|
|
|
|
static double prediction_time_factor = 1.0;
|
|
|
|
uint64_t tick_to_predict = tick(&gs);
|
|
|
|
prediction_time_factor = lerp(prediction_time_factor,
|
|
|
|
apply_this_tick_of_input_to_player(tick_to_predict);
|
|
|
|
target_prediction_time_factor, dt * 3.0);
|
|
|
|
process(&gs, TIMESTEP);
|
|
|
|
process(&gs, dt * prediction_time_factor, true);
|
|
|
|
time_to_process -= TIMESTEP;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (time_to_process >= TIMESTEP);
|
|
|
|
|
|
|
|
cpVect after = my_player_pos();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// use theses variables to suss out reprediction errors, enables you to
|
|
|
|
|
|
|
|
// breakpoint on when they happen
|
|
|
|
|
|
|
|
(void)before;
|
|
|
|
|
|
|
|
(void)after;
|
|
|
|
|
|
|
|
|
|
|
|
static int64_t last_sent_input_time = 0;
|
|
|
|
static int64_t last_sent_input_time = 0;
|
|
|
|
if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) >
|
|
|
|
if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) >
|
|
|
|