use crate::{ DResult, models::{ user::User, item::Item, journal::JournalType, } }; use std::collections::{BTreeMap}; use once_cell::sync::OnceCell; use mockall_double::double; #[double] use crate::db::DBTrans; use log::warn; use async_trait::async_trait; use super::species::SpeciesType; use itertools::Itertools; mod first_dog; #[allow(unused)] pub enum KillSubscriptionType { SpecificNPCSpecies { species: SpeciesType }, SpecificNPC { code: &'static str }, } #[async_trait] pub trait JournalChecker { fn kill_subscriptions(&self) -> Vec; async fn handle_kill( &self, trans: &DBTrans, user: &mut User, player: &mut Item, victim: &Item ) -> DResult; } pub struct JournalData { name: &'static str, details: &'static str, xp: u64, } pub fn journal_types() -> &'static BTreeMap { static JOURNAL_TYPES: OnceCell> = OnceCell::new(); return JOURNAL_TYPES.get_or_init(|| vec!( (JournalType::SlayedMeanDog, JournalData { name: "Slayed a mean dog", details: "killing a mean street dog for the first time.", xp: 100 }), (JournalType::Died, JournalData { name: "Carked it", details: "dying for the first time. Fortunately, you can come back by recloning in to a fresh body, just with fewer credits, a bit less experience, and a bruised ego! All your stuff is still on your body, so better go find it, or give up on it.", xp: 150 }) ).into_iter().collect()) } pub fn journal_checkers() -> &'static Vec<&'static (dyn JournalChecker + Sync + Send)> { static CHECKERS: OnceCell> = OnceCell::new(); CHECKERS.get_or_init(|| vec!( &first_dog::CHECKER )) } pub fn checkers_by_species() -> &'static BTreeMap> { static MAP: OnceCell>> = OnceCell::new(); MAP.get_or_init(|| { let species_groups = journal_checkers().iter().flat_map( |jc| jc.kill_subscriptions().into_iter() .filter_map(|sub| match sub { KillSubscriptionType::SpecificNPCSpecies { species } => Some((species.clone(), jc.clone())), _ => None }) ).group_by(|v| v.0.clone()); species_groups .into_iter() .map(|(species, g)| (species, g.into_iter().map(|v| v.1).collect())) .collect() }) } pub fn checkers_by_npc() -> &'static BTreeMap<&'static str, Vec<&'static (dyn JournalChecker + Sync + Send)>> { static MAP: OnceCell>> = OnceCell::new(); MAP.get_or_init(|| { let npc_groups = journal_checkers().iter().flat_map( |jc| jc.kill_subscriptions().into_iter() .filter_map(|sub| match sub { KillSubscriptionType::SpecificNPC { code } => Some((code.clone(), jc.clone())), _ => None }) ).group_by(|v| v.0.clone()); npc_groups .into_iter() .map(|(species, g)| (species, g.into_iter().map(|v| v.1).collect())) .collect() }) } pub async fn award_journal_if_needed(trans: &DBTrans, user: &mut User, player: &mut Item, journal: JournalType) -> DResult { if user.experience.journals.completed_journals.contains(&journal) { return Ok(false); } let journal_data = match journal_types().get(&journal) { None => { warn!("Tried to award journal type {:#?} that doesn't exist.", &journal); return Ok(false); }, Some(v) => v }; user.experience.journals.completed_journals.insert(journal); // Note: Not counted as 'change for this reroll' since it is permanent. player.total_xp += journal_data.xp; if let Some((sess, _)) = trans.find_session_for_player(&player.item_code).await? { trans.queue_for_session( &sess, Some(&format!("Journal earned: {} - You earned {} XP for {}\n", journal_data.name, journal_data.xp, journal_data.details) )).await?; } Ok(true) } pub async fn check_journal_for_kill(trans: &DBTrans, player: &mut Item, victim: &Item) -> DResult { if player.item_type != "player" { return Ok(false); } let mut user = match trans.find_by_username(&player.item_code).await? { None => return Ok(false), Some(u) => u }; let mut did_work = false; if let Some(checkers) = checkers_by_species().get(&victim.species) { for checker in checkers { did_work = did_work || checker.handle_kill(trans, &mut user, player, victim).await?; } } if let Some(checkers) = checkers_by_npc().get(victim.item_code.as_str()) { for checker in checkers { did_work = did_work || checker.handle_kill(trans, &mut user, player, victim).await?; } } if did_work { trans.save_user_model(&user).await?; } Ok(did_work) }