2023-07-24 22:46:50 +10:00
use super ::species ::SpeciesType ;
#[ double ]
use crate ::db ::DBTrans ;
2023-05-16 22:02:42 +10:00
use crate ::{
2023-07-24 22:46:50 +10:00
models ::{ item ::Item , journal ::JournalType , user ::User } ,
2023-05-16 22:02:42 +10:00
DResult ,
} ;
use async_trait ::async_trait ;
use itertools ::Itertools ;
2023-07-24 22:46:50 +10:00
use log ::warn ;
use mockall_double ::double ;
use once_cell ::sync ::OnceCell ;
use std ::collections ::BTreeMap ;
2023-05-16 22:02:42 +10:00
mod first_dog ;
2023-09-27 23:22:37 +10:00
mod killed_killbot ;
2023-05-16 22:02:42 +10:00
#[ 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 ,
2023-07-24 22:46:50 +10:00
victim : & Item ,
2023-05-16 22:02:42 +10:00
) -> 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
} ) ,
2023-09-27 23:22:37 +10:00
( JournalType ::TookOutAKillbot , JournalData {
name : " Killed a killbot " ,
details : " realising that in soviet melbs, you kill the killbots. " ,
xp : 200
} ) ,
( JournalType ::JoinedHackerClub , JournalData {
name : " Joined the Hackers' club " ,
details : " finding the secret Hackers' club. " ,
xp : 250
} ) ,
2023-05-16 22:02:42 +10:00
( 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
} )
2023-07-24 22:46:50 +10:00
) . into_iter ( ) . collect ( ) ) ;
2023-05-16 22:02:42 +10:00
}
pub fn journal_checkers ( ) -> & 'static Vec < & 'static ( dyn JournalChecker + Sync + Send ) > {
static CHECKERS : OnceCell < Vec < & 'static ( dyn JournalChecker + Sync + Send ) > > = OnceCell ::new ( ) ;
2023-09-27 23:22:37 +10:00
CHECKERS . get_or_init ( | | vec! [ & first_dog ::CHECKER , & killed_killbot ::CHECKER ] )
2023-05-16 22:02:42 +10:00
}
2023-07-24 22:46:50 +10:00
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 ) > > > =
2023-05-16 22:02:42 +10:00
OnceCell ::new ( ) ;
MAP . get_or_init ( | | {
2023-07-24 22:46:50 +10:00
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 ( ) ) ;
2023-05-16 22:02:42 +10:00
species_groups
. into_iter ( )
. map ( | ( species , g ) | ( species , g . into_iter ( ) . map ( | v | v . 1 ) . collect ( ) ) )
. collect ( )
} )
}
2023-07-24 22:46:50 +10:00
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 ) > > > =
2023-05-16 22:02:42 +10:00
OnceCell ::new ( ) ;
MAP . get_or_init ( | | {
2023-07-24 22:46:50 +10:00
let npc_groups = journal_checkers ( )
. iter ( )
. flat_map ( | jc | {
jc . kill_subscriptions ( )
. into_iter ( )
. filter_map ( | sub | match sub {
2023-10-08 16:47:48 +11:00
KillSubscriptionType ::SpecificNPC { code } = > Some ( ( code , * jc ) ) ,
2023-07-24 22:46:50 +10:00
_ = > None ,
} )
} )
2023-10-08 16:47:48 +11:00
. group_by ( | v | v . 0 ) ;
2023-05-16 22:02:42 +10:00
npc_groups
. into_iter ( )
. map ( | ( species , g ) | ( species , g . into_iter ( ) . map ( | v | v . 1 ) . collect ( ) ) )
. collect ( )
} )
}
2023-07-24 22:46:50 +10:00
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 )
{
2023-05-16 22:02:42 +10:00
return Ok ( false ) ;
}
let journal_data = match journal_types ( ) . get ( & journal ) {
None = > {
2023-07-24 22:46:50 +10:00
warn! (
" Tried to award journal type {:#?} that doesn't exist. " ,
& journal
) ;
2023-05-16 22:02:42 +10:00
return Ok ( false ) ;
2023-07-24 22:46:50 +10:00
}
Some ( v ) = > v ,
2023-05-16 22:02:42 +10:00
} ;
if let Some ( ( sess , _ ) ) = trans . find_session_for_player ( & player . item_code ) . await ? {
2023-07-24 22:46:50 +10:00
trans
. queue_for_session (
& sess ,
Some ( & format! (
" Journal earned: {} - You earned {} XP for {} \n " ,
journal_data . name , journal_data . xp , journal_data . details
) ) ,
)
. await ? ;
2023-09-23 23:55:29 +10:00
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 ? ;
2023-05-16 22:02:42 +10:00
}
Ok ( true )
}
2023-07-24 22:46:50 +10:00
pub async fn check_journal_for_kill (
trans : & DBTrans ,
player : & mut Item ,
victim : & Item ,
) -> DResult < bool > {
2023-05-16 22:02:42 +10:00
if player . item_type ! = " player " {
return Ok ( false ) ;
}
let mut user = match trans . find_by_username ( & player . item_code ) . await ? {
None = > return Ok ( false ) ,
2023-07-24 22:46:50 +10:00
Some ( u ) = > u ,
2023-05-16 22:02:42 +10:00
} ;
let mut did_work = false ;
if let Some ( checkers ) = checkers_by_species ( ) . get ( & victim . species ) {
for checker in checkers {
2023-07-24 22:46:50 +10:00
did_work = did_work
| | checker
. handle_kill ( trans , & mut user , player , victim )
. await ? ;
2023-05-16 22:02:42 +10:00
}
}
if let Some ( checkers ) = checkers_by_npc ( ) . get ( victim . item_code . as_str ( ) ) {
for checker in checkers {
2023-07-24 22:46:50 +10:00
did_work = did_work
| | checker
. handle_kill ( trans , & mut user , player , victim )
. await ? ;
2023-05-16 22:02:42 +10:00
}
}
2023-07-24 22:46:50 +10:00
2023-05-16 22:02:42 +10:00
if did_work {
trans . save_user_model ( & user ) . await ? ;
}
Ok ( did_work )
}