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