Addressed comments.

This commit is contained in:
Sam 2021-02-02 13:02:40 -05:00
parent 80954f3ba4
commit be8df9aef6
24 changed files with 505 additions and 466 deletions

View File

@ -30,10 +30,17 @@ pub enum GroupTarget {
OutOfGroup, OutOfGroup,
} }
#[derive(Copy, Clone)]
pub struct AttackerInfo<'a> {
pub entity: EcsEntity,
pub uid: Uid,
pub energy: Option<&'a Energy>,
}
#[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive #[derive(Clone, Debug, Serialize, Deserialize)] // TODO: Yeet clone derive
pub struct Attack { pub struct Attack {
damages: Vec<DamageComponent>, damages: Vec<AttackDamage>,
effects: Vec<EffectComponent>, effects: Vec<AttackEffect>,
crit_chance: f32, crit_chance: f32,
crit_multiplier: f32, crit_multiplier: f32,
} }
@ -50,50 +57,48 @@ impl Default for Attack {
} }
impl Attack { impl Attack {
pub fn with_damage(mut self, damage: DamageComponent) -> Self { pub fn with_damage(mut self, damage: AttackDamage) -> Self {
self.damages.push(damage); self.damages.push(damage);
self self
} }
pub fn with_effect(mut self, effect: EffectComponent) -> Self { pub fn with_effect(mut self, effect: AttackEffect) -> Self {
self.effects.push(effect); self.effects.push(effect);
self self
} }
pub fn with_crit(mut self, cc: f32, cm: f32) -> Self { pub fn with_crit(mut self, crit_chance: f32, crit_multiplier: f32) -> Self {
self.crit_chance = cc; self.crit_chance = crit_chance;
self.crit_multiplier = cm; self.crit_multiplier = crit_multiplier;
self self
} }
pub fn effects(&self) -> impl Iterator<Item = &EffectComponent> { self.effects.iter() } pub fn effects(&self) -> impl Iterator<Item = &AttackEffect> { self.effects.iter() }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn apply_attack( pub fn apply_attack(
&self, &self,
target_group: GroupTarget, target_group: GroupTarget,
attacker_entity: Option<EcsEntity>, attacker_info: Option<AttackerInfo>,
target_entity: EcsEntity, target_entity: EcsEntity,
target_inventory: Option<&Inventory>, target_inventory: Option<&Inventory>,
attacker_uid: Option<Uid>,
attacker_energy: Option<&Energy>,
dir: Dir, dir: Dir,
target_dodging: bool, target_dodging: bool,
// Currently just modifies damage, maybe look into modifying strength of other effects? // Currently just modifies damage, maybe look into modifying strength of other effects?
strength_modifier: f32, strength_modifier: f32,
) -> Vec<ServerEvent> { mut emit: impl FnMut(ServerEvent),
) {
let is_crit = thread_rng().gen::<f32>() < self.crit_chance; let is_crit = thread_rng().gen::<f32>() < self.crit_chance;
let mut accumulated_damage = 0.0; let mut accumulated_damage = 0.0;
let mut server_events = Vec::new();
for damage in self for damage in self
.damages .damages
.iter() .iter()
.filter(|d| d.target.map_or(true, |t| t == target_group)) .filter(|d| d.target.map_or(true, |t| t == target_group))
.filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging)) .filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging))
{ {
let change = damage.damage.modify_damage( let change = damage.damage.calculate_health_change(
target_inventory, target_inventory,
attacker_uid, attacker_info.map(|a| a.uid),
is_crit, is_crit,
self.crit_multiplier, self.crit_multiplier,
strength_modifier, strength_modifier,
@ -101,24 +106,24 @@ impl Attack {
let applied_damage = -change.amount as f32; let applied_damage = -change.amount as f32;
accumulated_damage += applied_damage; accumulated_damage += applied_damage;
if change.amount != 0 { if change.amount != 0 {
server_events.push(ServerEvent::Damage { emit(ServerEvent::Damage {
entity: target_entity, entity: target_entity,
change, change,
}); });
for effect in damage.effects.iter() { for effect in damage.effects.iter() {
match effect { match effect {
AttackEffect::Knockback(kb) => { CombatEffect::Knockback(kb) => {
let impulse = kb.calculate_impulse(dir); let impulse = kb.calculate_impulse(dir);
if !impulse.is_approx_zero() { if !impulse.is_approx_zero() {
server_events.push(ServerEvent::Knockback { emit(ServerEvent::Knockback {
entity: target_entity, entity: target_entity,
impulse, impulse,
}); });
} }
}, },
AttackEffect::EnergyReward(ec) => { CombatEffect::EnergyReward(ec) => {
if let Some(attacker_entity) = attacker_entity { if let Some(attacker_entity) = attacker_info.map(|a| a.entity) {
server_events.push(ServerEvent::EnergyChange { emit(ServerEvent::EnergyChange {
entity: attacker_entity, entity: attacker_entity,
change: EnergyChange { change: EnergyChange {
amount: *ec as i32, amount: *ec as i32,
@ -127,45 +132,55 @@ impl Attack {
}); });
} }
}, },
AttackEffect::Buff(b) => { CombatEffect::Buff(b) => {
if thread_rng().gen::<f32>() < b.chance { if thread_rng().gen::<f32>() < b.chance {
server_events.push(ServerEvent::Buff { emit(ServerEvent::Buff {
entity: target_entity, entity: target_entity,
buff_change: BuffChange::Add( buff_change: BuffChange::Add(
b.to_buff(attacker_uid, applied_damage), b.to_buff(attacker_info.map(|a| a.uid), applied_damage),
), ),
}); });
} }
}, },
AttackEffect::Lifesteal(l) => { CombatEffect::Lifesteal(l) => {
if let Some(attacker_entity) = attacker_entity { if let Some(attacker_entity) = attacker_info.map(|a| a.entity) {
let change = HealthChange { let change = HealthChange {
amount: (applied_damage * l) as i32, amount: (applied_damage * l) as i32,
cause: HealthSource::Heal { by: attacker_uid }, cause: HealthSource::Heal {
by: attacker_info.map(|a| a.uid),
},
}; };
server_events.push(ServerEvent::Damage { if change.amount != 0 {
entity: attacker_entity, emit(ServerEvent::Damage {
entity: attacker_entity,
change,
});
}
}
},
CombatEffect::Poise(p) => {
let change = PoiseChange::from_value(*p, target_inventory);
if change.amount != 0 {
emit(ServerEvent::PoiseChange {
entity: target_entity,
change, change,
kb_dir: *dir,
}); });
} }
}, },
AttackEffect::Poise(p) => { CombatEffect::Heal(h) => {
let change = PoiseChange::from_attack(*p, target_inventory);
server_events.push(ServerEvent::PoiseChange {
entity: target_entity,
change,
kb_dir: *dir,
});
},
AttackEffect::Heal(h) => {
let change = HealthChange { let change = HealthChange {
amount: *h as i32, amount: *h as i32,
cause: HealthSource::Heal { by: attacker_uid }, cause: HealthSource::Heal {
by: attacker_info.map(|a| a.uid),
},
}; };
server_events.push(ServerEvent::Damage { if change.amount != 0 {
entity: target_entity, emit(ServerEvent::Damage {
change, entity: target_entity,
}); change,
});
}
}, },
} }
} }
@ -180,17 +195,24 @@ impl Attack {
if match &effect.requirement { if match &effect.requirement {
Some(CombatRequirement::AnyDamage) => accumulated_damage > 0.0, Some(CombatRequirement::AnyDamage) => accumulated_damage > 0.0,
Some(CombatRequirement::SufficientEnergy(r)) => { Some(CombatRequirement::SufficientEnergy(r)) => {
if attacker_energy.map_or(true, |e| e.current() >= *r) { if let Some(AttackerInfo {
if let Some(attacker_entity) = attacker_entity { entity,
server_events.push(ServerEvent::EnergyChange { energy: Some(e),
entity: attacker_entity, ..
}) = attacker_info
{
let sufficient_energy = e.current() >= *r;
if sufficient_energy {
emit(ServerEvent::EnergyChange {
entity,
change: EnergyChange { change: EnergyChange {
amount: -(*r as i32), amount: -(*r as i32),
source: EnergySource::Ability, source: EnergySource::Ability,
}, },
}); });
} }
true
sufficient_energy
} else { } else {
false false
} }
@ -198,18 +220,18 @@ impl Attack {
None => true, None => true,
} { } {
match effect.effect { match effect.effect {
AttackEffect::Knockback(kb) => { CombatEffect::Knockback(kb) => {
let impulse = kb.calculate_impulse(dir); let impulse = kb.calculate_impulse(dir);
if !impulse.is_approx_zero() { if !impulse.is_approx_zero() {
server_events.push(ServerEvent::Knockback { emit(ServerEvent::Knockback {
entity: target_entity, entity: target_entity,
impulse, impulse,
}); });
} }
}, },
AttackEffect::EnergyReward(ec) => { CombatEffect::EnergyReward(ec) => {
if let Some(attacker_entity) = attacker_entity { if let Some(attacker_entity) = attacker_info.map(|a| a.entity) {
server_events.push(ServerEvent::EnergyChange { emit(ServerEvent::EnergyChange {
entity: attacker_entity, entity: attacker_entity,
change: EnergyChange { change: EnergyChange {
amount: ec as i32, amount: ec as i32,
@ -218,61 +240,70 @@ impl Attack {
}); });
} }
}, },
AttackEffect::Buff(b) => { CombatEffect::Buff(b) => {
if thread_rng().gen::<f32>() < b.chance { if thread_rng().gen::<f32>() < b.chance {
server_events.push(ServerEvent::Buff { emit(ServerEvent::Buff {
entity: target_entity, entity: target_entity,
buff_change: BuffChange::Add( buff_change: BuffChange::Add(
b.to_buff(attacker_uid, accumulated_damage), b.to_buff(attacker_info.map(|a| a.uid), accumulated_damage),
), ),
}); });
} }
}, },
AttackEffect::Lifesteal(l) => { CombatEffect::Lifesteal(l) => {
if let Some(attacker_entity) = attacker_entity { if let Some(attacker_entity) = attacker_info.map(|a| a.entity) {
let change = HealthChange { let change = HealthChange {
amount: (accumulated_damage * l) as i32, amount: (accumulated_damage * l) as i32,
cause: HealthSource::Heal { by: attacker_uid }, cause: HealthSource::Heal {
by: attacker_info.map(|a| a.uid),
},
}; };
server_events.push(ServerEvent::Damage { if change.amount != 0 {
entity: attacker_entity, emit(ServerEvent::Damage {
entity: attacker_entity,
change,
});
}
}
},
CombatEffect::Poise(p) => {
let change = PoiseChange::from_value(p, target_inventory);
if change.amount != 0 {
emit(ServerEvent::PoiseChange {
entity: target_entity,
change,
kb_dir: *dir,
});
}
},
CombatEffect::Heal(h) => {
let change = HealthChange {
amount: h as i32,
cause: HealthSource::Heal {
by: attacker_info.map(|a| a.uid),
},
};
if change.amount != 0 {
emit(ServerEvent::Damage {
entity: target_entity,
change, change,
}); });
} }
}, },
AttackEffect::Poise(p) => {
let change = PoiseChange::from_attack(p, target_inventory);
server_events.push(ServerEvent::PoiseChange {
entity: target_entity,
change,
kb_dir: *dir,
});
},
AttackEffect::Heal(h) => {
let change = HealthChange {
amount: h as i32,
cause: HealthSource::Heal { by: attacker_uid },
};
server_events.push(ServerEvent::Damage {
entity: target_entity,
change,
});
},
} }
} }
} }
server_events
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DamageComponent { pub struct AttackDamage {
damage: Damage, damage: Damage,
target: Option<GroupTarget>, target: Option<GroupTarget>,
effects: Vec<AttackEffect>, effects: Vec<CombatEffect>,
} }
impl DamageComponent { impl AttackDamage {
pub fn new(damage: Damage, target: Option<GroupTarget>) -> Self { pub fn new(damage: Damage, target: Option<GroupTarget>) -> Self {
Self { Self {
damage, damage,
@ -281,21 +312,21 @@ impl DamageComponent {
} }
} }
pub fn with_effect(mut self, effect: AttackEffect) -> Self { pub fn with_effect(mut self, effect: CombatEffect) -> Self {
self.effects.push(effect); self.effects.push(effect);
self self
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EffectComponent { pub struct AttackEffect {
target: Option<GroupTarget>, target: Option<GroupTarget>,
effect: AttackEffect, effect: CombatEffect,
requirement: Option<CombatRequirement>, requirement: Option<CombatRequirement>,
} }
impl EffectComponent { impl AttackEffect {
pub fn new(target: Option<GroupTarget>, effect: AttackEffect) -> Self { pub fn new(target: Option<GroupTarget>, effect: CombatEffect) -> Self {
Self { Self {
target, target,
effect, effect,
@ -308,11 +339,11 @@ impl EffectComponent {
self self
} }
pub fn effect(&self) -> &AttackEffect { &self.effect } pub fn effect(&self) -> &CombatEffect { &self.effect }
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AttackEffect { pub enum CombatEffect {
Heal(f32), Heal(f32),
Buff(CombatBuff), Buff(CombatBuff),
Knockback(Knockback), Knockback(Knockback),
@ -368,7 +399,7 @@ impl Damage {
} }
} }
pub fn modify_damage( pub fn calculate_health_change(
self, self,
inventory: Option<&Inventory>, inventory: Option<&Inventory>,
uid: Option<Uid>, uid: Option<Uid>,
@ -377,7 +408,7 @@ impl Damage {
damage_modifier: f32, damage_modifier: f32,
) -> HealthChange { ) -> HealthChange {
let mut damage = self.value * damage_modifier; let mut damage = self.value * damage_modifier;
let damage_reduction = inventory.map_or(0.0, |inv| Damage::compute_damage_reduction(inv)); let damage_reduction = inventory.map_or(0.0, Damage::compute_damage_reduction);
match self.source { match self.source {
DamageSource::Melee => { DamageSource::Melee => {
// Critical hit // Critical hit

View File

@ -170,7 +170,7 @@ impl Component for CharacterState {
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct MeleeAttack { pub struct Melee {
pub attack: Attack, pub attack: Attack,
pub range: f32, pub range: f32,
pub max_angle: f32, pub max_angle: f32,
@ -178,6 +178,6 @@ pub struct MeleeAttack {
pub hit_count: u32, pub hit_count: u32,
} }
impl Component for MeleeAttack { impl Component for Melee {
type Storage = VecStorage<Self>; type Storage = VecStorage<Self>;
} }

View File

@ -40,7 +40,7 @@ pub use buff::{
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs, Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
ModifierKind, ModifierKind,
}; };
pub use character_state::{CharacterState, MeleeAttack, StateUpdate}; pub use character_state::{CharacterState, Melee, StateUpdate};
pub use chat::{ pub use chat::{
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg, ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
}; };

View File

@ -21,8 +21,7 @@ pub struct PoiseChange {
impl PoiseChange { impl PoiseChange {
/// Alters poise damage as a result of armor poise damage reduction /// Alters poise damage as a result of armor poise damage reduction
pub fn modify_poise_damage(self, inventory: Option<&Inventory>) -> PoiseChange { pub fn modify_poise_damage(self, inventory: Option<&Inventory>) -> PoiseChange {
let poise_damage_reduction = let poise_damage_reduction = inventory.map_or(0.0, Poise::compute_poise_damage_reduction);
inventory.map_or(0.0, |inv| Poise::compute_poise_damage_reduction(inv));
let poise_damage = self.amount as f32 * (1.0 - poise_damage_reduction); let poise_damage = self.amount as f32 * (1.0 - poise_damage_reduction);
// Add match on poise source when different calculations per source // Add match on poise source when different calculations per source
// are needed/wanted // are needed/wanted
@ -32,10 +31,9 @@ impl PoiseChange {
} }
} }
/// Creates a poise change from an attack /// Creates a poise change from a float
pub fn from_attack(poise_damage: f32, inventory: Option<&Inventory>) -> Self { pub fn from_value(poise_damage: f32, inventory: Option<&Inventory>) -> Self {
let poise_damage_reduction = let poise_damage_reduction = inventory.map_or(0.0, Poise::compute_poise_damage_reduction);
inventory.map_or(0.0, |inv| Poise::compute_poise_damage_reduction(inv));
let poise_change = -poise_damage * (1.0 - poise_damage_reduction); let poise_change = -poise_damage * (1.0 - poise_damage_reduction);
Self { Self {
amount: poise_change as i32, amount: poise_change as i32,

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
combat::{ combat::{
Attack, AttackEffect, CombatBuff, CombatRequirement, Damage, DamageComponent, DamageSource, Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement, Damage,
EffectComponent, GroupTarget, Knockback, KnockbackDir, DamageSource, GroupTarget, Knockback, KnockbackDir,
}, },
uid::Uid, uid::Uid,
Explosion, RadiusEffect, Explosion, RadiusEffect,
@ -14,16 +14,10 @@ use std::time::Duration;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum Effect { pub enum Effect {
Attack(Attack), Attack(Attack),
//Knockback(Knockback),
//RewardEnergy(u32),
Explode(Explosion), Explode(Explosion),
Vanish, Vanish,
Stick, Stick,
Possess, Possess,
/* Buff {
* buff: BuffEffect,
* chance: Option<f32>,
* }, */
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -76,22 +70,25 @@ impl ProjectileConstructor {
knockback, knockback,
energy_regen, energy_regen,
} => { } => {
let knockback = AttackEffect::Knockback(Knockback { let knockback = AttackEffect::new(
strength: knockback, Some(GroupTarget::OutOfGroup),
direction: KnockbackDir::Away, CombatEffect::Knockback(Knockback {
}); strength: knockback,
let knockback = EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) direction: KnockbackDir::Away,
}),
)
.with_requirement(CombatRequirement::AnyDamage);
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
.with_requirement(CombatRequirement::AnyDamage); .with_requirement(CombatRequirement::AnyDamage);
let energy = AttackEffect::EnergyReward(energy_regen); let buff = CombatEffect::Buff(CombatBuff::default_physical());
let energy = EffectComponent::new(None, energy) let damage = AttackDamage::new(
.with_requirement(CombatRequirement::AnyDamage); Damage {
let buff = AttackEffect::Buff(CombatBuff::default_physical()); source: DamageSource::Projectile,
let damage = Damage { value: damage,
source: DamageSource::Projectile, },
value: damage, Some(GroupTarget::OutOfGroup),
}; )
let damage = .with_effect(buff);
DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)).with_effect(buff);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_crit(0.5, 1.2) .with_crit(0.5, 1.2)
@ -111,14 +108,15 @@ impl ProjectileConstructor {
radius, radius,
energy_regen, energy_regen,
} => { } => {
let energy = AttackEffect::EnergyReward(energy_regen); let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
let energy = EffectComponent::new(None, energy)
.with_requirement(CombatRequirement::AnyDamage); .with_requirement(CombatRequirement::AnyDamage);
let damage = Damage { let damage = AttackDamage::new(
source: DamageSource::Explosion, Damage {
value: damage, source: DamageSource::Explosion,
}; value: damage,
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)); },
Some(GroupTarget::OutOfGroup),
);
let attack = Attack::default().with_damage(damage).with_effect(energy); let attack = Attack::default().with_damage(damage).with_effect(energy);
let explosion = Explosion { let explosion = Explosion {
effects: vec![ effects: vec![
@ -139,14 +137,15 @@ impl ProjectileConstructor {
damage, damage,
energy_regen, energy_regen,
} => { } => {
let energy = AttackEffect::EnergyReward(energy_regen); let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
let energy = EffectComponent::new(None, energy)
.with_requirement(CombatRequirement::AnyDamage); .with_requirement(CombatRequirement::AnyDamage);
let damage = Damage { let damage = AttackDamage::new(
source: DamageSource::Energy, Damage {
value: damage, source: DamageSource::Energy,
}; value: damage,
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)); },
Some(GroupTarget::OutOfGroup),
);
let attack = Attack::default().with_damage(damage).with_effect(energy); let attack = Attack::default().with_damage(damage).with_effect(energy);
Projectile { Projectile {
@ -162,13 +161,14 @@ impl ProjectileConstructor {
damage, damage,
radius, radius,
} => { } => {
let damage = Damage { let damage = AttackDamage::new(
source: DamageSource::Explosion, Damage {
value: damage, source: DamageSource::Explosion,
}; value: damage,
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)); },
let heal = AttackEffect::Heal(heal); Some(GroupTarget::OutOfGroup),
let heal = EffectComponent::new(Some(GroupTarget::InGroup), heal); );
let heal = AttackEffect::new(Some(GroupTarget::InGroup), CombatEffect::Heal(heal));
let attack = Attack::default().with_damage(damage).with_effect(heal); let attack = Attack::default().with_damage(damage).with_effect(heal);
let explosion = Explosion { let explosion = Explosion {
effects: vec![RadiusEffect::Attack(attack)], effects: vec![RadiusEffect::Attack(attack)],

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
combat::{ combat::{
Attack, AttackEffect, CombatRequirement, Damage, DamageComponent, DamageSource, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage, DamageSource,
EffectComponent, GroupTarget, GroupTarget,
}, },
comp::{beam, Body, CharacterState, EnergyChange, EnergySource, Ori, Pos, StateUpdate}, comp::{beam, Body, CharacterState, EnergyChange, EnergySource, Ori, Pos, StateUpdate},
event::ServerEvent, event::ServerEvent,
@ -122,25 +122,32 @@ impl CharacterBehavior for Data {
if ability_key_is_pressed(data, self.static_data.ability_key) if ability_key_is_pressed(data, self.static_data.ability_key)
&& (self.static_data.energy_drain == 0 || update.energy.current() > 0) && (self.static_data.energy_drain == 0 || update.energy.current() > 0)
{ {
let damage = Damage {
source: DamageSource::Energy,
value: self.static_data.base_dps as f32 / self.static_data.tick_rate,
};
let heal = self.static_data.base_hps as f32 / self.static_data.tick_rate;
let heal = AttackEffect::Heal(heal);
let speed = let speed =
self.static_data.range / self.static_data.beam_duration.as_secs_f32(); self.static_data.range / self.static_data.beam_duration.as_secs_f32();
let energy = AttackEffect::EnergyReward(self.static_data.energy_regen); let energy = AttackEffect::new(
let energy = EffectComponent::new(None, energy) None,
.with_requirement(CombatRequirement::AnyDamage); CombatEffect::EnergyReward(self.static_data.energy_regen),
let lifesteal = AttackEffect::Lifesteal(self.static_data.lifesteal_eff); )
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)) .with_requirement(CombatRequirement::AnyDamage);
.with_effect(lifesteal); let lifesteal = CombatEffect::Lifesteal(self.static_data.lifesteal_eff);
let heal = EffectComponent::new(Some(GroupTarget::InGroup), heal) let damage = AttackDamage::new(
.with_requirement(CombatRequirement::SufficientEnergy( Damage {
self.static_data.energy_cost, source: DamageSource::Energy,
)); value: self.static_data.base_dps as f32 / self.static_data.tick_rate,
},
Some(GroupTarget::OutOfGroup),
)
.with_effect(lifesteal);
let heal = AttackEffect::new(
Some(GroupTarget::InGroup),
CombatEffect::Heal(
self.static_data.base_hps as f32 / self.static_data.tick_rate,
),
)
.with_requirement(CombatRequirement::SufficientEnergy(
self.static_data.energy_cost,
));
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_effect(energy) .with_effect(energy)

View File

@ -1,8 +1,6 @@
use crate::{ use crate::{
combat::{ combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
Attack, AttackEffect, CombatBuff, CombatRequirement, DamageComponent, EffectComponent, comp::{CharacterState, Melee, StateUpdate},
},
comp::{CharacterState, MeleeAttack, StateUpdate},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
@ -92,25 +90,30 @@ impl CharacterBehavior for Data {
..*self ..*self
}); });
let poise = AttackEffect::Poise(self.static_data.base_poise_damage as f32); let poise = AttackEffect::new(
let poise = EffectComponent::new(Some(GroupTarget::OutOfGroup), poise) Some(GroupTarget::OutOfGroup),
CombatEffect::Poise(self.static_data.base_poise_damage as f32),
)
.with_requirement(CombatRequirement::AnyDamage);
let knockback = AttackEffect::new(
Some(GroupTarget::OutOfGroup),
CombatEffect::Knockback(Knockback {
strength: self.static_data.knockback,
direction: KnockbackDir::Away,
}),
)
.with_requirement(CombatRequirement::AnyDamage);
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(50))
.with_requirement(CombatRequirement::AnyDamage); .with_requirement(CombatRequirement::AnyDamage);
let knockback = AttackEffect::Knockback(Knockback { let buff = CombatEffect::Buff(CombatBuff::default_physical());
strength: self.static_data.knockback, let damage = AttackDamage::new(
direction: KnockbackDir::Away, Damage {
}); source: DamageSource::Melee,
let knockback = EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) value: self.static_data.base_damage as f32,
.with_requirement(CombatRequirement::AnyDamage); },
let energy = AttackEffect::EnergyReward(50); Some(GroupTarget::OutOfGroup),
let energy = EffectComponent::new(None, energy) )
.with_requirement(CombatRequirement::AnyDamage); .with_effect(buff);
let buff = AttackEffect::Buff(CombatBuff::default_physical());
let damage = Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
};
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup))
.with_effect(buff);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_crit(0.5, 1.3) .with_crit(0.5, 1.3)
@ -119,7 +122,7 @@ impl CharacterBehavior for Data {
.with_effect(knockback); .with_effect(knockback);
// Hit attempt // Hit attempt
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, Melee {
attack, attack,
range: self.static_data.range, range: self.static_data.range,
max_angle: self.static_data.max_angle, max_angle: self.static_data.max_angle,
@ -158,14 +161,14 @@ impl CharacterBehavior for Data {
// Done // Done
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
} }
}, },
_ => { _ => {
// If it somehow ends up in an incorrect stage section // If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
}, },
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
comp::{ comp::{
Beam, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy, Health, Beam, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy, Health,
Inventory, MeleeAttack, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, Inventory, Melee, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel,
}, },
resources::DeltaTime, resources::DeltaTime,
uid::Uid, uid::Uid,
@ -57,7 +57,7 @@ pub struct JoinData<'a> {
pub inventory: &'a Inventory, pub inventory: &'a Inventory,
pub body: &'a Body, pub body: &'a Body,
pub physics: &'a PhysicsState, pub physics: &'a PhysicsState,
pub melee_attack: Option<&'a MeleeAttack>, pub melee_attack: Option<&'a Melee>,
pub updater: &'a LazyUpdate, pub updater: &'a LazyUpdate,
pub stats: &'a Stats, pub stats: &'a Stats,
} }
@ -84,7 +84,7 @@ pub type JoinTuple<'a> = (
&'a Health, &'a Health,
&'a Body, &'a Body,
&'a PhysicsState, &'a PhysicsState,
Option<&'a MeleeAttack>, Option<&'a Melee>,
Option<&'a Beam>, Option<&'a Beam>,
&'a Stats, &'a Stats,
); );

View File

@ -1,8 +1,6 @@
use crate::{ use crate::{
combat::{ combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
Attack, AttackEffect, CombatBuff, CombatRequirement, DamageComponent, EffectComponent, comp::{CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
},
comp::{CharacterState, EnergyChange, EnergySource, MeleeAttack, StateUpdate},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *}, utils::{StageSection, *},
@ -155,27 +153,33 @@ impl CharacterBehavior for Data {
exhausted: true, exhausted: true,
..*self ..*self
}); });
let poise = self.static_data.initial_poise_damage as f32 let poise = AttackEffect::new(
+ self.charge_amount * self.static_data.scaled_poise_damage as f32; Some(GroupTarget::OutOfGroup),
let poise = AttackEffect::Poise(poise); CombatEffect::Poise(
let poise = EffectComponent::new(Some(GroupTarget::OutOfGroup), poise) self.static_data.initial_poise_damage as f32
.with_requirement(CombatRequirement::AnyDamage); + self.charge_amount * self.static_data.scaled_poise_damage as f32,
let knockback = self.static_data.initial_knockback ),
+ self.charge_amount * self.static_data.scaled_knockback; )
let knockback = AttackEffect::Knockback(Knockback { .with_requirement(CombatRequirement::AnyDamage);
strength: knockback, let knockback = AttackEffect::new(
direction: KnockbackDir::Away, Some(GroupTarget::OutOfGroup),
}); CombatEffect::Knockback(Knockback {
let knockback = EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) strength: self.static_data.initial_knockback
.with_requirement(CombatRequirement::AnyDamage); + self.charge_amount * self.static_data.scaled_knockback,
let buff = AttackEffect::Buff(CombatBuff::default_physical()); direction: KnockbackDir::Away,
let damage = Damage { }),
source: DamageSource::Melee, )
value: self.static_data.initial_damage as f32 .with_requirement(CombatRequirement::AnyDamage);
+ self.charge_amount * self.static_data.scaled_damage as f32, let buff = CombatEffect::Buff(CombatBuff::default_physical());
}; let damage = AttackDamage::new(
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)) Damage {
.with_effect(buff); source: DamageSource::Melee,
value: self.static_data.initial_damage as f32
+ self.charge_amount * self.static_data.scaled_damage as f32,
},
Some(GroupTarget::OutOfGroup),
)
.with_effect(buff);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_crit(0.5, 1.3) .with_crit(0.5, 1.3)
@ -183,7 +187,7 @@ impl CharacterBehavior for Data {
.with_effect(knockback); .with_effect(knockback);
// Hit attempt // Hit attempt
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, Melee {
attack, attack,
range: self.static_data.range, range: self.static_data.range,
max_angle: self.static_data.max_angle.to_radians(), max_angle: self.static_data.max_angle.to_radians(),
@ -222,14 +226,14 @@ impl CharacterBehavior for Data {
// Done // Done
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
} }
}, },
_ => { _ => {
// If it somehow ends up in an incorrect stage section // If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
}, },
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
combat::{ combat::{
Attack, AttackEffect, CombatBuff, CombatRequirement, Damage, DamageComponent, DamageSource, Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement, Damage,
EffectComponent, GroupTarget, Knockback, KnockbackDir, DamageSource, GroupTarget, Knockback, KnockbackDir,
}, },
comp::{ comp::{
projectile, Body, CharacterState, EnergyChange, EnergySource, Gravity, LightEmitter, projectile, Body, CharacterState, EnergyChange, EnergySource, Gravity, LightEmitter,
@ -103,22 +103,25 @@ impl CharacterBehavior for Data {
let charge_frac = (self.timer.as_secs_f32() let charge_frac = (self.timer.as_secs_f32()
/ self.static_data.charge_duration.as_secs_f32()) / self.static_data.charge_duration.as_secs_f32())
.min(1.0); .min(1.0);
let knockback = self.static_data.initial_knockback let knockback = AttackEffect::new(
+ charge_frac * self.static_data.scaled_knockback; Some(GroupTarget::OutOfGroup),
let knockback = AttackEffect::Knockback(Knockback { CombatEffect::Knockback(Knockback {
strength: knockback, strength: self.static_data.initial_knockback
direction: KnockbackDir::Away, + charge_frac * self.static_data.scaled_knockback,
}); direction: KnockbackDir::Away,
let knockback = EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) }),
.with_requirement(CombatRequirement::AnyDamage); )
let buff = AttackEffect::Buff(CombatBuff::default_physical()); .with_requirement(CombatRequirement::AnyDamage);
let damage = Damage { let buff = CombatEffect::Buff(CombatBuff::default_physical());
source: DamageSource::Projectile, let damage = AttackDamage::new(
value: self.static_data.initial_damage as f32 Damage {
+ charge_frac * self.static_data.scaled_damage as f32, source: DamageSource::Projectile,
}; value: self.static_data.initial_damage as f32
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)) + charge_frac * self.static_data.scaled_damage as f32,
.with_effect(buff); },
Some(GroupTarget::OutOfGroup),
)
.with_effect(buff);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_crit(0.5, 1.2) .with_crit(0.5, 1.2)

View File

@ -1,8 +1,6 @@
use crate::{ use crate::{
combat::{ combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
Attack, AttackEffect, CombatBuff, CombatRequirement, DamageComponent, EffectComponent, comp::{CharacterState, Melee, StateUpdate},
},
comp::{CharacterState, MeleeAttack, StateUpdate},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
@ -174,37 +172,40 @@ impl CharacterBehavior for Data {
.scales_from_combo .scales_from_combo
.min(self.combo / self.static_data.num_stages) .min(self.combo / self.static_data.num_stages)
* self.static_data.stage_data[stage_index].damage_increase; * self.static_data.stage_data[stage_index].damage_increase;
let poise = self.static_data.stage_data[stage_index].base_poise_damage let poise = self.static_data.stage_data[stage_index].base_poise_damage
+ self + self
.static_data .static_data
.scales_from_combo .scales_from_combo
.min(self.combo / self.static_data.num_stages) .min(self.combo / self.static_data.num_stages)
* self.static_data.stage_data[stage_index].poise_damage_increase; * self.static_data.stage_data[stage_index].poise_damage_increase;
let poise = AttackEffect::Poise(poise as f32); let poise = AttackEffect::new(
let poise = EffectComponent::new(Some(GroupTarget::OutOfGroup), poise) Some(GroupTarget::OutOfGroup),
.with_requirement(CombatRequirement::AnyDamage); CombatEffect::Poise(poise as f32),
)
let knockback = AttackEffect::Knockback(Knockback { .with_requirement(CombatRequirement::AnyDamage);
strength: self.static_data.stage_data[stage_index].knockback, let knockback = AttackEffect::new(
direction: KnockbackDir::Away, Some(GroupTarget::OutOfGroup),
}); CombatEffect::Knockback(Knockback {
let knockback = EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) strength: self.static_data.stage_data[stage_index].knockback,
.with_requirement(CombatRequirement::AnyDamage); direction: KnockbackDir::Away,
}),
)
.with_requirement(CombatRequirement::AnyDamage);
let energy = self.static_data.max_energy_gain.min( let energy = self.static_data.max_energy_gain.min(
self.static_data.initial_energy_gain self.static_data.initial_energy_gain
+ self.combo * self.static_data.energy_increase, + self.combo * self.static_data.energy_increase,
); );
let energy = AttackEffect::EnergyReward(energy); let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy))
let energy = EffectComponent::new(None, energy)
.with_requirement(CombatRequirement::AnyDamage); .with_requirement(CombatRequirement::AnyDamage);
let buff = AttackEffect::Buff(CombatBuff::default_physical()); let buff = CombatEffect::Buff(CombatBuff::default_physical());
let damage = Damage { let damage = AttackDamage::new(
source: DamageSource::Melee, Damage {
value: damage as f32, source: DamageSource::Melee,
}; value: damage as f32,
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)) },
.with_effect(buff); Some(GroupTarget::OutOfGroup),
)
.with_effect(buff);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_crit(0.5, 1.3) .with_crit(0.5, 1.3)
@ -212,7 +213,7 @@ impl CharacterBehavior for Data {
.with_effect(poise) .with_effect(poise)
.with_effect(knockback); .with_effect(knockback);
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, Melee {
attack, attack,
range: self.static_data.stage_data[stage_index].range, range: self.static_data.stage_data[stage_index].range,
max_angle: self.static_data.stage_data[stage_index].angle.to_radians(), max_angle: self.static_data.stage_data[stage_index].angle.to_radians(),
@ -290,14 +291,14 @@ impl CharacterBehavior for Data {
// Done // Done
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
} }
}, },
_ => { _ => {
// If it somehow ends up in an incorrect stage section // If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
}, },
} }
@ -312,7 +313,7 @@ impl CharacterBehavior for Data {
stage_section: self.stage_section, stage_section: self.stage_section,
next_stage: self.next_stage, next_stage: self.next_stage,
}); });
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
} }
} }

View File

@ -1,8 +1,6 @@
use crate::{ use crate::{
combat::{ combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
Attack, AttackEffect, CombatBuff, CombatRequirement, DamageComponent, EffectComponent, comp::{CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
},
comp::{CharacterState, EnergyChange, EnergySource, MeleeAttack, StateUpdate},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
@ -132,36 +130,40 @@ impl CharacterBehavior for Data {
if !self.exhausted { if !self.exhausted {
// Hit attempt (also checks if player is moving) // Hit attempt (also checks if player is moving)
if update.vel.0.distance_squared(Vec3::zero()) > 1.0 { if update.vel.0.distance_squared(Vec3::zero()) > 1.0 {
let poise = self.static_data.base_poise_damage as f32 let poise = AttackEffect::new(
+ charge_frac * self.static_data.scaled_poise_damage as f32; Some(GroupTarget::OutOfGroup),
let poise = AttackEffect::Poise(poise); CombatEffect::Poise(
let poise = EffectComponent::new(Some(GroupTarget::OutOfGroup), poise) self.static_data.base_poise_damage as f32
.with_requirement(CombatRequirement::AnyDamage); + charge_frac * self.static_data.scaled_poise_damage as f32,
let knockback = self.static_data.base_knockback ),
+ charge_frac * self.static_data.scaled_knockback; )
let knockback = AttackEffect::Knockback(Knockback { .with_requirement(CombatRequirement::AnyDamage);
strength: knockback, let knockback = AttackEffect::new(
direction: KnockbackDir::Away, Some(GroupTarget::OutOfGroup),
}); CombatEffect::Knockback(Knockback {
let knockback = strength: self.static_data.base_knockback
EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) + charge_frac * self.static_data.scaled_knockback,
.with_requirement(CombatRequirement::AnyDamage); direction: KnockbackDir::Away,
let buff = AttackEffect::Buff(CombatBuff::default_physical()); }),
let damage = Damage { )
source: DamageSource::Melee, .with_requirement(CombatRequirement::AnyDamage);
value: self.static_data.base_damage as f32 let buff = CombatEffect::Buff(CombatBuff::default_physical());
+ charge_frac * self.static_data.scaled_damage as f32, let damage = AttackDamage::new(
}; Damage {
let damage = source: DamageSource::Melee,
DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)) value: self.static_data.base_damage as f32
.with_effect(buff); + charge_frac * self.static_data.scaled_damage as f32,
},
Some(GroupTarget::OutOfGroup),
)
.with_effect(buff);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_crit(0.5, 1.3) .with_crit(0.5, 1.3)
.with_effect(poise) .with_effect(poise)
.with_effect(knockback); .with_effect(knockback);
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, Melee {
attack, attack,
range: self.static_data.range, range: self.static_data.range,
max_angle: self.static_data.angle.to_radians(), max_angle: self.static_data.angle.to_radians(),
@ -246,14 +248,14 @@ impl CharacterBehavior for Data {
// Done // Done
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
} }
}, },
_ => { _ => {
// If it somehow ends up in an incorrect stage section // If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
}, },
} }

View File

@ -1,8 +1,6 @@
use crate::{ use crate::{
combat::{ combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
Attack, AttackEffect, CombatBuff, CombatRequirement, DamageComponent, EffectComponent, comp::{CharacterState, Melee, StateUpdate},
},
comp::{CharacterState, MeleeAttack, StateUpdate},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *}, utils::{StageSection, *},
@ -150,22 +148,28 @@ impl CharacterBehavior for Data {
}, },
StageSection::Recover => { StageSection::Recover => {
if !self.exhausted { if !self.exhausted {
let poise = AttackEffect::Poise(self.static_data.base_poise_damage as f32); let poise = AttackEffect::new(
let poise = EffectComponent::new(Some(GroupTarget::OutOfGroup), poise) Some(GroupTarget::OutOfGroup),
.with_requirement(CombatRequirement::AnyDamage); CombatEffect::Poise(self.static_data.base_poise_damage as f32),
let knockback = AttackEffect::Knockback(Knockback { )
strength: self.static_data.knockback, .with_requirement(CombatRequirement::AnyDamage);
direction: KnockbackDir::Away, let knockback = AttackEffect::new(
}); Some(GroupTarget::OutOfGroup),
let knockback = EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) CombatEffect::Knockback(Knockback {
.with_requirement(CombatRequirement::AnyDamage); strength: self.static_data.knockback,
let buff = AttackEffect::Buff(CombatBuff::default_physical()); direction: KnockbackDir::Away,
let damage = Damage { }),
source: DamageSource::Melee, )
value: self.static_data.base_damage as f32, .with_requirement(CombatRequirement::AnyDamage);
}; let buff = CombatEffect::Buff(CombatBuff::default_physical());
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)) let damage = AttackDamage::new(
.with_effect(buff); Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
},
Some(GroupTarget::OutOfGroup),
)
.with_effect(buff);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_crit(0.5, 1.3) .with_crit(0.5, 1.3)
@ -173,7 +177,7 @@ impl CharacterBehavior for Data {
.with_effect(knockback); .with_effect(knockback);
// Hit attempt, when animation plays // Hit attempt, when animation plays
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, Melee {
attack, attack,
range: self.static_data.range, range: self.static_data.range,
max_angle: self.static_data.max_angle.to_radians(), max_angle: self.static_data.max_angle.to_radians(),
@ -202,14 +206,14 @@ impl CharacterBehavior for Data {
// Done // Done
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
} }
}, },
_ => { _ => {
// If it somehow ends up in an incorrect stage section // If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
}, },
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
combat::{ combat::{
Attack, AttackEffect, CombatRequirement, Damage, DamageComponent, DamageSource, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage, DamageSource,
EffectComponent, GroupTarget, Knockback, GroupTarget, Knockback,
}, },
comp::{shockwave, CharacterState, StateUpdate}, comp::{shockwave, CharacterState, StateUpdate},
event::ServerEvent, event::ServerEvent,
@ -83,17 +83,23 @@ impl CharacterBehavior for Data {
}); });
} else { } else {
// Attack // Attack
let poise = AttackEffect::Poise(self.static_data.poise_damage as f32); let poise = AttackEffect::new(
let poise = EffectComponent::new(Some(GroupTarget::OutOfGroup), poise) Some(GroupTarget::OutOfGroup),
.with_requirement(CombatRequirement::AnyDamage); CombatEffect::Poise(self.static_data.poise_damage as f32),
let knockback = AttackEffect::Knockback(self.static_data.knockback); )
let knockback = EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) .with_requirement(CombatRequirement::AnyDamage);
.with_requirement(CombatRequirement::AnyDamage); let knockback = AttackEffect::new(
let damage = Damage { Some(GroupTarget::OutOfGroup),
source: DamageSource::Shockwave, CombatEffect::Knockback(self.static_data.knockback),
value: self.static_data.damage as f32, )
}; .with_requirement(CombatRequirement::AnyDamage);
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)); let damage = AttackDamage::new(
Damage {
source: DamageSource::Shockwave,
value: self.static_data.damage as f32,
},
Some(GroupTarget::OutOfGroup),
);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_effect(poise) .with_effect(poise)

View File

@ -1,8 +1,6 @@
use crate::{ use crate::{
combat::{ combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
Attack, AttackEffect, CombatBuff, CombatRequirement, DamageComponent, EffectComponent, comp::{CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
},
comp::{CharacterState, EnergyChange, EnergySource, MeleeAttack, StateUpdate},
consts::GRAVITY, consts::GRAVITY,
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -115,22 +113,28 @@ impl CharacterBehavior for Data {
..*self ..*self
}); });
let poise = AttackEffect::Poise(self.static_data.base_poise_damage as f32); let poise = AttackEffect::new(
let poise = EffectComponent::new(Some(GroupTarget::OutOfGroup), poise) Some(GroupTarget::OutOfGroup),
.with_requirement(CombatRequirement::AnyDamage); CombatEffect::Poise(self.static_data.base_poise_damage as f32),
let knockback = AttackEffect::Knockback(Knockback { )
strength: self.static_data.knockback, .with_requirement(CombatRequirement::AnyDamage);
direction: KnockbackDir::Away, let knockback = AttackEffect::new(
}); Some(GroupTarget::OutOfGroup),
let knockback = EffectComponent::new(Some(GroupTarget::OutOfGroup), knockback) CombatEffect::Knockback(Knockback {
.with_requirement(CombatRequirement::AnyDamage); strength: self.static_data.knockback,
let buff = AttackEffect::Buff(CombatBuff::default_physical()); direction: KnockbackDir::Away,
let damage = Damage { }),
source: DamageSource::Melee, )
value: self.static_data.base_damage as f32, .with_requirement(CombatRequirement::AnyDamage);
}; let buff = CombatEffect::Buff(CombatBuff::default_physical());
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup)) let damage = AttackDamage::new(
.with_effect(buff); Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
},
Some(GroupTarget::OutOfGroup),
)
.with_effect(buff);
let attack = Attack::default() let attack = Attack::default()
.with_damage(damage) .with_damage(damage)
.with_crit(0.5, 1.3) .with_crit(0.5, 1.3)
@ -138,7 +142,7 @@ impl CharacterBehavior for Data {
.with_effect(knockback); .with_effect(knockback);
// Hit attempt // Hit attempt
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, Melee {
attack, attack,
range: self.static_data.range, range: self.static_data.range,
max_angle: 180_f32.to_radians(), max_angle: 180_f32.to_radians(),
@ -213,14 +217,14 @@ impl CharacterBehavior for Data {
// Done // Done
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
} }
}, },
_ => { _ => {
// If it somehow ends up in an incorrect stage section // If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding; update.character = CharacterState::Wielding;
// Make sure attack component is removed // Make sure attack component is removed
data.updater.remove::<MeleeAttack>(data.entity); data.updater.remove::<Melee>(data.entity);
}, },
} }

View File

@ -1,4 +1,5 @@
use common::{ use common::{
combat::AttackerInfo,
comp::{ comp::{
group, Beam, BeamSegment, Body, Energy, Health, HealthSource, Inventory, Last, Ori, Pos, group, Beam, BeamSegment, Body, Energy, Health, HealthSource, Inventory, Last, Ori, Pos,
Scale, Scale,
@ -168,25 +169,27 @@ impl<'a> System<'a> for Sys {
continue; continue;
} }
let server_events = beam_segment.properties.attack.apply_attack( let attacker_info =
beam_owner
.zip(beam_segment.owner)
.map(|(entity, uid)| AttackerInfo {
entity,
uid,
energy: energies.get(entity),
});
beam_segment.properties.attack.apply_attack(
target_group, target_group,
beam_owner, attacker_info,
b, b,
inventory_b_maybe, inventory_b_maybe,
beam_segment.owner,
beam_owner.and_then(|e| energies.get(e)),
ori.0, ori.0,
false, false,
1.0, 1.0,
|e| server_emitter.emit(e),
); );
if !server_events.is_empty() { hit_entities.push(*uid_b);
hit_entities.push(*uid_b);
}
for event in server_events {
server_emitter.emit(event);
}
} }
} }
} }

View File

@ -3,8 +3,8 @@ use specs::{Entities, Join, LazyUpdate, Read, ReadExpect, ReadStorage, System, W
use common::{ use common::{
comp::{ comp::{
inventory::slot::{EquipSlot, Slot}, inventory::slot::{EquipSlot, Slot},
Beam, Body, CharacterState, Controller, Energy, Health, Inventory, MeleeAttack, Mounting, Beam, Body, CharacterState, Controller, Energy, Health, Inventory, Melee, Mounting, Ori,
Ori, PhysicsState, Poise, PoiseState, Pos, StateUpdate, Stats, Vel, PhysicsState, Poise, PoiseState, Pos, StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
metrics::SysMetrics, metrics::SysMetrics,
@ -69,7 +69,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Poise>, WriteStorage<'a, Poise>,
ReadStorage<'a, Body>, ReadStorage<'a, Body>,
ReadStorage<'a, PhysicsState>, ReadStorage<'a, PhysicsState>,
ReadStorage<'a, MeleeAttack>, ReadStorage<'a, Melee>,
ReadStorage<'a, Beam>, ReadStorage<'a, Beam>,
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
ReadStorage<'a, Mounting>, ReadStorage<'a, Mounting>,

View File

@ -1,5 +1,6 @@
use common::{ use common::{
comp::{group, Body, CharacterState, Energy, Health, Inventory, MeleeAttack, Ori, Pos, Scale}, combat::AttackerInfo,
comp::{group, Body, CharacterState, Energy, Health, Inventory, Melee, Ori, Pos, Scale},
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
metrics::SysMetrics, metrics::SysMetrics,
span, span,
@ -29,7 +30,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Energy>, ReadStorage<'a, Energy>,
ReadStorage<'a, Inventory>, ReadStorage<'a, Inventory>,
ReadStorage<'a, group::Group>, ReadStorage<'a, group::Group>,
WriteStorage<'a, MeleeAttack>, WriteStorage<'a, Melee>,
ReadStorage<'a, CharacterState>, ReadStorage<'a, CharacterState>,
); );
@ -129,25 +130,24 @@ impl<'a> System<'a> for Sys {
let dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); let dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
let server_events = attack.attack.apply_attack( let attacker_info = Some(AttackerInfo {
entity,
uid: *uid,
energy: energies.get(entity),
});
attack.attack.apply_attack(
target_group, target_group,
Some(entity), attacker_info,
b, b,
inventory_b_maybe, inventory_b_maybe,
Some(*uid),
energies.get(entity),
dir, dir,
is_dodge, is_dodge,
1.0, 1.0,
|e| server_emitter.emit(e),
); );
if !server_events.is_empty() { attack.hit_count += 1;
attack.hit_count += 1;
}
for event in server_events {
server_emitter.emit(event);
}
} }
} }
} }

