Expose collision information to LuaEntity on_step
[oweals/minetest.git] / src / server / player_sao.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2013-2020 Minetest core developers & community
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "player_sao.h"
22 #include "nodedef.h"
23 #include "remoteplayer.h"
24 #include "scripting_server.h"
25 #include "server.h"
26 #include "serverenvironment.h"
27
28 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
29                 bool is_singleplayer):
30         UnitSAO(env_, v3f(0,0,0)),
31         m_player(player_),
32         m_peer_id(peer_id_),
33         m_is_singleplayer(is_singleplayer)
34 {
35         SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT);
36
37         m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
38         m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
39         m_prop.physical = false;
40         m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
41         m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
42         m_prop.pointable = true;
43         // Start of default appearance, this should be overwritten by Lua
44         m_prop.visual = "upright_sprite";
45         m_prop.visual_size = v3f(1, 2, 1);
46         m_prop.textures.clear();
47         m_prop.textures.emplace_back("player.png");
48         m_prop.textures.emplace_back("player_back.png");
49         m_prop.colors.clear();
50         m_prop.colors.emplace_back(255, 255, 255, 255);
51         m_prop.spritediv = v2s16(1,1);
52         m_prop.eye_height = 1.625f;
53         // End of default appearance
54         m_prop.is_visible = true;
55         m_prop.backface_culling = false;
56         m_prop.makes_footstep_sound = true;
57         m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
58         m_hp = m_prop.hp_max;
59         m_breath = m_prop.breath_max;
60         // Disable zoom in survival mode using a value of 0
61         m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
62
63         if (!g_settings->getBool("enable_damage"))
64                 m_armor_groups["immortal"] = 1;
65 }
66
67 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
68 {
69         assert(player);
70         m_player = player;
71         m_privs = privs;
72 }
73
74 v3f PlayerSAO::getEyeOffset() const
75 {
76         return v3f(0, BS * m_prop.eye_height, 0);
77 }
78
79 std::string PlayerSAO::getDescription()
80 {
81         return std::string("player ") + m_player->getName();
82 }
83
84 // Called after id has been set and has been inserted in environment
85 void PlayerSAO::addedToEnvironment(u32 dtime_s)
86 {
87         ServerActiveObject::addedToEnvironment(dtime_s);
88         ServerActiveObject::setBasePosition(m_base_position);
89         m_player->setPlayerSAO(this);
90         m_player->setPeerId(m_peer_id);
91         m_last_good_position = m_base_position;
92 }
93
94 // Called before removing from environment
95 void PlayerSAO::removingFromEnvironment()
96 {
97         ServerActiveObject::removingFromEnvironment();
98         if (m_player->getPlayerSAO() == this) {
99                 unlinkPlayerSessionAndSave();
100                 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
101                         m_env->deleteParticleSpawner(attached_particle_spawner, false);
102                 }
103         }
104 }
105
106 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
107 {
108         std::ostringstream os(std::ios::binary);
109
110         // Protocol >= 15
111         writeU8(os, 1); // version
112         os << serializeString(m_player->getName()); // name
113         writeU8(os, 1); // is_player
114         writeS16(os, getId()); // id
115         writeV3F32(os, m_base_position);
116         writeV3F32(os, m_rotation);
117         writeU16(os, getHP());
118
119         std::ostringstream msg_os(std::ios::binary);
120         msg_os << serializeLongString(getPropertyPacket()); // message 1
121         msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2
122         msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3
123         for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
124                         ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
125                 msg_os << serializeLongString(generateUpdateBonePositionCommand((*ii).first,
126                         (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
127         }
128         msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4
129         msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5
130         // (AO_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
131         msg_os << serializeLongString(generateUpdateNametagAttributesCommand(m_prop.nametag_color)); // 6
132         int message_count = 6 + m_bone_position.size();
133         for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
134                         ii != m_attachment_child_ids.end(); ++ii) {
135                 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
136                         message_count++;
137                         msg_os << serializeLongString(obj->generateUpdateInfantCommand(*ii, protocol_version));
138                 }
139         }
140
141         writeU8(os, message_count);
142         os.write(msg_os.str().c_str(), msg_os.str().size());
143
144         // return result
145         return os.str();
146 }
147
148 void PlayerSAO::getStaticData(std::string * result) const
149 {
150         FATAL_ERROR("Obsolete function");
151 }
152
153 void PlayerSAO::step(float dtime, bool send_recommended)
154 {
155         if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
156                 // Get nose/mouth position, approximate with eye position
157                 v3s16 p = floatToInt(getEyePosition(), BS);
158                 MapNode n = m_env->getMap().getNode(p);
159                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
160                 // If node generates drown
161                 if (c.drowning > 0 && m_hp > 0) {
162                         if (m_breath > 0)
163                                 setBreath(m_breath - 1);
164
165                         // No more breath, damage player
166                         if (m_breath == 0) {
167                                 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
168                                 setHP(m_hp - c.drowning, reason);
169                                 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
170                         }
171                 }
172         }
173
174         if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
175                 // Get nose/mouth position, approximate with eye position
176                 v3s16 p = floatToInt(getEyePosition(), BS);
177                 MapNode n = m_env->getMap().getNode(p);
178                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
179                 // If player is alive & not drowning & not in ignore & not immortal, breathe
180                 if (m_breath < m_prop.breath_max && c.drowning == 0 &&
181                                 n.getContent() != CONTENT_IGNORE && m_hp > 0)
182                         setBreath(m_breath + 1);
183         }
184
185         if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
186                 u32 damage_per_second = 0;
187                 std::string nodename;
188                 // Lowest and highest damage points are 0.1 within collisionbox
189                 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
190
191                 // Sequence of damage points, starting 0.1 above feet and progressing
192                 // upwards in 1 node intervals, stopping below top damage point.
193                 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
194                         v3s16 p = floatToInt(m_base_position +
195                                 v3f(0.0f, dam_height * BS, 0.0f), BS);
196                         MapNode n = m_env->getMap().getNode(p);
197                         const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
198                         if (c.damage_per_second > damage_per_second) {
199                                 damage_per_second = c.damage_per_second;
200                                 nodename = c.name;
201                         }
202                 }
203
204                 // Top damage point
205                 v3s16 ptop = floatToInt(m_base_position +
206                         v3f(0.0f, dam_top * BS, 0.0f), BS);
207                 MapNode ntop = m_env->getMap().getNode(ptop);
208                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
209                 if (c.damage_per_second > damage_per_second) {
210                         damage_per_second = c.damage_per_second;
211                         nodename = c.name;
212                 }
213
214                 if (damage_per_second != 0 && m_hp > 0) {
215                         s32 newhp = (s32)m_hp - (s32)damage_per_second;
216                         PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
217                         setHP(newhp, reason);
218                         m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
219                 }
220         }
221
222         if (!m_properties_sent) {
223                 m_properties_sent = true;
224                 std::string str = getPropertyPacket();
225                 // create message and add to list
226                 ActiveObjectMessage aom(getId(), true, str);
227                 m_messages_out.push(aom);
228                 m_env->getScriptIface()->player_event(this, "properties_changed");
229         }
230
231         // If attached, check that our parent is still there. If it isn't, detach.
232         if (m_attachment_parent_id && !isAttached()) {
233                 m_attachment_parent_id = 0;
234                 m_attachment_bone = "";
235                 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
236                 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
237                 setBasePosition(m_last_good_position);
238                 m_env->getGameDef()->SendMovePlayer(m_peer_id);
239         }
240
241         //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
242
243         // Set lag pool maximums based on estimated lag
244         const float LAG_POOL_MIN = 5.0f;
245         float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
246         if(lag_pool_max < LAG_POOL_MIN)
247                 lag_pool_max = LAG_POOL_MIN;
248         m_dig_pool.setMax(lag_pool_max);
249         m_move_pool.setMax(lag_pool_max);
250
251         // Increment cheat prevention timers
252         m_dig_pool.add(dtime);
253         m_move_pool.add(dtime);
254         m_time_from_last_teleport += dtime;
255         m_time_from_last_punch += dtime;
256         m_nocheat_dig_time += dtime;
257         m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f);
258
259         // Each frame, parent position is copied if the object is attached,
260         // otherwise it's calculated normally.
261         // If the object gets detached this comes into effect automatically from
262         // the last known origin.
263         if (isAttached()) {
264                 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
265                 m_last_good_position = pos;
266                 setBasePosition(pos);
267         }
268
269         if (!send_recommended)
270                 return;
271
272         if (m_position_not_sent) {
273                 m_position_not_sent = false;
274                 float update_interval = m_env->getSendRecommendedInterval();
275                 v3f pos;
276                 // When attached, the position is only sent to clients where the
277                 // parent isn't known
278                 if (isAttached())
279                         pos = m_last_good_position;
280                 else
281                         pos = m_base_position;
282
283                 std::string str = generateUpdatePositionCommand(
284                         pos,
285                         v3f(0.0f, 0.0f, 0.0f),
286                         v3f(0.0f, 0.0f, 0.0f),
287                         m_rotation,
288                         true,
289                         false,
290                         update_interval
291                 );
292                 // create message and add to list
293                 m_messages_out.emplace(getId(), false, str);
294         }
295
296         if (!m_armor_groups_sent) {
297                 m_armor_groups_sent = true;
298                 // create message and add to list
299                 m_messages_out.emplace(getId(), true, generateUpdateArmorGroupsCommand());
300         }
301
302         if (!m_physics_override_sent) {
303                 m_physics_override_sent = true;
304                 // create message and add to list
305                 m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
306         }
307
308         if (!m_animation_sent) {
309                 m_animation_sent = true;
310                 // create message and add to list
311                 m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand());
312         }
313
314         if (!m_bone_position_sent) {
315                 m_bone_position_sent = true;
316                 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
317                                 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
318                         std::string str = generateUpdateBonePositionCommand((*ii).first,
319                                         (*ii).second.X, (*ii).second.Y);
320                         // create message and add to list
321                         m_messages_out.emplace(getId(), true, str);
322                 }
323         }
324
325         if (!m_attachment_sent) {
326                 m_attachment_sent = true;
327                 std::string str = generateUpdateAttachmentCommand();
328                 // create message and add to list
329                 ActiveObjectMessage aom(getId(), true, str);
330                 m_messages_out.push(aom);
331         }
332 }
333
334 std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
335 {
336         std::ostringstream os(std::ios::binary);
337         // command
338         writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
339         // parameters
340         writeF32(os, m_physics_override_speed);
341         writeF32(os, m_physics_override_jump);
342         writeF32(os, m_physics_override_gravity);
343         // these are sent inverted so we get true when the server sends nothing
344         writeU8(os, !m_physics_override_sneak);
345         writeU8(os, !m_physics_override_sneak_glitch);
346         writeU8(os, !m_physics_override_new_move);
347         return os.str();
348 }
349
350 void PlayerSAO::setBasePosition(const v3f &position)
351 {
352         if (m_player && position != m_base_position)
353                 m_player->setDirty(true);
354
355         // This needs to be ran for attachments too
356         ServerActiveObject::setBasePosition(position);
357
358         // Updating is not wanted/required for player migration
359         if (m_env) {
360                 m_position_not_sent = true;
361         }
362 }
363
364 void PlayerSAO::setPos(const v3f &pos)
365 {
366         if(isAttached())
367                 return;
368
369         // Send mapblock of target location
370         v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
371         m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
372
373         setBasePosition(pos);
374         // Movement caused by this command is always valid
375         m_last_good_position = pos;
376         m_move_pool.empty();
377         m_time_from_last_teleport = 0.0;
378         m_env->getGameDef()->SendMovePlayer(m_peer_id);
379 }
380
381 void PlayerSAO::moveTo(v3f pos, bool continuous)
382 {
383         if(isAttached())
384                 return;
385
386         setBasePosition(pos);
387         // Movement caused by this command is always valid
388         m_last_good_position = pos;
389         m_move_pool.empty();
390         m_time_from_last_teleport = 0.0;
391         m_env->getGameDef()->SendMovePlayer(m_peer_id);
392 }
393
394 void PlayerSAO::setPlayerYaw(const float yaw)
395 {
396         v3f rotation(0, yaw, 0);
397         if (m_player && yaw != m_rotation.Y)
398                 m_player->setDirty(true);
399
400         // Set player model yaw, not look view
401         UnitSAO::setRotation(rotation);
402 }
403
404 void PlayerSAO::setFov(const float fov)
405 {
406         if (m_player && fov != m_fov)
407                 m_player->setDirty(true);
408
409         m_fov = fov;
410 }
411
412 void PlayerSAO::setWantedRange(const s16 range)
413 {
414         if (m_player && range != m_wanted_range)
415                 m_player->setDirty(true);
416
417         m_wanted_range = range;
418 }
419
420 void PlayerSAO::setPlayerYawAndSend(const float yaw)
421 {
422         setPlayerYaw(yaw);
423         m_env->getGameDef()->SendMovePlayer(m_peer_id);
424 }
425
426 void PlayerSAO::setLookPitch(const float pitch)
427 {
428         if (m_player && pitch != m_pitch)
429                 m_player->setDirty(true);
430
431         m_pitch = pitch;
432 }
433
434 void PlayerSAO::setLookPitchAndSend(const float pitch)
435 {
436         setLookPitch(pitch);
437         m_env->getGameDef()->SendMovePlayer(m_peer_id);
438 }
439
440 u16 PlayerSAO::punch(v3f dir,
441         const ToolCapabilities *toolcap,
442         ServerActiveObject *puncher,
443         float time_from_last_punch)
444 {
445         if (!toolcap)
446                 return 0;
447
448         FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
449
450         // No effect if PvP disabled or if immortal
451         if (isImmortal() || !g_settings->getBool("enable_pvp")) {
452                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
453                         // create message and add to list
454                         sendPunchCommand();
455                         return 0;
456                 }
457         }
458
459         s32 old_hp = getHP();
460         HitParams hitparams = getHitParams(m_armor_groups, toolcap,
461                         time_from_last_punch);
462
463         PlayerSAO *playersao = m_player->getPlayerSAO();
464
465         bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
466                                 puncher, time_from_last_punch, toolcap, dir,
467                                 hitparams.hp);
468
469         if (!damage_handled) {
470                 setHP((s32)getHP() - (s32)hitparams.hp,
471                                 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
472         } else { // override client prediction
473                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
474                         // create message and add to list
475                         sendPunchCommand();
476                 }
477         }
478
479         actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
480                 ", hp=" << puncher->getHP() << ") punched " <<
481                 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
482                 "), damage=" << (old_hp - (s32)getHP()) <<
483                 (damage_handled ? " (handled by Lua)" : "") << std::endl;
484
485         return hitparams.wear;
486 }
487
488 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
489 {
490         s32 oldhp = m_hp;
491
492         hp = rangelim(hp, 0, m_prop.hp_max);
493
494         if (oldhp != hp) {
495                 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
496                 if (hp_change == 0)
497                         return;
498
499                 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
500         }
501
502         if (hp < oldhp && isImmortal())
503                 return;
504
505         m_hp = hp;
506
507         // Update properties on death
508         if ((hp == 0) != (oldhp == 0))
509                 m_properties_sent = false;
510 }
511
512 void PlayerSAO::setBreath(const u16 breath, bool send)
513 {
514         if (m_player && breath != m_breath)
515                 m_player->setDirty(true);
516
517         m_breath = rangelim(breath, 0, m_prop.breath_max);
518
519         if (send)
520                 m_env->getGameDef()->SendPlayerBreath(this);
521 }
522
523 Inventory *PlayerSAO::getInventory() const
524 {
525         return m_player ? &m_player->inventory : nullptr;
526 }
527
528 InventoryLocation PlayerSAO::getInventoryLocation() const
529 {
530         InventoryLocation loc;
531         loc.setPlayer(m_player->getName());
532         return loc;
533 }
534
535 u16 PlayerSAO::getWieldIndex() const
536 {
537         return m_player->getWieldIndex();
538 }
539
540 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
541 {
542         return m_player->getWieldedItem(selected, hand);
543 }
544
545 bool PlayerSAO::setWieldedItem(const ItemStack &item)
546 {
547         InventoryList *mlist = m_player->inventory.getList(getWieldList());
548         if (mlist) {
549                 mlist->changeItem(m_player->getWieldIndex(), item);
550                 return true;
551         }
552         return false;
553 }
554
555 void PlayerSAO::disconnected()
556 {
557         m_peer_id = PEER_ID_INEXISTENT;
558         m_pending_removal = true;
559 }
560
561 void PlayerSAO::unlinkPlayerSessionAndSave()
562 {
563         assert(m_player->getPlayerSAO() == this);
564         m_player->setPeerId(PEER_ID_INEXISTENT);
565         m_env->savePlayer(m_player);
566         m_player->setPlayerSAO(NULL);
567         m_env->removePlayer(m_player);
568 }
569
570 std::string PlayerSAO::getPropertyPacket()
571 {
572         m_prop.is_visible = (true);
573         return generateSetPropertiesCommand(m_prop);
574 }
575
576 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
577 {
578         if (m_max_speed_override_time == 0.0f)
579                 m_max_speed_override = vel;
580         else
581                 m_max_speed_override += vel;
582         if (m_player) {
583                 float accel = MYMIN(m_player->movement_acceleration_default,
584                                 m_player->movement_acceleration_air);
585                 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
586         }
587 }
588
589 bool PlayerSAO::checkMovementCheat()
590 {
591         if (isAttached() || m_is_singleplayer ||
592                         g_settings->getBool("disable_anticheat")) {
593                 m_last_good_position = m_base_position;
594                 return false;
595         }
596
597         bool cheated = false;
598         /*
599                 Check player movements
600
601                 NOTE: Actually the server should handle player physics like the
602                 client does and compare player's position to what is calculated
603                 on our side. This is required when eg. players fly due to an
604                 explosion. Altough a node-based alternative might be possible
605                 too, and much more lightweight.
606         */
607
608         float override_max_H, override_max_V;
609         if (m_max_speed_override_time > 0.0f) {
610                 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
611                 override_max_V = fabs(m_max_speed_override.Y);
612         } else {
613                 override_max_H = override_max_V = 0.0f;
614         }
615
616         float player_max_walk = 0; // horizontal movement
617         float player_max_jump = 0; // vertical upwards movement
618
619         if (m_privs.count("fast") != 0)
620                 player_max_walk = m_player->movement_speed_fast; // Fast speed
621         else
622                 player_max_walk = m_player->movement_speed_walk; // Normal speed
623         player_max_walk *= m_physics_override_speed;
624         player_max_walk = MYMAX(player_max_walk, override_max_H);
625
626         player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
627         // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
628         //        until this can be verified correctly, tolerate higher jumping speeds
629         player_max_jump *= 2.0;
630         player_max_jump = MYMAX(player_max_jump, override_max_V);
631
632         // Don't divide by zero!
633         if (player_max_walk < 0.0001f)
634                 player_max_walk = 0.0001f;
635         if (player_max_jump < 0.0001f)
636                 player_max_jump = 0.0001f;
637
638         v3f diff = (m_base_position - m_last_good_position);
639         float d_vert = diff.Y;
640         diff.Y = 0;
641         float d_horiz = diff.getLength();
642         float required_time = d_horiz / player_max_walk;
643
644         // FIXME: Checking downwards movement is not easily possible currently,
645         //        the server could calculate speed differences to examine the gravity
646         if (d_vert > 0) {
647                 // In certain cases (water, ladders) walking speed is applied vertically
648                 float s = MYMAX(player_max_jump, player_max_walk);
649                 required_time = MYMAX(required_time, d_vert / s);
650         }
651
652         if (m_move_pool.grab(required_time)) {
653                 m_last_good_position = m_base_position;
654         } else {
655                 const float LAG_POOL_MIN = 5.0;
656                 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
657                 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
658                 if (m_time_from_last_teleport > lag_pool_max) {
659                         actionstream << "Player " << m_player->getName()
660                                         << " moved too fast; resetting position"
661                                         << std::endl;
662                         cheated = true;
663                 }
664                 setBasePosition(m_last_good_position);
665         }
666         return cheated;
667 }
668
669 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
670 {
671         //update collision box
672         toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
673         toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
674
675         toset->MinEdge += m_base_position;
676         toset->MaxEdge += m_base_position;
677         return true;
678 }
679
680 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
681 {
682         if (!m_prop.is_visible || !m_prop.pointable) {
683                 return false;
684         }
685
686         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
687         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
688
689         return true;
690 }
691
692 float PlayerSAO::getZoomFOV() const
693 {
694         return m_prop.zoom_fov;
695 }