371 lines
13 KiB
Rust
371 lines
13 KiB
Rust
use engine::prelude::*;
|
|
use entity_manager::*;
|
|
use rpg_components::{
|
|
components::{abilityloader::AbilityLoader, statistics::Statistics},
|
|
damage_type::DamageType,
|
|
items::{ability_addon::AbilityAddonCollection, ability_book::AbilityBook},
|
|
};
|
|
|
|
use crate::game::content::abilities::handle_npc_death;
|
|
|
|
use anyhow::Result;
|
|
use cgmath::{vec3, Deg, Matrix2, Vector2};
|
|
|
|
use super::{
|
|
super::prelude::*,
|
|
particle_spawn::{ParticleSpawn, ParticleSpawnExecutor},
|
|
};
|
|
|
|
use crate::game::game::GameHandle;
|
|
use std::time::Duration;
|
|
|
|
const ODD_ANGLES: [Deg<f32>; 11] = [
|
|
Deg(0.0),
|
|
Deg(10.0),
|
|
Deg(-10.0),
|
|
Deg(20.0),
|
|
Deg(-20.0),
|
|
Deg(30.0),
|
|
Deg(-30.0),
|
|
Deg(40.0),
|
|
Deg(-40.0),
|
|
Deg(50.0),
|
|
Deg(-50.0),
|
|
];
|
|
|
|
const EVEN_ANGLES: [Deg<f32>; 10] = [
|
|
Deg(5.0),
|
|
Deg(-5.0),
|
|
Deg(15.0),
|
|
Deg(-15.0),
|
|
Deg(25.0),
|
|
Deg(-25.0),
|
|
Deg(35.0),
|
|
Deg(-35.0),
|
|
Deg(45.0),
|
|
Deg(-45.0),
|
|
];
|
|
|
|
pub struct ProjectileMarker;
|
|
|
|
impl EntityComponent for ProjectileMarker {
|
|
fn name(&self) -> &str {
|
|
Self::debug_name()
|
|
}
|
|
}
|
|
|
|
impl ComponentDebug for ProjectileMarker {
|
|
fn debug_name() -> &'static str {
|
|
"ProjectileMarker"
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct Projectile;
|
|
|
|
impl Projectile {
|
|
fn hit_event(
|
|
me: Entity,
|
|
collider: Entity,
|
|
owner: Entity,
|
|
level: u32,
|
|
addons: &AbilityAddonCollection,
|
|
game_handle: &GameHandle,
|
|
scene: &mut SceneContents<'_>,
|
|
) -> Result<CollisionEventType> {
|
|
// this handle is only needed for calculating loot bonus when creating a loot stash
|
|
// it only queries all users and their loot bonus passive
|
|
let s = unsafe { remove_life_time_mut(scene) };
|
|
|
|
let game = game_handle.upgrade();
|
|
|
|
let mut entity_multi_mut = scene.entities_multi_mut();
|
|
|
|
let collider_object = match entity_multi_mut.get(collider) {
|
|
Ok(collider) => collider,
|
|
Err(_) => return Ok(CollisionEventType::Ignore),
|
|
};
|
|
|
|
let projectile_object = match entity_multi_mut.get(me) {
|
|
Ok(projectile_object) => projectile_object,
|
|
Err(_) => return Ok(CollisionEventType::Ignore),
|
|
};
|
|
|
|
let mut collider_components = collider_object.multi_mut();
|
|
let mut projectile_components = projectile_object.multi_mut();
|
|
|
|
let my_faction = Faction::get(&mut projectile_components).unwrap();
|
|
let ability_info = projectile_components.get::<AbilityLoader>()?;
|
|
|
|
// don't collide with same 'Team' or neutral NPC's
|
|
let collider_faction = Faction::get(&mut collider_components);
|
|
|
|
if !my_faction.check_collision(collider_faction) {
|
|
// if there is no entity faction, that means we hit an environment object
|
|
// e.g. a tree or a stone.
|
|
// we then bounce or destroy the projectile
|
|
if addons.bounce() {
|
|
let position = projectile_components.get::<Location>().unwrap().position();
|
|
let movement = projectile_components.get::<Movement>().unwrap();
|
|
|
|
let direction = movement.direction();
|
|
let inverted = -direction;
|
|
let random_angle = Deg(Random::range_f32(-60.0, 60.0));
|
|
|
|
let new_direction = Matrix2::from_angle(random_angle) * inverted;
|
|
|
|
movement.set_direction(new_direction);
|
|
|
|
let location_info = projectile_components.get::<AbilityLocationInfo>()?;
|
|
|
|
location_info.new_direction_at(position);
|
|
}
|
|
|
|
return Ok(CollisionEventType::Ignore);
|
|
}
|
|
|
|
// skip any damage related calculation when the owner isn't present anymore
|
|
if let Ok(owner_object) = entity_multi_mut.get(owner) {
|
|
let owner_stats = owner_object.get_component::<Statistics>()?;
|
|
|
|
let mut base_damage = ability_info.damage(level, addons, owner_stats);
|
|
|
|
let explosion_radius =
|
|
ability_info.settings.parameter.target_radius + addons.explosion_radius();
|
|
|
|
let npc_location = projectile_components.get::<Location>().unwrap();
|
|
|
|
// don't cast an explosion when the radius is 0
|
|
if explosion_radius == 0.0 {
|
|
base_damage = AbilityLoader::check_for_crit(base_damage, owner_stats);
|
|
|
|
super::damage_and_experience(
|
|
base_damage,
|
|
ability_info.settings.parameter.damage_type,
|
|
&mut collider_components,
|
|
owner_object.multi_mut(),
|
|
s,
|
|
)?;
|
|
|
|
unsafe {
|
|
collider_components.clear_all_usages();
|
|
}
|
|
|
|
// spawn on hit particle effects
|
|
if !ability_info.settings.parameter.on_hit_particles.is_empty() {
|
|
let file_name =
|
|
game.build_data_path(&ability_info.settings.parameter.on_hit_particles);
|
|
let info = ParticleSystemInfo::load(file_name)?;
|
|
|
|
s.write_event(OnHitParticles {
|
|
particle_system_info: info,
|
|
collision_sound: ability_info.settings.parameter.on_collision_sound.clone(),
|
|
position: npc_location.position(),
|
|
offset: npc_location.offset(),
|
|
|
|
arc_info: None,
|
|
});
|
|
}
|
|
|
|
unsafe {
|
|
collider_components.clear_all_usages();
|
|
}
|
|
|
|
handle_npc_death(&game, s, collider, collider_components)?;
|
|
}
|
|
// if there is a explosion radius, create an explosion according to its radius
|
|
else {
|
|
let file_name = match ability_info.settings.parameter.damage_type {
|
|
DamageType::Physical => todo!(),
|
|
DamageType::Fire => game.build_data_path("particles/fire_explosion.particle"),
|
|
DamageType::Water => game.build_data_path("particles/ice_explosion.particle"),
|
|
DamageType::Air => todo!(),
|
|
};
|
|
|
|
let mut info = ParticleSystemInfo::load(file_name)?;
|
|
info.particles.minimum_velocity =
|
|
explosion_radius / info.system.duration.as_secs_f32();
|
|
info.particles.maximum_velocity = info.particles.minimum_velocity;
|
|
|
|
// spawn on hit particle effects
|
|
s.write_event(OnHitParticles {
|
|
particle_system_info: info,
|
|
collision_sound: ability_info.settings.parameter.on_collision_sound.clone(),
|
|
position: npc_location.position(),
|
|
offset: npc_location.offset(),
|
|
|
|
arc_info: Some(ArcInfo {
|
|
radius: explosion_radius,
|
|
base_damage,
|
|
damage_type: ability_info.settings.parameter.damage_type,
|
|
owner,
|
|
}),
|
|
});
|
|
}
|
|
}
|
|
|
|
scene.remove_entity(me)?;
|
|
|
|
Ok(CollisionEventType::Ignore)
|
|
}
|
|
|
|
pub fn create_particles(
|
|
info: &AbilityLoader,
|
|
game_handle: &GameHandle,
|
|
draw: &mut Draw,
|
|
particle_system_vulkan_objects: &ParticleSystemVulkanObjects,
|
|
) -> Result<ParticleSystem> {
|
|
let game = game_handle.upgrade();
|
|
let file_name = game.build_data_path(&info.settings.parameter.trail);
|
|
|
|
let mut particle_info = ParticleSystemInfo::load(file_name)?;
|
|
particle_info.system.duration = Duration::from_secs(300).into();
|
|
particle_info.particles.minimum_velocity = info.settings.parameter.speed / 3.0;
|
|
particle_info.particles.maximum_velocity = info.settings.parameter.speed / 2.0;
|
|
|
|
ParticleSystem::new_with_vk_objects(
|
|
particle_info,
|
|
game.engine(),
|
|
particle_system_vulkan_objects,
|
|
draw,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Projectile {
|
|
pub fn execute(
|
|
ability: &AbilityLoader,
|
|
_owner: Entity,
|
|
owner_components: &mut MultiMut<'_>,
|
|
direction: Vector2<f32>,
|
|
book: &AbilityBook,
|
|
game_handle: &GameHandle,
|
|
entities: &mut Entities<'_>,
|
|
events: &mut ContentEvents<'_>,
|
|
) -> Result<()> {
|
|
let mut projectile_count = 1 + book.addons().additional_projectiles() as usize;
|
|
|
|
let angles: &[Deg<f32>] = if (projectile_count % 2) == 0 {
|
|
projectile_count = projectile_count.min(EVEN_ANGLES.len());
|
|
|
|
&EVEN_ANGLES
|
|
} else {
|
|
projectile_count = projectile_count.min(ODD_ANGLES.len());
|
|
|
|
&ODD_ANGLES
|
|
};
|
|
|
|
let owner_hitbox = owner_components.get::<HitBox>()?;
|
|
let owner_location = owner_components.get::<Location>()?;
|
|
|
|
let projectile_size = book.addons().size();
|
|
let projectile_speed = book.addons().projectile_speed();
|
|
let projectile_distance = ability.settings.parameter.distance + book.addons().distance();
|
|
|
|
let ability_data = book.ability().data();
|
|
|
|
let game = game_handle.upgrade();
|
|
|
|
(0..projectile_count)
|
|
.into_iter()
|
|
.try_for_each(|i| -> Result<()> {
|
|
let angle = &angles[i];
|
|
|
|
let mut projectile_entity = game
|
|
.entity_manager()
|
|
.load_entity(game.engine().assets(), &ability.settings.meta.entity)?;
|
|
|
|
{
|
|
// alter the hitbox parameters
|
|
let projectile_hitbox = projectile_entity.get_component_mut::<HitBox>()?;
|
|
|
|
// Multiply the radius with potential ability modifications
|
|
projectile_hitbox.set_radius(projectile_size * projectile_hitbox.radius());
|
|
|
|
// set on hit event for the hitbox (only the server side will check for it)
|
|
|
|
projectile_hitbox.set_event({
|
|
let owner = _owner;
|
|
let level = book.level();
|
|
let addons = book.addons().clone();
|
|
let game_handle = game_handle.clone();
|
|
|
|
move |scene, me, collider| {
|
|
Self::hit_event(
|
|
me,
|
|
collider,
|
|
owner,
|
|
level,
|
|
&addons,
|
|
&game_handle,
|
|
scene,
|
|
)
|
|
}
|
|
});
|
|
|
|
let hitbox_radius = projectile_hitbox.radius();
|
|
|
|
let projectile_location = Location::new_and_setup(&mut projectile_entity)?;
|
|
|
|
// set projectile to owner location and set rotation accordingly
|
|
projectile_location.set_scale_uniform(projectile_size);
|
|
|
|
let direction = Matrix2::from_angle(*angle) * direction;
|
|
let position = owner_location.position()
|
|
+ direction.extend(0.0) * (hitbox_radius * 1.1 + owner_hitbox.radius());
|
|
|
|
projectile_location.update(position, direction);
|
|
|
|
// apply height offset
|
|
projectile_location.set_offset(vec3(
|
|
0.0,
|
|
0.0,
|
|
ability_data.settings.parameter.height_offset,
|
|
));
|
|
|
|
let mut projectile_movement = Movement::new(
|
|
ability.settings.parameter.speed + projectile_speed,
|
|
projectile_entity.get_component_mut::<Audio>().ok(),
|
|
);
|
|
|
|
if !ability_data.settings.parameter.idle_sound.is_empty() {
|
|
let audio = projectile_entity.get_component_mut::<Audio>().unwrap();
|
|
|
|
audio.add_custom_sound(
|
|
game.build_data_path(&ability_data.settings.parameter.idle_sound),
|
|
"idle_sound",
|
|
)?;
|
|
};
|
|
|
|
// set movement
|
|
projectile_movement.set_direction(direction);
|
|
|
|
let ability_location_info = AbilityLocationInfo::new(
|
|
position,
|
|
projectile_distance,
|
|
projectile_entity.get_component_mut::<Audio>().unwrap(),
|
|
);
|
|
|
|
if !ability_data.settings.parameter.trail.is_empty() {
|
|
events.write_event(ParticleSpawn {
|
|
entity: projectile_entity.as_entity(),
|
|
info: ability_data.clone(),
|
|
executor: ParticleSpawnExecutor::Projectile,
|
|
});
|
|
}
|
|
|
|
projectile_entity.insert_component(ability_location_info);
|
|
projectile_entity.insert_component(ability.clone());
|
|
projectile_entity.insert_component(projectile_movement);
|
|
projectile_entity.insert_component(ProjectileMarker);
|
|
Faction::copy(owner_components, &mut projectile_entity);
|
|
}
|
|
|
|
entities.add_entity(projectile_entity)?;
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|