blastmud/blastmud_game/src/static_content/journals.rs

191 lines
5.8 KiB
Rust
Raw Normal View History

use super::species::SpeciesType;
#[double]
use crate::db::DBTrans;
use crate::{
models::{item::Item, journal::JournalType, user::User},
DResult,
};
use async_trait::async_trait;
use itertools::Itertools;
use log::warn;
use mockall_double::double;
use once_cell::sync::OnceCell;
use std::collections::BTreeMap;
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<KillSubscriptionType>;
async fn handle_kill(
&self,
trans: &DBTrans,
user: &mut User,
player: &mut Item,
victim: &Item,
) -> DResult<bool>;
}
pub struct JournalData {
name: &'static str,
details: &'static str,
xp: u64,
}
pub fn journal_types() -> &'static BTreeMap<JournalType, JournalData> {
static JOURNAL_TYPES: OnceCell<BTreeMap<JournalType, JournalData>> = 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<Vec<&'static (dyn JournalChecker + Sync + Send)>> = OnceCell::new();
CHECKERS.get_or_init(|| vec![&first_dog::CHECKER])
}
pub fn checkers_by_species(
) -> &'static BTreeMap<SpeciesType, Vec<&'static (dyn JournalChecker + Sync + Send)>> {
static MAP: OnceCell<BTreeMap<SpeciesType, Vec<&'static (dyn JournalChecker + Sync + Send)>>> =
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))
}
_ => 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<BTreeMap<&'static str, Vec<&'static (dyn JournalChecker + Sync + Send)>>> =
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)),
_ => 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<bool> {
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,
};
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?;
user.experience.journals.completed_journals.insert(journal);
// Note: Not counted as 'change for this reroll' since it is permanent.
user.xp_adjusted(player, journal_data.xp as i64, trans, &sess)
.await?;
}
Ok(true)
}
pub async fn check_journal_for_kill(
trans: &DBTrans,
player: &mut Item,
victim: &Item,
) -> DResult<bool> {
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)
}