proxisim/src/lib.rs

359 lines
10 KiB
Rust

#![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<T>(&mut self) -> &mut Self
where
T: Message,
{
if !self.world.contains_resource::<Messages<T>>() {
/* self.world.init_resource::<Messages<T>>();
self.pre_turn.add_systems(
bevy_ecs::event::event_update_system::<T>
.run_if(bevy_ecs::event::event_update_condition::<T>),
); */
MessageRegistry::register_message::<T>(&mut self.world);
}
self
}
fn register_effect<Effect: 'static>(&mut self) -> EffectBuilder<'_> {
register_effect::<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>();
log.clear();
}
pub fn set_metrics(&mut self, recording: bool) {
let mut metrics = self.0.world.resource_mut::<Metrics>();
metrics.active = recording;
}
pub fn consume_metrics(&mut self) -> (Vec<Counter>, Vec<Histogram>) {
let entities = self.0.world.entities().len();
let components = self.0.world.components().len();
self.0
.world
.resource::<metrics::Metrics>()
.increment_counter(None, "entities", entities.into());
self.0
.world
.resource::<metrics::Metrics>()
.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::<FightStatus>();
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::<Log>();
let registry = self.0.world.resource::<EntityRegistry>();
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();
}
}