View File

@ -1,4 +1,5 @@
use common::{ use common::{
combat::AttackerInfo,
comp::{ comp::{
projectile, Energy, Group, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile, Vel, projectile, Energy, Group, HealthSource, Inventory, Ori, PhysicsState, Pos, Projectile, Vel,
}, },
@ -109,50 +110,28 @@ impl<'a> System<'a> for Sys {
let owner_entity = projectile let owner_entity = projectile
.owner .owner
.and_then(|u| uid_allocator.retrieve_entity_internal(u.into())); .and_then(|u| uid_allocator.retrieve_entity_internal(u.into()));
let server_events = attack.apply_attack(
let attacker_info =
owner_entity.zip(projectile.owner).map(|(entity, uid)| {
AttackerInfo {
entity,
uid,
energy: energies.get(entity),
}
});
attack.apply_attack(
target_group, target_group,
owner_entity, attacker_info,
target_entity, target_entity,
inventories.get(target_entity), inventories.get(target_entity),
projectile.owner,
owner_entity.and_then(|e| energies.get(e)),
ori.0, ori.0,
false, false,
1.0, 1.0,
|e| server_emitter.emit(e),
); );
for event in server_events {
server_emitter.emit(event);
}
} }
}, },
// projectile::Effect::Knockback(knockback) => {
// if let Some(other_entity) =
// uid_allocator.retrieve_entity_internal(other.into())
// {
// let impulse = knockback.calculate_impulse(ori.0);
// if !impulse.is_approx_zero() {
// server_emitter.emit(ServerEvent::Knockback {
// entity: other_entity,
// impulse,
// });
// }
// }
// },
// projectile::Effect::RewardEnergy(energy) => {
// if let Some(entity_owner) = projectile
// .owner
// .and_then(|u| uid_allocator.retrieve_entity_internal(u.into()))
// {
// server_emitter.emit(ServerEvent::EnergyChange {
// entity: entity_owner,
// change: EnergyChange {
// amount: energy as i32,
// source: EnergySource::HitEnemy,
// },
// });
// }
// },
projectile::Effect::Explode(e) => { projectile::Effect::Explode(e) => {
server_emitter.emit(ServerEvent::Explosion { server_emitter.emit(ServerEvent::Explosion {
pos: pos.0, pos: pos.0,
@ -175,26 +154,6 @@ impl<'a> System<'a> for Sys {
} }
} }
}, },
// TODO: Change to effect after !1472 merges
// projectile::Effect::Buff { buff, chance } => {
// if let Some(entity) =
// uid_allocator.retrieve_entity_internal(other.into())
// {
// if chance.map_or(true, |c| thread_rng().gen::<f32>() < c) {
// let source = if let Some(owner) = projectile.owner {
// BuffSource::Character { by: owner }
// } else {
// BuffSource::Unknown
// };
// let buff =
// Buff::new(buff.kind, buff.data, buff.cat_ids, source);
// server_emitter.emit(ServerEvent::Buff {
// entity,
// buff_change: BuffChange::Add(buff),
// });
// }
// }
// },
_ => {}, _ => {},
} }
} }

