Simple site simulation with combat
commit
8bba930fbd
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adventure_sim"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"enum_dispatch",
|
||||||
|
"fastrand",
|
||||||
|
"serde",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum_dispatch"
|
||||||
|
version = "0.3.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.153"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.78"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.197"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.197"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.52"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"uuid-macro-internal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid-macro-internal"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "adventure_sim"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bincode = "1.3.3"
|
||||||
|
enum_dispatch = "0.3.12"
|
||||||
|
fastrand = "2.0.1"
|
||||||
|
serde = { version = "1.0.197", features = [ "serde_derive" ] }
|
||||||
|
uuid = { version = "1.7.0", features = [ "v4", "fast-rng", "macro-diagnostics", "serde" ] }
|
||||||
|
|
@ -0,0 +1,372 @@
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt::Display,
|
||||||
|
num::NonZeroUsize,
|
||||||
|
ops::Sub,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
const SAVE_FILE: &'static str = "world.bin";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut world = match std::fs::read(SAVE_FILE) {
|
||||||
|
Ok(data) => bincode::deserialize(&data).unwrap(),
|
||||||
|
Err(_) => {
|
||||||
|
let mut world = World::default();
|
||||||
|
let mut site = Site::new(
|
||||||
|
"Gorbo's Keep",
|
||||||
|
fastrand::usize(1..5)
|
||||||
|
.try_into()
|
||||||
|
.expect("Generated site with size of 0"),
|
||||||
|
);
|
||||||
|
site.populate_randomly();
|
||||||
|
world.sites.push(site);
|
||||||
|
std::fs::write("world.bin", bincode::serialize(&world).unwrap()).unwrap();
|
||||||
|
world
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for site in world.sites.iter() {
|
||||||
|
println!("{site}")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
loop {
|
||||||
|
std::io::stdin().read_line(&mut input).unwrap();
|
||||||
|
if vec!["q", "quit", "exit"]
|
||||||
|
.iter()
|
||||||
|
.any(|quit_cmd| input.trim() == *quit_cmd)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
world.advance_time(Duration::from_secs(3600));
|
||||||
|
let end = std::time::Instant::now();
|
||||||
|
println!("World tick: {}us", end.sub(start).as_micros());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct World {
|
||||||
|
pub sites: Vec<Site>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
pub fn advance_time(&mut self, time: Duration) {
|
||||||
|
for site in self.sites.iter_mut() {
|
||||||
|
site.advance_time(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Site {
|
||||||
|
pub name: String,
|
||||||
|
areas: Vec<SiteArea>,
|
||||||
|
accumulated_time: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Site {
|
||||||
|
#[inline]
|
||||||
|
const fn minimum_tick() -> Duration {
|
||||||
|
Duration::from_secs(3600)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_tick(&mut self) {
|
||||||
|
for area in self.areas.iter_mut() {
|
||||||
|
match resolve_combat(&area.population, 600) {
|
||||||
|
Some(results) => {
|
||||||
|
for death in results.kills {
|
||||||
|
area.population
|
||||||
|
.retain(|creature| creature.uuid != death.killed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// If no combat, there is a chance of migration to other areas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Tick! {}", self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn areas(&self) -> &Vec<SiteArea> {
|
||||||
|
self.areas.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn areas_mut(&mut self) -> &mut Vec<SiteArea> {
|
||||||
|
self.areas.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn populate_randomly(&mut self) {
|
||||||
|
let area_count = self.areas.len();
|
||||||
|
{
|
||||||
|
let min_count = area_count;
|
||||||
|
let max_count = (5 + (area_count - 1) * 3).max(min_count);
|
||||||
|
for _ in 0..fastrand::usize(min_count..=max_count) {
|
||||||
|
let area_t = fastrand::f32().powi(2);
|
||||||
|
let index = (area_t * (area_count - 1) as f32).round() as usize;
|
||||||
|
self.areas
|
||||||
|
.get_mut(index)
|
||||||
|
.expect("Creature generation index for depth is out of bounds")
|
||||||
|
.population
|
||||||
|
.push(Creature::new(CreatureData::Bandit(Bandit::default())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let min_count = 0;
|
||||||
|
let max_count = area_count.max(min_count);
|
||||||
|
for _ in 0..fastrand::usize(min_count..=max_count) {
|
||||||
|
let area_t = 1.0 - fastrand::f32().powi(2);
|
||||||
|
let index = (area_t * (area_count - 1) as f32).round() as usize;
|
||||||
|
self.areas
|
||||||
|
.get_mut(index)
|
||||||
|
.expect("Creature generation index for depth is out of bounds")
|
||||||
|
.population
|
||||||
|
.push(Creature::new(CreatureData::Spider(Spider::default())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(name: &str, size: NonZeroUsize) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
areas: vec![SiteArea::default(); size.get()],
|
||||||
|
accumulated_time: Duration::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance_time(&mut self, time: Duration) {
|
||||||
|
self.accumulated_time += time;
|
||||||
|
while self.accumulated_time >= Self::minimum_tick() {
|
||||||
|
self.inner_tick();
|
||||||
|
self.accumulated_time -= Self::minimum_tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds a creature with given uuid, removes it from the site and returns it with ownership
|
||||||
|
pub fn remove_by_uuid(&mut self, creature_uuid: Uuid) -> Option<Creature> {
|
||||||
|
for area in self.areas.iter_mut() {
|
||||||
|
for i in 0..area.population.len() {
|
||||||
|
if area.population[i].uuid == creature_uuid {
|
||||||
|
return Some(area.population.swap_remove(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Site {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "site \"{}\" [", self.name)?;
|
||||||
|
for (index, area) in self.areas.iter().enumerate() {
|
||||||
|
write!(f, "\n\tarea {} [", index)?;
|
||||||
|
for creature in area.population.iter() {
|
||||||
|
write!(f, "\n\t\t{}", creature)?;
|
||||||
|
}
|
||||||
|
if area.population.len() > 0 {
|
||||||
|
write!(f, "\n\t")?;
|
||||||
|
}
|
||||||
|
write!(f, "]")?;
|
||||||
|
}
|
||||||
|
if !self.areas.is_empty() {
|
||||||
|
write!(f, "\n")?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct SiteArea {
|
||||||
|
pub population: Vec<Creature>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Creature {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub data: CreatureData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Creature {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} [{}]", self.data.species_id(), self.uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Creature {
|
||||||
|
pub fn new(data: CreatureData) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: Uuid::new_v4(),
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[enum_dispatch(CreatureData)]
|
||||||
|
pub trait CreatureImpl {
|
||||||
|
fn species_id(&self) -> String;
|
||||||
|
|
||||||
|
fn initiative_modifier(&self) -> i8 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn threat_rating(&self) -> u8 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn ally_predicate(&self, other: &dyn CreatureImpl) -> bool {
|
||||||
|
self.species_id() == other.species_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
|
#[enum_dispatch]
|
||||||
|
pub enum CreatureData {
|
||||||
|
Bandit(Bandit),
|
||||||
|
Spider(Spider),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct Bandit {}
|
||||||
|
|
||||||
|
impl CreatureImpl for Bandit {
|
||||||
|
fn species_id(&self) -> String {
|
||||||
|
"bandit".into()
|
||||||
|
}
|
||||||
|
fn threat_rating(&self) -> u8 {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct Spider {}
|
||||||
|
|
||||||
|
impl CreatureImpl for Spider {
|
||||||
|
fn species_id(&self) -> String {
|
||||||
|
"spider".into()
|
||||||
|
}
|
||||||
|
fn threat_rating(&self) -> u8 {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
fn initiative_modifier(&self) -> i8 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct KillData {
|
||||||
|
pub killed: Uuid,
|
||||||
|
pub killer: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CombatResults {
|
||||||
|
pub kills: Vec<KillData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CombatState {
|
||||||
|
initiative: i8,
|
||||||
|
health: u8,
|
||||||
|
damage: u8,
|
||||||
|
alive: bool,
|
||||||
|
enemies: HashSet<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CombatState {
|
||||||
|
pub fn from_creature(creature: &Creature) -> Self {
|
||||||
|
Self {
|
||||||
|
initiative: fastrand::i8(1..=4).saturating_add(creature.data.initiative_modifier()),
|
||||||
|
health: creature.data.threat_rating(),
|
||||||
|
damage: creature.data.threat_rating(),
|
||||||
|
alive: true,
|
||||||
|
enemies: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_combat(combatants: &Vec<Creature>, max_rounds: u16) -> Option<CombatResults> {
|
||||||
|
let mut participants: HashMap<Uuid, CombatState> = HashMap::new();
|
||||||
|
for index_1 in 0..combatants.len() {
|
||||||
|
for index_2 in (index_1 + 1)..combatants.len() {
|
||||||
|
if index_1 == index_2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let c1 = &combatants[index_1];
|
||||||
|
let c2 = &combatants[index_2];
|
||||||
|
let is_enemy = !c1.data.ally_predicate(&c2.data) || !c2.data.ally_predicate(&c1.data);
|
||||||
|
if is_enemy {
|
||||||
|
if !participants.contains_key(&c1.uuid) {
|
||||||
|
participants.insert(c1.uuid, CombatState::from_creature(&c1));
|
||||||
|
}
|
||||||
|
if !participants.contains_key(&c2.uuid) {
|
||||||
|
participants.insert(c2.uuid, CombatState::from_creature(&c2));
|
||||||
|
}
|
||||||
|
participants
|
||||||
|
.get_mut(&c1.uuid)
|
||||||
|
.unwrap()
|
||||||
|
.enemies
|
||||||
|
.insert(c2.uuid);
|
||||||
|
participants
|
||||||
|
.get_mut(&c2.uuid)
|
||||||
|
.unwrap()
|
||||||
|
.enemies
|
||||||
|
.insert(c1.uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if participants.is_empty() {
|
||||||
|
// No enemies in group :)
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time for violence
|
||||||
|
let mut order: Vec<_> = participants.iter().collect();
|
||||||
|
order.sort_unstable_by_key(|(_, combat_state)| -combat_state.initiative);
|
||||||
|
let order: Vec<Uuid> = order.iter().map(|(uuid, _)| **uuid).collect();
|
||||||
|
let mut kills: HashMap<Uuid, Uuid> = HashMap::new();
|
||||||
|
|
||||||
|
for _ in 0..max_rounds {
|
||||||
|
let should_continue = participants.iter().any(|(_, combat)| {
|
||||||
|
combat.alive && combat.enemies.iter().any(|uuid| participants[uuid].alive)
|
||||||
|
});
|
||||||
|
if !should_continue {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for uuid in order.iter() {
|
||||||
|
if !participants[uuid].alive {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let targets: Vec<_> = participants[uuid]
|
||||||
|
.enemies
|
||||||
|
.iter()
|
||||||
|
.filter(|enemy_uuid| participants[*enemy_uuid].alive)
|
||||||
|
.collect();
|
||||||
|
let target = match fastrand::choice(targets.iter()) {
|
||||||
|
Some(target) => **target,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let damage = participants[uuid].damage;
|
||||||
|
let enemy = participants.get_mut(&target).unwrap();
|
||||||
|
enemy.health = enemy.health.saturating_sub(fastrand::u8(0..=damage));
|
||||||
|
if enemy.health == 0 {
|
||||||
|
enemy.alive = false;
|
||||||
|
kills.insert(target, *uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(CombatResults {
|
||||||
|
kills: kills
|
||||||
|
.iter()
|
||||||
|
.map(|(killed, killer)| KillData {
|
||||||
|
killed: *killed,
|
||||||
|
killer: *killer,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue