266 lines
7.1 KiB
Rust
266 lines
7.1 KiB
Rust
|
use anyhow::Result;
|
||
|
use assetpath::AssetPath;
|
||
|
use engine::prelude::*;
|
||
|
|
||
|
use crate::Game;
|
||
|
|
||
|
use super::super::prelude::*;
|
||
|
|
||
|
use cgmath::Vector3;
|
||
|
use std::{path::Path, sync::Arc};
|
||
|
|
||
|
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<Self> {
|
||
|
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<Vec<AssetPath>> {
|
||
|
Ok(search_dir_recursively(
|
||
|
&ability_directory.full_path(),
|
||
|
ABILITY_SUFFIX,
|
||
|
)?)
|
||
|
}
|
||
|
|
||
|
pub fn create_ability(self) -> Result<Arc<dyn Ability>> {
|
||
|
match self.settings.parameter.ability_type {
|
||
|
AbilityType::Projectile => Ok(Arc::new(Projectile::new(self)?)),
|
||
|
AbilityType::SelfCast => Ok(Arc::new(SelfCast::new(self)?)),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
game: &Game,
|
||
|
position: Vector3<f32>,
|
||
|
offset: Vector3<f32>,
|
||
|
particle_system_vulkan_objects: &ParticleSystemVulkanObjects,
|
||
|
) -> Result<Option<EntityObject>> {
|
||
|
Ok(if self.settings.parameter.on_hit_particles.is_empty() {
|
||
|
None
|
||
|
} else {
|
||
|
let file_name = game.build_data_path(&self.settings.parameter.on_hit_particles);
|
||
|
let info = ParticleSystemInfo::load(file_name)?;
|
||
|
|
||
|
Some(Self::create_on_hit_particles(
|
||
|
info,
|
||
|
&self.settings.parameter.on_collision_sound,
|
||
|
game,
|
||
|
position,
|
||
|
offset,
|
||
|
particle_system_vulkan_objects,
|
||
|
)?)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
pub fn create_on_hit_particles(
|
||
|
info: ParticleSystemInfo,
|
||
|
on_collision_sound: &str,
|
||
|
game: &Game,
|
||
|
position: Vector3<f32>,
|
||
|
offset: Vector3<f32>,
|
||
|
particle_system_vulkan_objects: &ParticleSystemVulkanObjects,
|
||
|
) -> Result<EntityObject> {
|
||
|
let mut particle_entity = game.engine().assets().empty_entity();
|
||
|
|
||
|
{
|
||
|
let mut draw = Draw::new(Vec::new());
|
||
|
|
||
|
let particles = ParticleSystem::new_with_vk_objects(
|
||
|
info,
|
||
|
&game.engine(),
|
||
|
particle_system_vulkan_objects,
|
||
|
&mut draw,
|
||
|
)?;
|
||
|
|
||
|
if !on_collision_sound.is_empty() {
|
||
|
let mut audio = Audio::new(game.engine().context(), None)?;
|
||
|
audio.set_sound(game.build_data_path(on_collision_sound), SOUND_CREATE_KEY)?;
|
||
|
|
||
|
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"
|
||
|
}
|
||
|
}
|