Implement Team display in perround overview

Now tracks the scores of the teams and displays them on the homepage.
Also now displays the team numbers next to the overview of rounds won to know which team won which round
This commit is contained in:
Lol3rrr
2024-10-15 22:02:19 +02:00
parent a2be4c2167
commit 5cb9094f76
11 changed files with 263 additions and 253 deletions

38
Cargo.lock generated
View File

@@ -224,7 +224,7 @@ dependencies = [
"http 1.1.0", "http 1.1.0",
"http-body 1.0.1", "http-body 1.0.1",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper 1.5.0",
"hyper-util", "hyper-util",
"itoa", "itoa",
"matchit", "matchit",
@@ -663,7 +663,7 @@ dependencies = [
[[package]] [[package]]
name = "csdemo" name = "csdemo"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Lol3rrr/csdemo.git#7dfa4fa57ac25e17c3abe5ad7d7621e003786eca" source = "git+https://github.com/Lol3rrr/csdemo.git#195d2fbc15e7cf3795995519b696239233f5f9b8"
dependencies = [ dependencies = [
"bitter", "bitter",
"phf", "phf",
@@ -1328,9 +1328,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.30" version = "0.14.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@@ -1352,9 +1352,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.4.1" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@@ -1378,7 +1378,7 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http 1.1.0", "http 1.1.0",
"hyper 1.4.1", "hyper 1.5.0",
"hyper-util", "hyper-util",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
@@ -1395,7 +1395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [ dependencies = [
"bytes", "bytes",
"hyper 0.14.30", "hyper 0.14.31",
"native-tls", "native-tls",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
@@ -1412,7 +1412,7 @@ dependencies = [
"futures-util", "futures-util",
"http 1.1.0", "http 1.1.0",
"http-body 1.0.1", "http-body 1.0.1",
"hyper 1.4.1", "hyper 1.5.0",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio", "tokio",
@@ -2459,18 +2459,18 @@ dependencies = [
[[package]] [[package]]
name = "profiling" name = "profiling"
version = "1.0.15" version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
dependencies = [ dependencies = [
"profiling-procmacros", "profiling-procmacros",
] ]
[[package]] [[package]]
name = "profiling-procmacros" name = "profiling-procmacros"
version = "1.0.15" version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@@ -2798,7 +2798,7 @@ dependencies = [
"h2", "h2",
"http 0.2.12", "http 0.2.12",
"http-body 0.4.6", "http-body 0.4.6",
"hyper 0.14.30", "hyper 0.14.31",
"hyper-tls", "hyper-tls",
"ipnet", "ipnet",
"js-sys", "js-sys",
@@ -2838,7 +2838,7 @@ dependencies = [
"http 1.1.0", "http 1.1.0",
"http-body 1.0.1", "http-body 1.0.1",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper 1.5.0",
"hyper-rustls", "hyper-rustls",
"hyper-util", "hyper-util",
"ipnet", "ipnet",
@@ -2970,9 +2970,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
@@ -2987,9 +2987,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.17" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]] [[package]]
name = "ryu" name = "ryu"

View File

@@ -1,16 +1,14 @@
use std::collections::HashMap;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct EndOfGame { pub struct EndOfGame {
pub map: String, pub map: String,
pub players: Vec<(PlayerInfo, PlayerStats)>, pub players: Vec<(PlayerInfo, PlayerStats)>,
pub teams: HashMap<u32, TeamInfo>, pub teams: std::collections::HashMap<i32, TeamInfo>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct TeamInfo { pub struct TeamInfo {
pub name: String, pub end_score: usize,
pub score: usize, pub start_side: String,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -44,6 +42,7 @@ pub fn parse(buf: &[u8]) -> Result<EndOfGame, ()> {
let header = &output.header; let header = &output.header;
let mut player_stats = std::collections::HashMap::<_, PlayerStats>::new(); let mut player_stats = std::collections::HashMap::<_, PlayerStats>::new();
let mut pawn_to_player = std::collections::HashMap::<csdemo::structured::pawnid::PawnID, csdemo::UserId>::new();
let mut track = false; let mut track = false;
let mut player_life = std::collections::HashMap::<_, u8>::new(); let mut player_life = std::collections::HashMap::<_, u8>::new();
@@ -59,7 +58,13 @@ pub fn parse(buf: &[u8]) -> Result<EndOfGame, ()> {
track = true; track = true;
} }
csdemo::game_event::GameEvent::PlayerSpawn(pspawn) => { csdemo::game_event::GameEvent::PlayerSpawn(pspawn) => {
player_life.insert(pspawn.userid.unwrap(), 100); let userid = pspawn.userid.unwrap();
player_life.insert(userid.clone(), 100);
if let Some(pawn) = pspawn.userid_pawn.as_ref().map(|p| match p { csdemo::RawValue::I32(v) => Some(csdemo::structured::pawnid::PawnID::from(*v)), _ => None }).flatten() {
pawn_to_player.insert(pawn, userid);
}
} }
csdemo::game_event::GameEvent::WinPanelMatch(_) => { csdemo::game_event::GameEvent::WinPanelMatch(_) => {
track = false; track = false;
@@ -85,55 +90,42 @@ pub fn parse(buf: &[u8]) -> Result<EndOfGame, ()> {
}; };
} }
let mut teams = HashMap::new(); let mut teams = std::collections::HashMap::<i32, TeamInfo>::new();
let mut entity_to_team = HashMap::new();
let mut entity_to_name = HashMap::<_, String>::new(); let mut entity_to_team = std::collections::HashMap::new();
for tick_state in output.entity_states.ticks { for tick_state in output.entity_states.ticks {
for state in tick_state.states { for state in tick_state.states {
if state.class.as_ref() != "CCSTeam" { let team = match csdemo::structured::ccsteam::CCSTeam::try_from(&state) {
Ok(t) => t,
Err(_) => continue,
};
let pawns = team.player_pawns();
let player_ids = pawns.into_iter().filter_map(|pawn| pawn_to_player.get(&pawn)).collect::<Vec<_>>();
if player_ids.is_empty() {
if let Some(team_number) = entity_to_team.get(&team.entity_id()) {
if let Some(score) = team.score() {
if let Some(team_entry) = teams.get_mut(team_number) {
team_entry.end_score = score as usize;
}
}
}
continue; continue;
} }
let team = match state let team_number = player_ids.iter().filter_map(|p| output.player_info.get(*p).map(|p| p.team)).next().unwrap();
.get_prop("CCSTeam.m_iTeamNum")
.map(|p| p.value.as_u32()) entity_to_team.insert(team.entity_id(), team_number);
.flatten()
{ let team_entry = teams.entry(team_number).or_insert_with(|| {
Some(team) => { TeamInfo {
entity_to_team.insert(state.id, team.clone()); end_score: 0,
team start_side: team.team_name().map(|t| t.to_owned()).unwrap_or(String::new()),
} }
None => match entity_to_team.get(&state.id) { });
Some(t) => t.clone(), if let Some(score) = team.score() {
None => continue, team_entry.end_score = score as usize;
},
};
let name = match entity_to_name.get(&state.id) {
Some(n) => n.to_owned(),
None => match state
.get_prop("CCSTeam.m_szTeamname")
.map(|p| match &p.value {
csdemo::parser::Variant::String(v) => Some(v.to_owned()),
_ => None,
})
.flatten()
{
Some(n) => {
entity_to_name.insert(state.id, n.clone());
n
}
None => continue,
},
};
if let Some(score) = state
.get_prop("CCSTeam.m_iScore")
.map(|p| p.value.as_i32())
.flatten()
.map(|v| v as usize)
{
teams.insert(team, TeamInfo { name, score });
} }
} }
} }

View File

@@ -11,24 +11,7 @@ fn endofgame_nuke() {
let expected = endofgame::EndOfGame { let expected = endofgame::EndOfGame {
map: "de_nuke".to_owned(), map: "de_nuke".to_owned(),
teams: [ teams: [].into_iter().collect(),
(
3,
endofgame::TeamInfo {
name: "CT".to_owned(),
score: 8,
},
),
(
2,
endofgame::TeamInfo {
name: "TERRORIST".to_owned(),
score: 13,
},
),
]
.into_iter()
.collect(),
players: vec![ players: vec![
( (
endofgame::PlayerInfo { endofgame::PlayerInfo {

View File

@@ -102,33 +102,3 @@ pub struct AnalysisInput {
pub demoid: String, pub demoid: String,
pub path: PathBuf, pub path: PathBuf,
} }
#[derive(Debug)]
pub struct BaseInfo {
pub map: String,
pub players: Vec<(BasePlayerInfo, BasePlayerStats)>,
pub teams: Vec<(u32, BaseTeamInfo)>
}
#[derive(Debug)]
pub struct BaseTeamInfo {
pub score: usize,
pub name: String,
}
#[derive(Debug)]
pub struct BasePlayerInfo {
pub name: String,
pub steam_id: String,
pub team: i32,
pub color: i32,
pub ingame_id: i32,
}
#[derive(Debug)]
pub struct BasePlayerStats {
pub kills: usize,
pub deaths: usize,
pub damage: usize,
pub assists: usize,
}

View File

@@ -1,5 +1,35 @@
use super::*; use super::*;
#[derive(Debug)]
struct BaseInfo {
pub map: String,
pub players: Vec<(BasePlayerInfo, BasePlayerStats)>,
pub teams: std::collections::HashMap<i32, BaseTeamInfo>,
}
#[derive(Debug)]
struct BaseTeamInfo {
pub start_side: String,
pub end_score: usize,
}
#[derive(Debug)]
struct BasePlayerInfo {
pub name: String,
pub steam_id: String,
pub team: i32,
pub color: i32,
pub ingame_id: i32,
}
#[derive(Debug)]
struct BasePlayerStats {
pub kills: usize,
pub deaths: usize,
pub damage: usize,
pub assists: usize,
}
pub struct BaseAnalysis {} pub struct BaseAnalysis {}
impl BaseAnalysis { impl BaseAnalysis {
@@ -36,6 +66,12 @@ impl Analysis for BaseAnalysis {
let base_result = BaseInfo { let base_result = BaseInfo {
map: result.map, map: result.map,
teams: result.teams.into_iter().map(|(numb, team)| {
(numb, BaseTeamInfo {
end_score: team.end_score,
start_side: team.start_side,
})
}).collect(),
players: result players: result
.players .players
.into_iter() .into_iter()
@@ -57,12 +93,6 @@ impl Analysis for BaseAnalysis {
) )
}) })
.collect(), .collect(),
teams: result.teams.into_iter().map(|(numb, val)| {
(numb, BaseTeamInfo {
name: val.name,
score: val.score,
})
}).collect(),
}; };
let (player_info, player_stats): (Vec<_>, Vec<_>) = base_result let (player_info, player_stats): (Vec<_>, Vec<_>) = base_result
@@ -89,20 +119,20 @@ impl Analysis for BaseAnalysis {
}) })
.unzip(); .unzip();
let teams = base_result.teams.into_iter().map(|(numb, team)| {
crate::models::DemoTeam {
demo_id: input.demoid.clone(),
team: numb as i16,
end_score: team.end_score as i16,
start_name: team.start_side,
}
}).collect::<Vec<_>>();
let demo_info = crate::models::DemoInfo { let demo_info = crate::models::DemoInfo {
demo_id: input.demoid.clone(), demo_id: input.demoid.clone(),
map: base_result.map, map: base_result.map,
}; };
let demo_teams: Vec<crate::models::DemoTeam> = base_result.teams.into_iter().map(|(numb, info)| {
crate::models::DemoTeam {
demo_id: input.demoid.clone(),
team: numb as i16,
end_score: info.score as i16,
start_name: info.name,
}
}).collect();
Ok(Box::new(move |connection| { Ok(Box::new(move |connection| {
let store_demo_info_query = let store_demo_info_query =
diesel::dsl::insert_into(crate::schema::demo_info::dsl::demo_info) diesel::dsl::insert_into(crate::schema::demo_info::dsl::demo_info)
@@ -143,19 +173,26 @@ impl Analysis for BaseAnalysis {
)), )),
)); ));
let store_demo_teams = diesel::dsl::insert_into(crate::schema::demo_teams::dsl::demo_teams) let store_teams = diesel::dsl::insert_into(crate::schema::demo_teams::dsl::demo_teams)
.values(demo_teams).on_conflict((crate::schema::demo_teams::dsl::demo_id, crate::schema::demo_teams::dsl::team)) .values(teams)
.do_update() .on_conflict((
.set(( crate::schema::demo_teams::dsl::demo_id,
crate::schema::demo_teams::dsl::start_name.eq(diesel::upsert::excluded(crate::schema::demo_teams::dsl::start_name)), crate::schema::demo_teams::dsl::team,
crate::schema::demo_teams::dsl::end_score.eq(diesel::upsert::excluded(crate::schema::demo_teams::dsl::end_score)), ))
.do_update().set((
crate::schema::demo_teams::dsl::start_name.eq(diesel::upsert::excluded(
crate::schema::demo_teams::dsl::start_name,
)),
crate::schema::demo_teams::dsl::end_score.eq(diesel::upsert::excluded(
crate::schema::demo_teams::dsl::end_score,
)),
)); ));
Box::pin(async move { Box::pin(async move {
store_demo_info_query.execute(connection).await?; store_demo_info_query.execute(connection).await?;
store_demo_players_query.execute(connection).await?; store_demo_players_query.execute(connection).await?;
store_demo_player_stats_query.execute(connection).await?; store_demo_player_stats_query.execute(connection).await?;
store_demo_teams.execute(connection).await?; store_teams.execute(connection).await?;
Ok(()) Ok(())
}) })

View File

@@ -264,18 +264,11 @@ async fn scoreboard(
return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR); return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR);
} }
let team1_number: i16 = response.last().map(|(p, _)| p.team).unwrap(); let mut teams = std::collections::BTreeMap::new();
let mut team1 = Vec::new();
let mut team2 = Vec::new();
for (player, stats) in response { for (player, stats) in response {
let team_vec = if player.team == team1_number { let team = teams.entry(player.team as u32).or_insert(Vec::new());
&mut team1
} else {
&mut team2
};
team_vec.push(common::demo_analysis::ScoreBoardPlayer { team.push(common::demo_analysis::ScoreBoardPlayer {
name: player.name, name: player.name,
kills: stats.kills as usize, kills: stats.kills as usize,
deaths: stats.deaths as usize, deaths: stats.deaths as usize,
@@ -285,8 +278,7 @@ async fn scoreboard(
} }
Ok(axum::Json(common::demo_analysis::ScoreBoard { Ok(axum::Json(common::demo_analysis::ScoreBoard {
team1, teams: teams.into_iter().collect::<Vec<_>>()
team2,
})) }))
} }
@@ -373,17 +365,20 @@ async fn heatmap(
async fn perround( async fn perround(
session: UserSession, session: UserSession,
Path(demo_id): Path<String>, Path(demo_id): Path<String>,
) -> Result<axum::response::Json<Vec<common::demo_analysis::DemoRound>>, axum::http::StatusCode> { ) -> Result<axum::response::Json<common::demo_analysis::PerRoundResult>, axum::http::StatusCode> {
let rounds_query = crate::schema::demo_round::dsl::demo_round let rounds_query = crate::schema::demo_round::dsl::demo_round
.filter(crate::schema::demo_round::dsl::demo_id.eq(demo_id.clone())); .filter(crate::schema::demo_round::dsl::demo_id.eq(demo_id.clone()));
let round_players_query = crate::schema::demo_players::dsl::demo_players let round_players_query = crate::schema::demo_players::dsl::demo_players
.filter(crate::schema::demo_players::dsl::demo_id.eq(demo_id)); .filter(crate::schema::demo_players::dsl::demo_id.eq(demo_id.clone()));
let demo_teams = crate::schema::demo_teams::dsl::demo_teams
.filter(crate::schema::demo_teams::dsl::demo_id.eq(demo_id));
let mut db_con = crate::db_connection().await; let mut db_con = crate::db_connection().await;
let raw_rounds: Vec<crate::models::DemoRound> = rounds_query.load(&mut db_con).await.unwrap(); let raw_rounds: Vec<crate::models::DemoRound> = rounds_query.load(&mut db_con).await.unwrap();
let players: Vec<crate::models::DemoPlayer> = let players: Vec<crate::models::DemoPlayer> =
round_players_query.load(&mut db_con).await.unwrap(); round_players_query.load(&mut db_con).await.unwrap();
let raw_teams: Vec<crate::models::DemoTeam> = demo_teams.load(&mut db_con).await.unwrap();
let mut result = Vec::with_capacity(raw_rounds.len()); let mut result = Vec::with_capacity(raw_rounds.len());
for raw_round in raw_rounds.into_iter() { for raw_round in raw_rounds.into_iter() {
@@ -450,7 +445,17 @@ async fn perround(
result.push(common::demo_analysis::DemoRound { reason, events }); result.push(common::demo_analysis::DemoRound { reason, events });
} }
Ok(axum::Json(result)) let teams = raw_teams.into_iter().map(|dteam| {
common::demo_analysis::PerRoundTeam {
name: dteam.start_name,
number: dteam.team as u32,
}
}).collect();
Ok(axum::Json(common::demo_analysis::PerRoundResult {
rounds: result,
teams,
}))
} }
// The corresponding values for each map can be found using the Source2 Viewer and opening the // The corresponding values for each map can be found using the Source2 Viewer and opening the

View File

@@ -42,6 +42,16 @@ pub struct DemoInfo {
pub map: String, pub map: String,
} }
#[derive(Queryable, Selectable, Insertable, Debug)]
#[diesel(table_name = crate::schema::demo_teams)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct DemoTeam {
pub demo_id: String,
pub team: i16,
pub end_score: i16,
pub start_name: String,
}
#[derive(Queryable, Selectable, Insertable, Debug)] #[derive(Queryable, Selectable, Insertable, Debug)]
#[diesel(table_name = crate::schema::demo_players)] #[diesel(table_name = crate::schema::demo_players)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
@@ -65,16 +75,6 @@ pub struct DemoPlayerStats {
pub assists: i16, pub assists: i16,
} }
#[derive(Queryable, Selectable, Insertable, Debug)]
#[diesel(table_name = crate::schema::demo_teams)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct DemoTeam {
pub demo_id: String,
pub team: i16,
pub end_score: i16,
pub start_name: String,
}
#[derive(Queryable, Selectable, Insertable, Debug)] #[derive(Queryable, Selectable, Insertable, Debug)]
#[diesel(table_name = crate::schema::processing_status)] #[diesel(table_name = crate::schema::processing_status)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]

View File

@@ -0,0 +1,73 @@
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ScoreBoard {
pub teams: Vec<(u32, Vec<ScoreBoardPlayer>)>
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ScoreBoardPlayer {
pub name: String,
pub kills: usize,
pub deaths: usize,
pub damage: usize,
pub assists: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PlayerHeatmap {
pub name: String,
pub team: String,
pub png_data: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PerRoundResult {
pub teams: Vec<PerRoundTeam>,
pub rounds: Vec<DemoRound>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PerRoundTeam {
pub name: String,
pub number: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DemoRound {
pub reason: RoundWinReason,
pub events: Vec<RoundEvent>
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum RoundWinReason {
StillInProgress,
BombExploded,
VipEscaped,
VipKilled,
TSaved,
CtStoppedEscape,
RoundEndReasonTerroristsStopped,
BombDefused,
TKilled,
CTKilled,
Draw,
HostageRescued,
TimeRanOut,
RoundEndReasonHostagesNotRescued,
TerroristsNotEscaped,
VipNotEscaped,
GameStart,
TSurrender,
CTSurrender,
TPlanted,
CTReachedHostage,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum RoundEvent {
BombPlanted,
BombDefused,
Killed {
attacker: String,
died: String,
},
}

View File

@@ -18,67 +18,4 @@ pub struct DemoInfo {
pub map: String, pub map: String,
} }
pub mod demo_analysis { pub mod demo_analysis;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ScoreBoard {
pub team1: Vec<ScoreBoardPlayer>,
pub team2: Vec<ScoreBoardPlayer>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ScoreBoardPlayer {
pub name: String,
pub kills: usize,
pub deaths: usize,
pub damage: usize,
pub assists: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PlayerHeatmap {
pub name: String,
pub team: String,
pub png_data: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DemoRound {
pub reason: RoundWinReason,
pub events: Vec<RoundEvent>
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum RoundWinReason {
StillInProgress,
BombExploded,
VipEscaped,
VipKilled,
TSaved,
CtStoppedEscape,
RoundEndReasonTerroristsStopped,
BombDefused,
TKilled,
CTKilled,
Draw,
HostageRescued,
TimeRanOut,
RoundEndReasonHostagesNotRescued,
TerroristsNotEscaped,
VipNotEscaped,
GameStart,
TSurrender,
CTSurrender,
TPlanted,
CTReachedHostage,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum RoundEvent {
BombPlanted,
BombDefused,
Killed {
attacker: String,
died: String,
},
}
}

View File

@@ -1,26 +1,14 @@
use leptos::*; use leptos::*;
fn to_roman(mut number: u32) -> char {
if number < 12 {
char::from_u32(8544 + number).unwrap()
} else if number < 24 {
char::from_u32(8544 + (number - 12)).unwrap()
} else if number < 27 {
char::from_u32(8544 + (number - 24)).unwrap()
} else {
char::from_u32(8544 + (number - 27)).unwrap()
}
}
fn to_coloumn(idx: usize) -> usize { fn to_coloumn(idx: usize) -> usize {
if idx < 12 { if idx < 12 {
1 + idx 2 + idx
} else if idx < 24 { } else if idx < 24 {
1 + idx + 1 2 + idx + 1
} else if idx < 27 { } else if idx < 27 {
1 + idx + 2 2 + idx + 2
} else { } else {
1 + idx + 3 2 + idx + 3
} }
} }
@@ -35,18 +23,18 @@ pub fn per_round() -> impl leptos::IntoView {
.send() .send()
.await .await
.unwrap(); .unwrap();
res.json::<Vec<common::demo_analysis::DemoRound>>() res.json::<common::demo_analysis::PerRoundResult>()
.await .await
.unwrap() .unwrap()
}); });
let style = stylers::style! { let style = stylers::style! {
"PerRound", "PerRound",
.round_overview { .round_overview {
display: inline-grid; display: inline-grid;
width: 90vw; width: 90vw;
grid-template-columns: repeat(12, 1fr) 5px repeat(12, 1fr) 5px repeat(3, 1fr) 5px repeat(3, 1fr); grid-template-columns: auto repeat(12, 1fr) 5px repeat(12, 1fr) 5px repeat(3, 1fr) 5px repeat(3, 1fr);
grid-template-rows: repeat(3, auto); grid-template-rows: repeat(3, auto);
} }
@@ -84,7 +72,7 @@ pub fn per_round() -> impl leptos::IntoView {
let events_list = move || { let events_list = move || {
let round_index = round(); let round_index = round();
let current_round = perround_resource.get().map(|rs| rs.get(round_index).cloned()).flatten(); let current_round = perround_resource.get().map(|rs| rs.rounds.get(round_index).cloned()).flatten();
match current_round { match current_round {
Some(round) => { Some(round) => {
@@ -106,7 +94,7 @@ pub fn per_round() -> impl leptos::IntoView {
set_round.set(r); set_round.set(r);
}; };
let round = perround_resource.get().map(|rs| rs.get(r).cloned()).flatten(); let round = perround_resource.get().map(|rs| rs.rounds.get(r).cloned()).flatten();
let reason = round.map(|r| r.reason); let reason = round.map(|r| r.reason);
let (ct_won, t_won) = match &reason { let (ct_won, t_won) = match &reason {
@@ -159,11 +147,27 @@ pub fn per_round() -> impl leptos::IntoView {
}).collect::<Vec<_>>() }).collect::<Vec<_>>()
}; };
let team_names = move || {
let perround_teams = match perround_resource.get().map(|p| p.teams) {
Some(t) => t,
None => return view! {}.into_view(),
};
let upper = perround_teams.iter().find(|t| t.name == "CT").map(|t| t.number).unwrap_or(0);
let lower = perround_teams.iter().find(|t| t.name == "TERRORIST").map(|t| t.number).unwrap_or(0);
view! {
<span style="grid-column: 1; grid-row: 1">Team { upper }</span>
<span style="grid-column: 1; grid-row: 3">Team { lower }</span>
}.into_view()
};
view! { view! {
class=style, class=style,
<h3>Per Round</h3> <h3>Per Round</h3>
<div class="round_overview"> <div class="round_overview">
{ team_names }
{ round_overview } { round_overview }
</div> </div>

View File

@@ -20,14 +20,24 @@ pub fn scoreboard() -> impl leptos::IntoView {
let (ordering, set_ordering) = create_signal::<orderings::Ordering>(orderings::DAMAGE); let (ordering, set_ordering) = create_signal::<orderings::Ordering>(orderings::DAMAGE);
let scoreboards = move || {
scoreboard_resource
.get()
.into_iter()
.flat_map(|v| v.teams.into_iter())
.map(|(team, players)| view! {
<TeamScoreboard value=players team_name=format!("Team {}", team) />
})
.collect::<Vec<_>>()
};
view! { view! {
<h2>Scoreboard</h2> <h2>Scoreboard</h2>
<Suspense <Suspense
fallback=move || view! { <p>Loading Scoreboard data</p> } fallback=move || view! { <p>Loading Scoreboard data</p> }
> >
<TeamScoreboard info=scoreboard_resource team_name="Team 1".to_string() part=|s| s.team1 /> { scoreboards }
<TeamScoreboard info=scoreboard_resource team_name="Team 2".to_string() part=|s| s.team2 />
</Suspense> </Suspense>
} }
} }
@@ -79,7 +89,7 @@ mod orderings {
} }
#[leptos::component] #[leptos::component]
fn team_scoreboard(info: Resource<leptos_router::ParamsMap, common::demo_analysis::ScoreBoard>, team_name: String, part: fn(common::demo_analysis::ScoreBoard) -> Vec<common::demo_analysis::ScoreBoardPlayer>) -> impl IntoView { fn team_scoreboard(value: Vec<common::demo_analysis::ScoreBoardPlayer>, team_name: String) -> impl IntoView {
let (ordering, set_ordering) = create_signal::<orderings::Ordering>(orderings::DAMAGE); let (ordering, set_ordering) = create_signal::<orderings::Ordering>(orderings::DAMAGE);
let style = stylers::style! { let style = stylers::style! {
@@ -119,8 +129,7 @@ fn team_scoreboard(info: Resource<leptos_router::ParamsMap, common::demo_analysi
</tr> </tr>
{ {
move || { move || {
let value = info.get().map(|v| part(v)); let mut players: Vec<_> = value.clone().into_iter().collect();
let mut players: Vec<_> = value.into_iter().flat_map(|v| v).collect();
let sorting = ordering.get(); let sorting = ordering.get();
players.sort_unstable_by(|p1, p2| (sorting.sort_fn)(p1, p2)); players.sort_unstable_by(|p1, p2| (sorting.sort_fn)(p1, p2));