Implement soaks
Also fix some minor combat bugs found, including the refocusing bug, and the dead NPCs talking (mk II) bug.
This commit is contained in:
parent
2747dddd90
commit
c78dc64a7f
@ -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<f64> {
|
||||
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::<f64>()));
|
||||
|
||||
let mut clothes: Vec<Item> =
|
||||
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::<f64>()).min(damage_amount);
|
||||
damage_amount -= soak_amount;
|
||||
let clothes_damage = ((0..(soak_amount as i64))
|
||||
.filter(|_| rand::thread_rng().gen::<f64>() <
|
||||
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<bool> {
|
||||
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?;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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<SkillScaling>,
|
||||
}
|
||||
|
||||
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<SkillScaling>,
|
||||
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<BodyPart>,
|
||||
pub thickness: f64,
|
||||
pub dodge_penalty: f64,
|
||||
pub soaks: BTreeMap<DamageType, SoakData>,
|
||||
}
|
||||
|
||||
#[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<PossessionType> {
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user