use config::{Config, File}; use serde::{Deserialize, Serialize}; use serenity::prelude::TypeMapKey; use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use structopt::StructOpt; use tokio::sync::Mutex; #[derive(Debug, StructOpt)] #[structopt(name = "WOXlf", about = "WOXlf discord bot")] pub struct Args { pub cfg_path: PathBuf, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct BotConfig { pub token: String, pub app_id: u64, pub host_channel: u64, pub vote_channel: u64, pub category: u64, pub game_state_dir: PathBuf, pub occupation: Vec, pub adjective: Vec, } impl BotConfig { pub fn new(config_path: &Path) -> Result { let cfg = Config::builder() .add_source(File::from(config_path)) .build()?; cfg.try_deserialize() } pub fn get_game_state_path(&self) -> PathBuf { self.game_state_dir.join("wOxlf_data.toml") } } #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] pub enum Phase { Day, Night, } impl Default for Phase { fn default() -> Self { Self::Night } } impl Display for Phase { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let phase_name = match self { Self::Day => "Day", Self::Night => "Night", }; write!(f, "{}", phase_name) } } #[derive(Debug, Deserialize, Serialize, Clone, Default, Hash)] pub struct PlayerData { pub channel: u64, pub discord_id: u64, pub codename: String, pub vote_target: Option, } #[derive(Debug, Deserialize, Serialize, Clone, Default)] pub struct GameState { pub phase_number: u64, pub current_phase: Phase, pub phase_end_time: u64, pub player_data: Vec, } impl GameState { pub fn codename_exists(&self, codename: &str) -> bool { self.player_data .iter() .any(|data| data.codename.to_lowercase() == codename) } pub fn clear(&mut self) { self.player_data.clear(); self.current_phase = Phase::Night; self.phase_end_time = 0; self.phase_number = 1; } pub fn get_player_from_channel(&self, channel_id: u64) -> Option<&PlayerData> { self.player_data.iter().find(|p| p.channel == channel_id) } pub fn get_player_from_discord_id(&self, discord_id: u64) -> Option<&PlayerData> { self.player_data.iter().find(|p| p.discord_id == discord_id) } pub fn get_player_from_channel_mut(&mut self, channel_id: u64) -> Option<&mut PlayerData> { self.player_data .iter_mut() .find(|p| p.channel == channel_id) } pub fn get_player_by_codename(&self, codename: &str) -> Option { self.player_data .iter() .find(|p| p.codename.to_lowercase() == codename.to_lowercase()) .cloned() } pub fn next_phase(&mut self) { if self.current_phase == Phase::Night { self.current_phase = Phase::Day } else { self.phase_number += 1; self.current_phase = Phase::Night } for mut player in &mut self.player_data { player.vote_target = None } } pub fn get_phase_end_time(&self) -> String { format!("", self.phase_end_time) } pub fn get_phase_countdown(&self) -> String { format!("", self.phase_end_time) } pub fn add_time_to_phase(&mut self, hours: u64) { self.phase_end_time += hours * 60 * 60; } pub fn get_vote_tallies(&self) -> HashMap { let mut vote_set: HashMap = HashMap::new(); for player in &self.player_data { if let Some(vote_target) = player.vote_target { let target = self.get_player_from_discord_id(vote_target); if let Some(target) = target { *vote_set.entry(target.codename.clone()).or_insert(0) += 1; } } } vote_set } } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct GlobalData { pub cfg: BotConfig, pub game_state: GameState, } impl GlobalData { pub fn new(cfg: BotConfig) -> Self { Self { cfg, game_state: GameState::default(), } } } impl TypeMapKey for GlobalData { type Value = Arc>; } #[derive(Debug, Deserialize, Serialize, Clone)] pub enum MessageSource { Player(u64), Host, Automated, }