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:
+47
-60
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user