]> git.lizzy.rs Git - minetest.git/blob - src/server/player_sao.cpp
Settings: Remove unused functions
[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 (const auto &bone_pos : m_bone_position) {
124                 msg_os << serializeLongString(generateUpdateBonePositionCommand(
125                         bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size
126         }
127         msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4
128         msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5
129
130         int message_count = 5 + m_bone_position.size();
131
132         for (const auto &id : getAttachmentChildIds()) {
133                 if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
134                         message_count++;
135                         msg_os << serializeLongString(obj->generateUpdateInfantCommand(
136                                 id, protocol_version));
137                 }
138         }
139
140         writeU8(os, message_count);
141         std::string serialized = msg_os.str();
142         os.write(serialized.c_str(), serialized.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                 m_messages_out.emplace(getId(), true, str);
227                 m_env->getScriptIface()->player_event(this, "properties_changed");
228         }
229
230         // If attached, check that our parent is still there. If it isn't, detach.
231         if (m_attachment_parent_id && !isAttached()) {
232                 // This is handled when objects are removed from the map
233                 warningstream << "PlayerSAO::step() id=" << m_id <<
234                         " is attached to nonexistent parent. This is a bug." << std::endl;
235                 clearParentAttachment();
236                 setBasePosition(m_last_good_position);
237                 m_env->getGameDef()->SendMovePlayer(m_peer_id);
238         }
239
240         //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
241
242         // Set lag pool maximums based on estimated lag
243         const float LAG_POOL_MIN = 5.0f;
244         float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
245         if(lag_pool_max < LAG_POOL_MIN)
246                 lag_pool_max = LAG_POOL_MIN;
247         m_dig_pool.setMax(lag_pool_max);
248         m_move_pool.setMax(lag_pool_max);
249
250         // Increment cheat prevention timers
251         m_dig_pool.add(dtime);
252         m_move_pool.add(dtime);
253         m_time_from_last_teleport += dtime;
254         m_time_from_last_punch += dtime;
255         m_nocheat_dig_time += dtime;
256         m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f);
257
258         // Each frame, parent position is copied if the object is attached,
259         // otherwise it's calculated normally.
260         // If the object gets detached this comes into effect automatically from
261         // the last known origin.
262         if (isAttached()) {
263                 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
264                 m_last_good_position = pos;
265                 setBasePosition(pos);
266         }
267
268         if (!send_recommended)
269                 return;
270
271         if (m_position_not_sent) {
272                 m_position_not_sent = false;
273                 float update_interval = m_env->getSendRecommendedInterval();
274                 v3f pos;
275                 // When attached, the position is only sent to clients where the
276                 // parent isn't known
277                 if (isAttached())
278                         pos = m_last_good_position;
279                 else
280                         pos = m_base_position;
281
282                 std::string str = generateUpdatePositionCommand(
283                         pos,
284                         v3f(0.0f, 0.0f, 0.0f),
285                         v3f(0.0f, 0.0f, 0.0f),
286                         m_rotation,
287                         true,
288                         false,
289                         update_interval
290                 );
291                 // create message and add to list
292                 m_messages_out.emplace(getId(), false, str);
293         }
294
295         if (!m_physics_override_sent) {
296                 m_physics_override_sent = true;
297                 // create message and add to list
298                 m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
299         }
300
301         sendOutdatedData();
302 }
303
304 std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
305 {
306         std::ostringstream os(std::ios::binary);
307         // command
308         writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
309         // parameters
310         writeF32(os, m_physics_override_speed);
311         writeF32(os, m_physics_override_jump);
312         writeF32(os, m_physics_override_gravity);
313         // these are sent inverted so we get true when the server sends nothing
314         writeU8(os, !m_physics_override_sneak);
315         writeU8(os, !m_physics_override_sneak_glitch);
316         writeU8(os, !m_physics_override_new_move);
317         return os.str();
318 }
319
320 void PlayerSAO::setBasePosition(const v3f &position)
321 {
322         if (m_player && position != m_base_position)
323                 m_player->setDirty(true);
324
325         // This needs to be ran for attachments too
326         ServerActiveObject::setBasePosition(position);
327
328         // Updating is not wanted/required for player migration
329         if (m_env) {
330                 m_position_not_sent = true;
331         }
332 }
333
334 void PlayerSAO::setPos(const v3f &pos)
335 {
336         if(isAttached())
337                 return;
338
339         // Send mapblock of target location
340         v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
341         m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
342
343         setBasePosition(pos);
344         // Movement caused by this command is always valid
345         m_last_good_position = pos;
346         m_move_pool.empty();
347         m_time_from_last_teleport = 0.0;
348         m_env->getGameDef()->SendMovePlayer(m_peer_id);
349 }
350
351 void PlayerSAO::moveTo(v3f pos, bool continuous)
352 {
353         if(isAttached())
354                 return;
355
356         setBasePosition(pos);
357         // Movement caused by this command is always valid
358         m_last_good_position = pos;
359         m_move_pool.empty();
360         m_time_from_last_teleport = 0.0;
361         m_env->getGameDef()->SendMovePlayer(m_peer_id);
362 }
363
364 void PlayerSAO::setPlayerYaw(const float yaw)
365 {
366         v3f rotation(0, yaw, 0);
367         if (m_player && yaw != m_rotation.Y)
368                 m_player->setDirty(true);
369
370         // Set player model yaw, not look view
371         UnitSAO::setRotation(rotation);
372 }
373
374 void PlayerSAO::setFov(const float fov)
375 {
376         if (m_player && fov != m_fov)
377                 m_player->setDirty(true);
378
379         m_fov = fov;
380 }
381
382 void PlayerSAO::setWantedRange(const s16 range)
383 {
384         if (m_player && range != m_wanted_range)
385                 m_player->setDirty(true);
386
387         m_wanted_range = range;
388 }
389
390 void PlayerSAO::setPlayerYawAndSend(const float yaw)
391 {
392         setPlayerYaw(yaw);
393         m_env->getGameDef()->SendMovePlayer(m_peer_id);
394 }
395
396 void PlayerSAO::setLookPitch(const float pitch)
397 {
398         if (m_player && pitch != m_pitch)
399                 m_player->setDirty(true);
400
401         m_pitch = pitch;
402 }
403
404 void PlayerSAO::setLookPitchAndSend(const float pitch)
405 {
406         setLookPitch(pitch);
407         m_env->getGameDef()->SendMovePlayer(m_peer_id);
408 }
409
410 u16 PlayerSAO::punch(v3f dir,
411         const ToolCapabilities *toolcap,
412         ServerActiveObject *puncher,
413         float time_from_last_punch)
414 {
415         if (!toolcap)
416                 return 0;
417
418         FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
419
420         // No effect if PvP disabled or if immortal
421         if (isImmortal() || !g_settings->getBool("enable_pvp")) {
422                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
423                         // create message and add to list
424                         sendPunchCommand();
425                         return 0;
426                 }
427         }
428
429         s32 old_hp = getHP();
430         HitParams hitparams = getHitParams(m_armor_groups, toolcap,
431                         time_from_last_punch);
432
433         PlayerSAO *playersao = m_player->getPlayerSAO();
434
435         bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
436                                 puncher, time_from_last_punch, toolcap, dir,
437                                 hitparams.hp);
438
439         if (!damage_handled) {
440                 setHP((s32)getHP() - (s32)hitparams.hp,
441                                 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
442         } else { // override client prediction
443                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
444                         // create message and add to list
445                         sendPunchCommand();
446                 }
447         }
448
449         actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
450                 ", hp=" << puncher->getHP() << ") punched " <<
451                 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
452                 "), damage=" << (old_hp - (s32)getHP()) <<
453                 (damage_handled ? " (handled by Lua)" : "") << std::endl;
454
455         return hitparams.wear;
456 }
457
458 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
459 {
460         s32 oldhp = m_hp;
461
462         hp = rangelim(hp, 0, m_prop.hp_max);
463
464         if (oldhp != hp) {
465                 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
466                 if (hp_change == 0)
467                         return;
468
469                 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
470         }
471
472         if (hp < oldhp && isImmortal())
473                 return;
474
475         m_hp = hp;
476
477         // Update properties on death
478         if ((hp == 0) != (oldhp == 0))
479                 m_properties_sent = false;
480 }
481
482 void PlayerSAO::setBreath(const u16 breath, bool send)
483 {
484         if (m_player && breath != m_breath)
485                 m_player->setDirty(true);
486
487         m_breath = rangelim(breath, 0, m_prop.breath_max);
488
489         if (send)
490                 m_env->getGameDef()->SendPlayerBreath(this);
491 }
492
493 Inventory *PlayerSAO::getInventory() const
494 {
495         return m_player ? &m_player->inventory : nullptr;
496 }
497
498 InventoryLocation PlayerSAO::getInventoryLocation() const
499 {
500         InventoryLocation loc;
501         loc.setPlayer(m_player->getName());
502         return loc;
503 }
504
505 u16 PlayerSAO::getWieldIndex() const
506 {
507         return m_player->getWieldIndex();
508 }
509
510 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
511 {
512         return m_player->getWieldedItem(selected, hand);
513 }
514
515 bool PlayerSAO::setWieldedItem(const ItemStack &item)
516 {
517         InventoryList *mlist = m_player->inventory.getList(getWieldList());
518         if (mlist) {
519                 mlist->changeItem(m_player->getWieldIndex(), item);
520                 return true;
521         }
522         return false;
523 }
524
525 void PlayerSAO::disconnected()
526 {
527         m_peer_id = PEER_ID_INEXISTENT;
528         m_pending_removal = true;
529 }
530
531 void PlayerSAO::unlinkPlayerSessionAndSave()
532 {
533         assert(m_player->getPlayerSAO() == this);
534         m_player->setPeerId(PEER_ID_INEXISTENT);
535         m_env->savePlayer(m_player);
536         m_player->setPlayerSAO(NULL);
537         m_env->removePlayer(m_player);
538 }
539
540 std::string PlayerSAO::getPropertyPacket()
541 {
542         m_prop.is_visible = (true);
543         return generateSetPropertiesCommand(m_prop);
544 }
545
546 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
547 {
548         if (m_max_speed_override_time == 0.0f)
549                 m_max_speed_override = vel;
550         else
551                 m_max_speed_override += vel;
552         if (m_player) {
553                 float accel = MYMIN(m_player->movement_acceleration_default,
554                                 m_player->movement_acceleration_air);
555                 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
556         }
557 }
558
559 bool PlayerSAO::checkMovementCheat()
560 {
561         if (m_is_singleplayer ||
562                         g_settings->getBool("disable_anticheat")) {
563                 m_last_good_position = m_base_position;
564                 return false;
565         }
566         if (UnitSAO *parent = dynamic_cast<UnitSAO *>(getParent())) {
567                 v3f attachment_pos;
568                 {
569                         int parent_id;
570                         std::string bone;
571                         v3f attachment_rot;
572                         getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot);
573                 }
574
575                 v3f parent_pos = parent->getBasePosition();
576                 f32 diff = m_base_position.getDistanceFromSQ(parent_pos) - attachment_pos.getLengthSQ();
577                 const f32 maxdiff = 4.0f * BS; // fair trade-off value for various latencies
578
579                 if (diff > maxdiff * maxdiff) {
580                         setBasePosition(parent_pos);
581                         actionstream << "Server: " << m_player->getName()
582                                         << " moved away from parent; diff=" << sqrtf(diff) / BS
583                                         << " resetting position." << std::endl;
584                         return true;
585                 }
586                 // Player movement is locked to the entity. Skip further checks
587                 return false;
588         }
589
590         bool cheated = false;
591         /*
592                 Check player movements
593
594                 NOTE: Actually the server should handle player physics like the
595                 client does and compare player's position to what is calculated
596                 on our side. This is required when eg. players fly due to an
597                 explosion. Altough a node-based alternative might be possible
598                 too, and much more lightweight.
599         */
600
601         float override_max_H, override_max_V;
602         if (m_max_speed_override_time > 0.0f) {
603                 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
604                 override_max_V = fabs(m_max_speed_override.Y);
605         } else {
606                 override_max_H = override_max_V = 0.0f;
607         }
608
609         float player_max_walk = 0; // horizontal movement
610         float player_max_jump = 0; // vertical upwards movement
611
612         if (m_privs.count("fast") != 0)
613                 player_max_walk = m_player->movement_speed_fast; // Fast speed
614         else
615                 player_max_walk = m_player->movement_speed_walk; // Normal speed
616         player_max_walk *= m_physics_override_speed;
617         player_max_walk = MYMAX(player_max_walk, override_max_H);
618
619         player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
620         // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
621         //        until this can be verified correctly, tolerate higher jumping speeds
622         player_max_jump *= 2.0;
623         player_max_jump = MYMAX(player_max_jump, override_max_V);
624
625         // Don't divide by zero!
626         if (player_max_walk < 0.0001f)
627                 player_max_walk = 0.0001f;
628         if (player_max_jump < 0.0001f)
629                 player_max_jump = 0.0001f;
630
631         v3f diff = (m_base_position - m_last_good_position);
632         float d_vert = diff.Y;
633         diff.Y = 0;
634         float d_horiz = diff.getLength();
635         float required_time = d_horiz / player_max_walk;
636
637         // FIXME: Checking downwards movement is not easily possible currently,
638         //        the server could calculate speed differences to examine the gravity
639         if (d_vert > 0) {
640                 // In certain cases (water, ladders) walking speed is applied vertically
641                 float s = MYMAX(player_max_jump, player_max_walk);
642                 required_time = MYMAX(required_time, d_vert / s);
643         }
644
645         if (m_move_pool.grab(required_time)) {
646                 m_last_good_position = m_base_position;
647         } else {
648                 const float LAG_POOL_MIN = 5.0;
649                 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
650                 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
651                 if (m_time_from_last_teleport > lag_pool_max) {
652                         actionstream << "Server: " << m_player->getName()
653                                         << " moved too fast: V=" << d_vert << ", H=" << d_horiz
654                                         << "; resetting position." << std::endl;
655                         cheated = true;
656                 }
657                 setBasePosition(m_last_good_position);
658         }
659         return cheated;
660 }
661
662 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
663 {
664         //update collision box
665         toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
666         toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
667
668         toset->MinEdge += m_base_position;
669         toset->MaxEdge += m_base_position;
670         return true;
671 }
672
673 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
674 {
675         if (!m_prop.is_visible || !m_prop.pointable) {
676                 return false;
677         }
678
679         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
680         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
681
682         return true;
683 }
684
685 float PlayerSAO::getZoomFOV() const
686 {
687         return m_prop.zoom_fov;
688 }