]> git.lizzy.rs Git - dragonfireclient.git/blob - src/server/player_sao.cpp
9fb53380c35bed6fb6f88ca733475502b0919044
[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         s32 oldhp = m_hp;
462
463         hp = rangelim(hp, 0, m_prop.hp_max);
464
465         if (oldhp != hp) {
466                 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
467                 if (hp_change == 0)
468                         return;
469
470                 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
471         }
472
473         if (hp < oldhp && isImmortal())
474                 return;
475
476         m_hp = hp;
477
478         // Update properties on death
479         if ((hp == 0) != (oldhp == 0))
480                 m_properties_sent = false;
481 }
482
483 void PlayerSAO::setBreath(const u16 breath, bool send)
484 {
485         if (m_player && breath != m_breath)
486                 m_player->setDirty(true);
487
488         m_breath = rangelim(breath, 0, m_prop.breath_max);
489
490         if (send)
491                 m_env->getGameDef()->SendPlayerBreath(this);
492 }
493
494 Inventory *PlayerSAO::getInventory() const
495 {
496         return m_player ? &m_player->inventory : nullptr;
497 }
498
499 InventoryLocation PlayerSAO::getInventoryLocation() const
500 {
501         InventoryLocation loc;
502         loc.setPlayer(m_player->getName());
503         return loc;
504 }
505
506 u16 PlayerSAO::getWieldIndex() const
507 {
508         return m_player->getWieldIndex();
509 }
510
511 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
512 {
513         return m_player->getWieldedItem(selected, hand);
514 }
515
516 bool PlayerSAO::setWieldedItem(const ItemStack &item)
517 {
518         InventoryList *mlist = m_player->inventory.getList(getWieldList());
519         if (mlist) {
520                 mlist->changeItem(m_player->getWieldIndex(), item);
521                 return true;
522         }
523         return false;
524 }
525
526 void PlayerSAO::disconnected()
527 {
528         m_peer_id = PEER_ID_INEXISTENT;
529         m_pending_removal = true;
530 }
531
532 void PlayerSAO::unlinkPlayerSessionAndSave()
533 {
534         assert(m_player->getPlayerSAO() == this);
535         m_player->setPeerId(PEER_ID_INEXISTENT);
536         m_env->savePlayer(m_player);
537         m_player->setPlayerSAO(NULL);
538         m_env->removePlayer(m_player);
539 }
540
541 std::string PlayerSAO::getPropertyPacket()
542 {
543         m_prop.is_visible = (true);
544         return generateSetPropertiesCommand(m_prop);
545 }
546
547 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
548 {
549         if (m_max_speed_override_time == 0.0f)
550                 m_max_speed_override = vel;
551         else
552                 m_max_speed_override += vel;
553         if (m_player) {
554                 float accel = MYMIN(m_player->movement_acceleration_default,
555                                 m_player->movement_acceleration_air);
556                 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
557         }
558 }
559
560 bool PlayerSAO::checkMovementCheat()
561 {
562         if (m_is_singleplayer ||
563                         g_settings->getBool("disable_anticheat")) {
564                 m_last_good_position = m_base_position;
565                 return false;
566         }
567         if (UnitSAO *parent = dynamic_cast<UnitSAO *>(getParent())) {
568                 v3f attachment_pos;
569                 {
570                         int parent_id;
571                         std::string bone;
572                         v3f attachment_rot;
573                         bool force_visible;
574                         getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot, &force_visible);
575                 }
576
577                 v3f parent_pos = parent->getBasePosition();
578                 f32 diff = m_base_position.getDistanceFromSQ(parent_pos) - attachment_pos.getLengthSQ();
579                 const f32 maxdiff = 4.0f * BS; // fair trade-off value for various latencies
580
581                 if (diff > maxdiff * maxdiff) {
582                         setBasePosition(parent_pos);
583                         actionstream << "Server: " << m_player->getName()
584                                         << " moved away from parent; diff=" << sqrtf(diff) / BS
585                                         << " resetting position." << std::endl;
586                         return true;
587                 }
588                 // Player movement is locked to the entity. Skip further checks
589                 return false;
590         }
591
592         bool cheated = false;
593         /*
594                 Check player movements
595
596                 NOTE: Actually the server should handle player physics like the
597                 client does and compare player's position to what is calculated
598                 on our side. This is required when eg. players fly due to an
599                 explosion. Altough a node-based alternative might be possible
600                 too, and much more lightweight.
601         */
602
603         float override_max_H, override_max_V;
604         if (m_max_speed_override_time > 0.0f) {
605                 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
606                 override_max_V = fabs(m_max_speed_override.Y);
607         } else {
608                 override_max_H = override_max_V = 0.0f;
609         }
610
611         float player_max_walk = 0; // horizontal movement
612         float player_max_jump = 0; // vertical upwards movement
613
614         if (m_privs.count("fast") != 0)
615                 player_max_walk = m_player->movement_speed_fast; // Fast speed
616         else
617                 player_max_walk = m_player->movement_speed_walk; // Normal speed
618         player_max_walk *= m_physics_override_speed;
619         player_max_walk = MYMAX(player_max_walk, override_max_H);
620
621         player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
622         // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
623         //        until this can be verified correctly, tolerate higher jumping speeds
624         player_max_jump *= 2.0;
625         player_max_jump = MYMAX(player_max_jump, override_max_V);
626
627         // Don't divide by zero!
628         if (player_max_walk < 0.0001f)
629                 player_max_walk = 0.0001f;
630         if (player_max_jump < 0.0001f)
631                 player_max_jump = 0.0001f;
632
633         v3f diff = (m_base_position - m_last_good_position);
634         float d_vert = diff.Y;
635         diff.Y = 0;
636         float d_horiz = diff.getLength();
637         float required_time = d_horiz / player_max_walk;
638
639         // FIXME: Checking downwards movement is not easily possible currently,
640         //        the server could calculate speed differences to examine the gravity
641         if (d_vert > 0) {
642                 // In certain cases (water, ladders) walking speed is applied vertically
643                 float s = MYMAX(player_max_jump, player_max_walk);
644                 required_time = MYMAX(required_time, d_vert / s);
645         }
646
647         if (m_move_pool.grab(required_time)) {
648                 m_last_good_position = m_base_position;
649         } else {
650                 const float LAG_POOL_MIN = 5.0;
651                 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
652                 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
653                 if (m_time_from_last_teleport > lag_pool_max) {
654                         actionstream << "Server: " << m_player->getName()
655                                         << " moved too fast: V=" << d_vert << ", H=" << d_horiz
656                                         << "; resetting position." << std::endl;
657                         cheated = true;
658                 }
659                 setBasePosition(m_last_good_position);
660         }
661         return cheated;
662 }
663
664 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
665 {
666         //update collision box
667         toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
668         toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
669
670         toset->MinEdge += m_base_position;
671         toset->MaxEdge += m_base_position;
672         return true;
673 }
674
675 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
676 {
677         if (!m_prop.is_visible || !m_prop.pointable) {
678                 return false;
679         }
680
681         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
682         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
683
684         return true;
685 }
686
687 float PlayerSAO::getZoomFOV() const
688 {
689         return m_prop.zoom_fov;
690 }