Players stay in environment even when dead, damage flash and fall damage fixes
[oweals/minetest.git] / src / player.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010-2011 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 General Public License as published by
7 the Free Software Foundation; either version 2 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 General Public License for more details.
14
15 You should have received a copy of the GNU 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 "player.h"
21 #include "map.h"
22 #include "connection.h"
23 #include "constants.h"
24 #include "utility.h"
25 #ifndef SERVER
26 #include <ITextSceneNode.h>
27 #endif
28 #include "main.h" // For g_settings
29 #include "settings.h"
30 #include "nodedef.h"
31 #include "collision.h"
32 #include "environment.h"
33 #include "gamedef.h"
34
35 Player::Player(IGameDef *gamedef):
36         touching_ground(false),
37         in_water(false),
38         in_water_stable(false),
39         is_climbing(false),
40         swimming_up(false),
41         inventory(gamedef->idef()),
42         inventory_backup(NULL),
43         hp(20),
44         peer_id(PEER_ID_INEXISTENT),
45 // protected
46         m_gamedef(gamedef),
47         m_pitch(0),
48         m_yaw(0),
49         m_speed(0,0,0),
50         m_position(0,0,0)
51 {
52         updateName("<not set>");
53         resetInventory();
54 }
55
56 Player::~Player()
57 {
58         delete inventory_backup;
59 }
60
61 void Player::resetInventory()
62 {
63         inventory.clear();
64         inventory.addList("main", PLAYER_INVENTORY_SIZE);
65         inventory.addList("craft", 9);
66         inventory.addList("craftpreview", 1);
67         inventory.addList("craftresult", 1);
68 }
69
70 // Y direction is ignored
71 void Player::accelerate(v3f target_speed, f32 max_increase)
72 {
73         v3f d_wanted = target_speed - m_speed;
74         d_wanted.Y = 0;
75         f32 dl_wanted = d_wanted.getLength();
76         f32 dl = dl_wanted;
77         if(dl > max_increase)
78                 dl = max_increase;
79         
80         v3f d = d_wanted.normalize() * dl;
81
82         m_speed.X += d.X;
83         m_speed.Z += d.Z;
84         //m_speed += d;
85
86 #if 0 // old code
87         if(m_speed.X < target_speed.X - max_increase)
88                 m_speed.X += max_increase;
89         else if(m_speed.X > target_speed.X + max_increase)
90                 m_speed.X -= max_increase;
91         else if(m_speed.X < target_speed.X)
92                 m_speed.X = target_speed.X;
93         else if(m_speed.X > target_speed.X)
94                 m_speed.X = target_speed.X;
95
96         if(m_speed.Z < target_speed.Z - max_increase)
97                 m_speed.Z += max_increase;
98         else if(m_speed.Z > target_speed.Z + max_increase)
99                 m_speed.Z -= max_increase;
100         else if(m_speed.Z < target_speed.Z)
101                 m_speed.Z = target_speed.Z;
102         else if(m_speed.Z > target_speed.Z)
103                 m_speed.Z = target_speed.Z;
104 #endif
105 }
106
107 v3s16 Player::getLightPosition() const
108 {
109         return floatToInt(m_position + v3f(0,BS+BS/2,0), BS);
110 }
111
112 void Player::serialize(std::ostream &os)
113 {
114         // Utilize a Settings object for storing values
115         Settings args;
116         args.setS32("version", 1);
117         args.set("name", m_name);
118         //args.set("password", m_password);
119         args.setFloat("pitch", m_pitch);
120         args.setFloat("yaw", m_yaw);
121         args.setV3F("position", m_position);
122         args.setS32("hp", hp);
123
124         args.writeLines(os);
125
126         os<<"PlayerArgsEnd\n";
127         
128         // If actual inventory is backed up due to creative mode, save it
129         // instead of the dummy creative mode inventory
130         if(inventory_backup)
131                 inventory_backup->serialize(os);
132         else
133                 inventory.serialize(os);
134 }
135
136 void Player::deSerialize(std::istream &is)
137 {
138         Settings args;
139         
140         for(;;)
141         {
142                 if(is.eof())
143                         throw SerializationError
144                                         ("Player::deSerialize(): PlayerArgsEnd not found");
145                 std::string line;
146                 std::getline(is, line);
147                 std::string trimmedline = trim(line);
148                 if(trimmedline == "PlayerArgsEnd")
149                         break;
150                 args.parseConfigLine(line);
151         }
152
153         //args.getS32("version"); // Version field value not used
154         std::string name = args.get("name");
155         updateName(name.c_str());
156         setPitch(args.getFloat("pitch"));
157         setYaw(args.getFloat("yaw"));
158         setPosition(args.getV3F("position"));
159         try{
160                 hp = args.getS32("hp");
161         }catch(SettingNotFoundException &e){
162                 hp = 20;
163         }
164
165         inventory.deSerialize(is);
166
167         if(inventory.getList("craftpreview") == NULL)
168         {
169                 // Convert players without craftpreview
170                 inventory.addList("craftpreview", 1);
171
172                 bool craftresult_is_preview = true;
173                 if(args.exists("craftresult_is_preview"))
174                         craftresult_is_preview = args.getBool("craftresult_is_preview");
175                 if(craftresult_is_preview)
176                 {
177                         // Clear craftresult
178                         inventory.getList("craftresult")->changeItem(0, ItemStack());
179                 }
180         }
181 }
182
183 #ifndef SERVER
184 /*
185         LocalPlayer
186 */
187
188 LocalPlayer::LocalPlayer(IGameDef *gamedef):
189         Player(gamedef),
190         m_sneak_node(32767,32767,32767),
191         m_sneak_node_exists(false)
192 {
193         // Initialize hp to 0, so that no hearts will be shown if server
194         // doesn't support health points
195         hp = 0;
196 }
197
198 LocalPlayer::~LocalPlayer()
199 {
200 }
201
202 void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
203                 core::list<CollisionInfo> *collision_info)
204 {
205         INodeDefManager *nodemgr = m_gamedef->ndef();
206
207         v3f position = getPosition();
208         v3f oldpos = position;
209         v3s16 oldpos_i = floatToInt(oldpos, BS);
210
211         v3f old_speed = m_speed;
212
213         /*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
214                         <<oldpos_i.Z<<")"<<std::endl;*/
215
216         /*
217                 Calculate new position
218         */
219         position += m_speed * dtime;
220         
221         // Skip collision detection if a special movement mode is used
222         bool free_move = g_settings->getBool("free_move");
223         if(free_move)
224         {
225                 setPosition(position);
226                 return;
227         }
228
229         /*
230                 Collision detection
231         */
232         
233         // Player position in nodes
234         v3s16 pos_i = floatToInt(position, BS);
235         
236         /*
237                 Check if player is in water (the oscillating value)
238         */
239         try{
240                 // If in water, the threshold of coming out is at higher y
241                 if(in_water)
242                 {
243                         v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
244                         in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
245                 }
246                 // If not in water, the threshold of going in is at lower y
247                 else
248                 {
249                         v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
250                         in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
251                 }
252         }
253         catch(InvalidPositionException &e)
254         {
255                 in_water = false;
256         }
257
258         /*
259                 Check if player is in water (the stable value)
260         */
261         try{
262                 v3s16 pp = floatToInt(position + v3f(0,0,0), BS);
263                 in_water_stable = nodemgr->get(map.getNode(pp).getContent()).isLiquid();
264         }
265         catch(InvalidPositionException &e)
266         {
267                 in_water_stable = false;
268         }
269
270         /*
271                 Check if player is climbing
272         */
273
274         try {
275                 v3s16 pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
276                 v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
277                 is_climbing = ((nodemgr->get(map.getNode(pp).getContent()).climbable ||
278                 nodemgr->get(map.getNode(pp2).getContent()).climbable) && !free_move);
279         }
280         catch(InvalidPositionException &e)
281         {
282                 is_climbing = false;
283         }
284
285         /*
286                 Collision uncertainty radius
287                 Make it a bit larger than the maximum distance of movement
288         */
289         //f32 d = pos_max_d * 1.1;
290         // A fairly large value in here makes moving smoother
291         f32 d = 0.15*BS;
292
293         // This should always apply, otherwise there are glitches
294         assert(d > pos_max_d);
295
296         float player_radius = BS*0.35;
297         float player_height = BS*1.7;
298         
299         // Maximum distance over border for sneaking
300         f32 sneak_max = BS*0.4;
301
302         /*
303                 If sneaking, player has larger collision radius to keep from
304                 falling
305         */
306         /*if(control.sneak)
307                 player_radius = sneak_max + d*1.1;*/
308         
309         /*
310                 If sneaking, keep in range from the last walked node and don't
311                 fall off from it
312         */
313         if(control.sneak && m_sneak_node_exists)
314         {
315                 f32 maxd = 0.5*BS + sneak_max;
316                 v3f lwn_f = intToFloat(m_sneak_node, BS);
317                 position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd);
318                 position.Z = rangelim(position.Z, lwn_f.Z-maxd, lwn_f.Z+maxd);
319                 
320                 f32 min_y = lwn_f.Y + 0.5*BS;
321                 if(position.Y < min_y)
322                 {
323                         position.Y = min_y;
324
325                         //v3f old_speed = m_speed;
326
327                         if(m_speed.Y < 0)
328                                 m_speed.Y = 0;
329
330                         /*if(collision_info)
331                         {
332                                 // Report fall collision
333                                 if(old_speed.Y < m_speed.Y - 0.1)
334                                 {
335                                         CollisionInfo info;
336                                         info.t = COLLISION_FALL;
337                                         info.speed = m_speed.Y - old_speed.Y;
338                                         collision_info->push_back(info);
339                                 }
340                         }*/
341                 }
342         }
343
344         /*
345                 Calculate player collision box (new and old)
346         */
347         core::aabbox3d<f32> playerbox(
348                 position.X - player_radius,
349                 position.Y - 0.0,
350                 position.Z - player_radius,
351                 position.X + player_radius,
352                 position.Y + player_height,
353                 position.Z + player_radius
354         );
355         core::aabbox3d<f32> playerbox_old(
356                 oldpos.X - player_radius,
357                 oldpos.Y - 0.0,
358                 oldpos.Z - player_radius,
359                 oldpos.X + player_radius,
360                 oldpos.Y + player_height,
361                 oldpos.Z + player_radius
362         );
363
364         /*
365                 If the player's feet touch the topside of any node, this is
366                 set to true.
367
368                 Player is allowed to jump when this is true.
369         */
370         touching_ground = false;
371
372         /*std::cout<<"Checking collisions for ("
373                         <<oldpos_i.X<<","<<oldpos_i.Y<<","<<oldpos_i.Z
374                         <<") -> ("
375                         <<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z
376                         <<"):"<<std::endl;*/
377         
378         bool standing_on_unloaded = false;
379         
380         /*
381                 Go through every node around the player
382         */
383         for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
384         for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
385         for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
386         {
387                 bool is_unloaded = false;
388                 try{
389                         // Player collides into walkable nodes
390                         if(nodemgr->get(map.getNode(v3s16(x,y,z))).walkable == false)
391                                 continue;
392                 }
393                 catch(InvalidPositionException &e)
394                 {
395                         is_unloaded = true;
396                         // Doing nothing here will block the player from
397                         // walking over map borders
398                 }
399
400                 core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
401                 
402                 /*
403                         See if the player is touching ground.
404
405                         Player touches ground if player's minimum Y is near node's
406                         maximum Y and player's X-Z-area overlaps with the node's
407                         X-Z-area.
408
409                         Use 0.15*BS so that it is easier to get on a node.
410                 */
411                 if(
412                                 //fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < d
413                                 fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < 0.15*BS
414                                 && nodebox.MaxEdge.X-d > playerbox.MinEdge.X
415                                 && nodebox.MinEdge.X+d < playerbox.MaxEdge.X
416                                 && nodebox.MaxEdge.Z-d > playerbox.MinEdge.Z
417                                 && nodebox.MinEdge.Z+d < playerbox.MaxEdge.Z
418                 ){
419                         touching_ground = true;
420                         if(is_unloaded)
421                                 standing_on_unloaded = true;
422                 }
423                 
424                 // If player doesn't intersect with node, ignore node.
425                 if(playerbox.intersectsWithBox(nodebox) == false)
426                         continue;
427                 
428                 /*
429                         Go through every axis
430                 */
431                 v3f dirs[3] = {
432                         v3f(0,0,1), // back-front
433                         v3f(0,1,0), // top-bottom
434                         v3f(1,0,0), // right-left
435                 };
436                 for(u16 i=0; i<3; i++)
437                 {
438                         /*
439                                 Calculate values along the axis
440                         */
441                         f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
442                         f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
443                         f32 playermax = playerbox.MaxEdge.dotProduct(dirs[i]);
444                         f32 playermin = playerbox.MinEdge.dotProduct(dirs[i]);
445                         f32 playermax_old = playerbox_old.MaxEdge.dotProduct(dirs[i]);
446                         f32 playermin_old = playerbox_old.MinEdge.dotProduct(dirs[i]);
447                         
448                         /*
449                                 Check collision for the axis.
450                                 Collision happens when player is going through a surface.
451                         */
452                         /*f32 neg_d = d;
453                         f32 pos_d = d;
454                         // Make it easier to get on top of a node
455                         if(i == 1)
456                                 neg_d = 0.15*BS;
457                         bool negative_axis_collides =
458                                 (nodemax > playermin && nodemax <= playermin_old + neg_d
459                                         && m_speed.dotProduct(dirs[i]) < 0);
460                         bool positive_axis_collides =
461                                 (nodemin < playermax && nodemin >= playermax_old - pos_d
462                                         && m_speed.dotProduct(dirs[i]) > 0);*/
463                         bool negative_axis_collides =
464                                 (nodemax > playermin && nodemax <= playermin_old + d
465                                         && m_speed.dotProduct(dirs[i]) < 0);
466                         bool positive_axis_collides =
467                                 (nodemin < playermax && nodemin >= playermax_old - d
468                                         && m_speed.dotProduct(dirs[i]) > 0);
469                         bool main_axis_collides =
470                                         negative_axis_collides || positive_axis_collides;
471                         
472                         /*
473                                 Check overlap of player and node in other axes
474                         */
475                         bool other_axes_overlap = true;
476                         for(u16 j=0; j<3; j++)
477                         {
478                                 if(j == i)
479                                         continue;
480                                 f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
481                                 f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
482                                 f32 playermax = playerbox.MaxEdge.dotProduct(dirs[j]);
483                                 f32 playermin = playerbox.MinEdge.dotProduct(dirs[j]);
484                                 if(!(nodemax - d > playermin && nodemin + d < playermax))
485                                 {
486                                         other_axes_overlap = false;
487                                         break;
488                                 }
489                         }
490                         
491                         /*
492                                 If this is a collision, revert the position in the main
493                                 direction.
494                         */
495                         if(other_axes_overlap && main_axis_collides)
496                         {
497                                 //v3f old_speed = m_speed;
498
499                                 m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
500                                 position -= position.dotProduct(dirs[i]) * dirs[i];
501                                 position += oldpos.dotProduct(dirs[i]) * dirs[i];
502                                 
503                                 /*if(collision_info)
504                                 {
505                                         // Report fall collision
506                                         if(old_speed.Y < m_speed.Y - 0.1)
507                                         {
508                                                 CollisionInfo info;
509                                                 info.t = COLLISION_FALL;
510                                                 info.speed = m_speed.Y - old_speed.Y;
511                                                 collision_info->push_back(info);
512                                         }
513                                 }*/
514                         }
515                 
516                 }
517         } // xyz
518
519         /*
520                 Check the nodes under the player to see from which node the
521                 player is sneaking from, if any.
522         */
523         {
524                 v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS);
525                 v2f player_p2df(position.X, position.Z);
526                 f32 min_distance_f = 100000.0*BS;
527                 // If already seeking from some node, compare to it.
528                 /*if(m_sneak_node_exists)
529                 {
530                         v3f sneaknode_pf = intToFloat(m_sneak_node, BS);
531                         v2f sneaknode_p2df(sneaknode_pf.X, sneaknode_pf.Z);
532                         f32 d_horiz_f = player_p2df.getDistanceFrom(sneaknode_p2df);
533                         f32 d_vert_f = fabs(sneaknode_pf.Y + BS*0.5 - position.Y);
534                         // Ignore if player is not on the same level (likely dropped)
535                         if(d_vert_f < 0.15*BS)
536                                 min_distance_f = d_horiz_f;
537                 }*/
538                 v3s16 new_sneak_node = m_sneak_node;
539                 for(s16 x=-1; x<=1; x++)
540                 for(s16 z=-1; z<=1; z++)
541                 {
542                         v3s16 p = pos_i_bottom + v3s16(x,0,z);
543                         v3f pf = intToFloat(p, BS);
544                         v2f node_p2df(pf.X, pf.Z);
545                         f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
546                         f32 max_axis_distance_f = MYMAX(
547                                         fabs(player_p2df.X-node_p2df.X),
548                                         fabs(player_p2df.Y-node_p2df.Y));
549                                         
550                         if(distance_f > min_distance_f ||
551                                         max_axis_distance_f > 0.5*BS + sneak_max + 0.1*BS)
552                                 continue;
553
554                         try{
555                                 // The node to be sneaked on has to be walkable
556                                 if(nodemgr->get(map.getNode(p)).walkable == false)
557                                         continue;
558                                 // And the node above it has to be nonwalkable
559                                 if(nodemgr->get(map.getNode(p+v3s16(0,1,0))).walkable == true)
560                                         continue;
561                         }
562                         catch(InvalidPositionException &e)
563                         {
564                                 continue;
565                         }
566
567                         min_distance_f = distance_f;
568                         new_sneak_node = p;
569                 }
570                 
571                 bool sneak_node_found = (min_distance_f < 100000.0*BS*0.9);
572                 
573                 if(control.sneak && m_sneak_node_exists)
574                 {
575                         if(sneak_node_found)
576                                 m_sneak_node = new_sneak_node;
577                 }
578                 else
579                 {
580                         m_sneak_node = new_sneak_node;
581                         m_sneak_node_exists = sneak_node_found;
582                 }
583
584                 /*
585                         If sneaking, the player's collision box can be in air, so
586                         this has to be set explicitly
587                 */
588                 if(sneak_node_found && control.sneak)
589                         touching_ground = true;
590         }
591         
592         /*
593                 Set new position
594         */
595         setPosition(position);
596         
597         /*
598                 Report collisions
599         */
600         if(collision_info)
601         {
602                 // Report fall collision
603                 if(old_speed.Y < m_speed.Y - 0.1 && !standing_on_unloaded)
604                 {
605                         CollisionInfo info;
606                         info.t = COLLISION_FALL;
607                         info.speed = m_speed.Y - old_speed.Y;
608                         collision_info->push_back(info);
609                 }
610         }
611 }
612
613 void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
614 {
615         move(dtime, map, pos_max_d, NULL);
616 }
617
618 void LocalPlayer::applyControl(float dtime)
619 {
620         // Clear stuff
621         swimming_up = false;
622
623         // Random constants
624         f32 walk_acceleration = 4.0 * BS;
625         f32 walkspeed_max = 4.0 * BS;
626         
627         setPitch(control.pitch);
628         setYaw(control.yaw);
629         
630         v3f move_direction = v3f(0,0,1);
631         move_direction.rotateXZBy(getYaw());
632         
633         v3f speed = v3f(0,0,0);
634
635         bool free_move = g_settings->getBool("free_move");
636         bool fast_move = g_settings->getBool("fast_move");
637         bool continuous_forward = g_settings->getBool("continuous_forward");
638
639         if(free_move || is_climbing)
640         {
641                 v3f speed = getSpeed();
642                 speed.Y = 0;
643                 setSpeed(speed);
644         }
645
646         // Whether superspeed mode is used or not
647         bool superspeed = false;
648         
649         // If free movement and fast movement, always move fast
650         if(free_move && fast_move)
651                 superspeed = true;
652         
653         // Auxiliary button 1 (E)
654         if(control.aux1)
655         {
656                 if(free_move)
657                 {
658                         // In free movement mode, aux1 descends
659                         v3f speed = getSpeed();
660                         if(fast_move)
661                                 speed.Y = -20*BS;
662                         else
663                                 speed.Y = -walkspeed_max;
664                         setSpeed(speed);
665                 }
666                 else if(is_climbing)
667                 {
668                         v3f speed = getSpeed();
669                         speed.Y = -3*BS;
670                         setSpeed(speed);
671                 }
672                 else
673                 {
674                         // If not free movement but fast is allowed, aux1 is
675                         // "Turbo button"
676                         if(fast_move)
677                                 superspeed = true;
678                 }
679         }
680
681         if(continuous_forward)
682                 speed += move_direction;
683
684         if(control.up)
685         {
686                 if(continuous_forward)
687                         superspeed = true;
688                 else
689                         speed += move_direction;
690         }
691         if(control.down)
692         {
693                 speed -= move_direction;
694         }
695         if(control.left)
696         {
697                 speed += move_direction.crossProduct(v3f(0,1,0));
698         }
699         if(control.right)
700         {
701                 speed += move_direction.crossProduct(v3f(0,-1,0));
702         }
703         if(control.jump)
704         {
705                 if(free_move)
706                 {
707                         v3f speed = getSpeed();
708                         if(fast_move)
709                                 speed.Y = 20*BS;
710                         else
711                                 speed.Y = walkspeed_max;
712                         setSpeed(speed);
713                 }
714                 else if(touching_ground)
715                 {
716                         /*
717                                 NOTE: The d value in move() affects jump height by
718                                 raising the height at which the jump speed is kept
719                                 at its starting value
720                         */
721                         v3f speed = getSpeed();
722                         if(speed.Y >= -0.5*BS)
723                         {
724                                 speed.Y = 6.5*BS;
725                                 setSpeed(speed);
726                         }
727                 }
728                 // Use the oscillating value for getting out of water
729                 // (so that the player doesn't fly on the surface)
730                 else if(in_water)
731                 {
732                         v3f speed = getSpeed();
733                         speed.Y = 1.5*BS;
734                         setSpeed(speed);
735                         swimming_up = true;
736                 }
737                 else if(is_climbing)
738                 {
739                         v3f speed = getSpeed();
740                         speed.Y = 3*BS;
741                         setSpeed(speed);
742                 }
743         }
744
745         // The speed of the player (Y is ignored)
746         if(superspeed)
747                 speed = speed.normalize() * walkspeed_max * 5.0;
748         else if(control.sneak)
749                 speed = speed.normalize() * walkspeed_max / 3.0;
750         else
751                 speed = speed.normalize() * walkspeed_max;
752         
753         f32 inc = walk_acceleration * BS * dtime;
754         
755         // Faster acceleration if fast and free movement
756         if(free_move && fast_move)
757                 inc = walk_acceleration * BS * dtime * 10;
758         
759         // Accelerate to target speed with maximum increment
760         accelerate(speed, inc);
761 }
762 #endif
763