]> git.lizzy.rs Git - dragonfireclient.git/blob - src/localplayer.cpp
Last set of minor cleanups
[dragonfireclient.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         if(g_settings->getBool("always_fly_fast") && free_move && fast_move)
403                 superspeed = true;
404
405         // Old descend control
406         if(g_settings->getBool("aux1_descends"))
407         {
408                 // If free movement and fast movement, always move fast
409                 if(free_move && fast_move)
410                         superspeed = true;
411                 
412                 // Auxiliary button 1 (E)
413                 if(control.aux1)
414                 {
415                         if(free_move)
416                         {
417                                 // In free movement mode, aux1 descends
418                                 v3f speed = getSpeed();
419                                 if(fast_move)
420                                         speed.Y = -20*BS;
421                                 else
422                                         speed.Y = -walkspeed_max;
423                                 setSpeed(speed);
424                         }
425                         else if(is_climbing)
426                         {
427                                         v3f speed = getSpeed();
428                                 speed.Y = -3*BS;
429                                 setSpeed(speed);
430                         }
431                         else
432                         {
433                                 // If not free movement but fast is allowed, aux1 is
434                                 // "Turbo button"
435                                 if(fast_move)
436                                         superspeed = true;
437                         }
438                 }
439         }
440         // New minecraft-like descend control
441         else
442         {
443                 // Auxiliary button 1 (E)
444                 if(control.aux1)
445                 {
446                         if(!is_climbing)
447                         {
448                                 // aux1 is "Turbo button"
449                                 if(fast_move)
450                                         superspeed = true;
451                         }
452                 }
453
454                 if(control.sneak)
455                 {
456                         if(free_move)
457                         {
458                                 // In free movement mode, sneak descends
459                                 v3f speed = getSpeed();
460                                 if(fast_move && (control.aux1 ||
461                                                 g_settings->getBool("always_fly_fast")))
462                                         speed.Y = -20*BS;
463                                 else
464                                         speed.Y = -walkspeed_max;
465                                         setSpeed(speed);
466                         }
467                         else if(is_climbing)
468                         {
469                                 v3f speed = getSpeed();
470                                 speed.Y = -3*BS;
471                                 setSpeed(speed);
472                         }
473                 }
474         }
475
476         if(continuous_forward)
477                 speed += move_direction;
478
479         if(control.up)
480         {
481                 if(continuous_forward)
482                         superspeed = true;
483                 else
484                         speed += move_direction;
485         }
486         if(control.down)
487         {
488                 speed -= move_direction;
489         }
490         if(control.left)
491         {
492                 speed += move_direction.crossProduct(v3f(0,1,0));
493         }
494         if(control.right)
495         {
496                 speed += move_direction.crossProduct(v3f(0,-1,0));
497         }
498         if(control.jump)
499         {
500                 if(free_move)
501                 {
502                         v3f speed = getSpeed();
503                         
504                         if(g_settings->getBool("aux1_descends") ||
505                                         g_settings->getBool("always_fly_fast"))
506                         {
507                                 if(fast_move)
508                                         speed.Y = 20*BS;
509                                 else
510                                         speed.Y = walkspeed_max;
511                         } else {
512                                 if(fast_move && control.aux1)
513                                         speed.Y = 20*BS;
514                                 else
515                                         speed.Y = walkspeed_max;
516                         }
517                         
518                         setSpeed(speed);
519                 }
520                 else if(m_can_jump)
521                 {
522                         /*
523                                 NOTE: The d value in move() affects jump height by
524                                 raising the height at which the jump speed is kept
525                                 at its starting value
526                         */
527                         v3f speed = getSpeed();
528                         if(speed.Y >= -0.5*BS)
529                         {
530                                 speed.Y = 6.5*BS;
531                                 setSpeed(speed);
532                                 
533                                 MtEvent *e = new SimpleTriggerEvent("PlayerJump");
534                                 m_gamedef->event()->put(e);
535                         }
536                 }
537                 // Use the oscillating value for getting out of water
538                 // (so that the player doesn't fly on the surface)
539                 else if(in_water)
540                 {
541                         v3f speed = getSpeed();
542                         speed.Y = 1.5*BS;
543                         setSpeed(speed);
544                         swimming_up = true;
545                 }
546                 else if(is_climbing)
547                 {
548                         v3f speed = getSpeed();
549                         speed.Y = 3*BS;
550                         setSpeed(speed);
551                 }
552         }
553
554         // The speed of the player (Y is ignored)
555         if(superspeed)
556                 speed = speed.normalize() * walkspeed_max * 5.0;
557         else if(control.sneak && !free_move)
558                 speed = speed.normalize() * walkspeed_max / 3.0;
559         else
560                 speed = speed.normalize() * walkspeed_max;
561         
562         f32 inc = walk_acceleration * BS * dtime;
563         
564         // Faster acceleration if fast and free movement
565         if(free_move && fast_move && superspeed)
566                 inc = walk_acceleration * BS * dtime * 10;
567         
568         // Accelerate to target speed with maximum increment
569         accelerate(speed, inc);
570 }
571
572 v3s16 LocalPlayer::getStandingNodePos()
573 {
574         if(m_sneak_node_exists)
575                 return m_sneak_node;
576         return floatToInt(getPosition(), BS);
577 }
578