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("This function shall not be called for PlayerSAO");
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);
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);
221 if (!m_properties_sent) {
222 m_properties_sent = true;
223 std::string str = getPropertyPacket();
224 // create message and add to list
225 m_messages_out.emplace(getId(), true, str);
226 m_env->getScriptIface()->player_event(this, "properties_changed");
229 // If attached, check that our parent is still there. If it isn't, detach.
230 if (m_attachment_parent_id && !isAttached()) {
231 // This is handled when objects are removed from the map
232 warningstream << "PlayerSAO::step() id=" << m_id <<
233 " is attached to nonexistent parent. This is a bug." << std::endl;
234 clearParentAttachment();
235 setBasePosition(m_last_good_position);
236 m_env->getGameDef()->SendMovePlayer(m_peer_id);
239 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
241 // Set lag pool maximums based on estimated lag
242 const float LAG_POOL_MIN = 5.0f;
243 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
244 if(lag_pool_max < LAG_POOL_MIN)
245 lag_pool_max = LAG_POOL_MIN;
246 m_dig_pool.setMax(lag_pool_max);
247 m_move_pool.setMax(lag_pool_max);
249 // Increment cheat prevention timers
250 m_dig_pool.add(dtime);
251 m_move_pool.add(dtime);
252 m_time_from_last_teleport += dtime;
253 m_time_from_last_punch += dtime;
254 m_nocheat_dig_time += dtime;
255 m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f);
257 // Each frame, parent position is copied if the object is attached,
258 // otherwise it's calculated normally.
259 // If the object gets detached this comes into effect automatically from
260 // the last known origin.
261 if (auto *parent = getParent()) {
262 v3f pos = parent->getBasePosition();
263 m_last_good_position = pos;
264 setBasePosition(pos);
267 m_player->setSpeed(v3f());
270 if (!send_recommended)
273 if (m_position_not_sent) {
274 m_position_not_sent = false;
275 float update_interval = m_env->getSendRecommendedInterval();
277 // When attached, the position is only sent to clients where the
278 // parent isn't known
280 pos = m_last_good_position;
282 pos = m_base_position;
284 std::string str = generateUpdatePositionCommand(
286 v3f(0.0f, 0.0f, 0.0f),
287 v3f(0.0f, 0.0f, 0.0f),
293 // create message and add to list
294 m_messages_out.emplace(getId(), false, str);
297 if (!m_physics_override_sent) {
298 m_physics_override_sent = true;
299 // create message and add to list
300 m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
306 std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
309 // Will output a format warning client-side
313 const auto &phys = m_player->physics_override;
314 std::ostringstream os(std::ios::binary);
316 writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
318 writeF32(os, phys.speed);
319 writeF32(os, phys.jump);
320 writeF32(os, phys.gravity);
321 // MT 0.4.10 legacy: send inverted for detault `true` if the server sends nothing
322 writeU8(os, !phys.sneak);
323 writeU8(os, !phys.sneak_glitch);
324 writeU8(os, !phys.new_move);
328 void PlayerSAO::setBasePosition(v3f position)
330 if (m_player && position != m_base_position)
331 m_player->setDirty(true);
333 // This needs to be ran for attachments too
334 ServerActiveObject::setBasePosition(position);
336 // Updating is not wanted/required for player migration
338 m_position_not_sent = true;
342 void PlayerSAO::setPos(const v3f &pos)
347 // Send mapblock of target location
348 v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
349 m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
351 setBasePosition(pos);
352 // Movement caused by this command is always valid
353 m_last_good_position = getBasePosition();
355 m_time_from_last_teleport = 0.0;
356 m_env->getGameDef()->SendMovePlayer(m_peer_id);
359 void PlayerSAO::moveTo(v3f pos, bool continuous)
364 setBasePosition(pos);
365 // Movement caused by this command is always valid
366 m_last_good_position = getBasePosition();
368 m_time_from_last_teleport = 0.0;
369 m_env->getGameDef()->SendMovePlayer(m_peer_id);
372 void PlayerSAO::setPlayerYaw(const float yaw)
374 v3f rotation(0, yaw, 0);
375 if (m_player && yaw != m_rotation.Y)
376 m_player->setDirty(true);
378 // Set player model yaw, not look view
379 UnitSAO::setRotation(rotation);
382 void PlayerSAO::setFov(const float fov)
384 if (m_player && fov != m_fov)
385 m_player->setDirty(true);
390 void PlayerSAO::setWantedRange(const s16 range)
392 if (m_player && range != m_wanted_range)
393 m_player->setDirty(true);
395 m_wanted_range = range;
398 void PlayerSAO::setPlayerYawAndSend(const float yaw)
401 m_env->getGameDef()->SendMovePlayer(m_peer_id);
404 void PlayerSAO::setLookPitch(const float pitch)
406 if (m_player && pitch != m_pitch)
407 m_player->setDirty(true);
412 void PlayerSAO::setLookPitchAndSend(const float pitch)
415 m_env->getGameDef()->SendMovePlayer(m_peer_id);
418 u32 PlayerSAO::punch(v3f dir,
419 const ToolCapabilities *toolcap,
420 ServerActiveObject *puncher,
421 float time_from_last_punch,
427 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
429 // No effect if PvP disabled or if immortal
430 if (isImmortal() || !g_settings->getBool("enable_pvp")) {
431 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
432 // create message and add to list
438 s32 old_hp = getHP();
439 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
440 time_from_last_punch, initial_wear);
442 PlayerSAO *playersao = m_player->getPlayerSAO();
444 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
445 puncher, time_from_last_punch, toolcap, dir,
448 if (!damage_handled) {
449 setHP((s32)getHP() - (s32)hitparams.hp,
450 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
451 } else { // override client prediction
452 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
453 // create message and add to list
458 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
459 ", hp=" << puncher->getHP() << ") punched " <<
460 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
461 "), damage=" << (old_hp - (s32)getHP()) <<
462 (damage_handled ? " (handled by Lua)" : "") << std::endl;
464 return hitparams.wear;
467 void PlayerSAO::rightClick(ServerActiveObject *clicker)
469 m_env->getScriptIface()->on_rightclickplayer(this, clicker);
472 void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool from_client)
474 target_hp = rangelim(target_hp, 0, U16_MAX);
476 if (target_hp == m_hp)
477 return; // Nothing to do
479 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, target_hp - (s32)m_hp, reason);
481 s32 hp = (s32)m_hp + std::min(hp_change, U16_MAX); // Protection against s32 overflow
482 hp = rangelim(hp, 0, U16_MAX);
484 if (hp > m_prop.hp_max)
487 if (hp < m_hp && isImmortal())
488 hp = m_hp; // Do not allow immortal players to be damaged
490 // Update properties on death
491 if ((hp == 0) != (m_hp == 0))
492 m_properties_sent = false;
496 m_env->getGameDef()->HandlePlayerHPChange(this, reason);
497 } else if (from_client)
498 m_env->getGameDef()->SendPlayerHP(this, true);
501 void PlayerSAO::setBreath(const u16 breath, bool send)
503 if (m_player && breath != m_breath)
504 m_player->setDirty(true);
506 m_breath = rangelim(breath, 0, m_prop.breath_max);
509 m_env->getGameDef()->SendPlayerBreath(this);
512 Inventory *PlayerSAO::getInventory() const
514 return m_player ? &m_player->inventory : nullptr;
517 InventoryLocation PlayerSAO::getInventoryLocation() const
519 InventoryLocation loc;
520 loc.setPlayer(m_player->getName());
524 u16 PlayerSAO::getWieldIndex() const
526 return m_player->getWieldIndex();
529 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
531 return m_player->getWieldedItem(selected, hand);
534 bool PlayerSAO::getOffhandWieldedItem(ItemStack *offhand, ItemStack *place, IItemDefManager *itemdef_manager, PointedThing pointed) const
536 return m_player->getOffhandWieldedItem(offhand, place, itemdef_manager, pointed);
539 bool PlayerSAO::setWieldedItem(const ItemStack &item)
541 InventoryList *mlist = m_player->inventory.getList(getWieldList());
543 mlist->changeItem(m_player->getWieldIndex(), item);
549 bool PlayerSAO::setOffhandWieldedItem(const ItemStack &item)
551 InventoryList *olist = m_player->inventory.getList("offhand");
553 olist->changeItem(0, item);
559 void PlayerSAO::disconnected()
561 m_peer_id = PEER_ID_INEXISTENT;
565 void PlayerSAO::unlinkPlayerSessionAndSave()
567 assert(m_player->getPlayerSAO() == this);
568 m_player->setPeerId(PEER_ID_INEXISTENT);
569 m_env->savePlayer(m_player);
570 m_player->setPlayerSAO(NULL);
571 m_env->removePlayer(m_player);
574 std::string PlayerSAO::getPropertyPacket()
576 m_prop.is_visible = (true);
577 return generateSetPropertiesCommand(m_prop);
580 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
582 if (m_max_speed_override_time == 0.0f)
583 m_max_speed_override = vel;
585 m_max_speed_override += vel;
587 float accel = MYMIN(m_player->movement_acceleration_default,
588 m_player->movement_acceleration_air);
589 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
593 bool PlayerSAO::checkMovementCheat()
595 if (m_is_singleplayer ||
597 g_settings->getBool("disable_anticheat")) {
598 m_last_good_position = m_base_position;
602 bool cheated = false;
604 Check player movements
606 NOTE: Actually the server should handle player physics like the
607 client does and compare player's position to what is calculated
608 on our side. This is required when eg. players fly due to an
609 explosion. Altough a node-based alternative might be possible
610 too, and much more lightweight.
613 float override_max_H, override_max_V;
614 if (m_max_speed_override_time > 0.0f) {
615 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
616 override_max_V = fabs(m_max_speed_override.Y);
618 override_max_H = override_max_V = 0.0f;
621 float player_max_walk = 0; // horizontal movement
622 float player_max_jump = 0; // vertical upwards movement
624 if (m_privs.count("fast") != 0)
625 player_max_walk = m_player->movement_speed_fast; // Fast speed
627 player_max_walk = m_player->movement_speed_walk; // Normal speed
628 player_max_walk *= m_player->physics_override.speed;
629 player_max_walk = MYMAX(player_max_walk, override_max_H);
631 player_max_jump = m_player->movement_speed_jump * m_player->physics_override.jump;
632 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
633 // until this can be verified correctly, tolerate higher jumping speeds
634 player_max_jump *= 2.0;
635 player_max_jump = MYMAX(player_max_jump, override_max_V);
637 // Don't divide by zero!
638 if (player_max_walk < 0.0001f)
639 player_max_walk = 0.0001f;
640 if (player_max_jump < 0.0001f)
641 player_max_jump = 0.0001f;
643 v3f diff = (m_base_position - m_last_good_position);
644 float d_vert = diff.Y;
646 float d_horiz = diff.getLength();
647 float required_time = d_horiz / player_max_walk;
649 // FIXME: Checking downwards movement is not easily possible currently,
650 // the server could calculate speed differences to examine the gravity
652 // In certain cases (water, ladders) walking speed is applied vertically
653 float s = MYMAX(player_max_jump, player_max_walk);
654 required_time = MYMAX(required_time, d_vert / s);
657 if (m_move_pool.grab(required_time)) {
658 m_last_good_position = m_base_position;
660 const float LAG_POOL_MIN = 5.0;
661 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
662 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
663 if (m_time_from_last_teleport > lag_pool_max) {
664 actionstream << "Server: " << m_player->getName()
665 << " moved too fast: V=" << d_vert << ", H=" << d_horiz
666 << "; resetting position." << std::endl;
669 setBasePosition(m_last_good_position);
674 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
676 //update collision box
677 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
678 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
680 toset->MinEdge += m_base_position;
681 toset->MaxEdge += m_base_position;
685 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
687 if (!m_prop.is_visible || !m_prop.pointable) {
691 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
692 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
697 float PlayerSAO::getZoomFOV() const
699 return m_prop.zoom_fov;