3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2013-2020 Minetest core developers & community
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.
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.
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.
21 #include "player_sao.h"
23 #include "remoteplayer.h"
24 #include "scripting_server.h"
26 #include "serverenvironment.h"
28 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
29 bool is_singleplayer):
30 UnitSAO(env_, v3f(0,0,0)),
33 m_is_singleplayer(is_singleplayer)
35 SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT);
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;
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;
63 if (!g_settings->getBool("enable_damage"))
64 m_armor_groups["immortal"] = 1;
67 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
74 v3f PlayerSAO::getEyeOffset() const
76 return v3f(0, BS * m_prop.eye_height, 0);
79 std::string PlayerSAO::getDescription()
81 return std::string("player ") + m_player->getName();
84 // Called after id has been set and has been inserted in environment
85 void PlayerSAO::addedToEnvironment(u32 dtime_s)
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;
94 // Called before removing from environment
95 void PlayerSAO::removingFromEnvironment()
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);
106 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
108 std::ostringstream os(std::ios::binary);
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());
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 (const auto &bone_pos : m_bone_position) {
124 msg_os << serializeLongString(generateUpdateBonePositionCommand(
125 bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size
127 msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4
128 msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5
130 int message_count = 5 + m_bone_position.size();
132 for (const auto &id : getAttachmentChildIds()) {
133 if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
135 msg_os << serializeLongString(obj->generateUpdateInfantCommand(
136 id, protocol_version));
140 writeU8(os, message_count);
141 std::string serialized = msg_os.str();
142 os.write(serialized.c_str(), serialized.size());
148 void PlayerSAO::getStaticData(std::string * result) const
150 FATAL_ERROR("Obsolete function");
153 void PlayerSAO::step(float dtime, bool send_recommended)
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) {
163 setBreath(m_breath - 1);
165 // No more breath, damage player
167 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
168 setHP(m_hp - c.drowning, reason);
169 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
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);
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;
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;
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;
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);
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");
230 // If attached, check that our parent is still there. If it isn't, detach.
231 if (m_attachment_parent_id && !isAttached()) {
232 // This is handled when objects are removed from the map
233 warningstream << "PlayerSAO::step() id=" << m_id <<
234 " is attached to nonexistent parent. This is a bug." << std::endl;
235 clearParentAttachment();
236 setBasePosition(m_last_good_position);
237 m_env->getGameDef()->SendMovePlayer(m_peer_id);
240 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
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);
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);
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.
263 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
264 m_last_good_position = pos;
265 setBasePosition(pos);
268 if (!send_recommended)
271 if (m_position_not_sent) {
272 m_position_not_sent = false;
273 float update_interval = m_env->getSendRecommendedInterval();
275 // When attached, the position is only sent to clients where the
276 // parent isn't known
278 pos = m_last_good_position;
280 pos = m_base_position;
282 std::string str = generateUpdatePositionCommand(
284 v3f(0.0f, 0.0f, 0.0f),
285 v3f(0.0f, 0.0f, 0.0f),
291 // create message and add to list
292 m_messages_out.emplace(getId(), false, str);
295 if (!m_physics_override_sent) {
296 m_physics_override_sent = true;
297 // create message and add to list
298 m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
304 std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
306 std::ostringstream os(std::ios::binary);
308 writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
310 writeF32(os, m_physics_override_speed);
311 writeF32(os, m_physics_override_jump);
312 writeF32(os, m_physics_override_gravity);
313 // these are sent inverted so we get true when the server sends nothing
314 writeU8(os, !m_physics_override_sneak);
315 writeU8(os, !m_physics_override_sneak_glitch);
316 writeU8(os, !m_physics_override_new_move);
320 void PlayerSAO::setBasePosition(const v3f &position)
322 if (m_player && position != m_base_position)
323 m_player->setDirty(true);
325 // This needs to be ran for attachments too
326 ServerActiveObject::setBasePosition(position);
328 // Updating is not wanted/required for player migration
330 m_position_not_sent = true;
334 void PlayerSAO::setPos(const v3f &pos)
339 // Send mapblock of target location
340 v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
341 m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
343 setBasePosition(pos);
344 // Movement caused by this command is always valid
345 m_last_good_position = pos;
347 m_time_from_last_teleport = 0.0;
348 m_env->getGameDef()->SendMovePlayer(m_peer_id);
351 void PlayerSAO::moveTo(v3f pos, bool continuous)
356 setBasePosition(pos);
357 // Movement caused by this command is always valid
358 m_last_good_position = pos;
360 m_time_from_last_teleport = 0.0;
361 m_env->getGameDef()->SendMovePlayer(m_peer_id);
364 void PlayerSAO::setPlayerYaw(const float yaw)
366 v3f rotation(0, yaw, 0);
367 if (m_player && yaw != m_rotation.Y)
368 m_player->setDirty(true);
370 // Set player model yaw, not look view
371 UnitSAO::setRotation(rotation);
374 void PlayerSAO::setFov(const float fov)
376 if (m_player && fov != m_fov)
377 m_player->setDirty(true);
382 void PlayerSAO::setWantedRange(const s16 range)
384 if (m_player && range != m_wanted_range)
385 m_player->setDirty(true);
387 m_wanted_range = range;
390 void PlayerSAO::setPlayerYawAndSend(const float yaw)
393 m_env->getGameDef()->SendMovePlayer(m_peer_id);
396 void PlayerSAO::setLookPitch(const float pitch)
398 if (m_player && pitch != m_pitch)
399 m_player->setDirty(true);
404 void PlayerSAO::setLookPitchAndSend(const float pitch)
407 m_env->getGameDef()->SendMovePlayer(m_peer_id);
410 u16 PlayerSAO::punch(v3f dir,
411 const ToolCapabilities *toolcap,
412 ServerActiveObject *puncher,
413 float time_from_last_punch)
418 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
420 // No effect if PvP disabled or if immortal
421 if (isImmortal() || !g_settings->getBool("enable_pvp")) {
422 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
423 // create message and add to list
429 s32 old_hp = getHP();
430 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
431 time_from_last_punch);
433 PlayerSAO *playersao = m_player->getPlayerSAO();
435 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
436 puncher, time_from_last_punch, toolcap, dir,
439 if (!damage_handled) {
440 setHP((s32)getHP() - (s32)hitparams.hp,
441 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
442 } else { // override client prediction
443 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
444 // create message and add to list
449 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
450 ", hp=" << puncher->getHP() << ") punched " <<
451 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
452 "), damage=" << (old_hp - (s32)getHP()) <<
453 (damage_handled ? " (handled by Lua)" : "") << std::endl;
455 return hitparams.wear;
458 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
462 hp = rangelim(hp, 0, m_prop.hp_max);
465 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
469 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
472 if (hp < oldhp && isImmortal())
477 // Update properties on death
478 if ((hp == 0) != (oldhp == 0))
479 m_properties_sent = false;
482 void PlayerSAO::setBreath(const u16 breath, bool send)
484 if (m_player && breath != m_breath)
485 m_player->setDirty(true);
487 m_breath = rangelim(breath, 0, m_prop.breath_max);
490 m_env->getGameDef()->SendPlayerBreath(this);
493 Inventory *PlayerSAO::getInventory() const
495 return m_player ? &m_player->inventory : nullptr;
498 InventoryLocation PlayerSAO::getInventoryLocation() const
500 InventoryLocation loc;
501 loc.setPlayer(m_player->getName());
505 u16 PlayerSAO::getWieldIndex() const
507 return m_player->getWieldIndex();
510 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
512 return m_player->getWieldedItem(selected, hand);
515 bool PlayerSAO::setWieldedItem(const ItemStack &item)
517 InventoryList *mlist = m_player->inventory.getList(getWieldList());
519 mlist->changeItem(m_player->getWieldIndex(), item);
525 void PlayerSAO::disconnected()
527 m_peer_id = PEER_ID_INEXISTENT;
528 m_pending_removal = true;
531 void PlayerSAO::unlinkPlayerSessionAndSave()
533 assert(m_player->getPlayerSAO() == this);
534 m_player->setPeerId(PEER_ID_INEXISTENT);
535 m_env->savePlayer(m_player);
536 m_player->setPlayerSAO(NULL);
537 m_env->removePlayer(m_player);
540 std::string PlayerSAO::getPropertyPacket()
542 m_prop.is_visible = (true);
543 return generateSetPropertiesCommand(m_prop);
546 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
548 if (m_max_speed_override_time == 0.0f)
549 m_max_speed_override = vel;
551 m_max_speed_override += vel;
553 float accel = MYMIN(m_player->movement_acceleration_default,
554 m_player->movement_acceleration_air);
555 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
559 bool PlayerSAO::checkMovementCheat()
561 if (m_is_singleplayer ||
562 g_settings->getBool("disable_anticheat")) {
563 m_last_good_position = m_base_position;
566 if (UnitSAO *parent = dynamic_cast<UnitSAO *>(getParent())) {
572 getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot);
575 v3f parent_pos = parent->getBasePosition();
576 f32 diff = m_base_position.getDistanceFromSQ(parent_pos) - attachment_pos.getLengthSQ();
577 const f32 maxdiff = 4.0f * BS; // fair trade-off value for various latencies
579 if (diff > maxdiff * maxdiff) {
580 setBasePosition(parent_pos);
581 actionstream << "Server: " << m_player->getName()
582 << " moved away from parent; diff=" << sqrtf(diff) / BS
583 << " resetting position." << std::endl;
586 // Player movement is locked to the entity. Skip further checks
590 bool cheated = false;
592 Check player movements
594 NOTE: Actually the server should handle player physics like the
595 client does and compare player's position to what is calculated
596 on our side. This is required when eg. players fly due to an
597 explosion. Altough a node-based alternative might be possible
598 too, and much more lightweight.
601 float override_max_H, override_max_V;
602 if (m_max_speed_override_time > 0.0f) {
603 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
604 override_max_V = fabs(m_max_speed_override.Y);
606 override_max_H = override_max_V = 0.0f;
609 float player_max_walk = 0; // horizontal movement
610 float player_max_jump = 0; // vertical upwards movement
612 if (m_privs.count("fast") != 0)
613 player_max_walk = m_player->movement_speed_fast; // Fast speed
615 player_max_walk = m_player->movement_speed_walk; // Normal speed
616 player_max_walk *= m_physics_override_speed;
617 player_max_walk = MYMAX(player_max_walk, override_max_H);
619 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
620 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
621 // until this can be verified correctly, tolerate higher jumping speeds
622 player_max_jump *= 2.0;
623 player_max_jump = MYMAX(player_max_jump, override_max_V);
625 // Don't divide by zero!
626 if (player_max_walk < 0.0001f)
627 player_max_walk = 0.0001f;
628 if (player_max_jump < 0.0001f)
629 player_max_jump = 0.0001f;
631 v3f diff = (m_base_position - m_last_good_position);
632 float d_vert = diff.Y;
634 float d_horiz = diff.getLength();
635 float required_time = d_horiz / player_max_walk;
637 // FIXME: Checking downwards movement is not easily possible currently,
638 // the server could calculate speed differences to examine the gravity
640 // In certain cases (water, ladders) walking speed is applied vertically
641 float s = MYMAX(player_max_jump, player_max_walk);
642 required_time = MYMAX(required_time, d_vert / s);
645 if (m_move_pool.grab(required_time)) {
646 m_last_good_position = m_base_position;
648 const float LAG_POOL_MIN = 5.0;
649 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
650 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
651 if (m_time_from_last_teleport > lag_pool_max) {
652 actionstream << "Server: " << m_player->getName()
653 << " moved too fast: V=" << d_vert << ", H=" << d_horiz
654 << "; resetting position." << std::endl;
657 setBasePosition(m_last_good_position);
662 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
664 //update collision box
665 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
666 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
668 toset->MinEdge += m_base_position;
669 toset->MaxEdge += m_base_position;
673 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
675 if (!m_prop.is_visible || !m_prop.pointable) {
679 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
680 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
685 float PlayerSAO::getZoomFOV() const
687 return m_prop.zoom_fov;