From 4dc15a1e86ef1ae985fdf36f1a84d07b1de99ea7 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Tue, 25 Jun 2024 23:34:10 +0200 Subject: server can change map at runtime --- server/src/data.rs | 239 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 147 insertions(+), 92 deletions(-) (limited to 'server/src/data.rs') diff --git a/server/src/data.rs b/server/src/data.rs index 64509f37..e980ccbd 100644 --- a/server/src/data.rs +++ b/server/src/data.rs @@ -19,9 +19,14 @@ use crate::{ interaction::Recipe, protocol::{DemandIndex, ItemIndex, RecipeIndex, TileIndex}, }; +use anyhow::{anyhow, bail, Result}; use glam::{IVec2, Vec2}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, sync::RwLock}; +use std::{ + collections::{HashMap, HashSet}, + fs::File, + sync::RwLock, +}; #[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)] #[serde(rename_all = "snake_case")] @@ -78,7 +83,9 @@ pub struct Demand { #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Gamedata { - recipes: Vec, + #[serde(skip)] + pub recipes: Vec, + #[serde(skip)] pub demands: Vec, pub item_names: Vec, pub tile_names: Vec, @@ -90,105 +97,153 @@ pub struct Gamedata { pub customer_spawn: Vec2, } -pub fn build_gamedata( - recipes_in: Vec, - map_in: InitialMap, - demands_in: Vec, -) -> Gamedata { - let item_names = RwLock::new(Vec::new()); - let tile_names = RwLock::new(Vec::new()); - let mut recipes = Vec::new(); - let mut demands = Vec::new(); - - for r in recipes_in { - let r2 = r.clone(); - let mut inputs = r - .inputs - .into_iter() - .map(|i| ItemIndex(register(&item_names, i))); - let mut outputs = r - .outputs - .into_iter() - .map(|o| ItemIndex(register(&item_names, o))); - let tile = r.tile.map(|t| TileIndex(register(&tile_names, t))); - match r.action { - Action::Never => {} - Action::Passive => recipes.push(Recipe::Passive { - duration: r.duration.expect("duration for passive missing"), - warn: r.warn, - tile, - revert_duration: r.revert_duration, - input: inputs.next().expect("passive recipe without input"), - output: outputs.next(), - }), - Action::Active => recipes.push(Recipe::Active { - duration: r.duration.expect("duration for active missing"), - tile, - input: inputs.next().expect("active recipe without input"), - outputs: [outputs.next(), outputs.next()], - }), - Action::Instant => { - recipes.push(Recipe::Instant { +#[derive(Debug, Deserialize, Default)] +pub struct DataIndex { + pub maps: HashSet, + pub demands: HashSet, + pub recipes: HashSet, +} + +impl DataIndex { + pub fn reload(&mut self) -> anyhow::Result<()> { + *self = serde_yaml::from_reader(File::open("data/index.yaml")?)?; + Ok(()) + } + + pub fn generate(&self, spec: String) -> anyhow::Result { + let [demands, map, recipes] = spec + .split("-") + .collect::>() + .try_into() + .map_err(|_| anyhow!("data specification malformed"))?; + + if !self.demands.contains(demands) { + bail!("unknown demands: {demands:?}"); + } + if !self.maps.contains(map) { + bail!("unknown map: {map:?}"); + } + if !self.recipes.contains(recipes) { + bail!("unknown recipes: {recipes:?}"); + } + + let demands_path = format!("data/demands/{demands}.yaml"); + let map_path = format!("data/maps/{map}.yaml"); + let recipes_path = format!("data/recipes/{recipes}.yaml"); + + let demands_in = serde_yaml::from_reader(File::open(demands_path).unwrap()).unwrap(); + let map_in = serde_yaml::from_reader(File::open(map_path).unwrap()).unwrap(); + let recipes_in = serde_yaml::from_reader(File::open(recipes_path).unwrap()).unwrap(); + + Ok(Gamedata::build(recipes_in, map_in, demands_in)?) + } +} + +impl Gamedata { + pub fn build( + recipes_in: Vec, + map_in: InitialMap, + demands_in: Vec, + ) -> Result { + let item_names = RwLock::new(Vec::new()); + let tile_names = RwLock::new(Vec::new()); + let mut recipes = Vec::new(); + let mut demands = Vec::new(); + + for r in recipes_in { + let r2 = r.clone(); + let mut inputs = r + .inputs + .into_iter() + .map(|i| ItemIndex(register(&item_names, i))); + let mut outputs = r + .outputs + .into_iter() + .map(|o| ItemIndex(register(&item_names, o))); + let tile = r.tile.map(|t| TileIndex(register(&tile_names, t))); + match r.action { + Action::Never => {} + Action::Passive => recipes.push(Recipe::Passive { + duration: r.duration.expect("duration for passive missing"), + warn: r.warn, + tile, + revert_duration: r.revert_duration, + input: inputs.next().expect("passive recipe without input"), + output: outputs.next(), + }), + Action::Active => recipes.push(Recipe::Active { + duration: r.duration.expect("duration for active missing"), tile, - inputs: [inputs.next(), inputs.next()], + input: inputs.next().expect("active recipe without input"), outputs: [outputs.next(), outputs.next()], - }); + }), + Action::Instant => { + recipes.push(Recipe::Instant { + tile, + inputs: [inputs.next(), inputs.next()], + outputs: [outputs.next(), outputs.next()], + }); + } } + assert_eq!(inputs.next(), None, "{r2:?}"); + assert_eq!(outputs.next(), None, "{r2:?}"); } - assert_eq!(inputs.next(), None, "{r2:?}"); - assert_eq!(outputs.next(), None, "{r2:?}"); - } - for d in demands_in { - demands.push(Demand { - from: ItemIndex(register(&item_names, d.from)), - to: ItemIndex(register(&item_names, d.to)), - duration: d.duration, - }) - } + for d in demands_in { + demands.push(Demand { + from: ItemIndex(register(&item_names, d.from)), + to: ItemIndex(register(&item_names, d.to)), + duration: d.duration, + }) + } - let mut chef_spawn = Vec2::new(0., 0.); - let mut customer_spawn = Vec2::new(0., 0.); - let mut initial_map = HashMap::new(); - for (y, line) in map_in.map.iter().enumerate() { - for (x, tile) in line.trim().chars().enumerate() { - let pos = IVec2::new(x as i32, y as i32); - if tile == map_in.chef_spawn { - chef_spawn = pos.as_vec2() + Vec2::splat(0.5); + let mut chef_spawn = Vec2::new(0., 0.); + let mut customer_spawn = Vec2::new(0., 0.); + let mut initial_map = HashMap::new(); + for (y, line) in map_in.map.iter().enumerate() { + for (x, tile) in line.trim().chars().enumerate() { + let pos = IVec2::new(x as i32, y as i32); + if tile == map_in.chef_spawn { + chef_spawn = pos.as_vec2() + Vec2::splat(0.5); + } + if tile == map_in.customer_spawn { + customer_spawn = pos.as_vec2() + Vec2::splat(0.5); + } + let tilename = map_in + .tiles + .get(&tile) + .ok_or(anyhow!("tile {tile} is undefined"))? + .clone(); + let itemname = map_in.items.get(&tile).cloned(); + let tile = TileIndex(register(&tile_names, tilename)); + let item = itemname.map(|i| ItemIndex(register(&item_names, i))); + initial_map.insert(pos, (tile, item)); } - if tile == map_in.customer_spawn { - customer_spawn = pos.as_vec2() + Vec2::splat(0.5); - } - let tilename = map_in.tiles[&tile].clone(); - let itemname = map_in.items.get(&tile).cloned(); - let tile = TileIndex(register(&tile_names, tilename)); - let item = itemname.map(|i| ItemIndex(register(&item_names, i))); - initial_map.insert(pos, (tile, item)); } - } - let item_names = item_names.into_inner().unwrap(); - let tile_names = tile_names.into_inner().unwrap(); - - let tile_collide = tile_names - .iter() - .map(|i| !map_in.walkable.contains(i)) - .collect(); - let tile_interact = tile_names - .iter() - .map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i)) - .collect(); - - Gamedata { - demands, - tile_collide, - tile_interact, - recipes, - initial_map, - item_names, - tile_names, - chef_spawn, - customer_spawn, + let item_names = item_names.into_inner().unwrap(); + let tile_names = tile_names.into_inner().unwrap(); + + let tile_collide = tile_names + .iter() + .map(|i| !map_in.walkable.contains(i)) + .collect(); + let tile_interact = tile_names + .iter() + .map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i)) + .collect(); + + Ok(Gamedata { + demands, + tile_collide, + tile_interact, + recipes, + initial_map, + item_names, + tile_names, + chef_spawn, + customer_spawn, + }) } } -- cgit v1.2.3-70-g09d2