From e7ca6026a1d48e14ac9931eb9b0f6c937b2c501b Mon Sep 17 00:00:00 2001 From: tommy Date: Sun, 19 Apr 2026 10:05:46 -0400 Subject: [PATCH 1/3] Redesign submission pipeline with per-client SubmissionIntent OnFinish stashes one intent per client; OnReplaySaved looks it up and dispatches. Map/steamid/name are captured at finish time so disconnects mid-save can't corrupt the submission. --- .../scripting/include/offstyledb_records.inc | 271 +++++++++--------- .../scripting/include/offstyledb_shavit.inc | 10 +- 2 files changed, 141 insertions(+), 140 deletions(-) diff --git a/addons/sourcemod/scripting/include/offstyledb_records.inc b/addons/sourcemod/scripting/include/offstyledb_records.inc index 40778f8..34b9a45 100644 --- a/addons/sourcemod/scripting/include/offstyledb_records.inc +++ b/addons/sourcemod/scripting/include/offstyledb_records.inc @@ -6,6 +6,26 @@ #pragma newdecls required #pragma semicolon 1 +// A finalized submission, captured at OnFinish time. When we wait for a replay +// save (WR, or non-WR with replay_mode=1), we stash one of these per client and +// dispatch it when OnReplaySaved fires. When no wait is needed, we build one, +// dispatch it inline, and never touch gPending. +enum struct SubmissionIntent +{ + bool pending; + int rawStyle; + float time; + float sync; + int strafes; + int jumps; + bool isWR; + char map[64]; + char steamId[32]; + char name[MAX_NAME_LENGTH]; +} + +SubmissionIntent gPending[MAXPLAYERS + 1]; + bool IsSubmittableFinish(int client, int track) { return track == 0 @@ -16,199 +36,195 @@ bool IsSubmittableFinish(int client, int track) && !Shavit_IsPaused(client); } -// Called by the shavit compat layer from Shavit_OnFinish. +// Decision matrix applied in OnFinish: +// replay_mode=-1 -> submit everything from OnFinish, no replay +// replay_mode= 0 -> WRs wait for canonical OnReplaySaved; non-WRs submit inline +// replay_mode= 1 -> WRs and non-WRs both wait for OnReplaySaved +// Universal filter: submit_mode=0 drops non-WRs. void Records_HandleFinish(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime) { - DebugPrint("[OSdb] oldtime = %f newtime = %f", oldtime, time); + gPending[client].pending = false; + + DebugPrint("[OSdb] OnFinish: client=%d style=%d time=%f oldtime=%f track=%d", client, style, time, oldtime, track); - // oldtime <= time is a filter to prevent non-pbs from being submitted - // also means times wont submit if they never beat ur pb, like in the case - // of a skip being removed, but thats up the to server to delete the time + // oldtime <= time filters non-PB runs (e.g. when a skip got removed). if (!IsSubmittableFinish(client, track) || oldtime <= time) { return; } - float fWR = Shavit_GetWorldRecord(style, track); - bool isWR = (fWR == 0.0 || time <= fWR); + float fWR = Shavit_GetWorldRecord(style, track); + bool isWR = (fWR == 0.0 || time <= fWR); + int replayMode = gCV_ReplayMode.IntValue; + int submitMode = gCV_SubmitMode.IntValue; - if (isWR && gCV_ReplayMode.IntValue != -1) + if (!isWR && submitMode == 0) { - // WRs are handled by Shavit_OnReplaySaved with the correct replay file. - // When replays are disabled we submit here instead, since OnReplaySaved - // may not fire at all. + DebugPrint("[OSdb] Skipping non-WR submission (submit_mode=0)"); return; } - if (!isWR && gCV_SubmitMode.IntValue == 0) + bool waitForReplay = false; + if (replayMode != -1) { - DebugPrint("[OSdb] Skipping non-WR submission due to submit mode = 0 (WRs only)"); - return; + if (isWR || replayMode == 1) + { + waitForReplay = true; + } } - if (!isWR && gCV_ReplayMode.IntValue == 1) + SubmissionIntent intent; + BuildIntent(client, style, time, sync, strafes, jumps, isWR, intent); + + if (!waitForReplay) { - // Non-WR with replay attachment: submitted from Shavit_OnReplaySaved so the replay is attached - DebugPrint("[OSdb] Deferring non-WR submit to OnReplaySaved (replay_mode=1)"); + DispatchSubmission(intent, ""); return; } - SubmitRecordNow(client, style, time, sync, strafes, jumps, isWR, ""); + gPending[client] = intent; + DebugPrint("[OSdb] Stashed intent for client %d (isWR=%d, replay_mode=%d)", client, isWR, replayMode); } -// Called by the shavit compat layer from its pre-save hook -// (Shavit_ShouldSaveReplayCopy on v3, Shavit_AddAdditionalReplayPathsHere on v4). -// Returns true if shavit should save a replay we'll later pick up in -// Shavit_OnReplaySaved. When the replay cannot be saved (istoolong), submits the -// record directly without a replay and returns false. -bool Records_ShouldSaveReplay(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, bool isbestreplay, bool istoolong) +// Fills an intent from the shavit-finish params and the client's current identity. +void BuildIntent(int client, int style, float time, float sync, int strafes, int jumps, bool isWR, SubmissionIntent intent) { - DebugPrint("[OSdb] ShouldSaveReplay: client=%d style=%d time=%f oldtime=%f track=%d isbestreplay=%d istoolong=%d replay_mode=%d", - client, style, time, oldtime, track, isbestreplay, istoolong, gCV_ReplayMode.IntValue); - - if (gCV_ReplayMode.IntValue != 1 || isbestreplay) - { - return false; - } - - if (!IsSubmittableFinish(client, track) || oldtime <= time) - { - return false; - } + intent.pending = true; + intent.rawStyle = style; + intent.time = time; + intent.sync = sync; + intent.strafes = strafes; + intent.jumps = jumps; + intent.isWR = isWR; + + GetCurrentMap(intent.map, sizeof(intent.map)); + GetMapDisplayName(intent.map, intent.map, sizeof(intent.map)); + GetClientAuthId(client, AuthId_Steam3, intent.steamId, sizeof(intent.steamId)); + GetClientName(client, intent.name, sizeof(intent.name)); +} - if (gCV_SubmitMode.IntValue == 0) +// Called from the compat layer's pre-replay-save hook. Returns true if shavit +// should save a replay we'll pick up later. If shavit refuses to save +// (istoolong) we dispatch the stashed intent now without a replay. +bool Records_ShouldSaveReplay(int client, bool isbestreplay, bool istoolong) +{ + // Canonical WR saves happen regardless of what we do here. + if (isbestreplay) { return false; } - float fWR = Shavit_GetWorldRecord(style, track); - if (fWR == 0.0 || time <= fWR) + // We only ask for copy saves when a non-WR intent is stashed. + if (!gPending[client].pending || gPending[client].isWR) { - // Actually a WR - handled via shavit's canonical replay path return false; } if (istoolong) { - // Shavit won't save a replay for this run, but OnFinish deferred the - // submission to us. Submit the record now without a replay attachment. - DebugPrint("[OSdb] Non-WR replay too long, submitting record without replay"); - SubmitRecordNow(client, style, time, sync, strafes, jumps, false, ""); + DebugPrint("[OSdb] Non-WR replay too long, submitting without replay"); + DispatchSubmission(gPending[client], ""); + gPending[client].pending = false; return false; } - DebugPrint("[OSdb] Requesting non-WR replay copy"); + DebugPrint("[OSdb] Requesting non-WR replay copy for client %d", client); return true; } -// Called by the shavit compat layer from Shavit_OnReplaySaved. -// isOurCopy indicates the replay was saved at a path we asked for (non-WR copy). -// Callers must have already filtered out foreign saves (e.g. another plugin's -// additional-path registrations). -void Records_HandleReplaySaved(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, bool isbestreplay, const char[] replayPath, bool isOurCopy) +// Called from the compat layer's Shavit_OnReplaySaved handler. +// isOurCopy: the replay was saved at a path we asked for (v3: iscopy; v4: +// non-canonical path under our temp dir). Shavit's canonical WR saves arrive +// with isbestreplay=true and isOurCopy=false. +void Records_HandleReplaySaved(int client, bool isbestreplay, const char[] replayPath, bool isOurCopy) { - if (client == 0 || !IsSubmittableFinish(client, track)) + if (client == 0) { return; } - // Replays disabled: OnFinish submits everything, ignore this forward entirely - // so shavit not firing it (e.g. replay saving disabled server-side) never - // swallows a time. + // Replay uploads disabled: OnFinish already submitted everything. if (gCV_ReplayMode.IntValue == -1) { return; } - float fWR = Shavit_GetWorldRecord(style, track); - bool isWR = (fWR == 0.0 || time <= fWR); - - if (isOurCopy) + if (!gPending[client].pending) { - // Non-WR replay copy we requested via the shavit compat layer. - if (gCV_ReplayMode.IntValue != 1 || gCV_SubmitMode.IntValue == 0 || oldtime <= time || isWR) + // Save arrived for a finish we dropped (e.g. bad style, cheats, submit_mode=0). + // Clean up if it's our orphan; leave canonical saves alone. + if (isOurCopy) { CleanupTempReplay(replayPath); - return; } + return; } - else + + // Match the save type to the intent type. Anything else is an early fire + // for a save we're still waiting for, or a stray save we didn't request. + if (gPending[client].isWR && !isbestreplay) { - // Canonical (WR) save. - if (!isbestreplay || !isWR) - { - return; - } + return; + } + if (!gPending[client].isWR && !isOurCopy) + { + return; } - DebugPrint("[OSdb] OnReplaySaved submit: isWR=%d submit_mode=%d", isWR, gCV_SubmitMode.IntValue); - - SubmitRecordNow(client, style, time, sync, strafes, jumps, isWR, replayPath); + DebugPrint("[OSdb] Dispatching pending submission for client %d (isWR=%d)", client, gPending[client].isWR); + DispatchSubmission(gPending[client], replayPath); + gPending[client].pending = false; } -// Collects client context (map name, steamid, name, date) and dispatches the -// HTTP submission via SendRecord. Factored out so the OnFinish, OnReplaySaved, -// and istoolong code paths share one call shape. -void SubmitRecordNow(int client, int style, float time, float sync, int strafes, int jumps, bool isWR, const char[] replayPath) +public void OnClientDisconnect(int client) { - char sMap[64]; - GetCurrentMap(sMap, sizeof(sMap)); - GetMapDisplayName(sMap, sMap, sizeof(sMap)); - - char sSteamID[32]; - GetClientAuthId(client, AuthId_Steam3, sSteamID, sizeof(sSteamID)); - - char sName[MAX_NAME_LENGTH]; - GetClientName(client, sName, sizeof(sName)); - - SendRecord(sMap, sSteamID, sName, GetTime(), time, sync, strafes, jumps, style, isWR, replayPath); + gPending[client].pending = false; } -void SendRecord(char[] sMap, char[] sSteamID, char[] sName, int sDate, float time, float sync, int strafes, int jumps, int style, bool isWR, const char[] replayPath = "") +// POSTs the finalized submission to /submit_record_nr. On success, the callback +// may chain a replay upload. +void DispatchSubmission(SubmissionIntent intent, const char[] replayPath) { - DebugPrint("[OSdb] SendRecord called: Map=%s, SteamID=%s, Name=%s, Time=%.3f, Style=%d, IsWR=%s", - sMap, sSteamID, sName, time, style, isWR ? "true" : "false"); + DebugPrint("[OSdb] Dispatch: map=%s steamid=%s name=%s time=%.3f style=%d isWR=%s replay=%s", + intent.map, intent.steamId, intent.name, intent.time, intent.rawStyle, + intent.isWR ? "true" : "false", replayPath); if (sv_cheats.BoolValue) { - LogError("[OSdb] Attempted to submit record with sv_cheats enabled. Record data: %s | %s | %s | %s | %f | %f | %d | %d", - sMap, sSteamID, sName, sDate, time, sync, strafes, jumps); - DebugPrint("[OSdb] Record submission blocked due to sv_cheats being enabled"); + LogError("[OSdb] Attempted to submit record with sv_cheats enabled. map=%s steamid=%s time=%f", + intent.map, intent.steamId, intent.time); CleanupTempReplay(replayPath); return; } - int n_Style = ConvertStyle(style); - if (n_Style == -1) + int mappedStyle = ConvertStyle(intent.rawStyle); + if (mappedStyle == -1) { - DebugPrint("[OSdb] Style conversion failed for style %d, aborting record submission", style); + DebugPrint("[OSdb] Style conversion failed for raw=%d, dropping submission", intent.rawStyle); CleanupTempReplay(replayPath); return; } - DebugPrint("[OSdb] Style %d converted to %d, preparing HTTP request", style, n_Style); - - HTTPRequest hHTTPRequest; - JSONObject hJSON = new JSONObject(); - - hHTTPRequest = new HTTPRequest(API_BASE_URL... "/submit_record_nr"); - AddHeaders(hHTTPRequest); - hJSON.SetString("map", sMap); - hJSON.SetString("steamid", sSteamID); - hJSON.SetString("name", sName); - hJSON.SetFloat("time", time); - hJSON.SetFloat("sync", sync); - hJSON.SetInt("strafes", strafes); - hJSON.SetInt("jumps", jumps); - hJSON.SetInt("date", sDate); - hJSON.SetInt("tickrate", gI_Tickrate); - hJSON.SetInt("style", n_Style); + HTTPRequest req = new HTTPRequest(API_BASE_URL... "/submit_record_nr"); + AddHeaders(req); + + JSONObject json = new JSONObject(); + json.SetString("map", intent.map); + json.SetString("steamid", intent.steamId); + json.SetString("name", intent.name); + json.SetFloat("time", intent.time); + json.SetFloat("sync", intent.sync); + json.SetInt("strafes", intent.strafes); + json.SetInt("jumps", intent.jumps); + json.SetInt("date", GetTime()); + json.SetInt("tickrate", gI_Tickrate); + json.SetInt("style", mappedStyle); DataPack pack = new DataPack(); pack.WriteString(replayPath); - hHTTPRequest.Post(hJSON, OnRecordSubmitted, pack); - delete hJSON; + req.Post(json, OnRecordSubmitted, pack); + delete json; } void UploadReplay(const char[] replayKey, const char[] replayPath) @@ -218,7 +234,7 @@ void UploadReplay(const char[] replayKey, const char[] replayPath) return; } - DebugPrint("[OSdb] Uploading replay with key: %s, path: %s", replayKey, replayPath); + DebugPrint("[OSdb] Uploading replay: key=%s path=%s", replayKey, replayPath); HTTPRequest request = new HTTPRequest(API_BASE_URL... "/upload_replay"); AddHeaders(request); @@ -227,7 +243,6 @@ void UploadReplay(const char[] replayKey, const char[] replayPath) DataPack cleanupPack = new DataPack(); cleanupPack.WriteString(replayPath); - DebugPrint("[OSdb] Headers set, calling UploadFile"); request.UploadFile(replayPath, OnReplayUploaded, cleanupPack); } @@ -243,8 +258,6 @@ void CleanupTempReplay(const char[] replayPath) public void OnRecordSubmitted(HTTPResponse resp, any value, const char[] error) { - DebugPrint("[OSdb] OnRecordSubmitted callback fired"); - DataPack pack = view_as(value); pack.Reset(); @@ -252,14 +265,13 @@ public void OnRecordSubmitted(HTTPResponse resp, any value, const char[] error) pack.ReadString(replayPath, sizeof(replayPath)); delete pack; - if (gCV_ReplayMode.IntValue == -1) { - DebugPrint("[OSdb] Replay mode is -1, skipping replay upload"); + if (gCV_ReplayMode.IntValue == -1) + { + DebugPrint("[OSdb] Replay mode -1, not uploading replay"); CleanupTempReplay(replayPath); return; } - DebugPrint("[OSdb] Replay mode = %d, replayPath = '%s'", gCV_ReplayMode.IntValue, replayPath); - if (error[0] != '\0') { LogError("[OSdb] Record submission failed: %s", error); @@ -267,8 +279,6 @@ public void OnRecordSubmitted(HTTPResponse resp, any value, const char[] error) return; } - DebugPrint("[OSdb] HTTP response status: %d", resp.Status); - if (resp.Status != HTTPStatus_OK && resp.Status != HTTPStatus_Created) { LogError("[OSdb] Record submission failed with status %d", resp.Status); @@ -283,22 +293,15 @@ public void OnRecordSubmitted(HTTPResponse resp, any value, const char[] error) JSONObject data = view_as(resp.Data); hasReplayKey = data.GetString("replay_key", replayKey, sizeof(replayKey)); } - else - { - DebugPrint("[OSdb] Record submitted but resp.Data is null, no replay key in response"); - } - - DebugPrint("[OSdb] Has replay_key in response: %s, value: '%s', replayPath: '%s'", - hasReplayKey ? "true" : "false", replayKey, replayPath); if (hasReplayKey && replayKey[0] != '\0' && replayPath[0] != '\0') { - DebugPrint("[OSdb] Got replay key from response: %s, calling UploadReplay", replayKey); + DebugPrint("[OSdb] Got replay_key=%s, uploading %s", replayKey, replayPath); UploadReplay(replayKey, replayPath); } - else { - DebugPrint("[OSdb] Skipping replay upload - replayKey empty: %d, replayPath empty: %d", - replayKey[0] == '\0', replayPath[0] == '\0'); + else + { + DebugPrint("[OSdb] No replay upload (hasKey=%d, pathEmpty=%d)", hasReplayKey, replayPath[0] == '\0'); CleanupTempReplay(replayPath); } } diff --git a/addons/sourcemod/scripting/include/offstyledb_shavit.inc b/addons/sourcemod/scripting/include/offstyledb_shavit.inc index 73262c1..46293bd 100644 --- a/addons/sourcemod/scripting/include/offstyledb_shavit.inc +++ b/addons/sourcemod/scripting/include/offstyledb_shavit.inc @@ -184,9 +184,7 @@ public void Shavit_OnFinish(int client, int style, float time, int jumps, int st // v3: we return Plugin_Handled to ask shavit to save a /copy/ of the replay. public Action Shavit_ShouldSaveReplayCopy(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp, bool isbestreplay, bool istoolong) { - return Records_ShouldSaveReplay(client, style, time, jumps, strafes, sync, track, oldtime, isbestreplay, istoolong) - ? Plugin_Handled - : Plugin_Continue; + return Records_ShouldSaveReplay(client, isbestreplay, istoolong) ? Plugin_Handled : Plugin_Continue; } public void Shavit_OnReplaySaved(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp, bool isbestreplay, bool istoolong, bool iscopy, const char[] replaypath) @@ -197,14 +195,14 @@ public void Shavit_OnReplaySaved(int client, int style, float time, int jumps, i return; } - Records_HandleReplaySaved(client, style, time, jumps, strafes, sync, track, oldtime, isbestreplay, replaypath, iscopy); + Records_HandleReplaySaved(client, isbestreplay, replaypath, iscopy); } #else // v4: we register a temp path via Shavit_AlsoSaveReplayTo, and shavit calls us back // with the saved file in Shavit_OnReplaySaved. public void Shavit_AddAdditionalReplayPathsHere(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp, bool isbestreplay, bool istoolong) { - if (!Records_ShouldSaveReplay(client, style, time, jumps, strafes, sync, track, oldtime, isbestreplay, istoolong)) + if (!Records_ShouldSaveReplay(client, isbestreplay, istoolong)) { return; } @@ -232,6 +230,6 @@ public void Shavit_OnReplaySaved(int client, int style, float time, int jumps, i return; } - Records_HandleReplaySaved(client, style, time, jumps, strafes, sync, track, oldtime, isbestreplay, replayPath, isOurCopy); + Records_HandleReplaySaved(client, isbestreplay, replayPath, isOurCopy); } #endif From f83c0f627d11d93907db74f494c940c57c6af860 Mon Sep 17 00:00:00 2001 From: tommy Date: Sun, 19 Apr 2026 10:05:46 -0400 Subject: [PATCH 2/3] Fix PB filter dropping first-time finishes Shavit passes oldtime=0 when there's no prior PB on the style. The old filter treated that as a non-PB and dropped the submission. --- .../scripting/include/offstyledb_records.inc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/addons/sourcemod/scripting/include/offstyledb_records.inc b/addons/sourcemod/scripting/include/offstyledb_records.inc index 34b9a45..d057da4 100644 --- a/addons/sourcemod/scripting/include/offstyledb_records.inc +++ b/addons/sourcemod/scripting/include/offstyledb_records.inc @@ -47,12 +47,19 @@ void Records_HandleFinish(int client, int style, float time, int jumps, int stra DebugPrint("[OSdb] OnFinish: client=%d style=%d time=%f oldtime=%f track=%d", client, style, time, oldtime, track); - // oldtime <= time filters non-PB runs (e.g. when a skip got removed). - if (!IsSubmittableFinish(client, track) || oldtime <= time) + if (!IsSubmittableFinish(client, track)) { return; } + // Non-PB filter: drop runs that don't beat the player's prior PB. oldtime=0 + // means no prior PB (first-time finish on this style), which should submit. + if (oldtime != 0.0 && time >= oldtime) + { + DebugPrint("[OSdb] Dropping non-PB (time=%f >= oldtime=%f)", time, oldtime); + return; + } + float fWR = Shavit_GetWorldRecord(style, track); bool isWR = (fWR == 0.0 || time <= fWR); int replayMode = gCV_ReplayMode.IntValue; From 6f551685557b517cfaacc34297558118581b9361 Mon Sep 17 00:00:00 2001 From: tommy Date: Sun, 19 Apr 2026 10:05:46 -0400 Subject: [PATCH 3/3] Audit pass on submission pipeline Handle WR istoolong (was leaving the intent stashed forever). Drop OnClientDisconnect's pending clear since the intent carries its own client data. Elevate unmapped-style drops to LogError. Restructure the replay-mode decision as a literal case ladder. --- .../scripting/include/offstyledb_records.inc | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/addons/sourcemod/scripting/include/offstyledb_records.inc b/addons/sourcemod/scripting/include/offstyledb_records.inc index d057da4..4fa453c 100644 --- a/addons/sourcemod/scripting/include/offstyledb_records.inc +++ b/addons/sourcemod/scripting/include/offstyledb_records.inc @@ -36,11 +36,6 @@ bool IsSubmittableFinish(int client, int track) && !Shavit_IsPaused(client); } -// Decision matrix applied in OnFinish: -// replay_mode=-1 -> submit everything from OnFinish, no replay -// replay_mode= 0 -> WRs wait for canonical OnReplaySaved; non-WRs submit inline -// replay_mode= 1 -> WRs and non-WRs both wait for OnReplaySaved -// Universal filter: submit_mode=0 drops non-WRs. void Records_HandleFinish(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime) { gPending[client].pending = false; @@ -60,24 +55,31 @@ void Records_HandleFinish(int client, int style, float time, int jumps, int stra return; } - float fWR = Shavit_GetWorldRecord(style, track); - bool isWR = (fWR == 0.0 || time <= fWR); - int replayMode = gCV_ReplayMode.IntValue; - int submitMode = gCV_SubmitMode.IntValue; + float fWR = Shavit_GetWorldRecord(style, track); + bool isWR = (fWR == 0.0 || time <= fWR); - if (!isWR && submitMode == 0) + if (!isWR && gCV_SubmitMode.IntValue == 0) { - DebugPrint("[OSdb] Skipping non-WR submission (submit_mode=0)"); + DebugPrint("[OSdb] Skipping non-WR (submit_mode=0)"); return; } - bool waitForReplay = false; - if (replayMode != -1) + // Decide whether to attach a replay: + // replay_mode=-1 -> no replay, dispatch now + // replay_mode= 0 -> replay only for WRs; non-WRs dispatch now + // replay_mode= 1 -> replay for everything, always wait for OnReplaySaved + bool waitForReplay; + if (gCV_ReplayMode.IntValue == -1) { - if (isWR || replayMode == 1) - { - waitForReplay = true; - } + waitForReplay = false; + } + else if (gCV_ReplayMode.IntValue == 0) + { + waitForReplay = isWR; + } + else // replay_mode == 1 + { + waitForReplay = true; } SubmissionIntent intent; @@ -90,7 +92,7 @@ void Records_HandleFinish(int client, int style, float time, int jumps, int stra } gPending[client] = intent; - DebugPrint("[OSdb] Stashed intent for client %d (isWR=%d, replay_mode=%d)", client, isWR, replayMode); + DebugPrint("[OSdb] Stashed intent for client %d (isWR=%d)", client, isWR); } // Fills an intent from the shavit-finish params and the client's current identity. @@ -112,26 +114,28 @@ void BuildIntent(int client, int style, float time, float sync, int strafes, int // Called from the compat layer's pre-replay-save hook. Returns true if shavit // should save a replay we'll pick up later. If shavit refuses to save -// (istoolong) we dispatch the stashed intent now without a replay. +// (istoolong), OnReplaySaved won't fire either, so we dispatch the stashed +// intent now without a replay - this applies to WR intents as well, not just +// non-WR copies. bool Records_ShouldSaveReplay(int client, bool isbestreplay, bool istoolong) { - // Canonical WR saves happen regardless of what we do here. - if (isbestreplay) + if (!gPending[client].pending) { return false; } - // We only ask for copy saves when a non-WR intent is stashed. - if (!gPending[client].pending || gPending[client].isWR) + if (istoolong) { + DebugPrint("[OSdb] Replay too long (isWR=%d), submitting without replay", gPending[client].isWR); + DispatchSubmission(gPending[client], ""); + gPending[client].pending = false; return false; } - if (istoolong) + // Canonical WR saves are shavit's responsibility - we don't need to ask + // for an extra copy. + if (isbestreplay || gPending[client].isWR) { - DebugPrint("[OSdb] Non-WR replay too long, submitting without replay"); - DispatchSubmission(gPending[client], ""); - gPending[client].pending = false; return false; } @@ -183,11 +187,6 @@ void Records_HandleReplaySaved(int client, bool isbestreplay, const char[] repla gPending[client].pending = false; } -public void OnClientDisconnect(int client) -{ - gPending[client].pending = false; -} - // POSTs the finalized submission to /submit_record_nr. On success, the callback // may chain a replay upload. void DispatchSubmission(SubmissionIntent intent, const char[] replayPath) @@ -207,7 +206,8 @@ void DispatchSubmission(SubmissionIntent intent, const char[] replayPath) int mappedStyle = ConvertStyle(intent.rawStyle); if (mappedStyle == -1) { - DebugPrint("[OSdb] Style conversion failed for raw=%d, dropping submission", intent.rawStyle); + LogError("[OSdb] Style %d has no remote mapping - submission dropped for %s on %s (time=%f). Check the style mapping on the API server.", + intent.rawStyle, intent.steamId, intent.map, intent.time); CleanupTempReplay(replayPath); return; }