use poise::ChoiceParameter; use poise::serenity_prelude::UserId; use poise::serenity_prelude::utils::MessageBuilder; use rand::prelude::IndexedRandom; use rand::rng; 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, PartialOrd, )] #[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, #[name = "Land Mine"] LandMine, #[name = "Phrase Canceler"] PhraseCanceler, #[name = "Nuke"] Nuke, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Target { Myself, User(UserId), Everyone, Other, } 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(), ItemType::LandMine => "A land mine you can set up for someone to step on".to_string(), ItemType::PhraseCanceler => "Make a term antiquated".to_string(), ItemType::Nuke => "A nuclear warhead with a vvery large payload".to_string(), } } pub fn use_item_message(&self, target: &str, _item_data: &Option) -> Option { match self { ItemType::CancelInsurance => { Some("You are immune to the next (1) cancelings.".to_string()) } ItemType::TheConceptOfLove => { Some("I have DMed you the concept of love.".to_string()) } 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(); Some(fortune.to_string()) } ItemType::Nft => { None } ItemType::LicenseToBeHorny => { Some("https://media.discordapp.net/attachments/840015650286075945/1127022083919069184/Img_2022_10_21_05_08_12.jpg".to_string()) } ItemType::KillGun => { Some(format!("You draw your trusty kill gun and shoot one kill bullet. It hits it mark between {target}'s eyes, killing them instantly. They are now dead.")) } ItemType::CancelRay => { Some(format!("You shoot the cancel ray at {target}. As the ray impacts them, you can hear their phone buzz. They have been cancelled, you hear the liberal media in the distance.")) } ItemType::Helmet => { Some("Your trusty helmet regards you helmetly".to_string()) } ItemType::EMP => { Some("Humanity has forgotten a time before electricity. A time before all our weapons, toys, gadgets. We no longer have to try and remember that time. That time has returned".to_string()) } ItemType::LandMine => { None } ItemType::PhraseCanceler => { None } ItemType::Nuke => { Some("The Flock Enters A New Age https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmdmMWJuamQxZnQxcWlleHJtdXVtY3VyaDZlNHpwZGxobDl4c251ZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/HhTXt43pk1I1W/giphy.gif".to_string()) } } } pub fn block_message(&self, target: &str) -> String { match self { ItemType::CancelInsurance => format!( "The cancel beam nearly hits {target}, but they are surrounded by a shimmering blue energy shield. \"The liberal media won't strike this time!\"" ), ItemType::KillGun => format!( "The kill bullet shoots at kill velocity toward {target}! 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\"" ), _ => "How did you block that, wtf bro. Hacks! ".to_string(), } } pub fn blocked_by(&self) -> Vec { match self { ItemType::KillGun => vec![ItemType::Helmet], ItemType::CancelRay => vec![ItemType::CancelInsurance], _ => Vec::new(), } } } #[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 if item.starts_with("landmine") { Ok(ItemType::LandMine) } else if item.starts_with("phrasecanceler") { Ok(ItemType::PhraseCanceler) } else if item.starts_with("nuke") { Ok(ItemType::Nuke) } else { Err(InventoryError::UnkownItem) } } } impl Display for ItemType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.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 => 1_000, ItemType::Helmet => 10_000, ItemType::CancelRay => 1_000, ItemType::EMP => 50_000, ItemType::LandMine => 3_000, ItemType::PhraseCanceler => 2_000, ItemType::Nuke => 100_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()) } pub fn has_item(&self, item: ItemType) -> bool { if let Some(slot) = self.get_item(item) { slot.quantity > 0 } else { false } } }