veloren_common/states/
climb.rs

1use crate::{
2    comp::{
3        CharacterState, InputKind, Ori, StateUpdate,
4        character_state::OutputEvents,
5        skills::{ClimbSkill::*, SKILL_MODIFIERS, Skill},
6    },
7    consts::GRAVITY,
8    states::{
9        behavior::{CharacterBehavior, JoinData},
10        idle,
11        utils::*,
12    },
13    util::Dir,
14};
15use serde::{Deserialize, Serialize};
16use vek::*;
17
18use super::wielding;
19
20/// Separated out to condense update portions of character state
21#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
22pub struct StaticData {
23    pub energy_cost: f32,
24    pub movement_speed: f32,
25}
26
27#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
28pub struct Data {
29    /// Struct containing data that does not change over the course of the
30    /// character state
31    pub static_data: StaticData,
32    pub was_wielded: bool,
33}
34
35impl Data {
36    pub fn create_adjusted_by_skills(join_data: &JoinData) -> Self {
37        let modifiers = SKILL_MODIFIERS.general_tree.climb;
38        let mut data = Data::default();
39        if let Ok(level) = join_data.skill_set.skill_level(Skill::Climb(Cost)) {
40            data.static_data.energy_cost *= modifiers.energy_cost.powi(level.into());
41        }
42        if let Ok(level) = join_data.skill_set.skill_level(Skill::Climb(Speed)) {
43            data.static_data.movement_speed *= modifiers.speed.powi(level.into());
44        }
45        data
46    }
47
48    pub fn with_wielded(self, was_wielded: bool) -> Self {
49        Self {
50            was_wielded,
51            ..self
52        }
53    }
54
55    fn update_state_on_leaving(&self, update: &mut StateUpdate) {
56        if self.was_wielded {
57            update.character = CharacterState::Wielding(wielding::Data { is_sneaking: false });
58        } else {
59            update.character = CharacterState::Idle(idle::Data::default());
60        }
61    }
62}
63
64impl Default for Data {
65    fn default() -> Self {
66        Data {
67            static_data: StaticData {
68                energy_cost: 15.0,
69                movement_speed: 5.0,
70            },
71            was_wielded: false,
72        }
73    }
74}
75
76impl CharacterBehavior for Data {
77    fn behavior(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
78        let mut update = StateUpdate::from(data);
79
80        let Some(wall_dir) = data.physics.on_wall else {
81            self.update_state_on_leaving(&mut update);
82            return update;
83        };
84
85        // Exit climb if ground below
86        if data.physics.on_ground.is_some() {
87            self.update_state_on_leaving(&mut update);
88            return update;
89        }
90
91        // Set orientation based on wall direction
92        if let Some(ori_dir) = Dir::from_unnormalized(wall_dir.with_z(0.0)) {
93            // Smooth orientation
94            update.ori = update.ori.slerped_towards(
95                Ori::from(ori_dir),
96                if data.physics.on_ground.is_some() {
97                    9.0
98                } else {
99                    2.0
100                } * data.dt.0,
101            );
102        };
103        // Map movement direction onto wall
104        let upwards_vel = data.inputs.move_dir.dot(wall_dir.xy());
105        let crossed = wall_dir.cross(Vec3::unit_z());
106        let lateral_vel = crossed * data.inputs.move_dir.dot(crossed.xy());
107
108        let energy_use = lateral_vel
109            .with_z(upwards_vel.max(0.0) * self.static_data.energy_cost)
110            .magnitude()
111            .max(1.0);
112
113        // Update energy and exit climbing state if not enough
114        if update
115            .energy
116            .try_change_by(-energy_use * data.dt.0)
117            .is_err()
118        {
119            self.update_state_on_leaving(&mut update);
120        }
121
122        // By default idle on wall
123        update.vel.0.z += data.dt.0 * GRAVITY;
124
125        update.vel.0 += data.dt.0
126            * (lateral_vel.with_z(upwards_vel) + wall_dir)
127            * self.static_data.movement_speed.powi(2)
128            * data.scale.map_or(1.0, |s| s.0);
129
130        update
131    }
132
133    fn stand(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
134        let mut update = StateUpdate::from(data);
135        self.update_state_on_leaving(&mut update);
136        update
137    }
138
139    fn on_input(
140        &self,
141        data: &JoinData,
142        input: InputKind,
143        output_events: &mut OutputEvents,
144    ) -> StateUpdate {
145        let mut update = StateUpdate::from(data);
146        if matches!(input, InputKind::Jump) {
147            handle_walljump(data, output_events, &mut update, self.was_wielded);
148        }
149        update
150    }
151}