Added remaining bare minimum functionality
+ Added phase handling and player termination + Fixed an issue with some commands not saving game data + Added game_status command + Updated readme with commands and new features + clippy + fmtmsg_refactor
parent
e72d603149
commit
10fa6f6906
33
README.md
33
README.md
|
@ -5,10 +5,31 @@ Discord bot for managing an anonymous [Werewolf Game](https://en.wikipedia.org/w
|
|||
A host gets a list of players to play, and then begins the game with the `!start` command.
|
||||
|
||||
Each player is assigned a channel where they will view the game through. The player can read and send messages
|
||||
in this channel normally. When a message is sent, it is forwarded to all other channels. The message's author
|
||||
is obscured by a codename.
|
||||
in this channel normally. When a message is sent, it is forwarded to all other player channels. The message's author
|
||||
is obscured by a codename. During day phases, players can cast a vote for who they wish to "terminate" that day.
|
||||
|
||||
The bot also handles communications from the host, daily votes, and notifying players of deaths.
|
||||
The game proceeds as a normal Werewolf game.
|
||||
|
||||
## Channels
|
||||
* Host Channel: Used by the host to interact with the game, can also be used by spectators to see the game state.
|
||||
* Real player names are displayed along with code names in this channel
|
||||
* Player Channel: A channel for a single player, allows them to chat with the other players and run commands.
|
||||
* Vote Channel: Contains all the votes made in a game.
|
||||
|
||||
## Commands
|
||||
|
||||
## Host
|
||||
* `!start <list of player ids>` - starts the game
|
||||
* `!end` - Ends the current game
|
||||
* `!say <msg>` - Allows the host to speak into the game chat
|
||||
* `!broadcast <msg>` - Broadcasts a system message, this message is then pinned in each player channel
|
||||
* `!next_phase <duration> <msg>` - Send the next phase message. Also cycles the phase
|
||||
* `!terminate <player>` - Kills a player and removes them from the game
|
||||
* `!add_time <duration>` - Adds more time to the current game
|
||||
|
||||
## Players
|
||||
* `!vote <player>` - Casts a vote for a player to be terminated. Only can be used during the day phase
|
||||
* `!status` - Get the current game status. Includes time left in the phase and current vote tallies
|
||||
|
||||
## Example Config
|
||||
```toml
|
||||
|
@ -18,8 +39,12 @@ token = ""
|
|||
app_id = 0
|
||||
# Channel to accept host commands from
|
||||
host_channel = 1
|
||||
# Channel to put vote status messages in
|
||||
vote_channel = 2
|
||||
# Category to crate the player cannels in
|
||||
category = 2
|
||||
category = 3
|
||||
# Directory to save game data into, game data will be saved as "wOxlf.toml"
|
||||
game_state_dir = "."
|
||||
|
||||
# Random code names are generated in the format of <Adjective> <Occupation>
|
||||
|
||||
|
|
370
src/commands.rs
370
src/commands.rs
|
@ -1,87 +1,22 @@
|
|||
use crate::data::{BotConfig, GlobalData, MessageSource, PlayerData};
|
||||
use crate::helper::{clear_game_state, save_game_state, send_msg_to_player_channels};
|
||||
use rand::Rng;
|
||||
use serenity::framework::standard::macros::{command, group};
|
||||
use serenity::framework::standard::{Args, CommandResult};
|
||||
use serenity::framework::StandardFramework;
|
||||
use serenity::model::channel::{PermissionOverwrite, PermissionOverwriteType};
|
||||
use serenity::model::guild::{Guild, Member};
|
||||
use serenity::model::id::ChannelId;
|
||||
use serenity::model::prelude::{Message, UserId};
|
||||
use serenity::model::Permissions;
|
||||
use serenity::prelude::Context;
|
||||
use serenity::utils::MessageBuilder;
|
||||
|
||||
use crate::data::{GlobalData, MessageSource, Phase};
|
||||
use crate::helper;
|
||||
use crate::helper::{
|
||||
build_system_message, clear_game_state, get_phase_end_timestamp, print_game_status,
|
||||
save_game_state, send_msg_to_player_channels,
|
||||
};
|
||||
|
||||
#[group]
|
||||
#[commands(start, say, end, broadcast)]
|
||||
#[commands(start, say, end, broadcast, next_phase, terminate, add_time)]
|
||||
struct Host;
|
||||
|
||||
fn generate_codename(config: &BotConfig) -> String {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let occupation = &config.occupation[rng.gen_range(0..config.occupation.len())];
|
||||
let adj = &config.adjective[rng.gen_range(0..config.adjective.len())];
|
||||
|
||||
format!("{} {}", adj, occupation)
|
||||
}
|
||||
|
||||
async fn add_user_to_game(
|
||||
ctx: &Context,
|
||||
guild: &Guild,
|
||||
global_data: &mut GlobalData,
|
||||
discord_user: &Member,
|
||||
) -> CommandResult {
|
||||
let mut codename = generate_codename(&global_data.cfg);
|
||||
|
||||
while global_data.game_state.codename_exists(&codename) {
|
||||
codename = generate_codename(&global_data.cfg);
|
||||
}
|
||||
|
||||
let channel = guild
|
||||
.create_channel(&ctx.http, |c| {
|
||||
c.category(&ChannelId::from(global_data.cfg.category))
|
||||
.name(format!("{}'s Channel", discord_user.display_name()))
|
||||
})
|
||||
.await?;
|
||||
|
||||
let allow = Permissions::SEND_MESSAGES
|
||||
| Permissions::READ_MESSAGE_HISTORY
|
||||
| Permissions::READ_MESSAGE_HISTORY;
|
||||
|
||||
let overwrite = PermissionOverwrite {
|
||||
allow,
|
||||
deny: Default::default(),
|
||||
kind: PermissionOverwriteType::Member(discord_user.user.id),
|
||||
};
|
||||
|
||||
channel.create_permission(&ctx.http, &overwrite).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("SUBJECT CODENAME: ")
|
||||
.push_line(&codename)
|
||||
)
|
||||
}).await?;
|
||||
|
||||
channel.pin(&ctx.http, msg.id).await?;
|
||||
|
||||
let player_data = PlayerData {
|
||||
channel: channel.id.0,
|
||||
discord_id: discord_user.user.id.0,
|
||||
codename,
|
||||
};
|
||||
|
||||
global_data.game_state.player_data.push(player_data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[allowed_roles("wolfx host")]
|
||||
|
@ -96,9 +31,21 @@ async fn start(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
|
||||
clear_game_state(&mut global_data).unwrap();
|
||||
|
||||
let duration = match args.single::<u64>() {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
msg.reply(&ctx.http, "Error parsing phase duration!")
|
||||
.await
|
||||
.unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
global_data.game_state.phase_end_time = get_phase_end_timestamp(duration);
|
||||
|
||||
for player in args.iter::<u64>().flatten() {
|
||||
if let Some(discord_user) = guild.members.get(&UserId::from(player)) {
|
||||
add_user_to_game(ctx, &guild, &mut global_data, discord_user).await?;
|
||||
helper::add_user_to_game(ctx, &guild, &mut global_data, discord_user).await?;
|
||||
} else {
|
||||
msg.reply(
|
||||
&ctx.http,
|
||||
|
@ -136,6 +83,7 @@ async fn end(ctx: &Context, msg: &Message, mut _args: Args) -> CommandResult {
|
|||
|
||||
clear_game_state(&mut global_data).unwrap();
|
||||
|
||||
msg.reply(&ctx.http, "Game ended!").await.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -166,21 +114,281 @@ async fn broadcast(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
|||
|
||||
let global_data = global_data.lock().await;
|
||||
|
||||
let msg = MessageBuilder::new()
|
||||
.push_bold_line("\\*\\*IMPORTANT wOxlf SYSTEM MESSAGE\\*\\*")
|
||||
.push_line("")
|
||||
.push_line(args.rest())
|
||||
.push_line("")
|
||||
.push_bold_line("\\*\\*END OF SYSTEM MESSAGE\\*\\*")
|
||||
.build();
|
||||
let msg = build_system_message(args.rest());
|
||||
|
||||
send_msg_to_player_channels(ctx, &guild, &global_data, MessageSource::Host, &msg, true).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[allowed_roles("wolfx host")]
|
||||
async fn next_phase(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).await.unwrap();
|
||||
|
||||
let mut global_data = global_data.lock().await;
|
||||
|
||||
let duration = match args.single::<u64>() {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
msg.reply(&ctx.http, "Error parsing phase duration!")
|
||||
.await
|
||||
.unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
global_data.game_state.next_phase();
|
||||
|
||||
global_data.game_state.phase_end_time = get_phase_end_timestamp(duration);
|
||||
|
||||
let broadcast = MessageBuilder::new()
|
||||
.push_line(args.rest())
|
||||
.push_line("")
|
||||
.push(print_game_status(&global_data.game_state))
|
||||
.build();
|
||||
|
||||
let broadcast = build_system_message(&broadcast);
|
||||
|
||||
send_msg_to_player_channels(
|
||||
ctx,
|
||||
&guild,
|
||||
&global_data,
|
||||
MessageSource::Host,
|
||||
&broadcast,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
if global_data.game_state.current_phase == Phase::Day {
|
||||
let vote_channel = guild
|
||||
.channels
|
||||
.get(&ChannelId::from(global_data.cfg.vote_channel))
|
||||
.unwrap();
|
||||
vote_channel
|
||||
.send_message(&ctx.http, |m| {
|
||||
m.content(format!(
|
||||
"**DAY {} VOTES:**",
|
||||
global_data.game_state.phase_number
|
||||
))
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
msg.reply(
|
||||
&ctx.http,
|
||||
format!(
|
||||
"Phase has been cycled to {}.",
|
||||
&global_data.game_state.current_phase
|
||||
),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
save_game_state(&global_data).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[allowed_roles("wolfx host")]
|
||||
async fn terminate(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
let mut data = ctx.data.write().await;
|
||||
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||
let guild = msg.guild(&ctx.cache).await.unwrap();
|
||||
|
||||
let mut global_data = global_data.lock().await;
|
||||
|
||||
let target = args.rest().to_lowercase();
|
||||
let index = global_data
|
||||
.game_state
|
||||
.player_data
|
||||
.iter()
|
||||
.position(|p| p.codename.to_lowercase() == target);
|
||||
|
||||
if let Some(index) = index {
|
||||
let player = global_data.game_state.player_data.remove(index);
|
||||
|
||||
let player_channel = guild
|
||||
.channels
|
||||
.get(&ChannelId::from(player.channel))
|
||||
.unwrap();
|
||||
|
||||
player_channel.delete(&ctx.http).await.unwrap();
|
||||
|
||||
msg.reply(
|
||||
&ctx.http,
|
||||
format!("{} has been terminated.", player.codename),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
msg.reply(&ctx.http, "No subject found with that codename.")
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
save_game_state(&global_data).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[allowed_roles("wolfx host")]
|
||||
async fn add_time(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).await.unwrap();
|
||||
|
||||
let mut global_data = global_data.lock().await;
|
||||
|
||||
global_data.game_state.next_phase();
|
||||
|
||||
let duration = match args.single::<u64>() {
|
||||
Ok(d) => d,
|
||||
Err(_) => {
|
||||
msg.reply(&ctx.http, "Error parsing phase duration!")
|
||||
.await
|
||||
.unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
global_data.game_state.add_time_to_phase(duration);
|
||||
|
||||
let broadcast = MessageBuilder::new()
|
||||
.push_line("EXPERIMENT PHASE HAS BEEN EXTENDED!!!")
|
||||
.push_line("")
|
||||
.push(print_game_status(&global_data.game_state))
|
||||
.build();
|
||||
|
||||
let broadcast = build_system_message(&broadcast);
|
||||
|
||||
send_msg_to_player_channels(
|
||||
ctx,
|
||||
&guild,
|
||||
&global_data,
|
||||
MessageSource::Host,
|
||||
&broadcast,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
msg.reply(&ctx.http, "Phase has been updated")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
save_game_state(&global_data).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[group]
|
||||
#[commands(vote, status)]
|
||||
struct Player;
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
async fn vote(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
let mut data = ctx.data.write().await;
|
||||
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||
let guild = msg.guild(&ctx.cache).await.unwrap();
|
||||
|
||||
let mut global_data = global_data.lock().await;
|
||||
|
||||
if global_data.game_state.current_phase != Phase::Day {
|
||||
msg.reply(
|
||||
&ctx.http,
|
||||
"You can only select subject for termination during the day!",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if global_data
|
||||
.game_state
|
||||
.get_player_from_channel(msg.channel_id.0)
|
||||
.is_some()
|
||||
{
|
||||
let target_player = global_data.game_state.get_player_by_codename(args.rest());
|
||||
|
||||
if let Some(target_player) = target_player {
|
||||
let vote_channel = guild
|
||||
.channels
|
||||
.get(&ChannelId::from(global_data.cfg.vote_channel))
|
||||
.unwrap();
|
||||
|
||||
let player_data = global_data
|
||||
.game_state
|
||||
.get_player_from_channel_mut(msg.channel_id.0)
|
||||
.unwrap();
|
||||
player_data.vote_target = Some(target_player.channel);
|
||||
|
||||
vote_channel
|
||||
.send_message(&ctx.http, |m| {
|
||||
m.content(format!(
|
||||
"{} has selected {} for termination",
|
||||
&player_data.codename, target_player.codename
|
||||
))
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
msg.reply(&ctx.http, "Subject not found!").await.unwrap();
|
||||
}
|
||||
} else {
|
||||
msg.reply(
|
||||
&ctx.http,
|
||||
"This command needs to be run in a game channel, goober",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
save_game_state(&global_data).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
async fn status(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let global_data = data.get::<GlobalData>().unwrap();
|
||||
|
||||
let global_data = global_data.lock().await;
|
||||
|
||||
let mut msg_builder = MessageBuilder::new();
|
||||
|
||||
msg_builder.push(print_game_status(&global_data.game_state));
|
||||
|
||||
if global_data.game_state.current_phase == Phase::Day {
|
||||
msg_builder.push_line("");
|
||||
|
||||
let vote_tallies = global_data.game_state.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.get_vote_tallies() {
|
||||
msg_builder.push_line(format!("{}: {}", player, tally));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg.reply(&ctx.http, msg_builder.build()).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn command_framework() -> StandardFramework {
|
||||
StandardFramework::new()
|
||||
.configure(|c| c.prefix("!"))
|
||||
.group(&HOST_GROUP)
|
||||
.group(&PLAYER_GROUP)
|
||||
}
|
||||
|
|
74
src/data.rs
74
src/data.rs
|
@ -1,6 +1,8 @@
|
|||
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;
|
||||
|
@ -18,6 +20,7 @@ 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<String>,
|
||||
|
@ -38,7 +41,7 @@ impl BotConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
||||
pub enum Phase {
|
||||
Day,
|
||||
Night,
|
||||
|
@ -50,16 +53,30 @@ impl Default for Phase {
|
|||
}
|
||||
}
|
||||
|
||||
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<u64>,
|
||||
}
|
||||
|
||||
#[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<PlayerData>,
|
||||
}
|
||||
|
||||
|
@ -73,11 +90,66 @@ impl GameState {
|
|||
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_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<PlayerData> {
|
||||
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!("<t:{}:f>", self.phase_end_time)
|
||||
}
|
||||
|
||||
pub fn get_phase_countdown(&self) -> String {
|
||||
format!("<t:{}:R>", 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<String, u32> {
|
||||
let mut vote_set: HashMap<String, u32> = HashMap::new();
|
||||
|
||||
for player in &self.player_data {
|
||||
if let Some(vote_target) = player.vote_target {
|
||||
let target = self.get_player_from_channel(vote_target);
|
||||
if let Some(target) = target {
|
||||
*vote_set.entry(target.codename.clone()).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vote_set
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
|
|
112
src/helper.rs
112
src/helper.rs
|
@ -1,10 +1,19 @@
|
|||
use crate::data::{GlobalData, MessageSource};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
|
||||
use rand::Rng;
|
||||
use serenity::framework::standard::CommandResult;
|
||||
use serenity::model::channel::{PermissionOverwrite, PermissionOverwriteType};
|
||||
use serenity::model::guild::Member;
|
||||
use serenity::model::id::UserId;
|
||||
use serenity::model::prelude::ChannelId;
|
||||
use serenity::model::prelude::Guild;
|
||||
use serenity::model::Permissions;
|
||||
use serenity::prelude::Context;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use serenity::utils::MessageBuilder;
|
||||
|
||||
use crate::data::{BotConfig, GameState, GlobalData, MessageSource, PlayerData};
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
pub async fn send_msg_to_player_channels(
|
||||
ctx: &Context,
|
||||
|
@ -97,3 +106,100 @@ pub fn clear_game_state(global_data: &mut GlobalData) -> std::io::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_codename(config: &BotConfig) -> String {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let occupation = &config.occupation[rng.gen_range(0..config.occupation.len())];
|
||||
let adj = &config.adjective[rng.gen_range(0..config.adjective.len())];
|
||||
|
||||
format!("{} {}", adj, occupation)
|
||||
}
|
||||
|
||||
pub async fn add_user_to_game(
|
||||
ctx: &Context,
|
||||
guild: &Guild,
|
||||
global_data: &mut GlobalData,
|
||||
discord_user: &Member,
|
||||
) -> CommandResult {
|
||||
let mut codename = generate_codename(&global_data.cfg);
|
||||
|
||||
while global_data.game_state.codename_exists(&codename) {
|
||||
codename = generate_codename(&global_data.cfg);
|
||||
}
|
||||
|
||||
let channel = guild
|
||||
.create_channel(&ctx.http, |c| {
|
||||
c.category(&ChannelId::from(global_data.cfg.category))
|
||||
.name(format!("{}'s Channel", discord_user.display_name()))
|
||||
})
|
||||
.await?;
|
||||
|
||||
let allow = Permissions::SEND_MESSAGES
|
||||
| Permissions::READ_MESSAGE_HISTORY
|
||||
| Permissions::READ_MESSAGE_HISTORY;
|
||||
|
||||
let overwrite = PermissionOverwrite {
|
||||
allow,
|
||||
deny: Default::default(),
|
||||
kind: PermissionOverwriteType::Member(discord_user.user.id),
|
||||
};
|
||||
|
||||
channel.create_permission(&ctx.http, &overwrite).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("SUBJECT CODENAME: ")
|
||||
.push_line(&codename)
|
||||
.push(print_game_status(&global_data.game_state))
|
||||
)
|
||||
}).await?;
|
||||
|
||||
channel.pin(&ctx.http, msg.id).await?;
|
||||
|
||||
let player_data = PlayerData {
|
||||
channel: channel.id.0,
|
||||
discord_id: discord_user.user.id.0,
|
||||
vote_target: None,
|
||||
codename,
|
||||
};
|
||||
|
||||
global_data.game_state.player_data.push(player_data);
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn print_game_status(game_state: &GameState) -> String {
|
||||
MessageBuilder::new()
|
||||
.push_line(format!(
|
||||
"CURRENT EXPERIMENT PHASE: {} {}",
|
||||
game_state.current_phase, game_state.phase_number
|
||||
))
|
||||
.push_line(format!(
|
||||
"PHASE END TIME: {}",
|
||||
game_state.get_phase_end_time()
|
||||
))
|
||||
.push_line(format!("PHASE ENDING {}", game_state.get_phase_countdown()))
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn get_phase_end_timestamp(hours: u64) -> u64 {
|
||||
let end_time = std::time::SystemTime::now() + std::time::Duration::from_secs(hours * 60 * 60);
|
||||
end_time.duration_since(UNIX_EPOCH).unwrap().as_secs()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue