veloren_common/states/
climb.rs

1use crate::{
2    comp::{
3        CharacterState, 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
18/// Separated out to condense update portions of character state
19#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub struct StaticData {
21    pub energy_cost: f32,
22    pub movement_speed: f32,
23}
24
25#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
26pub struct Data {
27    /// Struct containing data that does not change over the course of the
28    /// character state
29    pub static_data: StaticData,
30}
31
32impl Data {
33    pub fn create_adjusted_by_skills(join_data: &JoinData) -> Self {
34        let modifiers = SKILL_MODIFIERS.general_tree.climb;
35        let mut data = Data::default();
36        if let Ok(level) = join_data.skill_set.skill_level(Skill::Climb(Cost)) {
37            data.static_data.energy_cost *= modifiers.energy_cost.powi(level.into());
38        }
39        if let Ok(level) = join_data.skill_set.skill_level(Skill::Climb(Speed)) {
40            data.static_data.movement_speed *= modifiers.speed.powi(level.into());
41        }
42        data
43    }
44}
45
46impl Default for Data {
47    fn default() -> Self {
48        Data {
49            static_data: StaticData {
50                energy_cost: 15.0,
51                movement_speed: 5.0,
52            },
53        }
54    }
55}
56
57impl CharacterBehavior for Data {
58    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
59        let mut update = StateUpdate::from(data);
60
61        let Some(wall_dir) = data.physics.on_wall else {
62            update.character = CharacterState::Idle(idle::Data::default());
63            return update;
64        };
65
66        // Exit climb if ground below
67        if data.physics.on_ground.is_some() {
68            update.character = CharacterState::Idle(idle::Data::default());
69            return update;
70        }
71
72        // Positive if walking into wall, negative if away
73        let wall_relative_movement =
74            data.inputs.move_dir * data.inputs.look_dir.map(|e| e.signum()) * wall_dir;
75
76        // If we move relative to the wall use up energy else default of 1.0
77        // (maybe something lower like 0.5)
78        let energy_use = if wall_relative_movement.reduce_partial_max() > 0.5 {
79            self.static_data.energy_cost
80        } else {
81            1.0
82        };
83        handle_walljump(data, output_events, &mut update);
84
85        // Update energy and exit climbing state if not enough
86        if update
87            .energy
88            .try_change_by(-energy_use * data.dt.0)
89            .is_err()
90        {
91            update.character = CharacterState::Idle(idle::Data::default());
92        }
93
94        // Set orientation based on wall direction
95        if let Some(ori_dir) = Dir::from_unnormalized(wall_dir.with_z(0.0)) {
96            // Smooth orientation
97            update.ori = update.ori.slerped_towards(
98                Ori::from(ori_dir),
99                if data.physics.on_ground.is_some() {
100                    9.0
101                } else {
102                    2.0
103                } * data.dt.0,
104            );
105        };
106
107        // By default idle on wall
108        update.vel.0.z += data.dt.0 * GRAVITY;
109
110        // Map movement direction onto wall
111        let upwards_vel = data.inputs.move_dir.dot(wall_dir.xy());
112        let crossed = wall_dir.cross(Vec3::unit_z());
113        let lateral_vel = crossed * data.inputs.move_dir.dot(crossed.xy());
114
115        update.vel.0 += data.dt.0
116            * (lateral_vel.with_z(upwards_vel) + wall_dir)
117            * self.static_data.movement_speed.powi(2)
118            * data.scale.map_or(1.0, |s| s.0);
119
120        update
121    }
122}