#[macro_use] extern crate rocket; use rand::Rng; use rocket::{ fs::{relative, FileServer}, response::stream::{Event, EventStream}, serde::{json::Json, Deserialize, Serialize}, tokio::{ self, select, time::{self, sleep, Duration, Instant}, }, Shutdown, State, }; use rocket_dyn_templates::{context, Template}; use std::{ collections::{HashMap, VecDeque}, sync::{Arc, RwLock}, }; #[derive(Serialize, Deserialize, Clone)] #[serde(crate = "rocket::serde")] enum TrackedState { Conscrit { invisible: bool, blurred: bool, captured: bool, mallette: bool, invisibility_codes: u32, blur_codes: u32, }, Vieux { color: u8, invisible: bool, }, } use TrackedState::{Conscrit, Vieux}; const BLURRED_MOVE: (f32, f32) = (0.0005, 0.0005); const BONUS_TIMEOUT: Duration = Duration::from_millis(5000); const EVENT_TIMEOUT: Duration = Duration::from_millis(100); impl TrackedState { fn invisible(&self) -> bool { match self { Conscrit { invisible, .. } => *invisible, Vieux { invisible, .. } => *invisible, } } fn blurred(&self) -> bool { match self { Conscrit { blurred, .. } => *blurred, Vieux { .. } => false, } } fn global_viewed(&self) -> bool { match self { Conscrit { captured, mallette, invisible, .. } => (*captured || *mallette) && !*invisible, Vieux { invisible, .. } => !*invisible, } } fn color(&self) -> u8 { match self { Vieux { color, .. } => *color, Conscrit { captured, .. } => { if *captured { 1 } else { 0 } } } } fn admin_color(&self) -> u8 { match self { Vieux { color, invisible } => { if *invisible { 2 } else { *color } } Conscrit { invisible, captured, .. } => { if *invisible { 2 } else if *captured { 1 } else { 0 } } } } } struct Tracked { id: String, name: String, pos: (f32, f32), state: TrackedState, } fn build_conscrit(id: String, name: String) -> Tracked { Tracked { id: id, name: name, pos: (0.0, 0.0), state: TrackedState::Conscrit { invisible: false, blurred: false, captured: false, mallette: false, invisibility_codes: 0, blur_codes: 0, }, } } fn build_vieux(id: String, name: String) -> Tracked { Tracked { id: id, name: name, pos: (0.0, 0.0), state: TrackedState::Vieux { invisible: true, color: 1, }, } } struct QueuedEvent { date: Instant, evt: Event, } impl QueuedEvent { fn expired(&self) -> bool { self.date.elapsed() >= EVENT_TIMEOUT } } impl From for QueuedEvent { fn from(evt: Event) -> QueuedEvent { QueuedEvent { date: Instant::now(), evt, } } } impl From for Event { fn from(queued_evt: QueuedEvent) -> Event { queued_evt.evt } } type Tracking = Arc>>>; type TrackingEventQueue = Arc>>>>; type AdminEventQueue = Arc>>; type AdminKey = String; #[derive(Serialize)] #[serde(crate = "rocket::serde")] struct TrackedInfo { name: String, pos: (f32, f32), color: u8, } #[derive(Serialize)] #[serde(crate = "rocket::serde")] struct AdminTrackedInfo { name: String, id: String, pos: (f32, f32), color: u8, state: TrackedState, } impl From for TrackedInfo { fn from(admin_info: AdminTrackedInfo) -> TrackedInfo { TrackedInfo { name: admin_info.name, pos: admin_info.pos, color: admin_info.color, } } } fn base_view(team: &Tracked) -> TrackedInfo { TrackedInfo { name: team.name.clone(), pos: team.pos, color: team.state.color(), } } fn admin_view(team: &Tracked) -> AdminTrackedInfo { AdminTrackedInfo { name: team.name.clone(), id: team.id.clone(), pos: team.pos, color: team.state.admin_color(), state: team.state.clone(), } } fn apparent_info(watcher: &Tracked, team: &Tracked) -> Option { if watcher.id == team.id { None } else if let Conscrit { captured, mallette, .. } = watcher.state { if captured { if team.state.invisible() { None } else if team.state.blurred() { let mut rng = rand::thread_rng(); let (lat, lon) = team.pos; Some(TrackedInfo { pos: ( lat + BLURRED_MOVE.0 * (rng.gen::() * 2.0 - 1.0), lon + BLURRED_MOVE.1 * (rng.gen::() * 2.0 - 1.0), ), ..base_view(team) }) } else { Some(base_view(team)) } } else { if mallette || team.state.global_viewed() { Some(base_view(team)) } else { None } } } else { Some(admin_view(team).into()) } } #[get("/track/?&")] fn tracked_view( id: &str, gpslog: Option, dbg: Option, tracking: &State, ) -> Option