// engine use engine::prelude::*; use assetpath::AssetPath; // use lua_wrapper::LuaFunction; use anyhow::Result; use lua_wrapper::LuaFunction; // std use std::collections::HashMap; use std::sync::{Mutex, MutexGuard, RwLock}; use std::{fs::create_dir_all, path::Path, sync::Arc}; // game use crate::game::configloader::*; use crate::game::content::prelude::*; use crate::loader::settings::*; use cgmath::Vector3; use promise::Promise; use super::handle::{StrongHandle, WeakHandle}; #[derive(Default, Debug, Clone)] pub struct MapInformation { // root directories for maps pub map_directories: Vec, // possible start map, where players get spawned on connection pub start_map: Option, // maps that are going to be ignored pub black_list: Vec, } pub struct Game { engine: Arc, game_settings: GameSection, _lua_scripts: LuaScripts, data_directory: String, pub attribute_settings: AttributeSettings, pub item_settings: ItemSettings, pub experience_settings: ExperienceSettings, pub mob_settings: MobSettings, pub ability_settings: AbilitySettings, pub maps: RwLock>, pub black_listed_maps: RwLock>, pub tiles: RwLock>, settings_file_location: AssetPath, item_system: Promise>, entity_manager: Mutex, slide_images: Mutex>>, pub change_map_context: LuaFunction, map_info: MapInformation, } pub type GameHandle = WeakHandle; pub type StrongGame = StrongHandle; impl GameHandle { pub fn gui_builder(&self, path: &str) -> Result> { let strong = self.upgrade(); GuiBuilder::new(strong.engine.gui_handler(), &strong.build_data_path(path)) } pub fn gui_snippet(&self, path: &str) -> Result> { let strong = self.upgrade(); GuiSnippet::new(strong.engine.gui_handler(), &strong.build_data_path(path)) } pub fn controller_icon(&self, button: ControllerButton) -> Result> { let game = self.upgrade(); let engine = game.engine(); engine .settings() .controller_button_for(engine, button, ControllerType::XBox) } pub fn build_data_path(&self, path: &str) -> AssetPath { self.upgrade().build_data_path(path) } } pub struct Settings { pub core_settings: CoreSettings, pub user_settings: UserSettings, pub data_path: String, pub user_settings_file: AssetPath, } impl Game { pub fn load_settings<'a>(data_path: impl Into>) -> Result { let data_path = data_path.into().unwrap_or("data"); println!("Data directory: {}", data_path); let mut core_settings = CoreSettings::load((data_path, "settings.conf"))?; let mut user_settings = UserSettings::load((data_path, "user_settings.conf"))?; let gavania_save_dir = settings_file_dir(); Self::verify_dir_existence(&gavania_save_dir)?; let user_settings_file = AssetPath::from((gavania_save_dir, "user_settings.conf")); if user_settings_file.exists() { user_settings.load_with_default(user_settings_file.clone())?; } else { user_settings.file_name = user_settings_file.clone(); } user_settings.store()?; Self::apply_data_dir(&mut core_settings, data_path); Ok(Settings { core_settings, user_settings, data_path: data_path.to_string(), user_settings_file, }) } pub fn new(settings: Settings) -> Result { #[cfg(target_os = "linux")] { Self::set_radeon_ray_tracing(); #[cfg(feature = "wayland")] Self::check_wayland_for_sdl(); } let game_settings = settings.core_settings.game.clone(); let lua_scripts = settings.core_settings.lua_scripts.clone(); let tile_directory = settings.core_settings.engine.tile_directory.full_path(); let map_info = settings.core_settings.map_info(); #[allow(unused_mut)] let mut engine_create_info = into_engine_info(settings.core_settings, settings.user_settings); { engine_create_info.app_info.application_name = "Gavania".to_string(); engine_create_info.app_info.application_version = 1; } let attribute_settings = AttributeSettings::load((settings.data_path.as_str(), "configs/stats.conf"))?; let mut item_settings = ItemSettings::load((settings.data_path.as_str(), "configs/items.conf"))?; let experience_settings = ExperienceSettings::load((settings.data_path.as_str(), "configs/experience.conf"))?; let mob_settings = MobSettings::load((settings.data_path.as_str(), "configs/mobs.conf"))?; let mut ability_settings = AbilitySettings::load((settings.data_path.as_str(), "configs/abilities.conf"))?; Self::apply_data_dir_to_configs( &mut item_settings, &mut ability_settings, &settings.data_path, ); // ----------------- maps --------------------- let mut map_map = HashMap::new(); let mut black_listed_maps = HashMap::new(); for map_directory in map_info.map_directories.iter() { let map_vector = search_dir_recursively(&map_directory.full_path(), "")?; 'outer: for file in &map_vector { let s = file.full_path(); let path = Path::new(s.as_str()); if let Some(name) = path.file_stem() { if let Some(name_str) = name.to_str() { let string = name_str.to_string(); for blacked in map_info.black_list.iter() { if *blacked == string { assert!( black_listed_maps .insert(string.clone(), file.clone()) .is_none(), "blacklisted map already present: {}", string ); continue 'outer; } } assert!( map_map.insert(string.clone(), file.clone()).is_none(), "map already present: {}", string ); } } } } // ----------------- tiles -------------------- let tile_vector = search_dir_recursively(&tile_directory, ".png")?; let mut tile_map = HashMap::new(); for file in &tile_vector { let s = file.full_path(); let path = Path::new(s.as_str()); if let Some(name) = path.file_stem() { if let Some(name_str) = name.to_str() { let string = name_str.to_string(); tile_map.insert(string.clone(), file.clone()); } } } let game = { engine_create_info.gui_info.resource_directory = { let mut path = AssetPath::from(settings.data_path.as_str()); path.assume_prefix_free(); path }; engine_create_info.resource_base_path = settings.data_path.clone(); let engine = Engine::new(engine_create_info.clone())?; let item_system = Promise::new({ let engine = engine.clone(); let item_settings = item_settings.clone(); let ability_settings = ability_settings.clone(); let attribute_settings = attribute_settings.clone(); let ability_directory = game_settings.ability_directory.clone(); let data_path = settings.data_path.clone(); move || { Ok(Arc::new(ItemSystem::new( &engine, &item_settings, &ability_settings, &attribute_settings, &ability_directory, &data_path, )?)) } }); StrongHandle::new(Game { engine, maps: RwLock::new(map_map), black_listed_maps: RwLock::new(black_listed_maps), tiles: RwLock::new(tile_map), change_map_context: LuaFunction::new(&lua_scripts.change_map)?, game_settings, _lua_scripts: lua_scripts, data_directory: settings.data_path.to_string(), attribute_settings, item_settings, experience_settings, mob_settings, ability_settings, item_system, entity_manager: Mutex::new(EntityManager::new( engine_create_info.asset_directories.entity_file_directory, )?), settings_file_location: settings.user_settings_file, slide_images: Mutex::new(HashMap::new()), map_info, }) }; Ok(game) } fn verify_dir_existence(dir: &str) -> Result<()> { let path = Path::new(&dir); if !path.exists() { create_dir_all(path)?; } Ok(()) } pub fn run(&self) -> Result<()> { self.engine.run() } pub fn set_game_state(&self, game_object: E) -> Result<()> where E: EngineObject + Send + Sync, { self.engine.set_game_object(Some(game_object)) } pub fn quit(&self) -> Result<()> { self.store_settings()?; self.engine.quit()?; Ok(()) } #[cfg(target_os = "linux")] fn set_radeon_ray_tracing() { std::env::set_var("RADV_PERFTEST", "rt"); } #[cfg(feature = "wayland")] #[cfg(target_os = "linux")] fn check_wayland_for_sdl() { if let Ok(session_type) = std::env::var("XDG_SESSION_TYPE") { if session_type == "wayland" { std::env::set_var("SDL_VIDEODRIVER", "wayland"); } } } pub fn map_directories(&self) -> &[AssetPath] { &self.map_info.map_directories } pub fn home_map(&self) -> Option<&String> { self.map_info.start_map.as_ref() } pub fn black_listed_maps(&self) -> &[String] { &self.map_info.black_list } pub fn engine(&self) -> &Arc { &self.engine } pub fn game_settings(&self) -> &GameSection { &self.game_settings } pub fn item_system(&self) -> Arc { self.item_system.get() } pub fn data_directory(&self) -> &str { &self.data_directory } pub fn build_data_path(&self, path: &str) -> AssetPath { AssetPath::from((self.data_directory.as_str(), path)) } pub fn entity_manager(&self) -> MutexGuard<'_, EntityManager> { self.entity_manager.lock().unwrap() } pub fn slide_images(&self) -> MutexGuard<'_, HashMap>> { self.slide_images.lock().unwrap() } pub fn gui_builder(&self, path: &str) -> Result> { GuiBuilder::new(self.engine.gui_handler(), &self.build_data_path(path)) } pub fn gui_snippet(&self, path: &str) -> Result> { GuiSnippet::new(self.engine.gui_handler(), &self.build_data_path(path)) } pub fn store_settings(&self) -> Result<()> { let mut user_settings = UserSettings::load(self.settings_file_location.clone())?; // first update settings if self.engine.window_config().is_fullscreen() { user_settings.window.fullscreen = true; } else { user_settings.window.fullscreen = false; let render_core = self.engine.context().render_core(); user_settings.window.width = render_core.width(); user_settings.window.height = render_core.height(); } { let sound = self.engine.sound(); user_settings.audio.master = sound.volume("master")?; user_settings.audio.gui = sound.volume("gui")?; user_settings.audio.sfx = sound.volume("sfx")?; user_settings.audio.music = sound.volume("music")?; } // check if renderer type actually changed // if so, clear all loaded entities let renderer_type = self.engine.settings().graphics_info()?.render_type; if renderer_type != user_settings.graphics.renderer_type { user_settings.graphics.renderer_type = renderer_type; self.engine.assets().clear()?; } user_settings.store()?; Ok(()) } pub fn set_frustum_check(scene: &mut Scene) { let entity_name = "Lightning".to_string(); scene.set_frustum_check(move |entity, _view_proj, _view_frustum, frustum_planes| { // filter everything that isnt Draw if !entity.contains_component::() { if entity.debug_name.as_ref() == Some(&entity_name) { println!("{entity_name}-Entity does not contain Draw"); } return Ok(false); } // filter everything that isnt Bounding Box let bb = match entity.get_component::() { Ok(bounding_box) => bounding_box, Err(_) => { if entity.debug_name.as_ref() == Some(&entity_name) { println!("{entity_name}-Entity does not contain BoundingBox"); } return Ok(false); } }; let transformation_matrix = entity .get_component::() .ok() .map(|location| location.transformation_matrix()); let planes = frustum_planes.as_array(); let corners: Vec> = bb .corners() .iter() .map(|corner| match transformation_matrix { Some(m) => (m * corner.extend(1.0)).xyz(), None => *corner, }) .collect(); // if there is a plane where all corners are outside, the box is not visible for plane in planes { let mut outside = true; for corner in corners.iter() { if !plane.is_above(*corner) { outside = false; break; } } if outside { return Ok(false); } } if entity.debug_name.as_ref() == Some(&entity_name) { println!("{entity_name}-Entity is inside"); } Ok(true) }); } fn apply_data_dir(settings: &mut CoreSettings, data_dir: &str) { // gui settings.gui.font.set_prefix(data_dir); // engine settings.engine.tile_directory.set_prefix(data_dir); settings.engine.entity_directory.set_prefix(data_dir); settings.engine.gltf_directory.set_prefix(data_dir); settings .engine .xbox_controller_directory .set_prefix(data_dir); settings .engine .steam_controller_directory .set_prefix(data_dir); settings .engine .ps4_controller_directory .set_prefix(data_dir); settings.engine.light_key.set_prefix(data_dir); settings.engine.dark_key.set_prefix(data_dir); // game settings.game.sounds_directory.set_prefix(data_dir); settings.game.music_directory.set_prefix(data_dir); settings.game.ability_icon_directory.set_prefix(data_dir); settings.game.ability_directory.set_prefix(data_dir); settings.game.particle_directory.set_prefix(data_dir); settings.game.slides_directory.set_prefix(data_dir); settings.game.npc_directory.set_prefix(data_dir); // map info settings.map_infos.directory.set_prefix(data_dir); // scripts settings.lua_scripts.change_map.set_prefix(data_dir); } fn apply_data_dir_to_configs( item_settings: &mut ItemSettings, ability_settings: &mut AbilitySettings, data_dir: &str, ) { item_settings.icon_paths.amulet.set_prefix(data_dir); item_settings.icon_paths.background.set_prefix(data_dir); item_settings.icon_paths.boots.set_prefix(data_dir); item_settings.icon_paths.chest.set_prefix(data_dir); item_settings.icon_paths.helmet.set_prefix(data_dir); item_settings.icon_paths.jewel.set_prefix(data_dir); item_settings.icon_paths.ring.set_prefix(data_dir); item_settings.icon_paths.belt.set_prefix(data_dir); item_settings.icon_paths.gloves.set_prefix(data_dir); item_settings.icon_paths.main_hand.set_prefix(data_dir); item_settings.icon_paths.off_hand.set_prefix(data_dir); item_settings.jewel_paths.first.set_prefix(data_dir); item_settings.jewel_paths.second.set_prefix(data_dir); item_settings.jewel_paths.third.set_prefix(data_dir); item_settings.jewel_paths.fourth.set_prefix(data_dir); ability_settings.icons.book.set_prefix(data_dir); ability_settings.icons.damage.set_prefix(data_dir); ability_settings.icons.projectile_speed.set_prefix(data_dir); ability_settings.icons.bounce.set_prefix(data_dir); ability_settings.icons.explosion.set_prefix(data_dir); ability_settings.icons.size.set_prefix(data_dir); ability_settings .icons .additional_projectiles .set_prefix(data_dir); ability_settings.icons.cool_down.set_prefix(data_dir); ability_settings.icons.distance.set_prefix(data_dir); ability_settings.icons.addon_background.set_prefix(data_dir); } fn gather_maps( map_directories: &[AssetPath], home_map: Option<&str>, ignore_maps: &[String], ) -> Result> { let mut maps = Vec::new(); for map_directory in map_directories.iter() { for map in search_dir_recursively(&map_directory.full_path(), "")? { let map_name = Path::new(&map.full_path()) .file_name() .expect("map is not a file name") .to_str() .expect("failed converting OsStr to str") .to_string(); if let Some(home) = home_map { if map_name == home { continue; } } if ignore_maps.contains(&map_name) { continue; } maps.push(map_name); } } Ok(maps) } pub fn next_map(&self, current_map: &str) -> Result { let map_directories = self.map_directories(); let home_map = self.home_map().map(|s| s.as_str()); let ignore_maps: Vec = self .black_listed_maps .read() .unwrap() .keys() .cloned() .collect(); let maps = Self::gather_maps(map_directories, home_map, &ignore_maps)?; let map_count = maps.len(); let next_map = self.change_map_context.execute(( current_map, home_map.unwrap_or(""), maps, map_count, ))?; Ok(next_map) } } impl Drop for Game { fn drop(&mut self) {} }