engine/gavania-core/src/game/game.rs
2024-08-23 13:22:09 +02:00

634 lines
20 KiB
Rust

// 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<AssetPath>,
// possible start map, where players get spawned on connection
pub start_map: Option<String>,
// maps that are going to be ignored
pub black_list: Vec<String>,
}
pub struct Game {
engine: Arc<Engine>,
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<HashMap<String, AssetPath>>,
pub black_listed_maps: RwLock<HashMap<String, AssetPath>>,
pub tiles: RwLock<HashMap<String, AssetPath>>,
settings_file_location: AssetPath,
item_system: Promise<Arc<ItemSystem>>,
entity_manager: Mutex<EntityManager>,
slide_images: Mutex<HashMap<String, Arc<Image>>>,
pub change_map_context: LuaFunction,
map_info: MapInformation,
}
pub type GameHandle = WeakHandle<Game>;
pub type StrongGame = StrongHandle<Game>;
impl GameHandle {
pub fn gui_builder(&self, path: &str) -> Result<Arc<GuiBuilder>> {
let strong = self.upgrade();
GuiBuilder::new(strong.engine.gui_handler(), &strong.build_data_path(path))
}
pub fn gui_snippet(&self, path: &str) -> Result<Arc<GuiSnippet>> {
let strong = self.upgrade();
GuiSnippet::new(strong.engine.gui_handler(), &strong.build_data_path(path))
}
pub fn controller_icon(&self, button: ControllerButton) -> Result<Arc<Image>> {
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<Option<&'a str>>) -> Result<Settings> {
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<StrongGame> {
#[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<E>(&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<Engine> {
&self.engine
}
pub fn game_settings(&self) -> &GameSection {
&self.game_settings
}
pub fn item_system(&self) -> Arc<ItemSystem> {
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<String, Arc<Image>>> {
self.slide_images.lock().unwrap()
}
pub fn gui_builder(&self, path: &str) -> Result<Arc<GuiBuilder>> {
GuiBuilder::new(self.engine.gui_handler(), &self.build_data_path(path))
}
pub fn gui_snippet(&self, path: &str) -> Result<Arc<GuiSnippet>> {
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::<Draw>() {
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::<BoundingBox>() {
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::<Location>()
.ok()
.map(|location| location.transformation_matrix());
let planes = frustum_planes.as_array();
let corners: Vec<Vector3<f32>> = 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<Vec<String>> {
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<String> {
let map_directories = self.map_directories();
let home_map = self.home_map().map(|s| s.as_str());
let ignore_maps: Vec<String> = 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) {}
}