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