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