FrenBot/src/discord/shop.rs

298 lines
8.9 KiB
Rust

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::<ItemType>() {
Ok(i) => i,
Err(_) => {
ctx.reply("I don't know what the heck that is tbh.").await?;
return Ok(());
}
};
let target_users: Vec<UserId> = 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<GlobalData>,
) -> 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::<User>(bot_user)?;
Ok(())
}