Applied basic functionality of attack-effects system to melee.

This commit is contained in:
Sam 2021-01-25 21:50:16 -05:00
parent d3b75df76f
commit 2690e9caa1
10 changed files with 177 additions and 89 deletions

View File

@ -24,11 +24,12 @@ pub enum GroupTarget {
OutOfGroup, OutOfGroup,
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct Attack { pub struct Attack {
damages: Vec<DamageComponent>, damages: Vec<DamageComponent>,
effects: Vec<EffectComponent>, effects: Vec<EffectComponent>,
crit_chance: f32, pub crit_chance: f32,
crit_multiplier: f32, pub crit_multiplier: f32,
} }
impl Default for Attack { impl Default for Attack {
@ -58,8 +59,17 @@ impl Attack {
self.crit_multiplier = cm; self.crit_multiplier = cm;
self self
} }
pub fn damages(&self) -> impl Iterator<Item = &DamageComponent> {
self.damages.iter()
}
pub fn effects(&self) -> impl Iterator<Item = &EffectComponent> {
self.effects.iter()
}
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct DamageComponent { pub struct DamageComponent {
damage: Damage, damage: Damage,
target: Option<GroupTarget>, target: Option<GroupTarget>,
@ -79,8 +89,21 @@ impl DamageComponent {
self.effects.push(effect); self.effects.push(effect);
self self
} }
pub fn target(&self) -> Option<GroupTarget> {
self.target
}
pub fn damage(&self) -> Damage {
self.damage
}
pub fn effects(&self) -> impl Iterator<Item = &AttackEffect> {
self.effects.iter()
}
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct EffectComponent { pub struct EffectComponent {
target: Option<GroupTarget>, target: Option<GroupTarget>,
effect: AttackEffect, effect: AttackEffect,
@ -92,12 +115,13 @@ impl EffectComponent {
} }
} }
#[derive(Debug, Serialize, Deserialize)]
pub enum AttackEffect { pub enum AttackEffect {
Heal(f32), //Heal(f32),
Buff(effect::BuffEffect), //Buff(effect::BuffEffect),
Knockback(Knockback), Knockback(Knockback),
EnergyChange(f32), /*EnergyChange(f32),
Lifesteal(f32), *Lifesteal(f32), */
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]

View File

@ -1,8 +1,8 @@
use crate::{ use crate::{
combat::Attack,
comp::{Energy, Ori, PoiseChange, Pos, Vel}, comp::{Energy, Ori, PoiseChange, Pos, Vel},
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ServerEvent},
states::{behavior::JoinData, *}, states::{behavior::JoinData, *},
Damage, GroupTarget, Knockback,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, VecStorage}; use specs::{Component, DerefFlaggedStorage, VecStorage};
@ -169,14 +169,13 @@ impl Component for CharacterState {
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>; type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct MeleeAttack { pub struct MeleeAttack {
pub effects: Vec<(Option<GroupTarget>, Damage, PoiseChange)>, pub attack: Attack,
pub range: f32, pub range: f32,
pub max_angle: f32, pub max_angle: f32,
pub applied: bool, pub applied: bool,
pub hit_count: u32, pub hit_count: u32,
pub knockback: Knockback,
} }
impl Component for MeleeAttack { impl Component for MeleeAttack {

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
combat::{Attack, AttackEffect, DamageComponent},
comp::{ comp::{
CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource, CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource,
StateUpdate, StateUpdate,
@ -92,27 +93,25 @@ impl CharacterBehavior for Data {
..*self ..*self
}); });
let damage = Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
};
let knockback = AttackEffect::Knockback(Knockback {
strength: self.static_data.knockback,
direction: KnockbackDir::Away,
});
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup))
.with_effect(knockback);
let attack = Attack::default().with_damage(damage);
// Hit attempt // Hit attempt
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, MeleeAttack {
effects: vec![( attack,
Some(GroupTarget::OutOfGroup),
Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
},
PoiseChange {
amount: -(self.static_data.base_poise_damage as i32),
source: PoiseSource::Attack,
},
)],
range: self.static_data.range, range: self.static_data.range,
max_angle: 180_f32.to_radians(), max_angle: self.static_data.max_angle,
applied: false, applied: false,
hit_count: 0, hit_count: 0,
knockback: Knockback {
strength: self.static_data.knockback,
direction: KnockbackDir::Away,
},
}); });
} else if self.timer < self.static_data.swing_duration { } else if self.timer < self.static_data.swing_duration {
// Swings // Swings

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
combat::{Attack, AttackEffect, DamageComponent},
comp::{ comp::{
CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource, CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource,
StateUpdate, StateUpdate,
@ -168,18 +169,21 @@ impl CharacterBehavior for Data {
}; };
let knockback = self.static_data.initial_knockback let knockback = self.static_data.initial_knockback
+ self.charge_amount * self.static_data.scaled_knockback; + self.charge_amount * self.static_data.scaled_knockback;
let knockback = AttackEffect::Knockback(Knockback {
strength: knockback,
direction: KnockbackDir::Away,
});
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup))
.with_effect(knockback);
let attack = Attack::default().with_damage(damage);
// Hit attempt // Hit attempt
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, MeleeAttack {
effects: vec![(Some(GroupTarget::OutOfGroup), damage, poise_damage)], 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(),
applied: false, applied: false,
hit_count: 0, hit_count: 0,
knockback: Knockback {
strength: knockback,
direction: KnockbackDir::Away,
},
}); });
} else if self.timer < self.static_data.swing_duration { } else if self.timer < self.static_data.swing_duration {
// Swings // Swings

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
combat::{Attack, AttackEffect, DamageComponent},
comp::{ comp::{
CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource, CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource,
StateUpdate, StateUpdate,
@ -181,26 +182,25 @@ 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].poise_damage_increase; * self.static_data.stage_data[stage_index].poise_damage_increase;
let damage = Damage {
source: DamageSource::Melee,
value: damage as f32,
};
let knockback = AttackEffect::Knockback(Knockback {
strength: self.static_data.stage_data[stage_index].knockback,
direction: KnockbackDir::Away,
});
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup))
.with_effect(knockback);
let attack = Attack::default().with_damage(damage);
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, MeleeAttack {
effects: vec![( attack,
Some(GroupTarget::OutOfGroup),
Damage {
source: DamageSource::Melee,
value: damage as f32,
},
PoiseChange {
amount: -(poise_damage as i32),
source: PoiseSource::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(),
applied: false, applied: false,
hit_count: 0, hit_count: 0,
knockback: Knockback {
strength: self.static_data.stage_data[stage_index].knockback,
direction: KnockbackDir::Away,
},
}); });
} }
}, },

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
combat::{Attack, AttackEffect, DamageComponent},
comp::{ comp::{
CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource, CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource,
StateUpdate, StateUpdate,
@ -145,20 +146,21 @@ impl CharacterBehavior for Data {
}; };
let knockback = self.static_data.base_knockback let knockback = self.static_data.base_knockback
+ charge_frac * self.static_data.scaled_knockback; + charge_frac * self.static_data.scaled_knockback;
let knockback = AttackEffect::Knockback(Knockback {
strength: knockback,
direction: KnockbackDir::Away,
});
let damage =
DamageComponent::new(damage, Some(GroupTarget::OutOfGroup))
.with_effect(knockback);
let attack = Attack::default().with_damage(damage);
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, MeleeAttack {
effects: vec![( attack,
Some(GroupTarget::OutOfGroup),
damage,
poise_damage,
)],
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(),
applied: false, applied: false,
hit_count: 0, hit_count: 0,
knockback: Knockback {
strength: knockback,
direction: KnockbackDir::Away,
},
}); });
} }
update.character = CharacterState::DashMelee(Data { update.character = CharacterState::DashMelee(Data {

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
combat::{Attack, AttackEffect, DamageComponent},
comp::{CharacterState, MeleeAttack, PoiseChange, PoiseSource, StateUpdate}, comp::{CharacterState, MeleeAttack, PoiseChange, PoiseSource, StateUpdate},
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
@ -147,27 +148,25 @@ impl CharacterBehavior for Data {
}, },
StageSection::Recover => { StageSection::Recover => {
if !self.exhausted { if !self.exhausted {
let damage = Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
};
let knockback = AttackEffect::Knockback(Knockback {
strength: self.static_data.knockback,
direction: KnockbackDir::Away,
});
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup))
.with_effect(knockback);
let attack = Attack::default().with_damage(damage);
// Hit attempt, when animation plays // Hit attempt, when animation plays
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, MeleeAttack {
effects: vec![( attack,
Some(GroupTarget::OutOfGroup),
Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
},
PoiseChange {
amount: -(self.static_data.base_poise_damage as i32),
source: PoiseSource::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(),
applied: false, applied: false,
hit_count: 0, hit_count: 0,
knockback: Knockback {
strength: self.static_data.knockback,
direction: KnockbackDir::Away,
},
}); });
update.character = CharacterState::LeapMelee(Data { update.character = CharacterState::LeapMelee(Data {

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
combat::{Attack, AttackEffect, DamageComponent},
comp::{ comp::{
CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource, CharacterState, EnergyChange, EnergySource, MeleeAttack, PoiseChange, PoiseSource,
StateUpdate, StateUpdate,
@ -114,27 +115,26 @@ impl CharacterBehavior for Data {
exhausted: true, exhausted: true,
..*self ..*self
}); });
let damage = Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
};
let knockback = AttackEffect::Knockback(Knockback {
strength: self.static_data.knockback,
direction: KnockbackDir::Away,
});
let damage = DamageComponent::new(damage, Some(GroupTarget::OutOfGroup))
.with_effect(knockback);
let attack = Attack::default().with_damage(damage);
// Hit attempt // Hit attempt
data.updater.insert(data.entity, MeleeAttack { data.updater.insert(data.entity, MeleeAttack {
effects: vec![( attack,
Some(GroupTarget::OutOfGroup),
Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
},
PoiseChange {
amount: -(self.static_data.base_poise_damage as i32),
source: PoiseSource::Attack,
},
)],
range: self.static_data.range, range: self.static_data.range,
max_angle: 180_f32.to_radians(), max_angle: 180_f32.to_radians(),
applied: false, applied: false,
hit_count: 0, hit_count: 0,
knockback: Knockback {
strength: self.static_data.knockback,
direction: KnockbackDir::Away,
},
}); });
} else if self.timer < self.static_data.swing_duration { } else if self.timer < self.static_data.swing_duration {
if matches!( if matches!(

View File

@ -16,7 +16,15 @@ pub mod state;
mod stats; mod stats;
// External // External
use specs::DispatcherBuilder; use common::{
combat::{Attack, AttackEffect, GroupTarget},
comp::Inventory,
event::ServerEvent,
uid::Uid,
util::Dir,
};
use rand::{thread_rng, Rng};
use specs::{DispatcherBuilder, Entity as EcsEntity};
// System names // System names
pub const CHARACTER_BEHAVIOR_SYS: &str = "character_behavior_sys"; pub const CHARACTER_BEHAVIOR_SYS: &str = "character_behavior_sys";
@ -48,3 +56,39 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(melee::Sys, MELEE_SYS, &[PROJECTILE_SYS]); dispatch_builder.add(melee::Sys, MELEE_SYS, &[PROJECTILE_SYS]);
dispatch_builder.add(aura::Sys, AURAS_SYS, &[]); dispatch_builder.add(aura::Sys, AURAS_SYS, &[]);
} }
pub fn apply_attack(
attack: &Attack,
target_group: GroupTarget,
target_entity: EcsEntity,
inventory: Option<&Inventory>,
uid: Uid,
dir: Dir,
) -> Vec<ServerEvent> {
let is_crit = thread_rng().gen::<f32>() < attack.crit_chance;
let mut accumulated_damage = 0.0;
let mut server_events = Vec::new();
for damage in attack
.damages()
.filter(|d| d.target().map_or(true, |t| t == target_group))
{
let change = damage.damage().modify_damage(inventory, Some(uid));
if change.amount != 0 {
server_events.push(ServerEvent::Damage { entity: target_entity, change });
for effect in damage.effects() {
match effect {
AttackEffect::Knockback(kb) => {
let impulse = kb.calculate_impulse(dir);
if !impulse.is_approx_zero() {
server_events.push(ServerEvent::Knockback {
entity: target_entity,
impulse,
});
}
},
}
}
}
}
server_events
}

View File

@ -1,3 +1,4 @@
use crate::apply_attack;
use common::{ use common::{
comp::{buff, group, Body, CharacterState, Health, Inventory, MeleeAttack, Ori, Pos, Scale}, comp::{buff, group, Body, CharacterState, Health, Inventory, MeleeAttack, Ori, Pos, Scale},
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
@ -75,13 +76,14 @@ impl<'a> System<'a> for Sys {
attack.applied = true; attack.applied = true;
// Go through all other entities // Go through all other entities
for (b, pos_b, scale_b_maybe, health_b, body_b, char_state_b_maybe) in ( for (b, pos_b, scale_b_maybe, health_b, body_b, char_state_b_maybe, inventory_b_maybe) in (
&entities, &entities,
&positions, &positions,
scales.maybe(), scales.maybe(),
&healths, &healths,
&bodies, &bodies,
char_states.maybe(), char_states.maybe(),
inventories.maybe(),
) )
.join() .join()
{ {
@ -118,7 +120,22 @@ impl<'a> System<'a> for Sys {
GroupTarget::OutOfGroup GroupTarget::OutOfGroup
}; };
for (target, damage, poise_change) in attack.effects.iter() { let dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0));
let server_events = apply_attack(
&attack.attack,
target_group,
b,
inventory_b_maybe,
*uid,
dir,
);
for event in server_events {
server_emitter.emit(event);
}
/*for (target, damage) in attack.damages.iter() {
if let Some(target) = target { if let Some(target) = target {
if *target != target_group if *target != target_group
|| (!matches!(target, GroupTarget::InGroup) && is_dodge) || (!matches!(target, GroupTarget::InGroup) && is_dodge)
@ -165,7 +182,7 @@ impl<'a> System<'a> for Sys {
} }
attack.hit_count += 1; attack.hit_count += 1;
} }*/
} }
} }
} }