use crate::config::GlobalData; use crate::discord::motivate::create_motivation_image; use crate::discord::{Context, get_role}; use crate::error::Error; use crate::inventory::{InventoryError, ItemData, ItemType, Operation, nft_value}; use crate::models::motivation::MotivationConfig; use crate::models::task::{Task, TaskType}; use crate::user::{User, UserError}; use poise::serenity_prelude::all::{ CreateAttachment, CreateMessage, EditRole, GuildId, Member, parse_user_mention, }; use poise::serenity_prelude::model::Colour; use poise::serenity_prelude::prelude::Mentionable; use poise::serenity_prelude::utils::MessageBuilder; use rand::prelude::IndexedRandom; use rand::{Rng, rng}; use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; use std::sync::Arc; use tokio::io::AsyncWriteExt; /// Shop at a real virtual shop in the Metaverse! #[poise::command(prefix_command, category = "Shop")] pub async fn shop(ctx: Context<'_>) -> Result<(), Error> { let bot_user = User::get_user(&ctx.data().db, ctx.cache().current_user().id)?; if bot_user.inventory.inventory.is_empty() { ctx.reply("Sorry shop is closed until we get more wares.") .await?; } else { let mut inv_msg = MessageBuilder::new(); inv_msg.push_bold_line("Fren has wares if you have coin:"); inv_msg.push_safe(bot_user.inventory.list_items(true)); ctx.reply(inv_msg.build()).await?; } Ok(()) } /// Look at your inventory, this button is forced bound to any key other than "B" #[poise::command(prefix_command, category = "Shop")] pub async fn inventory(ctx: Context<'_>) -> Result<(), Error> { let mut inv_msg = MessageBuilder::new(); let user = User::get_user(&ctx.data().db, ctx.author().id)?; if user.inventory.inventory.is_empty() { ctx.reply("Sorry your inventory is empty.").await?; } else { inv_msg.push_bold_line("Your inventory: "); inv_msg.push_safe(user.inventory.list_items(false)); ctx.reply(inv_msg.build()).await?; } Ok(()) } /// Buy an item from the shop with your hard earned cash #[poise::command(prefix_command, category = "Shop")] pub async fn buy( ctx: Context<'_>, #[description = "Item to buy"] #[rest] item: ItemType, ) -> Result<(), Error> { let res = User::item_transaction( &ctx.data().db, ctx.cache().current_user().id, ctx.author().id, item, Operation::Buy, ); if let Err(err) = res { if let Error::UserError(err) = err { let resp = match err { UserError::NotEnoughFunds => "Sorry Link, but I can't give store credit. Come back when you are mhhmm richer.".to_string(), UserError::InvalidTarget => "I don't know who you are or how you got here.".to_string(), UserError::InventoryError(err) => format!("Something went wrong with that item: {}", err) }; ctx.reply(resp).await?; } else { return Err(err); } } else { ctx.reply(format!("Congrats, you now own a '{}'", item)) .await?; } Ok(()) } async fn blockable_item( ctx: Context<'_>, guild_id: GuildId, msg_content: &str, role_name: &str, weapon_name: &str, block_item: ItemType, ) -> Result<(bool, Member), Error> { let split = msg_content.split(weapon_name); let target = parse_user_mention(split.last().unwrap()).unwrap(); let target_member = match guild_id.member(ctx, target).await { Ok(member) => member, Err(_) => { return Err(Error::CommandError( "I have no clue who that is tbh".to_string(), )); } }; if target_member.user.id == ctx.cache().current_user().id { return Err(Error::CommandError( "You can not harm me in a way that matters.".to_string(), )); } if User::try_use_item(&ctx.data().db, target, block_item, true).is_ok() { Ok((false, target_member)) } else { let role = if let Some(role) = get_role(ctx.http(), guild_id, role_name).await? { role } else { guild_id .create_role( &ctx, EditRole::new() .name(role_name) .colour(Colour::from_rgb(1, 1, 1)), ) .await? .id }; target_member.add_role(&ctx, role).await?; Task::add_task( &ctx.data().db, TaskType::RemoveRole { role_id: role.get(), user_id: target_member.user.id.get(), }, chrono::Utc::now() + chrono::Duration::seconds(ctx.data().cfg.effect_role_duration), )?; Ok((true, target_member)) } } /// Use an item and hope it doesn't use you #[poise::command(prefix_command, category = "Shop", guild_only, aliases("use"))] pub async fn use_item( ctx: Context<'_>, #[description = "item to use"] #[rest] item: String, ) -> Result<(), Error> { if item.is_empty() { ctx.reply("You need to select one item to use.").await?; return Ok(()); } let msg_content = item.to_lowercase(); let item = match msg_content.parse::() { Ok(i) => i, Err(_) => { ctx.reply("I don't know what the heck that is tbh.").await?; return Ok(()); } }; let item_data = match User::try_use_item(&ctx.data().db, ctx.author().id, item, false) { Ok(i) => i, Err(err) => { if let Error::UserError(UserError::InventoryError(InventoryError::NotEnoughItems)) = err { ctx.reply("Looks like you don't have enough of that item to use it") .await?; } return Ok(()); } }; match item { ItemType::CancelInsurance => { ctx.reply("You are immune to the next (1) cancelings.") .await?; } ItemType::TheConceptOfLove => { ctx.reply("I have DMed you the concept of love.").await?; ctx.author() .id .create_dm_channel(ctx) .await? .say( ctx, "DO NOT SHARE\nhttps://www.youtube.com/watch?v=HNy_retSME0", ) .await?; } ItemType::GoodFortune => { let good_fortunes = [ "Yes.", "OF COURSE.", "Carolyn, I'm sorry. So yes :)", "That sounds great!", "Yes, I am happy for you!", "||YES||", ]; let fortune = good_fortunes.choose(&mut rng()).unwrap(); ctx.reply(fortune.to_string()).await?; } ItemType::Nft => { if let Some(ItemData::Nft(path)) = item_data { let file: tokio::fs::File = match tokio::fs::File::open(&path).await { Ok(f) => f, Err(_) => { ctx.reply("Sorry this was a pump and dump").await?; return Ok(()); } }; let value = nft_value(&path); ctx.channel_id() .send_message( ctx, CreateMessage::new() .content(format!( "Your NFT my good friend. It's worth **{} FC**!", value )) .add_file( CreateAttachment::file(&file, "nft.jpg".to_string()) .await .unwrap(), ), ) .await?; } else { ctx.reply("Fren NFTs were never my creation, I merely promoted the brand") .await?; } } ItemType::LicenseToBeHorny => { ctx.reply("https://media.discordapp.net/attachments/840015650286075945/1127022083919069184/Img_2022_10_21_05_08_12.jpg").await?; } ItemType::KillGun => { let (outcome, target) = match blockable_item( ctx, ctx.guild_id().unwrap(), &msg_content, "Dead", "kill gun ", ItemType::Helmet, ) .await { Ok(ret) => ret, Err(err) => { ctx.reply(err.to_string()).await?; return Ok(()); } }; if outcome { ctx.reply(format!("You draw your trusty kill gun and shoot one kill bullet. It hits it mark between {}'s eyes, killing them instantly. They are now dead.", target.mention())).await?; } else { ctx.reply(format!("The kill bullet shoots at kill velocity toward {}! They smirk, and simply pull out their Helmet and put it on, the bullet bounces off and falls to the floor. The crowd gasps (like they do in my animes). \"No death today pal\"", target.mention())).await?; } } ItemType::CancelRay => { let (outcome, target) = match blockable_item( ctx, ctx.guild_id().unwrap(), &msg_content, "Cancelled", "cancel ray ", ItemType::CancelInsurance, ) .await { Ok(ret) => ret, Err(err) => { ctx.reply(err.to_string()).await?; return Ok(()); } }; if outcome { ctx.reply( format!("You shoot the cancel ray at {}. As the ray impacts them, you can hear their phone buzz. They have been cancelled, you hear the liberal media in the distance.", target.mention())).await?; } else { ctx.reply(format!("The ray nearly hits {}, but they are surrounded in a a shimmering blue energy shield. \"The liberal media won't strike this time!\"", target.mention())).await?; } } ItemType::Helmet => { ctx.reply("Your trusty helmet regards you helmetly").await?; } ItemType::TacticalNuke => { let mut users: Vec = ctx.data().db.filter(|_, _user: &User| true)?.collect(); for user in &mut users { for item in &mut user.inventory.inventory { if item.item_type == ItemType::KillGun || item.item_type == ItemType::CancelRay || item.item_type == ItemType::CancelInsurance || item.item_type == ItemType::Helmet { item.quantity = 0; } } ctx.data().db.insert(user.clone())?; } ctx.reply("I have become cancel, the canceller of worlds. https://tenor.com/view/explosion-mushroom-cloud-atomic-bomb-bomb-boom-gif-4464831").await?; } } Ok(()) } /// Sell an item for profit #[poise::command(prefix_command, category = "Shop", aliases("sell"))] pub async fn sell_item( ctx: Context<'_>, #[description = "Item to sell"] #[rest] item: ItemType, ) -> Result<(), Error> { let bot_user = ctx.cache().current_user().id; match User::item_transaction( &ctx.data().db, ctx.author().id, bot_user, item, Operation::Sell, ) { Ok(_) => { ctx.reply("I guess I could buy that...").await?; } Err(err) => { if let Error::UserError(err) = err { match err { UserError::NotEnoughFunds => { ctx.reply("What do I look like a charity service?").await?; } UserError::InvalidTarget => { ctx.reply("If you got here, that means I'm not real...????") .await?; } UserError::InventoryError(InventoryError::NotEnoughItems) => { ctx.reply("You don't have enough of that item to sell it lmao") .await?; } _ => {} } } else { return Err(err); } } } Ok(()) } /// Get help on what an item does #[poise::command(prefix_command, category = "Shop")] pub async fn item_help( ctx: Context<'_>, #[description = "Item to get help on"] #[rest] item: ItemType, ) -> Result<(), Error> { ctx.reply(format!("**{}**: {}", item, item.description())) .await?; Ok(()) } pub async fn restock_shop( ctx: &poise::serenity_prelude::Context, global_data: &Arc, ) -> Result<(), Error> { let mut bot_user = User::get_user(&global_data.db, ctx.cache.current_user().id)?; bot_user.inventory.inventory.clear(); bot_user .inventory .give_item(ItemType::TheConceptOfLove, rng().random_range(0..=5), None); bot_user .inventory .give_item(ItemType::GoodFortune, rng().random_range(0..10), None); bot_user .inventory .give_item(ItemType::CancelInsurance, rng().random_range(1..=3), None); bot_user .inventory .give_item(ItemType::LicenseToBeHorny, rng().random_range(0..=25), None); bot_user .inventory .give_item(ItemType::KillGun, rng().random_range(1..=3), None); bot_user .inventory .give_item(ItemType::CancelRay, rng().random_range(1..=3), None); bot_user .inventory .give_item(ItemType::Helmet, rng().random_range(1..=3), None); bot_user .inventory .give_item(ItemType::TacticalNuke, rng().random_range(0..=1), None); loop { let mut dir = tokio::fs::read_dir(&global_data.cfg.nft_path).await?; let mut count = 0; while let Ok(Some(_)) = dir.next_entry().await { count += 1; } if count > 64 { tokio::fs::remove_dir_all(&global_data.cfg.nft_path).await?; tokio::fs::create_dir(&global_data.cfg.nft_path).await? } let nft_motivation = MotivationConfig::generate_motivation( &global_data.db, &global_data.picox, "gold", None, ) .await?; let nft = create_motivation_image(nft_motivation).await?; let mut hasher = DefaultHasher::new(); hasher.write(&nft); let nft_hash = hasher.finish(); let path = global_data.cfg.nft_path.join(format!("{}.jpg", nft_hash)); if path.exists() { continue; } let mut file = tokio::fs::File::create(path.clone()).await?; file.write_all(&nft).await?; bot_user.inventory.give_item( ItemType::Nft, 1, Some(ItemData::Nft(path.to_str().unwrap().to_string())), ); break; } global_data.db.insert::(bot_user)?; Ok(()) }