Skip to main content

veloren_common/states/
glide_wield.rs

1use super::utils::*;
2use crate::{
3    comp::{
4        CharacterState, InventoryAction, Ori, StateUpdate, character_state::OutputEvents,
5        controller::InputKind, slot::EquipSlot,
6    },
7    event::LocalEvent,
8    outcome::Outcome,
9    states::{
10        behavior::{CharacterBehavior, JoinData},
11        glide, idle,
12    },
13};
14use serde::{Deserialize, Serialize};
15
16#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
17pub struct Data {
18    pub ori: Ori,
19    span_length: f32,
20    chord_length: f32,
21}
22
23impl From<&JoinData<'_>> for Data {
24    fn from(data: &JoinData) -> Self {
25        let scale = {
26            let mut scale = data.body.dimensions().z;
27            if let crate::comp::Body::Humanoid(humanoid) = data.body {
28                scale /= humanoid.height_scale()
29            };
30            scale.sqrt()
31        };
32
33        Self {
34            // Aspect ratio is what really matters for lift/drag ratio
35            // and the aerodynamics model works for ARs up to 25.
36            //
37            // The inflated dimensions are hopefully only a temporary
38            // bandaid for the poor glide ratio experienced under 2.5G.
39            //
40            // The formula is:
41            //  s: span_length_modifier
42            //  c: chord_length_modifier
43            //  h: height (this is a hack to balance different races)
44            //
45            // p_a = Pi/4 * c * h * s * h
46            // AR
47            //  = (s * h)^2 / p_a
48            //  = (s * h)^2  / (Pi / 4 * (c * h) * (s * h))
49            //  = (s * h) / (c * h) / (Pi / 4)
50            //  = s / c / Pi/4
51            //
52            // or if c is 1,
53            //  = s / Pi/4
54            //
55            // In other words, the bigger `span_length` the better.
56            //
57            // A span/chord ratio of 4.5 gives an AR of ~5.73.
58            // A span/chord ratio of 3.0 gives an ARI of ~3.82.
59            span_length: scale * 3.0,
60            chord_length: scale * 0.8,
61            ori: *data.ori,
62        }
63    }
64}
65
66impl CharacterBehavior for Data {
67    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
68        let mut update = StateUpdate::from(data);
69
70        handle_orientation(data, &mut update, 1.0, None);
71        handle_move(data, &mut update, 1.0);
72        handle_jump(data, output_events, &mut update, 1.0);
73        if input_is_pressed(data, InputKind::Roll) {
74            handle_input(data, output_events, &mut update, InputKind::Roll);
75        }
76        handle_glider_input_or(data, &mut update, output_events, handle_wield);
77
78        // If still in this state, do the things
79        if matches!(update.character, CharacterState::GlideWield(_)) {
80            // If not on the ground while wielding glider enter gliding state
81            update.character = if data.physics.on_ground.is_none() {
82                CharacterState::Glide(glide::Data::new(
83                    self.span_length,
84                    self.chord_length,
85                    self.ori,
86                ))
87            // make sure we have a glider and we're not (too deep) in water
88            } else if data
89                .inventory
90                .and_then(|inv| inv.equipped(EquipSlot::Glider))
91                .is_some()
92                && data.physics.in_liquid().is_none_or(|depth| depth < 0.5)
93            {
94                CharacterState::GlideWield(Self {
95                    // Glider tilt follows look dir
96                    ori: self.ori.slerped_towards(
97                        data.ori.slerped_towards(
98                            Ori::from(data.inputs.look_dir).pitched_up(0.6),
99                            (1.0 + data.inputs.look_dir.dot(*data.ori.look_dir()).max(0.0)) / 3.0,
100                        ),
101                        5.0 * data.dt.0,
102                    ),
103                    ..*self
104                })
105            } else {
106                CharacterState::Idle(idle::Data::default())
107            };
108        }
109
110        update
111    }
112
113    fn manipulate_loadout(
114        &self,
115        data: &JoinData,
116        output_events: &mut OutputEvents,
117        inv_action: InventoryAction,
118    ) -> StateUpdate {
119        let mut update = StateUpdate::from(data);
120        handle_manipulate_loadout(data, output_events, &mut update, inv_action);
121        update
122    }
123
124    fn unwield(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
125        let mut update = StateUpdate::from(data);
126        output_events.emit_local(LocalEvent::CreateOutcome(Outcome::Glider {
127            pos: data.pos.0,
128            wielded: false,
129        }));
130        update.character = CharacterState::Idle(idle::Data::default());
131        update
132    }
133
134    fn sit(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
135        let mut update = StateUpdate::from(data);
136        attempt_sit(data, &mut update);
137        update
138    }
139
140    fn crawl(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
141        let mut update = StateUpdate::from(data);
142        attempt_crawl(data, &mut update);
143        update
144    }
145
146    fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
147        let mut update = StateUpdate::from(data);
148        attempt_dance(data, &mut update);
149        update
150    }
151
152    fn sneak(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
153        let mut update = StateUpdate::from(data);
154        attempt_sneak(data, &mut update);
155        update
156    }
157}