]> git.lizzy.rs Git - minetest.git/blob - src/localplayer.cpp
9fb5e7642996d20798c3bdde351f70e07cfa07d5
[minetest.git] / src / localplayer.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "localplayer.h"
21
22 #include "event.h"
23 #include "collision.h"
24 #include "nodedef.h"
25 #include "settings.h"
26 #include "environment.h"
27 #include "map.h"
28 #include "client.h"
29
30 /*
31         LocalPlayer
32 */
33
34 LocalPlayer::LocalPlayer(Client *client, const char *name):
35         Player(name, client->idef()),
36         parent(0),
37         hp(PLAYER_MAX_HP),
38         got_teleported(false),
39         isAttached(false),
40         touching_ground(false),
41         in_liquid(false),
42         in_liquid_stable(false),
43         liquid_viscosity(0),
44         is_climbing(false),
45         swimming_vertical(false),
46         // Movement overrides are multipliers and must be 1 by default
47         physics_override_speed(1.0f),
48         physics_override_jump(1.0f),
49         physics_override_gravity(1.0f),
50         physics_override_sneak(true),
51         physics_override_sneak_glitch(true),
52         overridePosition(v3f(0,0,0)),
53         last_position(v3f(0,0,0)),
54         last_speed(v3f(0,0,0)),
55         last_pitch(0),
56         last_yaw(0),
57         last_keyPressed(0),
58         last_camera_fov(0),
59         last_wanted_range(0),
60         camera_impact(0.f),
61         last_animation(NO_ANIM),
62         hotbar_image(""),
63         hotbar_selected_image(""),
64         light_color(255,255,255,255),
65         hurt_tilt_timer(0.0f),
66         hurt_tilt_strength(0.0f),
67         m_position(0,0,0),
68         m_sneak_node(32767,32767,32767),
69         m_sneak_node_exists(false),
70         m_need_to_get_new_sneak_node(true),
71         m_sneak_node_bb_top(0,0,0,0,0,0),
72         m_old_node_below(32767,32767,32767),
73         m_old_node_below_type("air"),
74         m_can_jump(false),
75         m_breath(PLAYER_MAX_BREATH),
76         m_yaw(0),
77         m_pitch(0),
78         camera_barely_in_ceiling(false),
79         m_collisionbox(-BS * 0.30, 0.0, -BS * 0.30, BS * 0.30, BS * 1.75, BS * 0.30),
80         m_cao(NULL),
81         m_client(client)
82 {
83         // Initialize hp to 0, so that no hearts will be shown if server
84         // doesn't support health points
85         hp = 0;
86         eye_offset_first = v3f(0,0,0);
87         eye_offset_third = v3f(0,0,0);
88 }
89
90 LocalPlayer::~LocalPlayer()
91 {
92 }
93
94 static aabb3f getTopBoundingBox(const std::vector<aabb3f> &nodeboxes, float max_d=1/16*BS)
95 {
96         aabb3f b_max;
97         b_max.reset(-BS, -BS, -BS);
98         for (std::vector<aabb3f>::const_iterator it = nodeboxes.begin();
99                         it != nodeboxes.end(); ++it) {
100                 aabb3f box = *it;
101                 if (box.MaxEdge.Y > b_max.MaxEdge.Y)
102                         b_max = box;
103                 else if (box.MaxEdge.Y == b_max.MaxEdge.Y)
104                         b_max.addInternalBox(box);
105         }
106         return aabb3f(v3f(b_max.MinEdge.X, b_max.MaxEdge.Y, b_max.MinEdge.Z), b_max.MaxEdge);
107 }
108
109 void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
110                 std::vector<CollisionInfo> *collision_info)
111 {
112         Map *map = &env->getMap();
113         INodeDefManager *nodemgr = m_client->ndef();
114
115         v3f position = getPosition();
116
117         // Copy parent position if local player is attached
118         if(isAttached)
119         {
120                 setPosition(overridePosition);
121                 m_sneak_node_exists = false;
122                 return;
123         }
124
125         // Skip collision detection if noclip mode is used
126         bool fly_allowed = m_client->checkLocalPrivilege("fly");
127         bool noclip = m_client->checkLocalPrivilege("noclip") &&
128                 g_settings->getBool("noclip");
129         bool free_move = noclip && fly_allowed && g_settings->getBool("free_move");
130         if (free_move) {
131                 position += m_speed * dtime;
132                 setPosition(position);
133                 m_sneak_node_exists = false;
134                 return;
135         }
136
137         /*
138                 Collision detection
139         */
140
141         bool is_valid_position;
142         MapNode node;
143         v3s16 pp;
144
145         /*
146                 Check if player is in liquid (the oscillating value)
147         */
148
149         // If in liquid, the threshold of coming out is at higher y
150         if (in_liquid)
151         {
152                 pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
153                 node = map->getNodeNoEx(pp, &is_valid_position);
154                 if (is_valid_position) {
155                         in_liquid = nodemgr->get(node.getContent()).isLiquid();
156                         liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
157                 } else {
158                         in_liquid = false;
159                 }
160         }
161         // If not in liquid, the threshold of going in is at lower y
162         else
163         {
164                 pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
165                 node = map->getNodeNoEx(pp, &is_valid_position);
166                 if (is_valid_position) {
167                         in_liquid = nodemgr->get(node.getContent()).isLiquid();
168                         liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
169                 } else {
170                         in_liquid = false;
171                 }
172         }
173
174
175         /*
176                 Check if player is in liquid (the stable value)
177         */
178         pp = floatToInt(position + v3f(0,0,0), BS);
179         node = map->getNodeNoEx(pp, &is_valid_position);
180         if (is_valid_position) {
181                 in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
182         } else {
183                 in_liquid_stable = false;
184         }
185
186         /*
187                 Check if player is climbing
188         */
189
190
191         pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
192         v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
193         node = map->getNodeNoEx(pp, &is_valid_position);
194         bool is_valid_position2;
195         MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2);
196
197         if (!(is_valid_position && is_valid_position2)) {
198                 is_climbing = false;
199         } else {
200                 is_climbing = (nodemgr->get(node.getContent()).climbable
201                                 || nodemgr->get(node2.getContent()).climbable) && !free_move;
202         }
203
204
205         /*
206                 Collision uncertainty radius
207                 Make it a bit larger than the maximum distance of movement
208         */
209         //f32 d = pos_max_d * 1.1;
210         // A fairly large value in here makes moving smoother
211         f32 d = 0.15*BS;
212
213         // This should always apply, otherwise there are glitches
214         sanity_check(d > pos_max_d);
215
216         // Max. distance (X, Z) over border for sneaking determined by collision box
217         // * 0.49 to keep the center just barely on the node
218         v3f sneak_max = m_collisionbox.getExtent() * 0.49;
219
220         /*
221                 If sneaking, keep in range from the last walked node and don't
222                 fall off from it
223         */
224         if (control.sneak && m_sneak_node_exists &&
225                         !(fly_allowed && g_settings->getBool("free_move")) && !in_liquid &&
226                         physics_override_sneak && !got_teleported) {
227                 v3f sn_f = intToFloat(m_sneak_node, BS);
228                 const v3f bmin = m_sneak_node_bb_top.MinEdge;
229                 const v3f bmax = m_sneak_node_bb_top.MaxEdge;
230
231                 position.X = rangelim(position.X,
232                                 sn_f.X+bmin.X - sneak_max.X, sn_f.X+bmax.X + sneak_max.X);
233                 position.Z = rangelim(position.Z,
234                                 sn_f.Z+bmin.Z - sneak_max.Z, sn_f.Z+bmax.Z + sneak_max.Z);
235                 // Because we keep the player collision box on the node,
236                 // limiting position.Y is not necessary
237         }
238
239         if (got_teleported)
240                 got_teleported = false;
241
242         // TODO: this shouldn't be hardcoded but transmitted from server
243         float player_stepheight = touching_ground ? (BS*0.6) : (BS*0.2);
244
245 #ifdef __ANDROID__
246         player_stepheight += (0.6 * BS);
247 #endif
248
249         v3f accel_f = v3f(0,0,0);
250
251         collisionMoveResult result = collisionMoveSimple(env, m_client,
252                 pos_max_d, m_collisionbox, player_stepheight, dtime,
253                 &position, &m_speed, accel_f);
254
255         /*
256                 If the player's feet touch the topside of any node, this is
257                 set to true.
258
259                 Player is allowed to jump when this is true.
260         */
261         bool touching_ground_was = touching_ground;
262         touching_ground = result.touching_ground;
263
264     //bool standing_on_unloaded = result.standing_on_unloaded;
265
266         // We want the top of the sneak node to be below the players feet
267         f32 position_y_mod;
268         if (m_sneak_node_exists)
269                 position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - 0.05 * BS;
270         else
271                 position_y_mod = (1.0 - 0.05) * BS;
272         v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
273         /*
274                 Check the nodes under the player to see from which node the
275                 player is sneaking from, if any.  If the node from under
276                 the player has been removed, the player falls.
277         */
278         if (m_sneak_node_exists &&
279                         nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" &&
280                         m_old_node_below_type != "air") {
281                 // Old node appears to have been removed; that is,
282                 // it wasn't air before but now it is
283                 m_need_to_get_new_sneak_node = false;
284                 m_sneak_node_exists = false;
285         } else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") {
286                 // We are on something, so make sure to recalculate the sneak
287                 // node.
288                 m_need_to_get_new_sneak_node = true;
289         }
290
291         if (m_need_to_get_new_sneak_node && physics_override_sneak) {
292                 v3s16 pos_i_bottom = floatToInt(position - v3f(0, position_y_mod, 0), BS);
293                 v2f player_p2df(position.X, position.Z);
294                 f32 min_distance_f = 100000.0 * BS;
295                 v3s16 new_sneak_node = m_sneak_node;
296                 for(s16 x=-1; x<=1; x++)
297                 for(s16 z=-1; z<=1; z++)
298                 {
299                         v3s16 p = pos_i_bottom + v3s16(x,0,z);
300                         v3f pf = intToFloat(p, BS);
301                         v2f node_p2df(pf.X, pf.Z);
302                         f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
303
304                         if (distance_f > min_distance_f ||
305                                         fabs(player_p2df.X-node_p2df.X) > (.5+.1)*BS + sneak_max.X ||
306                                         fabs(player_p2df.Y-node_p2df.Y) > (.5+.1)*BS + sneak_max.Z)
307                                 continue;
308
309
310                         // The node to be sneaked on has to be walkable
311                         node = map->getNodeNoEx(p, &is_valid_position);
312                         if (!is_valid_position || nodemgr->get(node).walkable == false)
313                                 continue;
314                         // And the node above it has to be nonwalkable
315                         node = map->getNodeNoEx(p + v3s16(0,1,0), &is_valid_position);
316                         if (!is_valid_position || nodemgr->get(node).walkable)
317                                 continue;
318                         if (!physics_override_sneak_glitch) {
319                                 node = map->getNodeNoEx(p + v3s16(0,2,0), &is_valid_position);
320                                 if (!is_valid_position || nodemgr->get(node).walkable)
321                                         continue;
322                         }
323
324                         min_distance_f = distance_f;
325                         new_sneak_node = p;
326                 }
327
328                 bool sneak_node_found = (min_distance_f < 100000.0 * BS);
329                 m_sneak_node = new_sneak_node;
330                 m_sneak_node_exists = sneak_node_found;
331
332                 // Update saved top bounding box of sneak node
333                 if (sneak_node_found) {
334                         MapNode n = map->getNodeNoEx(m_sneak_node);
335                         std::vector<aabb3f> nodeboxes;
336                         n.getCollisionBoxes(nodemgr, &nodeboxes);
337                         m_sneak_node_bb_top = getTopBoundingBox(nodeboxes);
338                 }
339         }
340
341         /*
342                 Set new position
343         */
344         setPosition(position);
345
346         /*
347                 Report collisions
348         */
349
350         // Dont report if flying
351         if(collision_info && !(g_settings->getBool("free_move") && fly_allowed)) {
352                 for(size_t i=0; i<result.collisions.size(); i++) {
353                         const CollisionInfo &info = result.collisions[i];
354                         collision_info->push_back(info);
355                 }
356         }
357
358         if(!result.standing_on_object && !touching_ground_was && touching_ground) {
359                 MtEvent *e = new SimpleTriggerEvent("PlayerRegainGround");
360                 m_client->event()->put(e);
361
362                 // Set camera impact value to be used for view bobbing
363                 camera_impact = getSpeed().Y * -1;
364         }
365
366         {
367                 camera_barely_in_ceiling = false;
368                 v3s16 camera_np = floatToInt(getEyePosition(), BS);
369                 MapNode n = map->getNodeNoEx(camera_np);
370                 if(n.getContent() != CONTENT_IGNORE){
371                         if(nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2){
372                                 camera_barely_in_ceiling = true;
373                         }
374                 }
375         }
376
377         /*
378                 Update the node last under the player
379         */
380         m_old_node_below = floatToInt(position - v3f(0,BS/2,0), BS);
381         m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name;
382
383         /*
384                 Check properties of the node on which the player is standing
385         */
386         const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos()));
387         // Determine if jumping is possible
388         m_can_jump = touching_ground && !in_liquid;
389         if(itemgroup_get(f.groups, "disable_jump"))
390                 m_can_jump = false;
391         // Jump key pressed while jumping off from a bouncy block
392         if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
393                 m_speed.Y >= -0.5 * BS) {
394                 float jumpspeed = movement_speed_jump * physics_override_jump;
395                 if (m_speed.Y > 1) {
396                         // Reduce boost when speed already is high
397                         m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 ));
398                 } else {
399                         m_speed.Y += jumpspeed;
400                 }
401                 setSpeed(m_speed);
402                 m_can_jump = false;
403         }
404 }
405
406 void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d)
407 {
408         move(dtime, env, pos_max_d, NULL);
409 }
410
411 void LocalPlayer::applyControl(float dtime)
412 {
413         // Clear stuff
414         swimming_vertical = false;
415
416         setPitch(control.pitch);
417         setYaw(control.yaw);
418
419         // Nullify speed and don't run positioning code if the player is attached
420         if(isAttached)
421         {
422                 setSpeed(v3f(0,0,0));
423                 return;
424         }
425
426         v3f move_direction = v3f(0,0,1);
427         move_direction.rotateXZBy(getYaw());
428
429         v3f speedH = v3f(0,0,0); // Horizontal (X, Z)
430         v3f speedV = v3f(0,0,0); // Vertical (Y)
431
432         bool fly_allowed = m_client->checkLocalPrivilege("fly");
433         bool fast_allowed = m_client->checkLocalPrivilege("fast");
434
435         bool free_move = fly_allowed && g_settings->getBool("free_move");
436         bool fast_move = fast_allowed && g_settings->getBool("fast_move");
437         // When aux1_descends is enabled the fast key is used to go down, so fast isn't possible
438         bool fast_climb = fast_move && control.aux1 && !g_settings->getBool("aux1_descends");
439         bool continuous_forward = g_settings->getBool("continuous_forward");
440         bool always_fly_fast = g_settings->getBool("always_fly_fast");
441
442         // Whether superspeed mode is used or not
443         bool superspeed = false;
444
445         if (always_fly_fast && free_move && fast_move)
446                 superspeed = true;
447
448         // Old descend control
449         if(g_settings->getBool("aux1_descends"))
450         {
451                 // If free movement and fast movement, always move fast
452                 if(free_move && fast_move)
453                         superspeed = true;
454
455                 // Auxiliary button 1 (E)
456                 if(control.aux1)
457                 {
458                         if(free_move)
459                         {
460                                 // In free movement mode, aux1 descends
461                                 if(fast_move)
462                                         speedV.Y = -movement_speed_fast;
463                                 else
464                                         speedV.Y = -movement_speed_walk;
465                         }
466                         else if(in_liquid || in_liquid_stable)
467                         {
468                                 speedV.Y = -movement_speed_walk;
469                                 swimming_vertical = true;
470                         }
471                         else if(is_climbing)
472                         {
473                                 speedV.Y = -movement_speed_climb;
474                         }
475                         else
476                         {
477                                 // If not free movement but fast is allowed, aux1 is
478                                 // "Turbo button"
479                                 if(fast_move)
480                                         superspeed = true;
481                         }
482                 }
483         }
484         // New minecraft-like descend control
485         else
486         {
487                 // Auxiliary button 1 (E)
488                 if(control.aux1)
489                 {
490                         if(!is_climbing)
491                         {
492                                 // aux1 is "Turbo button"
493                                 if(fast_move)
494                                         superspeed = true;
495                         }
496                 }
497
498                 if(control.sneak)
499                 {
500                         if(free_move)
501                         {
502                                 // In free movement mode, sneak descends
503                                 if (fast_move && (control.aux1 || always_fly_fast))
504                                         speedV.Y = -movement_speed_fast;
505                                 else
506                                         speedV.Y = -movement_speed_walk;
507                         }
508                         else if(in_liquid || in_liquid_stable)
509                         {
510                                 if(fast_climb)
511                                         speedV.Y = -movement_speed_fast;
512                                 else
513                                         speedV.Y = -movement_speed_walk;
514                                 swimming_vertical = true;
515                         }
516                         else if(is_climbing)
517                         {
518                                 if(fast_climb)
519                                         speedV.Y = -movement_speed_fast;
520                                 else
521                                         speedV.Y = -movement_speed_climb;
522                         }
523                 }
524         }
525
526         if (continuous_forward)
527                 speedH += move_direction;
528
529         if (control.up) {
530                 if (continuous_forward) {
531                         if (fast_move)
532                                 superspeed = true;
533                 } else {
534                         speedH += move_direction;
535                 }
536         }
537         if (control.down) {
538                 speedH -= move_direction;
539         }
540         if (!control.up && !control.down) {
541                 speedH -= move_direction *
542                         (control.forw_move_joystick_axis / 32767.f);
543         }
544         if (control.left) {
545                 speedH += move_direction.crossProduct(v3f(0,1,0));
546         }
547         if (control.right) {
548                 speedH += move_direction.crossProduct(v3f(0,-1,0));
549         }
550         if (!control.left && !control.right) {
551                 speedH -= move_direction.crossProduct(v3f(0,1,0)) *
552                         (control.sidew_move_joystick_axis / 32767.f);
553         }
554         if(control.jump)
555         {
556                 if (free_move) {
557                         if (g_settings->getBool("aux1_descends") || always_fly_fast) {
558                                 if (fast_move)
559                                         speedV.Y = movement_speed_fast;
560                                 else
561                                         speedV.Y = movement_speed_walk;
562                         } else {
563                                 if(fast_move && control.aux1)
564                                         speedV.Y = movement_speed_fast;
565                                 else
566                                         speedV.Y = movement_speed_walk;
567                         }
568                 }
569                 else if(m_can_jump)
570                 {
571                         /*
572                                 NOTE: The d value in move() affects jump height by
573                                 raising the height at which the jump speed is kept
574                                 at its starting value
575                         */
576                         v3f speedJ = getSpeed();
577                         if(speedJ.Y >= -0.5 * BS)
578                         {
579                                 speedJ.Y = movement_speed_jump * physics_override_jump;
580                                 setSpeed(speedJ);
581
582                                 MtEvent *e = new SimpleTriggerEvent("PlayerJump");
583                                 m_client->event()->put(e);
584                         }
585                 }
586                 else if(in_liquid)
587                 {
588                         if(fast_climb)
589                                 speedV.Y = movement_speed_fast;
590                         else
591                                 speedV.Y = movement_speed_walk;
592                         swimming_vertical = true;
593                 }
594                 else if(is_climbing)
595                 {
596                         if(fast_climb)
597                                 speedV.Y = movement_speed_fast;
598                         else
599                                 speedV.Y = movement_speed_climb;
600                 }
601         }
602
603         // The speed of the player (Y is ignored)
604         if(superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb))
605                 speedH = speedH.normalize() * movement_speed_fast;
606         else if(control.sneak && !free_move && !in_liquid && !in_liquid_stable)
607                 speedH = speedH.normalize() * movement_speed_crouch;
608         else
609                 speedH = speedH.normalize() * movement_speed_walk;
610
611         // Acceleration increase
612         f32 incH = 0; // Horizontal (X, Z)
613         f32 incV = 0; // Vertical (Y)
614         if((!touching_ground && !free_move && !is_climbing && !in_liquid) || (!free_move && m_can_jump && control.jump))
615         {
616                 // Jumping and falling
617                 if(superspeed || (fast_move && control.aux1))
618                         incH = movement_acceleration_fast * BS * dtime;
619                 else
620                         incH = movement_acceleration_air * BS * dtime;
621                 incV = 0; // No vertical acceleration in air
622         }
623         else if (superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb))
624                 incH = incV = movement_acceleration_fast * BS * dtime;
625         else
626                 incH = incV = movement_acceleration_default * BS * dtime;
627
628         // Accelerate to target speed with maximum increment
629         accelerateHorizontal(speedH * physics_override_speed, incH * physics_override_speed);
630         accelerateVertical(speedV * physics_override_speed, incV * physics_override_speed);
631 }
632
633 v3s16 LocalPlayer::getStandingNodePos()
634 {
635         if(m_sneak_node_exists)
636                 return m_sneak_node;
637         return floatToInt(getPosition() - v3f(0, BS, 0), BS);
638 }
639
640 v3s16 LocalPlayer::getFootstepNodePos()
641 {
642         if (touching_ground)
643                 // BS * 0.05 below the player's feet ensures a 1/16th height
644                 // nodebox is detected instead of the node below it.
645                 return floatToInt(getPosition() - v3f(0, BS * 0.05f, 0), BS);
646         // A larger distance below is necessary for a footstep sound
647         // when landing after a jump or fall. BS * 0.5 ensures water
648         // sounds when swimming in 1 node deep water.
649         return floatToInt(getPosition() - v3f(0, BS * 0.5f, 0), BS);
650 }
651
652 v3s16 LocalPlayer::getLightPosition() const
653 {
654         return floatToInt(m_position + v3f(0,BS+BS/2,0), BS);
655 }
656
657 v3f LocalPlayer::getEyeOffset() const
658 {
659         float eye_height = camera_barely_in_ceiling ? 1.5f : 1.625f;
660         return v3f(0, BS * eye_height, 0);
661 }
662
663 // Horizontal acceleration (X and Z), Y direction is ignored
664 void LocalPlayer::accelerateHorizontal(const v3f &target_speed, const f32 max_increase)
665 {
666         if (max_increase == 0)
667                 return;
668
669         v3f d_wanted = target_speed - m_speed;
670         d_wanted.Y = 0;
671         f32 dl = d_wanted.getLength();
672         if (dl > max_increase)
673                 dl = max_increase;
674
675         v3f d = d_wanted.normalize() * dl;
676
677         m_speed.X += d.X;
678         m_speed.Z += d.Z;
679 }
680
681 // Vertical acceleration (Y), X and Z directions are ignored
682 void LocalPlayer::accelerateVertical(const v3f &target_speed, const f32 max_increase)
683 {
684         if (max_increase == 0)
685                 return;
686
687         f32 d_wanted = target_speed.Y - m_speed.Y;
688         if (d_wanted > max_increase)
689                 d_wanted = max_increase;
690         else if (d_wanted < -max_increase)
691                 d_wanted = -max_increase;
692
693         m_speed.Y += d_wanted;
694 }
695