Cleaving abilities

This commit is contained in:
Sam 2022-03-05 01:35:57 -05:00
parent a4d3de4833
commit ec03bd7032
22 changed files with 338 additions and 94 deletions

View File

@ -13,7 +13,6 @@ BasicRanged(
col: (1.0, 0.75, 0.11).into(),
..Default::default()
}),*/
projectile_gravity: Some(Gravity(0.15)),
projectile_speed: 60.0,
num_projectiles: 1,
projectile_spread: 0.0,

View File

@ -13,7 +13,6 @@ BasicRanged(
col: (1.0, 0.75, 0.11).into(),
..Default::default()
}),*/
projectile_gravity: Some(Gravity(0.15)),
projectile_speed: 60.0,
num_projectiles: 1,
projectile_spread: 0.0,

View File

@ -24,5 +24,4 @@ DashMelee(
recover_duration: 2.0,
ori_modifier: 0.1,
charge_through: true,
is_interruptible: false,
)

View File

@ -14,7 +14,6 @@ SpinMelee(
energy_cost: 0.0,
is_infinite: false,
movement_behavior: Stationary,
is_interruptible: false,
forward_speed: 0.0,
num_spins: 1,
specifier: None,

View File

@ -17,5 +17,4 @@ LeapMelee(
),
forward_leap_strength: 20.0,
vertical_leap_strength: 5.0,
damage_kind: Crushing,
)

View File

@ -15,7 +15,6 @@ SpinMelee(
energy_cost: 0,
is_infinite: false,
movement_behavior: Stationary,
is_interruptible: false,
forward_speed: 0.0,
num_spins: 1,
specifier: None,

View File

@ -1,25 +1,45 @@
// TODO: Make actual ability, just for testing right now
BasicMelee(
energy_cost: 50,
buildup_duration: 0.3,
swing_duration: 0.1,
recover_duration: 0.2,
melee_constructor: (
kind: Stab(
damage: 10,
poise: 0,
knockback: 0,
energy_regen: 0,
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 6,
poise: 0,
knockback: 0,
energy_regen: 3,
),
range: 3.0,
angle: 45.0,
multi_target: true,
),
buildup_duration: 0.3,
swing_duration: 0.1,
hit_timing: 0.5,
recover_duration: 0.5,
ori_modifier: 0.6,
),
range: 5.0,
angle: 10.0,
),
ori_modifier: 1.0,
(
melee_constructor: (
kind: Slash(
damage: 8,
poise: 0,
knockback: 0,
energy_regen: 5,
),
range: 3.0,
angle: 45.0,
multi_target: true,
),
buildup_duration: 0.3,
swing_duration: 0.1,
hit_timing: 0.5,
recover_duration: 0.3,
ori_modifier: 0.6,
),
],
is_stance: true,
energy_cost_per_strike: 10,
meta: (
kind: Some(Sword(Balanced)),
capabilities: (
// Block
bits: 0b00000010,
),
kind: Some(Sword(Cleaving)),
),
)
)

View File

@ -1,25 +1,21 @@
// TODO: Make actual ability, just for testing right now
BasicMelee(
energy_cost: 50,
buildup_duration: 0.3,
DiveMelee(
energy_cost: 20,
vertical_speed: 15,
movement_duration: 5,
swing_duration: 0.1,
recover_duration: 0.2,
recover_duration: 0.3,
melee_constructor: (
kind: Stab(
damage: 10,
kind: Slash(
damage: 30,
poise: 0,
knockback: 0,
energy_regen: 0,
energy_regen: 10,
),
range: 5.0,
angle: 10.0,
range: 6.0,
angle: 15.0,
multi_target: true,
),
ori_modifier: 1.0,
meta: (
kind: Some(Sword(Balanced)),
capabilities: (
// Block
bits: 0b00000010,
),
kind: Some(Sword(Cleaving)),
),
)
)

View File

@ -1,25 +1,25 @@
// TODO: Make actual ability, just for testing right now
BasicMelee(
energy_cost: 50,
buildup_duration: 0.3,
FinisherMelee(
energy_cost: 40,
buildup_duration: 0.4,
swing_duration: 0.1,
recover_duration: 0.2,
recover_duration: 0.4,
melee_constructor: (
kind: Stab(
damage: 10,
kind: Slash(
damage: 25,
poise: 0,
knockback: 0,
energy_regen: 0,
energy_regen: 10,
),
range: 5.0,
angle: 10.0,
range: 3.0,
angle: 15.0,
damage_effect: Some(ResetMelee),
),
ori_modifier: 1.0,
scaling: Some((
target: Buff,
kind: Linear,
)),
minimum_combo: 10,
meta: (
kind: Some(Sword(Balanced)),
capabilities: (
// Block
bits: 0b00000010,
),
kind: Some(Sword(Cleaving)),
),
)
)

View File

@ -1,25 +1,27 @@
// TODO: Make actual ability, just for testing right now
BasicMelee(
energy_cost: 50,
buildup_duration: 0.3,
swing_duration: 0.1,
recover_duration: 0.2,
melee_constructor: (
kind: Stab(
damage: 10,
poise: 0,
knockback: 0,
energy_regen: 0,
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 7,
poise: 0,
knockback: 0,
energy_regen: 10,
),
range: 6.0,
angle: 360.0,
multi_target: true,
),
buildup_duration: 0.3,
swing_duration: 0.1,
hit_timing: 0.6,
recover_duration: 0.3,
ori_modifier: 0.6,
),
range: 5.0,
angle: 10.0,
),
ori_modifier: 1.0,
],
is_stance: false,
energy_cost_per_strike: 20,
meta: (
kind: Some(Sword(Balanced)),
capabilities: (
// Block
bits: 0b00000010,
),
kind: Some(Sword(Cleaving)),
),
)
)

