Added simple world generation for development
parent
aad25db759
commit
34e14900eb
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "adventure_sim"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
[SITE:CAVE]
|
||||
[SIZE:1:3]
|
||||
# [UNDERGROUND]
|
||||
[UNDERGROUND]
|
||||
[NATURAL]
|
||||
|
||||
[SITE:DUNGEON]
|
||||
[SIZE:2:3]
|
||||
[UNDERGROUND]
|
||||
|
||||
[SITE:BANDIT_CAMP]
|
||||
[SIZE:1:1]
|
||||
# [OUTDOORS]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ pub fn init(app: &mut App) {
|
|||
// .add_systems(Update, systems::demo_2d);
|
||||
|
||||
let definitions = sim::Definitions::from_default_directory().unwrap();
|
||||
let game_world = sim::GameWorld::generate_mock(&definitions);
|
||||
let game_world = sim::GameWorld::generate_random(&definitions);
|
||||
// let game_world = match std::fs::read(sim::SAVE_FILE) {
|
||||
// Ok(data) => {
|
||||
// info!("Reading world data from \"{}\"", sim::SAVE_FILE);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ pub mod debug;
|
|||
pub mod game;
|
||||
pub mod game_setup;
|
||||
pub mod sim;
|
||||
pub mod sim_runner;
|
||||
pub mod util;
|
||||
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -47,11 +47,7 @@ impl Creature {
|
|||
}
|
||||
|
||||
pub fn patrol_chance(&self) -> f32 {
|
||||
if self.definition.patrols {
|
||||
0.5
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
if self.definition.patrols { 0.5 } else { 0.0 }
|
||||
}
|
||||
|
||||
pub fn health(&self) -> i32 {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ impl SiteDef {
|
|||
match tag.name.as_str() {
|
||||
"SIZE" => (result.min_size, result.max_size) = tag.parse_value().unwrap(),
|
||||
"NATURAL" => result.is_natural = true,
|
||||
"UNDERGROUND" => result.is_underground = true,
|
||||
_ => {
|
||||
// warn!("Unknown tag '{}' in SITE defs", tag.name);
|
||||
return Err(ParseError::UnknownTag {
|
||||
|
|
|
|||
|
|
@ -24,10 +24,11 @@ impl Display for SiteId {
|
|||
#[derive(Debug, Serialize, Deserialize, Reflect)]
|
||||
pub struct Site {
|
||||
id: SiteId,
|
||||
areas: Vec<SiteArea>,
|
||||
accumulated_time: Duration,
|
||||
pub name: String,
|
||||
pub definition: SiteDef,
|
||||
pub position: Coords,
|
||||
pub areas: Vec<SiteArea>,
|
||||
}
|
||||
|
||||
impl Site {
|
||||
|
|
@ -36,11 +37,17 @@ impl Site {
|
|||
Duration::from_secs(3600)
|
||||
}
|
||||
|
||||
pub fn new(definition: SiteDef, name: impl Into<String>, areas: Vec<SiteArea>) -> Self {
|
||||
pub fn new(
|
||||
definition: SiteDef,
|
||||
name: impl Into<String>,
|
||||
position: Coords,
|
||||
areas: Vec<SiteArea>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: SiteId::generate(),
|
||||
definition,
|
||||
name: name.into(),
|
||||
definition,
|
||||
position,
|
||||
areas,
|
||||
accumulated_time: Duration::default(),
|
||||
}
|
||||
|
|
@ -139,10 +146,15 @@ impl Site {
|
|||
println!("Tick! {self}");
|
||||
}
|
||||
|
||||
pub fn generate_from_def(site_def: &SiteDef) -> Self {
|
||||
pub fn generate_from_def(
|
||||
site_def: &SiteDef,
|
||||
name: impl Into<String>,
|
||||
position: Coords,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
site_def.clone(),
|
||||
"Gorbo's cave",
|
||||
name,
|
||||
position,
|
||||
vec![
|
||||
SiteArea::default();
|
||||
fastrand::usize(site_def.min_size as usize..=site_def.max_size as usize)
|
||||
|
|
@ -150,14 +162,6 @@ impl Site {
|
|||
)
|
||||
}
|
||||
|
||||
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, creature_defs: &[&CreatureDef]) {
|
||||
let area_count = self.areas.len();
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ impl Display for TravelGroupId {
|
|||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize, Reflect)]
|
||||
pub enum WorldPoint {
|
||||
Coords(WorldCoords),
|
||||
Coords(Coords),
|
||||
Site(SiteId),
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ pub struct TravelGroup {
|
|||
id: TravelGroupId,
|
||||
accumulated_movement: Kilometer,
|
||||
pub creatures: Vec<Creature>,
|
||||
pub position: WorldCoords,
|
||||
pub position: Coords,
|
||||
pub origin: Option<WorldPoint>,
|
||||
pub destination: Option<WorldPoint>,
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ pub struct TravelGroup {
|
|||
impl TravelGroup {
|
||||
pub fn new(
|
||||
creatures: Vec<Creature>,
|
||||
position: WorldCoords,
|
||||
position: Coords,
|
||||
origin: Option<WorldPoint>,
|
||||
destination: Option<WorldPoint>,
|
||||
) -> Self {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{fmt::Display, path::PathBuf};
|
||||
use std::{fmt::Display, ops::Range, path::PathBuf};
|
||||
|
||||
use bevy::prelude::Reflect;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -6,12 +6,62 @@ use serde::{Deserialize, Serialize};
|
|||
pub type Kilometer = f32;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Reflect)]
|
||||
pub struct WorldCoords {
|
||||
pub struct Coords {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
impl Display for WorldCoords {
|
||||
impl Coords {
|
||||
pub fn new(x: i32, y: i32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Reflect)]
|
||||
pub struct CoordRect {
|
||||
pub origin: Coords,
|
||||
pub size: Coords,
|
||||
}
|
||||
|
||||
impl CoordRect {
|
||||
pub fn from_size(center: Coords, size: Coords) -> Self {
|
||||
Self {
|
||||
origin: Coords {
|
||||
x: center.x - size.x / 2,
|
||||
y: center.y - size.y / 2,
|
||||
},
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.size.x <= 0 || self.size.y <= 0
|
||||
}
|
||||
|
||||
pub fn width_range(&self) -> Range<i32> {
|
||||
self.origin.x..(self.origin.x + self.size.x)
|
||||
}
|
||||
|
||||
pub fn height_range(&self) -> Range<i32> {
|
||||
self.origin.y..(self.origin.y + self.size.y)
|
||||
}
|
||||
|
||||
/// Return iterator of all possible coordinates in rect, also known as the cartesian product of
|
||||
/// the x and y ranges
|
||||
pub fn iter_coords(&self) -> impl Iterator<Item = Coords> {
|
||||
self.width_range()
|
||||
.flat_map(|x| self.height_range().map(move |y| Coords::new(x, y)))
|
||||
}
|
||||
|
||||
pub fn pick_random_coords(&self, count: usize) -> Vec<Coords> {
|
||||
if self.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
fastrand::choose_multiple(self.iter_coords(), count)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Coords {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,33 +5,34 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use super::prelude::*;
|
||||
|
||||
#[derive(Debug, Default, Resource, Serialize, Deserialize, Reflect)]
|
||||
#[derive(Debug, Resource, Serialize, Deserialize, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct GameWorld {
|
||||
pub area: CoordRect,
|
||||
pub sites: Vec<Site>,
|
||||
pub travel_groups: Vec<TravelGroup>,
|
||||
}
|
||||
|
||||
impl GameWorld {
|
||||
pub fn advance_time(&mut self, time: Duration) {
|
||||
for site in self.sites.iter_mut() {
|
||||
site.advance_time(time);
|
||||
impl Default for GameWorld {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
area: CoordRect::from_size(Coords::new(0, 0), Coords::new(16, 16)),
|
||||
sites: vec![],
|
||||
travel_groups: vec![],
|
||||
}
|
||||
for travel_group in self.travel_groups.iter_mut() {
|
||||
travel_group.advance_time(time);
|
||||
}
|
||||
}
|
||||
|
||||
impl GameWorld {
|
||||
pub fn generate_mock(defs: &Definitions) -> Self {
|
||||
let cave_def = defs.sites.get("CAVE").unwrap();
|
||||
let mut site = Site::generate_from_def(cave_def);
|
||||
site.populate_randomly(&defs.creatures.values().collect::<Vec<_>>());
|
||||
|
||||
let bandit_def = defs.creatures.get("BANDIT").unwrap();
|
||||
let spider_def = defs.creatures.get("SPIDER").unwrap();
|
||||
let cave = Site::new(
|
||||
cave_def.clone(),
|
||||
"Gorbo's cave",
|
||||
Coords::new(0, 0),
|
||||
vec![
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(defs.creatures.get("DRAGON").unwrap().clone()),
|
||||
|
|
@ -61,13 +62,58 @@ impl GameWorld {
|
|||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
],
|
||||
WorldCoords { x: 0, y: 0 },
|
||||
Some(WorldPoint::Coords(WorldCoords { x: 0, y: 0 })),
|
||||
Coords { x: 0, y: 0 },
|
||||
Some(WorldPoint::Coords(Coords { x: 0, y: 0 })),
|
||||
Some(WorldPoint::Site(cave.id())),
|
||||
);
|
||||
Self {
|
||||
sites: vec![cave],
|
||||
travel_groups: vec![group],
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_random(defs: &Definitions) -> Self {
|
||||
let mut world = Self {
|
||||
area: CoordRect::from_size(Coords::new(0, 0), Coords::new(32, 32)),
|
||||
..default()
|
||||
};
|
||||
|
||||
const SITE_COUNT: usize = 30;
|
||||
let site_locations = world.area.pick_random_coords(SITE_COUNT);
|
||||
let site_locations = site_locations.chunks(SITE_COUNT / 3);
|
||||
|
||||
for (chunk, locations) in site_locations.enumerate() {
|
||||
for (i, coords) in locations.iter().enumerate() {
|
||||
world.sites.push(match chunk {
|
||||
0 => Site::generate_from_def(
|
||||
defs.sites.get("DUNGEON").unwrap(),
|
||||
format!("Dungeon #{}", i + 1),
|
||||
*coords,
|
||||
),
|
||||
1 => Site::generate_from_def(
|
||||
defs.sites.get("CAVE").unwrap(),
|
||||
format!("Cave #{}", i + 1),
|
||||
*coords,
|
||||
),
|
||||
_ => Site::generate_from_def(
|
||||
defs.sites.get("BANDIT_CAMP").unwrap(),
|
||||
format!("Bandit camp #{}", i + 1),
|
||||
*coords,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
world
|
||||
}
|
||||
|
||||
pub fn advance_time(&mut self, time: Duration) {
|
||||
for site in self.sites.iter_mut() {
|
||||
site.advance_time(time);
|
||||
}
|
||||
for travel_group in self.travel_groups.iter_mut() {
|
||||
travel_group.advance_time(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,126 +0,0 @@
|
|||
use crate::sim;
|
||||
use std::{collections::HashMap, io::BufReader, path::PathBuf, time::Duration};
|
||||
|
||||
use sim::definitions::parser::DefinitionParser;
|
||||
use sim::prelude::*;
|
||||
|
||||
const SAVE_FILE: &str = "world.bin";
|
||||
|
||||
pub fn run_interactive_simulation() {
|
||||
let mut parse_error_files = vec![];
|
||||
let mut site_definitions = HashMap::new();
|
||||
let mut creature_definitions = HashMap::new();
|
||||
for entry in std::fs::read_dir(resources_path()).unwrap().flatten() {
|
||||
if entry.file_name().to_string_lossy().ends_with(".def") {
|
||||
let source = BufReader::new(std::fs::File::open(entry.path()).unwrap());
|
||||
match DefinitionParser::parse(source) {
|
||||
Ok(defs) => {
|
||||
for def in defs.0 {
|
||||
if let Some(prev) = site_definitions.insert(def.id.clone(), def) {
|
||||
eprintln!("Duplicate site definition '{}'", prev.id);
|
||||
}
|
||||
}
|
||||
for def in defs.1 {
|
||||
if let Some(prev) = creature_definitions.insert(def.id.clone(), def) {
|
||||
eprintln!("Duplicate site definition '{}'", prev.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
parse_error_files.push((err, entry.path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (err, path) in parse_error_files.iter() {
|
||||
eprintln!("Error\t{path:?}\n\t{err:?}")
|
||||
}
|
||||
if !parse_error_files.is_empty() {
|
||||
eprintln!("{} file(s) had parsing errors!", parse_error_files.len())
|
||||
}
|
||||
|
||||
let site_def = site_definitions.get("CAVE").unwrap();
|
||||
let mut world = match std::fs::read(SAVE_FILE) {
|
||||
Ok(data) => bincode::deserialize(&data).expect("Loading world data from file"),
|
||||
Err(_) => {
|
||||
let mut world = GameWorld::default();
|
||||
|
||||
let mut site = Site::generate_from_def(site_def);
|
||||
site.populate_randomly(&creature_definitions.values().collect::<Vec<_>>());
|
||||
|
||||
let bandit_def = creature_definitions.get("BANDIT").unwrap();
|
||||
let spider_def = creature_definitions.get("SPIDER").unwrap();
|
||||
let site = Site::new(
|
||||
site_def.clone(),
|
||||
"Gorbo's cave",
|
||||
vec![
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(
|
||||
creature_definitions.get("DRAGON").unwrap().clone(),
|
||||
),
|
||||
Creature::generate_from_def(
|
||||
creature_definitions.get("INDESTRUCTIBLE").unwrap().clone(),
|
||||
),
|
||||
]),
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(spider_def.clone()),
|
||||
]),
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
]),
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(spider_def.clone()),
|
||||
Creature::generate_from_def(spider_def.clone()),
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
||||
let group = TravelGroup::new(
|
||||
vec![
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
],
|
||||
WorldCoords { x: 0, y: 0 },
|
||||
Some(WorldPoint::Coords(WorldCoords { x: 0, y: 0 })),
|
||||
Some(WorldPoint::Site(site.id())),
|
||||
);
|
||||
|
||||
world.sites.push(site);
|
||||
world.travel_groups.push(group);
|
||||
std::fs::write("world.bin", bincode::serialize(&world).unwrap()).unwrap();
|
||||
world
|
||||
}
|
||||
};
|
||||
|
||||
for site in world.sites.iter() {
|
||||
println!("{site}");
|
||||
}
|
||||
|
||||
for group in world.travel_groups.iter() {
|
||||
println!("{group}");
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
loop {
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
if ["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 - start).as_micros());
|
||||
}
|
||||
}
|
||||
|
||||
fn resources_path() -> PathBuf {
|
||||
PathBuf::from(".").join("defs")
|
||||
}
|
||||
Loading…
Reference in New Issue