use crate::{interaction::Recipe, protocol::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)] pub tile: Option, #[serde(default)] pub inputs: Vec, #[serde(default)] pub outputs: Vec, #[serde(default)] pub action: Action, } #[derive(Debug, Clone, Deserialize)] pub struct InitialMap { map: Vec, tiles: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Gamedata { pub recipes: Vec, pub item_names: Vec, pub tile_names: Vec, #[serde(skip)] pub initial_map: HashMap, pub spawn: Vec2, } pub fn build_gamedata(recipes_in: Vec, map_in: InitialMap) -> Gamedata { let item_names = RwLock::new(Vec::new()); let tile_names = RwLock::new(Vec::new()); let mut recipes = Vec::new(); for r in recipes_in { let r2 = r.clone(); let mut inputs = r.inputs.into_iter().map(|i| register(&item_names, i)); let mut outputs = r.outputs.into_iter().map(|o| register(&item_names, o)); let tile = r.tile.map(|t| register(&tile_names, t)); match r.action { Action::Never => {} Action::Passive(duration) => recipes.push(Recipe::Passive { duration, 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:?}"); } let mut 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); let mut tilename = map_in.tiles[&tile.to_string()].clone(); if tilename == "spawn" { spawn = pos.as_vec2(); tilename = "floor".to_owned(); } let tile = register(&tile_names, tilename); initial_map.insert(pos, tile); } } Gamedata { recipes, initial_map, item_names: item_names.into_inner().unwrap(), tile_names: tile_names.into_inner().unwrap(), 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 get_tile(&self, name: &str) -> Option { self.tile_names.iter().position(|t| t == name) } } impl Action { pub fn duration(&self) -> f32 { match self { Action::Instant | Action::Never => 0., Action::Passive(x) | Action::Active(x) => *x, } } }