forked from blasthavers/blastmud
Condorra
078519be95
Also fix up bugs with navigation during death, and awarding payouts when you don't get any XP.
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)
|
|
}
|