diff --git a/src/config.rs b/src/config.rs index 5c2e3ed..b729cc7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,6 @@ use crate::error::Error; use crate::imgur; use crate::imgur::Image; -use crate::inventory::InventoryManager; use config::{Config, File}; use j_db::database::Database; use rand::prelude::SliceRandom; @@ -44,8 +43,6 @@ pub struct BotConfig { pub db_path: PathBuf, pub admins: Vec, - pub bot_inventory: InventoryManager, - #[serde(default)] pub albums: Vec, diff --git a/src/discord/emoji_race.rs b/src/discord/emoji_race.rs index 57c6bd4..fbe602f 100644 --- a/src/discord/emoji_race.rs +++ b/src/discord/emoji_race.rs @@ -1,6 +1,6 @@ use crate::config::{Channel, GlobalData}; use crate::error::Error; -use crate::user::{give_funds, try_take_funds, UserError}; +use crate::user::{User, UserError}; use crate::{command, group}; use rand::seq::IteratorRandom; use rand::{thread_rng, Rng}; @@ -133,7 +133,7 @@ async fn add_bet( let global = data.get_mut::().unwrap(); - try_take_funds(&global.db, bet.author, bet.amount).map_err(|err| match err { + User::try_take_funds(&global.db, bet.author, bet.amount).map_err(|err| match err { Error::UserError(e) => RaceError::BetFundError(e), _ => panic!("Recv'ed error when trying to bet: {}", err), })?; @@ -296,7 +296,7 @@ async fn race(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { winner_msg.mention(&winner); winner_msg.push_line(""); - give_funds(&global_data.db, winner, payout as u32)?; + User::give_funds(&global_data.db, winner, payout as u32)?; } } } diff --git a/src/discord/fren_coin.rs b/src/discord/fren_coin.rs index c1b690f..f8ba71f 100644 --- a/src/discord/fren_coin.rs +++ b/src/discord/fren_coin.rs @@ -1,6 +1,6 @@ use crate::config::GlobalData; use crate::error::Error; -use crate::user::{get_user, give_funds, transfer_funds, UserError}; +use crate::user::{User, UserError}; use crate::{command, group}; use rand::{thread_rng, Rng}; use serenity::client::Context; @@ -23,7 +23,7 @@ async fn balance(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let user = args.parse::().unwrap_or(msg.author.id); - let wallet = get_user(&global_data.db, user)?; + let wallet = User::get_user(&global_data.db, user)?; msg.reply( &ctx.http, @@ -72,7 +72,7 @@ async fn gift(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); - if let Err(e) = transfer_funds(&global_data.db, msg.author.id, target, amount) { + if let Err(e) = User::transfer_funds(&global_data.db, msg.author.id, target, amount) { if let Error::UserError(UserError::NotEnoughFunds) = e { msg.reply( &ctx.http, @@ -116,7 +116,7 @@ pub async fn give_coin( let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); - give_funds(&global_data.db, user, number_of_coins as u32)?; + User::give_funds(&global_data.db, user, number_of_coins as u32)?; global_data .cfg diff --git a/src/discord/joke.rs b/src/discord/joke.rs index 49dfb07..e6e0151 100644 --- a/src/discord/joke.rs +++ b/src/discord/joke.rs @@ -77,7 +77,7 @@ pub fn render_random( Ok(tera::Tera::one_off( template, - &tera::Context::from_serialize(&random_ctx)?, + &tera::Context::from_serialize(random_ctx)?, false, )?) } @@ -153,7 +153,7 @@ pub async fn add_random(ctx: &Context, msg: &Message, mut args: Args) -> Command #[command] #[example("8ball Funny Response haha")] pub async fn list_random(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - if args.len() < 1 { + if args.is_empty() { msg.reply( &ctx.http, "Look kid, you need to provide both the random name", diff --git a/src/discord/shop.rs b/src/discord/shop.rs index 1f11391..f7fcbf5 100644 --- a/src/discord/shop.rs +++ b/src/discord/shop.rs @@ -1,7 +1,7 @@ use crate::discord::motivate::create_image; use crate::error::Error; -use crate::inventory::{InventoryError, ItemData, ItemType}; -use crate::user::{get_user, give_item, try_take_funds, try_use_item, UserError}; +use crate::inventory::{InventoryError, ItemData, ItemType, Operation}; +use crate::user::{User, UserError}; use crate::{command, group, GlobalData}; use rand::prelude::SliceRandom; use rand::{thread_rng, Rng}; @@ -23,7 +23,9 @@ async fn shop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); - if global_data.cfg.bot_inventory.inventory.is_empty() { + let bot_user = User::get_user(&global_data.db, ctx.cache.current_user_id())?; + + if bot_user.inventory.inventory.is_empty() { msg.reply(&ctx.http, "Sorry shop is closed until we get more wares.") .await?; } else { @@ -31,7 +33,7 @@ async fn shop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { inv_msg.push_bold_line("Fren has wares if you have coin:"); - inv_msg.push_safe(global_data.cfg.bot_inventory.list_items(true)); + inv_msg.push_safe(bot_user.inventory.list_items(true)); msg.reply(&ctx.http, inv_msg.build()).await?; } @@ -47,7 +49,7 @@ async fn inventory(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let mut inv_msg = MessageBuilder::new(); - let user = get_user(&global_data.db, msg.author.id)?; + let user = User::get_user(&global_data.db, msg.author.id)?; if user.inventory.inventory.is_empty() { msg.reply(&ctx, "Sorry your inventory is empty.").await?; @@ -86,38 +88,31 @@ async fn buy(ctx: &Context, msg: &Message, args: Args) -> CommandResult { } }; - if let Some(item_slot) = global_data.cfg.bot_inventory.get_item(item) { - if let Err(err) = try_take_funds(&global_data.db, msg.author.id, item_slot.value() as u32) { - msg.reply( - &ctx.http, - format!( - "I see why you don't get to the cloud district very often: {}", - err - ), - ) - .await?; - return Ok(()); + let res = User::item_transaction( + &global_data.db, + ctx.cache.current_user_id(), + msg.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) + }; + + msg.reply(&ctx.http, resp).await?; + } else { + return Err(err.into()); } } else { - msg.reply(&ctx.http, "I don't have that in stock you goob") + msg.reply(&ctx, format!("Congrats, you now own a '{}'", item)) .await?; - return Ok(()); } - let item_data = match global_data.cfg.bot_inventory.try_take_item(item, 1) { - Ok(i) => i, - Err(err) => { - msg.reply(&ctx.http, format!("I can't just sell you that: {}", err)) - .await?; - return Ok(()); - } - }; - - give_item(&global_data.db, msg.author.id, item, 1, item_data)?; - - msg.reply(&ctx, format!("Congrats, you now own a '{}'", item)) - .await?; - Ok(()) } @@ -144,7 +139,7 @@ async fn use_item(ctx: &Context, msg: &Message, args: Args) -> CommandResult { } }; - let item_data = match try_use_item(&global_data.db, msg.author.id, item) { + let item_data = match User::try_use_item(&global_data.db, msg.author.id, item) { Ok(i) => i, Err(err) => { if let Error::UserError(UserError::InventoryError(InventoryError::NotEnoughItems)) = err @@ -227,19 +222,19 @@ pub async fn restock_shop(ctx: &Context) -> Result<(), CommandError> { let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); - global_data.cfg.bot_inventory.inventory.clear(); + let mut bot_user = User::get_user(&global_data.db, ctx.cache.current_user_id())?; - global_data.cfg.bot_inventory.give_item( + bot_user.inventory.inventory.clear(); + + bot_user.inventory.give_item( ItemType::TheConceptOfLove, thread_rng().gen_range(0..5), None, ); - global_data.cfg.bot_inventory.give_item( - ItemType::GoodFortune, - thread_rng().gen_range(0..10), - None, - ); - global_data.cfg.bot_inventory.give_item( + bot_user + .inventory + .give_item(ItemType::GoodFortune, thread_rng().gen_range(0..10), None); + bot_user.inventory.give_item( ItemType::CancelInsurance, thread_rng().gen_range(0..10), None, @@ -272,7 +267,7 @@ pub async fn restock_shop(ctx: &Context) -> Result<(), CommandError> { let mut file = tokio::fs::File::create(path.clone()).await?; file.write_all(&nft).await?; - global_data.cfg.bot_inventory.give_item( + bot_user.inventory.give_item( ItemType::Nft, 1, Some(ItemData::Nft(path.to_str().unwrap().to_string())), @@ -280,7 +275,7 @@ pub async fn restock_shop(ctx: &Context) -> Result<(), CommandError> { break; } - global_data.cfg.save(&global_data.args.cfg_path).await?; + global_data.db.insert::(bot_user)?; Ok(()) } diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index 137140f..ae24906 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -34,6 +34,14 @@ pub enum ItemType { Nft, } +#[derive(Debug, Clone, Hash, Eq, PartialEq, Copy)] +#[allow(dead_code)] +pub enum Operation { + Sell, + Buy, + Custom(i64), +} + impl FromStr for ItemType { type Err = InventoryError; @@ -117,7 +125,7 @@ pub struct InventoryManager { } impl InventoryManager { - pub fn get_item(&mut self, item_type: ItemType) -> Option<&mut InventorySlot> { + pub fn get_item_mut(&mut self, item_type: ItemType) -> Option<&mut InventorySlot> { if let Some(inv_ndx) = self.inventory.iter().position(|i| i.item_type == item_type) { Some(&mut self.inventory[inv_ndx]) } else { @@ -125,8 +133,16 @@ impl InventoryManager { } } + pub fn get_item(&self, item_type: ItemType) -> Option<&InventorySlot> { + if let Some(inv_ndx) = self.inventory.iter().position(|i| i.item_type == item_type) { + Some(&self.inventory[inv_ndx]) + } else { + None + } + } + pub fn give_item(&mut self, item_type: ItemType, quantity: i64, item_data: Option) { - match self.get_item(item_type) { + match self.get_item_mut(item_type) { None => self.inventory.push(InventorySlot { quantity, item_type, @@ -144,7 +160,7 @@ impl InventoryManager { item: ItemType, quantity: i64, ) -> Result, InventoryError> { - let slot = self.get_item(item); + let slot = self.get_item_mut(item); if let Some(slot) = slot { if slot.quantity < quantity { @@ -179,7 +195,9 @@ impl InventoryManager { } pub fn try_use_item(&mut self, item: ItemType) -> Result, InventoryError> { - let item_slot = self.get_item(item).ok_or(InventoryError::NotEnoughItems)?; + let item_slot = self + .get_item_mut(item) + .ok_or(InventoryError::NotEnoughItems)?; if item_slot.quantity < item_slot.use_cost() { return Err(InventoryError::NotEnoughItems); diff --git a/src/user/mod.rs b/src/user/mod.rs index dd2134f..b2b7940 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -1,5 +1,5 @@ use crate::error::Error; -use crate::inventory::{InventoryError, InventoryManager, ItemData, ItemType}; +use crate::inventory::{InventoryError, InventoryManager, ItemData, ItemType, Operation}; use j_db::database::Database; use j_db::model::JdbModel; use serde::{Deserialize, Serialize}; @@ -39,6 +39,7 @@ pub struct User { pub inventory: InventoryManager, } +#[allow(dead_code)] impl User { pub fn new(user_id: UserId) -> Self { Self { @@ -48,6 +49,147 @@ impl User { inventory: Default::default(), } } + + pub fn get_user(db: &Database, discord_id: UserId) -> Result { + let user: Option = db + .filter(|_, user: &User| user.user_id == discord_id)? + .next(); + + let user = match user { + None => db.insert::(User::new(discord_id))?, + Some(user) => user, + }; + + Ok(user) + } + + pub fn transfer_funds( + db: &Database, + src: UserId, + dest: UserId, + amount: u32, + ) -> Result<(), Error> { + Self::try_take_funds(db, src, amount)?; + + Self::give_funds(db, dest, amount)?; + Ok(()) + } + + pub fn give_funds(db: &Database, discord_id: UserId, amount: u32) -> Result<(), Error> { + let mut wallet = Self::get_user(db, discord_id)?; + wallet.coin_count += amount as i64; + + db.insert::(wallet)?; + + Ok(()) + } + + pub fn try_take_funds(db: &Database, discord_id: UserId, amount: u32) -> Result<(), Error> { + let mut wallet = Self::get_user(db, discord_id)?; + if wallet.coin_count < amount as i64 { + return Err(UserError::NotEnoughFunds.into()); + } + + wallet.coin_count -= amount as i64; + + db.insert::(wallet)?; + + Ok(()) + } + + pub fn give_item( + db: &Database, + discord_id: UserId, + item: ItemType, + quantity: i64, + item_data: Option, + ) -> Result<(), Error> { + let mut user = Self::get_user(db, discord_id)?; + + user.inventory.give_item(item, quantity, item_data); + + db.insert::(user)?; + + Ok(()) + } + + #[allow(dead_code)] + pub fn try_take_item( + db: &Database, + discord_id: UserId, + item: ItemType, + quantity: i64, + ) -> Result, Error> { + let mut user = Self::get_user(db, discord_id)?; + + let item = user + .inventory + .try_take_item(item, quantity) + .map_err(UserError::InventoryError)?; + + db.insert::(user)?; + + Ok(item) + } + + pub fn try_use_item( + db: &Database, + discord_id: UserId, + item: ItemType, + ) -> Result, Error> { + let mut user = Self::get_user(db, discord_id)?; + + let item = user + .inventory + .try_use_item(item) + .map_err(UserError::InventoryError)?; + + db.insert::(user)?; + + Ok(item) + } + + pub fn item_transaction( + db: &Database, + seller_id: UserId, + buyer_id: UserId, + item: ItemType, + operation: Operation, + ) -> Result<(), Error> { + let mut seller = Self::get_user(db, seller_id)?; + let mut buyer = Self::get_user(db, buyer_id)?; + + if seller.inventory.get_item(item).is_none() { + return Err(UserError::from(InventoryError::NotEnoughItems).into()); + } + + let item = seller.inventory.get_item_mut(item).unwrap(); + + let price = match operation { + Operation::Sell => item.sell_value(), + Operation::Buy => item.value(), + Operation::Custom(p) => p, + }; + + if buyer.coin_count < price { + return Err(UserError::NotEnoughFunds.into()); + } + + buyer.coin_count -= price; + + buyer + .inventory + .give_item(item.item_type, 1, item.item_data.clone()); + + item.quantity -= 1; + item.item_data = None; + + seller.coin_count += price; + + db.insert::(seller)?; + db.insert::(buyer)?; + Ok(()) + } } impl JdbModel for User { @@ -63,97 +205,3 @@ impl JdbModel for User { "Users".to_string() } } - -pub fn get_user(db: &Database, discord_id: UserId) -> Result { - let user: Option = db - .filter(|_, user: &User| user.user_id == discord_id)? - .next(); - - let user = match user { - None => db.insert::(User::new(discord_id))?, - Some(user) => user, - }; - - Ok(user) -} - -pub fn transfer_funds(db: &Database, src: UserId, dest: UserId, amount: u32) -> Result<(), Error> { - try_take_funds(db, src, amount)?; - - give_funds(db, dest, amount)?; - Ok(()) -} - -pub fn give_funds(db: &Database, discord_id: UserId, amount: u32) -> Result<(), Error> { - let mut wallet = get_user(db, discord_id)?; - wallet.coin_count += amount as i64; - - db.insert::(wallet)?; - - Ok(()) -} - -pub fn try_take_funds(db: &Database, discord_id: UserId, amount: u32) -> Result<(), Error> { - let mut wallet = get_user(db, discord_id)?; - if wallet.coin_count < amount as i64 { - return Err(UserError::NotEnoughFunds.into()); - } - - wallet.coin_count -= amount as i64; - - db.insert::(wallet)?; - - Ok(()) -} - -pub fn give_item( - db: &Database, - discord_id: UserId, - item: ItemType, - quantity: i64, - item_data: Option, -) -> Result<(), Error> { - let mut user = get_user(db, discord_id)?; - - user.inventory.give_item(item, quantity, item_data); - - db.insert::(user)?; - - Ok(()) -} - -#[allow(dead_code)] -pub fn try_take_item( - db: &Database, - discord_id: UserId, - item: ItemType, - quantity: i64, -) -> Result, Error> { - let mut user = get_user(db, discord_id)?; - - let item = user - .inventory - .try_take_item(item, quantity) - .map_err(UserError::InventoryError)?; - - db.insert::(user)?; - - Ok(item) -} - -pub fn try_use_item( - db: &Database, - discord_id: UserId, - item: ItemType, -) -> Result, Error> { - let mut user = get_user(db, discord_id)?; - - let item = user - .inventory - .try_use_item(item) - .map_err(UserError::InventoryError)?; - - db.insert::(user)?; - - Ok(item) -}