View File

@ -253,8 +253,10 @@ impl Attack {
if let Some(target_energy) = target.energy {
let energy_change = applied_damage * SLASHING_ENERGY_FRACTION;
if energy_change > target_energy.current() {
let health_damage = energy_change - target_energy.current();
accumulated_damage += health_damage;
let health_change = HealthChange {
amount: -(energy_change - target_energy.current()),
amount: -health_damage,
by: attacker.map(|x| x.into()),
cause: Some(damage.damage.source),
time,
@ -412,6 +414,18 @@ impl Attack {
});
}
},
CombatEffect::ResetMelee => {
if let Some(attacker_entity) = attacker.map(|a| a.entity) {
if target
.health
.map_or(false, |h| accumulated_damage > h.current())
{
emit(ServerEvent::ResetMelee {
entity: attacker_entity,
});
}
}
},
}
}
}
@ -559,6 +573,18 @@ impl Attack {
});
}
},
CombatEffect::ResetMelee => {
if let Some(attacker_entity) = attacker.map(|a| a.entity) {
if target
.health
.map_or(false, |h| accumulated_damage > h.current())
{
emit(ServerEvent::ResetMelee {
entity: attacker_entity,
});
}
}
},
}
}
}
@ -690,6 +716,8 @@ pub enum CombatEffect {
Lifesteal(f32),
Poise(f32),
Combo(i32),
// If the attack kills the target, reset the melee attack
ResetMelee,
}
#[cfg(not(target_arch = "wasm32"))]

View File

