use std::{ fmt, path::{Path, PathBuf}, }; use super::{ error::{Error, Result}, minemod::{self, MineMod}, scan, }; /// Represents an on-disk Minetest game. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Game { path: PathBuf, } impl Game { /// Open the given directory as a Minetest game. /// /// Note that the path may be relative, but only to the parent directory of the actual game. /// This is because Minetest uses the game's directory name to identify the game /// ([`Game::technical_name`]), so we need this information. pub fn open>(path: P) -> Result { Game::open_path(path.as_ref()) } fn open_path(path: &Path) -> Result { if path.file_name().is_none() { return Err(Error::InvalidGameDir(path.into())); } let conf = path.join("game.conf"); if !conf.is_file() { return Err(Error::InvalidGameDir(path.into())); } Ok(Game { path: path.into() }) } /// Returns the technical name of this game. /// /// This is the name that is used by minetest to identify the game. pub fn technical_name(&self) -> String { self.path .file_name() .expect("Somebody constructed an invalid `Game`") .to_str() .expect("Non-UTF8 directory encountered") .into() } /// Returns all mods that this game provides. pub fn mods(&self) -> Result> { let path = self.path.join("mods"); let mut mods = vec![]; for container in scan(&path, |p| minemod::open_mod_or_pack(p))? { mods.extend(container.mods()?); } Ok(mods) } } impl fmt::Display for Game { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.technical_name()) } }