2023-01-15 17:30:23 +11:00
use super ::{ VerbContext , UserVerb , UserVerbRef , UResult , user_error ,
get_player_item_or_fail , search_item_for_user } ;
use async_trait ::async_trait ;
use ansi ::ansi ;
2023-01-20 23:08:40 +11:00
use std ::time ;
use crate ::{
2023-01-22 01:16:00 +11:00
services ::{ broadcast_to_room , skills ::skill_check_and_grind } ,
2023-01-20 23:08:40 +11:00
db ::{ DBTrans , ItemSearchParams } ,
2023-01-22 01:16:00 +11:00
models ::{
item ::{ Item , LocationActionType , Subattack , SkillType } ,
task ::{ Task , TaskMeta , TaskDetails }
} ,
static_content ::{
possession_type ::{ WeaponData , BodyPart , possession_data , fist } ,
npc ::npc_by_code ,
} ,
2023-01-20 23:08:40 +11:00
regular_tasks ::{ TaskRunContext , TaskHandler } ,
DResult
} ;
2023-01-15 17:30:23 +11:00
use async_recursion ::async_recursion ;
2023-01-22 01:16:00 +11:00
use chrono ::Utc ;
2023-01-15 17:30:23 +11:00
2023-01-22 01:16:00 +11:00
#[ derive(Clone) ]
2023-01-20 23:08:40 +11:00
pub struct AttackTaskHandler ;
#[ async_trait ]
impl TaskHandler for AttackTaskHandler {
2023-01-22 01:16:00 +11:00
async fn do_task ( & self , ctx : & mut TaskRunContext ) -> DResult < Option < time ::Duration > > {
let ( ctype , ccode ) = ctx . task . meta . task_code . split_once ( " / " )
. ok_or ( " Invalid AttackTick task code " ) ? ;
let mut attacker_item = match ctx . trans . find_item_by_type_code ( ctype , ccode ) . await ? {
None = > { return Ok ( None ) ; } // Player is gone
Some ( item ) = > ( * item ) . clone ( )
} ;
let ( vtype , vcode ) =
match attacker_item . active_combat . as_ref ( ) . and_then ( | ac | ac . attacking . as_ref ( ) ) . and_then ( | v | v . split_once ( " / " ) ) {
None = > return Ok ( None ) ,
Some ( x ) = > x
} ;
let mut victim_item = match ctx . trans . find_item_by_type_code ( vtype , vcode ) . await ? {
None = > { return Ok ( None ) ; }
Some ( item ) = > ( * item ) . clone ( )
} ;
let weapon = what_wielded ( ctx . trans , & attacker_item ) . await ? ;
let attack_skill = * attacker_item . total_skills . get ( & weapon . uses_skill ) . unwrap_or ( & 0.0 ) ;
let victim_dodge_skill = * victim_item . total_skills . get ( & SkillType ::Dodge ) . unwrap_or ( & 0.0 ) ;
let dodge_result = skill_check_and_grind ( ctx . trans , & mut victim_item , & SkillType ::Dodge ,
attack_skill ) . await ? ;
let attack_result = skill_check_and_grind ( ctx . trans , & mut attacker_item , & weapon . uses_skill ,
victim_dodge_skill ) . await ? ;
if dodge_result > attack_result {
let msg_exp = format! ( " {} dodges out of the way of {} 's attack. \n " ,
victim_item . display_for_sentence ( true , 1 , true ) ,
attacker_item . display_for_sentence ( true , 1 , false ) ) ;
let msg_nonexp = format! ( " {} dodges out of the way of {} 's attack. \n " ,
victim_item . display_for_sentence ( false , 1 , true ) ,
attacker_item . display_for_sentence ( false , 1 , false ) ) ;
broadcast_to_room ( ctx . trans , & attacker_item . location , None , & msg_exp , Some ( & msg_nonexp ) ) . await ? ;
} else {
// TODO: Parry system of some kind?
// Determine body part...
let part = BodyPart ::sample ( ) ;
// TODO: Armour / soaks
// TODO: Calculate damage etc... and display health impact.
let msg_exp = weapon . normal_attack_success_message ( & attacker_item , & victim_item , & part , true ) + " . \n " ;
let msg_nonexp = weapon . normal_attack_success_message ( & attacker_item , & victim_item , & part , false ) + " . \n " ;
broadcast_to_room ( ctx . trans , & attacker_item . location , None , & msg_exp , Some ( & msg_nonexp ) ) . await ? ;
}
let msg_exp = & ( weapon . normal_attack_start_message ( & attacker_item , & victim_item , true ) + " . \n " ) ;
let msg_nonexp = & ( weapon . normal_attack_start_message ( & attacker_item , & victim_item , false ) + " . \n " ) ;
broadcast_to_room ( ctx . trans , & attacker_item . location , None , msg_exp , Some ( msg_nonexp ) ) . await ? ;
ctx . trans . save_item_model ( & attacker_item ) . await ? ;
ctx . trans . save_item_model ( & victim_item ) . await ? ;
Ok ( Some ( attack_speed ( & attacker_item ) ) )
2023-01-20 23:08:40 +11:00
}
}
2023-01-22 01:16:00 +11:00
pub static TASK_HANDLER : & ( dyn TaskHandler + Sync + Send ) = & AttackTaskHandler ;
2023-01-15 23:16:02 +11:00
pub async fn stop_attacking ( trans : & DBTrans , by_whom : & Item , to_whom : & Item ) -> UResult < ( ) > {
let mut new_to_whom = ( * to_whom ) . clone ( ) ;
if let Some ( ac ) = new_to_whom . active_combat . as_mut ( ) {
let old_attacker = format! ( " {} / {} " , by_whom . item_type , by_whom . item_code ) ;
ac . attacked_by . retain ( | v | v ! = & old_attacker ) ;
trans . save_item_model ( & new_to_whom ) . await ? ;
}
let mut new_by_whom = ( * by_whom ) . clone ( ) ;
if let Some ( ac ) = new_by_whom . active_combat . as_mut ( ) {
ac . attacking = None ;
}
new_by_whom . action_type = LocationActionType ::Normal ;
trans . save_item_model ( & new_by_whom ) . await ? ;
Ok ( ( ) )
}
2023-01-22 01:16:00 +11:00
async fn what_wielded ( _trans : & DBTrans , who : & Item ) -> DResult < & 'static WeaponData > {
// TODO: Search inventory for wielded item first.
if who . item_type = = " npc " {
if let Some ( intrinsic ) = npc_by_code ( ) . get ( who . item_code . as_str ( ) )
. and_then ( | npc | npc . intrinsic_weapon . as_ref ( ) ) {
if let Some ( weapon ) = possession_data ( ) . get ( intrinsic ) . and_then ( | p | p . weapon_data . as_ref ( ) ) {
return Ok ( weapon )
}
}
}
Ok ( fist ( ) )
}
fn attack_speed ( _who : & Item ) -> time ::Duration {
time ::Duration ::from_secs ( 5 )
}
2023-01-15 17:30:23 +11:00
#[ async_recursion ]
2023-01-15 23:16:02 +11:00
pub async fn start_attack ( trans : & DBTrans , by_whom : & Item , to_whom : & Item ) -> UResult < ( ) > {
2023-01-15 17:30:23 +11:00
let mut msg_exp = String ::new ( ) ;
let mut msg_nonexp = String ::new ( ) ;
let mut verb : String = " attacks " . to_string ( ) ;
match by_whom . action_type {
LocationActionType ::Sitting | LocationActionType ::Reclining = > {
msg_exp . push_str ( & format! ( ansi! ( " {} stands up. \n " ) , & by_whom . display ) ) ;
msg_nonexp . push_str ( & format! ( ansi! ( " {} stands up. \n " ) ,
by_whom . display_less_explicit . as_ref ( ) . unwrap_or ( & by_whom . display ) ) ) ;
} ,
LocationActionType ::Attacking ( _ ) = > {
match by_whom . active_combat . as_ref ( ) . and_then ( | ac | ac . attacking . as_ref ( ) . and_then ( | s | s . split_once ( " / " ) ) ) {
Some ( ( cur_type , cur_code ) ) if cur_type = = to_whom . item_type & & cur_code = = to_whom . item_code = >
user_error ( format! ( " You're already attacking {} ! " , to_whom . pronouns . object ) ) ? ,
Some ( ( cur_type , cur_code ) ) = > {
if let Some ( cur_item_arc ) = trans . find_item_by_type_code ( cur_type , cur_code ) . await ? {
2023-01-15 23:16:02 +11:00
stop_attacking ( trans , by_whom , & cur_item_arc ) . await ? ;
2023-01-15 17:30:23 +11:00
}
}
_ = > { }
}
verb = " refocuses " . to_string ( ) + & by_whom . pronouns . possessive + " attacks on " ;
} ,
_ = > { }
}
2023-01-22 01:16:00 +11:00
2023-01-15 17:30:23 +11:00
msg_exp . push_str ( & format! (
ansi! ( " <red>{} {} {}.<reset> \n " ) ,
& by_whom . display_for_sentence ( true , 1 , true ) ,
verb ,
& to_whom . display_for_sentence ( true , 1 , false ) )
) ;
msg_nonexp . push_str ( & format! (
ansi! ( " <red>{} {} {}.<reset> \n " ) ,
& by_whom . display_for_sentence ( false , 1 , true ) ,
verb ,
& to_whom . display_for_sentence ( false , 1 , false ) )
) ;
2023-01-22 01:16:00 +11:00
let wielded = what_wielded ( trans , by_whom ) . await ? ;
msg_exp . push_str ( & ( wielded . normal_attack_start_message ( by_whom , to_whom , true ) + " . \n " ) ) ;
msg_nonexp . push_str ( & ( wielded . normal_attack_start_message ( by_whom , to_whom , false ) + " . \n " ) ) ;
2023-01-20 23:08:40 +11:00
broadcast_to_room ( trans , & by_whom . location , None ::< & Item > , & msg_exp , Some ( msg_nonexp . as_str ( ) ) ) . await ? ;
2023-01-15 17:30:23 +11:00
let mut by_whom_for_update = by_whom . clone ( ) ;
by_whom_for_update . active_combat . get_or_insert_with ( | | Default ::default ( ) ) . attacking =
Some ( format! ( " {} / {} " ,
& to_whom . item_type , & to_whom . item_code ) ) ;
by_whom_for_update . action_type = LocationActionType ::Attacking ( Subattack ::Normal ) ;
let mut to_whom_for_update = to_whom . clone ( ) ;
to_whom_for_update . active_combat . get_or_insert_with ( | | Default ::default ( ) ) . attacked_by . push (
format! ( " {} / {} " ,
& by_whom . item_type , & by_whom . item_code )
) ;
2023-01-22 01:16:00 +11:00
trans . upsert_task ( & Task {
meta : TaskMeta {
task_code : format ! ( " {}/{} " , by_whom . item_type , by_whom . item_code ) ,
next_scheduled : Utc ::now ( ) + chrono ::Duration ::milliseconds (
attack_speed ( by_whom ) . as_millis ( ) as i64 ) ,
.. Default ::default ( )
} ,
details : TaskDetails ::AttackTick
} ) . await ? ;
2023-01-15 17:30:23 +11:00
trans . save_item_model ( & by_whom_for_update ) . await ? ;
trans . save_item_model ( & to_whom_for_update ) . await ? ;
// Auto-counterattack if victim isn't busy.
if to_whom_for_update . active_combat . as_ref ( ) . and_then ( | ac | ac . attacking . as_ref ( ) ) = = None {
start_attack ( trans , & to_whom_for_update , & by_whom_for_update ) . await ? ;
}
2023-01-22 01:16:00 +11:00
Ok ( ( ) )
2023-01-15 17:30:23 +11:00
}
pub struct Verb ;
#[ async_trait ]
impl UserVerb for Verb {
async fn handle ( self : & Self , ctx : & mut VerbContext , _verb : & str , remaining : & str ) -> UResult < ( ) > {
let player_item = get_player_item_or_fail ( ctx ) . await ? ;
let attack_whom = search_item_for_user ( ctx , & ItemSearchParams {
include_loc_contents : true ,
.. ItemSearchParams ::base ( & player_item , remaining )
} ) . await ? ;
match attack_whom . item_type . as_str ( ) {
" npc " = > { }
" player " = > { } ,
_ = > user_error ( " Only characters (players / NPCs) accept whispers " . to_string ( ) ) ?
}
if attack_whom . item_code = = player_item . item_code & & attack_whom . item_type = = player_item . item_type {
user_error ( " That's you, silly! " . to_string ( ) ) ?
}
if attack_whom . is_challenge_attack_only {
// Add challenge check here.
2023-01-22 01:16:00 +11:00
user_error ( ansi! ( " <blue>Your wristpad vibrates and blocks you from doing that.<reset> You get a feeling that while the empire might be gone, the system to stop subjects with working wristpads from fighting each unless they have an accepted challenge is very much functional. [Try <bold>help challenge<reset>] " ) . to_string ( ) ) ?
2023-01-15 17:30:23 +11:00
}
start_attack ( & ctx . trans , & player_item , & attack_whom ) . await
}
}
static VERB_INT : Verb = Verb ;
pub static VERB : UserVerbRef = & VERB_INT as UserVerbRef ;