use crate::{
TnuaAction, TnuaActionContext, TnuaActionInitiationDirective, TnuaActionLifecycleDirective,
TnuaActionLifecycleStatus, TnuaBasis, TnuaMotor, TnuaVelChange,
basis_capabilities::TnuaBasisWithGround,
math::{AdjustPrecision, Float, Vector3},
util::{MotionHelper, VelocityBoundary},
};
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct TnuaBuiltinKnockback {
pub shove: Vector3,
pub force_forward: Option<Dir3>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct TnuaBuiltinKnockbackConfig {
pub no_push_timeout: f32,
pub barrier_strength_diminishing: Float,
pub acceleration_limit: Float,
pub air_acceleration_limit: Float,
}
impl Default for TnuaBuiltinKnockbackConfig {
fn default() -> Self {
Self {
no_push_timeout: 0.2,
barrier_strength_diminishing: 2.0,
acceleration_limit: 3.0,
air_acceleration_limit: 1.0,
}
}
}
impl<B: TnuaBasis> TnuaAction<B> for TnuaBuiltinKnockback
where
B: TnuaBasisWithGround,
{
type Config = TnuaBuiltinKnockbackConfig;
type Memory = TnuaBuiltinKnockbackMemory;
fn initiation_decision(
&self,
_config: &Self::Config,
_sensors: &B::Sensors<'_>,
_ctx: crate::TnuaActionContext<B>,
_being_fed_for: &bevy::time::Stopwatch,
) -> TnuaActionInitiationDirective {
TnuaActionInitiationDirective::Allow
}
fn apply(
&self,
config: &Self::Config,
memory: &mut Self::Memory,
_sensors: &B::Sensors<'_>,
ctx: TnuaActionContext<B>,
_lifecycle_status: TnuaActionLifecycleStatus,
motor: &mut TnuaMotor,
) -> TnuaActionLifecycleDirective {
match memory {
TnuaBuiltinKnockbackMemory::Shove => {
let Some(boundary) = VelocityBoundary::new(
ctx.tracker.velocity,
ctx.tracker.velocity + self.shove,
config.no_push_timeout,
) else {
return TnuaActionLifecycleDirective::Finished;
};
motor.lin += TnuaVelChange::boost(self.shove);
*memory = TnuaBuiltinKnockbackMemory::Pushback { boundary };
}
TnuaBuiltinKnockbackMemory::Pushback { boundary } => {
boundary.update(ctx.tracker.velocity, ctx.frame_duration_as_duration());
if boundary.is_cleared() {
return TnuaActionLifecycleDirective::Finished;
} else if let Some((component_direction, component_limit)) = boundary
.calc_boost_part_on_boundary_axis_after_limit(
ctx.tracker.velocity,
motor.lin.calc_boost(ctx.frame_duration),
ctx.frame_duration * config.acceleration_limit,
config.barrier_strength_diminishing,
)
{
motor.lin.apply_boost_limit(
ctx.frame_duration,
component_direction,
component_limit,
);
}
}
}
if let Some(force_forward) = self.force_forward {
motor
.ang
.cancel_on_axis(ctx.up_direction.adjust_precision());
motor.ang += ctx.turn_to_direction(force_forward, ctx.up_direction);
}
TnuaActionLifecycleDirective::StillActive
}
fn influence_basis(
&self,
_config: &Self::Config,
_memory: &Self::Memory,
_ctx: crate::TnuaBasisContext,
_basis_input: &B,
_basis_config: &<B as TnuaBasis>::Config,
basis_memory: &mut <B as TnuaBasis>::Memory,
) {
B::violate_coyote_time(basis_memory);
}
}
#[derive(Default, Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub enum TnuaBuiltinKnockbackMemory {
#[default]
Shove,
Pushback { boundary: VelocityBoundary },
}