use crate::config::GlobalData; use crate::discord::Context; use crate::error::Error; use crate::image_manipulation::create_motivation_image; use crate::inventory::{InventoryError, ItemData, ItemType, Operation, Target}; use crate::user::{User, UserError}; use poise::ChoiceParameter; use poise::serenity_prelude::UserId; use poise::serenity_prelude::all::parse_user_mention; use poise::serenity_prelude::utils::MessageBuilder; 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(()) } /// 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 target_users: Vec = match item.target() { Target::Myself => { vec![ctx.author().id] } Target::User => { let item_name = format!("{} ", item.name().to_ascii_lowercase()); let split = msg_content.split(&item_name); let target = parse_user_mention(split.last().unwrap()).unwrap(); let target_member = match ctx.guild_id().unwrap().member(ctx, target).await { Ok(member) => member, Err(_) => { return Err(Error::CommandError( "I have no clue who that is tbh".to_string(), )); } }; vec![target_member.user.id] } Target::Everyone => ctx.guild().unwrap().members.keys().copied().collect(), }; 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(()); } }; for (ndx, target_user) in target_users.iter().enumerate() { let outcome = 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?; } } } 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::EMP, 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 = create_motivation_image(&global_data.db, &global_data.picox, "gold", None).await?; let mut hasher = DefaultHasher::new(); hasher.write(&nft); let nft_hash = hasher.finish(); let path = global_data.cfg.nft_path.join(format!("{nft_hash}.jpg")); 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(()) }