use crate::{ interaction::Recipe, protocol::{DemandIndex, ItemIndex, RecipeIndex, TileIndex}, }; use glam::{IVec2, Vec2}; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, sync::RwLock}; #[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)] #[serde(rename_all = "snake_case")] pub enum Action { #[default] Never, Passive(f32), Active(f32), Instant, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RecipeDecl { #[serde(default)] tile: Option, #[serde(default)] inputs: Vec, #[serde(default)] outputs: Vec, #[serde(default)] action: Action, #[serde(default)] warn: bool, } #[derive(Debug, Clone, Deserialize)] pub struct InitialMap { map: Vec, tiles: HashMap, items: HashMap, collider: Vec, walkable: Vec, chef_spawn: char, customer_spawn: char, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DemandDecl { from: String, to: String, duration: f32, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Demand { pub from: ItemIndex, pub to: ItemIndex, pub duration: f32, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Gamedata { recipes: Vec, pub demands: Vec, pub item_names: Vec, pub tile_names: Vec, pub tile_collide: Vec, pub tile_interact: Vec, #[serde(skip)] pub initial_map: HashMap)>, pub chef_spawn: Vec2, 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(duration) => recipes.push(Recipe::Passive { duration, warn: r.warn, tile, input: inputs.next().expect("passive recipe without input"), output: outputs.next(), }), Action::Active(duration) => recipes.push(Recipe::Active { duration, tile, 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:?}"); } 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().char_indices() { let pos = IVec2::new(x as i32, y as i32); if tile == map_in.chef_spawn { chef_spawn = pos.as_vec2(); } if tile == map_in.customer_spawn { customer_spawn = pos.as_vec2(); } 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, } } fn register(db: &RwLock>, 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 { self.tile_names .iter() .position(|t| t == name) .map(TileIndex) } pub fn get_item_by_name(&self, name: &str) -> Option { self.item_names .iter() .position(|t| t == name) .map(ItemIndex) } pub fn recipes(&self) -> impl Iterator { self.recipes .iter() .enumerate() .map(|(i, e)| (RecipeIndex(i), e)) } } impl Action { pub fn duration(&self) -> f32 { match self { Action::Instant | Action::Never => 0., Action::Passive(x) | Action::Active(x) => *x, } } }