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;
58 m_prop.show_on_minimap = true;
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;
64 if (!g_settings->getBool("enable_damage"))
65 m_armor_groups["immortal"] = 1;
68 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
75 v3f PlayerSAO::getEyeOffset() const
77 return v3f(0, BS * m_prop.eye_height, 0);
80 std::string PlayerSAO::getDescription()
82 return std::string("player ") + m_player->getName();
85 // Called after id has been set and has been inserted in environment
86 void PlayerSAO::addedToEnvironment(u32 dtime_s)
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;
95 // Called before removing from environment
96 void PlayerSAO::removingFromEnvironment()
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);
107 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
109 std::ostringstream os(std::ios::binary);
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());
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
128 msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size
129 msg_os << serializeString32(generateUpdatePhysicsOverrideCommand()); // 5 + m_bone_position.size
131 int message_count = 5 + m_bone_position.size();
133 for (const auto &id : getAttachmentChildIds()) {
134 if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
136 msg_os << serializeString32(obj->generateUpdateInfantCommand(
137 id, protocol_version));
141 writeU8(os, message_count);
142 std::string serialized = msg_os.str();
143 os.write(serialized.c_str(), serialized.size());
149 void PlayerSAO::getStaticData(std::string * result) const
151 FATAL_ERROR("Obsolete function");
154 void PlayerSAO::step(float dtime, bool send_recommended)
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) {
164 setBreath(m_breath - 1);
166 // No more breath, damage player
168 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
169 setHP(m_hp - c.drowning, reason);
170 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
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);
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;
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;
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;
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);
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");
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);
241 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
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);
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);
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.
264 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
265 m_last_good_position = pos;
266 setBasePosition(pos);
269 if (!send_recommended)
272 if (m_position_not_sent) {
273 m_position_not_sent = false;
274 float update_interval = m_env->getSendRecommendedInterval();
276 // When attached, the position is only sent to clients where the
277 // parent isn't known
279 pos = m_last_good_position;
281 pos = m_base_position;
283 std::string str = generateUpdatePositionCommand(
285 v3f(0.0f, 0.0f, 0.0f),
286 v3f(0.0f, 0.0f, 0.0f),
292 // create message and add to list
293 m_messages_out.emplace(getId(), false, str);
296 if (!m_physics_override_sent) {
297 m_physics_override_sent = true;
298 // create message and add to list
299 m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
305 std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
307 std::ostringstream os(std::ios::binary);
309 writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
311 writeF32(os, m_physics_override_speed);
312 writeF32(os, m_physics_override_jump);
313 writeF32(os, m_physics_override_gravity);
314 // these are sent inverted so we get true when the server sends nothing
315 writeU8(os, !m_physics_override_sneak);
316 writeU8(os, !m_physics_override_sneak_glitch);
317 writeU8(os, !m_physics_override_new_move);
321 void PlayerSAO::setBasePosition(const v3f &position)
323 if (m_player && position != m_base_position)
324 m_player->setDirty(true);
326 // This needs to be ran for attachments too
327 ServerActiveObject::setBasePosition(position);
329 // Updating is not wanted/required for player migration
331 m_position_not_sent = true;
335 void PlayerSAO::setPos(const v3f &pos)
340 // Send mapblock of target location
341 v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
342 m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
344 setBasePosition(pos);
345 // Movement caused by this command is always valid
346 m_last_good_position = pos;
348 m_time_from_last_teleport = 0.0;
349 m_env->getGameDef()->SendMovePlayer(m_peer_id);
352 void PlayerSAO::moveTo(v3f pos, bool continuous)
357 setBasePosition(pos);
358 // Movement caused by this command is always valid
359 m_last_good_position = pos;
361 m_time_from_last_teleport = 0.0;
362 m_env->getGameDef()->SendMovePlayer(m_peer_id);
365 void PlayerSAO::setPlayerYaw(const float yaw)
367 v3f rotation(0, yaw, 0);
368 if (m_player && yaw != m_rotation.Y)
369 m_player->setDirty(true);
371 // Set player model yaw, not look view
372 UnitSAO::setRotation(rotation);
375 void PlayerSAO::setFov(const float fov)
377 if (m_player && fov != m_fov)
378 m_player->setDirty(true);
383 void PlayerSAO::setWantedRange(const s16 range)
385 if (m_player && range != m_wanted_range)
386 m_player->setDirty(true);
388 m_wanted_range = range;
391 void PlayerSAO::setPlayerYawAndSend(const float yaw)
394 m_env->getGameDef()->SendMovePlayer(m_peer_id);
397 void PlayerSAO::setLookPitch(const float pitch)
399 if (m_player && pitch != m_pitch)
400 m_player->setDirty(true);
405 void PlayerSAO::setLookPitchAndSend(const float pitch)
408 m_env->getGameDef()->SendMovePlayer(m_peer_id);
411 u16 PlayerSAO::punch(v3f dir,
412 const ToolCapabilities *toolcap,
413 ServerActiveObject *puncher,
414 float time_from_last_punch)
419 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
421 // No effect if PvP disabled or if immortal
422 if (isImmortal() || !g_settings->getBool("enable_pvp")) {
423 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
424 // create message and add to list
430 s32 old_hp = getHP();
431 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
432 time_from_last_punch);
434 PlayerSAO *playersao = m_player->getPlayerSAO();
436 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
437 puncher, time_from_last_punch, toolcap, dir,
440 if (!damage_handled) {
441 setHP((s32)getHP() - (s32)hitparams.hp,
442 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
443 } else { // override client prediction
444 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
445 // create message and add to list
450 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
451 ", hp=" << puncher->getHP() << ") punched " <<
452 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
453 "), damage=" << (old_hp - (s32)getHP()) <<
454 (damage_handled ? " (handled by Lua)" : "") << std::endl;
456 return hitparams.wear;
459 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
462 return; // Nothing to do
464 if (m_hp <= 0 && hp < (s32)m_hp)
465 return; // Cannot take more damage
468 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - m_hp, reason);
472 hp = m_hp + hp_change;
476 hp = rangelim(hp, 0, m_prop.hp_max);
478 if (hp < oldhp && isImmortal())
479 return; // Do not allow immortal players to be damaged
483 // Update properties on death
484 if ((hp == 0) != (oldhp == 0))
485 m_properties_sent = false;
488 void PlayerSAO::setBreath(const u16 breath, bool send)
490 if (m_player && breath != m_breath)
491 m_player->setDirty(true);
493 m_breath = rangelim(breath, 0, m_prop.breath_max);
496 m_env->getGameDef()->SendPlayerBreath(this);
499 Inventory *PlayerSAO::getInventory() const
501 return m_player ? &m_player->inventory : nullptr;
504 InventoryLocation PlayerSAO::getInventoryLocation() const
506 InventoryLocation loc;
507 loc.setPlayer(m_player->getName());
511 u16 PlayerSAO::getWieldIndex() const
513 return m_player->getWieldIndex();
516 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
518 return m_player->getWieldedItem(selected, hand);
521 bool PlayerSAO::setWieldedItem(const ItemStack &item)
523 InventoryList *mlist = m_player->inventory.getList(getWieldList());
525 mlist->changeItem(m_player->getWieldIndex(), item);
531 void PlayerSAO::disconnected()
533 m_peer_id = PEER_ID_INEXISTENT;
534 m_pending_removal = true;
537 void PlayerSAO::unlinkPlayerSessionAndSave()
539 assert(m_player->getPlayerSAO() == this);
540 m_player->setPeerId(PEER_ID_INEXISTENT);
541 m_env->savePlayer(m_player);
542 m_player->setPlayerSAO(NULL);
543 m_env->removePlayer(m_player);
546 std::string PlayerSAO::getPropertyPacket()
548 m_prop.is_visible = (true);
549 return generateSetPropertiesCommand(m_prop);
552 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
554 if (m_max_speed_override_time == 0.0f)
555 m_max_speed_override = vel;
557 m_max_speed_override += vel;
559 float accel = MYMIN(m_player->movement_acceleration_default,
560 m_player->movement_acceleration_air);
561 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
565 bool PlayerSAO::checkMovementCheat()
567 if (m_is_singleplayer ||
568 g_settings->getBool("disable_anticheat")) {
569 m_last_good_position = m_base_position;
572 if (UnitSAO *parent = dynamic_cast<UnitSAO *>(getParent())) {
579 getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot, &force_visible);
582 v3f parent_pos = parent->getBasePosition();
583 f32 diff = m_base_position.getDistanceFromSQ(parent_pos) - attachment_pos.getLengthSQ();
584 const f32 maxdiff = 4.0f * BS; // fair trade-off value for various latencies
586 if (diff > maxdiff * maxdiff) {
587 setBasePosition(parent_pos);
588 actionstream << "Server: " << m_player->getName()
589 << " moved away from parent; diff=" << sqrtf(diff) / BS
590 << " resetting position." << std::endl;
593 // Player movement is locked to the entity. Skip further checks
597 bool cheated = false;
599 Check player movements
601 NOTE: Actually the server should handle player physics like the
602 client does and compare player's position to what is calculated
603 on our side. This is required when eg. players fly due to an
604 explosion. Altough a node-based alternative might be possible
605 too, and much more lightweight.
608 float override_max_H, override_max_V;
609 if (m_max_speed_override_time > 0.0f) {
610 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
611 override_max_V = fabs(m_max_speed_override.Y);
613 override_max_H = override_max_V = 0.0f;
616 float player_max_walk = 0; // horizontal movement
617 float player_max_jump = 0; // vertical upwards movement
619 if (m_privs.count("fast") != 0)
620 player_max_walk = m_player->movement_speed_fast; // Fast speed
622 player_max_walk = m_player->movement_speed_walk; // Normal speed
623 player_max_walk *= m_physics_override_speed;
624 player_max_walk = MYMAX(player_max_walk, override_max_H);
626 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
627 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
628 // until this can be verified correctly, tolerate higher jumping speeds
629 player_max_jump *= 2.0;
630 player_max_jump = MYMAX(player_max_jump, override_max_V);
632 // Don't divide by zero!
633 if (player_max_walk < 0.0001f)
634 player_max_walk = 0.0001f;
635 if (player_max_jump < 0.0001f)
636 player_max_jump = 0.0001f;
638 v3f diff = (m_base_position - m_last_good_position);
639 float d_vert = diff.Y;
641 float d_horiz = diff.getLength();
642 float required_time = d_horiz / player_max_walk;
644 // FIXME: Checking downwards movement is not easily possible currently,
645 // the server could calculate speed differences to examine the gravity
647 // In certain cases (water, ladders) walking speed is applied vertically
648 float s = MYMAX(player_max_jump, player_max_walk);
649 required_time = MYMAX(required_time, d_vert / s);
652 if (m_move_pool.grab(required_time)) {
653 m_last_good_position = m_base_position;
655 const float LAG_POOL_MIN = 5.0;
656 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
657 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
658 if (m_time_from_last_teleport > lag_pool_max) {
659 actionstream << "Server: " << m_player->getName()
660 << " moved too fast: V=" << d_vert << ", H=" << d_horiz
661 << "; resetting position." << std::endl;
664 setBasePosition(m_last_good_position);
669 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
671 //update collision box
672 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
673 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
675 toset->MinEdge += m_base_position;
676 toset->MaxEdge += m_base_position;
680 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
682 if (!m_prop.is_visible || !m_prop.pointable) {
686 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
687 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
692 float PlayerSAO::getZoomFOV() const
694 return m_prop.zoom_fov;