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