@ -406,12 +406,14 @@ impl From<&CharacterState> for CharacterAbilityType {
| CharacterState::Skate(_)
| CharacterState::Wallrun(_)
| CharacterState::ComboMelee2(_)
| CharacterState::FinisherMelee(_) => Self::Other,
| CharacterState::FinisherMelee(_)
| CharacterState::DiveMelee(_) => Self::Other,
}
}
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum CharacterAbility {
BasicMelee {
energy_cost: f32,
@ -674,6 +676,16 @@ pub enum CharacterAbility {
#[serde(default)]
meta: AbilityMeta,
},
DiveMelee {
energy_cost: f32,
vertical_speed: f32,
movement_duration: f32,
swing_duration: f32,
recover_duration: f32,
melee_constructor: MeleeConstructor,
#[serde(default)]
meta: AbilityMeta,
},
}
impl Default for CharacterAbility {
@ -769,6 +781,14 @@ impl CharacterAbility {
data.combo.map_or(false, |c| c.counter() >= *minimum_combo)
&& update.energy.try_change_by(-*energy_cost).is_ok()
},
CharacterAbility::DiveMelee {
energy_cost,
vertical_speed,
..
} => {
data.vel.0.z < -*vertical_speed
&& update.energy.try_change_by(-*energy_cost).is_ok()
},
CharacterAbility::ComboMelee { .. }
| CharacterAbility::Boost { .. }
| CharacterAbility::BasicBeam { .. }
@ -1206,6 +1226,20 @@ impl CharacterAbility {
*energy_cost /= stats.energy_efficiency;
*melee_constructor = melee_constructor.adjusted_by_stats(stats);
},
DiveMelee {
ref mut energy_cost,
vertical_speed: _,
movement_duration: _,
ref mut swing_duration,
ref mut recover_duration,
ref mut melee_constructor,
meta: _,
} => {
*swing_duration /= stats.speed;
*recover_duration /= stats.speed;
*energy_cost /= stats.energy_efficiency;
*melee_constructor = melee_constructor.adjusted_by_stats(stats);
},
}
self
}
@ -1230,7 +1264,8 @@ impl CharacterAbility {
| ComboMelee2 {
energy_cost_per_strike: energy_cost,
..
} => *energy_cost,
}
| DiveMelee { energy_cost, .. } => *energy_cost,
BasicBeam { energy_drain, .. } => {
if *energy_drain > f32::EPSILON {
1.0
@ -1272,7 +1307,8 @@ impl CharacterAbility {
| BasicSummon { meta, .. }
| SpriteSummon { meta, .. }
| FinisherMelee { meta, .. }
| Music { meta, .. } => *meta,
| Music { meta, .. }
| DiveMelee { meta, .. } => *meta,
}
}
@ -2385,11 +2421,32 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
stage_section: StageSection::Buildup,
exhausted: false,
}),
CharacterAbility::DiveMelee {
movement_duration,
swing_duration,
recover_duration,
melee_constructor,
energy_cost: _,
vertical_speed: _,
meta: _,
} => CharacterState::DiveMelee(dive_melee::Data {
static_data: dive_melee::StaticData {
movement_duration: Duration::from_secs_f32(*movement_duration),
swing_duration: Duration::from_secs_f32(*swing_duration),
recover_duration: Duration::from_secs_f32(*recover_duration),
melee_constructor: *melee_constructor,
ability_info,
},
timer: Duration::default(),
stage_section: StageSection::Movement,
exhausted: false,
}),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct AbilityMeta {
pub kind: Option<AbilityKind>,
#[serde(default)]

View File

@ -131,6 +131,9 @@ pub enum CharacterState {
Music(music::Data),
/// Melee attack that scales off and consumes combo
FinisherMelee(finisher_melee::Data),
/// State entered when diving, melee attack triggered upon landing on the
/// ground
DiveMelee(dive_melee::Data),
}
impl CharacterState {
@ -166,6 +169,7 @@ impl CharacterState {
..
})
| CharacterState::FinisherMelee(_)
| CharacterState::DiveMelee(_)
)
}
@ -206,6 +210,7 @@ impl CharacterState {
| CharacterState::BasicSummon(_)
| CharacterState::SpriteSummon(_)
| CharacterState::FinisherMelee(_)
| CharacterState::DiveMelee(_)
)
}
@ -229,6 +234,7 @@ impl CharacterState {
| CharacterState::Wielding(_)
| CharacterState::Talk
| CharacterState::FinisherMelee(_)
| CharacterState::DiveMelee(_)
)
}
@ -360,6 +366,7 @@ impl CharacterState {
CharacterState::Skate(data) => data.behavior(j, output_events),
CharacterState::Music(data) => data.behavior(j, output_events),
CharacterState::FinisherMelee(data) => data.behavior(j, output_events),
CharacterState::DiveMelee(data) => data.behavior(j, output_events),
}
}
@ -410,6 +417,7 @@ impl CharacterState {
CharacterState::Skate(data) => data.handle_event(j, output_events, action),
CharacterState::Music(data) => data.handle_event(j, output_events, action),
CharacterState::FinisherMelee(data) => data.handle_event(j, output_events, action),
CharacterState::DiveMelee(data) => data.handle_event(j, output_events, action),
}
}
@ -459,7 +467,7 @@ impl CharacterState {
CharacterState::SpriteInteract(_) => None,
CharacterState::FinisherMelee(data) => Some(data.static_data.ability_info),
CharacterState::Music(data) => Some(data.static_data.ability_info),
CharacterState::Skate(_) => None,
CharacterState::DiveMelee(data) => Some(data.static_data.ability_info),
}
}
@ -501,7 +509,7 @@ impl CharacterState {
CharacterState::SpriteInteract(data) => Some(data.stage_section),
CharacterState::FinisherMelee(data) => Some(data.stage_section),
CharacterState::Music(data) => Some(data.stage_section),
CharacterState::Skate(_) => None,
CharacterState::DiveMelee(data) => Some(data.stage_section),
}
}
}

View File

