use crate::discord::helper::{add_user_to_game, parse_duration_arg}; use crate::error; use crate::error::WoxlfError; use crate::game::global_data::GlobalData; use crate::game::message_router::{ dispatch_message, Median, MessageDest, MessageSource, WoxlfMessage, }; use crate::game::player_data::PlayerData; use crate::game::role::Role; use crate::game::Phase; use crate::messages::DiscordUser; use chrono::Duration; use rand::prelude::SliceRandom; use rand::thread_rng; use serenity::client::Context; use serenity::framework::standard::macros::{command, group}; use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::Message; use serenity::model::guild::Member; use serenity::model::id::{ChannelId, UserId}; use serenity::utils::MessageBuilder; #[group] #[commands(start, say, end, broadcast, next_phase, kill, add_time, test_theme)] struct Host; #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] async fn start(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { msg.channel_id.say(&ctx.http, "Starting game").await?; let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); let guild = msg.guild(&ctx.cache).unwrap(); let mut global_data = global_data.lock().await; let game_name = args.single::()?; let duration = parse_duration_arg(&mut args).await?; global_data.start_game(&game_name, Phase::Night, duration.into())?; let players: error::Result> = args .iter::() .flatten() .map(|discord_id| { let discord_id = match discord_id.parse::() { Ok(discord_id) => discord_id, Err(_) => { return Err(WoxlfError::DiscordIdParseError(discord_id)); } }; if let Some(discord_user) = guild.members.get(&discord_id) { Ok(discord_user) } else { Err(WoxlfError::DiscordIdParseError(discord_id.to_string())) } }) .collect(); let mut players = match players { Ok(players) => players, Err(e) => { let err_msg = match e { WoxlfError::DiscordIdParseError(e) => { format!("Error parsing '{}' as a discord user id.", e) } _ => "Internal bot error".to_string(), }; msg.reply(&ctx.http, err_msg).await?; return Ok(()); } }; players.shuffle(&mut thread_rng()); let mut first_names = global_data.game_cfg()?.first_name.clone(); let mut last_names = global_data.game_cfg()?.last_name.clone(); first_names.shuffle(&mut thread_rng()); last_names.shuffle(&mut thread_rng()); let mut profile_pics = global_data.get_profile_pic_album().await?; profile_pics.shuffle(&mut thread_rng()); let mut roles = global_data.game_cfg()?.roles.clone(); roles.shuffle(&mut thread_rng()); for player in players { let first_name = first_names.pop(); let last_name = last_names.pop(); let profile_pic_url = profile_pics.pop(); let role = roles.pop(); add_user_to_game( ctx, &guild, &mut global_data, player, first_name, last_name, profile_pic_url, role, ) .await?; } for player_data in &global_data.game_state()?.player_data { let channel = ChannelId::from(player_data.channel); let discord_user = guild.member(&ctx.http, player_data.discord_id).await?; let intro_message = global_data.templates()?.build_welcome_message( &global_data, &DiscordUser::from(&discord_user), player_data, )?; let msg = channel .send_message(&ctx.http, |m| m.content(intro_message)) .await?; channel.pin(&ctx.http, msg.id).await?; } global_data.save_game_state().unwrap(); ctx.http .edit_nickname(guild.id.0, Some(&global_data.game_cfg()?.bot_name)) .await?; Ok(()) } #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] async fn end(ctx: &Context, msg: &Message, mut _args: Args) -> CommandResult { let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); let guild = msg.guild(&ctx.cache).unwrap(); let mut global_data = global_data.lock().await; for player_data in &global_data.game_state_mut()?.player_data { let channel = guild .channels .get(&ChannelId::from(player_data.channel)) .unwrap(); channel.delete(&ctx.http).await?; } global_data.clear_game_state()?; ctx.http.edit_nickname(guild.id.0, None).await?; msg.reply(&ctx.http, "Game ended!").await.unwrap(); Ok(()) } #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] async fn say(ctx: &Context, _msg: &Message, args: Args) -> CommandResult { let data = ctx.data.read().await; let global_data = data.get::().unwrap(); let mut global_data = global_data.lock().await; let msg = WoxlfMessage::default() .source(MessageSource::Host) .dest(MessageDest::Broadcast) .median(Median::Webhook) .content(args.rest()) .clone(); dispatch_message(ctx, &mut global_data, msg).await?; Ok(()) } #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] async fn broadcast(ctx: &Context, _msg: &Message, args: Args) -> CommandResult { let data = ctx.data.read().await; let global_data = data.get::().unwrap(); let mut global_data = global_data.lock().await; let broadcast = global_data .templates()? .build_announcement(&global_data, args.rest())?; let woxlf_msg = WoxlfMessage::default() .source(MessageSource::Automated) .dest(MessageDest::Broadcast) .content(&broadcast) .median(Median::Webhook) .clone(); dispatch_message(ctx, &mut global_data, woxlf_msg).await?; Ok(()) } #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let data = ctx.data.read().await; let global_data = data.get::().unwrap(); let guild = msg.guild(&ctx.cache).unwrap(); let mut global_data = global_data.lock().await; let duration = parse_duration_arg(&mut args).await?; global_data.game_state_mut()?.next_phase(duration.into()); let broadcast = MessageBuilder::new() .push_line(args.rest()) .push_line("") .push(global_data.templates()?.build_satus_message(&global_data)?) .build(); let broadcast = global_data .templates()? .build_announcement(&global_data, &broadcast)?; let woxlf_msg = WoxlfMessage::default() .source(MessageSource::Automated) .dest(MessageDest::Broadcast) .median(Median::Webhook) .content(&broadcast) .clone(); dispatch_message(ctx, &mut global_data, woxlf_msg).await?; if global_data.game_state_mut()?.current_phase == Phase::Day { let vote_channel = guild .channels .get(&ChannelId::from( global_data.cfg.discord_config.vote_channel, )) .unwrap(); vote_channel .id() .send_message(&ctx.http, |m| { m.content(format!( "**{} {} Votes:**", global_data.game_cfg().unwrap().vote_phase_name.clone(), &global_data.game_state_mut().unwrap().phase_number )) }) .await?; } msg.reply( &ctx.http, format!( "Phase has been cycled to {}.", &global_data.get_phase_name()? ), ) .await?; global_data.save_game_state()?; Ok(()) } #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] async fn kill(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); let mut global_data = global_data.lock().await; let target = args.rest().to_lowercase(); let player = global_data .game_state_mut()? .get_player_by_codename_mut(&target); if let Some(player) = player { player.alive = false; msg.reply(&ctx.http, format!("{} has been killed.", player.codename)) .await?; } else { msg.reply(&ctx.http, "No player found with that codename.") .await?; } global_data.save_game_state().unwrap(); Ok(()) } #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] #[description = "Display all the messages from a theme"] async fn test_theme(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); let global_data = global_data.lock().await; let theme = args.parse::()?; let mut test_global_data = GlobalData::new(global_data.cfg.clone()); test_global_data.start_game(&theme, Phase::Day, Duration::hours(1))?; let test_player_1 = PlayerData { channel: 0, discord_id: 0, codename: "Test Player 1".to_string(), vote_target: None, profile_pic_url: "".to_string(), channel_webhook_id: 0, alive: true, role: Role::Villager, }; let player_1_discord = DiscordUser { display_name: "Test Player Discord".to_string(), mention: "@Test Player Discord".to_string(), }; let test_player_2 = PlayerData { channel: 0, discord_id: 0, codename: "Test Player 2".to_string(), vote_target: None, profile_pic_url: "".to_string(), channel_webhook_id: 0, alive: false, role: Role::Villager, }; let mut players = vec![test_player_1.clone(), test_player_2.clone()]; test_global_data .game_state_mut()? .player_data .append(&mut players); let codename_format = test_global_data.templates()?.build_name( &test_global_data, Some("FirstName".to_lowercase()), Some("LastName".to_lowercase()), )?; let phase_extend_message = test_global_data .templates()? .build_phase_extend_message(&test_global_data)?; let vote_message = test_global_data.templates()?.build_vote_message( &test_global_data, &test_player_1, &test_player_2, )?; let status_message = test_global_data .templates()? .build_satus_message(&test_global_data)?; let announcement_message = test_global_data .templates()? .build_announcement(&test_global_data, "Test announcement")?; let welcome_message = test_global_data.templates()?.build_welcome_message( &test_global_data, &player_1_discord, &test_player_1, )?; let tally_message = test_global_data .templates()? .build_vote_tally(&test_global_data)?; msg.channel_id .send_message(&ctx.http, |m| m.content("==Codename Format==")) .await?; msg.channel_id .send_message(&ctx.http, |m| m.content(codename_format)) .await?; tokio::time::sleep(std::time::Duration::from_millis(500)).await; msg.channel_id .send_message(&ctx.http, |m| m.content("==Phase Extend Message==")) .await?; msg.channel_id .send_message(&ctx.http, |m| m.content(phase_extend_message)) .await?; tokio::time::sleep(std::time::Duration::from_millis(500)).await; msg.channel_id .send_message(&ctx.http, |m| m.content("==Vote Message==")) .await?; msg.channel_id .send_message(&ctx.http, |m| m.content(vote_message)) .await?; tokio::time::sleep(std::time::Duration::from_millis(500)).await; msg.channel_id .send_message(&ctx.http, |m| m.content("==Status Message==")) .await?; msg.channel_id .send_message(&ctx.http, |m| m.content(status_message)) .await?; tokio::time::sleep(std::time::Duration::from_millis(500)).await; msg.channel_id .send_message(&ctx.http, |m| m.content("==Announcement Message==")) .await?; msg.channel_id .send_message(&ctx.http, |m| m.content(announcement_message)) .await?; tokio::time::sleep(std::time::Duration::from_millis(500)).await; msg.channel_id .send_message(&ctx.http, |m| m.content("==Welcome Message==")) .await?; msg.channel_id .send_message(&ctx.http, |m| m.content(welcome_message)) .await?; tokio::time::sleep(std::time::Duration::from_millis(500)).await; msg.channel_id .send_message(&ctx.http, |m| m.content("==Tally Message==")) .await?; msg.channel_id .send_message(&ctx.http, |m| m.content(tally_message)) .await?; tokio::time::sleep(std::time::Duration::from_millis(500)).await; Ok(()) } #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] async fn add_time(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let data = ctx.data.read().await; let global_data = data.get::().unwrap(); let mut global_data = global_data.lock().await; let duration = parse_duration_arg(&mut args).await?; global_data .game_state_mut()? .add_time_to_phase(duration.into()); let broadcast = MessageBuilder::new() .push( global_data .templates()? .build_phase_extend_message(&global_data)?, ) .push_line("") .push(global_data.templates()?.build_satus_message(&global_data)?) .build(); let broadcast = global_data .templates()? .build_announcement(&global_data, &broadcast)?; let woxlf_msg = WoxlfMessage::default() .source(MessageSource::Automated) .dest(MessageDest::Broadcast) .median(Median::Webhook) .content(&broadcast) .clone(); dispatch_message(ctx, &mut global_data, woxlf_msg).await?; msg.reply(&ctx.http, "Phase has been updated") .await .unwrap(); global_data.save_game_state().unwrap(); Ok(()) }