From c78dc64a7f04032f625868b1d9e9eb2794073198 Mon Sep 17 00:00:00 2001 From: Condorra Date: Thu, 25 May 2023 22:51:52 +1000 Subject: [PATCH] Implement soaks Also fix some minor combat bugs found, including the refocusing bug, and the dead NPCs talking (mk II) bug. --- blastmud_game/src/services/combat.rs | 244 +++++++++++++----- blastmud_game/src/static_content/npc.rs | 2 +- .../src/static_content/possession_type.rs | 140 ++++++---- .../static_content/possession_type/blade.rs | 45 ++-- .../static_content/possession_type/fangs.rs | 41 +-- .../possession_type/head_armour.rs | 50 +++- .../possession_type/lower_armour.rs | 26 +- .../possession_type/torso_armour.rs | 26 +- .../static_content/possession_type/whip.rs | 45 ++-- 9 files changed, 440 insertions(+), 179 deletions(-) diff --git a/blastmud_game/src/services/combat.rs b/blastmud_game/src/services/combat.rs index 5e3ebe7e..07f85a14 100644 --- a/blastmud_game/src/services/combat.rs +++ b/blastmud_game/src/services/combat.rs @@ -11,12 +11,12 @@ use crate::{ journal::JournalType, }, static_content::{ - possession_type::{WeaponData, possession_data, fist}, + possession_type::{WeaponData, WeaponAttackData, DamageType, possession_data, fist}, npc::npc_by_code, species::species_info_map, journals::{check_journal_for_kill, award_journal_if_needed} }, - message_handler::user_commands::{user_error, UResult}, + message_handler::user_commands::{user_error, UResult, CommandHandlingError}, regular_tasks::{TaskRunContext, TaskHandler}, DResult, }; @@ -27,9 +27,167 @@ use chrono::Utc; use async_recursion::async_recursion; use std::time; use ansi::ansi; -use rand::prelude::IteratorRandom; +use rand::{prelude::IteratorRandom, Rng}; use rand_distr::{Normal, Distribution}; +async fn soak_damage(ctx: &mut TaskRunContext<'_>, attack: &WeaponAttackData, victim: &Item, presoak_amount: f64) -> DResult { + let mut damage_by_type: Vec<(&DamageType, f64)> = + attack.other_damage_types.iter().map( + |(frac, dtype)| (dtype, frac * presoak_amount)).collect(); + damage_by_type.push((&attack.base_damage_type, presoak_amount - + damage_by_type.iter().map(|v|v.1).sum::())); + + let mut clothes: Vec = + ctx.trans.find_by_action_and_location(&victim.refstr(), + &LocationActionType::Worn).await? + .iter().map(|cl| (*cl.as_ref()).clone()).collect(); + clothes.sort_unstable_by(|c1, c2| c2.action_type_started.cmp(&c1.action_type_started)); + + let mut total_damage = 0.0; + + for (damage_type, mut damage_amount) in &damage_by_type { + for mut clothing in &mut clothes { + if let Some(soak) = clothing.possession_type + .as_ref() + .and_then(|pt| possession_data().get(pt)) + .and_then(|pd| pd.wear_data.as_ref()) + .and_then(|wd| wd.soaks.get(&damage_type)) { + if damage_amount <= 0.0 { + break; + } + let soak_amount: f64 = ((soak.max_soak - soak.min_soak) * + rand::thread_rng().gen::()).min(damage_amount); + damage_amount -= soak_amount; + let clothes_damage = ((0..(soak_amount as i64)) + .filter(|_| rand::thread_rng().gen::() < + soak.damage_probability_per_soak).count() + as u64).min(clothing.health); + if clothes_damage > 0 { + clothing.health -= clothes_damage; + if victim.item_type == "player" { + if let Some((vic_sess, sess_dat)) = + ctx.trans.find_session_for_player(&victim.item_code).await? + { + ctx.trans.queue_for_session( + &vic_sess, + Some(&format!("A few bits and pieces fly off your {}.\n", + clothing.display_for_session(&sess_dat))) + ).await?; + } + } + } + } + } + total_damage += damage_amount; + } + + for clothing in &clothes { + if clothing.health <= 0 { + ctx.trans.delete_item(&clothing.item_type, &clothing.item_code).await?; + if victim.item_type == "player" { + if let Some((vic_sess, sess_dat)) = + ctx.trans.find_session_for_player(&victim.item_code).await? + { + ctx.trans.queue_for_session( + &vic_sess, + Some(&format!("Your {} is completely destroyed; it crumbles away to nothing.\n", + clothing.display_for_session(&sess_dat))) + ).await?; + } + } + } + } + + Ok(total_damage) +} + +async fn process_attack(ctx: &mut TaskRunContext<'_>, attacker_item: &mut Item, victim_item: &mut Item, + attack: &WeaponAttackData, weapon: &WeaponData) -> DResult { + 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, victim_item, &SkillType::Dodge, + attack_skill).await?; + let user_opt = if attacker_item.item_type == "player" { + ctx.trans.find_by_username(&attacker_item.item_code).await? + } else { None }; + let attack_result = if let Some(user) = user_opt { + let raw_skill = *user.raw_skills.get(&weapon.uses_skill).unwrap_or(&0.0); + if raw_skill >= weapon.raw_min_to_learn && raw_skill <= weapon.raw_max_to_learn { + skill_check_and_grind(ctx.trans, attacker_item, &weapon.uses_skill, + victim_dodge_skill).await? + } else { + skill_check_only(&attacker_item, &weapon.uses_skill, victim_dodge_skill) + } + } else { + skill_check_only(&attacker_item, &weapon.uses_skill, victim_dodge_skill) + }; + + 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?; + ctx.trans.save_item_model(&attacker_item).await?; + ctx.trans.save_item_model(&victim_item).await?; + } else { + // TODO: Parry system of some kind? + + // Determine body part... + let part = victim_item.species.sample_body_part(); + + // TODO: Armour / soaks + + let mut mean_damage: f64 = attack.mean_damage; + for scaling in attack.skill_scaling.iter() { + let skill = *attacker_item.total_skills.get(&scaling.skill).unwrap_or(&0.0); + if skill >= scaling.min_skill { + mean_damage += (skill - scaling.min_skill) * scaling.mean_damage_per_point_over_min; + } + } + + let actual_damage_presoak = Normal::new(mean_damage, + attack.stdev_damage)? + .sample(&mut rand::thread_rng()).floor().max(1.0) as i64; + ctx.trans.save_item_model(&attacker_item).await?; + let actual_damage = soak_damage(ctx, &attack, victim_item, actual_damage_presoak as f64).await? as i64; + let msg_exp = attack.success_message(&attacker_item, victim_item, + &part, true); + let msg_nonexp = attack.success_message(&attacker_item, victim_item, + &part, false); + if actual_damage == 0 { + let msg_exp = format!( + "{}'s attack bounces off {}'s {}.\n", + &attacker_item.display_for_sentence(true, 1, true), + &victim_item.display_for_sentence(true, 1, false), + &part.display(victim_item.sex.clone()) + ); + let msg_nonexp = + format!( + "{}'s attack bounces off {}'s {}.\n", + attacker_item.display_for_sentence(false, 1, true), + victim_item.display_for_sentence(false, 1, false), + &part.display(None) + ); + broadcast_to_room(&ctx.trans, &victim_item.location, + None, &msg_exp, Some(&msg_nonexp)).await?; + } else if change_health(ctx.trans, -actual_damage, victim_item, + &msg_exp, &msg_nonexp).await? { + ctx.trans.save_item_model(victim_item).await?; + return Ok(true); + } + ctx.trans.save_item_model(victim_item).await?; + } + + let msg_exp = &(attack.start_message(&attacker_item, victim_item, true) + ".\n"); + let msg_nonexp = &(attack.start_message(&attacker_item, victim_item, false) + ".\n"); + broadcast_to_room(ctx.trans, &attacker_item.location, None, msg_exp, Some(msg_nonexp)).await?; + Ok(false) +} + #[derive(Clone)] pub struct AttackTaskHandler; #[async_trait] @@ -57,72 +215,12 @@ impl TaskHandler for AttackTaskHandler { } 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 user_opt = if ctype == "player" { ctx.trans.find_by_username(ccode).await? } else { None }; - let attack_result = if let Some(user) = user_opt { - let raw_skill = *user.raw_skills.get(&weapon.uses_skill).unwrap_or(&0.0); - if raw_skill >= weapon.raw_min_to_learn && raw_skill <= weapon.raw_max_to_learn { - skill_check_and_grind(ctx.trans, &mut attacker_item, &weapon.uses_skill, - victim_dodge_skill).await? - } else { - skill_check_only(&attacker_item, &weapon.uses_skill, victim_dodge_skill) - } + if process_attack(ctx, &mut attacker_item, &mut victim_item, &weapon.normal_attack, &weapon).await? { + Ok(None) } else { - skill_check_only(&attacker_item, &weapon.uses_skill, victim_dodge_skill) - }; - - 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?; - ctx.trans.save_item_model(&attacker_item).await?; - ctx.trans.save_item_model(&victim_item).await?; - } else { - // TODO: Parry system of some kind? - - // Determine body part... - let part = victim_item.species.sample_body_part(); - - // TODO: Armour / soaks - - // TODO: Calculate damage etc... and display health impact. - let mut mean_damage: f64 = weapon.normal_attack_mean_damage; - for scaling in weapon.normal_attack_skill_scaling.iter() { - let skill = *attacker_item.total_skills.get(&scaling.skill).unwrap_or(&0.0); - if skill >= scaling.min_skill { - mean_damage += (skill - scaling.min_skill) * scaling.mean_damage_per_point_over_min; - } - } - - let actual_damage = Normal::new(mean_damage, - weapon.normal_attack_stdev_damage)? - .sample(&mut rand::thread_rng()).floor().max(1.0) as i64; - ctx.trans.save_item_model(&attacker_item).await?; - let msg_exp = weapon.normal_attack_success_message(&attacker_item, &victim_item, - &part, true); - let msg_nonexp = weapon.normal_attack_success_message(&attacker_item, &victim_item, - &part, false); - if change_health(ctx.trans, -actual_damage, &mut victim_item, - &msg_exp, &msg_nonexp).await? { - ctx.trans.save_item_model(&victim_item).await?; - return Ok(None); - } - ctx.trans.save_item_model(&victim_item).await?; + Ok(Some(attack_speed(&attacker_item))) } - - 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?; - Ok(Some(attack_speed(&attacker_item))) } } @@ -332,14 +430,20 @@ pub async fn stop_attacking_mut(trans: &DBTrans, new_by_whom: &mut Item, new_to_ if let Some((vtype, vcode)) = new_vic.split_once("/") { if let Some(vic_item) = trans.find_item_by_type_code(vtype, vcode).await? { let mut new_vic_item = (*vic_item).clone(); - start_attack_mut(trans, new_by_whom, &mut new_vic_item); + match start_attack_mut(trans, new_by_whom, &mut new_vic_item).await { + Err(CommandHandlingError::UserError(_)) | Ok(()) => {}, + Err(CommandHandlingError::SystemError(e)) => return Err(e), + } trans.save_item_model(&new_vic_item).await?; } } + } else { + new_by_whom.action_type = LocationActionType::Normal; } + } else { + new_by_whom.action_type = LocationActionType::Normal; } } - new_by_whom.action_type = LocationActionType::Normal; Ok(()) } @@ -430,8 +534,8 @@ pub async fn start_attack_mut(trans: &DBTrans, by_whom: &mut Item, to_whom: &mut ); 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")); + 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")); broadcast_to_room(trans, &by_whom.location, None::<&Item>, &msg_exp, Some(msg_nonexp.as_str())).await?; diff --git a/blastmud_game/src/static_content/npc.rs b/blastmud_game/src/static_content/npc.rs index 78e6db68..9c77101c 100644 --- a/blastmud_game/src/static_content/npc.rs +++ b/blastmud_game/src/static_content/npc.rs @@ -270,7 +270,7 @@ impl TaskHandler for NPCSayTaskHandler { Some(r) => r }; - if npc_item.death_data.is_none() { + if npc_item.death_data.is_some() { return Ok(None); } diff --git a/blastmud_game/src/static_content/possession_type.rs b/blastmud_game/src/static_content/possession_type.rs index 272748d2..ce7943ec 100644 --- a/blastmud_game/src/static_content/possession_type.rs +++ b/blastmud_game/src/static_content/possession_type.rs @@ -33,15 +33,56 @@ pub struct SkillScaling { pub mean_damage_per_point_over_min: f64 } +#[allow(unused)] +#[derive(Eq,Ord,Clone,PartialEq,PartialOrd)] +pub enum DamageType { + Beat, + Slash, + Pierce, + Shock, + Bullet +} + +pub struct WeaponAttackData { + pub start_messages: AttackMessageChoice, + pub success_messages: AttackMessageChoicePart, + pub mean_damage: f64, + pub stdev_damage: f64, + pub base_damage_type: DamageType, + pub other_damage_types: Vec<(f64, DamageType)>, // Allocation fractions. + pub skill_scaling: Vec, +} + +impl Default for WeaponAttackData { + fn default() -> Self { + Self { + start_messages: + vec!(Box::new(|attacker, victim, exp| format!( + "{} makes an attack on {}", + &attacker.display_for_sentence(exp, 1, true), + &victim.display_for_sentence(exp,1, false)))), + success_messages: + vec!(Box::new(|attacker, victim, part, exp| + format!("{}'s attack on {} hits {} {}", + &attacker.display_for_sentence(exp, 1, true), + &victim.display_for_sentence(exp, 1, false), + &victim.pronouns.possessive, + part.display(victim.sex.clone()) + ))), + mean_damage: 1.0, + stdev_damage: 2.0, + base_damage_type: DamageType::Slash, + other_damage_types: vec!(), + skill_scaling: vec!() + } + } +} + pub struct WeaponData { pub uses_skill: SkillType, pub raw_min_to_learn: f64, pub raw_max_to_learn: f64, - pub normal_attack_start_messages: AttackMessageChoice, - pub normal_attack_success_messages: AttackMessageChoicePart, - pub normal_attack_mean_damage: f64, - pub normal_attack_stdev_damage: f64, - pub normal_attack_skill_scaling: Vec, + pub normal_attack: WeaponAttackData, } impl Default for WeaponData { @@ -50,22 +91,7 @@ impl Default for WeaponData { uses_skill: SkillType::Blades, raw_min_to_learn: 0.0, raw_max_to_learn: 15.0, - normal_attack_start_messages: - vec!(Box::new(|attacker, victim, exp| format!( - "{} makes an attack on {}", - &attacker.display_for_sentence(exp, 1, true), - &victim.display_for_sentence(exp,1, false)))), - normal_attack_success_messages: - vec!(Box::new(|attacker, victim, part, exp| - format!("{}'s attack on {} hits {} {}", - &attacker.display_for_sentence(exp, 1, true), - &victim.display_for_sentence(exp, 1, false), - &victim.pronouns.possessive, - part.display(victim.sex.clone()) - ))), - normal_attack_mean_damage: 1.0, - normal_attack_stdev_damage: 2.0, - normal_attack_skill_scaling: vec!() + normal_attack: Default::default(), } } } @@ -120,9 +146,17 @@ impl Default for UseData { } } +pub struct SoakData { + pub min_soak: f64, + pub max_soak: f64, + pub damage_probability_per_soak: f64, +} + pub struct WearData { pub covers_parts: Vec, pub thickness: f64, + pub dodge_penalty: f64, + pub soaks: BTreeMap, } #[async_trait] @@ -185,22 +219,22 @@ impl Default for PossessionData { } } -impl WeaponData { - pub fn normal_attack_start_message( +impl WeaponAttackData { + pub fn start_message( &self, attacker: &Item, victim: &Item, explicit_ok: bool) -> String { let mut rng = rand::thread_rng(); - self.normal_attack_start_messages[..].choose(&mut rng).map( + self.start_messages[..].choose(&mut rng).map( |f| f(attacker, victim, explicit_ok)).unwrap_or( "No message defined yet".to_owned()) } - pub fn normal_attack_success_message( + pub fn success_message( &self, attacker: &Item, victim: &Item, part: &BodyPart, explicit_ok: bool ) -> String { let mut rng = rand::thread_rng(); - self.normal_attack_success_messages[..].choose(&mut rng).map( + self.success_messages[..].choose(&mut rng).map( |f| f(attacker, victim, part, explicit_ok)).unwrap_or( "No message defined yet".to_owned()) } @@ -268,24 +302,27 @@ pub fn fist() -> &'static WeaponData { uses_skill: SkillType::Fists, raw_min_to_learn: 0.0, raw_max_to_learn: 2.0, - normal_attack_start_messages: vec!( - Box::new(|attacker, victim, exp| - format!("{} swings at {} with {} fists", - &attacker.display_for_sentence(exp, 1, true), - &victim.display_for_sentence(exp, 1, false), - &attacker.pronouns.possessive - ) - ) - ), - normal_attack_success_messages: vec!( - Box::new(|attacker, victim, part, exp| - format!("{}'s fists smash into {}'s {}", - &attacker.display_for_sentence(exp, 1, true), - &victim.display_for_sentence(exp, 1, false), - &part.display(victim.sex.clone()) - ) - ) - ), + normal_attack: WeaponAttackData { + start_messages: vec!( + Box::new(|attacker, victim, exp| + format!("{} swings at {} with {} fists", + &attacker.display_for_sentence(exp, 1, true), + &victim.display_for_sentence(exp, 1, false), + &attacker.pronouns.possessive + ) + ) + ), + success_messages: vec!( + Box::new(|attacker, victim, part, exp| + format!("{}'s fists smash into {}'s {}", + &attacker.display_for_sentence(exp, 1, true), + &victim.display_for_sentence(exp, 1, false), + &part.display(victim.sex.clone()) + ) + ) + ), + ..Default::default() + }, ..Default::default() } }) @@ -319,3 +356,18 @@ pub fn can_butcher_possessions() -> &'static Vec { .collect() }) } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn other_damage_types_add_to_less_than_one() { + for (_pt, pd) in possession_data().iter() { + if let Some(weapon_data) = pd.weapon_data.as_ref() { + let tot: f64 = weapon_data.normal_attack.other_damage_types.iter().map(|v|v.0).sum(); + assert!(tot >= 0.0); + assert!(tot < 1.0); + } + } + } +} diff --git a/blastmud_game/src/static_content/possession_type/blade.rs b/blastmud_game/src/static_content/possession_type/blade.rs index 025904b2..71ecd0e8 100644 --- a/blastmud_game/src/static_content/possession_type/blade.rs +++ b/blastmud_game/src/static_content/possession_type/blade.rs @@ -1,4 +1,4 @@ -use super::{PossessionData, WeaponData, PossessionType}; +use super::{PossessionData, WeaponData, WeaponAttackData, PossessionType}; use crate::models::item::SkillType; use once_cell::sync::OnceCell; @@ -17,26 +17,29 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { uses_skill: SkillType::Blades, raw_min_to_learn: 0.0, raw_max_to_learn: 2.0, - normal_attack_start_messages: vec!( - Box::new(|attacker, victim, exp| - format!("{} raises {} butcher knife menancingly, preparing to attack {}", - &attacker.display_for_sentence(exp, 1, true), - &attacker.pronouns.possessive, - &victim.display_for_sentence(exp, 1, false), - ) - ) - ), - normal_attack_success_messages: vec!( - Box::new(|attacker, victim, part, exp| - format!("{}'s butcher knife cuts into {}'s {}", - &attacker.display_for_sentence(exp, 1, true), - &victim.display_for_sentence(exp, 1, false), - &part.display(victim.sex.clone()) - ) - ) - ), - normal_attack_mean_damage: 2.0, - normal_attack_stdev_damage: 2.0, + normal_attack: WeaponAttackData { + start_messages: vec!( + Box::new(|attacker, victim, exp| + format!("{} raises {} butcher knife menancingly, preparing to attack {}", + &attacker.display_for_sentence(exp, 1, true), + &attacker.pronouns.possessive, + &victim.display_for_sentence(exp, 1, false), + ) + ) + ), + success_messages: vec!( + Box::new(|attacker, victim, part, exp| + format!("{}'s butcher knife cuts into {}'s {}", + &attacker.display_for_sentence(exp, 1, true), + &victim.display_for_sentence(exp, 1, false), + &part.display(victim.sex.clone()) + ) + ) + ), + mean_damage: 2.0, + stdev_damage: 2.0, + ..Default::default() + }, ..Default::default() }), ..Default::default() diff --git a/blastmud_game/src/static_content/possession_type/fangs.rs b/blastmud_game/src/static_content/possession_type/fangs.rs index 1c88843f..10a373cb 100644 --- a/blastmud_game/src/static_content/possession_type/fangs.rs +++ b/blastmud_game/src/static_content/possession_type/fangs.rs @@ -1,4 +1,4 @@ -use super::{PossessionData, WeaponData}; +use super::{PossessionData, WeaponData, WeaponAttackData}; use crate::models::item::SkillType; use once_cell::sync::OnceCell; @@ -11,24 +11,27 @@ pub fn data() -> &'static PossessionData { uses_skill: SkillType::Fists, raw_min_to_learn: 0.0, raw_max_to_learn: 2.0, - normal_attack_start_messages: vec!( - Box::new(|attacker, victim, exp| - format!("{} bares {} teeth and lunges at {}", - &attacker.display_for_sentence(exp, 1, true), - &attacker.pronouns.possessive, - &victim.display_for_sentence(exp, 1, false), - ) - ) - ), - normal_attack_success_messages: vec!( - Box::new(|attacker, victim, part, exp| - format!("{}'s teeth connect and tear at the flesh of {}'s {}", - &attacker.display_for_sentence(exp, 1, true), - &victim.display_for_sentence(exp, 1, false), - &part.display(victim.sex.clone()) - ) - ) - ), + normal_attack: WeaponAttackData { + start_messages: vec!( + Box::new(|attacker, victim, exp| + format!("{} bares {} teeth and lunges at {}", + &attacker.display_for_sentence(exp, 1, true), + &attacker.pronouns.possessive, + &victim.display_for_sentence(exp, 1, false), + ) + ) + ), + success_messages: vec!( + Box::new(|attacker, victim, part, exp| + format!("{}'s teeth connect and tear at the flesh of {}'s {}", + &attacker.display_for_sentence(exp, 1, true), + &victim.display_for_sentence(exp, 1, false), + &part.display(victim.sex.clone()) + ) + ) + ), + ..Default::default() + }, ..Default::default() }), ..Default::default() diff --git a/blastmud_game/src/static_content/possession_type/head_armour.rs b/blastmud_game/src/static_content/possession_type/head_armour.rs index b4692908..20c1f1ab 100644 --- a/blastmud_game/src/static_content/possession_type/head_armour.rs +++ b/blastmud_game/src/static_content/possession_type/head_armour.rs @@ -1,4 +1,4 @@ -use super::{PossessionData, PossessionType, WearData}; +use super::{PossessionData, PossessionType, WearData, DamageType, SoakData}; use crate::static_content::species::BodyPart; use once_cell::sync::OnceCell; @@ -15,6 +15,30 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { wear_data: Some(WearData { covers_parts: vec!(BodyPart::Head), thickness: 4.0, + dodge_penalty: 0.25, + soaks: vec!( + (DamageType::Beat, + SoakData { + min_soak: 2.0, + max_soak: 3.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Slash, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Pierce, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + ).into_iter().collect(), }), ..Default::default() } @@ -28,6 +52,30 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { wear_data: Some(WearData { covers_parts: vec!(BodyPart::Face), thickness: 4.0, + dodge_penalty: 0.25, + soaks: vec!( + (DamageType::Beat, + SoakData { + min_soak: 2.0, + max_soak: 3.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Slash, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Pierce, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + ).into_iter().collect() }), ..Default::default() } diff --git a/blastmud_game/src/static_content/possession_type/lower_armour.rs b/blastmud_game/src/static_content/possession_type/lower_armour.rs index 39079a33..72f6b73d 100644 --- a/blastmud_game/src/static_content/possession_type/lower_armour.rs +++ b/blastmud_game/src/static_content/possession_type/lower_armour.rs @@ -1,4 +1,4 @@ -use super::{PossessionData, PossessionType, WearData}; +use super::{PossessionData, PossessionType, WearData, DamageType, SoakData}; use crate::static_content::species::BodyPart; use once_cell::sync::OnceCell; @@ -15,6 +15,30 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { wear_data: Some(WearData { covers_parts: vec!(BodyPart::Groin, BodyPart::Legs), thickness: 4.0, + dodge_penalty: 0.5, + soaks: vec!( + (DamageType::Beat, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Slash, + SoakData { + min_soak: 2.0, + max_soak: 3.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Pierce, + SoakData { + min_soak: 2.0, + max_soak: 3.0, + damage_probability_per_soak: 0.1 + } + ), + ).into_iter().collect(), }), ..Default::default() } diff --git a/blastmud_game/src/static_content/possession_type/torso_armour.rs b/blastmud_game/src/static_content/possession_type/torso_armour.rs index cab25f75..e88e6b8e 100644 --- a/blastmud_game/src/static_content/possession_type/torso_armour.rs +++ b/blastmud_game/src/static_content/possession_type/torso_armour.rs @@ -1,4 +1,4 @@ -use super::{PossessionData, PossessionType, WearData}; +use super::{PossessionData, PossessionType, WearData, DamageType, SoakData}; use crate::static_content::species::BodyPart; use once_cell::sync::OnceCell; @@ -17,6 +17,30 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { BodyPart::Chest, BodyPart::Back), thickness: 4.0, + dodge_penalty: 0.5, + soaks: vec!( + (DamageType::Beat, + SoakData { + min_soak: 1.0, + max_soak: 2.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Slash, + SoakData { + min_soak: 2.0, + max_soak: 3.0, + damage_probability_per_soak: 0.1 + } + ), + (DamageType::Pierce, + SoakData { + min_soak: 2.0, + max_soak: 3.0, + damage_probability_per_soak: 0.1 + } + ), + ).into_iter().collect(), }), ..Default::default() } diff --git a/blastmud_game/src/static_content/possession_type/whip.rs b/blastmud_game/src/static_content/possession_type/whip.rs index aed53f10..97b56034 100644 --- a/blastmud_game/src/static_content/possession_type/whip.rs +++ b/blastmud_game/src/static_content/possession_type/whip.rs @@ -1,4 +1,4 @@ -use super::{PossessionData, PossessionType, WeaponData}; +use super::{PossessionData, PossessionType, WeaponData, WeaponAttackData}; use crate::models::item::SkillType; use once_cell::sync::OnceCell; @@ -16,26 +16,29 @@ pub fn data() -> &'static Vec<(PossessionType, PossessionData)> { uses_skill: SkillType::Whips, raw_min_to_learn: 0.0, raw_max_to_learn: 2.0, - normal_attack_start_messages: vec!( - Box::new(|attacker, victim, exp| - format!("{} lines up {} antenna whip for a strike on {}", - &attacker.display_for_sentence(exp, 1, true), - &attacker.pronouns.possessive, - &victim.display_for_sentence(exp, 1, false), - ) - ) - ), - normal_attack_success_messages: vec!( - Box::new(|attacker, victim, part, exp| - format!("{}'s antenna whip scores a painful red line across {}'s {}", - &attacker.display_for_sentence(exp, 1, true), - &victim.display_for_sentence(exp, 1, false), - &part.display(victim.sex.clone()) - ) - ) - ), - normal_attack_mean_damage: 3.0, - normal_attack_stdev_damage: 3.0, + normal_attack: WeaponAttackData { + start_messages: vec!( + Box::new(|attacker, victim, exp| + format!("{} lines up {} antenna whip for a strike on {}", + &attacker.display_for_sentence(exp, 1, true), + &attacker.pronouns.possessive, + &victim.display_for_sentence(exp, 1, false), + ) + ) + ), + success_messages: vec!( + Box::new(|attacker, victim, part, exp| + format!("{}'s antenna whip scores a painful red line across {}'s {}", + &attacker.display_for_sentence(exp, 1, true), + &victim.display_for_sentence(exp, 1, false), + &part.display(victim.sex.clone()) + ) + ) + ), + mean_damage: 3.0, + stdev_damage: 3.0, + ..Default::default() + }, ..Default::default() }), ..Default::default()