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