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