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