diff options
Diffstat (limited to 'server/src/data')
| -rw-r--r-- | server/src/data/demands.rs | 94 | ||||
| -rw-r--r-- | server/src/data/mod.rs | 388 | 
2 files changed, 0 insertions, 482 deletions
| diff --git a/server/src/data/demands.rs b/server/src/data/demands.rs deleted file mode 100644 index 2501e225..00000000 --- a/server/src/data/demands.rs +++ /dev/null @@ -1,94 +0,0 @@ -/* -    Hurry Curry! - a game about cooking -    Copyright 2024 metamuffin - -    This program is free software: you can redistribute it and/or modify -    it under the terms of the GNU Affero General Public License as published by -    the Free Software Foundation, version 3 of the License only. - -    This program is distributed in the hope that it will be useful, -    but WITHOUT ANY WARRANTY; without even the implied warranty of -    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -    GNU Affero General Public License for more details. - -    You should have received a copy of the GNU Affero General Public License -    along with this program.  If not, see <https://www.gnu.org/licenses/>. - -*/ -use super::Demand; -use crate::interaction::Recipe; -use hurrycurry_protocol::{ItemIndex, TileIndex}; -use std::collections::{HashMap, HashSet}; - -pub fn generate_demands( -    tiles: HashSet<TileIndex>, -    items: HashSet<ItemIndex>, -    raw_demands: &[(ItemIndex, Option<ItemIndex>, f32)], -    recipes: &[Recipe], -) -> Vec<Demand> { -    let recipes = recipes -        .iter() -        .filter(|r| r.tile().map(|t| tiles.contains(&t)).unwrap_or(true)) -        .collect::<Vec<_>>(); - -    let mut producable = HashMap::new(); - -    for i in &items { -        producable.insert(*i, 0.0); -    } - -    loop { -        let prod_count = producable.len(); - -        for r in &recipes { -            let output_count = r.outputs().iter().filter(|o| !items.contains(&o)).count(); -            let Some(ingred_cost) = r -                .inputs() -                .iter() -                .map(|i| producable.get(i).copied()) -                .reduce(|a, b| { -                    if let (Some(a), Some(b)) = (a, b) { -                        Some(a + b) -                    } else { -                        None -                    } -                }) -                .unwrap_or(Some(0.)) -            else { -                continue; -            }; - -            let base_cost = match r { -                Recipe::Passive { duration, .. } => 2. + duration * 0.1, -                Recipe::Active { duration, .. } => 2. + duration, -                Recipe::Instant { .. } => 1., -            }; - -            let output_cost = (ingred_cost + base_cost) / output_count as f32; -            for o in r.outputs() { -                let cost = producable.entry(o).or_insert(f32::INFINITY); -                *cost = cost.min(output_cost); -            } -        } - -        if prod_count == producable.len() { -            break; -        } -    } - -    raw_demands -        .iter() -        .filter_map(|(i, o, d)| { -            if let Some(cost) = producable.get(i) { -                Some(Demand { -                    from: *i, -                    to: *o, -                    duration: *d, -                    points: *cost as i64, -                }) -            } else { -                None -            } -        }) -        .collect() -} diff --git a/server/src/data/mod.rs b/server/src/data/mod.rs deleted file mode 100644 index 28347a25..00000000 --- a/server/src/data/mod.rs +++ /dev/null @@ -1,388 +0,0 @@ -/* -    Hurry Curry! - a game about cooking -    Copyright 2024 metamuffin -    Copyright 2024 nokoe - -    This program is free software: you can redistribute it and/or modify -    it under the terms of the GNU Affero General Public License as published by -    the Free Software Foundation, version 3 of the License only. - -    This program is distributed in the hope that it will be useful, -    but WITHOUT ANY WARRANTY; without even the implied warranty of -    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -    GNU Affero General Public License for more details. - -    You should have received a copy of the GNU Affero General Public License -    along with this program.  If not, see <https://www.gnu.org/licenses/>. - -*/ -use crate::{ -    entity::{construct_entity, Entity, EntityDecl}, -    interaction::Recipe, -}; -use anyhow::{anyhow, bail, Result}; -use demands::generate_demands; -use hurrycurry_protocol::{ -    glam::{IVec2, Vec2}, -    DemandIndex, ItemIndex, MapMetadata, RecipeIndex, TileIndex, -}; -use serde::{Deserialize, Serialize}; -use std::{ -    collections::{HashMap, HashSet}, -    fs::File, -    path::PathBuf, -    str::FromStr, -    sync::{Mutex, RwLock}, -}; -use tokio::fs::read_to_string; - -pub mod demands; - -#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)] -#[serde(rename_all = "snake_case")] -pub enum Action { -    #[default] -    Never, -    Passive, -    Active, -    Instant, -    Demand, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct RecipeDecl { -    #[serde(default)] -    tile: Option<String>, -    #[serde(default)] -    inputs: Vec<String>, -    #[serde(default)] -    outputs: Vec<String>, -    #[serde(default)] -    action: Action, -    #[serde(default)] -    warn: bool, -    #[serde(default)] -    revert_duration: Option<f32>, -    #[serde(default)] -    duration: Option<f32>, -    #[serde(default)] -    points: Option<i64>, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct InitialMap { -    map: Vec<String>, -    tiles: HashMap<char, String>, -    #[serde(default)] -    items: HashMap<char, String>, -    collider: Vec<String>, -    walkable: Vec<String>, -    chef_spawn: char, -    customer_spawn: char, -    #[serde(default)] -    entities: Vec<EntityDecl>, -    #[serde(default)] -    tile_entities: HashMap<char, EntityDecl>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DemandDecl { -    from: String, -    to: Option<String>, -    duration: f32, -    points: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Demand { -    pub from: ItemIndex, -    pub to: Option<ItemIndex>, -    pub duration: f32, -    pub points: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -#[rustfmt::skip] -pub struct Gamedata { -    pub spec: String, -    pub item_names: Vec<String>, -    pub tile_names: Vec<String>, -    pub tile_collide: Vec<bool>, -    pub tile_interact: Vec<bool>, -    pub map: HashMap<String, MapMetadata>, -    #[serde(skip)] pub recipes: Vec<Recipe>, -    #[serde(skip)] pub demands: Vec<Demand>, -    #[serde(skip)] pub initial_map: HashMap<IVec2, (TileIndex, Option<ItemIndex>)>, -    #[serde(skip)] pub chef_spawn: Vec2, -    #[serde(skip)] pub customer_spawn: Vec2, -    #[serde(skip)] pub entities: Vec<Entity>, -} - -#[derive(Debug, Deserialize, Default)] -pub struct DataIndex { -    pub maps: HashMap<String, MapMetadata>, -    pub demands: HashSet<String>, -    pub recipes: HashSet<String>, -} - -pub static DATA_DIR: Mutex<Option<PathBuf>> = Mutex::new(None); -fn data_dir() -> PathBuf { -    DATA_DIR -        .lock() -        .unwrap() -        .to_owned() -        .unwrap_or_else(|| PathBuf::from_str("data").unwrap()) -} - -impl DataIndex { -    pub fn reload(&mut self) -> Result<()> { -        *self = serde_yml::from_reader(File::open(data_dir().join("index.yaml"))?)?; -        Ok(()) -    } - -    pub async fn read_map(&self, name: &str) -> Result<String> { -        if !self.maps.contains_key(name) { -            bail!("unknown map: {name:?}"); -        } -        let path = data_dir().join(format!("maps/{name}.yaml")); -        Ok(read_to_string(path).await?) -    } -    pub async fn read_demands(&self, name: &str) -> Result<String> { -        if !self.demands.contains(name) { -            bail!("unknown demands: {name:?}"); -        } -        let path = data_dir().join(format!("demands/{name}.yaml")); -        Ok(read_to_string(path).await?) -    } -    pub async fn read_recipes(&self, name: &str) -> Result<String> { -        if !self.recipes.contains(name) { -            bail!("unknown recipes: {name:?}"); -        } -        let path = data_dir().join(format!("recipes/{name}.yaml")); -        Ok(read_to_string(path).await?) -    } - -    pub async fn generate(&self, spec: String) -> Result<Gamedata> { -        let (map, recipes) = spec.split_once("-").unwrap_or((spec.as_str(), "default")); - -        let map_in = serde_yml::from_str(&self.read_map(map).await?)?; -        let recipes_in = serde_yml::from_str(&self.read_recipes(recipes).await?)?; - -        let mut gd = Gamedata::build(spec, map_in, recipes_in)?; -        gd.map = self.maps.clone(); -        Ok(gd) -    } -} - -impl Gamedata { -    pub fn build(spec: String, map_in: InitialMap, recipes_in: Vec<RecipeDecl>) -> Result<Self> { -        let reg = ItemTileRegistry::default(); -        let mut recipes = Vec::new(); -        let mut entities = Vec::new(); -        let mut raw_demands = Vec::new(); - -        for mut r in recipes_in { -            let r2 = r.clone(); -            let mut inputs = r.inputs.into_iter().map(|i| reg.register_item(i)); -            let mut outputs = r.outputs.into_iter().map(|o| reg.register_item(o)); -            let tile = r.tile.map(|t| reg.register_tile(t)); -            match r.action { -                Action::Never => {} -                Action::Passive => recipes.push(Recipe::Passive { -                    duration: r.duration.ok_or(anyhow!("duration for passive missing"))?, -                    warn: r.warn, -                    tile, -                    revert_duration: r.revert_duration, -                    input: inputs -                        .next() -                        .ok_or(anyhow!("passive recipe without input"))?, -                    output: outputs.next(), -                }), -                Action::Active => recipes.push(Recipe::Active { -                    duration: r.duration.ok_or(anyhow!("duration for active missing"))?, -                    tile, -                    input: inputs -                        .next() -                        .ok_or(anyhow!("active recipe without input"))?, -                    outputs: [outputs.next(), outputs.next()], -                }), -                Action::Instant => { -                    recipes.push(Recipe::Instant { -                        points: r.points.take().unwrap_or(0), -                        tile, -                        inputs: [inputs.next(), inputs.next()], -                        outputs: [outputs.next(), outputs.next()], -                    }); -                } -                Action::Demand => raw_demands.push(( -                    inputs.next().ok_or(anyhow!("demand needs inputs"))?, -                    outputs.next(), -                    r.duration.unwrap_or(10.), -                )), -            } -            assert_eq!(inputs.next(), None, "{r2:?} inputs left over"); -            assert_eq!(outputs.next(), None, "{r2:?} outputs left over"); -            assert_eq!(r.points, None, "points specified where not possible") -        } - -        // TODO -        // for d in demands_in { -        //     demands.push(Demand { -        //         from: reg.register_item(d.from), -        //         to: d.to.map(|to| reg.register_item(to)), -        //         duration: d.duration, -        //         points: d.points, -        //     }) -        // } - -        let mut chef_spawn = Vec2::new(0., 0.); -        let mut customer_spawn = Vec2::new(0., 0.); -        let mut initial_map = HashMap::new(); -        let mut tiles_used = HashSet::new(); -        let mut items_used = HashSet::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(); -                if let Some(ent) = map_in.tile_entities.get(&tile) { -                    entities.push(construct_entity(Some(pos), ent, ®)?); -                } -                let itemname = map_in.items.get(&tile).cloned(); -                let tile = reg.register_tile(tilename); -                let item = itemname.map(|i| reg.register_item(i)); -                tiles_used.insert(tile); -                if let Some(i) = item { -                    items_used.insert(i); -                }; -                initial_map.insert(pos, (tile, item)); -            } -        } - -        entities.extend( -            map_in -                .entities -                .iter() -                .map(|decl| construct_entity(None, decl, ®)) -                .try_collect::<Vec<_>>()?, -        ); - -        let demands = generate_demands(tiles_used, items_used, &raw_demands, &recipes); - -        let item_names = reg.items.into_inner().unwrap(); -        let tile_names = reg.tiles.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 { -            spec, -            demands, -            tile_collide, -            tile_interact, -            recipes, -            map: HashMap::new(), -            initial_map, -            item_names, -            entities, -            tile_names, -            chef_spawn, -            customer_spawn, -        }) -    } -} - -#[derive(Default)] -pub struct ItemTileRegistry { -    tiles: RwLock<Vec<String>>, -    items: RwLock<Vec<String>>, -} - -impl ItemTileRegistry { -    pub fn register_tile(&self, name: String) -> TileIndex { -        TileIndex(Self::register(&self.tiles, name)) -    } -    pub fn register_item(&self, name: String) -> ItemIndex { -        ItemIndex(Self::register(&self.items, name)) -    } -    fn register(db: &RwLock<Vec<String>>, name: String) -> usize { -        let mut db = db.write().unwrap(); -        if let Some(index) = db.iter().position(|e| e == &name) { -            index -        } else { -            let index = db.len(); -            db.push(name); -            index -        } -    } -} - -impl Gamedata { -    pub fn tile_name(&self, index: TileIndex) -> &String { -        &self.tile_names[index.0] -    } -    pub fn is_tile_colliding(&self, index: TileIndex) -> bool { -        self.tile_collide[index.0] -    } -    pub fn is_tile_interactable(&self, index: TileIndex) -> bool { -        self.tile_interact[index.0] -    } -    pub fn item_name(&self, index: ItemIndex) -> &String { -        &self.item_names[index.0] -    } -    pub fn recipe(&self, index: RecipeIndex) -> &Recipe { -        &self.recipes[index.0] -    } -    pub fn demand(&self, index: DemandIndex) -> &Demand { -        &self.demands[index.0] -    } -    pub fn get_tile_by_name(&self, name: &str) -> Option<TileIndex> { -        self.tile_names -            .iter() -            .position(|t| t == name) -            .map(TileIndex) -    } -    pub fn get_item_by_name(&self, name: &str) -> Option<ItemIndex> { -        self.item_names -            .iter() -            .position(|t| t == name) -            .map(ItemIndex) -    } -    pub fn recipes(&self) -> impl Iterator<Item = (RecipeIndex, &Recipe)> { -        self.recipes -            .iter() -            .enumerate() -            .map(|(i, e)| (RecipeIndex(i), e)) -    } -} -/* -    Hurry Curry! - a game about cooking -    Copyright 2024 metamuffin - -    This program is free software: you can redistribute it and/or modify -    it under the terms of the GNU Affero General Public License as published by -    the Free Software Foundation, version 3 of the License only. - -    This program is distributed in the hope that it will be useful, -    but WITHOUT ANY WARRANTY; without even the implied warranty of -    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -    GNU Affero General Public License for more details. - -    You should have received a copy of the GNU Affero General Public License -    along with this program.  If not, see <https://www.gnu.org/licenses/>. - -*/
\ No newline at end of file | 
