fix(backend): dedup per-vehicle rows; add vehicle lineup, tournament, duration, draw
This commit is contained in:
+267
-139
@@ -79,6 +79,11 @@ struct LimitQuery {
|
|||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LangQuery {
|
||||||
|
lang: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct ResolveQuery {
|
struct ResolveQuery {
|
||||||
name: String,
|
name: String,
|
||||||
@@ -222,6 +227,11 @@ struct GameRow {
|
|||||||
player_count: i64,
|
player_count: i64,
|
||||||
winning_team: Option<String>,
|
winning_team: Option<String>,
|
||||||
losing_team: Option<String>,
|
losing_team: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
tournament_name: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
duration: Option<f64>,
|
||||||
|
draw: bool,
|
||||||
stats: GameStats,
|
stats: GameStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,9 +261,17 @@ struct GameParticipant {
|
|||||||
struct GamePlayer {
|
struct GamePlayer {
|
||||||
uid: String,
|
uid: String,
|
||||||
nick: Option<String>,
|
nick: Option<String>,
|
||||||
|
vehicles: Vec<Vehicle>,
|
||||||
stats: GameStats,
|
stats: GameStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Vehicle {
|
||||||
|
cdk: String,
|
||||||
|
name: String,
|
||||||
|
icon: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct PlayerSearchResponse {
|
struct PlayerSearchResponse {
|
||||||
players: Vec<PlayerRef>,
|
players: Vec<PlayerRef>,
|
||||||
@@ -535,12 +553,14 @@ async fn recent_games(
|
|||||||
async fn game_detail(
|
async fn game_detail(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Path(session_id): Path<String>,
|
Path(session_id): Path<String>,
|
||||||
|
Query(query): Query<LangQuery>,
|
||||||
) -> ApiResult<GameResponse> {
|
) -> ApiResult<GameResponse> {
|
||||||
let session_id = validate_session_id(&session_id)?;
|
let session_id = validate_session_id(&session_id)?;
|
||||||
|
let lang = query.lang.as_deref().unwrap_or("en");
|
||||||
let battles_conn = open_db(&state.battles_db)?;
|
let battles_conn = open_db(&state.battles_db)?;
|
||||||
let game = game_for(&battles_conn, session_id)?
|
let game = game_for(&battles_conn, session_id)?
|
||||||
.ok_or_else(|| ApiError::not_found("Game not found"))?;
|
.ok_or_else(|| ApiError::not_found("Game not found"))?;
|
||||||
let participants = game_participants_for(&battles_conn, session_id)?;
|
let participants = game_participants_for(&battles_conn, session_id, &state, lang)?;
|
||||||
Ok(Json(GameResponse { game, participants }))
|
Ok(Json(GameResponse { game, participants }))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1164,30 +1184,48 @@ fn period_history_for(conn: &Connection, team_name: &str) -> Result<Vec<PeriodHi
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn games_for(conn: &Connection, team_name: &str) -> Result<Vec<GameRow>, ApiError> {
|
fn games_for(conn: &Connection, team_name: &str) -> Result<Vec<GameRow>, ApiError> {
|
||||||
|
// Reduce per-vehicle duplicate rows to one row per UID (MAX) before summing.
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare(
|
.prepare(
|
||||||
"SELECT
|
"WITH per_player AS (
|
||||||
p.session_id,
|
SELECT session_id, UID,
|
||||||
COALESCE(m.endtime_unix, MAX(p.endtime_unix), 0) AS timestamp,
|
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
|
||||||
|
FROM player_games_hist
|
||||||
|
WHERE team_name = ?1 COLLATE NOCASE
|
||||||
|
GROUP BY session_id, UID
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
pp.session_id,
|
||||||
|
COALESCE(m.endtime_unix, MAX(pp.endtime_unix), 0) AS timestamp,
|
||||||
m.mission_name,
|
m.mission_name,
|
||||||
m.mission_mode,
|
m.mission_mode,
|
||||||
CASE
|
m.tournament_name,
|
||||||
WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 'Win'
|
m.duration,
|
||||||
ELSE 'Loss'
|
COALESCE(m.draw, 0),
|
||||||
END AS result,
|
CASE WHEN MAX(pp.won) = 1 THEN 'Win' ELSE 'Loss' END AS result,
|
||||||
COUNT(DISTINCT p.UID),
|
COUNT(*),
|
||||||
COALESCE(SUM(p.ground_kills), 0),
|
COALESCE(SUM(pp.ground_kills), 0),
|
||||||
COALESCE(SUM(p.air_kills), 0),
|
COALESCE(SUM(pp.air_kills), 0),
|
||||||
COALESCE(SUM(p.assists), 0),
|
COALESCE(SUM(pp.assists), 0),
|
||||||
COALESCE(SUM(p.captures), 0),
|
COALESCE(SUM(pp.captures), 0),
|
||||||
COALESCE(SUM(p.deaths), 0),
|
COALESCE(SUM(pp.deaths), 0),
|
||||||
COALESCE(SUM(p.score), 0),
|
COALESCE(SUM(pp.score), 0),
|
||||||
COALESCE(SUM(p.missile_evades), 0),
|
COALESCE(SUM(pp.missile_evades), 0),
|
||||||
COALESCE(SUM(p.shell_interceptions), 0),
|
COALESCE(SUM(pp.shell_interceptions), 0),
|
||||||
COALESCE(SUM(p.team_kills_stat), 0),
|
COALESCE(SUM(pp.team_kills_stat), 0),
|
||||||
(SELECT pg.team_name
|
(SELECT pg.team_name
|
||||||
FROM player_games_hist pg
|
FROM player_games_hist pg
|
||||||
WHERE pg.session_id = p.session_id
|
WHERE pg.session_id = pp.session_id
|
||||||
AND pg.team_name IS NOT NULL
|
AND pg.team_name IS NOT NULL
|
||||||
AND pg.team_name != ''
|
AND pg.team_name != ''
|
||||||
AND pg.victor_bool = 'Win'
|
AND pg.victor_bool = 'Win'
|
||||||
@@ -1196,17 +1234,16 @@ fn games_for(conn: &Connection, team_name: &str) -> Result<Vec<GameRow>, ApiErro
|
|||||||
LIMIT 1),
|
LIMIT 1),
|
||||||
(SELECT pg.team_name
|
(SELECT pg.team_name
|
||||||
FROM player_games_hist pg
|
FROM player_games_hist pg
|
||||||
WHERE pg.session_id = p.session_id
|
WHERE pg.session_id = pp.session_id
|
||||||
AND pg.team_name IS NOT NULL
|
AND pg.team_name IS NOT NULL
|
||||||
AND pg.team_name != ''
|
AND pg.team_name != ''
|
||||||
AND pg.victor_bool = 'Loss'
|
AND pg.victor_bool = 'Loss'
|
||||||
GROUP BY pg.team_name COLLATE NOCASE
|
GROUP BY pg.team_name COLLATE NOCASE
|
||||||
ORDER BY COUNT(DISTINCT pg.UID) DESC, pg.team_name COLLATE NOCASE
|
ORDER BY COUNT(DISTINCT pg.UID) DESC, pg.team_name COLLATE NOCASE
|
||||||
LIMIT 1)
|
LIMIT 1)
|
||||||
FROM player_games_hist p
|
FROM per_player pp
|
||||||
LEFT JOIN match_summary m ON m.session_id = p.session_id
|
LEFT JOIN match_summary m ON m.session_id = pp.session_id
|
||||||
WHERE p.team_name = ?1 COLLATE NOCASE
|
GROUP BY pp.session_id
|
||||||
GROUP BY p.session_id
|
|
||||||
ORDER BY timestamp DESC
|
ORDER BY timestamp DESC
|
||||||
LIMIT 100",
|
LIMIT 100",
|
||||||
)
|
)
|
||||||
@@ -1218,6 +1255,7 @@ fn games_for(conn: &Connection, team_name: &str) -> Result<Vec<GameRow>, ApiErro
|
|||||||
let timestamp: i64 = row.get(1)?;
|
let timestamp: i64 = row.get(1)?;
|
||||||
let map_name: Option<String> = row.get(2)?;
|
let map_name: Option<String> = row.get(2)?;
|
||||||
let mission_mode: Option<String> = row.get(3)?;
|
let mission_mode: Option<String> = row.get(3)?;
|
||||||
|
let draw_int: i64 = row.get(6)?;
|
||||||
Ok(GameRow {
|
Ok(GameRow {
|
||||||
team_name: None,
|
team_name: None,
|
||||||
session_id,
|
session_id,
|
||||||
@@ -1225,20 +1263,23 @@ fn games_for(conn: &Connection, team_name: &str) -> Result<Vec<GameRow>, ApiErro
|
|||||||
endtime_unix: timestamp,
|
endtime_unix: timestamp,
|
||||||
map_name,
|
map_name,
|
||||||
mission_mode,
|
mission_mode,
|
||||||
result: row.get(4)?,
|
result: row.get(7)?,
|
||||||
player_count: row.get(5)?,
|
player_count: row.get(8)?,
|
||||||
winning_team: row.get(15)?,
|
winning_team: row.get(18)?,
|
||||||
losing_team: row.get(16)?,
|
losing_team: row.get(19)?,
|
||||||
|
tournament_name: row.get(4)?,
|
||||||
|
duration: row.get(5)?,
|
||||||
|
draw: draw_int != 0,
|
||||||
stats: GameStats {
|
stats: GameStats {
|
||||||
ground_kills: row.get(6)?,
|
ground_kills: row.get(9)?,
|
||||||
air_kills: row.get(7)?,
|
air_kills: row.get(10)?,
|
||||||
assists: row.get(8)?,
|
assists: row.get(11)?,
|
||||||
captures: row.get(9)?,
|
captures: row.get(12)?,
|
||||||
deaths: row.get(10)?,
|
deaths: row.get(13)?,
|
||||||
score: row.get(11)?,
|
score: row.get(14)?,
|
||||||
missile_evades: row.get(12)?,
|
missile_evades: row.get(15)?,
|
||||||
shell_interceptions: row.get(13)?,
|
shell_interceptions: row.get(16)?,
|
||||||
team_kills_stat: row.get(14)?,
|
team_kills_stat: row.get(17)?,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1252,36 +1293,54 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
|
|||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare(
|
.prepare(
|
||||||
"WITH recent AS (
|
"WITH recent AS (
|
||||||
SELECT team_name, session_id, MAX(endtime_unix) AS timestamp
|
SELECT session_id, MAX(endtime_unix) AS timestamp
|
||||||
FROM player_games_hist
|
FROM player_games_hist
|
||||||
WHERE team_name IS NOT NULL AND team_name != ''
|
WHERE team_name IS NOT NULL AND team_name != ''
|
||||||
GROUP BY session_id
|
GROUP BY session_id
|
||||||
ORDER BY timestamp DESC
|
ORDER BY timestamp DESC
|
||||||
LIMIT ?1
|
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
|
||||||
|
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
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
r.team_name,
|
pp.team_name,
|
||||||
r.session_id,
|
pp.session_id,
|
||||||
COALESCE(m.endtime_unix, r.timestamp, 0) AS timestamp,
|
COALESCE(m.endtime_unix, MAX(pp.endtime_unix), 0) AS timestamp,
|
||||||
m.mission_name,
|
m.mission_name,
|
||||||
m.mission_mode,
|
m.mission_mode,
|
||||||
CASE
|
m.tournament_name,
|
||||||
WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 'Win'
|
m.duration,
|
||||||
ELSE 'Loss'
|
COALESCE(m.draw, 0),
|
||||||
END AS result,
|
CASE WHEN MAX(pp.won) = 1 THEN 'Win' ELSE 'Loss' END AS result,
|
||||||
COUNT(DISTINCT p.UID),
|
COUNT(*),
|
||||||
COALESCE(SUM(p.ground_kills), 0),
|
COALESCE(SUM(pp.ground_kills), 0),
|
||||||
COALESCE(SUM(p.air_kills), 0),
|
COALESCE(SUM(pp.air_kills), 0),
|
||||||
COALESCE(SUM(p.assists), 0),
|
COALESCE(SUM(pp.assists), 0),
|
||||||
COALESCE(SUM(p.captures), 0),
|
COALESCE(SUM(pp.captures), 0),
|
||||||
COALESCE(SUM(p.deaths), 0),
|
COALESCE(SUM(pp.deaths), 0),
|
||||||
COALESCE(SUM(p.score), 0),
|
COALESCE(SUM(pp.score), 0),
|
||||||
COALESCE(SUM(p.missile_evades), 0),
|
COALESCE(SUM(pp.missile_evades), 0),
|
||||||
COALESCE(SUM(p.shell_interceptions), 0),
|
COALESCE(SUM(pp.shell_interceptions), 0),
|
||||||
COALESCE(SUM(p.team_kills_stat), 0),
|
COALESCE(SUM(pp.team_kills_stat), 0),
|
||||||
(SELECT pg.team_name
|
(SELECT pg.team_name
|
||||||
FROM player_games_hist pg
|
FROM player_games_hist pg
|
||||||
WHERE pg.session_id = r.session_id
|
WHERE pg.session_id = pp.session_id
|
||||||
AND pg.team_name IS NOT NULL
|
AND pg.team_name IS NOT NULL
|
||||||
AND pg.team_name != ''
|
AND pg.team_name != ''
|
||||||
AND pg.victor_bool = 'Win'
|
AND pg.victor_bool = 'Win'
|
||||||
@@ -1290,18 +1349,16 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
|
|||||||
LIMIT 1),
|
LIMIT 1),
|
||||||
(SELECT pg.team_name
|
(SELECT pg.team_name
|
||||||
FROM player_games_hist pg
|
FROM player_games_hist pg
|
||||||
WHERE pg.session_id = r.session_id
|
WHERE pg.session_id = pp.session_id
|
||||||
AND pg.team_name IS NOT NULL
|
AND pg.team_name IS NOT NULL
|
||||||
AND pg.team_name != ''
|
AND pg.team_name != ''
|
||||||
AND pg.victor_bool = 'Loss'
|
AND pg.victor_bool = 'Loss'
|
||||||
GROUP BY pg.team_name COLLATE NOCASE
|
GROUP BY pg.team_name COLLATE NOCASE
|
||||||
ORDER BY COUNT(DISTINCT pg.UID) DESC, pg.team_name COLLATE NOCASE
|
ORDER BY COUNT(DISTINCT pg.UID) DESC, pg.team_name COLLATE NOCASE
|
||||||
LIMIT 1)
|
LIMIT 1)
|
||||||
FROM recent r
|
FROM per_player pp
|
||||||
JOIN player_games_hist p
|
LEFT JOIN match_summary m ON m.session_id = pp.session_id
|
||||||
ON p.session_id = r.session_id AND p.team_name = r.team_name COLLATE NOCASE
|
GROUP BY pp.team_name COLLATE NOCASE, pp.session_id
|
||||||
LEFT JOIN match_summary m ON m.session_id = r.session_id
|
|
||||||
GROUP BY r.team_name COLLATE NOCASE, r.session_id
|
|
||||||
ORDER BY timestamp DESC
|
ORDER BY timestamp DESC
|
||||||
LIMIT ?1",
|
LIMIT ?1",
|
||||||
)
|
)
|
||||||
@@ -1310,6 +1367,7 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
|
|||||||
let rows = stmt
|
let rows = stmt
|
||||||
.query_map(params![limit], |row| {
|
.query_map(params![limit], |row| {
|
||||||
let timestamp: i64 = row.get(2)?;
|
let timestamp: i64 = row.get(2)?;
|
||||||
|
let draw_int: i64 = row.get(7)?;
|
||||||
Ok(GameRow {
|
Ok(GameRow {
|
||||||
team_name: row.get(0)?,
|
team_name: row.get(0)?,
|
||||||
session_id: row.get(1)?,
|
session_id: row.get(1)?,
|
||||||
@@ -1317,20 +1375,23 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
|
|||||||
endtime_unix: timestamp,
|
endtime_unix: timestamp,
|
||||||
map_name: row.get(3)?,
|
map_name: row.get(3)?,
|
||||||
mission_mode: row.get(4)?,
|
mission_mode: row.get(4)?,
|
||||||
result: row.get(5)?,
|
result: row.get(8)?,
|
||||||
player_count: row.get(6)?,
|
player_count: row.get(9)?,
|
||||||
winning_team: row.get(16)?,
|
winning_team: row.get(19)?,
|
||||||
losing_team: row.get(17)?,
|
losing_team: row.get(20)?,
|
||||||
|
tournament_name: row.get(5)?,
|
||||||
|
duration: row.get(6)?,
|
||||||
|
draw: draw_int != 0,
|
||||||
stats: GameStats {
|
stats: GameStats {
|
||||||
ground_kills: row.get(7)?,
|
ground_kills: row.get(10)?,
|
||||||
air_kills: row.get(8)?,
|
air_kills: row.get(11)?,
|
||||||
assists: row.get(9)?,
|
assists: row.get(12)?,
|
||||||
captures: row.get(10)?,
|
captures: row.get(13)?,
|
||||||
deaths: row.get(11)?,
|
deaths: row.get(14)?,
|
||||||
score: row.get(12)?,
|
score: row.get(15)?,
|
||||||
missile_evades: row.get(13)?,
|
missile_evades: row.get(16)?,
|
||||||
shell_interceptions: row.get(14)?,
|
shell_interceptions: row.get(17)?,
|
||||||
team_kills_stat: row.get(15)?,
|
team_kills_stat: row.get(18)?,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1342,25 +1403,31 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn game_for(conn: &Connection, session_id: &str) -> Result<Option<GameRow>, ApiError> {
|
fn game_for(conn: &Connection, session_id: &str) -> Result<Option<GameRow>, ApiError> {
|
||||||
|
// player_games_hist stores one row per used vehicle per player, with the
|
||||||
|
// per-player stats duplicated across those rows. Reduce to one row per UID
|
||||||
|
// (MAX, since the values are identical) BEFORE summing team/game totals.
|
||||||
conn.query_row(
|
conn.query_row(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.session_id,
|
?1 AS session_id,
|
||||||
COALESCE(m.endtime_unix, MAX(p.endtime_unix), 0) AS timestamp,
|
COALESCE(m.endtime_unix, agg.timestamp, 0) AS timestamp,
|
||||||
m.mission_name,
|
m.mission_name,
|
||||||
m.mission_mode,
|
m.mission_mode,
|
||||||
COUNT(DISTINCT p.UID),
|
m.tournament_name,
|
||||||
COALESCE(SUM(p.ground_kills), 0),
|
m.duration,
|
||||||
COALESCE(SUM(p.air_kills), 0),
|
COALESCE(m.draw, 0),
|
||||||
COALESCE(SUM(p.assists), 0),
|
agg.player_count,
|
||||||
COALESCE(SUM(p.captures), 0),
|
agg.ground_kills,
|
||||||
COALESCE(SUM(p.deaths), 0),
|
agg.air_kills,
|
||||||
COALESCE(SUM(p.score), 0),
|
agg.assists,
|
||||||
COALESCE(SUM(p.missile_evades), 0),
|
agg.captures,
|
||||||
COALESCE(SUM(p.shell_interceptions), 0),
|
agg.deaths,
|
||||||
COALESCE(SUM(p.team_kills_stat), 0),
|
agg.score,
|
||||||
|
agg.missile_evades,
|
||||||
|
agg.shell_interceptions,
|
||||||
|
agg.team_kills_stat,
|
||||||
(SELECT pg.team_name
|
(SELECT pg.team_name
|
||||||
FROM player_games_hist pg
|
FROM player_games_hist pg
|
||||||
WHERE pg.session_id = p.session_id
|
WHERE pg.session_id = ?1
|
||||||
AND pg.team_name IS NOT NULL
|
AND pg.team_name IS NOT NULL
|
||||||
AND pg.team_name != ''
|
AND pg.team_name != ''
|
||||||
AND pg.victor_bool = 'Win'
|
AND pg.victor_bool = 'Win'
|
||||||
@@ -1369,20 +1436,49 @@ fn game_for(conn: &Connection, session_id: &str) -> Result<Option<GameRow>, ApiE
|
|||||||
LIMIT 1),
|
LIMIT 1),
|
||||||
(SELECT pg.team_name
|
(SELECT pg.team_name
|
||||||
FROM player_games_hist pg
|
FROM player_games_hist pg
|
||||||
WHERE pg.session_id = p.session_id
|
WHERE pg.session_id = ?1
|
||||||
AND pg.team_name IS NOT NULL
|
AND pg.team_name IS NOT NULL
|
||||||
AND pg.team_name != ''
|
AND pg.team_name != ''
|
||||||
AND pg.victor_bool = 'Loss'
|
AND pg.victor_bool = 'Loss'
|
||||||
GROUP BY pg.team_name COLLATE NOCASE
|
GROUP BY pg.team_name COLLATE NOCASE
|
||||||
ORDER BY COUNT(DISTINCT pg.UID) DESC, pg.team_name COLLATE NOCASE
|
ORDER BY COUNT(DISTINCT pg.UID) DESC, pg.team_name COLLATE NOCASE
|
||||||
LIMIT 1)
|
LIMIT 1)
|
||||||
FROM player_games_hist p
|
FROM (
|
||||||
LEFT JOIN match_summary m ON m.session_id = p.session_id
|
SELECT
|
||||||
WHERE p.session_id = ?1
|
COUNT(*) AS player_count,
|
||||||
GROUP BY p.session_id",
|
MAX(endtime_unix) AS timestamp,
|
||||||
|
COALESCE(SUM(ground_kills), 0) AS ground_kills,
|
||||||
|
COALESCE(SUM(air_kills), 0) AS air_kills,
|
||||||
|
COALESCE(SUM(assists), 0) AS assists,
|
||||||
|
COALESCE(SUM(captures), 0) AS captures,
|
||||||
|
COALESCE(SUM(deaths), 0) AS deaths,
|
||||||
|
COALESCE(SUM(score), 0) AS score,
|
||||||
|
COALESCE(SUM(missile_evades), 0) AS missile_evades,
|
||||||
|
COALESCE(SUM(shell_interceptions), 0) AS shell_interceptions,
|
||||||
|
COALESCE(SUM(team_kills_stat), 0) AS team_kills_stat
|
||||||
|
FROM (
|
||||||
|
SELECT UID,
|
||||||
|
MAX(endtime_unix) AS endtime_unix,
|
||||||
|
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
|
||||||
|
FROM player_games_hist
|
||||||
|
WHERE session_id = ?1
|
||||||
|
GROUP BY UID
|
||||||
|
)
|
||||||
|
) agg
|
||||||
|
LEFT JOIN match_summary m ON m.session_id = ?1
|
||||||
|
WHERE agg.player_count > 0",
|
||||||
params![session_id],
|
params![session_id],
|
||||||
|row| {
|
|row| {
|
||||||
let timestamp: i64 = row.get(1)?;
|
let timestamp: i64 = row.get(1)?;
|
||||||
|
let draw_int: i64 = row.get(6)?;
|
||||||
Ok(GameRow {
|
Ok(GameRow {
|
||||||
team_name: None,
|
team_name: None,
|
||||||
session_id: row.get(0)?,
|
session_id: row.get(0)?,
|
||||||
@@ -1391,19 +1487,22 @@ fn game_for(conn: &Connection, session_id: &str) -> Result<Option<GameRow>, ApiE
|
|||||||
map_name: row.get(2)?,
|
map_name: row.get(2)?,
|
||||||
mission_mode: row.get(3)?,
|
mission_mode: row.get(3)?,
|
||||||
result: String::new(),
|
result: String::new(),
|
||||||
player_count: row.get(4)?,
|
player_count: row.get(7)?,
|
||||||
winning_team: row.get(14)?,
|
winning_team: row.get(17)?,
|
||||||
losing_team: row.get(15)?,
|
losing_team: row.get(18)?,
|
||||||
|
tournament_name: row.get(4)?,
|
||||||
|
duration: row.get(5)?,
|
||||||
|
draw: draw_int != 0,
|
||||||
stats: GameStats {
|
stats: GameStats {
|
||||||
ground_kills: row.get(5)?,
|
ground_kills: row.get(8)?,
|
||||||
air_kills: row.get(6)?,
|
air_kills: row.get(9)?,
|
||||||
assists: row.get(7)?,
|
assists: row.get(10)?,
|
||||||
captures: row.get(8)?,
|
captures: row.get(11)?,
|
||||||
deaths: row.get(9)?,
|
deaths: row.get(12)?,
|
||||||
score: row.get(10)?,
|
score: row.get(13)?,
|
||||||
missile_evades: row.get(11)?,
|
missile_evades: row.get(14)?,
|
||||||
shell_interceptions: row.get(12)?,
|
shell_interceptions: row.get(15)?,
|
||||||
team_kills_stat: row.get(13)?,
|
team_kills_stat: row.get(16)?,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -1415,34 +1514,44 @@ fn game_for(conn: &Connection, session_id: &str) -> Result<Option<GameRow>, ApiE
|
|||||||
fn game_participants_for(
|
fn game_participants_for(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
|
state: &AppState,
|
||||||
|
lang: &str,
|
||||||
) -> Result<Vec<GameParticipant>, ApiError> {
|
) -> Result<Vec<GameParticipant>, ApiError> {
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare(
|
.prepare(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.team_name,
|
pp.team_name,
|
||||||
CASE
|
CASE WHEN MAX(pp.won) = 1 THEN 'Win' ELSE 'Loss' END AS result,
|
||||||
WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 'Win'
|
COUNT(*),
|
||||||
ELSE 'Loss'
|
COALESCE(SUM(pp.ground_kills), 0),
|
||||||
END AS result,
|
COALESCE(SUM(pp.air_kills), 0),
|
||||||
COUNT(DISTINCT p.UID),
|
COALESCE(SUM(pp.assists), 0),
|
||||||
COALESCE(SUM(p.ground_kills), 0),
|
COALESCE(SUM(pp.captures), 0),
|
||||||
COALESCE(SUM(p.air_kills), 0),
|
COALESCE(SUM(pp.deaths), 0),
|
||||||
COALESCE(SUM(p.assists), 0),
|
COALESCE(SUM(pp.score), 0),
|
||||||
COALESCE(SUM(p.captures), 0),
|
COALESCE(SUM(pp.missile_evades), 0),
|
||||||
COALESCE(SUM(p.deaths), 0),
|
COALESCE(SUM(pp.shell_interceptions), 0),
|
||||||
COALESCE(SUM(p.score), 0),
|
COALESCE(SUM(pp.team_kills_stat), 0)
|
||||||
COALESCE(SUM(p.missile_evades), 0),
|
FROM (
|
||||||
COALESCE(SUM(p.shell_interceptions), 0),
|
SELECT UID, team_name,
|
||||||
COALESCE(SUM(p.team_kills_stat), 0)
|
MAX(CASE WHEN victor_bool = 'Win' THEN 1 ELSE 0 END) AS won,
|
||||||
FROM player_games_hist p
|
MAX(ground_kills) AS ground_kills,
|
||||||
WHERE p.session_id = ?1 AND p.team_name IS NOT NULL AND p.team_name != ''
|
MAX(air_kills) AS air_kills,
|
||||||
GROUP BY p.team_name COLLATE NOCASE
|
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
|
||||||
|
FROM player_games_hist
|
||||||
|
WHERE session_id = ?1 AND team_name IS NOT NULL AND team_name != ''
|
||||||
|
GROUP BY UID, team_name COLLATE NOCASE
|
||||||
|
) pp
|
||||||
|
GROUP BY pp.team_name COLLATE NOCASE
|
||||||
ORDER BY
|
ORDER BY
|
||||||
CASE
|
CASE WHEN MAX(pp.won) = 1 THEN 0 ELSE 1 END,
|
||||||
WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 0
|
pp.team_name COLLATE NOCASE",
|
||||||
ELSE 1
|
|
||||||
END,
|
|
||||||
p.team_name COLLATE NOCASE",
|
|
||||||
)
|
)
|
||||||
.map_err(db_error)?;
|
.map_err(db_error)?;
|
||||||
|
|
||||||
@@ -1471,7 +1580,8 @@ fn game_participants_for(
|
|||||||
.map_err(db_error)?;
|
.map_err(db_error)?;
|
||||||
|
|
||||||
for participant in &mut participants {
|
for participant in &mut participants {
|
||||||
participant.players = game_players_for(conn, session_id, &participant.team_name)?;
|
participant.players =
|
||||||
|
game_players_for(conn, session_id, &participant.team_name, state, lang)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(participants)
|
Ok(participants)
|
||||||
@@ -1481,7 +1591,11 @@ fn game_players_for(
|
|||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
team_name: &str,
|
team_name: &str,
|
||||||
|
state: &AppState,
|
||||||
|
lang: &str,
|
||||||
) -> Result<Vec<GamePlayer>, ApiError> {
|
) -> Result<Vec<GamePlayer>, ApiError> {
|
||||||
|
// Stats are duplicated across a player's per-vehicle rows, so take MAX (not
|
||||||
|
// SUM) per UID. vehicle_internal collected (DISTINCT, comma-joined) for the lineup.
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare(
|
.prepare(
|
||||||
"SELECT
|
"SELECT
|
||||||
@@ -1494,27 +1608,41 @@ fn game_players_for(
|
|||||||
AND pg.nick != ''
|
AND pg.nick != ''
|
||||||
ORDER BY pg.endtime_unix DESC
|
ORDER BY pg.endtime_unix DESC
|
||||||
LIMIT 1),
|
LIMIT 1),
|
||||||
COALESCE(SUM(p.ground_kills), 0),
|
MAX(p.ground_kills),
|
||||||
COALESCE(SUM(p.air_kills), 0),
|
MAX(p.air_kills),
|
||||||
COALESCE(SUM(p.assists), 0),
|
MAX(p.assists),
|
||||||
COALESCE(SUM(p.captures), 0),
|
MAX(p.captures),
|
||||||
COALESCE(SUM(p.deaths), 0),
|
MAX(p.deaths),
|
||||||
COALESCE(SUM(p.score), 0),
|
MAX(p.score),
|
||||||
COALESCE(SUM(p.missile_evades), 0),
|
MAX(p.missile_evades),
|
||||||
COALESCE(SUM(p.shell_interceptions), 0),
|
MAX(p.shell_interceptions),
|
||||||
COALESCE(SUM(p.team_kills_stat), 0)
|
MAX(p.team_kills_stat),
|
||||||
|
GROUP_CONCAT(DISTINCT p.vehicle_internal)
|
||||||
FROM player_games_hist p
|
FROM player_games_hist p
|
||||||
WHERE p.session_id = ?1 AND p.team_name = ?2 COLLATE NOCASE
|
WHERE p.session_id = ?1 AND p.team_name = ?2 COLLATE NOCASE
|
||||||
GROUP BY p.UID
|
GROUP BY p.UID
|
||||||
ORDER BY COALESCE(SUM(p.score), 0) DESC, p.UID",
|
ORDER BY MAX(p.score) DESC, p.UID",
|
||||||
)
|
)
|
||||||
.map_err(db_error)?;
|
.map_err(db_error)?;
|
||||||
|
|
||||||
let players = stmt
|
let players = stmt
|
||||||
.query_map(params![session_id, team_name], |row| {
|
.query_map(params![session_id, team_name], |row| {
|
||||||
|
let cdks: Option<String> = row.get(11)?;
|
||||||
|
let vehicles = cdks
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split(',')
|
||||||
|
.map(|c| c.trim())
|
||||||
|
.filter(|c| !c.is_empty())
|
||||||
|
.map(|cdk| Vehicle {
|
||||||
|
name: lookup_vehicle_name(&state.vehicle_names, cdk, lang),
|
||||||
|
icon: lookup_vehicle_icon(&state.vehicle_icons, cdk),
|
||||||
|
cdk: cdk.to_string(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
Ok(GamePlayer {
|
Ok(GamePlayer {
|
||||||
uid: row.get(0)?,
|
uid: row.get(0)?,
|
||||||
nick: row.get(1)?,
|
nick: row.get(1)?,
|
||||||
|
vehicles,
|
||||||
stats: GameStats {
|
stats: GameStats {
|
||||||
ground_kills: row.get(2)?,
|
ground_kills: row.get(2)?,
|
||||||
air_kills: row.get(3)?,
|
air_kills: row.get(3)?,
|
||||||
|
|||||||
Reference in New Issue
Block a user