]> git.lizzy.rs Git - dragonfireclient.git/blob - src/server/player_sao.cpp
Reuse seed when updating stars
[dragonfireclient.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_prop.show_on_minimap = true;
59         m_hp = m_prop.hp_max;
60         m_breath = m_prop.breath_max;
61         // Disable zoom in survival mode using a value of 0
62         m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
63
64         if (!g_settings->getBool("enable_damage"))
65                 m_armor_groups["immortal"] = 1;
66 }
67
68 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
69 {
70         assert(player);
71         m_player = player;
72         m_privs = privs;
73 }
74
75 v3f PlayerSAO::getEyeOffset() const
76 {
77         return v3f(0, BS * m_prop.eye_height, 0);
78 }
79
80 std::string PlayerSAO::getDescription()
81 {
82         return std::string("player ") + m_player->getName();
83 }
84
85 // Called after id has been set and has been inserted in environment
86 void PlayerSAO::addedToEnvironment(u32 dtime_s)
87 {
88         ServerActiveObject::addedToEnvironment(dtime_s);
89         ServerActiveObject::setBasePosition(m_base_position);
90         m_player->setPlayerSAO(this);
91         m_player->setPeerId(m_peer_id);
92         m_last_good_position = m_base_position;
93 }
94
95 // Called before removing from environment
96 void PlayerSAO::removingFromEnvironment()
97 {
98         ServerActiveObject::removingFromEnvironment();
99         if (m_player->getPlayerSAO() == this) {
100                 unlinkPlayerSessionAndSave();
101                 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
102                         m_env->deleteParticleSpawner(attached_particle_spawner, false);
103                 }
104         }
105 }
106
107 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
108 {
109         std::ostringstream os(std::ios::binary);
110
111         // Protocol >= 15
112         writeU8(os, 1); // version
113         os << serializeString16(m_player->getName()); // name
114         writeU8(os, 1); // is_player
115         writeS16(os, getId()); // id
116         writeV3F32(os, m_base_position);
117         writeV3F32(os, m_rotation);
118         writeU16(os, getHP());
119
120         std::ostringstream msg_os(std::ios::binary);
121         msg_os << serializeString32(getPropertyPacket()); // message 1
122         msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2
123         msg_os << serializeString32(generateUpdateAnimationCommand()); // 3
124         for (const auto &bone_pos : m_bone_position) {
125                 msg_os << serializeString32(generateUpdateBonePositionCommand(
126                         bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N
127         }
128         msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size
129         msg_os << serializeString32(generateUpdatePhysicsOverrideCommand()); // 5 + m_bone_position.size
130
131         int message_count = 5 + m_bone_position.size();
132
133         for (const auto &id : getAttachmentChildIds()) {
134                 if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
135                         message_count++;
136                         msg_os << serializeString32(obj->generateUpdateInfantCommand(
137                                 id, protocol_version));
138                 }
139         }
140
141         writeU8(os, message_count);
142         std::string serialized = msg_os.str();
143         os.write(serialized.c_str(), serialized.size());
144
145         // return result
146         return os.str();
147 }
148
149 void PlayerSAO::getStaticData(std::string * result) const
150 {
151         FATAL_ERROR("Obsolete function");
152 }
153
154 void PlayerSAO::step(float dtime, bool send_recommended)
155 {
156         if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
157                 // Get nose/mouth position, approximate with eye position
158                 v3s16 p = floatToInt(getEyePosition(), BS);
159                 MapNode n = m_env->getMap().getNode(p);
160                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
161                 // If node generates drown
162                 if (c.drowning > 0 && m_hp > 0) {
163                         if (m_breath > 0)
164                                 setBreath(m_breath - 1);
165
166                         // No more breath, damage player
167                         if (m_breath == 0) {
168                                 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
169                                 setHP(m_hp - c.drowning, reason);
170                                 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
171                         }
172                 }
173         }
174
175         if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
176                 // Get nose/mouth position, approximate with eye position
177                 v3s16 p = floatToInt(getEyePosition(), BS);
178                 MapNode n = m_env->getMap().getNode(p);
179                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
180                 // If player is alive & not drowning & not in ignore & not immortal, breathe
181                 if (m_breath < m_prop.breath_max && c.drowning == 0 &&
182                                 n.getContent() != CONTENT_IGNORE && m_hp > 0)
183                         setBreath(m_breath + 1);
184         }
185
186         if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
187                 u32 damage_per_second = 0;
188                 std::string nodename;
189                 // Lowest and highest damage points are 0.1 within collisionbox
190                 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
191
192                 // Sequence of damage points, starting 0.1 above feet and progressing
193                 // upwards in 1 node intervals, stopping below top damage point.
194                 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
195                         v3s16 p = floatToInt(m_base_position +
196                                 v3f(0.0f, dam_height * BS, 0.0f), BS);
197                         MapNode n = m_env->getMap().getNode(p);
198                         const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
199                         if (c.damage_per_second > damage_per_second) {
200                                 damage_per_second = c.damage_per_second;
201                                 nodename = c.name;
202                         }
203                 }
204
205                 // Top damage point
206                 v3s16 ptop = floatToInt(m_base_position +
207                         v3f(0.0f, dam_top * BS, 0.0f), BS);
208                 MapNode ntop = m_env->getMap().getNode(ptop);
209                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
210                 if (c.damage_per_second > damage_per_second) {
211                         damage_per_second = c.damage_per_second;
212                         nodename = c.name;
213                 }
214
215                 if (damage_per_second != 0 && m_hp > 0) {
216                         s32 newhp = (s32)m_hp - (s32)damage_per_second;
217                         PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
218                         setHP(newhp, reason);
219                         m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
220                 }
221         }
222
223         if (!m_properties_sent) {
224                 m_properties_sent = true;
225                 std::string str = getPropertyPacket();
226                 // create message and add to list
227                 m_messages_out.emplace(getId(), true, str);
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                 // This is handled when objects are removed from the map
234                 warningstream << "PlayerSAO::step() id=" << m_id <<
235                         " is attached to nonexistent parent. This is a bug." << std::endl;
236                 clearParentAttachment();
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_physics_override_sent) {
297                 m_physics_override_sent = true;
298                 // create message and add to list
299                 m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
300         }
301
302         sendOutdatedData();
303 }
304
305 std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
306 {
307         std::ostringstream os(std::ios::binary);
308         // command
309         writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
310         // parameters
311         writeF32(os, m_physics_override_speed);
312         writeF32(os, m_physics_override_jump);
313         writeF32(os, m_physics_override_gravity);
314         // these are sent inverted so we get true when the server sends nothing
315         writeU8(os, !m_physics_override_sneak);
316         writeU8(os, !m_physics_override_sneak_glitch);
317         writeU8(os, !m_physics_override_new_move);
318         return os.str();
319 }
320
321 void PlayerSAO::setBasePosition(const v3f &position)
322 {
323         if (m_player && position != m_base_position)
324                 m_player->setDirty(true);
325
326         // This needs to be ran for attachments too
327         ServerActiveObject::setBasePosition(position);
328
329         // Updating is not wanted/required for player migration
330         if (m_env) {
331                 m_position_not_sent = true;
332         }
333 }
334
335 void PlayerSAO::setPos(const v3f &pos)
336 {
337         if(isAttached())
338                 return;
339
340         // Send mapblock of target location
341         v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
342         m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
343
344         setBasePosition(pos);
345         // Movement caused by this command is always valid
346         m_last_good_position = pos;
347         m_move_pool.empty();
348         m_time_from_last_teleport = 0.0;
349         m_env->getGameDef()->SendMovePlayer(m_peer_id);
350 }
351
352 void PlayerSAO::moveTo(v3f pos, bool continuous)
353 {
354         if(isAttached())
355                 return;
356
357         setBasePosition(pos);
358         // Movement caused by this command is always valid
359         m_last_good_position = pos;
360         m_move_pool.empty();
361         m_time_from_last_teleport = 0.0;
362         m_env->getGameDef()->SendMovePlayer(m_peer_id);
363 }
364
365 void PlayerSAO::setPlayerYaw(const float yaw)
366 {
367         v3f rotation(0, yaw, 0);
368         if (m_player && yaw != m_rotation.Y)
369                 m_player->setDirty(true);
370
371         // Set player model yaw, not look view
372         UnitSAO::setRotation(rotation);
373 }
374
375 void PlayerSAO::setFov(const float fov)
376 {
377         if (m_player && fov != m_fov)
378                 m_player->setDirty(true);
379
380         m_fov = fov;
381 }
382
383 void PlayerSAO::setWantedRange(const s16 range)
384 {
385         if (m_player && range != m_wanted_range)
386                 m_player->setDirty(true);
387
388         m_wanted_range = range;
389 }
390
391 void PlayerSAO::setPlayerYawAndSend(const float yaw)
392 {
393         setPlayerYaw(yaw);
394         m_env->getGameDef()->SendMovePlayer(m_peer_id);
395 }
396
397 void PlayerSAO::setLookPitch(const float pitch)
398 {
399         if (m_player && pitch != m_pitch)
400                 m_player->setDirty(true);
401
402         m_pitch = pitch;
403 }
404
405 void PlayerSAO::setLookPitchAndSend(const float pitch)
406 {
407         setLookPitch(pitch);
408         m_env->getGameDef()->SendMovePlayer(m_peer_id);
409 }
410
411 u16 PlayerSAO::punch(v3f dir,
412         const ToolCapabilities *toolcap,
413         ServerActiveObject *puncher,
414         float time_from_last_punch)
415 {
416         if (!toolcap)
417                 return 0;
418
419         FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
420
421         // No effect if PvP disabled or if immortal
422         if (isImmortal() || !g_settings->getBool("enable_pvp")) {
423                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
424                         // create message and add to list
425                         sendPunchCommand();
426                         return 0;
427                 }
428         }
429
430         s32 old_hp = getHP();
431         HitParams hitparams = getHitParams(m_armor_groups, toolcap,
432                         time_from_last_punch);
433
434         PlayerSAO *playersao = m_player->getPlayerSAO();
435
436         bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
437                                 puncher, time_from_last_punch, toolcap, dir,
438                                 hitparams.hp);
439
440         if (!damage_handled) {
441                 setHP((s32)getHP() - (s32)hitparams.hp,
442                                 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
443         } else { // override client prediction
444                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
445                         // create message and add to list
446                         sendPunchCommand();
447                 }
448         }
449
450         actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
451                 ", hp=" << puncher->getHP() << ") punched " <<
452                 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
453                 "), damage=" << (old_hp - (s32)getHP()) <<
454                 (damage_handled ? " (handled by Lua)" : "") << std::endl;
455
456         return hitparams.wear;
457 }
458
459 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
460 {
461         if (hp == (s32)m_hp)
462                 return; // Nothing to do
463
464         if (m_hp <= 0 && hp < (s32)m_hp)
465                 return; // Cannot take more damage
466
467         {
468                 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - m_hp, reason);
469                 if (hp_change == 0)
470                         return;
471
472                 hp = m_hp + hp_change;
473         }
474
475         s32 oldhp = m_hp;
476         hp = rangelim(hp, 0, m_prop.hp_max);
477
478         if (hp < oldhp && isImmortal())
479                 return; // Do not allow immortal players to be damaged
480
481         m_hp = hp;
482
483         // Update properties on death
484         if ((hp == 0) != (oldhp == 0))
485                 m_properties_sent = false;
486 }
487
488 void PlayerSAO::setBreath(const u16 breath, bool send)
489 {
490         if (m_player && breath != m_breath)
491                 m_player->setDirty(true);
492
493         m_breath = rangelim(breath, 0, m_prop.breath_max);
494
495         if (send)
496                 m_env->getGameDef()->SendPlayerBreath(this);
497 }
498
499 Inventory *PlayerSAO::getInventory() const
500 {
501         return m_player ? &m_player->inventory : nullptr;
502 }
503
504 InventoryLocation PlayerSAO::getInventoryLocation() const
505 {
506         InventoryLocation loc;
507         loc.setPlayer(m_player->getName());
508         return loc;
509 }
510
511 u16 PlayerSAO::getWieldIndex() const
512 {
513         return m_player->getWieldIndex();
514 }
515
516 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
517 {
518         return m_player->getWieldedItem(selected, hand);
519 }
520
521 bool PlayerSAO::setWieldedItem(const ItemStack &item)
522 {
523         InventoryList *mlist = m_player->inventory.getList(getWieldList());
524         if (mlist) {
525                 mlist->changeItem(m_player->getWieldIndex(), item);
526                 return true;
527         }
528         return false;
529 }
530
531 void PlayerSAO::disconnected()
532 {
533         m_peer_id = PEER_ID_INEXISTENT;
534         m_pending_removal = true;
535 }
536
537 void PlayerSAO::unlinkPlayerSessionAndSave()
538 {
539         assert(m_player->getPlayerSAO() == this);
540         m_player->setPeerId(PEER_ID_INEXISTENT);
541         m_env->savePlayer(m_player);
542         m_player->setPlayerSAO(NULL);
543         m_env->removePlayer(m_player);
544 }
545
546 std::string PlayerSAO::getPropertyPacket()
547 {
548         m_prop.is_visible = (true);
549         return generateSetPropertiesCommand(m_prop);
550 }
551
552 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
553 {
554         if (m_max_speed_override_time == 0.0f)
555                 m_max_speed_override = vel;
556         else
557                 m_max_speed_override += vel;
558         if (m_player) {
559                 float accel = MYMIN(m_player->movement_acceleration_default,
560                                 m_player->movement_acceleration_air);
561                 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
562         }
563 }
564
565 bool PlayerSAO::checkMovementCheat()
566 {
567         if (m_is_singleplayer ||
568                         g_settings->getBool("disable_anticheat")) {
569                 m_last_good_position = m_base_position;
570                 return false;
571         }
572         if (UnitSAO *parent = dynamic_cast<UnitSAO *>(getParent())) {
573                 v3f attachment_pos;
574                 {
575                         int parent_id;
576                         std::string bone;
577                         v3f attachment_rot;
578                         bool force_visible;
579                         getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot, &force_visible);
580                 }
581
582                 v3f parent_pos = parent->getBasePosition();
583                 f32 diff = m_base_position.getDistanceFromSQ(parent_pos) - attachment_pos.getLengthSQ();
584                 const f32 maxdiff = 4.0f * BS; // fair trade-off value for various latencies
585
586                 if (diff > maxdiff * maxdiff) {
587                         setBasePosition(parent_pos);
588                         actionstream << "Server: " << m_player->getName()
589                                         << " moved away from parent; diff=" << sqrtf(diff) / BS
590                                         << " resetting position." << std::endl;
591                         return true;
592                 }
593                 // Player movement is locked to the entity. Skip further checks
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 << "Server: " << m_player->getName()
660                                         << " moved too fast: V=" << d_vert << ", H=" << d_horiz
661                                         << "; resetting position." << 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 }