bevy_tnua/builtins/
dash.rs1use crate::math::{AdjustPrecision, AsF32, Float, Vector3};
2use bevy::prelude::*;
3
4use crate::util::MotionHelper;
5use crate::{
6 prelude::*, TnuaActionContext, TnuaActionInitiationDirective, TnuaActionLifecycleDirective,
7 TnuaActionLifecycleStatus, TnuaMotor,
8};
9
10#[derive(Clone, Debug)]
12pub struct TnuaBuiltinDash {
13 pub displacement: Vector3,
19
20 pub desired_forward: Option<Dir3>,
26
27 pub allow_in_air: bool,
29
30 pub speed: Float,
32
33 pub brake_to_speed: Float,
35
36 pub acceleration: Float,
38
39 pub brake_acceleration: Float,
43
44 pub input_buffer_time: Float,
48}
49
50impl Default for TnuaBuiltinDash {
51 fn default() -> Self {
52 Self {
53 displacement: Vector3::ZERO,
54 desired_forward: None,
55 allow_in_air: false,
56 speed: 80.0,
57 brake_to_speed: 20.0,
58 acceleration: 400.0,
59 brake_acceleration: 200.0,
60 input_buffer_time: 0.2,
61 }
62 }
63}
64
65impl TnuaAction for TnuaBuiltinDash {
66 const NAME: &'static str = "TnuaBuiltinStraightDash";
67 type State = TnuaBuiltinDashState;
68 const VIOLATES_COYOTE_TIME: bool = true;
69
70 fn initiation_decision(
71 &self,
72 ctx: crate::TnuaActionContext,
73 being_fed_for: &bevy::time::Stopwatch,
74 ) -> crate::TnuaActionInitiationDirective {
75 if !self.displacement.is_finite() || self.displacement == Vector3::ZERO {
76 TnuaActionInitiationDirective::Reject
77 } else if self.allow_in_air || !ctx.basis.is_airborne() {
78 TnuaActionInitiationDirective::Allow
80 } else if (being_fed_for.elapsed().as_secs_f64() as Float) < self.input_buffer_time {
81 TnuaActionInitiationDirective::Delay
82 } else {
83 TnuaActionInitiationDirective::Reject
84 }
85 }
86
87 fn apply(
88 &self,
89 state: &mut Self::State,
90 ctx: TnuaActionContext,
91 _lifecycle_status: TnuaActionLifecycleStatus,
92 motor: &mut TnuaMotor,
93 ) -> TnuaActionLifecycleDirective {
94 for _ in 0..3 {
96 return match state {
97 TnuaBuiltinDashState::PreDash => {
98 let Ok(direction) = Dir3::new(self.displacement.f32()) else {
99 return TnuaActionLifecycleDirective::Finished;
101 };
102 *state = TnuaBuiltinDashState::During {
103 direction,
104 destination: ctx.tracker.translation + self.displacement,
105 desired_forward: self.desired_forward,
106 consider_blocked_if_speed_is_less_than: Float::NEG_INFINITY,
107 };
108 continue;
109 }
110 TnuaBuiltinDashState::During {
111 direction,
112 destination,
113 desired_forward,
114 consider_blocked_if_speed_is_less_than,
115 } => {
116 let distance_to_destination = direction
117 .adjust_precision()
118 .dot(*destination - ctx.tracker.translation);
119 if distance_to_destination < 0.0 {
120 *state = TnuaBuiltinDashState::Braking {
121 direction: *direction,
122 };
123 continue;
124 }
125
126 let current_speed = direction.adjust_precision().dot(ctx.tracker.velocity);
127 if current_speed < *consider_blocked_if_speed_is_less_than {
128 return TnuaActionLifecycleDirective::Finished;
129 }
130
131 motor.lin = Default::default();
132 motor.lin.acceleration = -ctx.tracker.gravity;
133 motor.lin.boost = (direction.adjust_precision() * self.speed
134 - ctx.tracker.velocity)
135 .clamp_length_max(ctx.frame_duration * self.acceleration);
136 let expected_speed = direction
137 .adjust_precision()
138 .dot(ctx.tracker.velocity + motor.lin.boost);
139 *consider_blocked_if_speed_is_less_than = if current_speed < expected_speed {
140 0.5 * (current_speed + expected_speed)
141 } else {
142 0.5 * current_speed
143 };
144
145 if let Some(desired_forward) = desired_forward {
146 motor
147 .ang
148 .cancel_on_axis(ctx.up_direction.adjust_precision());
149 motor.ang += ctx.turn_to_direction(*desired_forward, ctx.up_direction);
150 }
151
152 TnuaActionLifecycleDirective::StillActive
153 }
154 TnuaBuiltinDashState::Braking { direction } => {
155 let remaining_speed = direction.adjust_precision().dot(ctx.tracker.velocity);
156 if remaining_speed <= self.brake_to_speed {
157 TnuaActionLifecycleDirective::Finished
158 } else {
159 motor.lin.boost = -direction.adjust_precision()
160 * (remaining_speed - self.brake_to_speed).min(self.brake_acceleration);
161 TnuaActionLifecycleDirective::StillActive
162 }
163 }
164 };
165 }
166 error!("Tnua could not decide on dash state");
167 TnuaActionLifecycleDirective::Finished
168 }
169}
170
171#[derive(Clone, Debug, Default)]
172pub enum TnuaBuiltinDashState {
173 #[default]
174 PreDash,
175 During {
176 direction: Dir3,
177 destination: Vector3,
178 desired_forward: Option<Dir3>,
179 consider_blocked_if_speed_is_less_than: Float,
180 },
181 Braking {
182 direction: Dir3,
183 },
184}