use crate::{ combat::{ Attack, AttackDamage, AttackEffect, CombatBuff, CombatBuffStrength, CombatEffect, CombatRequirement, Damage, DamageKind, DamageSource, GroupTarget, Knockback, KnockbackDir, }, comp::{ buff::BuffKind, item::{tool, Reagent}, }, uid::Uid, Explosion, RadiusEffect, }; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; use specs::Component; use std::time::Duration; use vek::Rgb; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Effect { Attack(Attack), Explode(Explosion), Vanish, Stick, Possess, Bonk, // Knock/dislodge/change objects on hit } #[derive(Clone, Debug)] pub struct Projectile { // TODO: use SmallVec for these effects pub hit_solid: Vec, pub hit_entity: Vec, /// Time left until the projectile will despawn pub time_left: Duration, pub owner: Option, /// Whether projectile collides with entities in the same group as its /// owner pub ignore_group: bool, /// Whether the projectile is sticky pub is_sticky: bool, /// Whether the projectile should use a point collider pub is_point: bool, } impl Component for Projectile { type Storage = specs::DenseVecStorage; } #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum ProjectileConstructor { Arrow { damage: f32, knockback: f32, energy_regen: f32, }, Knife { damage: f32, knockback: f32, energy_regen: f32, }, FireDroplet { damage: f32, radius: f32, energy_regen: f32, min_falloff: f32, reagent: Option, }, Fireball { damage: f32, radius: f32, energy_regen: f32, min_falloff: f32, }, Frostball { damage: f32, radius: f32, min_falloff: f32, }, Poisonball { damage: f32, radius: f32, min_falloff: f32, }, NecroticSphere { damage: f32, radius: f32, min_falloff: f32, }, Magicball { damage: f32, radius: f32, min_falloff: f32, }, Possess, ClayRocket { damage: f32, radius: f32, knockback: f32, min_falloff: f32, }, InkBomb { damage: f32, radius: f32, min_falloff: f32, }, Snowball { damage: f32, radius: f32, min_falloff: f32, }, ExplodingPumpkin { damage: f32, radius: f32, knockback: f32, min_falloff: f32, }, DagonBomb { damage: f32, radius: f32, knockback: f32, min_falloff: f32, }, IceBomb { damage: f32, radius: f32, knockback: f32, min_falloff: f32, }, LaserBeam { damage: f32, radius: f32, knockback: f32, min_falloff: f32, }, Trap { damage: f32, }, Mine { damage: f32, radius: f32, min_falloff: f32, }, } impl ProjectileConstructor { pub fn create_projectile( self, owner: Option, precision_mult: f32, tool_stats: tool::Stats, damage_effect: Option, ) -> Projectile { let instance = rand::random(); use ProjectileConstructor::*; match self { Arrow { damage, knockback, energy_regen, } => { let knockback = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Knockback(Knockback { strength: knockback, direction: KnockbackDir::Away, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) .with_requirement(CombatRequirement::AnyDamage); let buff = CombatEffect::Buff(CombatBuff { kind: BuffKind::Bleeding, dur_secs: 10.0, strength: CombatBuffStrength::DamageFraction(0.1), chance: 0.1, }) .adjusted_by_stats(tool_stats); let mut damage = AttackDamage::new( Damage { source: DamageSource::Projectile, kind: DamageKind::Piercing, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ) .with_effect(buff); if let Some(damage_effect) = damage_effect { damage = damage.with_effect(damage_effect); } let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(energy) .with_effect(knockback) .with_combo_increment(); Projectile { hit_solid: vec![Effect::Stick, Effect::Bonk], hit_entity: vec![Effect::Attack(attack), Effect::Vanish], time_left: Duration::from_secs(15), owner, ignore_group: true, is_sticky: true, is_point: true, } }, Knife { damage, knockback, energy_regen, } => { let knockback = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Knockback(Knockback { strength: knockback, direction: KnockbackDir::Away, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) .with_requirement(CombatRequirement::AnyDamage); let buff = CombatEffect::Buff(CombatBuff { kind: BuffKind::Bleeding, dur_secs: 10.0, strength: CombatBuffStrength::DamageFraction(0.1), chance: 0.1, }) .adjusted_by_stats(tool_stats); let mut damage = AttackDamage::new( Damage { source: DamageSource::Projectile, kind: DamageKind::Piercing, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ) .with_effect(buff); if let Some(damage_effect) = damage_effect { damage = damage.with_effect(damage_effect); } let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(energy) .with_effect(knockback) .with_combo_increment(); Projectile { hit_solid: vec![Effect::Stick, Effect::Bonk], hit_entity: vec![Effect::Attack(attack), Effect::Vanish], time_left: Duration::from_secs(15), owner, ignore_group: true, is_sticky: true, is_point: true, } }, FireDroplet { damage, radius, energy_regen, min_falloff, reagent, } => { let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) .with_requirement(CombatRequirement::AnyDamage); let buff = CombatEffect::Buff(CombatBuff { kind: BuffKind::Burning, dur_secs: 4.0, strength: CombatBuffStrength::DamageFraction(1.0), chance: 1.0, }) .adjusted_by_stats(tool_stats); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ) .with_effect(buff); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(energy) .with_combo_increment(); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(2.0, Rgb::black()), ], radius, reagent, min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, Fireball { damage, radius, energy_regen, min_falloff, } => { let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) .with_requirement(CombatRequirement::AnyDamage); let buff = CombatEffect::Buff(CombatBuff { kind: BuffKind::Burning, dur_secs: 5.0, strength: CombatBuffStrength::DamageFraction(0.1), chance: 0.1, }) .adjusted_by_stats(tool_stats); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ) .with_effect(buff); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(energy) .with_combo_increment(); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(2.0, Rgb::black()), ], radius, reagent: Some(Reagent::Red), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, Frostball { damage, radius, min_falloff, } => { let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_combo_increment(); let explosion = Explosion { effects: vec![RadiusEffect::Attack(attack)], radius, reagent: Some(Reagent::White), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, Poisonball { damage, radius, min_falloff, } => { let buff = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Buff(CombatBuff { kind: BuffKind::Poisoned, dur_secs: 5.0, strength: CombatBuffStrength::DamageFraction(0.8), chance: 1.0, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(buff); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(5.0, Rgb::black()), ], radius, reagent: Some(Reagent::Purple), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, NecroticSphere { damage, radius, min_falloff, } => { let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_combo_increment(); let explosion = Explosion { effects: vec![RadiusEffect::Attack(attack)], radius, reagent: Some(Reagent::Purple), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, Magicball { damage, radius, min_falloff, } => { let buff = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Buff(CombatBuff { kind: BuffKind::Poisoned, dur_secs: 5.0, strength: CombatBuffStrength::DamageFraction(0.8), chance: 1.0, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(buff); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(0.0, Rgb::black()), ], radius, reagent: Some(Reagent::Green), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, Possess => Projectile { hit_solid: vec![Effect::Stick], hit_entity: vec![Effect::Stick, Effect::Possess], time_left: Duration::from_secs(10), owner, ignore_group: false, is_sticky: true, is_point: true, }, ClayRocket { damage, radius, knockback, min_falloff, } => { let knockback = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Knockback(Knockback { strength: knockback, direction: KnockbackDir::Away, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(knockback); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(5.0, Rgb::black()), ], radius, reagent: Some(Reagent::Red), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, InkBomb { damage, radius, min_falloff, } => { let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let buff = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Buff(CombatBuff { kind: BuffKind::Wet, dur_secs: 8.0, strength: CombatBuffStrength::Value(0.5), chance: 1.0, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(buff) .with_combo_increment(); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(18.0, Rgb::new(4.0, 7.0, 32.0)), ], radius, reagent: Some(Reagent::Blue), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, Snowball { damage, radius, min_falloff, } => { let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult); let explosion = Explosion { effects: vec![RadiusEffect::Attack(attack)], radius, reagent: Some(Reagent::White), min_falloff, }; Projectile { hit_solid: vec![], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(120), owner, ignore_group: true, is_sticky: false, is_point: false, } }, ExplodingPumpkin { damage, radius, knockback, min_falloff, } => { let knockback = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Knockback(Knockback { strength: knockback, direction: KnockbackDir::Away, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let buff = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Buff(CombatBuff { kind: BuffKind::Burning, dur_secs: 5.0, strength: CombatBuffStrength::DamageFraction(0.2), chance: 1.0, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(knockback) .with_effect(buff); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(5.0, Rgb::black()), ], radius, reagent: Some(Reagent::Red), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, DagonBomb { damage, radius, knockback, min_falloff, } => { let knockback = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Knockback(Knockback { strength: knockback, direction: KnockbackDir::Away, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let buff = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Buff(CombatBuff { kind: BuffKind::Burning, dur_secs: 5.0, strength: CombatBuffStrength::DamageFraction(0.2), chance: 1.0, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(knockback) .with_effect(buff); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(25.0, Rgb::black()), ], radius, reagent: Some(Reagent::Blue), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, IceBomb { damage, radius, knockback, min_falloff, } => { let knockback = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Knockback(Knockback { strength: knockback, direction: KnockbackDir::Away, }), ) .with_requirement(CombatRequirement::AnyDamage); let buff = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Buff(CombatBuff { kind: BuffKind::Frozen, dur_secs: 5.0, strength: CombatBuffStrength::DamageFraction(0.05), chance: 1.0, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(knockback) .with_effect(buff); let variation = thread_rng().gen::(); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction( 30.0, Rgb::new( 83.0 - (20.0 * variation), 212.0 - (52.0 * variation), 255.0 - (62.0 * variation), ), ), ], radius, reagent: Some(Reagent::White), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, LaserBeam { damage, radius, knockback, min_falloff, } => { let knockback = AttackEffect::new( Some(GroupTarget::OutOfGroup), CombatEffect::Knockback(Knockback { strength: knockback, direction: KnockbackDir::Away, }) .adjusted_by_stats(tool_stats), ) .with_requirement(CombatRequirement::AnyDamage); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) .with_effect(knockback); let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), RadiusEffect::TerrainDestruction(10.0, Rgb::black()), ], radius, reagent: Some(Reagent::Yellow), min_falloff, }; Projectile { hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: true, } }, Trap { damage } => { let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Piercing, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult); Projectile { hit_solid: vec![], hit_entity: vec![Effect::Attack(attack), Effect::Vanish], time_left: Duration::from_secs(10), owner, ignore_group: true, is_sticky: true, is_point: false, } }, Mine { damage, radius, min_falloff, } => { let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, kind: DamageKind::Energy, value: damage, }, Some(GroupTarget::OutOfGroup), instance, ); let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult); let explosion = Explosion { effects: vec![RadiusEffect::Attack(attack)], radius, reagent: Some(Reagent::Yellow), min_falloff, }; Projectile { hit_solid: vec![], hit_entity: vec![Effect::Explode(explosion), Effect::Vanish], time_left: Duration::from_secs(120), owner, ignore_group: true, is_sticky: false, is_point: false, } }, } } // TODO: split this to three methods per stat #[must_use] pub fn modified_projectile(mut self, power: f32, regen: f32, range: f32) -> Self { use ProjectileConstructor::*; match self { Arrow { ref mut damage, ref mut energy_regen, .. } => { *damage *= power; *energy_regen *= regen; }, Knife { ref mut damage, ref mut energy_regen, .. } => { *damage *= power; *energy_regen *= regen; }, FireDroplet { ref mut damage, ref mut energy_regen, ref mut radius, .. } => { *damage *= power; *energy_regen *= regen; *radius *= range; }, Fireball { ref mut damage, ref mut energy_regen, ref mut radius, .. } => { *damage *= power; *energy_regen *= regen; *radius *= range; }, Frostball { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, Poisonball { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, NecroticSphere { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, Magicball { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, Possess => {}, ClayRocket { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, InkBomb { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, Snowball { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, ExplodingPumpkin { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, DagonBomb { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, IceBomb { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, LaserBeam { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, Trap { ref mut damage, .. } => { *damage *= power; }, Mine { ref mut damage, ref mut radius, .. } => { *damage *= power; *radius *= range; }, } self } pub fn is_explosive(&self) -> bool { use ProjectileConstructor::*; match self { Arrow { .. } => false, Knife { .. } => false, FireDroplet { .. } => true, Fireball { .. } => true, Frostball { .. } => true, Poisonball { .. } => true, NecroticSphere { .. } => true, Magicball { .. } => true, Possess => false, ClayRocket { .. } => true, Snowball { .. } => true, ExplodingPumpkin { .. } => true, DagonBomb { .. } => true, InkBomb { .. } => true, IceBomb { .. } => true, LaserBeam { .. } => true, Trap { .. } => false, Mine { .. } => true, } } }