initial commit

This commit is contained in:
TotallyNot 2023-12-31 21:26:43 +01:00
commit 86f9333aec
21 changed files with 6449 additions and 0 deletions

565
src/log.rs Normal file
View 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),
);
}