View File

@ -1,4 +1,5 @@
use common::{ use common::{
combat::AttackerInfo,
comp::{ comp::{
group, Body, Energy, Health, HealthSource, Inventory, Last, Ori, PhysicsState, Pos, Scale, group, Body, Energy, Health, HealthSource, Inventory, Last, Ori, PhysicsState, Pos, Scale,
Shockwave, ShockwaveHitEntities, Shockwave, ShockwaveHitEntities,
@ -195,25 +196,27 @@ impl<'a> System<'a> for Sys {
if hit { if hit {
let dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); let dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
let server_events = shockwave.properties.attack.apply_attack( let attacker_info =
shockwave_owner
.zip(shockwave.owner)
.map(|(entity, uid)| AttackerInfo {
entity,
uid,
energy: energies.get(entity),
});
shockwave.properties.attack.apply_attack(
target_group, target_group,
shockwave_owner, attacker_info,
b, b,
inventories.get(b), inventories.get(b),
shockwave.owner,
shockwave_owner.and_then(|e| energies.get(e)),
dir, dir,
false, false,
1.0, 1.0,
|e| server_emitter.emit(e),
); );
if !server_events.is_empty() { shockwave_hit_list.hit_entities.push(*uid_b);
shockwave_hit_list.hit_entities.push(*uid_b);
}
for event in server_events {
server_emitter.emit(event);
}
} }
} }
} }

