added log! macro

This commit is contained in:
TotallyNot 2024-01-01 11:59:18 +01:00
parent 86f9333aec
commit b45d04b872
5 changed files with 140 additions and 522 deletions

View file

@ -180,8 +180,11 @@ impl Simulation {
#[cfg(feature = "json")]
pub fn read_log(&self) -> serde_json::Value {
use entity_registry::EntityRegistry;
let log = self.0.world.resource::<Log>();
log.to_value()
let registry = self.0.world.resource::<EntityRegistry>();
log.to_value(registry)
}
}

View file

@ -1,20 +1,18 @@
use std::{collections::HashMap, sync::Mutex};
use std::sync::Mutex;
use bevy_ecs::{prelude::*, system::SystemParam};
use macros::LogMessage;
use crate::{
entity_registry::EntityRegistry,
hierarchy::Children,
player::{
stats::{
AdditiveBonus, BaselineStat, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat,
MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus, SimpleStatEffective,
SimpleStatMarker, Speed, StatMarker, StatType, Strength, WeaponAccuracy,
},
Player,
player::stats::{
AdditiveBonus, BaselineStat, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat,
MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus, SimpleStatEffective,
SimpleStatMarker, Speed, StatMarker, StatType, Strength, WeaponAccuracy,
},
weapon::{Weapon, WeaponVerb},
Name, Stages,
weapon::WeaponVerb,
Stages,
};
#[derive(Resource)]
@ -49,6 +47,7 @@ pub enum LogValue<'a> {
Unsigned(u32),
Bool(bool),
String(String),
Entity(Entity),
OptionNone,
Display(&'a (dyn std::fmt::Display + Send + Sync)),
Debug(&'a (dyn std::fmt::Debug + Send + Sync)),
@ -86,6 +85,12 @@ impl From<bool> for LogValue<'static> {
}
}
impl From<Entity> for LogValue<'static> {
fn from(value: Entity) -> Self {
Self::Entity(value)
}
}
impl<'a, T> From<Option<T>> for LogValue<'a>
where
T: Into<LogValue<'a>>,
@ -100,11 +105,7 @@ where
#[cfg(feature = "json")]
impl<'a> LogValue<'a> {
fn to_value(
&self,
player_registry: &HashMap<Entity, PlayerInfo>,
weapon_registry: &HashMap<Entity, WeaponInfo>,
) -> serde_json::Value {
fn to_value(&self, entity_registry: &EntityRegistry) -> serde_json::Value {
match self {
LogValue::OptionNone => serde_json::Value::Null,
LogValue::Float(val) => {
@ -115,27 +116,20 @@ impl<'a> LogValue<'a> {
LogValue::Unsigned(val) => serde_json::Value::Number(serde_json::Number::from(*val)),
LogValue::Debug(boxed) => serde_json::Value::String(format!("{boxed:?}")),
LogValue::Display(boxed) => serde_json::Value::String(format!("{boxed}")),
LogValue::Player(id) => serde_json::json!({
"type": "player",
"name": player_registry.get(id).unwrap().name,
}),
LogValue::Weapon(id) => serde_json::json!({
"type": "weapon",
"name": weapon_registry.get(id).unwrap().name,
}),
LogValue::Player(id) => {
serde_json::to_value(entity_registry.0.get(id).unwrap()).unwrap()
}
LogValue::Weapon(id) => {
serde_json::to_value(entity_registry.0.get(id).unwrap()).unwrap()
}
LogValue::Entity(id) => {
serde_json::to_value(entity_registry.0.get(id).unwrap()).unwrap()
}
}
}
}
pub trait LogMessage: Send + Sync + 'static {
fn torn_style(
&self,
_player_registry: HashMap<Entity, PlayerInfo>,
_weapon_registry: HashMap<Entity, WeaponInfo>,
) -> Option<String> {
None
}
fn tag(&self) -> &'static str;
fn entries(&self) -> Vec<(&'static str, LogValue<'_>)>;
@ -143,9 +137,6 @@ pub trait LogMessage: Send + Sync + 'static {
#[derive(Resource, Default)]
pub struct Log {
pub player_registry: HashMap<Entity, PlayerInfo>,
pub weapon_registry: HashMap<Entity, WeaponInfo>,
pub entries: Vec<Box<dyn LogMessage>>,
pub expanded: bool,
@ -153,7 +144,7 @@ pub struct Log {
impl Log {
#[cfg(feature = "json")]
pub fn to_value(&self) -> serde_json::Value {
pub fn to_value(&self, entity_registry: &EntityRegistry) -> serde_json::Value {
use serde_json::json;
serde_json::json!({
@ -161,7 +152,7 @@ impl Log {
json!({
"type": e.tag(),
"values": serde_json::Value::Object(
e.entries().iter().map(|e| (e.0.to_owned(), e.1.to_value(&self.player_registry, &self.weapon_registry))).collect()
e.entries().iter().map(|e| (e.0.to_owned(), e.1.to_value(entity_registry))).collect()
)
})
).collect::<Vec<_>>()
@ -190,11 +181,8 @@ impl std::fmt::Display for Log {
LogValue::OptionNone => write!(f, "None")?,
LogValue::Display(val) => write!(f, "\"{val}\"")?,
LogValue::Debug(val) => write!(f, "\"{val:?}\"")?,
LogValue::Player(id) => {
write!(f, "\"{}\"", self.player_registry.get(&id).unwrap().name)?
}
LogValue::Weapon(id) => {
write!(f, "\"{}\"", self.weapon_registry.get(&id).unwrap().name)?
LogValue::Player(id) | LogValue::Weapon(id) | LogValue::Entity(id) => {
write!(f, "{:?}", id)?
}
};
@ -418,31 +406,6 @@ fn logging_enabled(logging: Res<Logging>) -> bool {
logging.0
}
fn register_entities(
player_q: Query<(Entity, &Name), With<Player>>,
weapon_q: Query<(Entity, &Name, &WeaponVerb), With<Weapon>>,
mut log: ResMut<Log>,
) {
for (player, name) in player_q.iter() {
log.player_registry.insert(
player,
PlayerInfo {
name: name.0.clone(),
},
);
}
for (weapon, name, verb) in weapon_q.iter() {
log.weapon_registry.insert(
weapon,
WeaponInfo {
name: name.0.clone(),
verb: *verb,
},
);
}
}
fn append_log_messages(mut events: EventReader<LogEvent>, mut log: ResMut<Log>) {
for event in events.read() {
log.entries.push(event.0.lock().unwrap().take().unwrap());
@ -541,13 +504,59 @@ fn log_simple_stat_changes<Stat: SimpleStatMarker>(
}
}
pub struct DynamicLogMessage {
pub label: &'static str,
pub entries: Vec<(&'static str, LogValue<'static>)>,
}
impl LogMessage for DynamicLogMessage {
fn tag(&self) -> &'static str {
self.label
}
fn entries(&self) -> Vec<(&'static str, LogValue<'_>)> {
self.entries.clone()
}
}
#[macro_export]
macro_rules! log {
($logger:expr, $tag:literal, { $($fields:tt)* }) => {
$logger.log(|| $crate::log_message!($tag, { $($fields)* }))
};
}
#[macro_export]
macro_rules! log_message {
($tag:literal, { $($fields:tt)* }) => {
$crate::log::DynamicLogMessage { label: $tag, entries: $crate::log_values!($($fields)*) }
};
}
#[macro_export]
macro_rules! log_values {
(@ { $(,)* $($out:expr),* $(,)* } $(,)*) => {
vec![$($out),*]
};
(@ { $(,)* $($out:expr),* } $label:ident: $val:expr, $($rest:tt)*) => {
$crate::log_values!(@ { $($out),*, (stringify!($label),$val.into()) } $($rest)*)
};
(@ { $(,)* $($out:expr),* } $label:ident: ?$val:expr, $($rest:tt)*) => {
$crate::log_values!(@ { $($out),*, (stringify!($label),$crate::log::LogValue::String(format!("{:?}",$val))) } $($rest)*)
};
(@ { $(,)* $($out:expr),* } $label:ident: %$val:expr, $($rest:tt)*) => {
$crate::log_values!(@ { $($out),*, (stringify!($label),$crate::log::LogValue::String(format!("{}",$val))) } $($rest)*)
};
($($args:tt)* ) => {
$crate::log_values!(@ { } $($args)*,)
};
}
pub(crate) fn configure(stages: &mut Stages) {
stages.world.insert_resource(Log::default());
stages.world.insert_resource(Logging(false));
stages.add_event::<LogEvent>();
stages
.equip
.add_systems(register_entities.run_if(logging_enabled));
stages.post_turn.add_systems(append_log_messages);
stages.turn.add_systems(
@ -563,3 +572,22 @@ pub(crate) fn configure(stages: &mut Stages) {
.run_if(logging_enabled),
);
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn log_keys() {
let message = log_message!("test", { foo: 12u32, bar: ?17 });
assert_eq!(message.label, "test");
let mut entries = message.entries.into_iter();
let next = entries.next();
assert!(matches!(next, Some(("foo", LogValue::Unsigned(12)))));
let next = entries.next();
assert!(matches!(next, Some(("bar", LogValue::String(val))) if val == "17"));
}
}

View file

@ -1,5 +1,4 @@
use bevy_ecs::prelude::*;
use macros::LogMessage;
use rand::Rng as _;
use strum::Display;
@ -7,6 +6,7 @@ use crate::{
armour,
effect::Effects,
hierarchy::Children,
log,
log::Logger,
metrics::Metrics,
passives::{Education, FactionUpgrades, Merits},
@ -165,18 +165,6 @@ impl std::fmt::Display for BodyPart {
}
}
#[derive(Event)]
pub struct InitiateHit {
pub body_part: BodyPart,
pub weapon: Entity,
pub rounds: Option<u16>,
pub dmg: f32,
pub dmg_bonus_weapon: f32,
pub dmg_bonus_player: f32,
pub hit_chance: f32,
pub crit_rate: u16,
}
#[derive(Clone, Copy, PartialEq, Eq, Display)]
pub enum FightEndType {
Victory,
@ -184,16 +172,6 @@ pub enum FightEndType {
Loss,
}
#[derive(LogMessage)]
struct FightEnd {
#[log(player)]
actor: Entity,
#[log(player)]
recipient: Entity,
#[log(display)]
fight_end_type: FightEndType,
}
#[derive(Bundle)]
pub struct PlayerBundle {
pub name: Name,
@ -338,164 +316,6 @@ impl FromWorld for DamageSpread {
}
}
fn receive_hit(
(mut rng, spread): (ResMut<crate::Rng>, Local<DamageSpread>),
mut hit_init_events: EventReader<InitiateHit>,
current_q: Query<
(
Entity,
&Education,
Option<&Attacker>,
&EffectiveStat<Strength>,
),
(With<Current>, With<Player>),
>,
mut target_q: Query<
(
Entity,
&mut SimpleStatEffective<Health>,
&armour::ArmourBodyParts,
&EffectiveStat<Defence>,
),
With<CurrentTarget>,
>,
armour_q: Query<&armour::ArmourBodyPart>,
(mut commands, mut logger): (Commands, Logger),
metrics: Res<Metrics>,
) {
#[derive(LogMessage)]
struct HitTarget {
#[log(player)]
actor: Entity,
#[log(player)]
recipient: Entity,
#[log(weapon)]
weapon: Entity,
#[log(display)]
part: BodyPart,
part_mult: f32,
rounds: Option<u16>,
dmg: u32,
health_before: u16,
health_after: u16,
dmg_intrinsic: f32,
dmg_spread: f32,
armour_mitigation: f32,
def_mitigation: f32,
weapon_dmg: f32,
bonus_dmg: f32,
hit_chance: f32,
crit_rate: u16,
}
if hit_init_events.is_empty() {
return;
}
let (target, mut health, body_parts, target_def) = target_q.single_mut();
let (current, edu, attacker, current_str) = current_q.single();
let def_str_ratio = (target_def.value / current_str.value).clamp(1.0 / 32.0, 14.0);
let def_mitigation = if def_str_ratio < 1.0 {
0.5 * def_str_ratio.log(32.0) + 0.5
} else {
0.5 * def_str_ratio.log(14.0) + 0.5
};
let dmg_intrinsic = 7.0 * (current_str.value / 10.0).log10().powi(2)
+ 27.0 * (current_str.value / 10.0).log10()
+ 30.0;
for event in hit_init_events.read() {
let mult = match event.body_part {
BodyPart::Head | BodyPart::Heart | BodyPart::Throat => {
metrics.increment_counter(current, "crit", 1);
metrics.increment_counter(event.weapon, "crit", 1);
1.0
}
BodyPart::LeftHand | BodyPart::RightHand | BodyPart::LeftFoot | BodyPart::RightFoot => {
0.2
}
BodyPart::LeftArm | BodyPart::RightArm | BodyPart::LeftLeg | BodyPart::RightLeg => {
1.0 / 3.5
}
BodyPart::Groin | BodyPart::Stomach | BodyPart::Chest => 1.0 / 1.75,
};
metrics.increment_counter(current, "hit", 1);
metrics.increment_counter(event.weapon, "hit", 1);
let armour_parts = armour_q.get(body_parts.0[event.body_part.into()]).unwrap();
let piece = rng.sample(armour_parts);
let armour_mitigation = piece.map_or(0.0, |p| p.armour_value);
// NOTE: The beta distribution is defined on [0,1], so we rescale here
let dmg_spread = rng.sample(spread.0) / 10.0 + 1.0;
let mut dmg_bonus = event.dmg_bonus_weapon + event.dmg_bonus_player;
if edu.bio2380 && event.body_part == BodyPart::Throat {
dmg_bonus += 0.10;
}
let dmg = dmg_intrinsic
* event.dmg
* dmg_bonus
* (1.0 - armour_mitigation)
* (1.0 - def_mitigation)
* mult
* dmg_spread;
let dmg = dmg.round() as u32;
metrics.record_histogram(current, "dmg", dmg);
metrics.record_histogram(event.weapon, "dmg", dmg);
let health_before = health.value;
health.value = health.value.saturating_sub(dmg as u16);
logger.log(|| HitTarget {
actor: current,
recipient: target,
weapon: event.weapon,
part: event.body_part,
part_mult: mult,
rounds: event.rounds,
dmg,
health_before,
health_after: health.value,
dmg_spread,
dmg_intrinsic,
armour_mitigation,
def_mitigation,
weapon_dmg: event.dmg,
bonus_dmg: dmg_bonus,
hit_chance: event.hit_chance,
crit_rate: event.crit_rate,
});
if health.value == 0 {
commands.entity(target).insert(Defeated);
logger.log(|| FightEnd {
actor: current,
recipient: target,
fight_end_type: if attacker.is_some() {
FightEndType::Victory
} else {
FightEndType::Loss
},
});
metrics.increment_counter(current, "victory", 1);
}
}
}
// NOTE: unfortunately this function can't really be split into smaller parts due to the existence
// of multi turn bonuses
#[allow(clippy::too_many_arguments)]
@ -554,48 +374,6 @@ pub fn use_damaging_weapon(
Res<Metrics>,
),
) {
#[derive(LogMessage)]
pub struct MissTarget {
#[log(player)]
pub actor: Entity,
#[log(player)]
pub recipient: Entity,
#[log(weapon)]
pub weapon: Entity,
pub rounds: Option<u16>,
}
#[derive(LogMessage)]
struct HitTarget {
#[log(player)]
actor: Entity,
#[log(player)]
recipient: Entity,
#[log(weapon)]
weapon: Entity,
#[log(display)]
part: BodyPart,
part_mult: f32,
rounds: Option<u16>,
dmg: u32,
health_before: u16,
health_after: u16,
dmg_intrinsic: f32,
dmg_spread: f32,
armour_mitigation: f32,
def_mitigation: f32,
weapon_dmg: f32,
bonus_dmg: f32,
hit_chance: f32,
crit_rate: u16,
}
let Ok((weapon, w_dmg, acc, dmg_bonus, crit, children, non_targeted)) = weapon_q.get_single()
else {
return;
@ -674,11 +452,11 @@ pub fn use_damaging_weapon(
};
if hit_chance <= 1.0 && !rng.gen_bool(hit_chance as f64) {
logger.log(|| MissTarget {
weapon,
log!(logger, "miss_target", {
weapon: weapon,
actor: player,
recipient: target,
rounds,
rounds: rounds,
});
metrics.increment_counter(player, "miss", 1);
metrics.increment_counter(weapon, "miss", 1);
@ -745,7 +523,7 @@ pub fn use_damaging_weapon(
DamageProcEffect::MultiTurn { value, bonus }
if multi_attack_proc.is_none() =>
{
if rng.gen_bool(*value as f64) {
if rng.gen_bool((*value / 100.0) as f64) {
match bonus {
MultiTurnBonus::Blindfire => {
multi_attack_proc = Some(MultiAttack::Blindfire)
@ -763,6 +541,8 @@ pub fn use_damaging_weapon(
Some(MultiAttack::DoubleTap { first_shot: true })
}
};
metrics.increment_counter(player, bonus.counter_label(), 1);
metrics.increment_counter(weapon, bonus.counter_label(), 1);
}
}
_ => (),
@ -774,23 +554,22 @@ pub fn use_damaging_weapon(
health.value = health.value.saturating_sub(dmg as u16);
logger.log(|| HitTarget {
log!(logger, "hit_target", {
actor: player,
recipient: target,
weapon,
part: body_part,
weapon: weapon,
part: %body_part,
part_mult: mult,
rounds,
dmg,
health_before,
dmg: dmg,
health_before: health_before,
health_after: health.value,
dmg_spread,
dmg_intrinsic,
armour_mitigation,
def_mitigation,
dmg_spread: dmg_spread,
dmg_intrinsic: dmg_intrinsic,
armour_mitigation: armour_mitigation,
def_mitigation: def_mitigation,
weapon_dmg: w_dmg.0,
bonus_dmg: dmg_bonus.value,
hit_chance,
hit_chance: hit_chance,
crit_rate: crit.value,
});
@ -798,14 +577,14 @@ pub fn use_damaging_weapon(
defeated = true;
commands.entity(target).insert(Defeated);
logger.log(|| FightEnd {
log!(logger, "fight_end", {
actor: player,
recipient: target,
fight_end_type: if attacker {
fight_end_type: %if attacker {
FightEndType::Victory
} else {
FightEndType::Loss
},
}
});
metrics.increment_counter(player, "victory", 1);
}
@ -846,12 +625,12 @@ pub fn check_stalemate(
if *state == FightStatus::Ongoing && current_turns.0 >= 25 && attacker.is_some() {
commands.entity(current).insert(Defeated);
let target = target_q.single();
logger.log(|| FightEnd {
log!(logger, "fight_end", {
actor: current,
recipient: target,
fight_end_type: FightEndType::Stalemate,
fight_end_type: %FightEndType::Stalemate
});
metrics.increment_counter(current, "stalemate", 1);
if other_attackers_q.is_empty() {
@ -898,7 +677,6 @@ pub(crate) fn configure(stages: &mut Stages) {
status_effect::configure(stages);
stages.add_event::<ChooseWeapon>();
stages.add_event::<InitiateHit>();
stages.equip.add_systems(designate_first);
stages.pre_fight.add_systems(derive_max_health);
stages.pre_turn.add_systems(pick_action);

View file

@ -143,6 +143,18 @@ pub enum MultiTurnBonus {
Rage,
}
impl MultiTurnBonus {
#[inline(always)]
pub fn counter_label(self) -> &'static str {
match self {
Self::Blindfire => "proc_blindfire",
Self::Fury => "proc_fury",
Self::DoubleTap => "proc_double_tap",
Self::Rage => "proc_rage",
}
}
}
pub(crate) fn prepare_bonuses(
bonus_q: Query<(
&Parent,

View file

@ -1,28 +1,22 @@
use bevy_ecs::prelude::*;
use macros::LogMessage;
use rand::Rng as _;
use crate::{
effect::{Effects, TurnLimitedEffect},
hierarchy::{HierarchyBuilder, Parent},
log::Logger,
metrics::Metrics,
passives::{Education, FactionUpgrades, Merits},
player::{
stats::{
AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity,
EffectiveStat, SimpleStatBonus, SimpleStatBundle, SimpleStatEffective, Speed,
WeaponAccuracy,
SimpleStatBonus, SimpleStatBundle, SimpleStatEffective, WeaponAccuracy,
},
BodyPart, Current, CurrentTarget, InitiateHit, Player, Weapons,
Current, Weapons,
},
Id, Name, Rng, Stages,
Id, Name, Stages,
};
use self::{
bonus::{FirstTurnBonus, MultiTurnBonus, TurnTriggeredBonus},
temp::{NonTargeted, Uses},
};
use self::bonus::{FirstTurnBonus, MultiTurnBonus, TurnTriggeredBonus};
pub mod bonus;
pub mod temp;
@ -685,203 +679,6 @@ fn unset_current(weapon_q: Query<Entity, (With<Current>, With<Weapon>)>, mut com
}
}
// TODO: Move all mitigation aspects of this into the player system
pub fn use_damaging_weapon(
mut rng: ResMut<Rng>,
weapon_q: Query<
(
Entity,
&DamageStat,
&SimpleStatEffective<WeaponAccuracy>,
&SimpleStatEffective<DamageBonus>,
&SimpleStatEffective<CritRate>,
Has<NonTargeted>,
),
(With<Weapon>, With<Current>, Without<NeedsReload>),
>,
player_q: Query<
(
Entity,
&EffectiveStat<Speed>,
&SimpleStatEffective<CritRate>,
&SimpleStatEffective<WeaponAccuracy>,
&SimpleStatEffective<DamageBonus>,
),
(With<Player>, With<Current>),
>,
target_q: Query<(Entity, &EffectiveStat<Dexterity>), With<CurrentTarget>>,
(mut ammo_q, mut temp_q): (
Query<(
&mut Ammo,
&SimpleStatEffective<Clips>,
&RateOfFire,
&SimpleStatEffective<AmmoControl>,
)>,
Query<&mut Uses>,
),
mut hit_events: EventWriter<InitiateHit>,
(mut logger, mut commands, metrics): (Logger, Commands, Res<Metrics>),
) {
let Ok((weapon, dmg, acc, dmg_bonus, crit, non_targeted)) = weapon_q.get_single() else {
return;
};
let (player, player_spd, player_crit, acc_bonus, p_dmg_bonus) = player_q.single();
let (target, target_dex) = target_q.single();
if let Ok(mut uses) = temp_q.get_mut(weapon) {
uses.0 -= 1;
if uses.0 == 0 {
commands.entity(weapon).remove::<Usable>();
}
}
let spd_dex_ratio = (player_spd.value / target_dex.value).clamp(1.0 / 64.0, 64.0);
let base_hit_chance = if spd_dex_ratio < 1.0 {
0.5 * (8.0 * spd_dex_ratio.sqrt() - 1.0) / 7.0
} else {
1.0 - 0.5 * (8.0 / spd_dex_ratio.sqrt() - 1.0) / 7.0
};
let mut acc_eff = acc + acc_bonus;
let mut ammo = ammo_q
.get_mut(weapon)
.ok()
.map(|(ammo, clips, rof, ammo_ctrl)| {
let ammo_ctrl = 1.0 - (ammo_ctrl).value;
let rof_eff = ((rof.0[0] as f32) * ammo_ctrl)..((rof.0[1] as f32) * ammo_ctrl);
(ammo, clips, rof_eff)
});
enum MultiAttack {
Blindfire,
Rage(usize),
Fury(usize),
DoubleTap { fired_first: bool },
}
let mut multi_attack_proc = None;
let crit = player_crit + crit;
loop {
let rounds = ammo.as_mut().map(|(ref mut ammo, clips, rof)| {
let rounds = (rng.gen_range(rof.clone()).round() as u16).clamp(1, ammo.0);
metrics.increment_counter(player, "rounds_fired", rounds.into());
metrics.increment_counter(weapon, "rounds_fired", rounds.into());
ammo.0 -= rounds;
if ammo.0 == 0 {
if clips.value == 0 {
commands.entity(weapon).remove::<Usable>();
} else {
commands.entity(weapon).insert(NeedsReload);
}
}
rounds
});
let hit_chance = if base_hit_chance < 0.5 {
base_hit_chance + acc_eff.value * base_hit_chance
} else {
base_hit_chance + acc_eff.value * (1.0 - base_hit_chance)
};
if hit_chance <= 1.0 && !rng.gen_bool(hit_chance as f64) {
logger.log(|| MissTarget {
weapon,
actor: player,
recipient: target,
rounds,
});
metrics.increment_counter(player, "miss", 1);
metrics.increment_counter(weapon, "miss", 1);
if multi_attack_proc.is_none() {
return;
};
} else {
let body_part = if !non_targeted {
rng.sample(crit)
} else {
BodyPart::Stomach
};
hit_events.send(InitiateHit {
body_part,
weapon,
rounds,
dmg: dmg.0,
dmg_bonus_weapon: dmg_bonus.value,
dmg_bonus_player: p_dmg_bonus.value,
hit_chance,
crit_rate: crit.value,
});
}
match multi_attack_proc {
Some(MultiAttack::Blindfire) => acc_eff.value -= 5.0 / 50.0,
Some(MultiAttack::Rage(turns @ 1..)) => {
multi_attack_proc = Some(MultiAttack::Rage(turns - 1))
}
Some(MultiAttack::Fury(turns @ 1..)) => {
multi_attack_proc = Some(MultiAttack::Fury(turns - 1))
}
Some(MultiAttack::DoubleTap { fired_first: false }) => {
multi_attack_proc = Some(MultiAttack::DoubleTap { fired_first: true })
}
_ => break,
}
}
/*
let hit_chance = if base_hit_chance < 0.5 {
base_hit_chance + acc_eff.value * base_hit_chance
} else {
base_hit_chance + acc_eff.value * (1.0 - base_hit_chance)
};
if hit_chance <= 1.0 && !rng.gen_bool(hit_chance as f64) {
logger.log(|| MissTarget {
weapon,
actor: player,
recipient: target,
rounds: rounds.map(|(rounds, _)| rounds),
});
metrics.increment_counter(player, "miss", 1);
metrics.increment_counter(weapon, "miss", 1);
return;
}
let crit = player_crit + crit;
let body_part = if !non_targeted {
rng.sample(crit)
} else {
BodyPart::Stomach
};
let def_str_ratio = (target_def.value / player_str.value).clamp(1.0 / 32.0, 14.0);
let mitigation = if def_str_ratio < 1.0 {
0.5 * def_str_ratio.log(32.0) + 0.5
} else {
0.5 * def_str_ratio.log(14.0) + 0.5
};
let dmg_intrinsic = 7.0 * (player_str.value / 10.0).log10().powi(2)
+ 27.0 * (player_str.value / 10.0).log10()
+ 30.0;
init_hit.send(InitiateHit {
body_part,
weapon,
rounds: rounds.map(|(rounds, _)| rounds),
crit_rate: crit.value,
dmg: dmg.0,
dmg_bonus_weapon: (dmg_bonus + p_dmg_bonus).value,
dmg_intrinsic,
def_mitigation: mitigation,
hit_chance,
}); */
}
fn reload_weapon(
mut weapon_q: Query<
(