initial commit
This commit is contained in:
commit
86f9333aec
21 changed files with 6449 additions and 0 deletions
565
src/log.rs
Normal file
565
src/log.rs
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
use bevy_ecs::{prelude::*, system::SystemParam};
|
||||
use macros::LogMessage;
|
||||
|
||||
use crate::{
|
||||
hierarchy::Children,
|
||||
player::{
|
||||
stats::{
|
||||
AdditiveBonus, BaselineStat, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat,
|
||||
MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus, SimpleStatEffective,
|
||||
SimpleStatMarker, Speed, StatMarker, StatType, Strength, WeaponAccuracy,
|
||||
},
|
||||
Player,
|
||||
},
|
||||
weapon::{Weapon, WeaponVerb},
|
||||
Name, Stages,
|
||||
};
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct Logging(pub bool);
|
||||
|
||||
#[derive(Event)]
|
||||
struct LogEvent(Mutex<Option<Box<dyn LogMessage>>>);
|
||||
|
||||
impl<T> From<T> for LogEvent
|
||||
where
|
||||
T: LogMessage,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(Mutex::new(Some(Box::new(value))))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlayerInfo {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WeaponInfo {
|
||||
pub name: String,
|
||||
pub verb: WeaponVerb,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LogValue<'a> {
|
||||
Float(f32),
|
||||
Unsigned(u32),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
OptionNone,
|
||||
Display(&'a (dyn std::fmt::Display + Send + Sync)),
|
||||
Debug(&'a (dyn std::fmt::Debug + Send + Sync)),
|
||||
Player(Entity),
|
||||
Weapon(Entity),
|
||||
}
|
||||
|
||||
impl<'a> From<String> for LogValue<'a> {
|
||||
fn from(value: String) -> Self {
|
||||
Self::String(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<f32> for LogValue<'a> {
|
||||
fn from(value: f32) -> Self {
|
||||
Self::Float(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<u32> for LogValue<'a> {
|
||||
fn from(value: u32) -> Self {
|
||||
Self::Unsigned(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<u16> for LogValue<'a> {
|
||||
fn from(value: u16) -> Self {
|
||||
Self::Unsigned(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for LogValue<'static> {
|
||||
fn from(value: bool) -> Self {
|
||||
Self::Bool(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<Option<T>> for LogValue<'a>
|
||||
where
|
||||
T: Into<LogValue<'a>>,
|
||||
{
|
||||
fn from(value: Option<T>) -> Self {
|
||||
match value {
|
||||
Some(val) => val.into(),
|
||||
None => LogValue::OptionNone,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
impl<'a> LogValue<'a> {
|
||||
fn to_value(
|
||||
&self,
|
||||
player_registry: &HashMap<Entity, PlayerInfo>,
|
||||
weapon_registry: &HashMap<Entity, WeaponInfo>,
|
||||
) -> serde_json::Value {
|
||||
match self {
|
||||
LogValue::OptionNone => serde_json::Value::Null,
|
||||
LogValue::Float(val) => {
|
||||
serde_json::Value::Number(serde_json::Number::from_f64(*val as f64).unwrap())
|
||||
}
|
||||
LogValue::String(val) => serde_json::Value::String(val.clone()),
|
||||
LogValue::Bool(val) => serde_json::Value::Bool(*val),
|
||||
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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<'_>)>;
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl Log {
|
||||
#[cfg(feature = "json")]
|
||||
pub fn to_value(&self) -> serde_json::Value {
|
||||
use serde_json::json;
|
||||
|
||||
serde_json::json!({
|
||||
"entries": self.entries.iter().map(|e|
|
||||
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()
|
||||
)
|
||||
})
|
||||
).collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Log {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut iter = self.entries.iter().peekable();
|
||||
while let Some(entry) = iter.next() {
|
||||
write!(f, "{}: {{ ", entry.tag())?;
|
||||
|
||||
let mut fields = entry.entries().into_iter().peekable();
|
||||
while let Some(field) = fields.next() {
|
||||
if self.expanded {
|
||||
write!(f, "\n ")?;
|
||||
}
|
||||
write!(f, "{} = ", field.0)?;
|
||||
|
||||
match field.1 {
|
||||
LogValue::String(val) => write!(f, "\"{val}\"")?,
|
||||
LogValue::Float(val) => write!(f, "{val}")?,
|
||||
LogValue::Bool(val) => write!(f, "{val}")?,
|
||||
LogValue::Unsigned(val) => write!(f, "{val}")?,
|
||||
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)?
|
||||
}
|
||||
};
|
||||
|
||||
if fields.peek().is_some() {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
if self.expanded {
|
||||
writeln!(f)?;
|
||||
} else {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
|
||||
if iter.peek().is_some() {
|
||||
writeln!(f, "}}")?;
|
||||
} else {
|
||||
write!(f, "}}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/* impl std::fmt::Display for Log {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for entry in &self.entries {
|
||||
match entry {
|
||||
LogEntry::Hit {
|
||||
actor,
|
||||
recipient,
|
||||
weapon,
|
||||
dmg,
|
||||
rounds,
|
||||
crit,
|
||||
part,
|
||||
} => {
|
||||
let actor_info = self.player_registry.get(actor).unwrap();
|
||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
||||
let weapon_info = self.weapon_registry.get(weapon).unwrap();
|
||||
|
||||
write!(f, "{} ", actor_info.name)?;
|
||||
|
||||
match weapon_info.verb {
|
||||
WeaponVerb::Fired => {
|
||||
write!(
|
||||
f,
|
||||
"fired {} rounds from of their {} ",
|
||||
rounds.unwrap(),
|
||||
weapon_info.name
|
||||
)?;
|
||||
if *crit {
|
||||
write!(f, "critically ")?;
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
"hitting {} in the {} for {}",
|
||||
recipient_info.name, part, dmg
|
||||
)?;
|
||||
}
|
||||
WeaponVerb::Hit => {
|
||||
if *crit {
|
||||
write!(f, "critically ")?;
|
||||
}
|
||||
|
||||
// TODO: Pronouns and weapon verbs
|
||||
writeln!(
|
||||
f,
|
||||
"hit {} with their {} in the {} for {}",
|
||||
recipient_info.name, weapon_info.name, part, dmg
|
||||
)?;
|
||||
}
|
||||
WeaponVerb::Exploded => {
|
||||
writeln!(
|
||||
f,
|
||||
"{} threw a {} at {}, it exploded for {}",
|
||||
actor_info.name, weapon_info.name, recipient_info.name, dmg
|
||||
)?;
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
LogEntry::Miss {
|
||||
actor,
|
||||
recipient,
|
||||
weapon,
|
||||
rounds,
|
||||
} => {
|
||||
let actor_info = self.player_registry.get(actor).unwrap();
|
||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
||||
let weapon_info = self.weapon_registry.get(weapon).unwrap();
|
||||
|
||||
match weapon_info.verb {
|
||||
WeaponVerb::Hit => {
|
||||
writeln!(
|
||||
f,
|
||||
"{} missed {} with their {}",
|
||||
actor_info.name, recipient_info.name, weapon_info.name
|
||||
)?;
|
||||
}
|
||||
WeaponVerb::Fired => {
|
||||
writeln!(
|
||||
f,
|
||||
"{} fired {} rounds of their {} missing {}",
|
||||
actor_info.name,
|
||||
rounds.unwrap(),
|
||||
weapon_info.name,
|
||||
recipient_info.name
|
||||
)?;
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
LogEntry::Defeat { actor, recipient } => {
|
||||
let actor_info = self.player_registry.get(actor).unwrap();
|
||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
||||
|
||||
writeln!(f, "{} defeated {}", actor_info.name, recipient_info.name)?;
|
||||
}
|
||||
LogEntry::Stalemate { actor, recipient } => {
|
||||
let actor_info = self.player_registry.get(actor).unwrap();
|
||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{} stalemated against {}",
|
||||
actor_info.name, recipient_info.name
|
||||
)?;
|
||||
}
|
||||
LogEntry::Loss { actor, recipient } => {
|
||||
let actor_info = self.player_registry.get(actor).unwrap();
|
||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{} lost against {}",
|
||||
recipient_info.name, actor_info.name
|
||||
)?;
|
||||
}
|
||||
LogEntry::Reload { actor, weapon } => {
|
||||
let actor_info = self.player_registry.get(actor).unwrap();
|
||||
let weapon_info = self.weapon_registry.get(weapon).unwrap();
|
||||
|
||||
writeln!(f, "{} reloaded their {}", actor_info.name, weapon_info.name)?;
|
||||
}
|
||||
LogEntry::UsedDebuffTemp {
|
||||
actor,
|
||||
recipient,
|
||||
temp,
|
||||
weapon,
|
||||
immune,
|
||||
} => {
|
||||
let actor_info = self.player_registry.get(actor).unwrap();
|
||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
||||
let weapon_info = self.weapon_registry.get(weapon).unwrap();
|
||||
|
||||
match temp {
|
||||
DebuffingTemp::SmokeGrenade => {
|
||||
write!(
|
||||
f,
|
||||
"{} threw a Smoke Grenade, smoke clouds around {}",
|
||||
actor_info.name, recipient_info.name
|
||||
)?;
|
||||
}
|
||||
DebuffingTemp::TearGas => {
|
||||
write!(
|
||||
f,
|
||||
"{} threw a Tear Gas Grenade near {}",
|
||||
actor_info.name, recipient_info.name
|
||||
)?;
|
||||
}
|
||||
DebuffingTemp::PepperSpray => {
|
||||
write!(
|
||||
f,
|
||||
"{} sprayed Pepper Spray in {}'s face",
|
||||
actor_info.name, recipient_info.name
|
||||
)?;
|
||||
}
|
||||
_ => {
|
||||
write!(
|
||||
f,
|
||||
"{} threw a {} at {}",
|
||||
actor_info.name, recipient_info.name, weapon_info.name
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if *immune {
|
||||
writeln!(f, " but it was ineffective")?;
|
||||
} else {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
} */
|
||||
|
||||
#[derive(SystemParam)]
|
||||
pub struct Logger<'w> {
|
||||
event_writer: EventWriter<'w, LogEvent>,
|
||||
logging: Res<'w, Logging>,
|
||||
}
|
||||
|
||||
impl<'w> Logger<'w> {
|
||||
pub fn log<B, M>(&mut self, body: B)
|
||||
where
|
||||
B: FnOnce() -> M,
|
||||
M: LogMessage,
|
||||
{
|
||||
if self.logging.0 {
|
||||
self.event_writer.send(body().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(macros::LogMessage)]
|
||||
struct StatChange {
|
||||
#[log(player)]
|
||||
target: Entity,
|
||||
#[log(debug)]
|
||||
stat: StatType,
|
||||
|
||||
#[log(debug)]
|
||||
effects_add: Vec<(&'static str, f32)>,
|
||||
|
||||
#[log(debug)]
|
||||
effects_mult: Vec<(&'static str, f32)>,
|
||||
|
||||
baseline: f32,
|
||||
|
||||
effective: f32,
|
||||
}
|
||||
|
||||
fn log_stat_changes<Stat: StatMarker>(
|
||||
stat_q: Query<
|
||||
(Entity, &BaselineStat<Stat>, &EffectiveStat<Stat>, &Children),
|
||||
Changed<EffectiveStat<Stat>>,
|
||||
>,
|
||||
add_q: Query<&AdditiveBonus<Stat>>,
|
||||
mult_q: Query<&MultiplicativeBonus<Stat>>,
|
||||
mut logger: Logger,
|
||||
) {
|
||||
for (player, baseline, effective, children) in stat_q.iter() {
|
||||
let effects_add = add_q
|
||||
.iter_many(children.get())
|
||||
.map(|eff| (eff.label, eff.value))
|
||||
.collect();
|
||||
let effects_mult = mult_q
|
||||
.iter_many(children.get())
|
||||
.map(|eff| (eff.label, eff.value))
|
||||
.collect();
|
||||
|
||||
logger.log(|| StatChange {
|
||||
target: player,
|
||||
stat: Stat::stat_type(),
|
||||
effects_add,
|
||||
effects_mult,
|
||||
baseline: baseline.value,
|
||||
effective: effective.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn log_simple_stat_changes<Stat: SimpleStatMarker>(
|
||||
stat_q: Query<
|
||||
(
|
||||
Entity,
|
||||
&SimpleStatBaseline<Stat>,
|
||||
&SimpleStatEffective<Stat>,
|
||||
&Children,
|
||||
),
|
||||
Changed<SimpleStatEffective<Stat>>,
|
||||
>,
|
||||
bonus_q: Query<&SimpleStatBonus<Stat>>,
|
||||
mut logger: Logger,
|
||||
) where
|
||||
Stat::ValueType: Into<LogValue<'static>>,
|
||||
Stat::BonusType: std::fmt::Debug,
|
||||
{
|
||||
#[derive(LogMessage)]
|
||||
struct AppliedBonus {
|
||||
#[log(debug)]
|
||||
target: Entity,
|
||||
#[log(display)]
|
||||
stat: &'static str,
|
||||
baseline: LogValue<'static>,
|
||||
effective: LogValue<'static>,
|
||||
#[log(debug)]
|
||||
bonuses: Vec<(&'static str, String)>,
|
||||
}
|
||||
|
||||
for (target, baseline, effective, children) in stat_q.iter() {
|
||||
let bonuses = bonus_q
|
||||
.iter_many(children.get())
|
||||
.map(|eff| (eff.label, format!("{:?}", eff.value)))
|
||||
.collect();
|
||||
|
||||
logger.log(|| AppliedBonus {
|
||||
target,
|
||||
stat: std::any::type_name::<Stat>(),
|
||||
baseline: baseline.value.into(),
|
||||
effective: effective.value.into(),
|
||||
bonuses,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
(
|
||||
log_stat_changes::<Strength>,
|
||||
log_stat_changes::<Defence>,
|
||||
log_stat_changes::<Speed>,
|
||||
log_stat_changes::<Dexterity>,
|
||||
log_simple_stat_changes::<CritRate>,
|
||||
log_simple_stat_changes::<WeaponAccuracy>,
|
||||
log_simple_stat_changes::<DamageBonus>,
|
||||
)
|
||||
.run_if(logging_enabled),
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue