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
PlayerSAO(ServerEnvironment * env_,RemotePlayer * player_,session_t peer_id_,bool is_singleplayer)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
finalize(RemotePlayer * player,const std::set<std::string> & privs)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
getEyeOffset() const75 v3f PlayerSAO::getEyeOffset() const
76 {
77 return v3f(0, BS * m_prop.eye_height, 0);
78 }
79
getDescription()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
addedToEnvironment(u32 dtime_s)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
removingFromEnvironment()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
getClientInitializationData(u16 protocol_version)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
getStaticData(std::string * result) const149 void PlayerSAO::getStaticData(std::string * result) const
150 {
151 FATAL_ERROR("This function shall not be called for PlayerSAO");
152 }
153
step(float dtime,bool send_recommended)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
generateUpdatePhysicsOverrideCommand() const308 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
setBasePosition(const v3f & position)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
setPos(const v3f & pos)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
moveTo(v3f pos,bool continuous)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
setPlayerYaw(const float yaw)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
setFov(const float fov)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
setWantedRange(const s16 range)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
setPlayerYawAndSend(const float yaw)394 void PlayerSAO::setPlayerYawAndSend(const float yaw)
395 {
396 setPlayerYaw(yaw);
397 m_env->getGameDef()->SendMovePlayer(m_peer_id);
398 }
399
setLookPitch(const float pitch)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
setLookPitchAndSend(const float pitch)408 void PlayerSAO::setLookPitchAndSend(const float pitch)
409 {
410 setLookPitch(pitch);
411 m_env->getGameDef()->SendMovePlayer(m_peer_id);
412 }
413
punch(v3f dir,const ToolCapabilities * toolcap,ServerActiveObject * puncher,float time_from_last_punch)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
rightClick(ServerActiveObject * clicker)462 void PlayerSAO::rightClick(ServerActiveObject *clicker)
463 {
464 m_env->getScriptIface()->on_rightclickplayer(this, clicker);
465 }
466
setHP(s32 hp,const PlayerHPChangeReason & reason)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
setBreath(const u16 breath,bool send)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
getInventory() const507 Inventory *PlayerSAO::getInventory() const
508 {
509 return m_player ? &m_player->inventory : nullptr;
510 }
511
getInventoryLocation() const512 InventoryLocation PlayerSAO::getInventoryLocation() const
513 {
514 InventoryLocation loc;
515 loc.setPlayer(m_player->getName());
516 return loc;
517 }
518
getWieldIndex() const519 u16 PlayerSAO::getWieldIndex() const
520 {
521 return m_player->getWieldIndex();
522 }
523
getWieldedItem(ItemStack * selected,ItemStack * hand) const524 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
525 {
526 return m_player->getWieldedItem(selected, hand);
527 }
528
setWieldedItem(const ItemStack & item)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
disconnected()539 void PlayerSAO::disconnected()
540 {
541 m_peer_id = PEER_ID_INEXISTENT;
542 markForRemoval();
543 }
544
unlinkPlayerSessionAndSave()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
getPropertyPacket()554 std::string PlayerSAO::getPropertyPacket()
555 {
556 m_prop.is_visible = (true);
557 return generateSetPropertiesCommand(m_prop);
558 }
559
setMaxSpeedOverride(const v3f & vel)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
checkMovementCheat()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
getCollisionBox(aabb3f * toset) const654 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
getSelectionBox(aabb3f * toset) const665 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
getZoomFOV() const677 float PlayerSAO::getZoomFOV() const
678 {
679 return m_prop.zoom_fov;
680 }
681