use anyhow::Result; use assetpath::AssetPath; use engine::prelude::*; use cgmath::Vector3; use std::path::Path; use crate::{ ability_type::AbilityType, damage_type::DamageType, items::{ability_addon::AbilityAddonCollection, ability_book::Ability}, }; use super::statistics::Statistics; const ABILITY_SUFFIX: &str = ".abil"; create_settings_section!( AbilityMetaSettings, "Meta", { icon: AssetPath, entity: String, particles: String, }, Serialize, Deserialize, ); create_settings_section!( AbilityParameterSettings, "Parameter", { // shared ability_type: AbilityType, light: bool, idle_sound: String, base_damage: u32, damage_per_level: u32, base_mana_cost: u32, mana_cost_per_level: u32, damage_type: DamageType, cool_down: PersistentDuration, // projectile trail: String, on_hit_particles: String, height_offset: f32, on_collision_sound: String, target_radius: f32, target_particles: String, speed: f32, distance: f32, // self-cast t_min: f32, procedure_count: u32, delay: PersistentDuration, duration: PersistentDuration, arc: f32, radius: f32, }, Serialize, Deserialize, ); create_settings_container!( AbilitySettings, { meta: AbilityMetaSettings, parameter: AbilityParameterSettings, }, Serialize, Deserialize, ); #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] pub struct AbilityLoader { file_path: AssetPath, name: String, pub settings: AbilitySettings, } impl AbilityLoader { pub fn load(data_path: &str, file: AssetPath) -> Result { let mut settings = AbilitySettings::load(file.clone())?; settings.meta.icon.set_prefix(data_path); let mut ability_loader = AbilityLoader::default(); ability_loader.file_path = file.clone(); ability_loader.name = Path::new(&file.full_path()) .file_stem() .expect("could not get file stem in ability loader") .to_str() .expect("failed converting OsStr to string") .to_string(); ability_loader.settings = settings; Ok(ability_loader) } pub fn save(&self) -> Result<()> { self.settings.store()?; Ok(()) } pub fn list_all_ability_files(ability_directory: &AssetPath) -> Result> { Ok(search_dir_recursively( &ability_directory.full_path(), ABILITY_SUFFIX, )?) } pub fn create_ability(&self) -> Ability { Ability { data: self.clone() } } pub fn name(&self) -> &str { &self.name } pub fn set_name(&mut self, name: &str) { self.name = name.to_string(); } pub fn set_path(&mut self, ability_directory: &str) { self.file_path = AssetPath::from(( ability_directory, format!("{}{}", self.name.clone(), ABILITY_SUFFIX), )); self.settings.file_name = self.file_path.clone(); } pub fn icon_name(&self) -> &AssetPath { &self.settings.meta.icon } pub fn entity_name(&self) -> &str { &self.settings.meta.entity } pub fn check_for_crit(dmg: u32, owner_stats: &Statistics) -> u32 { if Coin::flip(owner_stats.critical_hit_chance.raw() / 100.0) { let crit_dmg = ((owner_stats.critical_hit_damage.raw() + 1.0) * dmg as f32) as u32; println!("CRIT: {} from base {}", crit_dmg, dmg); crit_dmg } else { dmg } } pub fn base_damage(&self, ability_level: u32) -> u32 { self.settings.parameter.base_damage + self.settings.parameter.damage_per_level * (ability_level - 1) } pub fn damage( &self, ability_level: u32, addons: &AbilityAddonCollection, owner_stats: &Statistics, ) -> u32 { // calculate damage of base ability let ability_base_damage = self.base_damage(ability_level); // get bonus damage from statistics let stats_damage = match self.settings.parameter.damage_type { DamageType::Air => owner_stats.air_damage.raw(), DamageType::Fire => owner_stats.fire_damage.raw(), DamageType::Water => owner_stats.water_damage.raw(), DamageType::Physical => owner_stats.physical_damage.raw(), }; // damage from addons multiplied with level let addon_damage = addons.damage() * ability_level; // sum up ability_base_damage + stats_damage + addon_damage } pub fn create_on_hit_particles2( &self, engine: &Engine, position: Vector3, offset: Vector3, particle_system_vulkan_objects: &ParticleSystemVulkanObjects, build_path: impl Fn(&str) -> AssetPath, ) -> Result> { Ok(if self.settings.parameter.on_hit_particles.is_empty() { None } else { let file_name = build_path(&self.settings.parameter.on_hit_particles); let info = ParticleSystemInfo::load(file_name)?; Some(Self::create_on_hit_particles( engine, info, &self.settings.parameter.on_collision_sound, position, offset, particle_system_vulkan_objects, build_path, )?) }) } pub fn create_on_hit_particles( engine: &Engine, info: ParticleSystemInfo, on_collision_sound: &str, position: Vector3, offset: Vector3, particle_system_vulkan_objects: &ParticleSystemVulkanObjects, build_path: impl Fn(&str) -> AssetPath, ) -> Result { let mut particle_entity = engine.assets().empty_entity(); { let mut draw = Draw::new(Vec::new()); let particles = ParticleSystem::new_with_vk_objects( info, engine, particle_system_vulkan_objects, &mut draw, )?; if !on_collision_sound.is_empty() { let mut audio = Audio::new(engine.context(), None)?; audio.set_sound(build_path(on_collision_sound), ON_ENABLE_SOUND)?; particle_entity.insert_component(audio); } particle_entity.insert_component(draw); particle_entity.insert_component(particles); let location = Location::new_and_setup(&mut particle_entity)?; location.set_position(position); location.set_offset(offset); particle_entity.insert_component(BoundingBox { min: [-0.5, -0.5, 0.0], max: [0.5, 0.5, 1.0], }); } Ok(particle_entity) } } impl EntityComponent for AbilityLoader { fn name(&self) -> &str { Self::debug_name() } } impl ComponentDebug for AbilityLoader { fn debug_name() -> &'static str { "AbilityLoader" } }