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 (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
124 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
125 msg_os << serializeLongString(generateUpdateBonePositionCommand((*ii).first,
126 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
128 msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4
129 msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5
130 // (AO_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
131 msg_os << serializeLongString(generateUpdateNametagAttributesCommand(m_prop.nametag_color)); // 6
132 int message_count = 6 + m_bone_position.size();
133 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
134 ii != m_attachment_child_ids.end(); ++ii) {
135 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
137 msg_os << serializeLongString(obj->generateUpdateInfantCommand(*ii, protocol_version));
141 writeU8(os, message_count);
142 os.write(msg_os.str().c_str(), msg_os.str().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 ActiveObjectMessage aom(getId(), true, str);
227 m_messages_out.push(aom);
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 m_attachment_parent_id = 0;
234 m_attachment_bone = "";
235 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
236 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
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_armor_groups_sent) {
297 m_armor_groups_sent = true;
298 // create message and add to list
299 m_messages_out.emplace(getId(), true, generateUpdateArmorGroupsCommand());
302 if (!m_physics_override_sent) {
303 m_physics_override_sent = true;
304 // create message and add to list
305 m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
308 if (!m_animation_sent) {
309 m_animation_sent = true;
310 // create message and add to list
311 m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand());
314 if (!m_bone_position_sent) {
315 m_bone_position_sent = true;
316 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
317 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
318 std::string str = generateUpdateBonePositionCommand((*ii).first,
319 (*ii).second.X, (*ii).second.Y);
320 // create message and add to list
321 m_messages_out.emplace(getId(), true, str);
325 if (!m_attachment_sent) {
326 m_attachment_sent = true;
327 std::string str = generateUpdateAttachmentCommand();
328 // create message and add to list
329 ActiveObjectMessage aom(getId(), true, str);
330 m_messages_out.push(aom);
334 std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
336 std::ostringstream os(std::ios::binary);
338 writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
340 writeF32(os, m_physics_override_speed);
341 writeF32(os, m_physics_override_jump);
342 writeF32(os, m_physics_override_gravity);
343 // these are sent inverted so we get true when the server sends nothing
344 writeU8(os, !m_physics_override_sneak);
345 writeU8(os, !m_physics_override_sneak_glitch);
346 writeU8(os, !m_physics_override_new_move);
350 void PlayerSAO::setBasePosition(const v3f &position)
352 if (m_player && position != m_base_position)
353 m_player->setDirty(true);
355 // This needs to be ran for attachments too
356 ServerActiveObject::setBasePosition(position);
358 // Updating is not wanted/required for player migration
360 m_position_not_sent = true;
364 void PlayerSAO::setPos(const v3f &pos)
369 // Send mapblock of target location
370 v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
371 m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
373 setBasePosition(pos);
374 // Movement caused by this command is always valid
375 m_last_good_position = pos;
377 m_time_from_last_teleport = 0.0;
378 m_env->getGameDef()->SendMovePlayer(m_peer_id);
381 void PlayerSAO::moveTo(v3f pos, bool continuous)
386 setBasePosition(pos);
387 // Movement caused by this command is always valid
388 m_last_good_position = pos;
390 m_time_from_last_teleport = 0.0;
391 m_env->getGameDef()->SendMovePlayer(m_peer_id);
394 void PlayerSAO::setPlayerYaw(const float yaw)
396 v3f rotation(0, yaw, 0);
397 if (m_player && yaw != m_rotation.Y)
398 m_player->setDirty(true);
400 // Set player model yaw, not look view
401 UnitSAO::setRotation(rotation);
404 void PlayerSAO::setFov(const float fov)
406 if (m_player && fov != m_fov)
407 m_player->setDirty(true);
412 void PlayerSAO::setWantedRange(const s16 range)
414 if (m_player && range != m_wanted_range)
415 m_player->setDirty(true);
417 m_wanted_range = range;
420 void PlayerSAO::setPlayerYawAndSend(const float yaw)
423 m_env->getGameDef()->SendMovePlayer(m_peer_id);
426 void PlayerSAO::setLookPitch(const float pitch)
428 if (m_player && pitch != m_pitch)
429 m_player->setDirty(true);
434 void PlayerSAO::setLookPitchAndSend(const float pitch)
437 m_env->getGameDef()->SendMovePlayer(m_peer_id);
440 u16 PlayerSAO::punch(v3f dir,
441 const ToolCapabilities *toolcap,
442 ServerActiveObject *puncher,
443 float time_from_last_punch)
448 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
450 // No effect if PvP disabled or if immortal
451 if (isImmortal() || !g_settings->getBool("enable_pvp")) {
452 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
453 // create message and add to list
459 s32 old_hp = getHP();
460 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
461 time_from_last_punch);
463 PlayerSAO *playersao = m_player->getPlayerSAO();
465 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
466 puncher, time_from_last_punch, toolcap, dir,
469 if (!damage_handled) {
470 setHP((s32)getHP() - (s32)hitparams.hp,
471 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
472 } else { // override client prediction
473 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
474 // create message and add to list
479 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
480 ", hp=" << puncher->getHP() << ") punched " <<
481 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
482 "), damage=" << (old_hp - (s32)getHP()) <<
483 (damage_handled ? " (handled by Lua)" : "") << std::endl;
485 return hitparams.wear;
488 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
492 hp = rangelim(hp, 0, m_prop.hp_max);
495 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
499 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
502 if (hp < oldhp && isImmortal())
507 // Update properties on death
508 if ((hp == 0) != (oldhp == 0))
509 m_properties_sent = false;
512 void PlayerSAO::setBreath(const u16 breath, bool send)
514 if (m_player && breath != m_breath)
515 m_player->setDirty(true);
517 m_breath = rangelim(breath, 0, m_prop.breath_max);
520 m_env->getGameDef()->SendPlayerBreath(this);
523 Inventory *PlayerSAO::getInventory() const
525 return m_player ? &m_player->inventory : nullptr;
528 InventoryLocation PlayerSAO::getInventoryLocation() const
530 InventoryLocation loc;
531 loc.setPlayer(m_player->getName());
535 u16 PlayerSAO::getWieldIndex() const
537 return m_player->getWieldIndex();
540 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
542 return m_player->getWieldedItem(selected, hand);
545 bool PlayerSAO::setWieldedItem(const ItemStack &item)
547 InventoryList *mlist = m_player->inventory.getList(getWieldList());
549 mlist->changeItem(m_player->getWieldIndex(), item);
555 void PlayerSAO::disconnected()
557 m_peer_id = PEER_ID_INEXISTENT;
558 m_pending_removal = true;
561 void PlayerSAO::unlinkPlayerSessionAndSave()
563 assert(m_player->getPlayerSAO() == this);
564 m_player->setPeerId(PEER_ID_INEXISTENT);
565 m_env->savePlayer(m_player);
566 m_player->setPlayerSAO(NULL);
567 m_env->removePlayer(m_player);
570 std::string PlayerSAO::getPropertyPacket()
572 m_prop.is_visible = (true);
573 return generateSetPropertiesCommand(m_prop);
576 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
578 if (m_max_speed_override_time == 0.0f)
579 m_max_speed_override = vel;
581 m_max_speed_override += vel;
583 float accel = MYMIN(m_player->movement_acceleration_default,
584 m_player->movement_acceleration_air);
585 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
589 bool PlayerSAO::checkMovementCheat()
591 if (isAttached() || m_is_singleplayer ||
592 g_settings->getBool("disable_anticheat")) {
593 m_last_good_position = m_base_position;
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 << "Player " << m_player->getName()
660 << " moved too fast; resetting position"
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;