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