diff --git a/src/discord/emergency.rs b/src/discord/emergency.rs new file mode 100644 index 0000000..25d5864 --- /dev/null +++ b/src/discord/emergency.rs @@ -0,0 +1,202 @@ +use crate::discord::Context; +use crate::discord::is_not_cancelled; +use crate::error::Error; +use crate::inventory::ItemType; +use crate::models::lil_fren::LilFren; +use crate::user::User; +use log::info; +use poise::serenity_prelude::{Mentionable, UserId, parse_user_mention}; +use rand::distr::StandardUniform; +use rand::prelude::Distribution; +use rand::{Rng, RngExt}; +use std::cmp::PartialEq; +use std::collections::HashSet; +use std::time::Duration; + +#[derive(Debug, PartialEq, Eq)] +pub enum Outcomes { + EveryoneDies, + DogDies, + AllInvolvedDie, + CallerDies, + SuspectDies, + CopsWriteCallerATicket, + CopsWriteSuspectATicket, + CopsNeverShowUp, +} + +impl Outcomes { + pub fn msg(&self, caller: &str, suspects: &[String]) -> String { + let suspect_str = suspects.join(","); + + match self { + Outcomes::EveryoneDies => { + "ON YOUR KNEES, ON THE GROUND, THEY'RE ALL REACHING!!!! *gunshot noises* Thank god we did that ICE training, that could have been really bad.".to_string() + } + Outcomes::DogDies => { + "OH FUCK IS THAT A DOG IN A CLIPPERS JERSY, SHOOT IT *gunshot noises* (you might want to check lil buddy)".to_string() + } + Outcomes::AllInvolvedDie => { + "*gunshot noises* Hey... umm... this wasn't the active shooter call. We kinda just killed everyone... oh well".to_string() + } + Outcomes::CallerDies => { + format!("*gunshot noises* Damn {caller} was a bitch, glad they are shut up thanks to my trust government furnished killing machine; a gun!") + } + Outcomes::SuspectDies => { + format!("SUSPECT IS FLEEING THE SCENE. {suspect_str} is heading towards #uwu-cuite-zone. THINK OF THE KIDS. *gunshot noises*") + } + Outcomes::CopsWriteCallerATicket => { + format!("After a long invesigation, I have to give you {caller} a ticket. Really sorry, but I'm under my quota for your race group... I MEAN yeah please only water your grass on Tuesdays and Wednesdays") + } + Outcomes::CopsWriteSuspectATicket => { + format!("{suspect_str}, for disturbing the peace you are hereby CHARGED with TOMFOOLERY. With A HEFTY fine.") + } + Outcomes::CopsNeverShowUp => { + "".to_string() + } + } + } +} + +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> Outcomes { + match rng.random_range(0..100) { + 0 => Outcomes::EveryoneDies, + 1 => Outcomes::DogDies, + 2..=10 => Outcomes::AllInvolvedDie, + 11..=30 => Outcomes::CallerDies, + 31..=50 => Outcomes::SuspectDies, + 51..=70 => Outcomes::CopsWriteCallerATicket, + 71..=90 => Outcomes::CopsWriteSuspectATicket, + _ => Outcomes::CopsNeverShowUp, + } + } +} + +#[poise::command( + prefix_command, + guild_only, + aliases("911"), + category = "emergency", + check = "is_not_cancelled", + user_cooldown = 3600 +)] +pub async fn call_911(ctx: Context<'_>, #[rest] msg: String) -> Result<(), Error> { + ctx.reply("Thank you for reporting your emergency Fren PD is on the case!") + .await?; + + let mut involved_parties = HashSet::new(); + + let role_regex = regex::Regex::new("<@.*>").unwrap(); + + for mention in role_regex.find_iter(&msg) { + if let Some(user_id) = parse_user_mention(mention.as_str()) { + involved_parties.insert(user_id); + } + } + + let involved_parties: Vec = involved_parties.iter().cloned().collect(); + + let outcome: Outcomes = rand::rng().random(); + + let response_time = match rand::random_range(0..100) { + 0..=60 => rand::random_range(1..20), + 61..=90 => rand::random_range(20..60 * 2), + _ => rand::random_range(60 * 2..60 * 48), + }; + + if outcome == Outcomes::CopsNeverShowUp { + info!("Nope, they never coming"); + return Ok(()); + } + + info!("Sleeping for {response_time} minutes until the cops show up."); + tokio::time::sleep(Duration::from_mins(response_time)).await; + + ctx.reply("🚨 FREN PD IS HERE! NO ONE MOVE! 🚨").await?; + + let involved_party_names: Vec = involved_parties + .iter() + .map(|m| m.mention().to_string()) + .collect(); + ctx.reply(outcome.msg(&ctx.author().mention().to_string(), &involved_party_names)) + .await?; + match outcome { + Outcomes::EveryoneDies => { + let members: Vec = ctx.guild().unwrap().members.keys().copied().collect(); + + for member in members { + User::use_item_on_user( + ctx, + member, + ctx.guild_id().unwrap(), + ItemType::KillGun, + &None, + ) + .await?; + } + } + Outcomes::DogDies => { + let lil_buddy = LilFren::get_lil_fren(&ctx.data().db)?; + if let Some(mut lil_buddy) = lil_buddy { + lil_buddy.hunger = -500.0; + lil_buddy.thirst = -500.0; + + ctx.data().db.insert(lil_buddy)?; + } + } + Outcomes::AllInvolvedDie => { + User::use_item_on_user( + ctx, + ctx.author().id, + ctx.guild_id().unwrap(), + ItemType::KillGun, + &None, + ) + .await?; + for suspect in involved_parties { + User::use_item_on_user( + ctx, + suspect, + ctx.guild_id().unwrap(), + ItemType::KillGun, + &None, + ) + .await?; + } + } + Outcomes::CallerDies => { + User::use_item_on_user( + ctx, + ctx.author().id, + ctx.guild_id().unwrap(), + ItemType::KillGun, + &None, + ) + .await?; + } + Outcomes::SuspectDies => { + for suspect in involved_parties { + User::use_item_on_user( + ctx, + suspect, + ctx.guild_id().unwrap(), + ItemType::KillGun, + &None, + ) + .await?; + } + } + Outcomes::CopsWriteCallerATicket => { + User::take_funds(&ctx.data().db, ctx.author().id, 1000)?; + } + Outcomes::CopsWriteSuspectATicket => { + for suspect in involved_parties { + User::take_funds(&ctx.data().db, suspect, 1000)?; + } + } + Outcomes::CopsNeverShowUp => {} + } + + Ok(()) +} diff --git a/src/discord/mod.rs b/src/discord/mod.rs index 0694ed5..64dcec0 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -5,6 +5,7 @@ mod album; mod birthday; mod celeryman; mod color; +mod emergency; mod emoji_race; mod fren_coin; mod image; @@ -383,6 +384,7 @@ pub async fn run_bot(global_data: GlobalData) { celeryman::tayne(), color::set_color(), color::remove_color(), + emergency::call_911(), emoji_race::bet(), emoji_race::race(), emoji_race::start_race(), diff --git a/src/models/task.rs b/src/models/task.rs index f1bd14e..4b5a162 100644 --- a/src/models/task.rs +++ b/src/models/task.rs @@ -9,12 +9,13 @@ use chrono::{Days, Duration, TimeDelta, TimeZone, Timelike, Utc}; use j_db::database::Database; use j_db::model::JdbModel; use log::{error, info}; +use poise::serenity_prelude::ChannelId; use poise::serenity_prelude::all::Mentionable; use serde::{Deserialize, Serialize}; use std::ops::Add; use std::sync::Arc; -#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub enum TaskType { RemoveRole { user_id: u64, role_id: u64 }, CheckBirthdays, @@ -22,6 +23,7 @@ pub enum TaskType { RestockShop, UpdateLilBuddy, UpdateGogurtRate, + Say { msg: String, channel: ChannelId }, } impl TaskType { @@ -80,7 +82,7 @@ impl Task { db.insert::(Task { id: None, - task_type, + task_type: task_type.clone(), time, })?; @@ -207,6 +209,9 @@ impl Task { GogurtReserves::get_next_market_update_time(&data.db)?, )?; } + TaskType::Say { ref msg, channel } => { + channel.say(&ctx.http, msg).await?; + } } let _ = data.db.remove::(task.id().unwrap()).is_ok();