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 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
638 s32 old_hp = getHP();
639 const ItemStack &punchitem = puncher->getWieldedItem();
641 PunchDamageResult result = getPunchDamage(
645 time_from_last_punch);
647 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
648 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
650 if (!damage_handled) {
651 if (result.did_punch) {
652 setHP((s32)getHP() - result.damage,
653 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
655 std::string str = gob_cmd_punched(getHP());
656 // create message and add to list
657 ActiveObjectMessage aom(getId(), true, str);
658 m_messages_out.push(aom);
662 if (getHP() == 0 && !isGone()) {
663 m_pending_removal = true;
664 clearParentAttachment();
665 clearChildAttachments();
666 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
669 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
670 ", hp=" << puncher->getHP() << ") punched " <<
671 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
672 "), damage=" << (old_hp - (s32)getHP()) <<
673 (damage_handled ? " (handled by Lua)" : "") << std::endl;
678 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
683 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
686 void LuaEntitySAO::setPos(const v3f &pos)
690 m_base_position = pos;
691 sendPosition(false, true);
694 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
698 m_base_position = pos;
700 sendPosition(true, true);
703 float LuaEntitySAO::getMinimumSavedMovement()
708 std::string LuaEntitySAO::getDescription()
710 std::ostringstream os(std::ios::binary);
711 os<<"LuaEntitySAO at (";
712 os<<(m_base_position.X/BS)<<",";
713 os<<(m_base_position.Y/BS)<<",";
714 os<<(m_base_position.Z/BS);
719 void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
721 m_hp = rangelim(hp, 0, U16_MAX);
724 u16 LuaEntitySAO::getHP() const
729 void LuaEntitySAO::setVelocity(v3f velocity)
731 m_velocity = velocity;
734 v3f LuaEntitySAO::getVelocity()
739 void LuaEntitySAO::setAcceleration(v3f acceleration)
741 m_acceleration = acceleration;
744 v3f LuaEntitySAO::getAcceleration()
746 return m_acceleration;
749 void LuaEntitySAO::setTextureMod(const std::string &mod)
751 std::string str = gob_cmd_set_texture_mod(mod);
752 m_current_texture_modifier = mod;
753 // create message and add to list
754 ActiveObjectMessage aom(getId(), true, str);
755 m_messages_out.push(aom);
758 std::string LuaEntitySAO::getTextureMod() const
760 return m_current_texture_modifier;
763 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
764 bool select_horiz_by_yawpitch)
766 std::string str = gob_cmd_set_sprite(
770 select_horiz_by_yawpitch
772 // create message and add to list
773 ActiveObjectMessage aom(getId(), true, str);
774 m_messages_out.push(aom);
777 std::string LuaEntitySAO::getName()
782 std::string LuaEntitySAO::getPropertyPacket()
784 return gob_cmd_set_properties(m_prop);
787 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
789 // If the object is attached client-side, don't waste bandwidth sending its position to clients
793 m_last_sent_move_precision = m_base_position.getDistanceFrom(
794 m_last_sent_position);
795 m_last_sent_position_timer = 0;
796 m_last_sent_position = m_base_position;
797 m_last_sent_velocity = m_velocity;
798 //m_last_sent_acceleration = m_acceleration;
799 m_last_sent_rotation = m_rotation;
801 float update_interval = m_env->getSendRecommendedInterval();
803 std::string str = gob_cmd_update_position(
812 // create message and add to list
813 ActiveObjectMessage aom(getId(), false, str);
814 m_messages_out.push(aom);
817 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
821 //update collision box
822 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
823 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
825 toset->MinEdge += m_base_position;
826 toset->MaxEdge += m_base_position;
834 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
836 if (!m_prop.is_visible || !m_prop.pointable) {
840 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
841 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
846 bool LuaEntitySAO::collideWithObjects() const
848 return m_prop.collideWithObjects;
855 // No prototype, PlayerSAO does not need to be deserialized
857 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
858 bool is_singleplayer):
859 UnitSAO(env_, v3f(0,0,0)),
862 m_is_singleplayer(is_singleplayer)
864 assert(m_peer_id != 0); // pre-condition
866 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
867 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
868 m_prop.physical = false;
870 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
871 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
872 m_prop.pointable = true;
873 // Start of default appearance, this should be overwritten by Lua
874 m_prop.visual = "upright_sprite";
875 m_prop.visual_size = v3f(1, 2, 1);
876 m_prop.textures.clear();
877 m_prop.textures.emplace_back("player.png");
878 m_prop.textures.emplace_back("player_back.png");
879 m_prop.colors.clear();
880 m_prop.colors.emplace_back(255, 255, 255, 255);
881 m_prop.spritediv = v2s16(1,1);
882 m_prop.eye_height = 1.625f;
883 // End of default appearance
884 m_prop.is_visible = true;
885 m_prop.backface_culling = false;
886 m_prop.makes_footstep_sound = true;
887 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
888 m_hp = m_prop.hp_max;
889 m_breath = m_prop.breath_max;
890 // Disable zoom in survival mode using a value of 0
891 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
893 if (!g_settings->getBool("enable_damage"))
894 m_armor_groups["immortal"] = 1;
897 PlayerSAO::~PlayerSAO()
899 if(m_inventory != &m_player->inventory)
903 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
908 m_inventory = &m_player->inventory;
911 v3f PlayerSAO::getEyeOffset() const
913 return v3f(0, BS * m_prop.eye_height, 0);
916 std::string PlayerSAO::getDescription()
918 return std::string("player ") + m_player->getName();
921 // Called after id has been set and has been inserted in environment
922 void PlayerSAO::addedToEnvironment(u32 dtime_s)
924 ServerActiveObject::addedToEnvironment(dtime_s);
925 ServerActiveObject::setBasePosition(m_base_position);
926 m_player->setPlayerSAO(this);
927 m_player->setPeerId(m_peer_id);
928 m_last_good_position = m_base_position;
931 // Called before removing from environment
932 void PlayerSAO::removingFromEnvironment()
934 ServerActiveObject::removingFromEnvironment();
935 if (m_player->getPlayerSAO() == this) {
936 unlinkPlayerSessionAndSave();
937 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
938 m_env->deleteParticleSpawner(attached_particle_spawner, false);
943 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
945 std::ostringstream os(std::ios::binary);
948 writeU8(os, 1); // version
949 os << serializeString(m_player->getName()); // name
950 writeU8(os, 1); // is_player
951 writeS16(os, getId()); // id
952 writeV3F32(os, m_base_position);
953 writeV3F32(os, m_rotation);
954 writeU16(os, getHP());
956 std::ostringstream msg_os(std::ios::binary);
957 msg_os << serializeLongString(getPropertyPacket()); // message 1
958 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
959 msg_os << serializeLongString(gob_cmd_update_animation(
960 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
961 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
962 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
963 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
964 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
966 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
967 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
968 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
969 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
970 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
971 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
972 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
973 int message_count = 6 + m_bone_position.size();
974 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
975 ii != m_attachment_child_ids.end(); ++ii) {
976 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
978 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
979 obj->getClientInitializationData(protocol_version)));
983 writeU8(os, message_count);
984 os.write(msg_os.str().c_str(), msg_os.str().size());
990 void PlayerSAO::getStaticData(std::string * result) const
992 FATAL_ERROR("Deprecated function");
995 void PlayerSAO::step(float dtime, bool send_recommended)
997 if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
998 // Get nose/mouth position, approximate with eye position
999 v3s16 p = floatToInt(getEyePosition(), BS);
1000 MapNode n = m_env->getMap().getNodeNoEx(p);
1001 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1002 // If node generates drown
1003 if (c.drowning > 0 && m_hp > 0) {
1005 setBreath(m_breath - 1);
1007 // No more breath, damage player
1008 if (m_breath == 0) {
1009 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
1010 setHP(m_hp - c.drowning, reason);
1011 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1016 if (m_breathing_interval.step(dtime, 0.5f)) {
1017 // Get nose/mouth position, approximate with eye position
1018 v3s16 p = floatToInt(getEyePosition(), BS);
1019 MapNode n = m_env->getMap().getNodeNoEx(p);
1020 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1021 // If player is alive & no drowning & not in ignore, breathe
1022 if (m_breath < m_prop.breath_max &&
1023 c.drowning == 0 && n.getContent() != CONTENT_IGNORE && m_hp > 0)
1024 setBreath(m_breath + 1);
1027 if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
1028 u32 damage_per_second = 0;
1029 std::string nodename;
1030 // Lowest and highest damage points are 0.1 within collisionbox
1031 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1033 // Sequence of damage points, starting 0.1 above feet and progressing
1034 // upwards in 1 node intervals, stopping below top damage point.
1035 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1036 v3s16 p = floatToInt(m_base_position +
1037 v3f(0.0f, dam_height * BS, 0.0f), BS);
1038 MapNode n = m_env->getMap().getNodeNoEx(p);
1039 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1040 if (c.damage_per_second > damage_per_second) {
1041 damage_per_second = c.damage_per_second;
1047 v3s16 ptop = floatToInt(m_base_position +
1048 v3f(0.0f, dam_top * BS, 0.0f), BS);
1049 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1050 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
1051 if (c.damage_per_second > damage_per_second) {
1052 damage_per_second = c.damage_per_second;
1056 if (damage_per_second != 0 && m_hp > 0) {
1057 s32 newhp = (s32)m_hp - (s32)damage_per_second;
1058 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
1059 setHP(newhp, reason);
1060 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1064 if (!m_properties_sent) {
1065 m_properties_sent = true;
1066 std::string str = getPropertyPacket();
1067 // create message and add to list
1068 ActiveObjectMessage aom(getId(), true, str);
1069 m_messages_out.push(aom);
1072 // If attached, check that our parent is still there. If it isn't, detach.
1073 if (m_attachment_parent_id && !isAttached()) {
1074 m_attachment_parent_id = 0;
1075 m_attachment_bone = "";
1076 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1077 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1078 setBasePosition(m_last_good_position);
1079 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1082 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1084 // Set lag pool maximums based on estimated lag
1085 const float LAG_POOL_MIN = 5.0f;
1086 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1087 if(lag_pool_max < LAG_POOL_MIN)
1088 lag_pool_max = LAG_POOL_MIN;
1089 m_dig_pool.setMax(lag_pool_max);
1090 m_move_pool.setMax(lag_pool_max);
1092 // Increment cheat prevention timers
1093 m_dig_pool.add(dtime);
1094 m_move_pool.add(dtime);
1095 m_time_from_last_teleport += dtime;
1096 m_time_from_last_punch += dtime;
1097 m_nocheat_dig_time += dtime;
1099 // Each frame, parent position is copied if the object is attached,
1100 // otherwise it's calculated normally.
1101 // If the object gets detached this comes into effect automatically from
1102 // the last known origin.
1104 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1105 m_last_good_position = pos;
1106 setBasePosition(pos);
1109 if (!send_recommended)
1112 // If the object is attached client-side, don't waste bandwidth sending its
1113 // position or rotation to clients.
1114 if (m_position_not_sent && !isAttached()) {
1115 m_position_not_sent = false;
1116 float update_interval = m_env->getSendRecommendedInterval();
1118 if (isAttached()) // Just in case we ever do send attachment position too
1119 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1121 pos = m_base_position;
1123 std::string str = gob_cmd_update_position(
1125 v3f(0.0f, 0.0f, 0.0f),
1126 v3f(0.0f, 0.0f, 0.0f),
1132 // create message and add to list
1133 ActiveObjectMessage aom(getId(), false, str);
1134 m_messages_out.push(aom);
1137 if (!m_armor_groups_sent) {
1138 m_armor_groups_sent = true;
1139 std::string str = gob_cmd_update_armor_groups(
1141 // create message and add to list
1142 ActiveObjectMessage aom(getId(), true, str);
1143 m_messages_out.push(aom);
1146 if (!m_physics_override_sent) {
1147 m_physics_override_sent = true;
1148 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1149 m_physics_override_jump, m_physics_override_gravity,
1150 m_physics_override_sneak, m_physics_override_sneak_glitch,
1151 m_physics_override_new_move);
1152 // create message and add to list
1153 ActiveObjectMessage aom(getId(), true, str);
1154 m_messages_out.push(aom);
1157 if (!m_animation_sent) {
1158 m_animation_sent = true;
1159 std::string str = gob_cmd_update_animation(
1160 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1161 // create message and add to list
1162 ActiveObjectMessage aom(getId(), true, str);
1163 m_messages_out.push(aom);
1166 if (!m_bone_position_sent) {
1167 m_bone_position_sent = true;
1168 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1169 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1170 std::string str = gob_cmd_update_bone_position((*ii).first,
1171 (*ii).second.X, (*ii).second.Y);
1172 // create message and add to list
1173 ActiveObjectMessage aom(getId(), true, str);
1174 m_messages_out.push(aom);
1178 if (!m_attachment_sent) {
1179 m_attachment_sent = true;
1180 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1181 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1182 // create message and add to list
1183 ActiveObjectMessage aom(getId(), true, str);
1184 m_messages_out.push(aom);
1188 void PlayerSAO::setBasePosition(const v3f &position)
1190 if (m_player && position != m_base_position)
1191 m_player->setDirty(true);
1193 // This needs to be ran for attachments too
1194 ServerActiveObject::setBasePosition(position);
1196 // Updating is not wanted/required for player migration
1198 m_position_not_sent = true;
1202 void PlayerSAO::setPos(const v3f &pos)
1207 // Send mapblock of target location
1208 v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
1209 m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
1211 setBasePosition(pos);
1212 // Movement caused by this command is always valid
1213 m_last_good_position = pos;
1214 m_move_pool.empty();
1215 m_time_from_last_teleport = 0.0;
1216 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1219 void PlayerSAO::moveTo(v3f pos, bool continuous)
1224 setBasePosition(pos);
1225 // Movement caused by this command is always valid
1226 m_last_good_position = pos;
1227 m_move_pool.empty();
1228 m_time_from_last_teleport = 0.0;
1229 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1232 void PlayerSAO::setPlayerYaw(const float yaw)
1234 v3f rotation(0, yaw, 0);
1235 if (m_player && yaw != m_rotation.Y)
1236 m_player->setDirty(true);
1238 // Set player model yaw, not look view
1239 UnitSAO::setRotation(rotation);
1242 void PlayerSAO::setFov(const float fov)
1244 if (m_player && fov != m_fov)
1245 m_player->setDirty(true);
1250 void PlayerSAO::setWantedRange(const s16 range)
1252 if (m_player && range != m_wanted_range)
1253 m_player->setDirty(true);
1255 m_wanted_range = range;
1258 void PlayerSAO::setPlayerYawAndSend(const float yaw)
1261 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1264 void PlayerSAO::setLookPitch(const float pitch)
1266 if (m_player && pitch != m_pitch)
1267 m_player->setDirty(true);
1272 void PlayerSAO::setLookPitchAndSend(const float pitch)
1274 setLookPitch(pitch);
1275 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1278 int PlayerSAO::punch(v3f dir,
1279 const ToolCapabilities *toolcap,
1280 ServerActiveObject *puncher,
1281 float time_from_last_punch)
1286 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
1288 // No effect if PvP disabled
1289 if (!g_settings->getBool("enable_pvp")) {
1290 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1291 std::string str = gob_cmd_punched(getHP());
1292 // create message and add to list
1293 ActiveObjectMessage aom(getId(), true, str);
1294 m_messages_out.push(aom);
1299 s32 old_hp = getHP();
1300 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1301 time_from_last_punch);
1303 PlayerSAO *playersao = m_player->getPlayerSAO();
1305 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1306 puncher, time_from_last_punch, toolcap, dir,
1309 if (!damage_handled) {
1310 setHP((s32)getHP() - (s32)hitparams.hp,
1311 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1312 } else { // override client prediction
1313 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1314 std::string str = gob_cmd_punched(getHP());
1315 // create message and add to list
1316 ActiveObjectMessage aom(getId(), true, str);
1317 m_messages_out.push(aom);
1321 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
1322 ", hp=" << puncher->getHP() << ") punched " <<
1323 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
1324 "), damage=" << (old_hp - (s32)getHP()) <<
1325 (damage_handled ? " (handled by Lua)" : "") << std::endl;
1327 return hitparams.wear;
1330 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
1334 hp = rangelim(hp, 0, m_prop.hp_max);
1337 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1341 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
1344 if (hp < oldhp && isImmortal())
1349 // Update properties on death
1350 if ((hp == 0) != (oldhp == 0))
1351 m_properties_sent = false;
1354 void PlayerSAO::setBreath(const u16 breath, bool send)
1356 if (m_player && breath != m_breath)
1357 m_player->setDirty(true);
1359 m_breath = rangelim(breath, 0, m_prop.breath_max);
1362 m_env->getGameDef()->SendPlayerBreath(this);
1365 Inventory* PlayerSAO::getInventory()
1369 const Inventory* PlayerSAO::getInventory() const
1374 InventoryLocation PlayerSAO::getInventoryLocation() const
1376 InventoryLocation loc;
1377 loc.setPlayer(m_player->getName());
1381 std::string PlayerSAO::getWieldList() const
1386 ItemStack PlayerSAO::getWieldedItem() const
1388 const Inventory *inv = getInventory();
1390 const InventoryList *mlist = inv->getList(getWieldList());
1391 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1392 ret = mlist->getItem(getWieldIndex());
1396 ItemStack PlayerSAO::getWieldedItemOrHand() const
1398 const Inventory *inv = getInventory();
1400 const InventoryList *mlist = inv->getList(getWieldList());
1401 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1402 ret = mlist->getItem(getWieldIndex());
1403 if (ret.name.empty()) {
1404 const InventoryList *hlist = inv->getList("hand");
1406 ret = hlist->getItem(0);
1411 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1413 Inventory *inv = getInventory();
1415 InventoryList *mlist = inv->getList(getWieldList());
1417 mlist->changeItem(getWieldIndex(), item);
1424 int PlayerSAO::getWieldIndex() const
1426 return m_wield_index;
1429 void PlayerSAO::setWieldIndex(int i)
1431 if(i != m_wield_index) {
1436 void PlayerSAO::disconnected()
1439 m_pending_removal = true;
1442 void PlayerSAO::unlinkPlayerSessionAndSave()
1444 assert(m_player->getPlayerSAO() == this);
1445 m_player->setPeerId(PEER_ID_INEXISTENT);
1446 m_env->savePlayer(m_player);
1447 m_player->setPlayerSAO(NULL);
1448 m_env->removePlayer(m_player);
1451 std::string PlayerSAO::getPropertyPacket()
1453 m_prop.is_visible = (true);
1454 return gob_cmd_set_properties(m_prop);
1457 bool PlayerSAO::checkMovementCheat()
1459 if (isAttached() || m_is_singleplayer ||
1460 g_settings->getBool("disable_anticheat")) {
1461 m_last_good_position = m_base_position;
1465 bool cheated = false;
1467 Check player movements
1469 NOTE: Actually the server should handle player physics like the
1470 client does and compare player's position to what is calculated
1471 on our side. This is required when eg. players fly due to an
1472 explosion. Altough a node-based alternative might be possible
1473 too, and much more lightweight.
1476 float player_max_walk = 0; // horizontal movement
1477 float player_max_jump = 0; // vertical upwards movement
1479 if (m_privs.count("fast") != 0)
1480 player_max_walk = m_player->movement_speed_fast; // Fast speed
1482 player_max_walk = m_player->movement_speed_walk; // Normal speed
1483 player_max_walk *= m_physics_override_speed;
1484 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1485 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1486 // until this can be verified correctly, tolerate higher jumping speeds
1487 player_max_jump *= 2.0;
1489 // Don't divide by zero!
1490 if (player_max_walk < 0.0001f)
1491 player_max_walk = 0.0001f;
1492 if (player_max_jump < 0.0001f)
1493 player_max_jump = 0.0001f;
1495 v3f diff = (m_base_position - m_last_good_position);
1496 float d_vert = diff.Y;
1498 float d_horiz = diff.getLength();
1499 float required_time = d_horiz / player_max_walk;
1501 // FIXME: Checking downwards movement is not easily possible currently,
1502 // the server could calculate speed differences to examine the gravity
1504 // In certain cases (water, ladders) walking speed is applied vertically
1505 float s = MYMAX(player_max_jump, player_max_walk);
1506 required_time = MYMAX(required_time, d_vert / s);
1509 if (m_move_pool.grab(required_time)) {
1510 m_last_good_position = m_base_position;
1512 const float LAG_POOL_MIN = 5.0;
1513 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1514 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1515 if (m_time_from_last_teleport > lag_pool_max) {
1516 actionstream << "Player " << m_player->getName()
1517 << " moved too fast; resetting position"
1521 setBasePosition(m_last_good_position);
1526 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1528 //update collision box
1529 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1530 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1532 toset->MinEdge += m_base_position;
1533 toset->MaxEdge += m_base_position;
1537 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1539 if (!m_prop.is_visible || !m_prop.pointable) {
1543 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1544 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1549 float PlayerSAO::getZoomFOV() const
1551 return m_prop.zoom_fov;