@ -40,6 +40,7 @@ impl Component for Melee {
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MeleeConstructor {
pub kind: MeleeConstructorKind,
// This multiplied by a fraction is added to what is specified in kind

View File

@ -226,6 +226,9 @@ pub enum ServerEvent {
entity: EcsEntity,
update: comp::MapMarkerChange,
},
ResetMelee {
entity: EcsEntity,
},
}
pub struct EventBus<E> {

View File

@ -0,0 +1,121 @@
use crate::{
comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *},
wielding,
},
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
/// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
/// How long the state is moving
pub movement_duration: Duration,
/// How long the weapon swings
pub swing_duration: Duration,
/// How long the state has until exiting
pub recover_duration: Duration,
/// Used to construct the Melee attack
pub melee_constructor: MeleeConstructor,
/// What key is used to press ability
pub ability_info: AbilityInfo,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
/// Struct containing data that does not change over the course of the
/// character state
pub static_data: StaticData,
/// Timer for each stage
pub timer: Duration,
/// What section the character stage is in
pub stage_section: StageSection,
/// Whether the attack can deal more damage
pub exhausted: bool,
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
match self.stage_section {
StageSection::Movement => {
if data.physics.on_ground.is_some() {
// Transitions to swing portion of state upon hitting ground
if let CharacterState::DiveMelee(c) = &mut update.character {
c.timer = Duration::default();
c.stage_section = StageSection::Action;
}
} else if self.timer < self.static_data.movement_duration {
if let CharacterState::DiveMelee(c) = &mut update.character {
c.timer = self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default();
}
} else {
// In character state for too long, leaving in case somethign went wrong
update.character =
CharacterState::Wielding(wielding::Data { is_sneaking: false });
}
},
StageSection::Action => {
if !self.exhausted {
// Attack
let crit_data = get_crit_data(data, self.static_data.ability_info);
let buff_strength = get_buff_strength(data, self.static_data.ability_info);
data.updater.insert(
data.entity,
self.static_data
.melee_constructor
.create_melee(crit_data, buff_strength),
);
if let CharacterState::DiveMelee(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None);
c.exhausted = true;
}
} else if self.timer < self.static_data.swing_duration {
if let CharacterState::DiveMelee(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None);
}
} else {
// Transitions to recover portion
if let CharacterState::DiveMelee(c) = &mut update.character {
c.timer = Duration::default();
c.stage_section = StageSection::Recover;
}
}
},
StageSection::Recover => {
handle_orientation(data, &mut update, 0.5, None);
handle_move(data, &mut update, 0.3);
if self.timer < self.static_data.recover_duration {
// Complete recovery delay before finishing state
if let CharacterState::DiveMelee(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None);
}
} else {
// Done
update.character =
CharacterState::Wielding(wielding::Data { is_sneaking: false });
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding(wielding::Data { is_sneaking: false });
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
},
}
update
}
}

View File

@ -14,6 +14,7 @@ pub mod combo_melee;
pub mod combo_melee2;
pub mod dance;
pub mod dash_melee;
pub mod dive_melee;
pub mod equipping;
pub mod finisher_melee;
pub mod glide;

View File

@ -197,7 +197,8 @@ impl<'a> System<'a> for Sys {
| CharacterState::BasicSummon(_)
| CharacterState::SelfBuff(_)
| CharacterState::SpriteSummon(_)
| CharacterState::FinisherMelee(_) => {
| CharacterState::FinisherMelee(_)
| CharacterState::DiveMelee(_) => {
if energy.regen_rate != 0.0 {
energy.regen_rate = 0.0
}

View File

@ -1359,3 +1359,12 @@ pub fn handle_update_map_marker(
}
}
}
pub fn handle_reset_melee(server: &Server, entity: EcsEntity) {
let ecs = &server.state.ecs();
if let Some(mut melee) = ecs.write_storage::<comp::Melee>().get_mut(entity) {
melee.applied = false;
melee.hit_count = 0;
}
}

View File

@ -13,7 +13,7 @@ use entity_manipulation::{
handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change,
handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook,
handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground, handle_parry,
handle_poise, handle_respawn, handle_teleport_to, handle_update_map_marker,
handle_poise, handle_reset_melee, handle_respawn, handle_teleport_to, handle_update_map_marker,
};
use group_manip::handle_group;
use information::handle_site_info;
@ -287,6 +287,7 @@ impl Server {
ServerEvent::UpdateMapMarker { entity, update } => {
handle_update_map_marker(self, entity, update)
},
ServerEvent::ResetMelee { entity } => handle_reset_melee(self, entity),
}
}

View File

@ -240,6 +240,7 @@ fn empty_ability_info() -> states::utils::AbilityInfo {
hand: None,
input: InputKind::Primary,
input_attr: None,
ability_meta: None,
ability_meta: Default::default(),
ability: None,
}
}

View File

@ -293,6 +293,7 @@ fn empty_ability_info() -> states::utils::AbilityInfo {
hand: None,
input: InputKind::Primary,
input_attr: None,
ability_meta: None,
ability_meta: Default::default(),
ability: None,
}
}