fix: dedup battle-logs list (one row/session), case-insensitive vehicle lookup, win/loss colors, clearer dividers, centered stat columns

This commit is contained in:
FURRO404
2026-06-18 01:29:30 -07:00
parent 616139d6ce
commit 51c0dd264a
4 changed files with 81 additions and 84 deletions
+47 -60
View File
@@ -1322,6 +1322,9 @@ fn games_for(conn: &Connection, team_name: &str) -> Result<Vec<GameRow>, ApiErro
}
fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiError> {
// One row per SESSION (not per team) so the battle-logs list shows each game
// once. Both team names come from the winner/loser subqueries; player_count is
// the larger team's size so the "NvN" label is per-side.
let mut stmt = conn
.prepare(
"WITH recent AS (
@@ -1332,47 +1335,25 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
ORDER BY timestamp DESC
LIMIT ?1
),
per_player AS (
SELECT session_id, team_name, UID,
MAX(endtime_unix) AS endtime_unix,
MAX(CASE WHEN victor_bool = 'Win' THEN 1 ELSE 0 END) AS won,
MAX(ground_kills) AS ground_kills,
MAX(air_kills) AS air_kills,
MAX(assists) AS assists,
MAX(captures) AS captures,
MAX(deaths) AS deaths,
MAX(score) AS score,
MAX(missile_evades) AS missile_evades,
MAX(shell_interceptions) AS shell_interceptions,
MAX(team_kills_stat) AS team_kills_stat
team_size AS (
SELECT session_id, team_name, COUNT(DISTINCT UID) AS players
FROM player_games_hist
WHERE session_id IN (SELECT session_id FROM recent)
AND team_name IS NOT NULL AND team_name != ''
GROUP BY session_id, team_name COLLATE NOCASE, UID
GROUP BY session_id, team_name COLLATE NOCASE
)
SELECT
pp.team_name,
pp.session_id,
COALESCE(m.endtime_unix, MAX(pp.endtime_unix), 0) AS timestamp,
r.session_id,
COALESCE(m.endtime_unix, r.timestamp, 0) AS timestamp,
m.mission_name,
m.mission_mode,
m.tournament_name,
m.duration,
COALESCE(m.draw, 0),
CASE WHEN MAX(pp.won) = 1 THEN 'Win' ELSE 'Loss' END AS result,
COUNT(*),
COALESCE(SUM(pp.ground_kills), 0),
COALESCE(SUM(pp.air_kills), 0),
COALESCE(SUM(pp.assists), 0),
COALESCE(SUM(pp.captures), 0),
COALESCE(SUM(pp.deaths), 0),
COALESCE(SUM(pp.score), 0),
COALESCE(SUM(pp.missile_evades), 0),
COALESCE(SUM(pp.shell_interceptions), 0),
COALESCE(SUM(pp.team_kills_stat), 0),
(SELECT MAX(players) FROM team_size ts WHERE ts.session_id = r.session_id) AS player_count,
(SELECT pg.team_name
FROM player_games_hist pg
WHERE pg.session_id = pp.session_id
WHERE pg.session_id = r.session_id
AND pg.team_name IS NOT NULL
AND pg.team_name != ''
AND pg.victor_bool = 'Win'
@@ -1381,16 +1362,15 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
LIMIT 1),
(SELECT pg.team_name
FROM player_games_hist pg
WHERE pg.session_id = pp.session_id
WHERE pg.session_id = r.session_id
AND pg.team_name IS NOT NULL
AND pg.team_name != ''
AND pg.victor_bool = 'Loss'
GROUP BY pg.team_name COLLATE NOCASE
ORDER BY COUNT(DISTINCT pg.UID) DESC, pg.team_name COLLATE NOCASE
LIMIT 1)
FROM per_player pp
LEFT JOIN match_summary m ON m.session_id = pp.session_id
GROUP BY pp.team_name COLLATE NOCASE, pp.session_id
FROM recent r
LEFT JOIN match_summary m ON m.session_id = r.session_id
ORDER BY timestamp DESC
LIMIT ?1",
)
@@ -1398,32 +1378,32 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
let rows = stmt
.query_map(params![limit], |row| {
let timestamp: i64 = row.get(2)?;
let draw_int: i64 = row.get(7)?;
let timestamp: i64 = row.get(1)?;
let draw_int: i64 = row.get(6)?;
Ok(GameRow {
team_name: row.get(0)?,
session_id: row.get(1)?,
team_name: None,
session_id: row.get(0)?,
timestamp,
endtime_unix: timestamp,
map_name: row.get(3)?,
mission_mode: row.get(4)?,
result: row.get(8)?,
player_count: row.get(9)?,
winning_team: row.get(19)?,
losing_team: row.get(20)?,
tournament_name: row.get(5)?,
duration: row.get(6)?,
map_name: row.get(2)?,
mission_mode: row.get(3)?,
result: String::new(),
player_count: row.get(7)?,
winning_team: row.get(8)?,
losing_team: row.get(9)?,
tournament_name: row.get(4)?,
duration: row.get(5)?,
draw: draw_int != 0,
stats: GameStats {
ground_kills: row.get(10)?,
air_kills: row.get(11)?,
assists: row.get(12)?,
captures: row.get(13)?,
deaths: row.get(14)?,
score: row.get(15)?,
missile_evades: row.get(16)?,
shell_interceptions: row.get(17)?,
team_kills_stat: row.get(18)?,
ground_kills: 0,
air_kills: 0,
assists: 0,
captures: 0,
deaths: 0,
score: 0,
missile_evades: 0,
shell_interceptions: 0,
team_kills_stat: 0,
},
})
})
@@ -1796,11 +1776,18 @@ fn allowed_origins() -> AllowOrigin {
}
}
// Vehicle cdk keys are lowercased on load and at lookup time so DB casing
// (e.g. "us_M4A2_76W_sherman" vs "us_m4a2_76w_sherman") never misses, mirroring
// the Python LangTableReader's case-insensitive behaviour.
fn load_vehicle_names(path: &FsPath) -> HashMap<String, HashMap<String, String>> {
match fs::read_to_string(path) {
let parsed: HashMap<String, HashMap<String, String>> = match fs::read_to_string(path) {
Ok(s) => serde_json::from_str(&s).unwrap_or_default(),
Err(_) => HashMap::new(),
}
};
parsed
.into_iter()
.map(|(k, v)| (k.to_lowercase(), v))
.collect()
}
fn load_vehicle_icons(path: &FsPath) -> HashMap<String, String> {
@@ -1815,7 +1802,7 @@ fn load_vehicle_icons(path: &FsPath) -> HashMap<String, String> {
entry.get(0).and_then(|v| v.as_str()),
entry.get(2).and_then(|v| v.as_str()),
) {
out.insert(cdk.to_string(), icon.to_string());
out.insert(cdk.to_lowercase(), icon.to_string());
}
}
out
@@ -1826,7 +1813,7 @@ fn lookup_vehicle_name(
cdk: &str,
lang: &str,
) -> String {
if let Some(by_lang) = names.get(cdk) {
if let Some(by_lang) = names.get(&cdk.to_lowercase()) {
if let Some(n) = by_lang.get(lang) {
return n.clone();
}
@@ -1839,9 +1826,9 @@ fn lookup_vehicle_name(
fn lookup_vehicle_icon(icons: &HashMap<String, String>, cdk: &str) -> String {
icons
.get(cdk)
.get(&cdk.to_lowercase())
.cloned()
.unwrap_or_else(|| format!("{cdk}.png"))
.unwrap_or_else(|| format!("{}.png", cdk.to_lowercase()))
}
fn resolve_db_path(env_key: &str, default_file: &str) -> PathBuf {