initial commit
This commit is contained in:
commit
86f9333aec
21 changed files with 6449 additions and 0 deletions
444
src/lib.rs
Normal file
444
src/lib.rs
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
#![warn(clippy::perf, clippy::style, clippy::all)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
use bevy_ecs::{prelude::*, schedule::ScheduleLabel};
|
||||
use effect::{register_effect, EffectBuilder};
|
||||
use metrics::Metrics;
|
||||
use rand::SeedableRng;
|
||||
|
||||
use crate::{
|
||||
log::{Log, Logging},
|
||||
player::{Attacker, Current, Defender},
|
||||
};
|
||||
|
||||
mod armour;
|
||||
pub mod dto;
|
||||
mod effect;
|
||||
mod entity_registry;
|
||||
mod hierarchy;
|
||||
pub mod log;
|
||||
mod metrics;
|
||||
mod passives;
|
||||
mod player;
|
||||
mod weapon;
|
||||
|
||||
#[derive(Component, Debug, Default)]
|
||||
struct Name(String);
|
||||
|
||||
#[derive(Component, Debug, Default)]
|
||||
struct Id(usize);
|
||||
|
||||
// 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,
|
||||
PreFight,
|
||||
PreTurn,
|
||||
Turn,
|
||||
PostTurn,
|
||||
PostFight,
|
||||
Restore,
|
||||
}
|
||||
|
||||
struct Stages {
|
||||
equip: Schedule,
|
||||
snapshot: 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: Event,
|
||||
{
|
||||
if !self.world.contains_resource::<Events<T>>() {
|
||||
self.world.init_resource::<Events<T>>();
|
||||
self.pre_turn.add_systems(
|
||||
bevy_ecs::event::event_update_system::<T>
|
||||
.run_if(bevy_ecs::event::event_update_condition::<T>),
|
||||
);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn register_effect<Effect: 'static>(&mut self) -> EffectBuilder {
|
||||
register_effect::<Effect>(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Simulation(Stages);
|
||||
|
||||
impl Simulation {
|
||||
pub fn new(attacker: dto::Player, defender: dto::Player) -> Self {
|
||||
let world = World::new();
|
||||
let mut stages = Stages {
|
||||
equip: Schedule::new(Stage::Equip),
|
||||
snapshot: Schedule::new(Stage::Snapshot),
|
||||
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_entropy()));
|
||||
|
||||
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);
|
||||
stages.pre_fight.run(&mut stages.world);
|
||||
effect::run_effects(&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.entries.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<dto::Counter>, Vec<dto::Histogram>) {
|
||||
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 {
|
||||
let log = self.0.world.resource::<Log>();
|
||||
log.to_value()
|
||||
}
|
||||
}
|
||||
|
||||
/* 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() -> dto::Player {
|
||||
use dto::*;
|
||||
|
||||
Player {
|
||||
name: "Test".to_owned(),
|
||||
id: 0,
|
||||
level: 10,
|
||||
stats: Stats {
|
||||
str: 10_000.0,
|
||||
def: 10.0,
|
||||
spd: 10.0,
|
||||
dex: 10.0,
|
||||
},
|
||||
merits: Default::default(),
|
||||
education: Default::default(),
|
||||
faction: Default::default(),
|
||||
drug: None,
|
||||
strategy: PlayerStrategy::AlwaysFists,
|
||||
weapons: Weapons {
|
||||
primary: Some(Weapon {
|
||||
name: "Test".to_owned(),
|
||||
cat: WeaponCategory::Rifle,
|
||||
dmg: 50.0,
|
||||
acc: 50.0,
|
||||
ammo: WeaponAmmo {
|
||||
clips: 3,
|
||||
clip_size: 25,
|
||||
rate_of_fire: [3, 5],
|
||||
},
|
||||
mods: Vec::default(),
|
||||
bonuses: vec![WeaponBonusInfo {
|
||||
bonus: WeaponBonus::Expose,
|
||||
value: 9.0,
|
||||
}],
|
||||
experience: 100.0,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
armour: ArmourPieces {
|
||||
helmet: Some(Armour {
|
||||
name: "Test".to_owned(),
|
||||
armour_value: 50.0,
|
||||
coverage: [
|
||||
100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0,
|
||||
],
|
||||
immunities: Vec::default(),
|
||||
set: None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn defender() -> dto::Player {
|
||||
use dto::*;
|
||||
|
||||
Player {
|
||||
name: "Test".to_owned(),
|
||||
id: 1,
|
||||
level: 10,
|
||||
stats: Stats {
|
||||
str: 10_000.0,
|
||||
def: 10.0,
|
||||
spd: 10.0,
|
||||
dex: 10.0,
|
||||
},
|
||||
merits: Default::default(),
|
||||
education: Default::default(),
|
||||
faction: Default::default(),
|
||||
drug: None,
|
||||
strategy: PlayerStrategy::AlwaysFists,
|
||||
weapons: Weapons {
|
||||
primary: Some(Weapon {
|
||||
name: "Test".to_owned(),
|
||||
cat: WeaponCategory::Rifle,
|
||||
dmg: 50.0,
|
||||
acc: 50.0,
|
||||
ammo: WeaponAmmo {
|
||||
clips: 3,
|
||||
clip_size: 25,
|
||||
rate_of_fire: [3, 5],
|
||||
},
|
||||
mods: Vec::default(),
|
||||
bonuses: vec![WeaponBonusInfo {
|
||||
bonus: WeaponBonus::Powerful,
|
||||
value: 35.0,
|
||||
}],
|
||||
experience: 100.0,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
armour: ArmourPieces {
|
||||
helmet: Some(Armour {
|
||||
name: "Test".to_owned(),
|
||||
armour_value: 50.0,
|
||||
coverage: [
|
||||
100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0,
|
||||
],
|
||||
immunities: Vec::default(),
|
||||
set: None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_simulator() {
|
||||
let mut sim = Simulation::new(attacker(), defender());
|
||||
sim.run_once();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metrics() {
|
||||
let mut sim = Simulation::new(attacker(), defender());
|
||||
sim.set_metrics(true);
|
||||
for _ in 0..20 {
|
||||
sim.run_once();
|
||||
}
|
||||
sim.consume_metrics();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue