blastmud/blastmud_game/src/static_content/journals.rs

178 lines
5.7 KiB
Rust
Raw Normal View History

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<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.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<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.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<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
};
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<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)
}