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