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