use poise::serenity_prelude::utils::MessageBuilder; use serde::{Deserialize, Serialize}; use std::collections::hash_map::DefaultHasher; use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; use std::str::FromStr; #[derive(Debug, Clone)] pub enum InventoryError { NotEnoughItems, UnkownItem, } impl Display for InventoryError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { InventoryError::NotEnoughItems => write!(f, "Not enough items"), InventoryError::UnkownItem => write!(f, "Not sure that that is, maybe check Walmart?"), } } } impl std::error::Error for InventoryError {} #[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] #[serde(tag = "type", content = "data")] pub enum ItemData { Nft(String), } #[derive( Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, Copy, poise::ChoiceParameter, )] #[allow(clippy::upper_case_acronyms)] pub enum ItemType { #[name = "Cancel Insurance"] CancelInsurance, #[name = "The Concept of Love"] TheConceptOfLove, #[name = "Good Fortune"] GoodFortune, #[name = "NFT"] Nft, #[name = "License to be Horny"] LicenseToBeHorny, #[name = "Kill Gun"] KillGun, #[name = "Cancel Ray"] CancelRay, #[name = "Helmet"] Helmet, #[name = "EMP"] EMP, } impl ItemType { pub fn description(&self) -> String { match self { ItemType::CancelInsurance => "Automatically used to prevent being canceled".to_string(), ItemType::TheConceptOfLove => "Understand!".to_string(), ItemType::GoodFortune => { "Used to give a good fortune. `!use good fortune am I not cringe?`".to_string() } ItemType::Nft => "A Non Fungible Token on the REAL FREN blockchain network".to_string(), ItemType::LicenseToBeHorny => "Allows one horny time".to_string(), ItemType::KillGun => "Used to kill people. `!use kill gun @Austin`".to_string(), ItemType::CancelRay => "Used to cancel people. `!use cancel ray @Austin`".to_string(), ItemType::Helmet => "Automatically used to block being killed".to_string(), ItemType::EMP => "Disables weapons and defenses".to_string(), } } } #[derive(Debug, Clone, Hash, Eq, PartialEq, Copy)] #[allow(dead_code)] pub enum Operation { Sell, Buy, Custom(i64), } impl FromStr for ItemType { type Err = InventoryError; fn from_str(s: &str) -> Result { let item = s.to_lowercase().replace(' ', ""); if item.starts_with("cancelinsurance") { Ok(ItemType::CancelInsurance) } else if item.starts_with("theconceptoflove") { Ok(ItemType::TheConceptOfLove) } else if item.starts_with("goodfortune") { Ok(ItemType::GoodFortune) } else if item.starts_with("nft") { Ok(ItemType::Nft) } else if item.starts_with("licensetobehorny") { Ok(ItemType::LicenseToBeHorny) } else if item.starts_with("killgun") { Ok(ItemType::KillGun) } else if item.starts_with("helmet") { Ok(ItemType::Helmet) } else if item.starts_with("cancelray") { Ok(ItemType::CancelRay) } else if item.starts_with("emp") { Ok(ItemType::EMP) } else { Err(InventoryError::UnkownItem) } } } impl Display for ItemType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let name = match self { ItemType::CancelInsurance => "Cancel Insurance".to_string(), ItemType::TheConceptOfLove => "The Concept of Love".to_string(), ItemType::GoodFortune => "Good Fortune".to_string(), ItemType::Nft => "NFT".to_string(), ItemType::LicenseToBeHorny => "License to be Horny".to_string(), ItemType::KillGun => "Kill Gun".to_string(), ItemType::Helmet => "Helmet".to_string(), ItemType::CancelRay => "Cancel Ray".to_string(), ItemType::EMP => "EMP".to_string(), }; write!(f, "{name}") } } pub fn nft_value(s: &String) -> i64 { let mut hasher = DefaultHasher::new(); s.hash(&mut hasher); let hash = hasher.finish(); let per_value = (hash as f64) / (u64::MAX as f64); ((2000.0 * per_value) as i64) - 500 } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InventorySlot { pub quantity: i64, pub item_type: ItemType, pub item_data: Option, } impl InventorySlot { pub fn value(&self) -> i64 { match self.item_type { ItemType::CancelInsurance => 10_000, ItemType::TheConceptOfLove => 300, ItemType::GoodFortune => 75, ItemType::Nft => 250, ItemType::LicenseToBeHorny => 250, ItemType::KillGun => 2_000, ItemType::Helmet => 10_000, ItemType::CancelRay => 2_000, ItemType::EMP => 50_000, } } pub fn sell_value(&self) -> i64 { match self.item_type { ItemType::Nft => { if let Some(ItemData::Nft(s)) = &self.item_data { nft_value(s) } else { 0 } } _ => self.value() / 2, } } pub fn use_cost(&self) -> i64 { match self.item_type { ItemType::Nft => 0, ItemType::Helmet => 0, ItemType::CancelInsurance => 0, _ => 1, } } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct InventoryManager { pub inventory: Vec, } impl InventoryManager { 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 { None } } 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_mut(item_type) { None => self.inventory.push(InventorySlot { quantity, item_type, item_data, }), Some(slot) => { slot.item_data = item_data; // Only one NFT at a time if item_type != ItemType::Nft { slot.quantity += quantity; } else { slot.quantity = 1; } } } } pub fn try_take_item( &mut self, item: ItemType, quantity: i64, ) -> Result, InventoryError> { let slot = self.get_item_mut(item); if let Some(slot) = slot { if slot.quantity < quantity { return Err(InventoryError::NotEnoughItems); } slot.quantity -= quantity; Ok(slot.item_data.clone()) } else { Err(InventoryError::NotEnoughItems) } } pub fn list_items(&self, buy_value: bool) -> String { let mut msg_builder = MessageBuilder::new(); for item_slot in &self.inventory { if item_slot.quantity != 0 { let value = if buy_value { item_slot.value() } else { item_slot.sell_value() }; msg_builder.push_line(format!( "* {} [{} coins] (x{})", item_slot.item_type, value, item_slot.quantity )); } } msg_builder.build() } pub fn try_use_item( &mut self, item: ItemType, force: bool, ) -> Result, InventoryError> { let item_slot = self .get_item_mut(item) .ok_or(InventoryError::NotEnoughItems)?; if item_slot.quantity < item_slot.use_cost() || item_slot.quantity == 0 { return Err(InventoryError::NotEnoughItems); } if force { item_slot.quantity -= 1; } else { item_slot.quantity -= item_slot.use_cost(); } Ok(item_slot.item_data.clone()) } }