Skip to content

Commit bff6a6b

Browse files
committed
Dropdown fully respecting hermes, overlay syncing to dropdown also for host, fully isolating progress for individual players on dropdown.
1 parent 9f521ec commit bff6a6b

2 files changed

Lines changed: 171 additions & 10 deletions

File tree

source/main.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ static void find_and_set_resource_path(char *path_buffer, size_t buffer_size) {
170170
* @brief Serializes the TemplateData into a flat byte buffer.
171171
* This "flattens" the complex data structure so it can be sent to another process.
172172
*/
173-
static size_t serialize_template_data(TemplateData *td, char *buffer) {
173+
size_t serialize_template_data(TemplateData *td, char *buffer) {
174174
if (!td || !buffer) return 0;
175175

176176
char *head = buffer;
@@ -334,7 +334,7 @@ static void deserialize_template_data(char *buffer, TemplateData *target_td) {
334334
// inside the tracker process - the receiver merges host state without clobbering its
335335
// own loaded resources. Returns true on success, false if counts don't match (indicating
336336
// the receiver hasn't finished reloading to the host's template yet).
337-
static bool merge_coop_progress(const char *buffer, TemplateData *target) {
337+
bool merge_coop_progress(const char *buffer, TemplateData *target) {
338338
if (!buffer || !target) return false;
339339
const char *head = buffer;
340340

@@ -2498,14 +2498,27 @@ int main(int argc, char *argv[]) {
24982498
if (apply_buf && apply_size > 0) {
24992499
bool merged = merge_coop_progress(apply_buf, tracker->template_data);
25002500
if (merged) {
2501-
tracker->time_since_last_update = tracker->template_data->host_time_since_last_update;
2501+
// Only mirror the host's timer when the data is genuinely
2502+
// fresh from the host (new_data or new_player_data). Cached
2503+
// per-player snapshots carry the host_time_since_last_update
2504+
// that was frozen in when the host serialized them, so
2505+
// applying them on a dropdown switch would spuriously reset
2506+
// the receiver's timer. The receiver keeps incrementing
2507+
// locally between fresh broadcasts, which keeps it in sync
2508+
// with the host.
2509+
bool fresh_from_host = (new_data || new_player_data);
2510+
if (fresh_from_host) {
2511+
tracker->time_since_last_update =
2512+
tracker->template_data->host_time_since_last_update;
2513+
}
25022514
SDL_SetAtomicInt(&g_needs_update, 1);
25032515
SDL_SetAtomicInt(&g_game_data_changed, 1);
25042516
rcv_last_applied_player_idx = sel;
25052517
log_message(LOG_INFO, "[COOP] Receiver: applied %s state. "
2506-
"overall_progress=%.1f%%\n",
2518+
"overall_progress=%.1f%% (timer_mirrored=%s)\n",
25072519
sel >= 0 ? "per-player" : "merged",
2508-
tracker->template_data->overall_progress_percentage);
2520+
tracker->template_data->overall_progress_percentage,
2521+
fresh_from_host ? "yes" : "no");
25092522
} else {
25102523
log_message(LOG_ERROR, "[COOP DEBUG] Receiver: merge_coop_progress returned false!\n");
25112524
}

source/tracker.cpp

Lines changed: 153 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10400,6 +10400,113 @@ void tracker_recalculate_progress(Tracker *t, const AppSettings *settings) {
1040010400
}
1040110401

1040210402

10403+
// Defined in main.cpp. Shared so the host-side Hermes path can update the
10404+
// per-player and merged snapshot caches directly without re-reading disk.
10405+
extern size_t serialize_template_data(TemplateData *td, char *buffer);
10406+
extern bool merge_coop_progress(const char *buffer, TemplateData *target);
10407+
10408+
10409+
// Apply a single Hermes event to (a) the source player's snapshot and (b) the
10410+
// merged snapshot, then restore the currently-displayed view into
10411+
// t->template_data based on the dropdown selection. Returns true if any
10412+
// snapshot actually changed.
10413+
//
10414+
// Rationale: the host sees Hermes events from every player in the lobby (LAN
10415+
// games route everyone's stats through the host's world). Applying those events
10416+
// directly to t->template_data made the currently-visible view drift regardless
10417+
// of which player the dropdown had selected. The snapshots are authoritative
10418+
// per-player state; template_data is just a display buffer rebuilt from them.
10419+
static bool hermes_apply_event_to_coop_snapshots(
10420+
Tracker *t, const AppSettings *settings,
10421+
int player_idx, const char *event_type, const cJSON *data,
10422+
char *workbuf, size_t workbuf_size) {
10423+
if (!t || !t->template_data || !settings || !workbuf) return false;
10424+
10425+
bool is_stat = (strcmp(event_type, "stat") == 0);
10426+
bool is_adv = (strcmp(event_type, "advancement") == 0);
10427+
if (!is_stat && !is_adv) return false;
10428+
10429+
bool any_changed = false;
10430+
10431+
// 1. Per-player snapshot: single-player semantics (highest-wins for stats).
10432+
if (player_idx >= 0 && player_idx < MAX_COOP_PLAYERS &&
10433+
t->coop_player_snapshots[player_idx] &&
10434+
t->coop_player_snapshot_sizes[player_idx] >= sizeof(TemplateData)) {
10435+
if (merge_coop_progress(t->coop_player_snapshots[player_idx], t->template_data)) {
10436+
bool changed = false;
10437+
if (is_stat) {
10438+
changed = hermes_apply_stat_event(t, data, false);
10439+
} else {
10440+
changed = hermes_apply_advancement_event(t, data);
10441+
}
10442+
if (changed) {
10443+
tracker_recalculate_progress(t, settings);
10444+
size_t new_size = serialize_template_data(t->template_data, workbuf);
10445+
if (new_size > 0 && new_size <= workbuf_size) {
10446+
char *new_buf = (char *) realloc(t->coop_player_snapshots[player_idx], new_size);
10447+
if (new_buf) {
10448+
memcpy(new_buf, workbuf, new_size);
10449+
t->coop_player_snapshots[player_idx] = new_buf;
10450+
t->coop_player_snapshot_sizes[player_idx] = new_size;
10451+
any_changed = true;
10452+
}
10453+
}
10454+
}
10455+
}
10456+
}
10457+
10458+
// 2. Merged snapshot: coop_stat_merge-aware. Relies on the per-UUID delta
10459+
// cache (seeded from disk during tracker_update_coop_merged) so repeat
10460+
// events from the same player advance the merged total correctly.
10461+
if (t->coop_merged_snapshot && t->coop_merged_snapshot_size >= sizeof(TemplateData)) {
10462+
if (merge_coop_progress(t->coop_merged_snapshot, t->template_data)) {
10463+
bool changed = false;
10464+
const char *ev_uuid = (player_idx >= 0 && player_idx < settings->coop_player_count)
10465+
? settings->coop_players[player_idx].uuid
10466+
: nullptr;
10467+
if (is_stat) {
10468+
if (settings->coop_stat_merge == COOP_STAT_CUMULATIVE) {
10469+
changed = hermes_apply_stat_event_cumulative(t, data, ev_uuid, false);
10470+
} else {
10471+
bool c1 = hermes_apply_stat_event(t, data, true);
10472+
bool c2 = hermes_apply_stat_event_cumulative(t, data, ev_uuid, true);
10473+
changed = c1 || c2;
10474+
}
10475+
} else {
10476+
changed = hermes_apply_advancement_event(t, data);
10477+
}
10478+
if (changed) {
10479+
tracker_recalculate_progress(t, settings);
10480+
size_t new_size = serialize_template_data(t->template_data, workbuf);
10481+
if (new_size > 0 && new_size <= workbuf_size) {
10482+
char *new_buf = (char *) realloc(t->coop_merged_snapshot, new_size);
10483+
if (new_buf) {
10484+
memcpy(new_buf, workbuf, new_size);
10485+
t->coop_merged_snapshot = new_buf;
10486+
t->coop_merged_snapshot_size = new_size;
10487+
any_changed = true;
10488+
}
10489+
}
10490+
}
10491+
}
10492+
}
10493+
10494+
// 3. Restore the currently-displayed view so the UI reflects the update.
10495+
int sel = t->selected_coop_player_idx;
10496+
const char *restore_buf = nullptr;
10497+
if (sel >= 0 && sel < MAX_COOP_PLAYERS && t->coop_player_snapshots[sel]) {
10498+
restore_buf = t->coop_player_snapshots[sel];
10499+
} else if (t->coop_merged_snapshot) {
10500+
restore_buf = t->coop_merged_snapshot;
10501+
}
10502+
if (restore_buf) {
10503+
merge_coop_progress(restore_buf, t->template_data);
10504+
}
10505+
10506+
return any_changed;
10507+
}
10508+
10509+
1040310510
void tracker_poll_hermes_log(Tracker *t, const AppSettings *settings) {
1040410511
if (!settings->using_hermes || !t->hermes_active || !t->hermes_play_log)
1040510512
return;
@@ -10408,6 +10515,11 @@ void tracker_poll_hermes_log(Tracker *t, const AppSettings *settings) {
1040810515
return;
1040910516

1041010517
bool any_changed = false;
10518+
bool snapshots_changed = false;
10519+
// Lazily allocated scratch buffer for snapshot re-serialization. Sized to
10520+
// match the broadcast buffer used on the file-merge path.
10521+
char *workbuf = nullptr;
10522+
const size_t workbuf_size = 4 * 1024 * 1024;
1041110523

1041210524
while (true) {
1041310525
long start_offset = ftell(t->hermes_play_log);
@@ -10462,12 +10574,15 @@ void tracker_poll_hermes_log(Tracker *t, const AppSettings *settings) {
1046210574

1046310575
// --- Player identity filter ---
1046410576
// Hermes events include a "player" object with "name" and "uuid".
10465-
// In coop HOST mode: accept events from any player in the lobby roster.
10577+
// In coop HOST mode: accept events from any player in the lobby roster,
10578+
// and remember which roster slot they came from so the event can be
10579+
// applied to that player's snapshot specifically.
1046610580
// In singleplayer/receiver: accept only the local player's events.
1046710581
// Match by UUID first (authoritative, case-insensitive hex), then
1046810582
// fall back to case-insensitive username (Hermes "name" may differ
1046910583
// in case; legacy stats files are fully lowercase).
1047010584
const char *event_player_uuid = nullptr; // Used for stat cache key
10585+
int matched_player_idx = -1; // Roster index of the source player (coop host)
1047110586
cJSON *player_json = cJSON_GetObjectItem(data, "player");
1047210587
if (cJSON_IsObject(player_json)) {
1047310588
cJSON *uuid_json = cJSON_GetObjectItem(player_json, "uuid");
@@ -10485,12 +10600,14 @@ void tracker_poll_hermes_log(Tracker *t, const AppSettings *settings) {
1048510600
strcasecmp(ev_uuid, rp->uuid) == 0) {
1048610601
player_match = true;
1048710602
event_player_uuid = ev_uuid;
10603+
matched_player_idx = p;
1048810604
break;
1048910605
}
1049010606
if (!player_match && ev_name && rp->username[0] != '\0' &&
1049110607
strcasecmp(ev_name, rp->username) == 0) {
1049210608
player_match = true;
1049310609
event_player_uuid = ev_uuid; // may be null, that's OK
10610+
matched_player_idx = p;
1049410611
break;
1049510612
}
1049610613
}
@@ -10513,9 +10630,27 @@ void tracker_poll_hermes_log(Tracker *t, const AppSettings *settings) {
1051310630
// If no player object in event, allow it through (older Hermes versions)
1051410631

1051510632
// --- Dispatch event ---
10516-
if (strcmp(type, "stat") == 0) {
10517-
bool is_coop_host = (settings->network_mode == NETWORK_HOST &&
10518-
t->hermes_coop_stat_cache);
10633+
bool is_coop_host = (settings->network_mode == NETWORK_HOST &&
10634+
t->hermes_coop_stat_cache);
10635+
bool have_snapshots = (matched_player_idx >= 0 &&
10636+
matched_player_idx < MAX_COOP_PLAYERS &&
10637+
t->coop_player_snapshots[matched_player_idx] &&
10638+
t->coop_merged_snapshot);
10639+
10640+
if (is_coop_host && have_snapshots &&
10641+
(strcmp(type, "stat") == 0 || strcmp(type, "advancement") == 0)) {
10642+
// Per-player isolation path: update the source player's snapshot
10643+
// and the merged snapshot, then restore the selected view.
10644+
if (!workbuf) {
10645+
workbuf = (char *) malloc(workbuf_size);
10646+
}
10647+
if (workbuf &&
10648+
hermes_apply_event_to_coop_snapshots(t, settings, matched_player_idx,
10649+
type, data, workbuf, workbuf_size)) {
10650+
any_changed = true;
10651+
snapshots_changed = true;
10652+
}
10653+
} else if (strcmp(type, "stat") == 0) {
1051910654
if (is_coop_host && settings->coop_stat_merge == COOP_STAT_CUMULATIVE) {
1052010655
// CUMULATIVE mode: track per-player deltas for everything
1052110656
if (hermes_apply_stat_event_cumulative(t, data, event_player_uuid))
@@ -10543,9 +10678,22 @@ void tracker_poll_hermes_log(Tracker *t, const AppSettings *settings) {
1054310678
}
1054410679

1054510680
if (any_changed) {
10546-
tracker_recalculate_progress(t, settings);
10681+
// The snapshot path already calls tracker_recalculate_progress inside
10682+
// hermes_apply_event_to_coop_snapshots (per snapshot) and restores the
10683+
// displayed view. Only recalc here for the legacy direct-apply path.
10684+
if (!snapshots_changed) {
10685+
tracker_recalculate_progress(t, settings);
10686+
}
1054710687
t->hermes_wants_ipc_flush = true;
10688+
// If we updated coop snapshots, broadcast the new merged view to
10689+
// receivers. The main loop's g_coop_broadcast_needed handler takes
10690+
// care of serialization + coop_net_broadcast.
10691+
if (snapshots_changed) {
10692+
SDL_SetAtomicInt(&g_coop_broadcast_needed, 1);
10693+
}
1054810694
}
10695+
10696+
if (workbuf) free(workbuf);
1054910697
}
1055010698

1055110699
// =============================================================================

0 commit comments

Comments
 (0)