Made combo melee more ergonomic to use when it is a stance

This commit is contained in:
Sam 2022-09-11 21:49:31 -04:00
parent 21aabb5663
commit 8d3567b6b2
23 changed files with 257 additions and 119 deletions

View File

@ -1379,37 +1379,40 @@ impl CharacterAbility {
#[must_use] #[must_use]
pub fn contextualize(mut self, data: &JoinData) -> Self { pub fn contextualize(mut self, data: &JoinData) -> Self {
if let Some(ability_info) = data.character.ability_info() { if let Some(AbilityKind::Sword(old_stance)) = data
if let Some(AbilityKind::Sword(old_stance)) = ability_info.ability_meta.kind { .character
if let Some(AbilityKind::Sword(new_stance)) = self.ability_meta().kind { .ability_info()
let energy_reduction = if old_stance == new_stance { .and_then(|info| info.ability_meta)
0.75 .and_then(|meta| meta.kind)
} else if old_stance == SwordStance::Balanced { {
1.0 if let Some(AbilityKind::Sword(new_stance)) = self.ability_meta().kind {
} else { let energy_reduction = if old_stance == new_stance {
1.5 0.75
}; } else if old_stance == SwordStance::Balanced {
use CharacterAbility::*; 1.0
match &mut self { } else {
BasicMelee { energy_cost, .. } 1.5
| ComboMelee2 { };
energy_cost_per_strike: energy_cost, use CharacterAbility::*;
.. match &mut self {
} BasicMelee { energy_cost, .. }
| FinisherMelee { energy_cost, .. } | ComboMelee2 {
| DashMelee { energy_cost, .. } energy_cost_per_strike: energy_cost,
| SpinMelee { energy_cost, .. } ..
| ChargedMelee { energy_cost, .. }
| Shockwave { energy_cost, .. }
| BasicBlock { energy_cost, .. }
| SelfBuff { energy_cost, .. }
| DiveMelee { energy_cost, .. }
| RiposteMelee { energy_cost, .. }
| RapidMelee { energy_cost, .. } => {
*energy_cost *= energy_reduction;
},
_ => {},
} }
| FinisherMelee { energy_cost, .. }
| DashMelee { energy_cost, .. }
| SpinMelee { energy_cost, .. }
| ChargedMelee { energy_cost, .. }
| Shockwave { energy_cost, .. }
| BasicBlock { energy_cost, .. }
| SelfBuff { energy_cost, .. }
| DiveMelee { energy_cost, .. }
| RiposteMelee { energy_cost, .. }
| RapidMelee { energy_cost, .. } => {
*energy_cost *= energy_reduction;
},
_ => {},
} }
} }
} }

View File

@ -281,13 +281,16 @@ impl CharacterState {
} }
pub fn is_parry(&self) -> bool { pub fn is_parry(&self) -> bool {
let from_capability = let from_capability = if let Some(capabilities) = self
if let Some(capabilities) = self.ability_info().map(|a| a.ability_meta.capabilities) { .ability_info()
capabilities.contains(Capability::BUILDUP_PARRIES) .and_then(|a| a.ability_meta)
&& matches!(self.stage_section(), Some(StageSection::Buildup)) .map(|m| m.capabilities)
} else { {
false capabilities.contains(Capability::BUILDUP_PARRIES)
}; && matches!(self.stage_section(), Some(StageSection::Buildup))
} else {
false
};
let from_state = match self { let from_state = match self {
CharacterState::BasicBlock(c) => c.is_parry(), CharacterState::BasicBlock(c) => c.is_parry(),
CharacterState::RiposteMelee(c) => matches!(c.stage_section, StageSection::Buildup), CharacterState::RiposteMelee(c) => matches!(c.stage_section, StageSection::Buildup),
@ -503,7 +506,7 @@ impl CharacterState {
CharacterState::Skate(_) => None, CharacterState::Skate(_) => None,
CharacterState::Glide(_) => None, CharacterState::Glide(_) => None,
CharacterState::GlideWield(_) => None, CharacterState::GlideWield(_) => None,
CharacterState::Stunned(_) => None, CharacterState::Stunned(data) => Some(data.static_data.ability_info),
CharacterState::Sit => None, CharacterState::Sit => None,
CharacterState::Dance => None, CharacterState::Dance => None,
CharacterState::BasicBlock(data) => Some(data.static_data.ability_info), CharacterState::BasicBlock(data) => Some(data.static_data.ability_info),
@ -528,8 +531,8 @@ impl CharacterState {
CharacterState::BasicSummon(data) => Some(data.static_data.ability_info), CharacterState::BasicSummon(data) => Some(data.static_data.ability_info),
CharacterState::SelfBuff(data) => Some(data.static_data.ability_info), CharacterState::SelfBuff(data) => Some(data.static_data.ability_info),
CharacterState::SpriteSummon(data) => Some(data.static_data.ability_info), CharacterState::SpriteSummon(data) => Some(data.static_data.ability_info),
CharacterState::UseItem(_) => None, CharacterState::UseItem(data) => Some(data.static_data.ability_info),
CharacterState::SpriteInteract(_) => None, CharacterState::SpriteInteract(data) => Some(data.static_data.ability_info),
CharacterState::FinisherMelee(data) => Some(data.static_data.ability_info), CharacterState::FinisherMelee(data) => Some(data.static_data.ability_info),
CharacterState::Music(data) => Some(data.static_data.ability_info), CharacterState::Music(data) => Some(data.static_data.ability_info),
CharacterState::DiveMelee(data) => Some(data.static_data.ability_info), CharacterState::DiveMelee(data) => Some(data.static_data.ability_info),

View File

@ -78,7 +78,11 @@ pub enum PoiseState {
impl PoiseState { impl PoiseState {
/// Returns the optional stunned character state and duration of stun, and /// Returns the optional stunned character state and duration of stun, and
/// optional impulse strength corresponding to a particular poise state /// optional impulse strength corresponding to a particular poise state
pub fn poise_effect(&self, was_wielded: bool) -> (Option<(CharacterState, f64)>, Option<f32>) { pub fn poise_effect(
&self,
was_wielded: bool,
ability_info: states::utils::AbilityInfo,
) -> (Option<(CharacterState, f64)>, Option<f32>) {
use states::{ use states::{
stunned::{Data, StaticData}, stunned::{Data, StaticData},
utils::StageSection, utils::StageSection,
@ -113,6 +117,7 @@ impl PoiseState {
recover_duration, recover_duration,
movement_speed, movement_speed,
poise_state: *self, poise_state: *self,
ability_info,
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,
@ -262,10 +267,9 @@ impl Poise {
let from_char = { let from_char = {
let resistant = char_state let resistant = char_state
.and_then(|cs| cs.ability_info()) .and_then(|cs| cs.ability_info())
.and_then(|a| a.ability_meta)
.map_or(false, |a| { .map_or(false, |a| {
a.ability_meta a.capabilities.contains(Capability::POISE_RESISTANT)
.capabilities
.contains(Capability::POISE_RESISTANT)
}); });
if resistant { 0.5 } else { 0.0 } if resistant { 0.5 } else { 0.0 }
}; };

View File

@ -96,7 +96,7 @@ impl CharacterBehavior for Data {
} }
}, },
StageSection::Action => { StageSection::Action => {
if input_is_pressed(data, self.static_data.ability_info.input) if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input))
&& (self.static_data.energy_drain <= f32::EPSILON && (self.static_data.energy_drain <= f32::EPSILON
|| update.energy.current() > 0.0) || update.energy.current() > 0.0)
{ {

View File

@ -76,7 +76,7 @@ impl CharacterBehavior for Data {
}, },
StageSection::Action => { StageSection::Action => {
if self.static_data.can_hold if self.static_data.can_hold
&& input_is_pressed(data, self.static_data.ability_info.input) && self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input))
{ {
// Block // Block
update.character = CharacterState::BasicBlock(Data { update.character = CharacterState::BasicBlock(Data {

View File

@ -118,7 +118,7 @@ impl CharacterBehavior for Data {
}); });
} else { } else {
// Done // Done
if input_is_pressed(data, self.static_data.ability_info.input) { if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
reset_state(self, data, output_events, &mut update); reset_state(self, data, output_events, &mut update);
} else { } else {
end_ability(data, &mut update); end_ability(data, &mut update);
@ -146,10 +146,12 @@ fn reset_state(
output_events: &mut OutputEvents, output_events: &mut OutputEvents,
update: &mut StateUpdate, update: &mut StateUpdate,
) { ) {
handle_input( if let Some(input) = data.static_data.ability_info.input {
join, handle_input(
output_events, join,
update, output_events,
data.static_data.ability_info.input, update,
); input,
);
}
} }

View File

@ -123,7 +123,7 @@ impl CharacterBehavior for Data {
}); });
} else { } else {
// Done // Done
if input_is_pressed(data, self.static_data.ability_info.input) { if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
reset_state(self, data, output_events, &mut update); reset_state(self, data, output_events, &mut update);
} else { } else {
end_ability(data, &mut update); end_ability(data, &mut update);
@ -149,10 +149,12 @@ fn reset_state(
output_events: &mut OutputEvents, output_events: &mut OutputEvents,
update: &mut StateUpdate, update: &mut StateUpdate,
) { ) {
handle_input( if let Some(input) = data.static_data.ability_info.input {
join, handle_input(
output_events, join,
update, output_events,
data.static_data.ability_info.input, update,
); input,
);
}
} }

View File

@ -46,7 +46,7 @@ impl CharacterBehavior for Data {
}); });
} else { } else {
// Done // Done
if input_is_pressed(data, self.static_data.ability_info.input) { if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
reset_state(self, data, output_events, &mut update); reset_state(self, data, output_events, &mut update);
} else { } else {
update.vel.0 = update.vel.0.try_normalized().unwrap_or_default() update.vel.0 = update.vel.0.try_normalized().unwrap_or_default()
@ -69,10 +69,12 @@ fn reset_state(
output_events: &mut OutputEvents, output_events: &mut OutputEvents,
update: &mut StateUpdate, update: &mut StateUpdate,
) { ) {
handle_input( if let Some(input) = data.static_data.ability_info.input {
join, handle_input(
output_events, join,
update, output_events,
data.static_data.ability_info.input, update,
); input,
);
}
} }

View File

@ -58,7 +58,7 @@ impl CharacterBehavior for Data {
match self.stage_section { match self.stage_section {
StageSection::Charge => { StageSection::Charge => {
if input_is_pressed(data, self.static_data.ability_info.input) if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input))
&& update.energy.current() >= self.static_data.energy_cost && update.energy.current() >= self.static_data.energy_cost
&& self.timer < self.static_data.charge_duration && self.timer < self.static_data.charge_duration
{ {
@ -77,7 +77,7 @@ impl CharacterBehavior for Data {
update update
.energy .energy
.change_by(-self.static_data.energy_drain * data.dt.0); .change_by(-self.static_data.energy_drain * data.dt.0);
} else if input_is_pressed(data, self.static_data.ability_info.input) } else if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input))
&& update.energy.current() >= self.static_data.energy_cost && update.energy.current() >= self.static_data.energy_cost
{ {
// Maintains charge // Maintains charge

View File

@ -96,7 +96,7 @@ impl CharacterBehavior for Data {
} }
}, },
StageSection::Charge => { StageSection::Charge => {
if !input_is_pressed(data, self.static_data.ability_info.input) && !self.exhausted { if !self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) && !self.exhausted {
let charge_frac = self.charge_frac(); let charge_frac = self.charge_frac();
let arrow = ProjectileConstructor::Arrow { let arrow = ProjectileConstructor::Arrow {
damage: self.static_data.initial_damage as f32 damage: self.static_data.initial_damage as f32
@ -138,7 +138,7 @@ impl CharacterBehavior for Data {
..*self ..*self
}); });
} else if self.timer < self.static_data.charge_duration } else if self.timer < self.static_data.charge_duration
&& input_is_pressed(data, self.static_data.ability_info.input) && self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input))
{ {
// Charges // Charges
update.character = CharacterState::ChargedRanged(Data { update.character = CharacterState::ChargedRanged(Data {
@ -150,7 +150,7 @@ impl CharacterBehavior for Data {
update update
.energy .energy
.change_by(-self.static_data.energy_drain * data.dt.0); .change_by(-self.static_data.energy_drain * data.dt.0);
} else if input_is_pressed(data, self.static_data.ability_info.input) { } else if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
// Holds charge // Holds charge
update.character = CharacterState::ChargedRanged(Data { update.character = CharacterState::ChargedRanged(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(data, self.timer, None),

View File

@ -341,7 +341,7 @@ impl CharacterBehavior for Data {
}); });
} else { } else {
// Done // Done
if input_is_pressed(data, self.static_data.ability_info.input) { if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
reset_state(self, data, output_events, &mut update); reset_state(self, data, output_events, &mut update);
} else { } else {
end_ability(data, &mut update); end_ability(data, &mut update);
@ -382,14 +382,16 @@ fn reset_state(
output_events: &mut OutputEvents, output_events: &mut OutputEvents,
update: &mut StateUpdate, update: &mut StateUpdate,
) { ) {
handle_input( if let Some(input) = data.static_data.ability_info.input {
join, handle_input(
output_events, join,
update, output_events,
data.static_data.ability_info.input, update,
); input,
);
if let CharacterState::ComboMelee(c) = &mut update.character { if let CharacterState::ComboMelee(c) = &mut update.character {
c.stage = (data.stage % data.static_data.num_stages) + 1; c.stage = (data.stage % data.static_data.num_stages) + 1;
}
} }
} }

View File

@ -1,11 +1,11 @@
use crate::{ use crate::{
comp::{ comp::{
character_state::OutputEvents, tool::Stats, CharacterState, InputKind, Melee, character_state::OutputEvents, tool::Stats, CharacterState, InputKind, Melee,
MeleeConstructor, StateUpdate, InputAttr MeleeConstructor, StateUpdate, InputAttr, InventoryAction, slot::{Slot, EquipSlot},
}, },
states::{ states::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*, idle, wielding,
}, },
uid::Uid, uid::Uid,
}; };
@ -116,7 +116,7 @@ impl CharacterBehavior for Data {
let ability_input = if self.static_data.is_stance { let ability_input = if self.static_data.is_stance {
InputKind::Primary InputKind::Primary
} else { } else {
self.static_data.ability_info.input self.static_data.ability_info.input.unwrap_or(InputKind::Primary)
}; };
handle_orientation(data, &mut update, 1.0, None); handle_orientation(data, &mut update, 1.0, None);
@ -237,7 +237,7 @@ impl CharacterBehavior for Data {
if input_is_pressed(data, ability_input) { if input_is_pressed(data, ability_input) {
next_strike(&mut update) next_strike(&mut update)
} else if !input_is_pressed(data, self.static_data.ability_info.input) { } else if !self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
attempt_input(data, output_events, &mut update); attempt_input(data, output_events, &mut update);
} }
}, },
@ -255,7 +255,7 @@ impl CharacterBehavior for Data {
) -> StateUpdate { ) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
if matches!(data.character, CharacterState::ComboMelee2(data) if data.static_data.ability_info.input == input && input != InputKind::Primary && data.stage_section.is_none()) { if matches!(data.character, CharacterState::ComboMelee2(data) if data.static_data.ability_info.input == Some(input) && input != InputKind::Primary && data.stage_section.is_none()) {
end_ability(data, &mut update); end_ability(data, &mut update);
} else { } else {
update.queued_inputs.insert(input, InputAttr { update.queued_inputs.insert(input, InputAttr {
@ -265,6 +265,97 @@ impl CharacterBehavior for Data {
} }
update update
} }
fn swap_equipped_weapons(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
if let CharacterState::ComboMelee2(c) = data.character {
if c.stage_section.is_none() {
update.character =
CharacterState::Wielding(wielding::Data { is_sneaking: data.character.is_stealthy() });
attempt_swap_equipped_weapons(data, &mut update);
}
}
update
}
fn unwield(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
if let CharacterState::ComboMelee2(c) = data.character {
if c.stage_section.is_none() {
update.character = CharacterState::Idle(idle::Data {
is_sneaking: data.character.is_stealthy(),
footwear: None,
});
}
}
update
}
fn manipulate_loadout(
&self,
data: &JoinData,
output_events: &mut OutputEvents,
inv_action: InventoryAction,
) -> StateUpdate {
let mut update = StateUpdate::from(data);
if let CharacterState::ComboMelee2(c) = data.character {
if c.stage_section.is_none() {
match inv_action {
InventoryAction::Drop(slot)
| InventoryAction::Swap(slot, _)
| InventoryAction::Swap(_, Slot::Equip(slot)) if matches!(slot, EquipSlot::ActiveMainhand | EquipSlot::ActiveOffhand) => {
update.character = CharacterState::Idle(idle::Data {
is_sneaking: data.character.is_stealthy(),
footwear: None,
});
},
_ => (),
}
handle_manipulate_loadout(data, output_events, &mut update, inv_action);
}
}
update
}
fn glide_wield(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
if let CharacterState::ComboMelee2(c) = data.character {
if c.stage_section.is_none() {
attempt_glide_wield(data, &mut update, output_events);
}
}
update
}
fn sit(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
if let CharacterState::ComboMelee2(c) = data.character {
if c.stage_section.is_none() {
attempt_sit(data, &mut update);
}
}
update
}
fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
if let CharacterState::ComboMelee2(c) = data.character {
if c.stage_section.is_none() {
attempt_dance(data, &mut update);
}
}
update
}
fn sneak(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
if let CharacterState::ComboMelee2(c) = data.character {
if c.stage_section.is_none() && data.physics.on_ground.is_some() && data.body.is_humanoid() {
update.character = CharacterState::Wielding(wielding::Data { is_sneaking: true });
}
}
update
}
} }
impl Data { impl Data {

View File

@ -81,7 +81,7 @@ impl CharacterBehavior for Data {
} else { } else {
// Transitions to charge section of stage // Transitions to charge section of stage
update.character = CharacterState::DashMelee(Data { update.character = CharacterState::DashMelee(Data {
auto_charge: !input_is_pressed(data, self.static_data.ability_info.input), auto_charge: !self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)),
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Charge, stage_section: StageSection::Charge,
..*self ..*self
@ -90,7 +90,7 @@ impl CharacterBehavior for Data {
}, },
StageSection::Charge => { StageSection::Charge => {
if self.timer < self.charge_end_timer if self.timer < self.charge_end_timer
&& (input_is_pressed(data, self.static_data.ability_info.input) && (self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input))
|| (self.auto_charge && self.timer < self.static_data.charge_duration)) || (self.auto_charge && self.timer < self.static_data.charge_duration))
&& update.energy.current() > 0.0 && update.energy.current() > 0.0
{ {

View File

@ -56,7 +56,7 @@ impl CharacterBehavior for Data {
}); });
} else { } else {
// Done // Done
if input_is_pressed(data, self.static_data.ability_info.input) { if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
reset_state(self, data, output_events, &mut update); reset_state(self, data, output_events, &mut update);
} else { } else {
end_ability(data, &mut update); end_ability(data, &mut update);
@ -72,7 +72,7 @@ impl CharacterBehavior for Data {
} }
// At end of state logic so an interrupt isn't overwritten // At end of state logic so an interrupt isn't overwritten
if !input_is_pressed(data, self.static_data.ability_info.input) { if !self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
handle_dodge_input(data, &mut update); handle_dodge_input(data, &mut update);
} }
@ -86,10 +86,12 @@ fn reset_state(
output_events: &mut OutputEvents, output_events: &mut OutputEvents,
update: &mut StateUpdate, update: &mut StateUpdate,
) { ) {
handle_input( if let Some(input) = data.static_data.ability_info.input {
join, handle_input(
output_events, join,
update, output_events,
data.static_data.ability_info.input, update,
); input,
);
}
} }

View File

@ -84,7 +84,7 @@ impl CharacterBehavior for Data {
.unwrap_or_default(), .unwrap_or_default(),
..*self ..*self
}); });
} else if input_is_pressed(data, self.static_data.ability_info.input) } else if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input))
&& update.energy.current() >= self.static_data.energy_cost && update.energy.current() >= self.static_data.energy_cost
{ {
// Fire if input is pressed still // Fire if input is pressed still

View File

@ -121,7 +121,7 @@ impl CharacterBehavior for Data {
} else if update.energy.current() as f32 >= self.static_data.energy_cost } else if update.energy.current() as f32 >= self.static_data.energy_cost
&& (self.consecutive_spins < self.static_data.num_spins && (self.consecutive_spins < self.static_data.num_spins
|| (self.static_data.is_infinite || (self.static_data.is_infinite
&& input_is_pressed(data, self.static_data.ability_info.input))) && self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input))))
{ {
update.character = CharacterState::SpinMelee(Data { update.character = CharacterState::SpinMelee(Data {
timer: Duration::default(), timer: Duration::default(),

View File

@ -29,6 +29,8 @@ pub struct StaticData {
pub was_wielded: bool, pub was_wielded: bool,
/// Was sneaking /// Was sneaking
pub was_sneak: bool, pub was_sneak: bool,
/// Miscellaneous information about the ability
pub ability_info: AbilityInfo,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -19,6 +19,8 @@ pub struct StaticData {
pub movement_speed: f32, pub movement_speed: f32,
/// Poise state (used for determining animation in the client) /// Poise state (used for determining animation in the client)
pub poise_state: PoiseState, pub poise_state: PoiseState,
/// Miscellaneous information about the ability
pub ability_info: AbilityInfo,
} }
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]

View File

@ -36,6 +36,8 @@ pub struct StaticData {
pub was_wielded: bool, pub was_wielded: bool,
/// Was sneaking /// Was sneaking
pub was_sneak: bool, pub was_sneak: bool,
/// Miscellaneous information about the ability
pub ability_info: AbilityInfo,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -806,8 +806,9 @@ pub fn handle_manipulate_loadout(
inv_slot, inv_slot,
item_kind, item_kind,
item_hash: item.item_hash(), item_hash: item.item_hash(),
was_wielded: matches!(data.character, CharacterState::Wielding(_)), was_wielded: data.character.is_wield(),
was_sneak: data.character.is_stealthy(), was_sneak: data.character.is_stealthy(),
ability_info: AbilityInfo::from_forced_state_change(data.character),
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,
@ -910,8 +911,9 @@ pub fn handle_manipulate_loadout(
recover_duration, recover_duration,
sprite_pos, sprite_pos,
sprite_kind: sprite_interact, sprite_kind: sprite_interact,
was_wielded: matches!(data.character, CharacterState::Wielding(_)), was_wielded: data.character.is_wield(),
was_sneak: data.character.is_stealthy(), was_sneak: data.character.is_stealthy(),
ability_info: AbilityInfo::from_forced_state_change(data.character),
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,
@ -1060,7 +1062,7 @@ pub fn handle_dodge_input(data: &JoinData<'_>, update: &mut StateUpdate) {
)); ));
if let CharacterState::Roll(roll) = &mut update.character { if let CharacterState::Roll(roll) = &mut update.character {
if let CharacterState::ComboMelee(c) = data.character { if let CharacterState::ComboMelee(c) = data.character {
roll.was_combo = Some((c.static_data.ability_info.input, c.stage)); roll.was_combo = c.static_data.ability_info.input.map(|input| (input, c.stage));
roll.was_wielded = true; roll.was_wielded = true;
} else { } else {
if data.character.is_wield() { if data.character.is_wield() {
@ -1085,7 +1087,7 @@ pub fn handle_interrupts(
// Check that the input used to enter current character state (if there was one) // Check that the input used to enter current character state (if there was one)
// is not pressed // is not pressed
if input_override if input_override
.or_else(|| data.character.ability_info().map(|a| a.input)) .or_else(|| data.character.ability_info().and_then(|a| a.input))
.map_or(true, |input| !input_is_pressed(data, input)) .map_or(true, |input| !input_is_pressed(data, input))
{ {
let can_dodge = { let can_dodge = {
@ -1095,16 +1097,14 @@ pub fn handle_interrupts(
.map_or(true, |stage_section| { .map_or(true, |stage_section| {
matches!(stage_section, StageSection::Buildup) matches!(stage_section, StageSection::Buildup)
}); });
let interruptible = data.character.ability_info().map_or(false, |info| { let interruptible = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| {
info.ability_meta meta.capabilities
.capabilities
.contains(Capability::ROLL_INTERRUPT) .contains(Capability::ROLL_INTERRUPT)
}); });
in_buildup || interruptible in_buildup || interruptible
}; };
let can_block = data.character.ability_info().map_or(false, |info| { let can_block = data.character.ability_info().and_then(|info| info.ability_meta).map_or(false, |meta| {
info.ability_meta meta.capabilities
.capabilities
.contains(Capability::BLOCK_INTERRUPT) .contains(Capability::BLOCK_INTERRUPT)
}); });
if can_dodge { if can_dodge {
@ -1281,12 +1281,13 @@ impl MovementDirection {
} }
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AbilityInfo { pub struct AbilityInfo {
pub tool: Option<ToolKind>, pub tool: Option<ToolKind>,
pub hand: Option<HandInfo>, pub hand: Option<HandInfo>,
pub input: InputKind, pub input: Option<InputKind>,
pub input_attr: Option<InputAttr>, pub input_attr: Option<InputAttr>,
pub ability_meta: AbilityMeta, pub ability_meta: Option<AbilityMeta>,
pub ability: Option<Ability>, pub ability: Option<Ability>,
pub return_ability: Option<InputKind>, pub return_ability: Option<InputKind>,
} }
@ -1314,7 +1315,7 @@ impl AbilityInfo {
.map(|(i, a)| a.get_ability(i, data.inventory, Some(data.skill_set))); .map(|(i, a)| a.get_ability(i, data.inventory, Some(data.skill_set)));
let return_ability = if data.character.should_be_returned_to() { let return_ability = if data.character.should_be_returned_to() {
data.character.ability_info().map(|info| info.input) data.character.ability_info().and_then(|info| info.input)
} else { } else {
None None
}; };
@ -1322,13 +1323,31 @@ impl AbilityInfo {
Self { Self {
tool, tool,
hand, hand,
input, input: Some(input),
input_attr: data.controller.queued_inputs.get(&input).copied(), input_attr: data.controller.queued_inputs.get(&input).copied(),
ability_meta, ability_meta: Some(ability_meta),
ability, ability,
return_ability, return_ability,
} }
} }
pub fn from_forced_state_change(char_state: &CharacterState) -> Self {
let return_ability = if char_state.should_be_returned_to() {
char_state.ability_info().and_then(|info| info.input)
} else {
None
};
Self {
tool: None,
hand: None,
input: None,
input_attr: None,
ability_meta: None,
ability: None,
return_ability,
}
}
} }
pub fn end_ability(data: &JoinData<'_>, update: &mut StateUpdate) { pub fn end_ability(data: &JoinData<'_>, update: &mut StateUpdate) {

View File

@ -17,7 +17,7 @@ use common::{
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
states::{ states::{
behavior::{JoinData, JoinStruct}, behavior::{JoinData, JoinStruct},
idle, idle, utils,
}, },
terrain::TerrainGrid, terrain::TerrainGrid,
uid::Uid, uid::Uid,
@ -148,10 +148,11 @@ impl<'a> System<'a> for Sys {
// Enter stunned state if poise damage is enough // Enter stunned state if poise damage is enough
if let Some(mut poise) = poises.get_mut(entity) { if let Some(mut poise) = poises.get_mut(entity) {
let was_wielded = char_state.is_wield(); let was_wielded = char_state.is_wield();
let ability_info = utils::AbilityInfo::from_forced_state_change(&char_state);
let poise_state = poise.poise_state(); let poise_state = poise.poise_state();
let pos = pos.0; let pos = pos.0;
if let (Some((stunned_state, stunned_duration)), impulse_strength) = if let (Some((stunned_state, stunned_duration)), impulse_strength) =
poise_state.poise_effect(was_wielded) poise_state.poise_effect(was_wielded, ability_info)
{ {
// Reset poise if there is some stunned state to apply // Reset poise if there is some stunned state to apply
poise.reset(*read_data.time, stunned_duration); poise.reset(*read_data.time, stunned_duration);

View File

@ -26,7 +26,7 @@ use common::{
outcome::{HealthChangeInfo, Outcome}, outcome::{HealthChangeInfo, Outcome},
resources::Time, resources::Time,
rtsim::RtSimEntity, rtsim::RtSimEntity,
states::utils::StageSection, states::utils::{AbilityInfo, StageSection},
terrain::{Block, BlockKind, TerrainGrid}, terrain::{Block, BlockKind, TerrainGrid},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
util::Dir, util::Dir,
@ -1324,8 +1324,9 @@ pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
) { ) {
let poise_state = comp::poise::PoiseState::Interrupted; let poise_state = comp::poise::PoiseState::Interrupted;
let was_wielded = char_state.is_wield(); let was_wielded = char_state.is_wield();
let ability_info = AbilityInfo::from_forced_state_change(&char_state);
if let (Some((stunned_state, stunned_duration)), impulse_strength) = if let (Some((stunned_state, stunned_duration)), impulse_strength) =
poise_state.poise_effect(was_wielded) poise_state.poise_effect(was_wielded, ability_info)
{ {
// Reset poise if there is some stunned state to apply // Reset poise if there is some stunned state to apply
poise.reset(*time, stunned_duration); poise.reset(*time, stunned_duration);

View File

@ -250,7 +250,7 @@ impl<'a> Widget for BuffsBar<'a> {
{ {
match buff.kind { match buff.kind {
BuffIconKind::Buff { kind, .. } => event.push(Event::RemoveBuff(kind)), BuffIconKind::Buff { kind, .. } => event.push(Event::RemoveBuff(kind)),
BuffIconKind::Ability { .. } => todo!(), BuffIconKind::Ability { .. } => {},
} }
}; };
}); });
@ -413,7 +413,7 @@ impl<'a> Widget for BuffsBar<'a> {
{ {
match buff.kind { match buff.kind {
BuffIconKind::Buff { kind, .. } => event.push(Event::RemoveBuff(kind)), BuffIconKind::Buff { kind, .. } => event.push(Event::RemoveBuff(kind)),
BuffIconKind::Ability { .. } => todo!(), BuffIconKind::Ability { .. } => {},
} }
} }
Text::new(&remaining_time) Text::new(&remaining_time)