Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion GenOnlineService/Controllers/Lobby/LobbyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ public async Task<APIResult> Delete(Int64 lobbyID)
&& data.ContainsKey("total_money")
&& data.ContainsKey("won")
&& data.ContainsKey("side")
&& data.ContainsKey("desynced")
)
{
Int64 user_id = TokenHelper.GetUserID(this);
Expand All @@ -321,6 +322,7 @@ public async Task<APIResult> Delete(Int64 lobbyID)
int total_money = data["total_money"].GetInt32();
int side = data["side"].GetInt32();
bool won = data["won"].GetBoolean();
bool desynced = data["desynced"].GetBoolean();
UInt64 match_id = data["match_id"].GetUInt64();

// were they really in the match they claim to be in?
Expand All @@ -340,7 +342,7 @@ public async Task<APIResult> Delete(Int64 lobbyID)
// store in DB
await using var db = await _dbFactory.CreateDbContextAsync();
await Database.MatchHistory.CommitPlayerOutcome(db, slotIndexInLobby, match_id, side,
buildings_built, buildings_killed, buildings_lost, units_built, units_killed, units_lost, total_money, won);
buildings_built, buildings_killed, buildings_lost, units_built, units_killed, units_lost, total_money, won, desynced);
}
}
}
Expand Down
31 changes: 26 additions & 5 deletions GenOnlineService/Database/Database.MatchHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ public struct MatchdataMemberModel

[JsonConverter(typeof(BoolFromIntConverter))]
public bool won { get; set; } = false; // tinyint(4) DEFAULT NULL

[JsonConverter(typeof(BoolFromIntConverter))]
public bool desynced {get; set; } = false;

public List<MemberMetadataModel> metadata { get; set; } = new List<MemberMetadataModel>();

public MatchdataMemberModel()
Expand Down Expand Up @@ -333,7 +337,8 @@ public static async Task CommitPlayerOutcome(
int unitsKilled,
int unitsLost,
int totalMoney,
bool won)
bool won,
bool desynced)
{
if (slotIndex < 0 || slotIndex > 7)
return;
Expand Down Expand Up @@ -361,6 +366,7 @@ public static async Task CommitPlayerOutcome(
model.units_lost = unitsLost;
model.total_money = totalMoney;
model.won = won;
model.desynced = desynced;

// 4. Serialize back
string updatedJson = JsonSerializer.Serialize(model);
Expand Down Expand Up @@ -545,7 +551,22 @@ public static async Task DetermineLobbyWinnerIfNotPresent(
}
}

// 3. Build winner groups from active, non-observer members.
// 3. Check if any member reported a desync. If so, mark all non-observer members as losers which the
// ExternalLeaderboardClient interprets as a No Result
bool anyDesync = members.Values.Any(m => m.desynced);
if (anyDesync)
{
foreach (var kv in members)
{
if (kv.Value.side == Constants.OBSERVER_SIDE_VALUE)
continue;

await UpdateMatchHistorySetWinFlag(db, lobby.MatchID, kv.Key, false);
}
return;
}

// 4. Build winner groups from active, non-observer members.
// Teamless players are treated individually using synthetic keys.
Dictionary<int, List<int>> winGroups = new();
foreach (var kv in members)
Expand All @@ -563,7 +584,7 @@ public static async Task DetermineLobbyWinnerIfNotPresent(
slotList.Add(kv.Key);
}

// 4. Compare winner groups by report count.
// 5. Compare winner groups by report count.
// A unique maximum wins; ties or no winner groups fall back to
// the abandoned timestamp-based resolution.
int? conclusiveWinningTeam = null;
Expand All @@ -587,7 +608,7 @@ public static async Task DetermineLobbyWinnerIfNotPresent(
}
}

// 5. If conclusively determined, propagate the win to the team
// 6. If conclusively determined, propagate the win to the team
// and explicitly award a loss to everyone else.
if (conclusiveWinningTeam != null || conclusiveWinningSlot != -1)
{
Expand All @@ -603,7 +624,7 @@ public static async Task DetermineLobbyWinnerIfNotPresent(
return;
}

// 6. No winner — determine who quit last (= winner) using the most accurate timing available.
// 7. No winner — determine who quit last (= winner) using the most accurate timing available.
// Prefer TimePlayerAbandonedIngame (recorded the instant each player's WS dropped while
// in-game) over TimeMemberLeft (recorded when the player was structurally removed from the
// lobby, which can happen much later, or earlier due to a fresh-session reconnect, skewing
Expand Down
Loading