added log! macro
This commit is contained in:
parent
86f9333aec
commit
b45d04b872
5 changed files with 140 additions and 522 deletions
|
|
@ -180,8 +180,11 @@ impl Simulation {
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
pub fn read_log(&self) -> serde_json::Value {
|
pub fn read_log(&self) -> serde_json::Value {
|
||||||
|
use entity_registry::EntityRegistry;
|
||||||
|
|
||||||
let log = self.0.world.resource::<Log>();
|
let log = self.0.world.resource::<Log>();
|
||||||
log.to_value()
|
let registry = self.0.world.resource::<EntityRegistry>();
|
||||||
|
log.to_value(registry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
166
src/log.rs
166
src/log.rs
|
|
@ -1,20 +1,18 @@
|
||||||
use std::{collections::HashMap, sync::Mutex};
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use bevy_ecs::{prelude::*, system::SystemParam};
|
use bevy_ecs::{prelude::*, system::SystemParam};
|
||||||
use macros::LogMessage;
|
use macros::LogMessage;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
entity_registry::EntityRegistry,
|
||||||
hierarchy::Children,
|
hierarchy::Children,
|
||||||
player::{
|
player::stats::{
|
||||||
stats::{
|
AdditiveBonus, BaselineStat, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat,
|
||||||
AdditiveBonus, BaselineStat, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat,
|
MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus, SimpleStatEffective,
|
||||||
MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus, SimpleStatEffective,
|
SimpleStatMarker, Speed, StatMarker, StatType, Strength, WeaponAccuracy,
|
||||||
SimpleStatMarker, Speed, StatMarker, StatType, Strength, WeaponAccuracy,
|
|
||||||
},
|
|
||||||
Player,
|
|
||||||
},
|
},
|
||||||
weapon::{Weapon, WeaponVerb},
|
weapon::WeaponVerb,
|
||||||
Name, Stages,
|
Stages,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
|
|
@ -49,6 +47,7 @@ pub enum LogValue<'a> {
|
||||||
Unsigned(u32),
|
Unsigned(u32),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
String(String),
|
String(String),
|
||||||
|
Entity(Entity),
|
||||||
OptionNone,
|
OptionNone,
|
||||||
Display(&'a (dyn std::fmt::Display + Send + Sync)),
|
Display(&'a (dyn std::fmt::Display + Send + Sync)),
|
||||||
Debug(&'a (dyn std::fmt::Debug + 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>
|
impl<'a, T> From<Option<T>> for LogValue<'a>
|
||||||
where
|
where
|
||||||
T: Into<LogValue<'a>>,
|
T: Into<LogValue<'a>>,
|
||||||
|
|
@ -100,11 +105,7 @@ where
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
impl<'a> LogValue<'a> {
|
impl<'a> LogValue<'a> {
|
||||||
fn to_value(
|
fn to_value(&self, entity_registry: &EntityRegistry) -> serde_json::Value {
|
||||||
&self,
|
|
||||||
player_registry: &HashMap<Entity, PlayerInfo>,
|
|
||||||
weapon_registry: &HashMap<Entity, WeaponInfo>,
|
|
||||||
) -> serde_json::Value {
|
|
||||||
match self {
|
match self {
|
||||||
LogValue::OptionNone => serde_json::Value::Null,
|
LogValue::OptionNone => serde_json::Value::Null,
|
||||||
LogValue::Float(val) => {
|
LogValue::Float(val) => {
|
||||||
|
|
@ -115,27 +116,20 @@ impl<'a> LogValue<'a> {
|
||||||
LogValue::Unsigned(val) => serde_json::Value::Number(serde_json::Number::from(*val)),
|
LogValue::Unsigned(val) => serde_json::Value::Number(serde_json::Number::from(*val)),
|
||||||
LogValue::Debug(boxed) => serde_json::Value::String(format!("{boxed:?}")),
|
LogValue::Debug(boxed) => serde_json::Value::String(format!("{boxed:?}")),
|
||||||
LogValue::Display(boxed) => serde_json::Value::String(format!("{boxed}")),
|
LogValue::Display(boxed) => serde_json::Value::String(format!("{boxed}")),
|
||||||
LogValue::Player(id) => serde_json::json!({
|
LogValue::Player(id) => {
|
||||||
"type": "player",
|
serde_json::to_value(entity_registry.0.get(id).unwrap()).unwrap()
|
||||||
"name": player_registry.get(id).unwrap().name,
|
}
|
||||||
}),
|
LogValue::Weapon(id) => {
|
||||||
LogValue::Weapon(id) => serde_json::json!({
|
serde_json::to_value(entity_registry.0.get(id).unwrap()).unwrap()
|
||||||
"type": "weapon",
|
}
|
||||||
"name": weapon_registry.get(id).unwrap().name,
|
LogValue::Entity(id) => {
|
||||||
}),
|
serde_json::to_value(entity_registry.0.get(id).unwrap()).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LogMessage: Send + Sync + 'static {
|
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 tag(&self) -> &'static str;
|
||||||
|
|
||||||
fn entries(&self) -> Vec<(&'static str, LogValue<'_>)>;
|
fn entries(&self) -> Vec<(&'static str, LogValue<'_>)>;
|
||||||
|
|
@ -143,9 +137,6 @@ pub trait LogMessage: Send + Sync + 'static {
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
pub struct Log {
|
pub struct Log {
|
||||||
pub player_registry: HashMap<Entity, PlayerInfo>,
|
|
||||||
pub weapon_registry: HashMap<Entity, WeaponInfo>,
|
|
||||||
|
|
||||||
pub entries: Vec<Box<dyn LogMessage>>,
|
pub entries: Vec<Box<dyn LogMessage>>,
|
||||||
|
|
||||||
pub expanded: bool,
|
pub expanded: bool,
|
||||||
|
|
@ -153,7 +144,7 @@ pub struct Log {
|
||||||
|
|
||||||
impl Log {
|
impl Log {
|
||||||
#[cfg(feature = "json")]
|
#[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;
|
use serde_json::json;
|
||||||
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
|
|
@ -161,7 +152,7 @@ impl Log {
|
||||||
json!({
|
json!({
|
||||||
"type": e.tag(),
|
"type": e.tag(),
|
||||||
"values": serde_json::Value::Object(
|
"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<_>>()
|
).collect::<Vec<_>>()
|
||||||
|
|
@ -190,11 +181,8 @@ impl std::fmt::Display for Log {
|
||||||
LogValue::OptionNone => write!(f, "None")?,
|
LogValue::OptionNone => write!(f, "None")?,
|
||||||
LogValue::Display(val) => write!(f, "\"{val}\"")?,
|
LogValue::Display(val) => write!(f, "\"{val}\"")?,
|
||||||
LogValue::Debug(val) => write!(f, "\"{val:?}\"")?,
|
LogValue::Debug(val) => write!(f, "\"{val:?}\"")?,
|
||||||
LogValue::Player(id) => {
|
LogValue::Player(id) | LogValue::Weapon(id) | LogValue::Entity(id) => {
|
||||||
write!(f, "\"{}\"", self.player_registry.get(&id).unwrap().name)?
|
write!(f, "{:?}", id)?
|
||||||
}
|
|
||||||
LogValue::Weapon(id) => {
|
|
||||||
write!(f, "\"{}\"", self.weapon_registry.get(&id).unwrap().name)?
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -418,31 +406,6 @@ fn logging_enabled(logging: Res<Logging>) -> bool {
|
||||||
logging.0
|
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>) {
|
fn append_log_messages(mut events: EventReader<LogEvent>, mut log: ResMut<Log>) {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
log.entries.push(event.0.lock().unwrap().take().unwrap());
|
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) {
|
pub(crate) fn configure(stages: &mut Stages) {
|
||||||
stages.world.insert_resource(Log::default());
|
stages.world.insert_resource(Log::default());
|
||||||
stages.world.insert_resource(Logging(false));
|
stages.world.insert_resource(Logging(false));
|
||||||
stages.add_event::<LogEvent>();
|
stages.add_event::<LogEvent>();
|
||||||
stages
|
|
||||||
.equip
|
|
||||||
.add_systems(register_entities.run_if(logging_enabled));
|
|
||||||
|
|
||||||
stages.post_turn.add_systems(append_log_messages);
|
stages.post_turn.add_systems(append_log_messages);
|
||||||
stages.turn.add_systems(
|
stages.turn.add_systems(
|
||||||
|
|
@ -563,3 +572,22 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
.run_if(logging_enabled),
|
.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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use macros::LogMessage;
|
|
||||||
use rand::Rng as _;
|
use rand::Rng as _;
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
|
|
||||||
|
|
@ -7,6 +6,7 @@ use crate::{
|
||||||
armour,
|
armour,
|
||||||
effect::Effects,
|
effect::Effects,
|
||||||
hierarchy::Children,
|
hierarchy::Children,
|
||||||
|
log,
|
||||||
log::Logger,
|
log::Logger,
|
||||||
metrics::Metrics,
|
metrics::Metrics,
|
||||||
passives::{Education, FactionUpgrades, Merits},
|
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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Display)]
|
||||||
pub enum FightEndType {
|
pub enum FightEndType {
|
||||||
Victory,
|
Victory,
|
||||||
|
|
@ -184,16 +172,6 @@ pub enum FightEndType {
|
||||||
Loss,
|
Loss,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(LogMessage)]
|
|
||||||
struct FightEnd {
|
|
||||||
#[log(player)]
|
|
||||||
actor: Entity,
|
|
||||||
#[log(player)]
|
|
||||||
recipient: Entity,
|
|
||||||
#[log(display)]
|
|
||||||
fight_end_type: FightEndType,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
pub struct PlayerBundle {
|
pub struct PlayerBundle {
|
||||||
pub name: Name,
|
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
|
// NOTE: unfortunately this function can't really be split into smaller parts due to the existence
|
||||||
// of multi turn bonuses
|
// of multi turn bonuses
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|
@ -554,48 +374,6 @@ pub fn use_damaging_weapon(
|
||||||
Res<Metrics>,
|
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()
|
let Ok((weapon, w_dmg, acc, dmg_bonus, crit, children, non_targeted)) = weapon_q.get_single()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -674,11 +452,11 @@ pub fn use_damaging_weapon(
|
||||||
};
|
};
|
||||||
|
|
||||||
if hit_chance <= 1.0 && !rng.gen_bool(hit_chance as f64) {
|
if hit_chance <= 1.0 && !rng.gen_bool(hit_chance as f64) {
|
||||||
logger.log(|| MissTarget {
|
log!(logger, "miss_target", {
|
||||||
weapon,
|
weapon: weapon,
|
||||||
actor: player,
|
actor: player,
|
||||||
recipient: target,
|
recipient: target,
|
||||||
rounds,
|
rounds: rounds,
|
||||||
});
|
});
|
||||||
metrics.increment_counter(player, "miss", 1);
|
metrics.increment_counter(player, "miss", 1);
|
||||||
metrics.increment_counter(weapon, "miss", 1);
|
metrics.increment_counter(weapon, "miss", 1);
|
||||||
|
|
@ -745,7 +523,7 @@ pub fn use_damaging_weapon(
|
||||||
DamageProcEffect::MultiTurn { value, bonus }
|
DamageProcEffect::MultiTurn { value, bonus }
|
||||||
if multi_attack_proc.is_none() =>
|
if multi_attack_proc.is_none() =>
|
||||||
{
|
{
|
||||||
if rng.gen_bool(*value as f64) {
|
if rng.gen_bool((*value / 100.0) as f64) {
|
||||||
match bonus {
|
match bonus {
|
||||||
MultiTurnBonus::Blindfire => {
|
MultiTurnBonus::Blindfire => {
|
||||||
multi_attack_proc = Some(MultiAttack::Blindfire)
|
multi_attack_proc = Some(MultiAttack::Blindfire)
|
||||||
|
|
@ -763,6 +541,8 @@ pub fn use_damaging_weapon(
|
||||||
Some(MultiAttack::DoubleTap { first_shot: true })
|
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);
|
health.value = health.value.saturating_sub(dmg as u16);
|
||||||
|
|
||||||
logger.log(|| HitTarget {
|
log!(logger, "hit_target", {
|
||||||
actor: player,
|
actor: player,
|
||||||
recipient: target,
|
recipient: target,
|
||||||
weapon,
|
weapon: weapon,
|
||||||
part: body_part,
|
part: %body_part,
|
||||||
part_mult: mult,
|
part_mult: mult,
|
||||||
rounds,
|
dmg: dmg,
|
||||||
dmg,
|
health_before: health_before,
|
||||||
health_before,
|
|
||||||
health_after: health.value,
|
health_after: health.value,
|
||||||
dmg_spread,
|
dmg_spread: dmg_spread,
|
||||||
dmg_intrinsic,
|
dmg_intrinsic: dmg_intrinsic,
|
||||||
armour_mitigation,
|
armour_mitigation: armour_mitigation,
|
||||||
def_mitigation,
|
def_mitigation: def_mitigation,
|
||||||
weapon_dmg: w_dmg.0,
|
weapon_dmg: w_dmg.0,
|
||||||
bonus_dmg: dmg_bonus.value,
|
bonus_dmg: dmg_bonus.value,
|
||||||
hit_chance,
|
hit_chance: hit_chance,
|
||||||
crit_rate: crit.value,
|
crit_rate: crit.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -798,14 +577,14 @@ pub fn use_damaging_weapon(
|
||||||
defeated = true;
|
defeated = true;
|
||||||
|
|
||||||
commands.entity(target).insert(Defeated);
|
commands.entity(target).insert(Defeated);
|
||||||
logger.log(|| FightEnd {
|
log!(logger, "fight_end", {
|
||||||
actor: player,
|
actor: player,
|
||||||
recipient: target,
|
recipient: target,
|
||||||
fight_end_type: if attacker {
|
fight_end_type: %if attacker {
|
||||||
FightEndType::Victory
|
FightEndType::Victory
|
||||||
} else {
|
} else {
|
||||||
FightEndType::Loss
|
FightEndType::Loss
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
metrics.increment_counter(player, "victory", 1);
|
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() {
|
if *state == FightStatus::Ongoing && current_turns.0 >= 25 && attacker.is_some() {
|
||||||
commands.entity(current).insert(Defeated);
|
commands.entity(current).insert(Defeated);
|
||||||
let target = target_q.single();
|
let target = target_q.single();
|
||||||
logger.log(|| FightEnd {
|
|
||||||
|
log!(logger, "fight_end", {
|
||||||
actor: current,
|
actor: current,
|
||||||
recipient: target,
|
recipient: target,
|
||||||
fight_end_type: FightEndType::Stalemate,
|
fight_end_type: %FightEndType::Stalemate
|
||||||
});
|
});
|
||||||
|
|
||||||
metrics.increment_counter(current, "stalemate", 1);
|
metrics.increment_counter(current, "stalemate", 1);
|
||||||
|
|
||||||
if other_attackers_q.is_empty() {
|
if other_attackers_q.is_empty() {
|
||||||
|
|
@ -898,7 +677,6 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
status_effect::configure(stages);
|
status_effect::configure(stages);
|
||||||
|
|
||||||
stages.add_event::<ChooseWeapon>();
|
stages.add_event::<ChooseWeapon>();
|
||||||
stages.add_event::<InitiateHit>();
|
|
||||||
stages.equip.add_systems(designate_first);
|
stages.equip.add_systems(designate_first);
|
||||||
stages.pre_fight.add_systems(derive_max_health);
|
stages.pre_fight.add_systems(derive_max_health);
|
||||||
stages.pre_turn.add_systems(pick_action);
|
stages.pre_turn.add_systems(pick_action);
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,18 @@ pub enum MultiTurnBonus {
|
||||||
Rage,
|
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(
|
pub(crate) fn prepare_bonuses(
|
||||||
bonus_q: Query<(
|
bonus_q: Query<(
|
||||||
&Parent,
|
&Parent,
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,22 @@
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use macros::LogMessage;
|
use macros::LogMessage;
|
||||||
use rand::Rng as _;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
effect::{Effects, TurnLimitedEffect},
|
effect::{Effects, TurnLimitedEffect},
|
||||||
hierarchy::{HierarchyBuilder, Parent},
|
hierarchy::{HierarchyBuilder, Parent},
|
||||||
log::Logger,
|
log::Logger,
|
||||||
metrics::Metrics,
|
|
||||||
passives::{Education, FactionUpgrades, Merits},
|
passives::{Education, FactionUpgrades, Merits},
|
||||||
player::{
|
player::{
|
||||||
stats::{
|
stats::{
|
||||||
AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity,
|
AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity,
|
||||||
EffectiveStat, SimpleStatBonus, SimpleStatBundle, SimpleStatEffective, Speed,
|
SimpleStatBonus, SimpleStatBundle, SimpleStatEffective, WeaponAccuracy,
|
||||||
WeaponAccuracy,
|
|
||||||
},
|
},
|
||||||
BodyPart, Current, CurrentTarget, InitiateHit, Player, Weapons,
|
Current, Weapons,
|
||||||
},
|
},
|
||||||
Id, Name, Rng, Stages,
|
Id, Name, Stages,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{
|
use self::bonus::{FirstTurnBonus, MultiTurnBonus, TurnTriggeredBonus};
|
||||||
bonus::{FirstTurnBonus, MultiTurnBonus, TurnTriggeredBonus},
|
|
||||||
temp::{NonTargeted, Uses},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod bonus;
|
pub mod bonus;
|
||||||
pub mod temp;
|
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(
|
fn reload_weapon(
|
||||||
mut weapon_q: Query<
|
mut weapon_q: Query<
|
||||||
(
|
(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue