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