#![warn(clippy::perf, clippy::style, clippy::all)] #![allow(clippy::type_complexity)] use bevy_ecs::{message::MessageRegistry, prelude::*, schedule::ScheduleLabel}; use effect::{EffectBuilder, register_effect}; use metrics::Metrics; use proxisim_models::{ bundle::player::{Attacker, Current, Defender}, dto::{ metrics::{Counter, Histogram}, player::PlayerDto, }, }; use rand::SeedableRng; use crate::log::{Log, Logging}; mod armour; mod effect; mod entity_registry; // mod hierarchy; pub mod log; mod metrics; mod passives; mod player; mod weapon; // TODO: This is a bottleneck, so probably better to change this to a `Local` or use `thread_rng` // instead. Then again, the whole simulator isn't very parallelisable, so it may be a moot point #[derive(Resource)] struct Rng(pub rand::rngs::SmallRng); impl std::ops::Deref for Rng { type Target = rand::rngs::SmallRng; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for Rng { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } #[derive(Resource, Debug, PartialEq, Eq)] enum FightStatus { Ongoing, Over, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ScheduleLabel)] enum Stage { Equip, Snapshot, Passives, PreFight, PreTurn, Turn, PostTurn, PostFight, Restore, } struct Stages { equip: Schedule, snapshot: Schedule, passives: Schedule, pre_fight: Schedule, pre_turn: Schedule, turn: Schedule, post_turn: Schedule, post_fight: Schedule, restore: Schedule, world: World, } impl Stages { fn add_event(&mut self) -> &mut Self where T: Message, { if !self.world.contains_resource::>() { /* self.world.init_resource::>(); self.pre_turn.add_systems( bevy_ecs::event::event_update_system:: .run_if(bevy_ecs::event::event_update_condition::), ); */ MessageRegistry::register_message::(&mut self.world); } self } fn register_effect(&mut self) -> EffectBuilder<'_> { register_effect::(self) } } pub struct Simulation(Stages); impl Simulation { pub fn new(attacker: PlayerDto, defender: PlayerDto) -> Self { let world = World::new(); let mut stages = Stages { equip: Schedule::new(Stage::Equip), snapshot: Schedule::new(Stage::Snapshot), passives: Schedule::new(Stage::Passives), pre_fight: Schedule::new(Stage::PreFight), pre_turn: Schedule::new(Stage::PreTurn), turn: Schedule::new(Stage::Turn), post_turn: Schedule::new(Stage::PostTurn), post_fight: Schedule::new(Stage::PostFight), restore: Schedule::new(Stage::Restore), world, }; metrics::configure(&mut stages); effect::configure(&mut stages); player::configure(&mut stages); passives::configure(&mut stages); weapon::configure(&mut stages); armour::configure(&mut stages); log::configure(&mut stages); entity_registry::configure(&mut stages); stages.world.insert_resource(FightStatus::Ongoing); stages .world .insert_resource(Rng(rand::rngs::SmallRng::from_os_rng())); stages.world.insert_resource(Logging(true)); attacker .spawn(&mut stages.world) .insert((Attacker, Current)); defender.spawn(&mut stages.world).insert(Defender); stages.equip.run(&mut stages.world); effect::run_effects(&mut stages.world); stages.passives.run(&mut stages.world); effect::run_effects(&mut stages.world); stages.pre_fight.run(&mut stages.world); stages.snapshot.run(&mut stages.world); Self(stages) } pub fn set_logging(&mut self, logging: bool) { self.0.world.insert_resource(Logging(logging)); } pub fn truncate_log(&mut self) { let mut log = self.0.world.resource_mut::(); log.clear(); } pub fn set_metrics(&mut self, recording: bool) { let mut metrics = self.0.world.resource_mut::(); metrics.active = recording; } pub fn consume_metrics(&mut self) -> (Vec, Vec) { let entities = self.0.world.entities().len(); let components = self.0.world.components().len(); self.0 .world .resource::() .increment_counter(None, "entities", entities.into()); self.0 .world .resource::() .increment_counter(None, "components", components as u64); metrics::consume_metrics(&self.0.world) } pub fn run_once(&mut self) { loop { self.0.pre_turn.run(&mut self.0.world); effect::run_effects(&mut self.0.world); self.0.turn.run(&mut self.0.world); effect::run_effects(&mut self.0.world); self.0.post_turn.run(&mut self.0.world); effect::run_effects(&mut self.0.world); let state = self.0.world.resource::(); if *state == FightStatus::Over { break; } } self.0.post_fight.run(&mut self.0.world); self.0.restore.run(&mut self.0.world); } #[cfg(feature = "json")] pub fn read_log(&self) -> serde_json::Value { use entity_registry::EntityRegistry; let log = self.0.world.resource::(); let registry = self.0.world.resource::(); log.to_value(registry) } } /* fn main() { let attacker = dto::Player { name: "Pyrit".to_owned(), level: 100, stats: dto::Stats { str: 1_035_562_970.0, def: 1_309_681_178.0, spd: 1_035_547_487.0, dex: 339_651_454.0, }, merits: dto::Merits { life: 10, crits: 10, brawn: 10, protection: 10, sharpness: 10, ..Default::default() }, education: dto::Education::default(), faction: dto::FactionUpgrades { def: 14, dex: 14, side_effects: 10, ..Default::default() }, drug: Some(dto::DrugCooldown::Xanax), weapons: dto::Weapons { primary: Some(dto::Weapon { name: "ArmaLite M-15A4".to_owned(), cat: dto::WeaponCategory::Rifle, dmg: 68.73, acc: 59.41, mods: vec![dto::WeaponMod::AcogSight], ammo: dto::WeaponAmmo { clips: 3, clip_size: 15, rate_of_fire: [3, 5], }, experience: 100.0, }), melee: Some(dto::MeleeWeapon { name: "Pillow".to_owned(), cat: dto::WeaponCategory::Club, japanese: false, dmg: 1.18, acc: 64.41, experience: 100.0, }), temp: Some(dto::Temp::PepperSpray), ..Default::default() }, strategy: dto::PlayerStrategy::InOrder { order: vec![ // crate::dto::WeaponSlot::Melee, // crate::dto::WeaponSlot::Temporary, // crate::dto::WeaponSlot::Primary, ], reload: true, }, }; let defender = dto::Player { name: "olesien".to_owned(), level: 100, stats: dto::Stats { str: 1_101_841_257.0, def: 745_915_274.0, spd: 1_218_894_919.0, dex: 1_301_489_826.0, }, merits: dto::Merits { life: 10, crits: 10, brawn: 10, protection: 9, sharpness: 10, evasion: 10, ..Default::default() }, education: dto::Education::default(), faction: dto::FactionUpgrades { str: 14, spd: 13, acc: 5, dmg: 1, side_effects: 10, ..Default::default() }, drug: Some(dto::DrugCooldown::Xanax), weapons: dto::Weapons { primary: Some(dto::Weapon { name: "ArmaLite M-15A4".to_owned(), cat: dto::WeaponCategory::Rifle, dmg: 68.73, acc: 59.41, mods: vec![dto::WeaponMod::Laser100mw], ammo: dto::WeaponAmmo { clips: 3, clip_size: 15, rate_of_fire: [3, 5], }, experience: 100.0, }), melee: Some(dto::MeleeWeapon { name: "Pillow".to_owned(), cat: dto::WeaponCategory::Club, japanese: false, dmg: 1.54, acc: 64.41, experience: 100.0, }), temp: Some(crate::dto::Temp::Heg), ..Default::default() }, strategy: dto::PlayerStrategy::InOrder { order: vec![ // crate::dto::WeaponSlot::Melee, // crate::dto::WeaponSlot::Temporary, // crate::dto::WeaponSlot::Primary, ], reload: false, }, }; let mut simulation = Simulation::new(attacker, defender); let log_value = simulation.run_once(); println!("{log_value:?}"); } */ #[cfg(test)] mod tests { use super::*; fn attacker() -> PlayerDto { serde_json::from_str(include_str!("./attacker.json")).unwrap() } fn defender() -> PlayerDto { serde_json::from_str(include_str!("./defender.json")).unwrap() } #[test] fn init_simulator() { Simulation::new(attacker(), defender()); } #[test] fn metrics() { let mut sim = Simulation::new(attacker(), defender()); sim.set_metrics(true); sim.run_once(); sim.consume_metrics(); } }