···2424rand_chacha = "0.9.0"
2525futures = "0.3.31"
2626matchbox_socket = "0.12.0"
2727-uuid = "1.17.0"
2727+uuid = { version = "1.17.0", features = ["serde", "v4"] }
2828rmp-serde = "1.3.0"
2929tauri-plugin-store = "2.2.0"
3030specta = { version = "=2.0.0-rc.22", features = ["chrono", "uuid"] }
+3-3
backend/src/game/events.rs
···11use serde::{Deserialize, Serialize};
2233-use super::{location::Location, state::PlayerPing, PlayerId};
33+use super::{location::Location, state::PlayerPing, Id};
4455/// An event used between players to update state
66#[derive(Debug, Clone, Serialize, Deserialize)]
77-pub enum GameEvent<Id: PlayerId> {
77+pub enum GameEvent {
88 /// A player has been caught and is now a seeker, contains the ID of the caught player
99 PlayerCaught(Id),
1010 /// Public ping from a player revealing location
1111- Ping(PlayerPing<Id>),
1111+ Ping(PlayerPing),
1212 /// Force the player specified in `0` to ping, optionally display the ping as from the user
1313 /// specified in `1`.
1414 ForcePing(Id, Option<Id>),
+33-40
backend/src/game/mod.rs
···22pub use events::GameEvent;
33use powerups::PowerUpType;
44pub use settings::GameSettings;
55-use std::{
66- collections::HashMap,
77- fmt::{Debug, Display},
88- hash::Hash,
99- sync::Arc,
1010- time::Duration,
1111-};
55+use std::{collections::HashMap, sync::Arc, time::Duration};
126use uuid::Uuid;
137148use tokio::{sync::RwLock, time::MissedTickBehavior};
···2418pub use state::GameState;
2519pub use transport::Transport;
26202727-pub trait PlayerId:
2828- Display + Debug + Hash + Ord + Eq + PartialEq + Send + Sync + Sized + Copy + Clone + specta::Type
2929-{
3030-3131-}
3232-3333-impl PlayerId for Uuid {}
2121+pub type Id = Uuid;
34223523/// Convenence alias for UTC DT
3624pub type UtcDT = DateTime<Utc>;
···3826/// Struct representing an ongoing game, handles communication with
3927/// other clients via [Transport], gets location with [LocationService], and provides high-level methods for
4028/// taking actions in the game.
4141-pub struct Game<Id: PlayerId, L: LocationService, T: Transport<Id>> {
4242- state: RwLock<GameState<Id>>,
2929+pub struct Game<L: LocationService, T: Transport> {
3030+ state: RwLock<GameState>,
4331 transport: Arc<T>,
4432 location: L,
4533 interval: Duration,
4634}
47354848-impl<Id: PlayerId, L: LocationService, T: Transport<Id>> Game<Id, L, T> {
3636+impl<L: LocationService, T: Transport> Game<L, T> {
4937 pub fn new(
5038 my_id: Id,
5139 interval: Duration,
···5442 transport: Arc<T>,
5543 location: L,
5644 ) -> Self {
5757- let state = GameState::<Id>::new(settings, my_id, initial_caught_state);
4545+ let state = GameState::new(settings, my_id, initial_caught_state);
58465947 Self {
6048 transport,
···6452 }
6553 }
66546767- pub async fn clone_state(&self) -> GameState<Id> {
5555+ pub async fn clone_state(&self) -> GameState {
6856 self.state.read().await.clone()
6957 }
7058···116104 }
117105 }
118106119119- async fn consume_event(&self, event: GameEvent<Id>) {
107107+ async fn consume_event(&self, event: GameEvent) {
120108 let mut state = self.state.write().await;
121109122110 match event {
···235223 use super::*;
236224 use tokio::{sync::Mutex, task::yield_now, test};
237225238238- type GameEventRx = tokio::sync::mpsc::Receiver<GameEvent<u32>>;
239239- type GameEventTx = tokio::sync::mpsc::Sender<GameEvent<u32>>;
240240-241241- impl PlayerId for u32 {}
226226+ type GameEventRx = tokio::sync::mpsc::Receiver<GameEvent>;
227227+ type GameEventTx = tokio::sync::mpsc::Sender<GameEvent>;
242228243229 struct MockTransport {
244230 rx: Mutex<GameEventRx>,
245231 txs: Vec<GameEventTx>,
246232 }
247233248248- impl Transport<u32> for MockTransport {
249249- async fn receive_message(&self) -> Option<GameEvent<u32>> {
234234+ impl Transport for MockTransport {
235235+ async fn receive_message(&self) -> Option<GameEvent> {
250236 let mut rx = self.rx.lock().await;
251237 rx.recv().await
252238 }
253239254254- async fn send_message(&self, msg: GameEvent<u32>) {
240240+ async fn send_message(&self, msg: GameEvent) {
255241 for (_id, tx) in self.txs.iter().enumerate() {
256242 tx.send(msg.clone()).await.expect("Failed to send msg");
257243 }
···270256 }
271257 }
272258273273- type TestGame = Game<u32, MockLocation, MockTransport>;
259259+ type TestGame = Game<MockLocation, MockTransport>;
274260275261 struct MockMatch {
262262+ uuids: Vec<Uuid>,
276263 games: HashMap<u32, Arc<TestGame>>,
277264 settings: GameSettings,
278265 mock_now: UtcDT,
···282269283270 impl MockMatch {
284271 pub fn new(settings: GameSettings, players: u32, seekers: u32) -> Self {
272272+ let uuids = (0..players)
273273+ .into_iter()
274274+ .map(|_| uuid::Uuid::new_v4())
275275+ .collect::<Vec<_>>();
276276+285277 let channels = (0..players)
286278 .into_iter()
287279 .map(|_| tokio::sync::mpsc::channel(10))
···289281290282 let initial_caught_state = (0..players)
291283 .into_iter()
292292- .map(|id| (id, id < seekers))
284284+ .map(|id| (uuids[id as usize], id < seekers))
293285 .collect::<HashMap<_, _>>();
294286 let txs = channels
295287 .iter()
···306298 };
307299 let location = MockLocation;
308300 let game = TestGame::new(
309309- id as u32,
301301+ uuids[id],
310302 INTERVAL,
311303 initial_caught_state.clone(),
312304 settings.clone(),
···321313 Self {
322314 settings,
323315 games,
316316+ uuids,
324317 mock_now: Utc::now(),
325318 }
326319 }
···339332 self.mock_now += d;
340333 }
341334342342- pub async fn assert_all_states(&self, f: impl Fn(&GameState<u32>)) {
335335+ pub async fn assert_all_states(&self, f: impl Fn(&GameState)) {
343336 for (_, game) in &self.games {
344337 let state = game.state.read().await;
345338 f(&state);
···408401409402 mat.assert_all_states(|s| {
410403 assert_eq!(
411411- s.get_caught(1),
404404+ s.get_caught(mat.uuids[1]),
412405 Some(true),
413406 "Game {} sees player 1 as not caught",
414407 s.id
···432425433426 mat.assert_all_states(|s| {
434427 for id in 0..4 {
435435- let ping = s.get_ping(id);
428428+ let ping = s.get_ping(mat.uuids[id]);
436429 if id == 0 {
437430 assert!(
438431 ping.is_none(),
···457450458451 mat.assert_all_states(|s| {
459452 for id in 0..4 {
460460- let ping = s.get_ping(id);
453453+ let ping = s.get_ping(mat.uuids[id]);
461454 if id <= 1 {
462455 assert!(
463456 ping.is_none(),
···535528 mat.tick().await;
536529537530 mat.assert_all_states(|s| {
538538- if let Some(ping) = s.get_ping(1) {
531531+ if let Some(ping) = s.get_ping(mat.uuids[1]) {
539532 assert_eq!(
540540- ping.real_player, 0,
533533+ ping.real_player, mat.uuids[0],
541534 "Ping for 1 is not truly 0 (in {})",
542535 s.id
543536 );
···568561 mat.assert_all_states(|s| {
569562 // Player 0 is a seeker, player 1 user the powerup, so 2 is the only one that should
570563 // could have pinged
571571- assert!(s.get_ping(2).is_some());
572572- assert!(s.get_ping(0).is_none());
573573- assert!(s.get_ping(1).is_none());
564564+ assert!(s.get_ping(mat.uuids[2]).is_some());
565565+ assert!(s.get_ping(mat.uuids[0]).is_none());
566566+ assert!(s.get_ping(mat.uuids[1]).is_none());
574567 })
575568 .await;
576569 }
···594587 mat.assert_all_states(|s| {
595588 for id in 0..3 {
596589 assert!(
597597- s.get_caught(id).is_some(),
590590+ s.get_caught(mat.uuids[id]).is_some(),
598591 "Player {} should be pinged due to the powerup (in {})",
599592 id,
600593 s.id
+13-13
backend/src/game/state.rs
···1313 location::Location,
1414 powerups::PowerUpType,
1515 settings::{GameSettings, PingStartCondition},
1616- PlayerId, UtcDT,
1616+ Id, UtcDT,
1717};
18181919#[derive(Debug, Clone, Serialize, Deserialize, specta::Type)]
2020/// An on-map ping of a player
2121-pub struct PlayerPing<Id: PlayerId> {
2121+pub struct PlayerPing {
2222 /// Location of the ping
2323 loc: Location,
2424 /// Time the ping happened
···2929 pub real_player: Id,
3030}
31313232-impl<Id: PlayerId> PlayerPing<Id> {
3232+impl PlayerPing {
3333 pub fn new(loc: Location, display_player: Id, real_player: Id) -> Self {
3434 Self {
3535 loc,
···42424343#[derive(Debug, Clone, Serialize, specta::Type)]
4444/// This struct handles all logic regarding state updates
4545-pub struct GameState<Id: PlayerId> {
4545+pub struct GameState {
4646 /// The id of this player in this game
4747 pub id: Id,
4848···6565 caught_state: HashMap<Id, bool>,
66666767 /// A map of the latest global ping results for each player
6868- pings: HashMap<Id, PlayerPing<Id>>,
6868+ pings: HashMap<Id, PlayerPing>,
69697070 /// Powerup on the map that players can grab. Only one at a time
7171 available_powerup: Option<Location>,
···9292 shared_random_state: u64,
9393}
94949595-impl<Id: PlayerId> GameState<Id> {
9595+impl GameState {
9696 pub fn new(settings: GameSettings, my_id: Id, initial_caught_state: HashMap<Id, bool>) -> Self {
9797 let mut rand = ChaCha20Rng::seed_from_u64(settings.random_seed as u64);
9898 let increment = rand.random_range(-100..100);
···230230 }
231231232232 /// Add a ping for a specific player
233233- pub fn add_ping(&mut self, ping: PlayerPing<Id>) {
233233+ pub fn add_ping(&mut self, ping: PlayerPing) {
234234 self.pings.insert(ping.display_player, ping);
235235 }
236236237237 /// Get a ping for a player
238238 #[cfg(test)]
239239- pub fn get_ping(&self, player: Id) -> Option<&PlayerPing<Id>> {
239239+ pub fn get_ping(&self, player: Id) -> Option<&PlayerPing> {
240240 self.pings.get(&player)
241241 }
242242243243 /// Remove a ping from the map
244244- pub fn remove_ping(&mut self, player: Id) -> Option<PlayerPing<Id>> {
244244+ pub fn remove_ping(&mut self, player: Id) -> Option<PlayerPing> {
245245 self.pings.remove(&player)
246246 }
247247248248 /// Iterate over all seekers in the game
249249- pub fn iter_seekers(&self) -> impl Iterator<Item = Id> + use<'_, Id> {
249249+ pub fn iter_seekers(&self) -> impl Iterator<Item = Id> + use<'_> {
250250 self.caught_state
251251 .iter()
252252 .filter_map(|(k, v)| if *v { Some(*k) } else { None })
···260260 }
261261262262 /// Iterate over all hiders in the game
263263- fn iter_hiders(&self) -> impl Iterator<Item = Id> + use<'_, Id> {
263263+ fn iter_hiders(&self) -> impl Iterator<Item = Id> + use<'_> {
264264 self.caught_state
265265 .iter()
266266 .filter_map(|(k, v)| if !*v { Some(*k) } else { None })
···274274 }
275275276276 /// Create a [PlayerPing] with the latest location saved for the player
277277- pub fn create_self_ping(&self) -> Option<PlayerPing<Id>> {
277277+ pub fn create_self_ping(&self) -> Option<PlayerPing> {
278278 self.create_ping(self.id)
279279 }
280280281281 /// Create a [PlayerPing] with the latest location as another player
282282- pub fn create_ping(&self, id: Id) -> Option<PlayerPing<Id>> {
282282+ pub fn create_ping(&self, id: Id) -> Option<PlayerPing> {
283283 self.get_loc()
284284 .map(|loc| PlayerPing::new(loc.clone(), id, self.id))
285285 }
···6677use std::{sync::Arc, time::Duration};
8899-use game::{Game as BaseGame, GameSettings, GameState as BaseGameState};
99+use game::{Game as BaseGame, GameSettings, GameState};
1010use lobby::{Lobby, LobbyState, StartGameInfo};
1111use location::TauriLocation;
1212use profile::PlayerProfile;
···1818use transport::MatchboxTransport;
1919use uuid::Uuid;
20202121-type Game = BaseGame<Uuid, TauriLocation, MatchboxTransport>;
2121+type Game = BaseGame<TauriLocation, MatchboxTransport>;
22222323enum AppState {
2424 Setup,
···241241242242// AppScreen::Game COMMANDS
243243244244-type AppGameState = BaseGameState<Uuid>;
245245-246244#[tauri::command]
247245#[specta::specta]
248246/// (Screen: Game) Mark this player as caught, this player will become a seeker. Returns the new game state
249249-async fn mark_caught(state: State<'_, AppStateHandle>) -> Result<AppGameState> {
247247+async fn mark_caught(state: State<'_, AppStateHandle>) -> Result<GameState> {
250248 let state = state.read().await;
251249 if let AppState::Game(game) = &*state {
252250 game.mark_caught().await;
···260258#[specta::specta]
261259/// (Screen: Game) Grab a powerup on the map, this should be called when the user is *in range* of
262260/// the powerup. Returns the new game state after rolling for the powerup
263263-async fn grab_powerup(state: State<'_, AppStateHandle>) -> Result<AppGameState> {
261261+async fn grab_powerup(state: State<'_, AppStateHandle>) -> Result<GameState> {
264262 let state = state.read().await;
265263 if let AppState::Game(game) = &*state {
266264 game.get_powerup().await;
···274272#[specta::specta]
275273/// (Screen: Game) Use the currently held powerup in the player's held_powerup. Does nothing if the
276274/// player has none. Returns the updated game state
277277-async fn use_powerup(state: State<'_, AppStateHandle>) -> Result<AppGameState> {
275275+async fn use_powerup(state: State<'_, AppStateHandle>) -> Result<GameState> {
278276 let state = state.read().await;
279277 if let AppState::Game(game) = &*state {
280278 game.use_powerup().await;
···298296 switch_teams,
299297 host_start_game,
300298 mark_caught,
301301- // grab_powerup,
302302- // use_powerup,
299299+ grab_powerup,
300300+ use_powerup,
303301 ]);
304302305303 #[cfg(debug_assertions)]
+4-4
backend/src/transport.rs
···1414#[derive(Debug, Serialize, Deserialize, Clone)]
1515pub enum TransportMessage {
1616 /// Message related to the actual game
1717- Game(GameEvent<Uuid>),
1717+ Game(GameEvent),
1818 /// Message related to the pre-game lobby
1919 Lobby(LobbyMessage),
2020 /// Internal message when peer connects
···154154 }
155155}
156156157157-impl Transport<Uuid> for MatchboxTransport {
158158- async fn receive_message(&self) -> Option<GameEvent<Uuid>> {
157157+impl Transport for MatchboxTransport {
158158+ async fn receive_message(&self) -> Option<GameEvent> {
159159 self.recv_transport_message()
160160 .await
161161 .and_then(|(_, msg)| match msg {
···164164 })
165165 }
166166167167- async fn send_message(&self, msg: GameEvent<Uuid>) {
167167+ async fn send_message(&self, msg: GameEvent) {
168168 let msg = TransportMessage::Game(msg);
169169 self.send_transport_message(None, msg).await;
170170 }
+357
frontend/src/bindings.ts
···11+22+// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
33+44+/** user-defined commands **/
55+66+77+export const commands = {
88+/**
99+ * (Screen: Menu) Start/Join a new lobby, set `join_code` to `null` to be host,
1010+ * set it to a join code to be a client. This triggers a screen change to [AppScreen::Lobby]
1111+ */
1212+async startLobby(joinCode: string | null, settings: GameSettings) : Promise<Result<null, string>> {
1313+ try {
1414+ return { status: "ok", data: await TAURI_INVOKE("start_lobby", { joinCode, settings }) };
1515+} catch (e) {
1616+ if(e instanceof Error) throw e;
1717+ else return { status: "error", error: e as any };
1818+}
1919+},
2020+/**
2121+ * Quit a running game or leave a lobby
2222+ */
2323+async quitGameOrLobby() : Promise<Result<null, string>> {
2424+ try {
2525+ return { status: "ok", data: await TAURI_INVOKE("quit_game_or_lobby") };
2626+} catch (e) {
2727+ if(e instanceof Error) throw e;
2828+ else return { status: "error", error: e as any };
2929+}
3030+},
3131+/**
3232+ * Get the screen the app should currently be on, returns [AppScreen]
3333+ */
3434+async getCurrentScreen() : Promise<Result<AppScreen, string>> {
3535+ try {
3636+ return { status: "ok", data: await TAURI_INVOKE("get_current_screen") };
3737+} catch (e) {
3838+ if(e instanceof Error) throw e;
3939+ else return { status: "error", error: e as any };
4040+}
4141+},
4242+/**
4343+ * (Screen: Menu) Update the player's profile and persist it
4444+ */
4545+async updateProfile(newProfile: PlayerProfile) : Promise<Result<null, string>> {
4646+ try {
4747+ return { status: "ok", data: await TAURI_INVOKE("update_profile", { newProfile }) };
4848+} catch (e) {
4949+ if(e instanceof Error) throw e;
5050+ else return { status: "error", error: e as any };
5151+}
5252+},
5353+/**
5454+ * (Screen: Lobby) Get the current state of the lobby, call after receiving an update event
5555+ */
5656+async getLobbyState() : Promise<Result<LobbyState, string>> {
5757+ try {
5858+ return { status: "ok", data: await TAURI_INVOKE("get_lobby_state") };
5959+} catch (e) {
6060+ if(e instanceof Error) throw e;
6161+ else return { status: "error", error: e as any };
6262+}
6363+},
6464+/**
6565+ * (Screen: Lobby) HOST ONLY: Push new settings to everyone, does nothing on clients. Returns the
6666+ * new lobby state
6767+ */
6868+async hostUpdateSettings(settings: GameSettings) : Promise<Result<LobbyState, string>> {
6969+ try {
7070+ return { status: "ok", data: await TAURI_INVOKE("host_update_settings", { settings }) };
7171+} catch (e) {
7272+ if(e instanceof Error) throw e;
7373+ else return { status: "error", error: e as any };
7474+}
7575+},
7676+/**
7777+ * (Screen: Lobby) Switch teams between seekers and hiders, returns the new [LobbyState]
7878+ */
7979+async switchTeams(seeker: boolean) : Promise<Result<LobbyState, string>> {
8080+ try {
8181+ return { status: "ok", data: await TAURI_INVOKE("switch_teams", { seeker }) };
8282+} catch (e) {
8383+ if(e instanceof Error) throw e;
8484+ else return { status: "error", error: e as any };
8585+}
8686+},
8787+/**
8888+ * (Screen: Lobby) HOST ONLY: Start the game, stops anyone else from joining and switched screen
8989+ * to AppScreen::Game.
9090+ */
9191+async hostStartGame() : Promise<Result<null, string>> {
9292+ try {
9393+ return { status: "ok", data: await TAURI_INVOKE("host_start_game") };
9494+} catch (e) {
9595+ if(e instanceof Error) throw e;
9696+ else return { status: "error", error: e as any };
9797+}
9898+},
9999+/**
100100+ * (Screen: Game) Mark this player as caught, this player will become a seeker. Returns the new game state
101101+ */
102102+async markCaught() : Promise<Result<GameState, string>> {
103103+ try {
104104+ return { status: "ok", data: await TAURI_INVOKE("mark_caught") };
105105+} catch (e) {
106106+ if(e instanceof Error) throw e;
107107+ else return { status: "error", error: e as any };
108108+}
109109+},
110110+/**
111111+ * (Screen: Game) Grab a powerup on the map, this should be called when the user is *in range* of
112112+ * the powerup. Returns the new game state after rolling for the powerup
113113+ */
114114+async grabPowerup() : Promise<Result<GameState, string>> {
115115+ try {
116116+ return { status: "ok", data: await TAURI_INVOKE("grab_powerup") };
117117+} catch (e) {
118118+ if(e instanceof Error) throw e;
119119+ else return { status: "error", error: e as any };
120120+}
121121+},
122122+/**
123123+ * (Screen: Game) Use the currently held powerup in the player's held_powerup. Does nothing if the
124124+ * player has none. Returns the updated game state
125125+ */
126126+async usePowerup() : Promise<Result<GameState, string>> {
127127+ try {
128128+ return { status: "ok", data: await TAURI_INVOKE("use_powerup") };
129129+} catch (e) {
130130+ if(e instanceof Error) throw e;
131131+ else return { status: "error", error: e as any };
132132+}
133133+}
134134+}
135135+136136+/** user-defined events **/
137137+138138+139139+140140+/** user-defined constants **/
141141+142142+143143+144144+/** user-defined types **/
145145+146146+export type AppScreen = "Setup" | "Menu" | "Lobby" | "Game"
147147+/**
148148+ * Settings for the game, host is the only person able to change these
149149+ */
150150+export type GameSettings = {
151151+/**
152152+ * The random seed used for shared rng
153153+ */
154154+random_seed: number;
155155+/**
156156+ * The number of seconds to wait before seekers are allowed to go
157157+ */
158158+hiding_time_seconds: number;
159159+/**
160160+ * Condition to wait for global pings to begin
161161+ */
162162+ping_start: PingStartCondition;
163163+/**
164164+ * Time between pings after the condition is met (first ping is either after the interval or
165165+ * instantly after the condition is met depending on the condition)
166166+ */
167167+ping_minutes_interval: number;
168168+/**
169169+ * Condition for powerups to start spawning
170170+ */
171171+powerup_start: PingStartCondition;
172172+/**
173173+ * Chance every minute of a powerup spawning, out of 100
174174+ */
175175+powerup_chance: number;
176176+/**
177177+ * Hard cooldown between powerups spawning
178178+ */
179179+powerup_minutes_cooldown: number;
180180+/**
181181+ * Locations that powerups may spawn at
182182+ */
183183+powerup_locations: Location[] }
184184+/**
185185+ * This struct handles all logic regarding state updates
186186+ */
187187+export type GameState = {
188188+/**
189189+ * The id of this player in this game
190190+ */
191191+id: string;
192192+/**
193193+ * The powerup the player is currently holding
194194+ */
195195+held_powerup: PowerUpType | null;
196196+/**
197197+ * When the game started
198198+ */
199199+game_started: string;
200200+/**
201201+ * When seekers were allowed to begin
202202+ */
203203+seekers_started: string | null;
204204+/**
205205+ * Last time we pinged all players
206206+ */
207207+last_global_ping: string | null;
208208+/**
209209+ * Last time a powerup was spawned
210210+ */
211211+last_powerup_spawn: string | null;
212212+/**
213213+ * Hashmap tracking if a player is a seeker (true) or a hider (false)
214214+ */
215215+caught_state: Partial<{ [key in string]: boolean }>;
216216+/**
217217+ * A map of the latest global ping results for each player
218218+ */
219219+pings: Partial<{ [key in string]: PlayerPing }>;
220220+/**
221221+ * Powerup on the map that players can grab. Only one at a time
222222+ */
223223+available_powerup: Location | null }
224224+export type LobbyState = { profiles: Partial<{ [key in string]: PlayerProfile }>; join_code: string;
225225+/**
226226+ * True represents seeker, false hider
227227+ */
228228+teams: Partial<{ [key in string]: boolean }>; self_seeker: boolean; settings: GameSettings }
229229+/**
230230+ * Some location in the world as gotten from a Geolocation API
231231+ */
232232+export type Location = {
233233+/**
234234+ * Latitude
235235+ */
236236+lat: number;
237237+/**
238238+ * Longitude
239239+ */
240240+long: number;
241241+/**
242242+ * The bearing (float normalized from 0 to 1) optional as GPS can't always determine
243243+ */
244244+heading: number | null }
245245+/**
246246+ * The starting condition for global pings to begin
247247+ */
248248+export type PingStartCondition =
249249+/**
250250+ * Wait For X players to be caught before beginning global pings
251251+ */
252252+{ Players: number } |
253253+/**
254254+ * Wait for X minutes after game start to begin global pings
255255+ */
256256+{ Minutes: number } |
257257+/**
258258+ * Don't wait at all, ping location after seekers are released
259259+ */
260260+"Instant"
261261+/**
262262+ * An on-map ping of a player
263263+ */
264264+export type PlayerPing = {
265265+/**
266266+ * Location of the ping
267267+ */
268268+loc: Location;
269269+/**
270270+ * Time the ping happened
271271+ */
272272+timestamp: string;
273273+/**
274274+ * The player to display as
275275+ */
276276+display_player: string;
277277+/**
278278+ * The actual player that initialized this ping
279279+ */
280280+real_player: string }
281281+export type PlayerProfile = { display_name: string; pfp_base64: string | null }
282282+/**
283283+ * Type of powerup
284284+ */
285285+export type PowerUpType =
286286+/**
287287+ * Ping a random seeker instead of a hider
288288+ */
289289+"PingSeeker" |
290290+/**
291291+ * Pings all seekers locations on the map for hiders
292292+ */
293293+"PingAllSeekers" |
294294+/**
295295+ * Ping another random hider instantly
296296+ */
297297+"ForcePingOther"
298298+299299+/** tauri-specta globals **/
300300+301301+import {
302302+ invoke as TAURI_INVOKE,
303303+ Channel as TAURI_CHANNEL,
304304+} from "@tauri-apps/api/core";
305305+import * as TAURI_API_EVENT from "@tauri-apps/api/event";
306306+import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
307307+308308+type __EventObj__<T> = {
309309+ listen: (
310310+ cb: TAURI_API_EVENT.EventCallback<T>,
311311+ ) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
312312+ once: (
313313+ cb: TAURI_API_EVENT.EventCallback<T>,
314314+ ) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
315315+ emit: null extends T
316316+ ? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
317317+ : (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
318318+};
319319+320320+export type Result<T, E> =
321321+ | { status: "ok"; data: T }
322322+ | { status: "error"; error: E };
323323+324324+function __makeEvents__<T extends Record<string, any>>(
325325+ mappings: Record<keyof T, string>,
326326+) {
327327+ return new Proxy(
328328+ {} as unknown as {
329329+ [K in keyof T]: __EventObj__<T[K]> & {
330330+ (handle: __WebviewWindow__): __EventObj__<T[K]>;
331331+ };
332332+ },
333333+ {
334334+ get: (_, event) => {
335335+ const name = mappings[event as keyof T];
336336+337337+ return new Proxy((() => {}) as any, {
338338+ apply: (_, __, [window]: [__WebviewWindow__]) => ({
339339+ listen: (arg: any) => window.listen(name, arg),
340340+ once: (arg: any) => window.once(name, arg),
341341+ emit: (arg: any) => window.emit(name, arg),
342342+ }),
343343+ get: (_, command: keyof __EventObj__<any>) => {
344344+ switch (command) {
345345+ case "listen":
346346+ return (arg: any) => TAURI_API_EVENT.listen(name, arg);
347347+ case "once":
348348+ return (arg: any) => TAURI_API_EVENT.once(name, arg);
349349+ case "emit":
350350+ return (arg: any) => TAURI_API_EVENT.emit(name, arg);
351351+ }
352352+ },
353353+ });
354354+ },
355355+ },
356356+ );
357357+}