forked from blasthavers/blastmud
190 lines
5.7 KiB
Rust
190 lines
5.7 KiB
Rust
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,
|
|
};
|
|
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)
|
|
}
|