3ea3536e25b54d8784d263a3a7edd23fde5a05d9
[oweals/minetest.git] / src / server / player_sao.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2013-2020 Minetest core developers & community
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "player_sao.h"
22 #include "nodedef.h"
23 #include "remoteplayer.h"
24 #include "scripting_server.h"
25 #include "server.h"
26 #include "serverenvironment.h"
27
28 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
29                 bool is_singleplayer):
30         UnitSAO(env_, v3f(0,0,0)),
31         m_player(player_),
32         m_peer_id(peer_id_),
33         m_is_singleplayer(is_singleplayer)
34 {
35         SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT);
36
37         m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
38         m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
39         m_prop.physical = false;
40         m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
41         m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
42         m_prop.pointable = true;
43         // Start of default appearance, this should be overwritten by Lua
44         m_prop.visual = "upright_sprite";
45         m_prop.visual_size = v3f(1, 2, 1);
46         m_prop.textures.clear();
47         m_prop.textures.emplace_back("player.png");
48         m_prop.textures.emplace_back("player_back.png");
49         m_prop.colors.clear();
50         m_prop.colors.emplace_back(255, 255, 255, 255);
51         m_prop.spritediv = v2s16(1,1);
52         m_prop.eye_height = 1.625f;
53         // End of default appearance
54         m_prop.is_visible = true;
55         m_prop.backface_culling = false;
56         m_prop.makes_footstep_sound = true;
57         m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
58         m_hp = m_prop.hp_max;
59         m_breath = m_prop.breath_max;
60         // Disable zoom in survival mode using a value of 0
61         m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
62
63         if (!g_settings->getBool("enable_damage"))
64                 m_armor_groups["immortal"] = 1;
65 }
66
67 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
68 {
69         assert(player);
70         m_player = player;
71         m_privs = privs;
72 }
73
74 v3f PlayerSAO::getEyeOffset() const
75 {
76         return v3f(0, BS * m_prop.eye_height, 0);
77 }
78
79 std::string PlayerSAO::getDescription()
80 {
81         return std::string("player ") + m_player->getName();
82 }
83
84 // Called after id has been set and has been inserted in environment
85 void PlayerSAO::addedToEnvironment(u32 dtime_s)
86 {
87         ServerActiveObject::addedToEnvironment(dtime_s);
88         ServerActiveObject::setBasePosition(m_base_position);
89         m_player->setPlayerSAO(this);
90         m_player->setPeerId(m_peer_id);
91         m_last_good_position = m_base_position;
92 }
93
94 // Called before removing from environment
95 void PlayerSAO::removingFromEnvironment()
96 {
97         ServerActiveObject::removingFromEnvironment();
98         if (m_player->getPlayerSAO() == this) {
99                 unlinkPlayerSessionAndSave();
100                 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
101                         m_env->deleteParticleSpawner(attached_particle_spawner, false);
102                 }
103         }
104 }
105
106 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
107 {
108         std::ostringstream os(std::ios::binary);
109
110         // Protocol >= 15
111         writeU8(os, 1); // version
112         os << serializeString(m_player->getName()); // name
113         writeU8(os, 1); // is_player
114         writeS16(os, getId()); // id
115         writeV3F32(os, m_base_position);
116         writeV3F32(os, m_rotation);
117         writeU16(os, getHP());
118
119         std::ostringstream msg_os(std::ios::binary);
120         msg_os << serializeLongString(getPropertyPacket()); // message 1
121         msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2
122         msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3
123         for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
124                         ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
125                 msg_os << serializeLongString(generateUpdateBonePositionCommand((*ii).first,
126                         (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
127         }
128         msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4
129         msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5
130         // (AO_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
131         msg_os << serializeLongString(generateUpdateNametagAttributesCommand(m_prop.nametag_color)); // 6
132         int message_count = 6 + m_bone_position.size();
133         for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
134                         ii != m_attachment_child_ids.end(); ++ii) {
135                 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
136                         message_count++;
137                         msg_os << serializeLongString(obj->generateUpdateInfantCommand(*ii, protocol_version));
138                 }
139         }
140
141         writeU8(os, message_count);
142         os.write(msg_os.str().c_str(), msg_os.str().size());
143
144         // return result
145         return os.str();
146 }
147
148 void PlayerSAO::getStaticData(std::string * result) const
149 {
150         FATAL_ERROR("Obsolete function");
151 }
152
153 void PlayerSAO::step(float dtime, bool send_recommended)
154 {
155         if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
156                 // Get nose/mouth position, approximate with eye position
157                 v3s16 p = floatToInt(getEyePosition(), BS);
158                 MapNode n = m_env->getMap().getNode(p);
159                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
160                 // If node generates drown
161                 if (c.drowning > 0 && m_hp > 0) {
162                         if (m_breath > 0)
163                                 setBreath(m_breath - 1);
164
165                         // No more breath, damage player
166                         if (m_breath == 0) {
167                                 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
168                                 setHP(m_hp - c.drowning, reason);
169                                 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
170                         }
171                 }
172         }
173
174         if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
175                 // Get nose/mouth position, approximate with eye position
176                 v3s16 p = floatToInt(getEyePosition(), BS);
177                 MapNode n = m_env->getMap().getNode(p);
178                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
179                 // If player is alive & not drowning & not in ignore & not immortal, breathe
180                 if (m_breath < m_prop.breath_max && c.drowning == 0 &&
181                                 n.getContent() != CONTENT_IGNORE && m_hp > 0)
182                         setBreath(m_breath + 1);
183         }
184
185         if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
186                 u32 damage_per_second = 0;
187                 std::string nodename;
188                 // Lowest and highest damage points are 0.1 within collisionbox
189                 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
190
191                 // Sequence of damage points, starting 0.1 above feet and progressing
192                 // upwards in 1 node intervals, stopping below top damage point.
193                 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
194                         v3s16 p = floatToInt(m_base_position +
195                                 v3f(0.0f, dam_height * BS, 0.0f), BS);
196                         MapNode n = m_env->getMap().getNode(p);
197                         const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
198                         if (c.damage_per_second > damage_per_second) {
199                                 damage_per_second = c.damage_per_second;
200                                 nodename = c.name;
201                         }
202                 }
203
204                 // Top damage point
205                 v3s16 ptop = floatToInt(m_base_position +
206                         v3f(0.0f, dam_top * BS, 0.0f), BS);
207                 MapNode ntop = m_env->getMap().getNode(ptop);
208                 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
209                 if (c.damage_per_second > damage_per_second) {
210                         damage_per_second = c.damage_per_second;
211                         nodename = c.name;
212                 }
213
214                 if (damage_per_second != 0 && m_hp > 0) {
215                         s32 newhp = (s32)m_hp - (s32)damage_per_second;
216                         PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
217                         setHP(newhp, reason);
218                         m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
219                 }
220         }
221
222         if (!m_properties_sent) {
223                 m_properties_sent = true;
224                 std::string str = getPropertyPacket();
225                 // create message and add to list
226                 m_messages_out.emplace(getId(), true, str);
227                 m_env->getScriptIface()->player_event(this, "properties_changed");
228         }
229
230         // If attached, check that our parent is still there. If it isn't, detach.
231         if (m_attachment_parent_id && !isAttached()) {
232                 m_attachment_parent_id = 0;
233                 m_attachment_bone = "";
234                 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
235                 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
236                 setBasePosition(m_last_good_position);
237                 m_env->getGameDef()->SendMovePlayer(m_peer_id);
238         }
239
240         //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
241
242         // Set lag pool maximums based on estimated lag
243         const float LAG_POOL_MIN = 5.0f;
244         float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
245         if(lag_pool_max < LAG_POOL_MIN)
246                 lag_pool_max = LAG_POOL_MIN;
247         m_dig_pool.setMax(lag_pool_max);
248         m_move_pool.setMax(lag_pool_max);
249
250         // Increment cheat prevention timers
251         m_dig_pool.add(dtime);
252         m_move_pool.add(dtime);
253         m_time_from_last_teleport += dtime;
254         m_time_from_last_punch += dtime;
255         m_nocheat_dig_time += dtime;
256         m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f);
257
258         // Each frame, parent position is copied if the object is attached,
259         // otherwise it's calculated normally.
260         // If the object gets detached this comes into effect automatically from
261         // the last known origin.
262         if (isAttached()) {
263                 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
264                 m_last_good_position = pos;
265                 setBasePosition(pos);
266         }
267
268         if (!send_recommended)
269                 return;
270
271         if (m_position_not_sent) {
272                 m_position_not_sent = false;
273                 float update_interval = m_env->getSendRecommendedInterval();
274                 v3f pos;
275                 // When attached, the position is only sent to clients where the
276                 // parent isn't known
277                 if (isAttached())
278                         pos = m_last_good_position;
279                 else
280                         pos = m_base_position;
281
282                 std::string str = generateUpdatePositionCommand(
283                         pos,
284                         v3f(0.0f, 0.0f, 0.0f),
285                         v3f(0.0f, 0.0f, 0.0f),
286                         m_rotation,
287                         true,
288                         false,
289                         update_interval
290                 );
291                 // create message and add to list
292                 m_messages_out.emplace(getId(), false, str);
293         }
294
295         if (!m_armor_groups_sent) {
296                 m_armor_groups_sent = true;
297                 // create message and add to list
298                 m_messages_out.emplace(getId(), true, generateUpdateArmorGroupsCommand());
299         }
300
301         if (!m_physics_override_sent) {
302                 m_physics_override_sent = true;
303                 // create message and add to list
304                 m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
305         }
306
307         if (!m_animation_sent) {
308                 m_animation_sent = true;
309                 // create message and add to list
310                 m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand());
311         }
312
313         if (!m_bone_position_sent) {
314                 m_bone_position_sent = true;
315                 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
316                                 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
317                         std::string str = generateUpdateBonePositionCommand((*ii).first,
318                                         (*ii).second.X, (*ii).second.Y);
319                         // create message and add to list
320                         m_messages_out.emplace(getId(), true, str);
321                 }
322         }
323
324         if (!m_attachment_sent) {
325                 m_attachment_sent = true;
326                 // create message and add to list
327                 m_messages_out.emplace(getId(), true, generateUpdateAttachmentCommand());
328         }
329 }
330
331 std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
332 {
333         std::ostringstream os(std::ios::binary);
334         // command
335         writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
336         // parameters
337         writeF32(os, m_physics_override_speed);
338         writeF32(os, m_physics_override_jump);
339         writeF32(os, m_physics_override_gravity);
340         // these are sent inverted so we get true when the server sends nothing
341         writeU8(os, !m_physics_override_sneak);
342         writeU8(os, !m_physics_override_sneak_glitch);
343         writeU8(os, !m_physics_override_new_move);
344         return os.str();
345 }
346
347 void PlayerSAO::setBasePosition(const v3f &position)
348 {
349         if (m_player && position != m_base_position)
350                 m_player->setDirty(true);
351
352         // This needs to be ran for attachments too
353         ServerActiveObject::setBasePosition(position);
354
355         // Updating is not wanted/required for player migration
356         if (m_env) {
357                 m_position_not_sent = true;
358         }
359 }
360
361 void PlayerSAO::setPos(const v3f &pos)
362 {
363         if(isAttached())
364                 return;
365
366         // Send mapblock of target location
367         v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
368         m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
369
370         setBasePosition(pos);
371         // Movement caused by this command is always valid
372         m_last_good_position = pos;
373         m_move_pool.empty();
374         m_time_from_last_teleport = 0.0;
375         m_env->getGameDef()->SendMovePlayer(m_peer_id);
376 }
377
378 void PlayerSAO::moveTo(v3f pos, bool continuous)
379 {
380         if(isAttached())
381                 return;
382
383         setBasePosition(pos);
384         // Movement caused by this command is always valid
385         m_last_good_position = pos;
386         m_move_pool.empty();
387         m_time_from_last_teleport = 0.0;
388         m_env->getGameDef()->SendMovePlayer(m_peer_id);
389 }
390
391 void PlayerSAO::setPlayerYaw(const float yaw)
392 {
393         v3f rotation(0, yaw, 0);
394         if (m_player && yaw != m_rotation.Y)
395                 m_player->setDirty(true);
396
397         // Set player model yaw, not look view
398         UnitSAO::setRotation(rotation);
399 }
400
401 void PlayerSAO::setFov(const float fov)
402 {
403         if (m_player && fov != m_fov)
404                 m_player->setDirty(true);
405
406         m_fov = fov;
407 }
408
409 void PlayerSAO::setWantedRange(const s16 range)
410 {
411         if (m_player && range != m_wanted_range)
412                 m_player->setDirty(true);
413
414         m_wanted_range = range;
415 }
416
417 void PlayerSAO::setPlayerYawAndSend(const float yaw)
418 {
419         setPlayerYaw(yaw);
420         m_env->getGameDef()->SendMovePlayer(m_peer_id);
421 }
422
423 void PlayerSAO::setLookPitch(const float pitch)
424 {
425         if (m_player && pitch != m_pitch)
426                 m_player->setDirty(true);
427
428         m_pitch = pitch;
429 }
430
431 void PlayerSAO::setLookPitchAndSend(const float pitch)
432 {
433         setLookPitch(pitch);
434         m_env->getGameDef()->SendMovePlayer(m_peer_id);
435 }
436
437 u16 PlayerSAO::punch(v3f dir,
438         const ToolCapabilities *toolcap,
439         ServerActiveObject *puncher,
440         float time_from_last_punch)
441 {
442         if (!toolcap)
443                 return 0;
444
445         FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
446
447         // No effect if PvP disabled or if immortal
448         if (isImmortal() || !g_settings->getBool("enable_pvp")) {
449                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
450                         // create message and add to list
451                         sendPunchCommand();
452                         return 0;
453                 }
454         }
455
456         s32 old_hp = getHP();
457         HitParams hitparams = getHitParams(m_armor_groups, toolcap,
458                         time_from_last_punch);
459
460         PlayerSAO *playersao = m_player->getPlayerSAO();
461
462         bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
463                                 puncher, time_from_last_punch, toolcap, dir,
464                                 hitparams.hp);
465
466         if (!damage_handled) {
467                 setHP((s32)getHP() - (s32)hitparams.hp,
468                                 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
469         } else { // override client prediction
470                 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
471                         // create message and add to list
472                         sendPunchCommand();
473                 }
474         }
475
476         actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
477                 ", hp=" << puncher->getHP() << ") punched " <<
478                 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
479                 "), damage=" << (old_hp - (s32)getHP()) <<
480                 (damage_handled ? " (handled by Lua)" : "") << std::endl;
481
482         return hitparams.wear;
483 }
484
485 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
486 {
487         s32 oldhp = m_hp;
488
489         hp = rangelim(hp, 0, m_prop.hp_max);
490
491         if (oldhp != hp) {
492                 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
493                 if (hp_change == 0)
494                         return;
495
496                 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
497         }
498
499         if (hp < oldhp && isImmortal())
500                 return;
501
502         m_hp = hp;
503
504         // Update properties on death
505         if ((hp == 0) != (oldhp == 0))
506                 m_properties_sent = false;
507 }
508
509 void PlayerSAO::setBreath(const u16 breath, bool send)
510 {
511         if (m_player && breath != m_breath)
512                 m_player->setDirty(true);
513
514         m_breath = rangelim(breath, 0, m_prop.breath_max);
515
516         if (send)
517                 m_env->getGameDef()->SendPlayerBreath(this);
518 }
519
520 Inventory *PlayerSAO::getInventory() const
521 {
522         return m_player ? &m_player->inventory : nullptr;
523 }
524
525 InventoryLocation PlayerSAO::getInventoryLocation() const
526 {
527         InventoryLocation loc;
528         loc.setPlayer(m_player->getName());
529         return loc;
530 }
531
532 u16 PlayerSAO::getWieldIndex() const
533 {
534         return m_player->getWieldIndex();
535 }
536
537 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
538 {
539         return m_player->getWieldedItem(selected, hand);
540 }
541
542 bool PlayerSAO::setWieldedItem(const ItemStack &item)
543 {
544         InventoryList *mlist = m_player->inventory.getList(getWieldList());
545         if (mlist) {
546                 mlist->changeItem(m_player->getWieldIndex(), item);
547                 return true;
548         }
549         return false;
550 }
551
552 void PlayerSAO::disconnected()
553 {
554         m_peer_id = PEER_ID_INEXISTENT;
555         m_pending_removal = true;
556 }
557
558 void PlayerSAO::unlinkPlayerSessionAndSave()
559 {
560         assert(m_player->getPlayerSAO() == this);
561         m_player->setPeerId(PEER_ID_INEXISTENT);
562         m_env->savePlayer(m_player);
563         m_player->setPlayerSAO(NULL);
564         m_env->removePlayer(m_player);
565 }
566
567 std::string PlayerSAO::getPropertyPacket()
568 {
569         m_prop.is_visible = (true);
570         return generateSetPropertiesCommand(m_prop);
571 }
572
573 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
574 {
575         if (m_max_speed_override_time == 0.0f)
576                 m_max_speed_override = vel;
577         else
578                 m_max_speed_override += vel;
579         if (m_player) {
580                 float accel = MYMIN(m_player->movement_acceleration_default,
581                                 m_player->movement_acceleration_air);
582                 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
583         }
584 }
585
586 bool PlayerSAO::checkMovementCheat()
587 {
588         if (isAttached() || m_is_singleplayer ||
589                         g_settings->getBool("disable_anticheat")) {
590                 m_last_good_position = m_base_position;
591                 return false;
592         }
593
594         bool cheated = false;
595         /*
596                 Check player movements
597
598                 NOTE: Actually the server should handle player physics like the
599                 client does and compare player's position to what is calculated
600                 on our side. This is required when eg. players fly due to an
601                 explosion. Altough a node-based alternative might be possible
602                 too, and much more lightweight.
603         */
604
605         float override_max_H, override_max_V;
606         if (m_max_speed_override_time > 0.0f) {
607                 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
608                 override_max_V = fabs(m_max_speed_override.Y);
609         } else {
610                 override_max_H = override_max_V = 0.0f;
611         }
612
613         float player_max_walk = 0; // horizontal movement
614         float player_max_jump = 0; // vertical upwards movement
615
616         if (m_privs.count("fast") != 0)
617                 player_max_walk = m_player->movement_speed_fast; // Fast speed
618         else
619                 player_max_walk = m_player->movement_speed_walk; // Normal speed
620         player_max_walk *= m_physics_override_speed;
621         player_max_walk = MYMAX(player_max_walk, override_max_H);
622
623         player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
624         // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
625         //        until this can be verified correctly, tolerate higher jumping speeds
626         player_max_jump *= 2.0;
627         player_max_jump = MYMAX(player_max_jump, override_max_V);
628
629         // Don't divide by zero!
630         if (player_max_walk < 0.0001f)
631                 player_max_walk = 0.0001f;
632         if (player_max_jump < 0.0001f)
633                 player_max_jump = 0.0001f;
634
635         v3f diff = (m_base_position - m_last_good_position);
636         float d_vert = diff.Y;
637         diff.Y = 0;
638         float d_horiz = diff.getLength();
639         float required_time = d_horiz / player_max_walk;
640
641         // FIXME: Checking downwards movement is not easily possible currently,
642         //        the server could calculate speed differences to examine the gravity
643         if (d_vert > 0) {
644                 // In certain cases (water, ladders) walking speed is applied vertically
645                 float s = MYMAX(player_max_jump, player_max_walk);
646                 required_time = MYMAX(required_time, d_vert / s);
647         }
648
649         if (m_move_pool.grab(required_time)) {
650                 m_last_good_position = m_base_position;
651         } else {
652                 const float LAG_POOL_MIN = 5.0;
653                 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
654                 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
655                 if (m_time_from_last_teleport > lag_pool_max) {
656                         actionstream << "Server: " << m_player->getName()
657                                         << " moved too fast: V=" << d_vert << ", H=" << d_horiz
658                                         << "; resetting position." << std::endl;
659                         cheated = true;
660                 }
661                 setBasePosition(m_last_good_position);
662         }
663         return cheated;
664 }
665
666 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
667 {
668         //update collision box
669         toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
670         toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
671
672         toset->MinEdge += m_base_position;
673         toset->MaxEdge += m_base_position;
674         return true;
675 }
676
677 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
678 {
679         if (!m_prop.is_visible || !m_prop.pointable) {
680                 return false;
681         }
682
683         toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
684         toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
685
686         return true;
687 }
688
689 float PlayerSAO::getZoomFOV() const
690 {
691         return m_prop.zoom_fov;
692 }