bevy_tnua/builtins/
dash.rs1use crate::basis_capabilities::TnuaBasisWithGround;
2use crate::math::{AdjustPrecision, AsF32, Float, Vector3};
3use bevy::prelude::*;
4use serde::{Deserialize, Serialize};
5
6use crate::util::MotionHelper;
7use crate::{
8 TnuaAction, TnuaActionContext, TnuaActionInitiationDirective, TnuaActionLifecycleDirective,
9 TnuaActionLifecycleStatus, TnuaBasis, TnuaMotor,
10};
11
12#[derive(Clone, Debug, Default)]
14#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
15pub struct TnuaBuiltinDash {
16 pub displacement: Vector3,
26
27 pub desired_forward: Option<Dir3>,
33
34 pub allow_in_air: bool,
36}
37
38#[derive(Clone, Serialize, Deserialize)]
39pub struct TnuaBuiltinDashConfig {
40 pub speed: Float,
42
43 pub horizontal_distance: Float,
46
47 pub vertical_distance: Float,
50
51 pub brake_to_speed: Float,
53
54 pub acceleration: Float,
56
57 pub brake_acceleration: Float,
61
62 pub input_buffer_time: Float,
66}
67
68impl Default for TnuaBuiltinDashConfig {
69 fn default() -> Self {
70 Self {
71 speed: 80.0,
72 horizontal_distance: 1.0,
73 vertical_distance: 1.0,
74 brake_to_speed: 20.0,
75 acceleration: 400.0,
76 brake_acceleration: 200.0,
77 input_buffer_time: 0.2,
78 }
79 }
80}
81
82impl<B: TnuaBasis> TnuaAction<B> for TnuaBuiltinDash
83where
84 B: TnuaBasisWithGround,
85{
86 type Config = TnuaBuiltinDashConfig;
87 type Memory = TnuaBuiltinDashMemory;
88
89 fn initiation_decision(
90 &self,
91 config: &Self::Config,
92 _sensors: &B::Sensors<'_>,
93 ctx: crate::TnuaActionContext<B>,
94 being_fed_for: &bevy::time::Stopwatch,
95 ) -> crate::TnuaActionInitiationDirective {
96 if !self.displacement.is_finite() || self.displacement == Vector3::ZERO {
97 TnuaActionInitiationDirective::Reject
98 } else if self.allow_in_air || !B::is_airborne(ctx.basis) {
99 TnuaActionInitiationDirective::Allow
101 } else if (being_fed_for.elapsed().as_secs_f64() as Float) < config.input_buffer_time {
102 TnuaActionInitiationDirective::Delay
103 } else {
104 TnuaActionInitiationDirective::Reject
105 }
106 }
107
108 fn apply(
109 &self,
110 config: &Self::Config,
111 memory: &mut Self::Memory,
112 _sensors: &B::Sensors<'_>,
113 ctx: TnuaActionContext<B>,
114 _lifecycle_status: TnuaActionLifecycleStatus,
115 motor: &mut TnuaMotor,
116 ) -> TnuaActionLifecycleDirective {
117 for _ in 0..3 {
119 return match memory {
120 TnuaBuiltinDashMemory::PreDash => {
121 let horizontal_displacement = self
122 .displacement
123 .reject_from(ctx.up_direction.adjust_precision());
124 let vertical_displacement = self
125 .displacement
126 .project_onto(ctx.up_direction.adjust_precision());
127 let displacement = config.horizontal_distance * horizontal_displacement
128 + config.vertical_distance * vertical_displacement;
129 let Ok(direction) = Dir3::new(displacement.f32()) else {
130 return TnuaActionLifecycleDirective::Finished;
132 };
133 *memory = TnuaBuiltinDashMemory::During {
134 direction,
135 destination: ctx.tracker.translation + displacement,
136 desired_forward: self.desired_forward,
137 consider_blocked_if_speed_is_less_than: Float::NEG_INFINITY,
138 };
139 continue;
140 }
141 TnuaBuiltinDashMemory::During {
142 direction,
143 destination,
144 desired_forward,
145 consider_blocked_if_speed_is_less_than,
146 } => {
147 let distance_to_destination = direction
148 .adjust_precision()
149 .dot(*destination - ctx.tracker.translation);
150 if distance_to_destination < 0.0 {
151 *memory = TnuaBuiltinDashMemory::Braking {
152 direction: *direction,
153 };
154 continue;
155 }
156
157 let current_speed = direction.adjust_precision().dot(ctx.tracker.velocity);
158 if current_speed < *consider_blocked_if_speed_is_less_than {
159 return TnuaActionLifecycleDirective::Finished;
160 }
161
162 motor.lin = Default::default();
163 motor.lin.acceleration = -ctx.tracker.gravity;
164 motor.lin.boost = (direction.adjust_precision() * config.speed
165 - ctx.tracker.velocity)
166 .clamp_length_max(ctx.frame_duration * config.acceleration);
167 let expected_speed = direction
168 .adjust_precision()
169 .dot(ctx.tracker.velocity + motor.lin.boost);
170 *consider_blocked_if_speed_is_less_than = if current_speed < expected_speed {
171 0.5 * (current_speed + expected_speed)
172 } else {
173 0.5 * current_speed
174 };
175
176 if let Some(desired_forward) = desired_forward {
177 motor
178 .ang
179 .cancel_on_axis(ctx.up_direction.adjust_precision());
180 motor.ang += ctx.turn_to_direction(*desired_forward, ctx.up_direction);
181 }
182
183 TnuaActionLifecycleDirective::StillActive
184 }
185 TnuaBuiltinDashMemory::Braking { direction } => {
186 let remaining_speed = direction.adjust_precision().dot(ctx.tracker.velocity);
187 if remaining_speed <= config.brake_to_speed {
188 TnuaActionLifecycleDirective::Finished
189 } else {
190 motor.lin.boost = -direction.adjust_precision()
191 * (remaining_speed - config.brake_to_speed)
192 .min(config.brake_acceleration);
193 TnuaActionLifecycleDirective::StillActive
194 }
195 }
196 };
197 }
198 error!("Tnua could not decide on dash state");
199 TnuaActionLifecycleDirective::Finished
200 }
201
202 fn influence_basis(
203 &self,
204 _config: &Self::Config,
205 _memory: &Self::Memory,
206 _ctx: crate::TnuaBasisContext,
207 _basis_input: &B,
208 _basis_config: &<B as TnuaBasis>::Config,
209 basis_memory: &mut <B as TnuaBasis>::Memory,
210 ) {
211 B::violate_coyote_time(basis_memory);
212 }
213}
214
215#[derive(Clone, Debug, Default)]
216#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
217pub enum TnuaBuiltinDashMemory {
218 #[default]
219 PreDash,
220 During {
221 direction: Dir3,
222 destination: Vector3,
223 desired_forward: Option<Dir3>,
224 consider_blocked_if_speed_is_less_than: Float,
225 },
226 Braking {
227 direction: Dir3,
228 },
229}