veloren_common/states/
climb.rs1use 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#[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 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 if data.physics.on_ground.is_some() {
87 self.update_state_on_leaving(&mut update);
88 return update;
89 }
90
91 if let Some(ori_dir) = Dir::from_unnormalized(wall_dir.with_z(0.0)) {
93 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 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 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 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}