]> git.lizzy.rs Git - minetest.git/blob - src/server/player_sao.cpp
Move f1000 sanitizing to the places that still use this type
[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         std::ostringstream os(std::ios::binary);
309         // command
310         writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
311         // parameters
312         writeF32(os, m_physics_override_speed);
313         writeF32(os, m_physics_override_jump);
314         writeF32(os, m_physics_override_gravity);
315         // these are sent inverted so we get true when the server sends nothing
316         writeU8(os, !m_physics_override_sneak);
317         writeU8(os, !m_physics_override_sneak_glitch);
318         writeU8(os, !m_physics_override_new_move);
319         return os.str();
320 }
321
322 void PlayerSAO::setBasePosition(v3f position)
323 {
324         if (m_player && position != m_base_position)
325                 m_player->setDirty(true);
326
327         // This needs to be ran for attachments too
328         ServerActiveObject::setBasePosition(position);
329
330         // Updating is not wanted/required for player migration
331         if (m_env) {
332                 m_position_not_sent = true;
333         }
334 }
335
336 void PlayerSAO::setPos(const v3f &pos)
337 {
338         if(isAttached())
339                 return;
340
341         // Send mapblock of target location
342         v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
343         m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
344
345         setBasePosition(pos);
346         // Movement caused by this command is always valid
347         m_last_good_position = getBasePosition();
348         m_move_pool.empty();
349         m_time_from_last_teleport = 0.0;
350         m_env->getGameDef()->SendMovePlayer(m_peer_id);
351 }
352
353 void PlayerSAO::moveTo(v3f pos, bool continuous)
354 {
355         if(isAttached())
356                 return;
357
358         setBasePosition(pos);
359         // Movement caused by this command is always valid
360         m_last_good_position = getBasePosition();
361         m_move_pool.empty();
362         m_time_from_last_teleport = 0.0;
363         m_env->getGameDef()->SendMovePlayer(m_peer_id);
364 }
365
366 void PlayerSAO::setPlayerYaw(const float yaw)
367 {
368         v3f rotation(0, yaw, 0);
369         if (m_player && yaw != m_rotation.Y)
370                 m_player->setDirty(true);
371
372         // Set player model yaw, not look view
373         UnitSAO::setRotation(rotation);
374 }
375
376 void PlayerSAO::setFov(const float fov)
377 {
378         if (m_player && fov != m_fov)
379                 m_player->setDirty(true);
380
381         m_fov = fov;
382 }
383
384 void PlayerSAO::setWantedRange(const s16 range)
385 {
386         if (m_player && range != m_wanted_range)
387                 m_player->setDirty(true);
388
389         m_wanted_range = range;
390 }
391
392 void PlayerSAO::setPlayerYawAndSend(const float yaw)
393 {
394         setPlayerYaw(yaw);
395         m_env->getGameDef()->SendMovePlayer(m_peer_id);
396 }
397
398 void PlayerSAO::setLookPitch(const float pitch)
399 {
400         if (m_player && pitch != m_pitch)
401                 m_player->setDirty(true);
402
403         m_pitch = pitch;
404 }
405
406 void PlayerSAO::setLookPitchAndSend(const float pitch)
407 {
408         setLookPitch(pitch);
409         m_env->getGameDef()->SendMovePlayer(m_peer_id);
410 }
411
412 u32 PlayerSAO::punch(v3f dir,
413         const ToolCapabilities *toolcap,
414         ServerActiveObject *puncher,
415         float time_from_last_punch,
416         u16 initial_wear)
417 {
418         if (!toolcap)
419                 return 0;
420
421         FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
422
423         // No effect if PvP disabled or if immortal
424         if (isImmortal() || !g_settings->getBool("enable_pvp")) {
425                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
426                         // create message and add to list
427                         sendPunchCommand();
428                         return 0;
429                 }
430         }
431
432         s32 old_hp = getHP();
433         HitParams hitparams = getHitParams(m_armor_groups, toolcap,
434                         time_from_last_punch, initial_wear);
435
436         PlayerSAO *playersao = m_player->getPlayerSAO();
437
438         bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
439                                 puncher, time_from_last_punch, toolcap, dir,
440                                 hitparams.hp);
441
442         if (!damage_handled) {
443                 setHP((s32)getHP() - (s32)hitparams.hp,
444                                 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
445         } else { // override client prediction
446                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
447                         // create message and add to list
448                         sendPunchCommand();
449                 }
450         }
451
452         actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
453                 ", hp=" << puncher->getHP() << ") punched " <<
454                 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
455                 "), damage=" << (old_hp - (s32)getHP()) <<
456                 (damage_handled ? " (handled by Lua)" : "") << std::endl;
457
458         return hitparams.wear;
459 }
460
461 void PlayerSAO::rightClick(ServerActiveObject *clicker)
462 {
463         m_env->getScriptIface()->on_rightclickplayer(this, clicker);
464 }
465
466 void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool from_client)
467 {
468         target_hp = rangelim(target_hp, 0, U16_MAX);
469
470         if (target_hp == m_hp)
471                 return; // Nothing to do
472
473         s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, target_hp - (s32)m_hp, reason);
474
475         s32 hp = (s32)m_hp + std::min(hp_change, U16_MAX); // Protection against s32 overflow
476         hp = rangelim(hp, 0, U16_MAX);
477
478         if (hp > m_prop.hp_max)
479                 hp = m_prop.hp_max;
480
481         if (hp < m_hp && isImmortal())
482                 hp = m_hp; // Do not allow immortal players to be damaged
483
484         // Update properties on death
485         if ((hp == 0) != (m_hp == 0))
486                 m_properties_sent = false;
487
488         if (hp != m_hp) {
489                 m_hp = hp;
490                 m_env->getGameDef()->HandlePlayerHPChange(this, reason);
491         } else if (from_client)
492                 m_env->getGameDef()->SendPlayerHP(this, true);
493 }
494
495 void PlayerSAO::setBreath(const u16 breath, bool send)
496 {
497         if (m_player && breath != m_breath)
498                 m_player->setDirty(true);
499
500         m_breath = rangelim(breath, 0, m_prop.breath_max);
501
502         if (send)
503                 m_env->getGameDef()->SendPlayerBreath(this);
504 }
505
506 Inventory *PlayerSAO::getInventory() const
507 {
508         return m_player ? &m_player->inventory : nullptr;
509 }
510
511 InventoryLocation PlayerSAO::getInventoryLocation() const
512 {
513         InventoryLocation loc;
514         loc.setPlayer(m_player->getName());
515         return loc;
516 }
517
518 u16 PlayerSAO::getWieldIndex() const
519 {
520         return m_player->getWieldIndex();
521 }
522
523 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
524 {
525         return m_player->getWieldedItem(selected, hand);
526 }
527
528 bool PlayerSAO::setWieldedItem(const ItemStack &item)
529 {
530         InventoryList *mlist = m_player->inventory.getList(getWieldList());
531         if (mlist) {
532                 mlist->changeItem(m_player->getWieldIndex(), item);
533                 return true;
534         }
535         return false;
536 }
537
538 void PlayerSAO::disconnected()
539 {
540         m_peer_id = PEER_ID_INEXISTENT;
541         markForRemoval();
542 }
543
544 void PlayerSAO::unlinkPlayerSessionAndSave()
545 {
546         assert(m_player->getPlayerSAO() == this);
547         m_player->setPeerId(PEER_ID_INEXISTENT);
548         m_env->savePlayer(m_player);
549         m_player->setPlayerSAO(NULL);
550         m_env->removePlayer(m_player);
551 }
552
553 std::string PlayerSAO::getPropertyPacket()
554 {
555         m_prop.is_visible = (true);
556         return generateSetPropertiesCommand(m_prop);
557 }
558
559 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
560 {
561         if (m_max_speed_override_time == 0.0f)
562                 m_max_speed_override = vel;
563         else
564                 m_max_speed_override += vel;
565         if (m_player) {
566                 float accel = MYMIN(m_player->movement_acceleration_default,
567                                 m_player->movement_acceleration_air);
568                 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
569         }
570 }
571
572 bool PlayerSAO::checkMovementCheat()
573 {
574         if (m_is_singleplayer ||
575                         isAttached() ||
576                         g_settings->getBool("disable_anticheat")) {
577                 m_last_good_position = m_base_position;
578                 return false;
579         }
580
581         bool cheated = false;
582         /*
583                 Check player movements
584
585                 NOTE: Actually the server should handle player physics like the
586                 client does and compare player's position to what is calculated
587                 on our side. This is required when eg. players fly due to an
588                 explosion. Altough a node-based alternative might be possible
589                 too, and much more lightweight.
590         */
591
592         float override_max_H, override_max_V;
593         if (m_max_speed_override_time > 0.0f) {
594                 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
595                 override_max_V = fabs(m_max_speed_override.Y);
596         } else {
597                 override_max_H = override_max_V = 0.0f;
598         }
599
600         float player_max_walk = 0; // horizontal movement
601         float player_max_jump = 0; // vertical upwards movement
602
603         if (m_privs.count("fast") != 0)
604                 player_max_walk = m_player->movement_speed_fast; // Fast speed
605         else
606                 player_max_walk = m_player->movement_speed_walk; // Normal speed
607         player_max_walk *= m_physics_override_speed;
608         player_max_walk = MYMAX(player_max_walk, override_max_H);
609
610         player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
611         // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
612         //        until this can be verified correctly, tolerate higher jumping speeds
613         player_max_jump *= 2.0;
614         player_max_jump = MYMAX(player_max_jump, override_max_V);
615
616         // Don't divide by zero!
617         if (player_max_walk < 0.0001f)
618                 player_max_walk = 0.0001f;
619         if (player_max_jump < 0.0001f)
620                 player_max_jump = 0.0001f;
621
622         v3f diff = (m_base_position - m_last_good_position);
623         float d_vert = diff.Y;
624         diff.Y = 0;
625         float d_horiz = diff.getLength();
626         float required_time = d_horiz / player_max_walk;
627
628         // FIXME: Checking downwards movement is not easily possible currently,
629         //        the server could calculate speed differences to examine the gravity
630         if (d_vert > 0) {
631                 // In certain cases (water, ladders) walking speed is applied vertically
632                 float s = MYMAX(player_max_jump, player_max_walk);
633                 required_time = MYMAX(required_time, d_vert / s);
634         }
635
636         if (m_move_pool.grab(required_time)) {
637                 m_last_good_position = m_base_position;
638         } else {
639                 const float LAG_POOL_MIN = 5.0;
640                 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
641                 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
642                 if (m_time_from_last_teleport > lag_pool_max) {
643                         actionstream << "Server: " << m_player->getName()
644                                         << " moved too fast: V=" << d_vert << ", H=" << d_horiz
645                                         << "; resetting position." << std::endl;
646                         cheated = true;
647                 }
648                 setBasePosition(m_last_good_position);
649         }
650         return cheated;
651 }
652
653 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
654 {
655         //update collision box
656         toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
657         toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
658
659         toset->MinEdge += m_base_position;
660         toset->MaxEdge += m_base_position;
661         return true;
662 }
663
664 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
665 {
666         if (!m_prop.is_visible || !m_prop.pointable) {
667                 return false;
668         }
669
670         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
671         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
672
673         return true;
674 }
675
676 float PlayerSAO::getZoomFOV() const
677 {
678         return m_prop.zoom_fov;
679 }