Added config for themes. Fixes #1

* WOxlf now supports a number of game themes
 * Each theme can have custom messages, profile pics, phrasing, etc
 * Messages are defined as tera templates
* Couple small improvements
+ Clippy + fmt
This commit is contained in:
2022-05-28 16:31:41 -06:00
parent 2e5c102887
commit 230930a23c
12 changed files with 771 additions and 191 deletions
+99 -55
View File
@@ -13,13 +13,12 @@ use serenity::model::prelude::{Message, UserId};
use serenity::prelude::Context;
use serenity::utils::MessageBuilder;
use crate::discord::helper::{
add_user_to_game, build_system_message, parse_duration_arg, send_msg_to_player_channels,
};
use crate::discord::helper::{add_user_to_game, parse_duration_arg, send_msg_to_player_channels};
use crate::error::{Result, WoxlfError};
use crate::game::global_data::GlobalData;
use crate::game::MessageSource;
use crate::game::Phase;
use crate::messages::DiscordUser;
#[group]
#[commands(start, say, end, broadcast, next_phase, terminate, add_time)]
@@ -36,9 +35,10 @@ async fn start(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.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(Phase::Night, duration.into());
global_data.start_game(&game_name, Phase::Night, duration.into())?;
let players: Result<Vec<&Member>> = args
.iter::<String>()
@@ -77,12 +77,38 @@ async fn start(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
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());
for player in players {
add_user_to_game(ctx, &guild, &mut global_data, player).await?;
let first_name = first_names.pop();
let last_name = last_names.pop();
add_user_to_game(ctx, &guild, &mut global_data, player, first_name, last_name).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(())
}
@@ -107,6 +133,7 @@ async fn end(ctx: &Context, msg: &Message, mut _args: Args) -> CommandResult {
global_data.clear_game_state()?;
ctx.http.edit_nickname(guild.id.0, None).await?;
msg.reply(&ctx.http, "Game ended!").await.unwrap();
Ok(())
}
@@ -121,14 +148,12 @@ async fn say(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let mut global_data = global_data.lock().await;
let msg = format!("**wOxlf **> {}", args.rest());
send_msg_to_player_channels(
ctx,
&guild,
&mut global_data,
MessageSource::Host,
&msg,
args.rest(),
None,
false,
)
@@ -147,7 +172,9 @@ async fn broadcast(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let mut global_data = global_data.lock().await;
let msg = build_system_message(args.rest());
let msg = global_data
.templates()?
.build_announcement(&global_data, args.rest())?;
send_msg_to_player_channels(
ctx,
@@ -180,10 +207,12 @@ async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
let broadcast = MessageBuilder::new()
.push_line(args.rest())
.push_line("")
.push(global_data.print_game_status())
.push(global_data.templates()?.build_satus_message(&global_data)?)
.build();
let broadcast = build_system_message(&broadcast);
let broadcast = global_data
.templates()?
.build_announcement(&global_data, &broadcast)?;
send_msg_to_player_channels(
ctx,
@@ -199,13 +228,16 @@ async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
if global_data.game_state_mut()?.current_phase == Phase::Day {
let vote_channel = guild
.channels
.get(&ChannelId::from(global_data.cfg.vote_channel))
.get(&ChannelId::from(
global_data.cfg.discord_config.vote_channel,
))
.unwrap();
vote_channel
.send_message(&ctx.http, |m| {
m.content(format!(
"**DAY {} VOTES:**",
global_data.game_state_mut().unwrap().phase_number
"**{} {} Votes:**",
global_data.game_cfg().unwrap().vote_phase_name.clone(),
&global_data.game_state_mut().unwrap().phase_number
))
})
.await?;
@@ -215,7 +247,7 @@ async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
&ctx.http,
format!(
"Phase has been cycled to {}.",
&global_data.game_state_mut()?.current_phase
&global_data.get_phase_name()?
),
)
.await?;
@@ -252,14 +284,11 @@ async fn terminate(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
player_channel.delete(&ctx.http).await.unwrap();
msg.reply(
&ctx.http,
format!("{} has been terminated.", player.codename),
)
.await
.unwrap();
msg.reply(&ctx.http, format!("{} ", player.codename))
.await
.unwrap();
} else {
msg.reply(&ctx.http, "No subject found with that codename.")
msg.reply(&ctx.http, "No player found with that codename.")
.await
.unwrap();
}
@@ -285,12 +314,18 @@ async fn add_time(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
.add_time_to_phase(duration.into());
let broadcast = MessageBuilder::new()
.push_line("EXPERIMENT PHASE HAS BEEN EXTENDED!!!")
.push(
global_data
.templates()?
.build_phase_extend_message(&global_data)?,
)
.push_line("")
.push(global_data.print_game_status())
.push(global_data.templates()?.build_satus_message(&global_data)?)
.build();
let broadcast = build_system_message(&broadcast);
let broadcast = global_data
.templates()?
.build_announcement(&global_data, &broadcast)?;
send_msg_to_player_channels(
ctx,
@@ -328,7 +363,10 @@ async fn vote(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
if global_data.game_state_mut()?.current_phase != Phase::Day {
msg.reply(
&ctx.http,
"You can only select subject for termination during the day!",
format!(
"You can only vote during the {} phase.",
global_data.game_cfg()?.vote_phase_name
),
)
.await
.unwrap();
@@ -347,7 +385,9 @@ async fn vote(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
if let Some(target_player) = target_player {
let vote_channel = guild
.channels
.get(&ChannelId::from(global_data.cfg.vote_channel))
.get(&ChannelId::from(
global_data.cfg.discord_config.vote_channel,
))
.unwrap();
let player_data = global_data
@@ -357,25 +397,30 @@ async fn vote(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
player_data.cast_vote(target_player.discord_id);
vote_channel
.send_message(&ctx.http, |m| {
m.content(format!(
"{} has selected {} for termination",
&player_data.codename, target_player.codename
))
})
.await
// borrow as immutable
let player_data = global_data
.game_state()?
.get_player_from_channel(msg.channel_id.0)
.unwrap();
let vote_msg = global_data.templates()?.build_vote_message(
&global_data,
player_data,
&target_player,
)?;
vote_channel
.send_message(&ctx.http, |m| m.content(vote_msg))
.await?;
} else {
msg.reply(&ctx.http, "Subject not found!").await.unwrap();
msg.reply(&ctx.http, "Target not found!").await.unwrap();
}
} else {
msg.reply(
&ctx.http,
"This command needs to be run in a game channel, goober",
)
.await
.unwrap();
.await?;
}
global_data.save_game_state().unwrap();
@@ -384,7 +429,7 @@ async fn vote(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
#[command]
#[only_in(guilds)]
#[description = "Get the game status. $status"]
#[description = "Get the game status."]
async fn status(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
@@ -393,21 +438,20 @@ async fn status(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let mut msg_builder = MessageBuilder::new();
msg_builder.push(global_data.print_game_status());
msg_builder.push(
global_data
.templates()?
.build_satus_message(&global_data)
.unwrap(),
);
if global_data.game_state_mut()?.current_phase == Phase::Day {
msg_builder.push_line("");
let vote_tallies = global_data.game_state_mut()?.get_vote_tallies();
if vote_tallies.is_empty() {
msg_builder.push_line("NO TERMINATION VOTES HAVE BEEN CAST");
} else {
msg_builder.push_line("TERMINATION VOTE TALLIES:");
for (player, tally) in global_data.game_state_mut()?.get_vote_tallies() {
msg_builder.push_line(format!("{}: {}", player, tally));
}
}
msg_builder.push_line(
global_data
.templates()?
.build_vote_tally(&global_data)
.unwrap(),
);
}
msg.reply(&ctx.http, msg_builder.build()).await.unwrap();
@@ -417,7 +461,7 @@ async fn status(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
#[command]
#[only_in(guilds)]
#[description = "Get the other players in the game. $status"]
#[description = "Get the other players in the game."]
async fn players(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let data = ctx.data.read().await;
let global_data = data.get::<GlobalData>().unwrap();
@@ -426,12 +470,12 @@ async fn players(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let mut msg_builder = MessageBuilder::new();
msg_builder.push_line("Test Subjects:");
msg_builder.push_line(&global_data.game_cfg()?.player_group_name);
for player in &global_data.game_state()?.player_data {
msg_builder.push("* ").push(&player.codename);
if msg.channel_id.0 == global_data.cfg.host_channel {
if msg.channel_id.0 == global_data.cfg.discord_config.host_channel {
let guild = msg.guild(&ctx.cache).await.unwrap();
let member = guild.members.get(&UserId::from(player.discord_id)).unwrap();
msg_builder.push_line(format!(" ({})", member.display_name()));
+21 -40
View File
@@ -5,14 +5,13 @@ use serenity::model::channel::{Message, PermissionOverwrite, PermissionOverwrite
use serenity::model::guild::{Guild, Member};
use serenity::model::id::{ChannelId, UserId};
use serenity::model::Permissions;
use serenity::utils::MessageBuilder;
use crate::error;
use crate::error::WoxlfError;
use crate::game::game_state::PhaseDuration;
use crate::game::global_data::GlobalData;
use crate::game::player_data::PlayerData;
use crate::game::MessageSource;
use crate::{error, game};
use serenity::prelude::SerenityError;
fn filter_source_channel(player_data: &&PlayerData, msg_source: &MessageSource) -> bool {
@@ -75,7 +74,9 @@ pub async fn send_msg_to_player_channels(
let host_channel = guild
.channels
.get(&ChannelId::from(global_data.cfg.host_channel))
.get(&ChannelId::from(
global_data.cfg.discord_config.host_channel,
))
.unwrap();
let source = match msg_source {
@@ -195,7 +196,7 @@ pub async fn send_webhook_msg_to_player_channels(
send_webhook_msg(
&ctx.http,
global_data.cfg.host_webhook_id,
global_data.cfg.discord_config.host_webhook_id,
&host_channel_username,
profile_pic,
msg,
@@ -210,16 +211,21 @@ pub async fn add_user_to_game(
guild: &Guild,
global_data: &mut GlobalData,
discord_user: &Member,
) -> error::Result<()> {
let mut codename = game::generate_codename(&global_data.cfg);
while global_data.game_state_mut()?.codename_exists(&codename) {
codename = game::generate_codename(&global_data.cfg);
first_name: Option<String>,
last_name: Option<String>,
) -> error::Result<PlayerData> {
if first_name == None && last_name == None {
return Err(WoxlfError::RanOutOfCodenames);
}
let codename = global_data
.templates()?
.build_name(global_data, first_name, last_name)
.unwrap();
let channel = guild
.create_channel(&ctx.http, |c| {
c.category(&ChannelId::from(global_data.cfg.category))
c.category(&ChannelId::from(global_data.cfg.discord_config.category))
.name(format!("{}'s Channel", discord_user.display_name()))
})
.await?;
@@ -242,24 +248,6 @@ pub async fn add_user_to_game(
)
.await?;
let msg = channel.send_message(&ctx.http, |m| {
m.content(MessageBuilder::new()
.push("Welcome ")
.mention(discord_user)
.push_line(" to your WOxlf Terminal. You may use this terminal to communicate to other subjects.")
.push_line("You will also use this terminal for choosing one of your fellow subjects for termination.")
.push_line("Happy testing :)")
.push_line("")
.push_line("Do $help to see all commands.")
.push_line("")
.push("SUBJECT CODENAME: ")
.push_line(&codename)
.push(global_data.print_game_status())
)
}).await?;
channel.pin(&ctx.http, msg.id).await?;
let player_data = PlayerData {
channel: channel.id.0,
discord_id: discord_user.user.id.0,
@@ -269,19 +257,12 @@ pub async fn add_user_to_game(
profile_pic_url: global_data.get_profile_pic_url().await?,
};
global_data.game_state_mut()?.player_data.push(player_data);
global_data
.game_state_mut()?
.player_data
.push(player_data.clone());
Ok(())
}
pub fn build_system_message(msg: &str) -> String {
MessageBuilder::new()
.push_bold_line("\\*\\*IMPORTANT wOxlf SYSTEM MESSAGE\\*\\*")
.push_line("")
.push_line(msg)
.push_line("")
.push_bold_line("\\*\\*END OF SYSTEM MESSAGE\\*\\*")
.build()
Ok(player_data)
}
pub async fn parse_duration_arg(args: &mut Args) -> error::Result<PhaseDuration> {