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