1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use crate::{
    comp::{
        character_state::OutputEvents,
        skills::{ClimbSkill::*, Skill, SKILL_MODIFIERS},
        CharacterState, Climb, InputKind, Ori, StateUpdate,
    },
    consts::GRAVITY,
    event::LocalEvent,
    states::{
        behavior::{CharacterBehavior, JoinData},
        idle,
        utils::*,
    },
    util::Dir,
};
use serde::{Deserialize, Serialize};
use vek::*;

/// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
    pub energy_cost: f32,
    pub movement_speed: f32,
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
    /// Struct containing data that does not change over the course of the
    /// character state
    pub static_data: StaticData,
}

impl Data {
    pub fn create_adjusted_by_skills(join_data: &JoinData) -> Self {
        let modifiers = SKILL_MODIFIERS.general_tree.climb;
        let mut data = Data::default();
        if let Ok(level) = join_data.skill_set.skill_level(Skill::Climb(Cost)) {
            data.static_data.energy_cost *= modifiers.energy_cost.powi(level.into());
        }
        if let Ok(level) = join_data.skill_set.skill_level(Skill::Climb(Speed)) {
            data.static_data.movement_speed *= modifiers.speed.powi(level.into());
        }
        data
    }
}

impl Default for Data {
    fn default() -> Self {
        Data {
            static_data: StaticData {
                energy_cost: 15.0,
                movement_speed: 5.0,
            },
        }
    }
}

impl CharacterBehavior for Data {
    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
        let mut update = StateUpdate::from(data);

        // If no wall is in front of character or we stopped climbing;
        let (wall_dir, climb) = if let (Some(wall_dir), Some(climb), None) = (
            data.physics.on_wall,
            data.inputs.climb,
            data.physics.on_ground,
        ) {
            (wall_dir, climb)
        } else {
            if let Some(impulse) = input_is_pressed(data, InputKind::Jump)
                .then(|| data.body.jump_impulse())
                .flatten()
            {
                // How strong the climb boost is relative to a normal jump
                const CLIMB_BOOST_JUMP_FACTOR: f32 = 0.5;
                // They've climbed atop something, give them a boost
                output_events.emit_local(LocalEvent::Jump(
                    data.entity,
                    CLIMB_BOOST_JUMP_FACTOR * impulse / data.mass.0
                        * data.scale.map_or(1.0, |s| s.0.powf(13.0).powf(0.25)),
                ));
            };
            update.character = CharacterState::Idle(idle::Data::default());
            return update;
        };
        // Move player
        update.vel.0 += Vec2::broadcast(data.dt.0)
            * data.inputs.move_dir
            * if update.vel.0.magnitude_squared() < self.static_data.movement_speed.powi(2) {
                self.static_data.movement_speed.powi(2)
            } else {
                0.0
            };

        // Expend energy if climbing
        let energy_use = match climb {
            Climb::Up => self.static_data.energy_cost,
            Climb::Down => 1.0,
            Climb::Hold => 1.0,
        };

        if update
            .energy
            .try_change_by(-energy_use * data.dt.0)
            .is_err()
        {
            update.character = CharacterState::Idle(idle::Data::default());
        }

        // Set orientation direction based on wall direction
        if let Some(ori_dir) = Dir::from_unnormalized(Vec2::from(wall_dir).into()) {
            // Smooth orientation
            update.ori = update.ori.slerped_towards(
                Ori::from(ori_dir),
                if data.physics.on_ground.is_some() {
                    9.0
                } else {
                    2.0
                } * data.dt.0,
            );
        };

        // Apply Vertical Climbing Movement
        match climb {
            Climb::Down => {
                update.vel.0.z += data.dt.0
                    * (GRAVITY
                        - self.static_data.movement_speed.powi(2) * data.scale.map_or(1.0, |s| s.0))
            },
            Climb::Up => {
                update.vel.0.z += data.dt.0
                    * (GRAVITY
                        + self.static_data.movement_speed.powi(2) * data.scale.map_or(1.0, |s| s.0))
            },
            Climb::Hold => update.vel.0.z += data.dt.0 * GRAVITY,
        }

        update
    }
}