From c6786a77f7141a24f639779eb89b23f44efcb957 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Sat, 9 Aug 2025 17:46:19 -0600 Subject: [PATCH] Add consequences for being dead or canceled --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/discord/celeryman.rs | 3 +- src/discord/emoji_race.rs | 15 +++++++-- src/discord/fren_coin.rs | 9 ++++-- src/discord/little_fren.rs | 56 ++++++++++++++++++++++++++-------- src/discord/mod.rs | 57 ++++++++++++++++++++++++++++++----- src/discord/shop.rs | 27 +++++++++-------- src/discord/stonks.rs | 16 ++++++++-- src/models/gogurt_reserves.rs | 6 ++-- src/models/task.rs | 10 ++++-- 11 files changed, 154 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d83adc9..922e71b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1093,7 +1093,7 @@ dependencies = [ [[package]] name = "fren" -version = "2.3.0" +version = "2.4.0" dependencies = [ "axum 0.8.1", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index e5e0366..7c69e25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fren" -version = "2.3.0" +version = "2.4.0" edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/discord/celeryman.rs b/src/discord/celeryman.rs index bd453c1..ac885f3 100644 --- a/src/discord/celeryman.rs +++ b/src/discord/celeryman.rs @@ -1,8 +1,9 @@ use crate::discord::Context; +use crate::discord::is_not_cancelled; use crate::error::Error; /// Are you sure? -#[poise::command(prefix_command, category = "Celery Man")] +#[poise::command(prefix_command, category = "Celery Man", check = "is_not_cancelled")] pub async fn nudetayne(ctx: Context<'_>) -> Result<(), Error> { if ctx.invoked_command_name() == "nudetayne" { ctx.reply("Not computing, please repeat.").await?; diff --git a/src/discord/emoji_race.rs b/src/discord/emoji_race.rs index cd784ce..f46226f 100644 --- a/src/discord/emoji_race.rs +++ b/src/discord/emoji_race.rs @@ -1,4 +1,5 @@ use crate::discord::Context; +use crate::discord::is_not_cancelled; use crate::error::Error; use crate::models::race; use crate::models::race::{Bet, NUMBER_OF_RACERS, RACE_SIZE, RaceError, Racer}; @@ -40,7 +41,12 @@ async fn add_bet(ctx: Context<'_>, bet: Bet, racers: &[Racer]) -> Result<(), Rac } /// Setup an emoji race to bet on -#[poise::command(prefix_command, guild_only, category = "Race")] +#[poise::command( + prefix_command, + guild_only, + category = "Race", + check = "is_not_cancelled" +)] pub async fn race(ctx: Context<'_>) -> Result<(), Error> { cleanup_race(&ctx.data().db)?; @@ -154,7 +160,12 @@ pub async fn start_race(ctx: Context<'_>) -> Result<(), Error> { } /// Bet on a race of emojis, a sound idea -#[poise::command(prefix_command, guild_only, category = "Race")] +#[poise::command( + prefix_command, + guild_only, + category = "Race", + check = "is_not_cancelled" +)] pub async fn bet( ctx: Context<'_>, #[description = "Racer to bet on"] racer: EmojiIdentifier, diff --git a/src/discord/fren_coin.rs b/src/discord/fren_coin.rs index 26f068d..fdb6591 100644 --- a/src/discord/fren_coin.rs +++ b/src/discord/fren_coin.rs @@ -1,4 +1,4 @@ -use crate::discord::Context; +use crate::discord::{Context, is_alive}; use crate::error::Error; use crate::user::{User, UserError}; use j_db::database::Database; @@ -28,7 +28,12 @@ pub async fn balance( } /// Gift someone the gift that keeps on giving, Fren Coins! -#[poise::command(prefix_command, category = "Fren Coin", aliases("audit"))] +#[poise::command( + prefix_command, + category = "Fren Coin", + aliases("audit"), + check = "is_alive" +)] pub async fn gift( ctx: Context<'_>, #[description = "User to gift coin to"] target: poise::serenity_prelude::User, diff --git a/src/discord/little_fren.rs b/src/discord/little_fren.rs index ade4e86..e042591 100644 --- a/src/discord/little_fren.rs +++ b/src/discord/little_fren.rs @@ -1,4 +1,4 @@ -use crate::discord::Context; +use crate::discord::{Context, is_not_cancelled}; use crate::error::Error; use crate::models::lil_fren; use crate::models::lil_fren::{AliveState, LilFren, LilFrenState}; @@ -8,17 +8,22 @@ use rand::prelude::IndexedRandom; use rand::{Rng, rng}; /// Adopt a new little buddy! -#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +#[poise::command( + prefix_command, + guild_only, + category = "Lil Buddy", + check = "is_not_cancelled" +)] pub async fn adopt( ctx: Context<'_>, #[description = "Lil buddy to adopt"] lil_buddy: EmojiIdentifier, ) -> Result<(), Error> { - if let Some(lil_fren) = LilFren::get_lil_fren(&ctx.data().db)? { - if lil_fren.is_alive() == AliveState::Alive { - ctx.reply("Your buddy is still alive, please take care of him :)") - .await?; - return Ok(()); - } + if let Some(lil_fren) = LilFren::get_lil_fren(&ctx.data().db)? + && lil_fren.is_alive() == AliveState::Alive + { + ctx.reply("Your buddy is still alive, please take care of him :)") + .await?; + return Ok(()); } LilFren::create_new_lil_fren(&ctx.data().db, lil_buddy.id)?; @@ -36,7 +41,12 @@ pub async fn adopt( } /// Checkup on your little buddy! -#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +#[poise::command( + prefix_command, + guild_only, + category = "Lil Buddy", + check = "is_not_cancelled" +)] pub async fn checkup(ctx: Context<'_>) -> Result<(), Error> { let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; @@ -89,7 +99,12 @@ pub async fn checkup(ctx: Context<'_>) -> Result<(), Error> { } /// Feed your little buddy something totally normal! -#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +#[poise::command( + prefix_command, + guild_only, + category = "Lil Buddy", + check = "is_not_cancelled" +)] pub async fn feed( ctx: Context<'_>, #[description = "Item to feed lil buddy"] food: String, @@ -125,7 +140,12 @@ pub async fn feed( } /// Give your little buddy the juice of life. -#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +#[poise::command( + prefix_command, + guild_only, + category = "Lil Buddy", + check = "is_not_cancelled" +)] pub async fn give_water(ctx: Context<'_>) -> Result<(), Error> { let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; @@ -158,7 +178,12 @@ pub async fn give_water(ctx: Context<'_>) -> Result<(), Error> { } /// Play with little buddy before he shreds up the remote again -#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +#[poise::command( + prefix_command, + guild_only, + category = "Lil Buddy", + check = "is_not_cancelled" +)] pub async fn play(ctx: Context<'_>) -> Result<(), Error> { let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; @@ -185,7 +210,12 @@ pub async fn play(ctx: Context<'_>) -> Result<(), Error> { } /// Give your little buddy medicine -#[poise::command(prefix_command, guild_only, category = "Lil Buddy")] +#[poise::command( + prefix_command, + guild_only, + category = "Lil Buddy", + check = "is_not_cancelled" +)] pub async fn give_medicine(ctx: Context<'_>) -> Result<(), Error> { let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?; diff --git a/src/discord/mod.rs b/src/discord/mod.rs index be646e3..f0dcade 100644 --- a/src/discord/mod.rs +++ b/src/discord/mod.rs @@ -1,3 +1,4 @@ +use rand::seq::IteratorRandom; mod admin; mod album; mod birthday; @@ -22,11 +23,10 @@ use crate::error::Error; use crate::event_listener::{Listener, TriggerEvent, TriggerType}; use crate::models::social_credit::SocialCreditPhrase; use crate::models::task::Task; -use crate::user::User; +use crate::user::{User, UserRole}; use log::{debug, error, info}; use poise::serenity_prelude::{GuildId, Http, Message, MessageBuilder, ReactionType, RoleId}; use poise::{FrameworkOptions, find_command, serenity_prelude as serenity}; -use rand::prelude::IteratorRandom; use rand::{Rng, rng}; use songbird::SerenityInit; use std::sync::Arc; @@ -83,11 +83,11 @@ async fn handle_message( || new_message.content.eq_ignore_ascii_case("mhmm") { let mut bot_state = data.bot_state.lock().await; - if let Some(u) = bot_state.accepted_nsfw { - if new_message.author.id == u { - new_message.reply(&ctx.http, "||https://cdn.discordapp.com/attachments/614891432079130625/1041545254362423368/unknown.png||").await.unwrap(); - bot_state.accepted_nsfw = None; - } + if let Some(u) = bot_state.accepted_nsfw + && new_message.author.id == u + { + new_message.reply(&ctx.http, "||https://cdn.discordapp.com/attachments/614891432079130625/1041545254362423368/unknown.png||").await.unwrap(); + bot_state.accepted_nsfw = None; } } @@ -445,3 +445,46 @@ pub async fn get_role( .map(|(role_id, _)| role_id) .copied()) } + +async fn check_has_user_role(ctx: Context<'_>, user_role: UserRole) -> Result { + let role = get_role(ctx.http(), ctx.data().cfg.guild_id, &user_role.to_string()).await?; + + if let Some(role) = role { + Ok(ctx + .author() + .has_role(&ctx, ctx.data().cfg.guild_id, role) + .await?) + } else { + Ok(false) + } +} + +pub async fn is_alive(ctx: Context<'_>) -> Result { + let is_ghoul = check_has_user_role(ctx, UserRole::Ghoul).await?; + if check_has_user_role(ctx, UserRole::Dead).await? && !is_ghoul { + ctx.reply("Sorry there ghosty looking fella, you appear to be unalived. I only give service to those who are alive.").await?; + Ok(false) + } else { + if is_ghoul && rng().random_bool(0.50) { + let ghoul_responses = [ + "Oh god, what's wrong with your face? Looks like someone took it through a blender and lit it on fire", + "You kiss your mother with that mouth?", + "Oh! I love your cosplay, chic fallout is so IN right now frfr", + "Have you tried moisturizer? Seems like it couldn't umm... hurt...", + "OH damn, can I get a warning next time before you jump scare me with that", + ]; + let resp = ghoul_responses.iter().choose(&mut rng()).unwrap(); + ctx.reply(*resp).await?; + } + Ok(true) + } +} + +pub async fn is_not_cancelled(ctx: Context<'_>) -> Result { + if check_has_user_role(ctx, UserRole::Cancelled).await? { + ctx.reply("You're CANCELLED?? Yikes! I can't interact with you, I don't want to become a conservative influencer.").await?; + Ok(false) + } else { + Ok(true) + } +} diff --git a/src/discord/shop.rs b/src/discord/shop.rs index 6b9f144..2cea98f 100644 --- a/src/discord/shop.rs +++ b/src/discord/shop.rs @@ -1,5 +1,6 @@ use crate::config::GlobalData; use crate::discord::Context; +use crate::discord::is_alive; use crate::error::Error; use crate::event_listener::{Action, Expiration, Listener, TriggerType}; use crate::image_manipulation::create_motivation_image; @@ -15,7 +16,7 @@ use std::sync::Arc; use tokio::io::AsyncWriteExt; /// Shop at a real virtual shop in the Metaverse! -#[poise::command(prefix_command, category = "Shop")] +#[poise::command(prefix_command, category = "Shop", check = "is_alive")] pub async fn shop(ctx: Context<'_>) -> Result<(), Error> { let bot_user = User::get_user(&ctx.data().db, ctx.cache().current_user().id)?; @@ -56,7 +57,7 @@ pub async fn inventory(ctx: Context<'_>) -> Result<(), Error> { } /// Buy an item from the shop with your hard-earned cash -#[poise::command(prefix_command, category = "Shop")] +#[poise::command(prefix_command, category = "Shop", check = "is_alive")] pub async fn buy( ctx: Context<'_>, #[description = "Item to buy"] @@ -127,7 +128,7 @@ pub async fn license_to_be_horny(ctx: Context<'_>) -> Result<(), Error> { } /// Use a kill gun to kill someone -#[poise::command(prefix_command, category = "Shop", guild_only)] +#[poise::command(prefix_command, category = "Shop", guild_only, check = "is_alive")] pub async fn kill_gun( ctx: Context<'_>, #[description = "target to kill"] target: poise::serenity_prelude::User, @@ -137,7 +138,7 @@ pub async fn kill_gun( } /// Use a cancel ray to cancel someone -#[poise::command(prefix_command, category = "Shop", guild_only)] +#[poise::command(prefix_command, category = "Shop", guild_only, check = "is_alive")] pub async fn cancel_ray( ctx: Context<'_>, #[description = "target to cancel"] target: poise::serenity_prelude::User, @@ -154,14 +155,14 @@ pub async fn helmet(ctx: Context<'_>) -> Result<(), Error> { } /// Deploy a tactical emp -#[poise::command(prefix_command, category = "Shop", guild_only)] +#[poise::command(prefix_command, category = "Shop", guild_only, check = "is_alive")] pub async fn emp(ctx: Context<'_>) -> Result<(), Error> { use_item(ctx, ItemType::EMP, Target::Everyone).await?; Ok(()) } /// Deploy a land mine -#[poise::command(prefix_command, category = "Shop")] +#[poise::command(prefix_command, category = "Shop", check = "is_alive")] pub async fn land_mine( ctx: Context<'_>, #[description = "Channel to land mine"] channel: Channel, @@ -195,7 +196,7 @@ pub async fn land_mine( } /// Use your Phrase Canceler -#[poise::command(prefix_command, category = "Shop")] +#[poise::command(prefix_command, category = "Shop", check = "is_alive")] pub async fn phrase_canceler( ctx: Context<'_>, #[description = "Phrase to cancel"] @@ -231,7 +232,7 @@ pub async fn phrase_canceler( } /// Drop a nuke on this discord -#[poise::command(prefix_command, category = "Shop", guild_only)] +#[poise::command(prefix_command, category = "Shop", guild_only, check = "is_alive")] pub async fn nuke(ctx: Context<'_>) -> Result<(), Error> { if use_item(ctx, ItemType::Nuke, Target::Everyone).await? { Listener::add_listener(&ctx.data().db, { @@ -283,10 +284,10 @@ pub async fn use_item(ctx: Context<'_>, item: ItemType, target: Target) -> Resul User::use_item_on_user(ctx, *target_user, ctx.guild_id().unwrap(), item, &item_data) .await?; - if let Some(outcome) = outcome { - if ndx + 1 == target_users.len() { - ctx.reply(outcome).await?; - } + if let Some(outcome) = outcome + && ndx + 1 == target_users.len() + { + ctx.reply(outcome).await?; } } @@ -294,7 +295,7 @@ pub async fn use_item(ctx: Context<'_>, item: ItemType, target: Target) -> Resul } /// Sell an item for profit -#[poise::command(prefix_command, category = "Shop", aliases("sell"))] +#[poise::command(prefix_command, category = "Shop", aliases("sell"), check = "is_alive")] pub async fn sell_item( ctx: Context<'_>, #[description = "Item to sell"] diff --git a/src/discord/stonks.rs b/src/discord/stonks.rs index aefa973..6994c72 100644 --- a/src/discord/stonks.rs +++ b/src/discord/stonks.rs @@ -1,10 +1,15 @@ -use crate::discord::Context; +use crate::discord::{Context, is_alive}; use crate::error::Error; use crate::models::gogurt_reserves::GogurtReserves; use poise::serenity_prelude::{Mentionable, MessageBuilder}; /// Contribute to the Gogurt Reserve -#[poise::command(prefix_command, category = "Stonks", aliases("cgr"))] +#[poise::command( + prefix_command, + category = "Stonks", + aliases("cgr"), + check = "is_alive" +)] pub async fn contribute_to_gogurt_reserve( ctx: Context<'_>, #[description = "amount of FCs to spend"] contribute_amount: u64, @@ -21,7 +26,12 @@ pub async fn contribute_to_gogurt_reserve( } /// Sell gogurt from the Gogurt Reserve -#[poise::command(prefix_command, category = "Stonks", aliases("sgr"))] +#[poise::command( + prefix_command, + category = "Stonks", + aliases("sgr"), + check = "is_alive" +)] pub async fn sell_from_gogurt_reserve( ctx: Context<'_>, #[description = "number of pounds of gogurt to sell"] gogurt_to_sell: f64, diff --git a/src/models/gogurt_reserves.rs b/src/models/gogurt_reserves.rs index 028f866..61a16d2 100644 --- a/src/models/gogurt_reserves.rs +++ b/src/models/gogurt_reserves.rs @@ -9,16 +9,16 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use thiserror::Error; -const OPENING_HOUR: u32 = 8; +const OPENING_HOUR: u32 = 7; const UPDATE_HOUR: u32 = 12; -const CLOSING_HOUR: u32 = 16; +const CLOSING_HOUR: u32 = 18; #[derive(Debug, Error)] pub enum GogurtError { #[error("Wow, you really don't have enough gogurt. What are you 12?? Just got buy some poor.")] NotEnoughGogurt, #[error( - "Sorry, gogurt can only be bought and sold between the hours of 8AM to 4PM Naperville Time." + "Sorry, gogurt can only be bought and sold between the hours of 7AM to 6PM Naperville Time." )] OutsideOfGogurtTradingHours, } diff --git a/src/models/task.rs b/src/models/task.rs index ae5d918..1d1cf0f 100644 --- a/src/models/task.rs +++ b/src/models/task.rs @@ -209,12 +209,16 @@ impl Task { let midday_update_time = GogurtReserves::market_update_time(); let next_check = if chicago_time.time() >= midday_update_time { - chicago_time.with_time(market_open_time) + chicago_time + .with_time(market_open_time) + .unwrap() + .checked_add_days(Days::new(1)) + .unwrap() } else { - chicago_time.with_time(midday_update_time) + chicago_time.with_time(midday_update_time).unwrap() }; - let next_check = next_check.unwrap().with_timezone(&Utc); + let next_check = next_check.with_timezone(&Utc); Task::add_task(&data.db, TaskType::UpdateGogurtRate, next_check)?; }