View File

@ -163,7 +163,7 @@ impl State {
ecs.register::<comp::Admin>(); ecs.register::<comp::Admin>();
ecs.register::<comp::Waypoint>(); ecs.register::<comp::Waypoint>();
ecs.register::<comp::Projectile>(); ecs.register::<comp::Projectile>();
ecs.register::<comp::MeleeAttack>(); ecs.register::<comp::Melee>();
ecs.register::<comp::ItemDrop>(); ecs.register::<comp::ItemDrop>();
ecs.register::<comp::ChatMode>(); ecs.register::<comp::ChatMode>();
ecs.register::<comp::Faction>(); ecs.register::<comp::Faction>();

View File

@ -174,7 +174,7 @@ pub fn handle_beam(server: &mut Server, properties: beam::Properties, pos: Pos,
heal: properties heal: properties
.attack .attack
.effects() .effects()
.any(|e| matches!(e.effect(), combat::AttackEffect::Heal(h) if *h > 0.0)), .any(|e| matches!(e.effect(), combat::CombatEffect::Heal(h) if *h > 0.0)),
}); });
state.create_beam(properties, pos, ori).build(); state.create_beam(properties, pos, ori).build();
} }

View File

@ -510,7 +510,8 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
source: DamageSource::Falling, source: DamageSource::Falling,
value: falldmg, value: falldmg,
}; };
let change = damage.modify_damage(inventories.get(entity), None, false, 0.0, 1.0); let change =
damage.calculate_health_change(inventories.get(entity), None, false, 0.0, 1.0);
health.change_by(change); health.change_by(change);
} }
// Handle poise change // Handle poise change
@ -578,7 +579,7 @@ pub fn handle_explosion(
// Add an outcome // Add an outcome
// Uses radius as outcome power, makes negative if explosion has healing effect // Uses radius as outcome power, makes negative if explosion has healing effect
let outcome_power = explosion.radius let outcome_power = explosion.radius
* if explosion.effects.iter().any(|e| matches!(e, RadiusEffect::Attack(a) if a.effects().any(|e| matches!(e.effect(), combat::AttackEffect::Heal(h) if *h > 0.0)))) { * if explosion.effects.iter().any(|e| matches!(e, RadiusEffect::Attack(a) if a.effects().any(|e| matches!(e.effect(), combat::CombatEffect::Heal(h) if *h > 0.0)))) {
-1.0 -1.0
} else { } else {
1.0 1.0
@ -708,23 +709,27 @@ pub fn handle_explosion(
.unwrap_or_else(Vec3::unit_z), .unwrap_or_else(Vec3::unit_z),
); );
let server_events = attack.apply_attack( let attacker_info =
target_group, owner_entity
owner_entity, .zip(owner)
entity_b, .map(|(entity, uid)| combat::AttackerInfo {
inventory_b_maybe, entity,
owner, uid,
owner_entity.and_then(|e| energies.get(e)), energy: energies.get(entity),
dir, });
false,
strength,
);
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>(); let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
for event in server_events { attack.apply_attack(
server_eventbus.emit_now(event); target_group,
} attacker_info,
entity_b,
inventory_b_maybe,
dir,
false,
strength,
|e| server_eventbus.emit_now(e),
);
} }
} }
}, },

View File

@ -90,7 +90,13 @@ impl StateExt for State {
}, },
Effect::Damage(damage) => { Effect::Damage(damage) => {
let inventories = self.ecs().read_storage::<Inventory>(); let inventories = self.ecs().read_storage::<Inventory>();
let change = damage.modify_damage(inventories.get(entity), source, false, 0.0, 1.0); let change = damage.calculate_health_change(
inventories.get(entity),
source,
false,
0.0,
1.0,
);
self.ecs() self.ecs()
.write_storage::<comp::Health>() .write_storage::<comp::Health>()
.get_mut(entity) .get_mut(entity)