bevy_tnua/builtins/
crouch.rs1use crate::math::{AdjustPrecision, Float};
2use bevy::prelude::*;
3
4use crate::control_helpers::TnuaCrouchEnforcedAction;
5use crate::{TnuaAction, TnuaMotor, TnuaVelChange};
6use crate::{
7 TnuaActionContext, TnuaActionInitiationDirective, TnuaActionLifecycleDirective,
8 TnuaActionLifecycleStatus,
9};
10
11use super::TnuaBuiltinWalk;
12
13#[derive(Clone, Debug)]
26pub struct TnuaBuiltinCrouch {
27 pub float_offset: Float,
34
35 pub height_change_impulse_for_duration: Float,
43
44 pub height_change_impulse_limit: Float,
46
47 pub uncancellable: bool,
54}
55
56impl Default for TnuaBuiltinCrouch {
57 fn default() -> Self {
58 Self {
59 float_offset: 0.0,
60 height_change_impulse_for_duration: 0.02,
61 height_change_impulse_limit: 40.0,
62 uncancellable: false,
63 }
64 }
65}
66
67impl TnuaAction for TnuaBuiltinCrouch {
68 const NAME: &'static str = "TnuaBuiltinCrouch";
69 type State = TnuaBuiltinCrouchState;
70 const VIOLATES_COYOTE_TIME: bool = false;
71
72 fn initiation_decision(
73 &self,
74 ctx: TnuaActionContext,
75 _being_fed_for: &bevy::time::Stopwatch,
76 ) -> TnuaActionInitiationDirective {
77 if ctx.proximity_sensor.output.is_some() {
78 TnuaActionInitiationDirective::Allow
79 } else {
80 TnuaActionInitiationDirective::Delay
81 }
82 }
83
84 fn apply(
85 &self,
86 state: &mut Self::State,
87 ctx: TnuaActionContext,
88 lifecycle_status: TnuaActionLifecycleStatus,
89 motor: &mut TnuaMotor,
90 ) -> TnuaActionLifecycleDirective {
91 let Some((walk_basis, walk_state)) = ctx.concrete_basis::<TnuaBuiltinWalk>() else {
92 error!("Cannot crouch - basis is not TnuaBuiltinWalk");
93 return TnuaActionLifecycleDirective::Finished;
94 };
95 let Some(sensor_output) = &ctx.proximity_sensor.output else {
96 return TnuaActionLifecycleDirective::Reschedule { after_seconds: 0.0 };
97 };
98 let spring_offset_up = walk_basis.float_height - sensor_output.proximity.adjust_precision();
99 let spring_offset_down =
100 spring_offset_up.adjust_precision() + self.float_offset.adjust_precision();
101
102 match lifecycle_status {
103 TnuaActionLifecycleStatus::Initiated => {}
104 TnuaActionLifecycleStatus::CancelledFrom => {}
105 TnuaActionLifecycleStatus::StillFed => {}
106 TnuaActionLifecycleStatus::NoLongerFed => {
107 *state = TnuaBuiltinCrouchState::Rising;
108 }
109 TnuaActionLifecycleStatus::CancelledInto => {
110 if !self.uncancellable {
111 *state = TnuaBuiltinCrouchState::Rising;
112 }
113 }
114 }
115
116 let spring_force = |spring_offset: Float| -> TnuaVelChange {
117 walk_basis.spring_force(walk_state, &ctx.as_basis_context(), spring_offset)
118 };
119
120 let impulse_or_spring_force = |spring_offset: Float| -> TnuaVelChange {
121 let spring_force = spring_force(spring_offset);
122 let spring_force_boost = crate::util::calc_boost(&spring_force, ctx.frame_duration);
123 let impulse_boost = self.impulse_boost(spring_offset);
124 if spring_force_boost.length_squared() < impulse_boost.powi(2) {
125 TnuaVelChange::boost(impulse_boost * ctx.up_direction.adjust_precision())
126 } else {
127 spring_force
128 }
129 };
130
131 let mut set_vel_change = |vel_change: TnuaVelChange| {
132 motor
133 .lin
134 .cancel_on_axis(ctx.up_direction.adjust_precision());
135 motor.lin += vel_change;
136 };
137
138 match state {
139 TnuaBuiltinCrouchState::Sinking => {
140 if spring_offset_down < -0.01 {
141 set_vel_change(impulse_or_spring_force(spring_offset_down));
142 } else {
143 *state = TnuaBuiltinCrouchState::Maintaining;
144 set_vel_change(spring_force(spring_offset_down));
145 }
146 lifecycle_status.directive_simple()
147 }
148 TnuaBuiltinCrouchState::Maintaining => {
149 set_vel_change(spring_force(spring_offset_down));
150 TnuaActionLifecycleDirective::StillActive
152 }
153 TnuaBuiltinCrouchState::Rising => {
154 if 0.01 < spring_offset_up {
155 set_vel_change(impulse_or_spring_force(spring_offset_up));
156
157 if matches!(lifecycle_status, TnuaActionLifecycleStatus::CancelledInto) {
158 TnuaActionLifecycleDirective::Reschedule { after_seconds: 0.0 }
160 } else {
161 TnuaActionLifecycleDirective::StillActive
163 }
164 } else {
165 TnuaActionLifecycleDirective::Finished
166 }
167 }
168 }
169 }
170}
171
172impl TnuaBuiltinCrouch {
173 fn impulse_boost(&self, spring_offset: Float) -> Float {
174 let velocity_to_get_to_new_float_height =
175 spring_offset / self.height_change_impulse_for_duration;
176 velocity_to_get_to_new_float_height.clamp(
177 -self.height_change_impulse_limit,
178 self.height_change_impulse_limit,
179 )
180 }
181}
182
183#[derive(Default, Debug, Clone)]
184pub enum TnuaBuiltinCrouchState {
185 #[default]
187 Sinking,
188 Maintaining,
190 Rising,
192}
193
194impl TnuaCrouchEnforcedAction for TnuaBuiltinCrouch {
195 fn range_to_cast_up(&self, _state: &Self::State) -> Float {
196 -self.float_offset
197 }
198
199 fn prevent_cancellation(&mut self) {
200 self.uncancellable = true;
201 }
202}