wOxlf/src/discord/host.rs

489 lines
14 KiB
Rust

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::<GlobalData>().unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await;
let game_name = args.single::<String>()?;
let duration = parse_duration_arg(&mut args).await?;
global_data.start_game(&game_name, Phase::Night, duration.into())?;
let players: error::Result<Vec<&Member>> = args
.iter::<String>()
.flatten()
.map(|discord_id| {
let discord_id = match discord_id.parse::<UserId>() {
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::<GlobalData>().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::<GlobalData>().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::<GlobalData>().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::<GlobalData>().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::<GlobalData>().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::<GlobalData>().unwrap();
let global_data = global_data.lock().await;
let theme = args.parse::<String>()?;
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::<GlobalData>().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(())
}