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() const
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() const
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, this, &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 // TODO after a protocol bump: only send the object initialization data
579 // to older clients (superfluous since this message exists)
580 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
581 obj->getClientInitializationData(protocol_version)));
585 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
588 writeU8(os, message_count);
589 os.write(msg_os.str().c_str(), msg_os.str().size());
595 void LuaEntitySAO::getStaticData(std::string *result) const
597 verbosestream<<FUNCTION_NAME<<std::endl;
598 std::ostringstream os(std::ios::binary);
599 // version must be 1 to keep backwards-compatibility. See version2
602 os<<serializeString(m_init_name);
605 std::string state = m_env->getScriptIface()->
606 luaentity_GetStaticdata(m_id);
607 os<<serializeLongString(state);
609 os<<serializeLongString(m_init_state);
612 writeV3F1000(os, m_velocity);
614 writeF1000(os, m_rotation.Y);
616 // version2. Increase this variable for new values
617 writeU8(os, 1); // PROTOCOL_VERSION >= 37
619 writeF1000(os, m_rotation.X);
620 writeF1000(os, m_rotation.Z);
622 // <write new values>
627 int LuaEntitySAO::punch(v3f dir,
628 const ToolCapabilities *toolcap,
629 ServerActiveObject *puncher,
630 float time_from_last_punch)
633 // Delete unknown LuaEntities when punched
634 m_pending_removal = true;
638 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
640 s32 old_hp = getHP();
641 const ItemStack &punchitem = puncher->getWieldedItem();
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 std::string str = gob_cmd_punched(getHP());
658 // create message and add to list
659 ActiveObjectMessage aom(getId(), true, str);
660 m_messages_out.push(aom);
664 if (getHP() == 0 && !isGone()) {
665 m_pending_removal = true;
666 clearParentAttachment();
667 clearChildAttachments();
668 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
671 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
672 ", hp=" << puncher->getHP() << ") punched " <<
673 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
674 "), damage=" << (old_hp - (s32)getHP()) <<
675 (damage_handled ? " (handled by Lua)" : "") << std::endl;
680 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
685 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
688 void LuaEntitySAO::setPos(const v3f &pos)
692 m_base_position = pos;
693 sendPosition(false, true);
696 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
700 m_base_position = pos;
702 sendPosition(true, true);
705 float LuaEntitySAO::getMinimumSavedMovement()
710 std::string LuaEntitySAO::getDescription()
712 std::ostringstream os(std::ios::binary);
713 os<<"LuaEntitySAO at (";
714 os<<(m_base_position.X/BS)<<",";
715 os<<(m_base_position.Y/BS)<<",";
716 os<<(m_base_position.Z/BS);
721 void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
723 m_hp = rangelim(hp, 0, U16_MAX);
726 u16 LuaEntitySAO::getHP() const
731 void LuaEntitySAO::setVelocity(v3f velocity)
733 m_velocity = velocity;
736 v3f LuaEntitySAO::getVelocity()
741 void LuaEntitySAO::setAcceleration(v3f acceleration)
743 m_acceleration = acceleration;
746 v3f LuaEntitySAO::getAcceleration()
748 return m_acceleration;
751 void LuaEntitySAO::setTextureMod(const std::string &mod)
753 std::string str = gob_cmd_set_texture_mod(mod);
754 m_current_texture_modifier = mod;
755 // create message and add to list
756 ActiveObjectMessage aom(getId(), true, str);
757 m_messages_out.push(aom);
760 std::string LuaEntitySAO::getTextureMod() const
762 return m_current_texture_modifier;
765 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
766 bool select_horiz_by_yawpitch)
768 std::string str = gob_cmd_set_sprite(
772 select_horiz_by_yawpitch
774 // create message and add to list
775 ActiveObjectMessage aom(getId(), true, str);
776 m_messages_out.push(aom);
779 std::string LuaEntitySAO::getName()
784 std::string LuaEntitySAO::getPropertyPacket()
786 return gob_cmd_set_properties(m_prop);
789 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
791 // If the object is attached client-side, don't waste bandwidth sending its position to clients
795 m_last_sent_move_precision = m_base_position.getDistanceFrom(
796 m_last_sent_position);
797 m_last_sent_position_timer = 0;
798 m_last_sent_position = m_base_position;
799 m_last_sent_velocity = m_velocity;
800 //m_last_sent_acceleration = m_acceleration;
801 m_last_sent_rotation = m_rotation;
803 float update_interval = m_env->getSendRecommendedInterval();
805 std::string str = gob_cmd_update_position(
814 // create message and add to list
815 ActiveObjectMessage aom(getId(), false, str);
816 m_messages_out.push(aom);
819 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
823 //update collision box
824 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
825 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
827 toset->MinEdge += m_base_position;
828 toset->MaxEdge += m_base_position;
836 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
838 if (!m_prop.is_visible || !m_prop.pointable) {
842 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
843 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
848 bool LuaEntitySAO::collideWithObjects() const
850 return m_prop.collideWithObjects;
857 // No prototype, PlayerSAO does not need to be deserialized
859 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
860 bool is_singleplayer):
861 UnitSAO(env_, v3f(0,0,0)),
864 m_is_singleplayer(is_singleplayer)
866 assert(m_peer_id != 0); // pre-condition
868 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
869 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
870 m_prop.physical = false;
872 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
873 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
874 m_prop.pointable = true;
875 // Start of default appearance, this should be overwritten by Lua
876 m_prop.visual = "upright_sprite";
877 m_prop.visual_size = v3f(1, 2, 1);
878 m_prop.textures.clear();
879 m_prop.textures.emplace_back("player.png");
880 m_prop.textures.emplace_back("player_back.png");
881 m_prop.colors.clear();
882 m_prop.colors.emplace_back(255, 255, 255, 255);
883 m_prop.spritediv = v2s16(1,1);
884 m_prop.eye_height = 1.625f;
885 // End of default appearance
886 m_prop.is_visible = true;
887 m_prop.backface_culling = false;
888 m_prop.makes_footstep_sound = true;
889 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
890 m_hp = m_prop.hp_max;
891 m_breath = m_prop.breath_max;
892 // Disable zoom in survival mode using a value of 0
893 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
895 if (!g_settings->getBool("enable_damage"))
896 m_armor_groups["immortal"] = 1;
899 PlayerSAO::~PlayerSAO()
901 if(m_inventory != &m_player->inventory)
905 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
910 m_inventory = &m_player->inventory;
913 v3f PlayerSAO::getEyeOffset() const
915 return v3f(0, BS * m_prop.eye_height, 0);
918 std::string PlayerSAO::getDescription()
920 return std::string("player ") + m_player->getName();
923 // Called after id has been set and has been inserted in environment
924 void PlayerSAO::addedToEnvironment(u32 dtime_s)
926 ServerActiveObject::addedToEnvironment(dtime_s);
927 ServerActiveObject::setBasePosition(m_base_position);
928 m_player->setPlayerSAO(this);
929 m_player->setPeerId(m_peer_id);
930 m_last_good_position = m_base_position;
933 // Called before removing from environment
934 void PlayerSAO::removingFromEnvironment()
936 ServerActiveObject::removingFromEnvironment();
937 if (m_player->getPlayerSAO() == this) {
938 unlinkPlayerSessionAndSave();
939 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
940 m_env->deleteParticleSpawner(attached_particle_spawner, false);
945 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
947 std::ostringstream os(std::ios::binary);
950 writeU8(os, 1); // version
951 os << serializeString(m_player->getName()); // name
952 writeU8(os, 1); // is_player
953 writeS16(os, getId()); // id
954 writeV3F32(os, m_base_position);
955 writeV3F32(os, m_rotation);
956 writeU16(os, getHP());
958 std::ostringstream msg_os(std::ios::binary);
959 msg_os << serializeLongString(getPropertyPacket()); // message 1
960 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
961 msg_os << serializeLongString(gob_cmd_update_animation(
962 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
963 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
964 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
965 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
966 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
968 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
969 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
970 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
971 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
972 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
973 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
974 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
975 int message_count = 6 + m_bone_position.size();
976 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
977 ii != m_attachment_child_ids.end(); ++ii) {
978 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
980 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
981 obj->getClientInitializationData(protocol_version)));
985 writeU8(os, message_count);
986 os.write(msg_os.str().c_str(), msg_os.str().size());
992 void PlayerSAO::getStaticData(std::string * result) const
994 FATAL_ERROR("Deprecated function");
997 void PlayerSAO::step(float dtime, bool send_recommended)
999 if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
1000 // Get nose/mouth position, approximate with eye position
1001 v3s16 p = floatToInt(getEyePosition(), BS);
1002 MapNode n = m_env->getMap().getNodeNoEx(p);
1003 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1004 // If node generates drown
1005 if (c.drowning > 0 && m_hp > 0) {
1007 setBreath(m_breath - 1);
1009 // No more breath, damage player
1010 if (m_breath == 0) {
1011 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
1012 setHP(m_hp - c.drowning, reason);
1013 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1018 if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
1019 // Get nose/mouth position, approximate with eye position
1020 v3s16 p = floatToInt(getEyePosition(), BS);
1021 MapNode n = m_env->getMap().getNodeNoEx(p);
1022 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1023 // If player is alive & not drowning & not in ignore & not immortal, breathe
1024 if (m_breath < m_prop.breath_max && c.drowning == 0 &&
1025 n.getContent() != CONTENT_IGNORE && m_hp > 0)
1026 setBreath(m_breath + 1);
1029 if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
1030 u32 damage_per_second = 0;
1031 std::string nodename;
1032 // Lowest and highest damage points are 0.1 within collisionbox
1033 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1035 // Sequence of damage points, starting 0.1 above feet and progressing
1036 // upwards in 1 node intervals, stopping below top damage point.
1037 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1038 v3s16 p = floatToInt(m_base_position +
1039 v3f(0.0f, dam_height * BS, 0.0f), BS);
1040 MapNode n = m_env->getMap().getNodeNoEx(p);
1041 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1042 if (c.damage_per_second > damage_per_second) {
1043 damage_per_second = c.damage_per_second;
1049 v3s16 ptop = floatToInt(m_base_position +
1050 v3f(0.0f, dam_top * BS, 0.0f), BS);
1051 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1052 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
1053 if (c.damage_per_second > damage_per_second) {
1054 damage_per_second = c.damage_per_second;
1058 if (damage_per_second != 0 && m_hp > 0) {
1059 s32 newhp = (s32)m_hp - (s32)damage_per_second;
1060 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
1061 setHP(newhp, reason);
1062 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1066 if (!m_properties_sent) {
1067 m_properties_sent = true;
1068 std::string str = getPropertyPacket();
1069 // create message and add to list
1070 ActiveObjectMessage aom(getId(), true, str);
1071 m_messages_out.push(aom);
1072 m_env->getScriptIface()->player_event(this, "properties_changed");
1075 // If attached, check that our parent is still there. If it isn't, detach.
1076 if (m_attachment_parent_id && !isAttached()) {
1077 m_attachment_parent_id = 0;
1078 m_attachment_bone = "";
1079 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1080 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1081 setBasePosition(m_last_good_position);
1082 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1085 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1087 // Set lag pool maximums based on estimated lag
1088 const float LAG_POOL_MIN = 5.0f;
1089 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1090 if(lag_pool_max < LAG_POOL_MIN)
1091 lag_pool_max = LAG_POOL_MIN;
1092 m_dig_pool.setMax(lag_pool_max);
1093 m_move_pool.setMax(lag_pool_max);
1095 // Increment cheat prevention timers
1096 m_dig_pool.add(dtime);
1097 m_move_pool.add(dtime);
1098 m_time_from_last_teleport += dtime;
1099 m_time_from_last_punch += dtime;
1100 m_nocheat_dig_time += dtime;
1102 // Each frame, parent position is copied if the object is attached,
1103 // otherwise it's calculated normally.
1104 // If the object gets detached this comes into effect automatically from
1105 // the last known origin.
1107 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1108 m_last_good_position = pos;
1109 setBasePosition(pos);
1112 if (!send_recommended)
1115 // If the object is attached client-side, don't waste bandwidth sending its
1116 // position or rotation to clients.
1117 if (m_position_not_sent && !isAttached()) {
1118 m_position_not_sent = false;
1119 float update_interval = m_env->getSendRecommendedInterval();
1121 if (isAttached()) // Just in case we ever do send attachment position too
1122 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1124 pos = m_base_position;
1126 std::string str = gob_cmd_update_position(
1128 v3f(0.0f, 0.0f, 0.0f),
1129 v3f(0.0f, 0.0f, 0.0f),
1135 // create message and add to list
1136 ActiveObjectMessage aom(getId(), false, str);
1137 m_messages_out.push(aom);
1140 if (!m_armor_groups_sent) {
1141 m_armor_groups_sent = true;
1142 std::string str = gob_cmd_update_armor_groups(
1144 // create message and add to list
1145 ActiveObjectMessage aom(getId(), true, str);
1146 m_messages_out.push(aom);
1149 if (!m_physics_override_sent) {
1150 m_physics_override_sent = true;
1151 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1152 m_physics_override_jump, m_physics_override_gravity,
1153 m_physics_override_sneak, m_physics_override_sneak_glitch,
1154 m_physics_override_new_move);
1155 // create message and add to list
1156 ActiveObjectMessage aom(getId(), true, str);
1157 m_messages_out.push(aom);
1160 if (!m_animation_sent) {
1161 m_animation_sent = true;
1162 std::string str = gob_cmd_update_animation(
1163 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1164 // create message and add to list
1165 ActiveObjectMessage aom(getId(), true, str);
1166 m_messages_out.push(aom);
1169 if (!m_bone_position_sent) {
1170 m_bone_position_sent = true;
1171 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1172 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1173 std::string str = gob_cmd_update_bone_position((*ii).first,
1174 (*ii).second.X, (*ii).second.Y);
1175 // create message and add to list
1176 ActiveObjectMessage aom(getId(), true, str);
1177 m_messages_out.push(aom);
1181 if (!m_attachment_sent) {
1182 m_attachment_sent = true;
1183 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1184 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1185 // create message and add to list
1186 ActiveObjectMessage aom(getId(), true, str);
1187 m_messages_out.push(aom);
1191 void PlayerSAO::setBasePosition(const v3f &position)
1193 if (m_player && position != m_base_position)
1194 m_player->setDirty(true);
1196 // This needs to be ran for attachments too
1197 ServerActiveObject::setBasePosition(position);
1199 // Updating is not wanted/required for player migration
1201 m_position_not_sent = true;
1205 void PlayerSAO::setPos(const v3f &pos)
1210 // Send mapblock of target location
1211 v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
1212 m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
1214 setBasePosition(pos);
1215 // Movement caused by this command is always valid
1216 m_last_good_position = pos;
1217 m_move_pool.empty();
1218 m_time_from_last_teleport = 0.0;
1219 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1222 void PlayerSAO::moveTo(v3f pos, bool continuous)
1227 setBasePosition(pos);
1228 // Movement caused by this command is always valid
1229 m_last_good_position = pos;
1230 m_move_pool.empty();
1231 m_time_from_last_teleport = 0.0;
1232 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1235 void PlayerSAO::setPlayerYaw(const float yaw)
1237 v3f rotation(0, yaw, 0);
1238 if (m_player && yaw != m_rotation.Y)
1239 m_player->setDirty(true);
1241 // Set player model yaw, not look view
1242 UnitSAO::setRotation(rotation);
1245 void PlayerSAO::setFov(const float fov)
1247 if (m_player && fov != m_fov)
1248 m_player->setDirty(true);
1253 void PlayerSAO::setWantedRange(const s16 range)
1255 if (m_player && range != m_wanted_range)
1256 m_player->setDirty(true);
1258 m_wanted_range = range;
1261 void PlayerSAO::setPlayerYawAndSend(const float yaw)
1264 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1267 void PlayerSAO::setLookPitch(const float pitch)
1269 if (m_player && pitch != m_pitch)
1270 m_player->setDirty(true);
1275 void PlayerSAO::setLookPitchAndSend(const float pitch)
1277 setLookPitch(pitch);
1278 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1281 int PlayerSAO::punch(v3f dir,
1282 const ToolCapabilities *toolcap,
1283 ServerActiveObject *puncher,
1284 float time_from_last_punch)
1289 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
1291 // No effect if PvP disabled or if immortal
1292 if (isImmortal() || !g_settings->getBool("enable_pvp")) {
1293 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1294 std::string str = gob_cmd_punched(getHP());
1295 // create message and add to list
1296 ActiveObjectMessage aom(getId(), true, str);
1297 m_messages_out.push(aom);
1302 s32 old_hp = getHP();
1303 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1304 time_from_last_punch);
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);
1324 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
1325 ", hp=" << puncher->getHP() << ") punched " <<
1326 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
1327 "), damage=" << (old_hp - (s32)getHP()) <<
1328 (damage_handled ? " (handled by Lua)" : "") << std::endl;
1330 return hitparams.wear;
1333 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
1337 hp = rangelim(hp, 0, m_prop.hp_max);
1340 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1344 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
1347 if (hp < oldhp && isImmortal())
1352 // Update properties on death
1353 if ((hp == 0) != (oldhp == 0))
1354 m_properties_sent = false;
1357 void PlayerSAO::setBreath(const u16 breath, bool send)
1359 if (m_player && breath != m_breath)
1360 m_player->setDirty(true);
1362 m_breath = rangelim(breath, 0, m_prop.breath_max);
1365 m_env->getGameDef()->SendPlayerBreath(this);
1368 Inventory* PlayerSAO::getInventory()
1372 const Inventory* PlayerSAO::getInventory() const
1377 InventoryLocation PlayerSAO::getInventoryLocation() const
1379 InventoryLocation loc;
1380 loc.setPlayer(m_player->getName());
1384 std::string PlayerSAO::getWieldList() const
1389 ItemStack PlayerSAO::getWieldedItem() const
1391 const Inventory *inv = getInventory();
1393 const InventoryList *mlist = inv->getList(getWieldList());
1394 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1395 ret = mlist->getItem(getWieldIndex());
1399 ItemStack PlayerSAO::getWieldedItemOrHand() const
1401 const Inventory *inv = getInventory();
1403 const InventoryList *mlist = inv->getList(getWieldList());
1404 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1405 ret = mlist->getItem(getWieldIndex());
1406 if (ret.name.empty()) {
1407 const InventoryList *hlist = inv->getList("hand");
1409 ret = hlist->getItem(0);
1414 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1416 Inventory *inv = getInventory();
1418 InventoryList *mlist = inv->getList(getWieldList());
1420 mlist->changeItem(getWieldIndex(), item);
1427 int PlayerSAO::getWieldIndex() const
1429 return m_wield_index;
1432 void PlayerSAO::setWieldIndex(int i)
1434 if(i != m_wield_index) {
1439 void PlayerSAO::disconnected()
1442 m_pending_removal = true;
1445 void PlayerSAO::unlinkPlayerSessionAndSave()
1447 assert(m_player->getPlayerSAO() == this);
1448 m_player->setPeerId(PEER_ID_INEXISTENT);
1449 m_env->savePlayer(m_player);
1450 m_player->setPlayerSAO(NULL);
1451 m_env->removePlayer(m_player);
1454 std::string PlayerSAO::getPropertyPacket()
1456 m_prop.is_visible = (true);
1457 return gob_cmd_set_properties(m_prop);
1460 bool PlayerSAO::checkMovementCheat()
1462 if (isAttached() || m_is_singleplayer ||
1463 g_settings->getBool("disable_anticheat")) {
1464 m_last_good_position = m_base_position;
1468 bool cheated = false;
1470 Check player movements
1472 NOTE: Actually the server should handle player physics like the
1473 client does and compare player's position to what is calculated
1474 on our side. This is required when eg. players fly due to an
1475 explosion. Altough a node-based alternative might be possible
1476 too, and much more lightweight.
1479 float player_max_walk = 0; // horizontal movement
1480 float player_max_jump = 0; // vertical upwards movement
1482 if (m_privs.count("fast") != 0)
1483 player_max_walk = m_player->movement_speed_fast; // Fast speed
1485 player_max_walk = m_player->movement_speed_walk; // Normal speed
1486 player_max_walk *= m_physics_override_speed;
1487 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1488 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1489 // until this can be verified correctly, tolerate higher jumping speeds
1490 player_max_jump *= 2.0;
1492 // Don't divide by zero!
1493 if (player_max_walk < 0.0001f)
1494 player_max_walk = 0.0001f;
1495 if (player_max_jump < 0.0001f)
1496 player_max_jump = 0.0001f;
1498 v3f diff = (m_base_position - m_last_good_position);
1499 float d_vert = diff.Y;
1501 float d_horiz = diff.getLength();
1502 float required_time = d_horiz / player_max_walk;
1504 // FIXME: Checking downwards movement is not easily possible currently,
1505 // the server could calculate speed differences to examine the gravity
1507 // In certain cases (water, ladders) walking speed is applied vertically
1508 float s = MYMAX(player_max_jump, player_max_walk);
1509 required_time = MYMAX(required_time, d_vert / s);
1512 if (m_move_pool.grab(required_time)) {
1513 m_last_good_position = m_base_position;
1515 const float LAG_POOL_MIN = 5.0;
1516 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1517 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1518 if (m_time_from_last_teleport > lag_pool_max) {
1519 actionstream << "Player " << m_player->getName()
1520 << " moved too fast; resetting position"
1524 setBasePosition(m_last_good_position);
1529 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1531 //update collision box
1532 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1533 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1535 toset->MinEdge += m_base_position;
1536 toset->MaxEdge += m_base_position;
1540 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1542 if (!m_prop.is_visible || !m_prop.pointable) {
1546 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1547 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1552 float PlayerSAO::getZoomFOV() const
1554 return m_prop.zoom_fov;