3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "content_sao.h"
21 #include "util/serialize.h"
22 #include "collision.h"
23 #include "environment.h"
24 #include "tool.h" // For ToolCapabilities
27 #include "remoteplayer.h"
29 #include "scripting_server.h"
30 #include "genericobject.h"
35 std::map<u16, ServerActiveObject::Factory> ServerActiveObject::m_types;
41 class TestSAO : public ServerActiveObject
44 TestSAO(ServerEnvironment *env, v3f pos):
45 ServerActiveObject(env, pos),
49 ServerActiveObject::registerType(getType(), create);
51 ActiveObjectType getType() const
52 { return ACTIVEOBJECT_TYPE_TEST; }
54 static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
55 const std::string &data)
57 return new TestSAO(env, pos);
60 void step(float dtime, bool send_recommended)
65 m_pending_removal = true;
69 m_base_position.Y += dtime * BS * 2;
70 if(m_base_position.Y > 8*BS)
71 m_base_position.Y = 2*BS;
73 if (!send_recommended)
83 data += itos(0); // 0 = position
85 data += itos(m_base_position.X);
87 data += itos(m_base_position.Y);
89 data += itos(m_base_position.Z);
91 ActiveObjectMessage aom(getId(), false, data);
92 m_messages_out.push(aom);
96 bool getCollisionBox(aabb3f *toset) const { return false; }
98 virtual bool getSelectionBox(aabb3f *toset) const { return false; }
100 bool collideWithObjects() const { return false; }
107 // Prototype (registers item for deserialization)
108 TestSAO proto_TestSAO(NULL, v3f(0,0,0));
114 UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos):
115 ServerActiveObject(env, pos)
117 // Initialize something to armor groups
118 m_armor_groups["fleshy"] = 100;
121 ServerActiveObject *UnitSAO::getParent() const
123 if (!m_attachment_parent_id)
125 // Check if the parent still exists
126 ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id);
131 void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups)
133 m_armor_groups = armor_groups;
134 m_armor_groups_sent = false;
137 const ItemGroupList &UnitSAO::getArmorGroups()
139 return m_armor_groups;
142 void UnitSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop)
144 // store these so they can be updated to clients
145 m_animation_range = frame_range;
146 m_animation_speed = frame_speed;
147 m_animation_blend = frame_blend;
148 m_animation_loop = frame_loop;
149 m_animation_sent = false;
152 void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop)
154 *frame_range = m_animation_range;
155 *frame_speed = m_animation_speed;
156 *frame_blend = m_animation_blend;
157 *frame_loop = m_animation_loop;
160 void UnitSAO::setAnimationSpeed(float frame_speed)
162 m_animation_speed = frame_speed;
163 m_animation_speed_sent = false;
166 void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation)
168 // store these so they can be updated to clients
169 m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
170 m_bone_position_sent = false;
173 void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation)
175 *position = m_bone_position[bone].X;
176 *rotation = m_bone_position[bone].Y;
179 void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation)
181 // Attachments need to be handled on both the server and client.
182 // If we just attach on the server, we can only copy the position of the parent. Attachments
183 // are still sent to clients at an interval so players might see them lagging, plus we can't
184 // read and attach to skeletal bones.
185 // If we just attach on the client, the server still sees the child at its original location.
186 // This breaks some things so we also give the server the most accurate representation
187 // even if players only see the client changes.
189 int old_parent = m_attachment_parent_id;
190 m_attachment_parent_id = parent_id;
191 m_attachment_bone = bone;
192 m_attachment_position = position;
193 m_attachment_rotation = rotation;
194 m_attachment_sent = false;
196 if (parent_id != old_parent) {
197 onDetach(old_parent);
202 void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
205 *parent_id = m_attachment_parent_id;
206 *bone = m_attachment_bone;
207 *position = m_attachment_position;
208 *rotation = m_attachment_rotation;
211 void UnitSAO::clearChildAttachments()
213 for (int child_id : m_attachment_child_ids) {
214 // Child can be NULL if it was deleted earlier
215 if (ServerActiveObject *child = m_env->getActiveObject(child_id))
216 child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
218 m_attachment_child_ids.clear();
221 void UnitSAO::clearParentAttachment()
223 ServerActiveObject *parent = nullptr;
224 if (m_attachment_parent_id) {
225 parent = m_env->getActiveObject(m_attachment_parent_id);
226 setAttachment(0, "", m_attachment_position, m_attachment_rotation);
228 setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
232 parent->removeAttachmentChild(m_id);
235 void UnitSAO::addAttachmentChild(int child_id)
237 m_attachment_child_ids.insert(child_id);
240 void UnitSAO::removeAttachmentChild(int child_id)
242 m_attachment_child_ids.erase(child_id);
245 const std::unordered_set<int> &UnitSAO::getAttachmentChildIds()
247 return m_attachment_child_ids;
250 void UnitSAO::onAttach(int parent_id)
255 ServerActiveObject *parent = m_env->getActiveObject(parent_id);
257 if (!parent || parent->isGone())
258 return; // Do not try to notify soon gone parent
260 if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) {
261 // Call parent's on_attach field
262 m_env->getScriptIface()->luaentity_on_attach_child(parent_id, this);
266 void UnitSAO::onDetach(int parent_id)
271 ServerActiveObject *parent = m_env->getActiveObject(parent_id);
272 if (getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
273 m_env->getScriptIface()->luaentity_on_detach(m_id, parent);
275 if (!parent || parent->isGone())
276 return; // Do not try to notify soon gone parent
278 if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
279 m_env->getScriptIface()->luaentity_on_detach_child(parent_id, this);
282 ObjectProperties* UnitSAO::accessObjectProperties()
287 void UnitSAO::notifyObjectPropertiesModified()
289 m_properties_sent = false;
296 // Prototype (registers item for deserialization)
297 LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", "");
299 LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
300 const std::string &name, const std::string &state):
305 // Only register type if no environment supplied
307 ServerActiveObject::registerType(getType(), create);
312 LuaEntitySAO::~LuaEntitySAO()
315 m_env->getScriptIface()->luaentity_Remove(m_id);
318 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
319 m_env->deleteParticleSpawner(attached_particle_spawner, false);
323 void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
325 ServerActiveObject::addedToEnvironment(dtime_s);
327 // Create entity from name
328 m_registered = m_env->getScriptIface()->
329 luaentity_Add(m_id, m_init_name.c_str());
333 m_env->getScriptIface()->
334 luaentity_GetProperties(m_id, &m_prop);
335 // Initialize HP from properties
336 m_hp = m_prop.hp_max;
337 // Activate entity, supplying serialized state
338 m_env->getScriptIface()->
339 luaentity_Activate(m_id, m_init_state, dtime_s);
341 m_prop.infotext = m_init_name;
345 ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
346 const std::string &data)
354 while (!data.empty()) { // breakable, run for one iteration
355 std::istringstream is(data, std::ios::binary);
356 // 'version' does not allow to incrementally extend the parameter list thus
357 // we need another variable to build on top of 'version=1'. Ugly hack but worksâ„¢
359 u8 version = readU8(is);
361 name = deSerializeString(is);
362 state = deSerializeLongString(is);
368 velocity = readV3F1000(is);
369 // yaw must be yaw to be backwards-compatible
370 rotation.Y = readF1000(is);
372 if (is.good()) // EOF for old formats
373 version2 = readU8(is);
375 if (version2 < 1) // PROTOCOL_VERSION < 37
379 rotation.X = readF1000(is);
380 rotation.Z = readF1000(is);
388 infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\""
389 << state << "\")" << std::endl;
390 LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
392 sao->m_velocity = velocity;
393 sao->m_rotation = rotation;
397 void LuaEntitySAO::step(float dtime, bool send_recommended)
399 if(!m_properties_sent)
401 m_properties_sent = true;
402 std::string str = getPropertyPacket();
403 // create message and add to list
404 ActiveObjectMessage aom(getId(), true, str);
405 m_messages_out.push(aom);
408 // If attached, check that our parent is still there. If it isn't, detach.
409 if(m_attachment_parent_id && !isAttached())
411 m_attachment_parent_id = 0;
412 m_attachment_bone = "";
413 m_attachment_position = v3f(0,0,0);
414 m_attachment_rotation = v3f(0,0,0);
415 sendPosition(false, true);
418 m_last_sent_position_timer += dtime;
420 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
421 // If the object gets detached this comes into effect automatically from the last known origin
424 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
425 m_base_position = pos;
426 m_velocity = v3f(0,0,0);
427 m_acceleration = v3f(0,0,0);
432 aabb3f box = m_prop.collisionbox;
435 collisionMoveResult moveresult;
436 f32 pos_max_d = BS*0.25; // Distance per iteration
437 v3f p_pos = m_base_position;
438 v3f p_velocity = m_velocity;
439 v3f p_acceleration = m_acceleration;
440 moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
441 pos_max_d, box, m_prop.stepheight, dtime,
442 &p_pos, &p_velocity, p_acceleration,
443 this, m_prop.collideWithObjects);
446 m_base_position = p_pos;
447 m_velocity = p_velocity;
448 m_acceleration = p_acceleration;
450 m_base_position += dtime * m_velocity + 0.5 * dtime
451 * dtime * m_acceleration;
452 m_velocity += dtime * m_acceleration;
455 if (m_prop.automatic_face_movement_dir &&
456 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
457 float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
458 + m_prop.automatic_face_movement_dir_offset;
459 float max_rotation_per_sec =
460 m_prop.automatic_face_movement_max_rotation_per_sec;
462 if (max_rotation_per_sec > 0) {
463 m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
464 wrappedApproachShortest(m_rotation.Y, target_yaw,
465 dtime * max_rotation_per_sec, 360.f);
467 // Negative values of max_rotation_per_sec mean disabled.
468 m_rotation.Y = target_yaw;
474 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
477 if (!send_recommended)
482 // TODO: force send when acceleration changes enough?
483 float minchange = 0.2*BS;
484 if(m_last_sent_position_timer > 1.0){
486 } else if(m_last_sent_position_timer > 0.2){
489 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
490 move_d += m_last_sent_move_precision;
491 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
492 if (move_d > minchange || vel_d > minchange ||
493 std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f ||
494 std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f ||
495 std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) {
497 sendPosition(true, false);
501 if (!m_armor_groups_sent) {
502 m_armor_groups_sent = true;
503 std::string str = gob_cmd_update_armor_groups(
505 // create message and add to list
506 ActiveObjectMessage aom(getId(), true, str);
507 m_messages_out.push(aom);
510 if (!m_animation_sent) {
511 m_animation_sent = true;
512 std::string str = gob_cmd_update_animation(
513 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
514 // create message and add to list
515 ActiveObjectMessage aom(getId(), true, str);
516 m_messages_out.push(aom);
519 if (!m_animation_speed_sent) {
520 m_animation_speed_sent = true;
521 std::string str = gob_cmd_update_animation_speed(m_animation_speed);
522 // create message and add to list
523 ActiveObjectMessage aom(getId(), true, str);
524 m_messages_out.push(aom);
527 if (!m_bone_position_sent) {
528 m_bone_position_sent = true;
529 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
530 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
531 std::string str = gob_cmd_update_bone_position((*ii).first,
532 (*ii).second.X, (*ii).second.Y);
533 // create message and add to list
534 ActiveObjectMessage aom(getId(), true, str);
535 m_messages_out.push(aom);
539 if (!m_attachment_sent) {
540 m_attachment_sent = true;
541 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
542 // create message and add to list
543 ActiveObjectMessage aom(getId(), true, str);
544 m_messages_out.push(aom);
548 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
550 std::ostringstream os(std::ios::binary);
552 // PROTOCOL_VERSION >= 37
553 writeU8(os, 1); // version
554 os << serializeString(""); // name
555 writeU8(os, 0); // is_player
556 writeU16(os, getId()); //id
557 writeV3F32(os, m_base_position);
558 writeV3F32(os, m_rotation);
561 std::ostringstream msg_os(std::ios::binary);
562 msg_os << serializeLongString(getPropertyPacket()); // message 1
563 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
564 msg_os << serializeLongString(gob_cmd_update_animation(
565 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
566 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
567 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
568 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
569 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
571 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
572 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
573 int message_count = 4 + m_bone_position.size();
574 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
575 (ii != m_attachment_child_ids.end()); ++ii) {
576 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
578 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
579 obj->getClientInitializationData(protocol_version)));
583 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
586 writeU8(os, message_count);
587 os.write(msg_os.str().c_str(), msg_os.str().size());
593 void LuaEntitySAO::getStaticData(std::string *result) const
595 verbosestream<<FUNCTION_NAME<<std::endl;
596 std::ostringstream os(std::ios::binary);
597 // version must be 1 to keep backwards-compatibility. See version2
600 os<<serializeString(m_init_name);
603 std::string state = m_env->getScriptIface()->
604 luaentity_GetStaticdata(m_id);
605 os<<serializeLongString(state);
607 os<<serializeLongString(m_init_state);
610 writeV3F1000(os, m_velocity);
612 writeF1000(os, m_rotation.Y);
614 // version2. Increase this variable for new values
615 writeU8(os, 1); // PROTOCOL_VERSION >= 37
617 writeF1000(os, m_rotation.X);
618 writeF1000(os, m_rotation.Z);
620 // <write new values>
625 int LuaEntitySAO::punch(v3f dir,
626 const ToolCapabilities *toolcap,
627 ServerActiveObject *puncher,
628 float time_from_last_punch)
631 // Delete unknown LuaEntities when punched
632 m_pending_removal = true;
636 ItemStack *punchitem = NULL;
637 ItemStack punchitem_static;
639 punchitem_static = puncher->getWieldedItem();
640 punchitem = &punchitem_static;
643 PunchDamageResult result = getPunchDamage(
647 time_from_last_punch);
649 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
650 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
652 if (!damage_handled) {
653 if (result.did_punch) {
654 setHP((s32)getHP() - result.damage,
655 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
657 if (result.damage > 0) {
658 std::string punchername = puncher ? puncher->getDescription() : "nil";
660 actionstream << getDescription() << " punched by "
661 << punchername << ", damage " << result.damage
662 << " hp, health now " << getHP() << " hp" << std::endl;
665 std::string str = gob_cmd_punched(getHP());
666 // create message and add to list
667 ActiveObjectMessage aom(getId(), true, str);
668 m_messages_out.push(aom);
672 if (getHP() == 0 && !isGone()) {
673 m_pending_removal = true;
674 clearParentAttachment();
675 clearChildAttachments();
676 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
682 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
687 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
690 void LuaEntitySAO::setPos(const v3f &pos)
694 m_base_position = pos;
695 sendPosition(false, true);
698 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
702 m_base_position = pos;
704 sendPosition(true, true);
707 float LuaEntitySAO::getMinimumSavedMovement()
712 std::string LuaEntitySAO::getDescription()
714 std::ostringstream os(std::ios::binary);
715 os<<"LuaEntitySAO at (";
716 os<<(m_base_position.X/BS)<<",";
717 os<<(m_base_position.Y/BS)<<",";
718 os<<(m_base_position.Z/BS);
723 void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
725 m_hp = rangelim(hp, 0, U16_MAX);
728 u16 LuaEntitySAO::getHP() const
733 void LuaEntitySAO::setVelocity(v3f velocity)
735 m_velocity = velocity;
738 v3f LuaEntitySAO::getVelocity()
743 void LuaEntitySAO::setAcceleration(v3f acceleration)
745 m_acceleration = acceleration;
748 v3f LuaEntitySAO::getAcceleration()
750 return m_acceleration;
753 void LuaEntitySAO::setTextureMod(const std::string &mod)
755 std::string str = gob_cmd_set_texture_mod(mod);
756 m_current_texture_modifier = mod;
757 // create message and add to list
758 ActiveObjectMessage aom(getId(), true, str);
759 m_messages_out.push(aom);
762 std::string LuaEntitySAO::getTextureMod() const
764 return m_current_texture_modifier;
767 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
768 bool select_horiz_by_yawpitch)
770 std::string str = gob_cmd_set_sprite(
774 select_horiz_by_yawpitch
776 // create message and add to list
777 ActiveObjectMessage aom(getId(), true, str);
778 m_messages_out.push(aom);
781 std::string LuaEntitySAO::getName()
786 std::string LuaEntitySAO::getPropertyPacket()
788 return gob_cmd_set_properties(m_prop);
791 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
793 // If the object is attached client-side, don't waste bandwidth sending its position to clients
797 m_last_sent_move_precision = m_base_position.getDistanceFrom(
798 m_last_sent_position);
799 m_last_sent_position_timer = 0;
800 m_last_sent_position = m_base_position;
801 m_last_sent_velocity = m_velocity;
802 //m_last_sent_acceleration = m_acceleration;
803 m_last_sent_rotation = m_rotation;
805 float update_interval = m_env->getSendRecommendedInterval();
807 std::string str = gob_cmd_update_position(
816 // create message and add to list
817 ActiveObjectMessage aom(getId(), false, str);
818 m_messages_out.push(aom);
821 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
825 //update collision box
826 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
827 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
829 toset->MinEdge += m_base_position;
830 toset->MaxEdge += m_base_position;
838 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
840 if (!m_prop.is_visible || !m_prop.pointable) {
844 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
845 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
850 bool LuaEntitySAO::collideWithObjects() const
852 return m_prop.collideWithObjects;
859 // No prototype, PlayerSAO does not need to be deserialized
861 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
862 bool is_singleplayer):
863 UnitSAO(env_, v3f(0,0,0)),
866 m_is_singleplayer(is_singleplayer)
868 assert(m_peer_id != 0); // pre-condition
870 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
871 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
872 m_prop.physical = false;
874 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
875 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
876 m_prop.pointable = true;
877 // Start of default appearance, this should be overwritten by Lua
878 m_prop.visual = "upright_sprite";
879 m_prop.visual_size = v3f(1, 2, 1);
880 m_prop.textures.clear();
881 m_prop.textures.emplace_back("player.png");
882 m_prop.textures.emplace_back("player_back.png");
883 m_prop.colors.clear();
884 m_prop.colors.emplace_back(255, 255, 255, 255);
885 m_prop.spritediv = v2s16(1,1);
886 m_prop.eye_height = 1.625f;
887 // End of default appearance
888 m_prop.is_visible = true;
889 m_prop.backface_culling = false;
890 m_prop.makes_footstep_sound = true;
891 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
892 m_hp = m_prop.hp_max;
893 m_breath = m_prop.breath_max;
894 // Disable zoom in survival mode using a value of 0
895 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
898 PlayerSAO::~PlayerSAO()
900 if(m_inventory != &m_player->inventory)
904 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
909 m_inventory = &m_player->inventory;
912 v3f PlayerSAO::getEyeOffset() const
914 return v3f(0, BS * m_prop.eye_height, 0);
917 std::string PlayerSAO::getDescription()
919 return std::string("player ") + m_player->getName();
922 // Called after id has been set and has been inserted in environment
923 void PlayerSAO::addedToEnvironment(u32 dtime_s)
925 ServerActiveObject::addedToEnvironment(dtime_s);
926 ServerActiveObject::setBasePosition(m_base_position);
927 m_player->setPlayerSAO(this);
928 m_player->setPeerId(m_peer_id);
929 m_last_good_position = m_base_position;
932 // Called before removing from environment
933 void PlayerSAO::removingFromEnvironment()
935 ServerActiveObject::removingFromEnvironment();
936 if (m_player->getPlayerSAO() == this) {
937 unlinkPlayerSessionAndSave();
938 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
939 m_env->deleteParticleSpawner(attached_particle_spawner, false);
944 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
946 std::ostringstream os(std::ios::binary);
949 writeU8(os, 1); // version
950 os << serializeString(m_player->getName()); // name
951 writeU8(os, 1); // is_player
952 writeS16(os, getId()); // id
953 writeV3F32(os, m_base_position);
954 writeV3F32(os, m_rotation);
955 writeU16(os, getHP());
957 std::ostringstream msg_os(std::ios::binary);
958 msg_os << serializeLongString(getPropertyPacket()); // message 1
959 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
960 msg_os << serializeLongString(gob_cmd_update_animation(
961 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
962 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
963 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
964 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
965 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
967 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
968 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
969 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
970 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
971 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
972 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
973 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
974 int message_count = 6 + m_bone_position.size();
975 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
976 ii != m_attachment_child_ids.end(); ++ii) {
977 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
979 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
980 obj->getClientInitializationData(protocol_version)));
984 writeU8(os, message_count);
985 os.write(msg_os.str().c_str(), msg_os.str().size());
991 void PlayerSAO::getStaticData(std::string * result) const
993 FATAL_ERROR("Deprecated function");
996 void PlayerSAO::step(float dtime, bool send_recommended)
998 if (m_drowning_interval.step(dtime, 2.0f)) {
999 // Get nose/mouth position, approximate with eye position
1000 v3s16 p = floatToInt(getEyePosition(), BS);
1001 MapNode n = m_env->getMap().getNodeNoEx(p);
1002 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1003 // If node generates drown
1004 if (c.drowning > 0 && m_hp > 0) {
1006 setBreath(m_breath - 1);
1008 // No more breath, damage player
1009 if (m_breath == 0) {
1010 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
1011 setHP(m_hp - c.drowning, reason);
1012 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1017 if (m_breathing_interval.step(dtime, 0.5f)) {
1018 // Get nose/mouth position, approximate with eye position
1019 v3s16 p = floatToInt(getEyePosition(), BS);
1020 MapNode n = m_env->getMap().getNodeNoEx(p);
1021 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1022 // If player is alive & no drowning & not in ignore, breathe
1023 if (m_breath < m_prop.breath_max &&
1024 c.drowning == 0 && n.getContent() != CONTENT_IGNORE && m_hp > 0)
1025 setBreath(m_breath + 1);
1028 if (m_node_hurt_interval.step(dtime, 1.0f)) {
1029 u32 damage_per_second = 0;
1030 std::string nodename;
1031 // Lowest and highest damage points are 0.1 within collisionbox
1032 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1034 // Sequence of damage points, starting 0.1 above feet and progressing
1035 // upwards in 1 node intervals, stopping below top damage point.
1036 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1037 v3s16 p = floatToInt(m_base_position +
1038 v3f(0.0f, dam_height * BS, 0.0f), BS);
1039 MapNode n = m_env->getMap().getNodeNoEx(p);
1040 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1041 if (c.damage_per_second > damage_per_second) {
1042 damage_per_second = c.damage_per_second;
1048 v3s16 ptop = floatToInt(m_base_position +
1049 v3f(0.0f, dam_top * BS, 0.0f), BS);
1050 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1051 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
1052 if (c.damage_per_second > damage_per_second) {
1053 damage_per_second = c.damage_per_second;
1057 if (damage_per_second != 0 && m_hp > 0) {
1058 s32 newhp = (s32)m_hp - (s32)damage_per_second;
1059 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
1060 setHP(newhp, reason);
1061 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1065 if (!m_properties_sent) {
1066 m_properties_sent = true;
1067 std::string str = getPropertyPacket();
1068 // create message and add to list
1069 ActiveObjectMessage aom(getId(), true, str);
1070 m_messages_out.push(aom);
1073 // If attached, check that our parent is still there. If it isn't, detach.
1074 if (m_attachment_parent_id && !isAttached()) {
1075 m_attachment_parent_id = 0;
1076 m_attachment_bone = "";
1077 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1078 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1079 setBasePosition(m_last_good_position);
1080 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1083 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1085 // Set lag pool maximums based on estimated lag
1086 const float LAG_POOL_MIN = 5.0f;
1087 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1088 if(lag_pool_max < LAG_POOL_MIN)
1089 lag_pool_max = LAG_POOL_MIN;
1090 m_dig_pool.setMax(lag_pool_max);
1091 m_move_pool.setMax(lag_pool_max);
1093 // Increment cheat prevention timers
1094 m_dig_pool.add(dtime);
1095 m_move_pool.add(dtime);
1096 m_time_from_last_teleport += dtime;
1097 m_time_from_last_punch += dtime;
1098 m_nocheat_dig_time += dtime;
1100 // Each frame, parent position is copied if the object is attached,
1101 // otherwise it's calculated normally.
1102 // If the object gets detached this comes into effect automatically from
1103 // the last known origin.
1105 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1106 m_last_good_position = pos;
1107 setBasePosition(pos);
1110 if (!send_recommended)
1113 // If the object is attached client-side, don't waste bandwidth sending its
1114 // position or rotation to clients.
1115 if (m_position_not_sent && !isAttached()) {
1116 m_position_not_sent = false;
1117 float update_interval = m_env->getSendRecommendedInterval();
1119 if (isAttached()) // Just in case we ever do send attachment position too
1120 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1122 pos = m_base_position;
1124 std::string str = gob_cmd_update_position(
1126 v3f(0.0f, 0.0f, 0.0f),
1127 v3f(0.0f, 0.0f, 0.0f),
1133 // create message and add to list
1134 ActiveObjectMessage aom(getId(), false, str);
1135 m_messages_out.push(aom);
1138 if (!m_armor_groups_sent) {
1139 m_armor_groups_sent = true;
1140 std::string str = gob_cmd_update_armor_groups(
1142 // create message and add to list
1143 ActiveObjectMessage aom(getId(), true, str);
1144 m_messages_out.push(aom);
1147 if (!m_physics_override_sent) {
1148 m_physics_override_sent = true;
1149 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1150 m_physics_override_jump, m_physics_override_gravity,
1151 m_physics_override_sneak, m_physics_override_sneak_glitch,
1152 m_physics_override_new_move);
1153 // create message and add to list
1154 ActiveObjectMessage aom(getId(), true, str);
1155 m_messages_out.push(aom);
1158 if (!m_animation_sent) {
1159 m_animation_sent = true;
1160 std::string str = gob_cmd_update_animation(
1161 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1162 // create message and add to list
1163 ActiveObjectMessage aom(getId(), true, str);
1164 m_messages_out.push(aom);
1167 if (!m_bone_position_sent) {
1168 m_bone_position_sent = true;
1169 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1170 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1171 std::string str = gob_cmd_update_bone_position((*ii).first,
1172 (*ii).second.X, (*ii).second.Y);
1173 // create message and add to list
1174 ActiveObjectMessage aom(getId(), true, str);
1175 m_messages_out.push(aom);
1179 if (!m_attachment_sent) {
1180 m_attachment_sent = true;
1181 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1182 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1183 // create message and add to list
1184 ActiveObjectMessage aom(getId(), true, str);
1185 m_messages_out.push(aom);
1189 void PlayerSAO::setBasePosition(const v3f &position)
1191 if (m_player && position != m_base_position)
1192 m_player->setDirty(true);
1194 // This needs to be ran for attachments too
1195 ServerActiveObject::setBasePosition(position);
1197 // Updating is not wanted/required for player migration
1199 m_position_not_sent = true;
1203 void PlayerSAO::setPos(const v3f &pos)
1208 // Send mapblock of target location
1209 v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
1210 m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
1212 setBasePosition(pos);
1213 // Movement caused by this command is always valid
1214 m_last_good_position = pos;
1215 m_move_pool.empty();
1216 m_time_from_last_teleport = 0.0;
1217 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1220 void PlayerSAO::moveTo(v3f pos, bool continuous)
1225 setBasePosition(pos);
1226 // Movement caused by this command is always valid
1227 m_last_good_position = pos;
1228 m_move_pool.empty();
1229 m_time_from_last_teleport = 0.0;
1230 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1233 void PlayerSAO::setPlayerYaw(const float yaw)
1235 v3f rotation(0, yaw, 0);
1236 if (m_player && yaw != m_rotation.Y)
1237 m_player->setDirty(true);
1239 // Set player model yaw, not look view
1240 UnitSAO::setRotation(rotation);
1243 void PlayerSAO::setFov(const float fov)
1245 if (m_player && fov != m_fov)
1246 m_player->setDirty(true);
1251 void PlayerSAO::setWantedRange(const s16 range)
1253 if (m_player && range != m_wanted_range)
1254 m_player->setDirty(true);
1256 m_wanted_range = range;
1259 void PlayerSAO::setPlayerYawAndSend(const float yaw)
1262 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1265 void PlayerSAO::setLookPitch(const float pitch)
1267 if (m_player && pitch != m_pitch)
1268 m_player->setDirty(true);
1273 void PlayerSAO::setLookPitchAndSend(const float pitch)
1275 setLookPitch(pitch);
1276 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1279 int PlayerSAO::punch(v3f dir,
1280 const ToolCapabilities *toolcap,
1281 ServerActiveObject *puncher,
1282 float time_from_last_punch)
1287 // No effect if PvP disabled
1288 if (!g_settings->getBool("enable_pvp")) {
1289 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1290 std::string str = gob_cmd_punched(getHP());
1291 // create message and add to list
1292 ActiveObjectMessage aom(getId(), true, str);
1293 m_messages_out.push(aom);
1298 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1299 time_from_last_punch);
1301 std::string punchername = "nil";
1304 punchername = puncher->getDescription();
1306 PlayerSAO *playersao = m_player->getPlayerSAO();
1308 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1309 puncher, time_from_last_punch, toolcap, dir,
1312 if (!damage_handled) {
1313 setHP((s32)getHP() - (s32)hitparams.hp,
1314 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1315 } else { // override client prediction
1316 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1317 std::string str = gob_cmd_punched(getHP());
1318 // create message and add to list
1319 ActiveObjectMessage aom(getId(), true, str);
1320 m_messages_out.push(aom);
1325 actionstream << "Player " << m_player->getName() << " punched by "
1327 if (!damage_handled) {
1328 actionstream << ", damage " << hitparams.hp << " HP";
1330 actionstream << ", damage handled by lua";
1332 actionstream << std::endl;
1334 return hitparams.wear;
1337 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
1341 hp = rangelim(hp, 0, m_prop.hp_max);
1343 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1347 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
1349 if (hp < oldhp && !g_settings->getBool("enable_damage"))
1354 // Update properties on death
1355 if ((hp == 0) != (oldhp == 0))
1356 m_properties_sent = false;
1359 void PlayerSAO::setBreath(const u16 breath, bool send)
1361 if (m_player && breath != m_breath)
1362 m_player->setDirty(true);
1364 m_breath = rangelim(breath, 0, m_prop.breath_max);
1367 m_env->getGameDef()->SendPlayerBreath(this);
1370 Inventory* PlayerSAO::getInventory()
1374 const Inventory* PlayerSAO::getInventory() const
1379 InventoryLocation PlayerSAO::getInventoryLocation() const
1381 InventoryLocation loc;
1382 loc.setPlayer(m_player->getName());
1386 std::string PlayerSAO::getWieldList() const
1391 ItemStack PlayerSAO::getWieldedItem() const
1393 const Inventory *inv = getInventory();
1395 const InventoryList *mlist = inv->getList(getWieldList());
1396 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1397 ret = mlist->getItem(getWieldIndex());
1401 ItemStack PlayerSAO::getWieldedItemOrHand() const
1403 const Inventory *inv = getInventory();
1405 const InventoryList *mlist = inv->getList(getWieldList());
1406 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1407 ret = mlist->getItem(getWieldIndex());
1408 if (ret.name.empty()) {
1409 const InventoryList *hlist = inv->getList("hand");
1411 ret = hlist->getItem(0);
1416 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1418 Inventory *inv = getInventory();
1420 InventoryList *mlist = inv->getList(getWieldList());
1422 mlist->changeItem(getWieldIndex(), item);
1429 int PlayerSAO::getWieldIndex() const
1431 return m_wield_index;
1434 void PlayerSAO::setWieldIndex(int i)
1436 if(i != m_wield_index) {
1441 void PlayerSAO::disconnected()
1444 m_pending_removal = true;
1447 void PlayerSAO::unlinkPlayerSessionAndSave()
1449 assert(m_player->getPlayerSAO() == this);
1450 m_player->setPeerId(PEER_ID_INEXISTENT);
1451 m_env->savePlayer(m_player);
1452 m_player->setPlayerSAO(NULL);
1453 m_env->removePlayer(m_player);
1456 std::string PlayerSAO::getPropertyPacket()
1458 m_prop.is_visible = (true);
1459 return gob_cmd_set_properties(m_prop);
1462 bool PlayerSAO::checkMovementCheat()
1464 if (isAttached() || m_is_singleplayer ||
1465 g_settings->getBool("disable_anticheat")) {
1466 m_last_good_position = m_base_position;
1470 bool cheated = false;
1472 Check player movements
1474 NOTE: Actually the server should handle player physics like the
1475 client does and compare player's position to what is calculated
1476 on our side. This is required when eg. players fly due to an
1477 explosion. Altough a node-based alternative might be possible
1478 too, and much more lightweight.
1481 float player_max_walk = 0; // horizontal movement
1482 float player_max_jump = 0; // vertical upwards movement
1484 if (m_privs.count("fast") != 0)
1485 player_max_walk = m_player->movement_speed_fast; // Fast speed
1487 player_max_walk = m_player->movement_speed_walk; // Normal speed
1488 player_max_walk *= m_physics_override_speed;
1489 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1490 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1491 // until this can be verified correctly, tolerate higher jumping speeds
1492 player_max_jump *= 2.0;
1494 // Don't divide by zero!
1495 if (player_max_walk < 0.0001f)
1496 player_max_walk = 0.0001f;
1497 if (player_max_jump < 0.0001f)
1498 player_max_jump = 0.0001f;
1500 v3f diff = (m_base_position - m_last_good_position);
1501 float d_vert = diff.Y;
1503 float d_horiz = diff.getLength();
1504 float required_time = d_horiz / player_max_walk;
1506 // FIXME: Checking downwards movement is not easily possible currently,
1507 // the server could calculate speed differences to examine the gravity
1509 // In certain cases (water, ladders) walking speed is applied vertically
1510 float s = MYMAX(player_max_jump, player_max_walk);
1511 required_time = MYMAX(required_time, d_vert / s);
1514 if (m_move_pool.grab(required_time)) {
1515 m_last_good_position = m_base_position;
1517 const float LAG_POOL_MIN = 5.0;
1518 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1519 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1520 if (m_time_from_last_teleport > lag_pool_max) {
1521 actionstream << "Player " << m_player->getName()
1522 << " moved too fast; resetting position"
1526 setBasePosition(m_last_good_position);
1531 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1533 //update collision box
1534 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1535 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1537 toset->MinEdge += m_base_position;
1538 toset->MaxEdge += m_base_position;
1542 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1544 if (!m_prop.is_visible || !m_prop.pointable) {
1548 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1549 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1554 float PlayerSAO::getZoomFOV() const
1556 return m_prop.zoom_fov;