]> git.lizzy.rs Git - minetest.git/blob - src/server/player_sao.cpp
Remove redundant on_dieplayer calls
[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(const 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 = pos;
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 = pos;
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 u16 PlayerSAO::punch(v3f dir,
413         const ToolCapabilities *toolcap,
414         ServerActiveObject *puncher,
415         float time_from_last_punch)
416 {
417         if (!toolcap)
418                 return 0;
419
420         FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
421
422         // No effect if PvP disabled or if immortal
423         if (isImmortal() || !g_settings->getBool("enable_pvp")) {
424                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
425                         // create message and add to list
426                         sendPunchCommand();
427                         return 0;
428                 }
429         }
430
431         s32 old_hp = getHP();
432         HitParams hitparams = getHitParams(m_armor_groups, toolcap,
433                         time_from_last_punch);
434
435         PlayerSAO *playersao = m_player->getPlayerSAO();
436
437         bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
438                                 puncher, time_from_last_punch, toolcap, dir,
439                                 hitparams.hp);
440
441         if (!damage_handled) {
442                 setHP((s32)getHP() - (s32)hitparams.hp,
443                                 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
444         } else { // override client prediction
445                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
446                         // create message and add to list
447                         sendPunchCommand();
448                 }
449         }
450
451         actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
452                 ", hp=" << puncher->getHP() << ") punched " <<
453                 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
454                 "), damage=" << (old_hp - (s32)getHP()) <<
455                 (damage_handled ? " (handled by Lua)" : "") << std::endl;
456
457         return hitparams.wear;
458 }
459
460 void PlayerSAO::rightClick(ServerActiveObject *clicker)
461 {
462         m_env->getScriptIface()->on_rightclickplayer(this, clicker);
463 }
464
465 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
466 {
467         if (hp == (s32)m_hp)
468                 return; // Nothing to do
469
470         if (m_hp <= 0 && hp < (s32)m_hp)
471                 return; // Cannot take more damage
472
473         {
474                 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - m_hp, reason);
475                 if (hp_change == 0)
476                         return;
477
478                 hp = m_hp + hp_change;
479         }
480
481         s32 oldhp = m_hp;
482         hp = rangelim(hp, 0, m_prop.hp_max);
483
484         if (hp < oldhp && isImmortal())
485                 return; // Do not allow immortal players to be damaged
486
487         m_hp = hp;
488
489         // Update properties on death
490         if ((hp == 0) != (oldhp == 0))
491                 m_properties_sent = false;
492
493         m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
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 }