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 std::istringstream is(data, std::ios::binary);
356 u8 version = readU8(is);
357 // check if version is supported
359 name = deSerializeString(is);
360 state = deSerializeLongString(is);
362 else if(version == 1){
363 name = deSerializeString(is);
364 state = deSerializeLongString(is);
366 velocity = readV3F1000(is);
367 rotation = readV3F1000(is);
371 infostream<<"LuaEntitySAO::create(name=\""<<name<<"\" state=\""
372 <<state<<"\")"<<std::endl;
373 LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
375 sao->m_velocity = velocity;
376 sao->m_rotation = rotation;
380 void LuaEntitySAO::step(float dtime, bool send_recommended)
382 if(!m_properties_sent)
384 m_properties_sent = true;
385 std::string str = getPropertyPacket();
386 // create message and add to list
387 ActiveObjectMessage aom(getId(), true, str);
388 m_messages_out.push(aom);
391 // If attached, check that our parent is still there. If it isn't, detach.
392 if(m_attachment_parent_id && !isAttached())
394 m_attachment_parent_id = 0;
395 m_attachment_bone = "";
396 m_attachment_position = v3f(0,0,0);
397 m_attachment_rotation = v3f(0,0,0);
398 sendPosition(false, true);
401 m_last_sent_position_timer += dtime;
403 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
404 // If the object gets detached this comes into effect automatically from the last known origin
407 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
408 m_base_position = pos;
409 m_velocity = v3f(0,0,0);
410 m_acceleration = v3f(0,0,0);
415 aabb3f box = m_prop.collisionbox;
418 collisionMoveResult moveresult;
419 f32 pos_max_d = BS*0.25; // Distance per iteration
420 v3f p_pos = m_base_position;
421 v3f p_velocity = m_velocity;
422 v3f p_acceleration = m_acceleration;
423 moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
424 pos_max_d, box, m_prop.stepheight, dtime,
425 &p_pos, &p_velocity, p_acceleration,
426 this, m_prop.collideWithObjects);
429 m_base_position = p_pos;
430 m_velocity = p_velocity;
431 m_acceleration = p_acceleration;
433 m_base_position += dtime * m_velocity + 0.5 * dtime
434 * dtime * m_acceleration;
435 m_velocity += dtime * m_acceleration;
438 if (m_prop.automatic_face_movement_dir &&
439 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
441 float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
442 + m_prop.automatic_face_movement_dir_offset;
443 float max_rotation_delta =
444 dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
446 m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
447 wrappedApproachShortest(m_rotation.Y, target_yaw, max_rotation_delta, 360.f);
452 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
455 if (!send_recommended)
460 // TODO: force send when acceleration changes enough?
461 float minchange = 0.2*BS;
462 if(m_last_sent_position_timer > 1.0){
464 } else if(m_last_sent_position_timer > 0.2){
467 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
468 move_d += m_last_sent_move_precision;
469 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
470 if (move_d > minchange || vel_d > minchange ||
471 std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f ||
472 std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f ||
473 std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) {
475 sendPosition(true, false);
479 if (!m_armor_groups_sent) {
480 m_armor_groups_sent = true;
481 std::string str = gob_cmd_update_armor_groups(
483 // create message and add to list
484 ActiveObjectMessage aom(getId(), true, str);
485 m_messages_out.push(aom);
488 if (!m_animation_sent) {
489 m_animation_sent = true;
490 std::string str = gob_cmd_update_animation(
491 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
492 // create message and add to list
493 ActiveObjectMessage aom(getId(), true, str);
494 m_messages_out.push(aom);
497 if (!m_animation_speed_sent) {
498 m_animation_speed_sent = true;
499 std::string str = gob_cmd_update_animation_speed(m_animation_speed);
500 // create message and add to list
501 ActiveObjectMessage aom(getId(), true, str);
502 m_messages_out.push(aom);
505 if (!m_bone_position_sent) {
506 m_bone_position_sent = true;
507 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
508 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
509 std::string str = gob_cmd_update_bone_position((*ii).first,
510 (*ii).second.X, (*ii).second.Y);
511 // create message and add to list
512 ActiveObjectMessage aom(getId(), true, str);
513 m_messages_out.push(aom);
517 if (!m_attachment_sent) {
518 m_attachment_sent = true;
519 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
520 // create message and add to list
521 ActiveObjectMessage aom(getId(), true, str);
522 m_messages_out.push(aom);
526 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
528 std::ostringstream os(std::ios::binary);
531 writeU8(os, 1); // version
532 os << serializeString(""); // name
533 writeU8(os, 0); // is_player
534 writeS16(os, getId()); //id
535 writeV3F1000(os, m_base_position);
536 writeV3F1000(os, m_rotation);
539 std::ostringstream msg_os(std::ios::binary);
540 msg_os << serializeLongString(getPropertyPacket()); // message 1
541 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
542 msg_os << serializeLongString(gob_cmd_update_animation(
543 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
544 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
545 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
546 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
547 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
549 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
550 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
551 int message_count = 4 + m_bone_position.size();
552 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
553 (ii != m_attachment_child_ids.end()); ++ii) {
554 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
556 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
557 obj->getClientInitializationData(protocol_version)));
561 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
564 writeU8(os, message_count);
565 os.write(msg_os.str().c_str(), msg_os.str().size());
571 void LuaEntitySAO::getStaticData(std::string *result) const
573 verbosestream<<FUNCTION_NAME<<std::endl;
574 std::ostringstream os(std::ios::binary);
578 os<<serializeString(m_init_name);
581 std::string state = m_env->getScriptIface()->
582 luaentity_GetStaticdata(m_id);
583 os<<serializeLongString(state);
585 os<<serializeLongString(m_init_state);
590 writeV3F1000(os, m_velocity);
592 writeV3F1000(os, m_rotation);
597 int LuaEntitySAO::punch(v3f dir,
598 const ToolCapabilities *toolcap,
599 ServerActiveObject *puncher,
600 float time_from_last_punch)
603 // Delete unknown LuaEntities when punched
604 m_pending_removal = true;
608 ItemStack *punchitem = NULL;
609 ItemStack punchitem_static;
611 punchitem_static = puncher->getWieldedItem();
612 punchitem = &punchitem_static;
615 PunchDamageResult result = getPunchDamage(
619 time_from_last_punch);
621 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
622 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
624 if (!damage_handled) {
625 if (result.did_punch) {
626 setHP(getHP() - result.damage,
627 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
629 if (result.damage > 0) {
630 std::string punchername = puncher ? puncher->getDescription() : "nil";
632 actionstream << getDescription() << " punched by "
633 << punchername << ", damage " << result.damage
634 << " hp, health now " << getHP() << " hp" << std::endl;
637 std::string str = gob_cmd_punched(result.damage, getHP());
638 // create message and add to list
639 ActiveObjectMessage aom(getId(), true, str);
640 m_messages_out.push(aom);
644 if (getHP() == 0 && !isGone()) {
645 m_pending_removal = true;
646 clearParentAttachment();
647 clearChildAttachments();
648 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
654 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
659 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
662 void LuaEntitySAO::setPos(const v3f &pos)
666 m_base_position = pos;
667 sendPosition(false, true);
670 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
674 m_base_position = pos;
676 sendPosition(true, true);
679 float LuaEntitySAO::getMinimumSavedMovement()
684 std::string LuaEntitySAO::getDescription()
686 std::ostringstream os(std::ios::binary);
687 os<<"LuaEntitySAO at (";
688 os<<(m_base_position.X/BS)<<",";
689 os<<(m_base_position.Y/BS)<<",";
690 os<<(m_base_position.Z/BS);
695 void LuaEntitySAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
702 s16 LuaEntitySAO::getHP() const
707 void LuaEntitySAO::setVelocity(v3f velocity)
709 m_velocity = velocity;
712 v3f LuaEntitySAO::getVelocity()
717 void LuaEntitySAO::setAcceleration(v3f acceleration)
719 m_acceleration = acceleration;
722 v3f LuaEntitySAO::getAcceleration()
724 return m_acceleration;
727 void LuaEntitySAO::setTextureMod(const std::string &mod)
729 std::string str = gob_cmd_set_texture_mod(mod);
730 m_current_texture_modifier = mod;
731 // create message and add to list
732 ActiveObjectMessage aom(getId(), true, str);
733 m_messages_out.push(aom);
736 std::string LuaEntitySAO::getTextureMod() const
738 return m_current_texture_modifier;
741 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
742 bool select_horiz_by_yawpitch)
744 std::string str = gob_cmd_set_sprite(
748 select_horiz_by_yawpitch
750 // create message and add to list
751 ActiveObjectMessage aom(getId(), true, str);
752 m_messages_out.push(aom);
755 std::string LuaEntitySAO::getName()
760 std::string LuaEntitySAO::getPropertyPacket()
762 return gob_cmd_set_properties(m_prop);
765 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
767 // If the object is attached client-side, don't waste bandwidth sending its position to clients
771 m_last_sent_move_precision = m_base_position.getDistanceFrom(
772 m_last_sent_position);
773 m_last_sent_position_timer = 0;
774 m_last_sent_position = m_base_position;
775 m_last_sent_velocity = m_velocity;
776 //m_last_sent_acceleration = m_acceleration;
777 m_last_sent_rotation = m_rotation;
779 float update_interval = m_env->getSendRecommendedInterval();
781 std::string str = gob_cmd_update_position(
790 // create message and add to list
791 ActiveObjectMessage aom(getId(), false, str);
792 m_messages_out.push(aom);
795 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
799 //update collision box
800 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
801 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
803 toset->MinEdge += m_base_position;
804 toset->MaxEdge += m_base_position;
812 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
814 if (!m_prop.is_visible || !m_prop.pointable) {
818 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
819 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
824 bool LuaEntitySAO::collideWithObjects() const
826 return m_prop.collideWithObjects;
833 // No prototype, PlayerSAO does not need to be deserialized
835 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
836 bool is_singleplayer):
837 UnitSAO(env_, v3f(0,0,0)),
840 m_is_singleplayer(is_singleplayer)
842 assert(m_peer_id != 0); // pre-condition
844 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
845 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
846 m_prop.physical = false;
848 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
849 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
850 m_prop.pointable = true;
851 // Start of default appearance, this should be overwritten by Lua
852 m_prop.visual = "upright_sprite";
853 m_prop.visual_size = v2f(1, 2);
854 m_prop.textures.clear();
855 m_prop.textures.emplace_back("player.png");
856 m_prop.textures.emplace_back("player_back.png");
857 m_prop.colors.clear();
858 m_prop.colors.emplace_back(255, 255, 255, 255);
859 m_prop.spritediv = v2s16(1,1);
860 m_prop.eye_height = 1.625f;
861 // End of default appearance
862 m_prop.is_visible = true;
863 m_prop.backface_culling = false;
864 m_prop.makes_footstep_sound = true;
865 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
866 m_hp = m_prop.hp_max;
867 m_breath = m_prop.breath_max;
868 // Disable zoom in survival mode using a value of 0
869 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
872 PlayerSAO::~PlayerSAO()
874 if(m_inventory != &m_player->inventory)
878 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
883 m_inventory = &m_player->inventory;
886 v3f PlayerSAO::getEyeOffset() const
888 return v3f(0, BS * m_prop.eye_height, 0);
891 std::string PlayerSAO::getDescription()
893 return std::string("player ") + m_player->getName();
896 // Called after id has been set and has been inserted in environment
897 void PlayerSAO::addedToEnvironment(u32 dtime_s)
899 ServerActiveObject::addedToEnvironment(dtime_s);
900 ServerActiveObject::setBasePosition(m_base_position);
901 m_player->setPlayerSAO(this);
902 m_player->setPeerId(m_peer_id);
903 m_last_good_position = m_base_position;
906 // Called before removing from environment
907 void PlayerSAO::removingFromEnvironment()
909 ServerActiveObject::removingFromEnvironment();
910 if (m_player->getPlayerSAO() == this) {
911 unlinkPlayerSessionAndSave();
912 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
913 m_env->deleteParticleSpawner(attached_particle_spawner, false);
918 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
920 std::ostringstream os(std::ios::binary);
923 writeU8(os, 1); // version
924 os << serializeString(m_player->getName()); // name
925 writeU8(os, 1); // is_player
926 writeS16(os, getId()); // id
927 writeV3F1000(os, m_base_position);
928 writeV3F1000(os, m_rotation);
929 writeS16(os, getHP());
931 std::ostringstream msg_os(std::ios::binary);
932 msg_os << serializeLongString(getPropertyPacket()); // message 1
933 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
934 msg_os << serializeLongString(gob_cmd_update_animation(
935 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
936 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
937 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
938 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
939 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
941 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
942 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
943 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
944 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
945 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
946 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
947 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
948 int message_count = 6 + m_bone_position.size();
949 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
950 ii != m_attachment_child_ids.end(); ++ii) {
951 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
953 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
954 obj->getClientInitializationData(protocol_version)));
958 writeU8(os, message_count);
959 os.write(msg_os.str().c_str(), msg_os.str().size());
965 void PlayerSAO::getStaticData(std::string * result) const
967 FATAL_ERROR("Deprecated function");
970 void PlayerSAO::step(float dtime, bool send_recommended)
972 if (m_drowning_interval.step(dtime, 2.0f)) {
973 // Get nose/mouth position, approximate with eye position
974 v3s16 p = floatToInt(getEyePosition(), BS);
975 MapNode n = m_env->getMap().getNodeNoEx(p);
976 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
977 // If node generates drown
978 if (c.drowning > 0 && m_hp > 0) {
980 setBreath(m_breath - 1);
982 // No more breath, damage player
984 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
985 setHP(m_hp - c.drowning, reason);
986 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
991 if (m_breathing_interval.step(dtime, 0.5f)) {
992 // Get nose/mouth position, approximate with eye position
993 v3s16 p = floatToInt(getEyePosition(), BS);
994 MapNode n = m_env->getMap().getNodeNoEx(p);
995 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
996 // If player is alive & no drowning, breathe
997 if (m_hp > 0 && m_breath < m_prop.breath_max && c.drowning == 0)
998 setBreath(m_breath + 1);
1001 if (m_node_hurt_interval.step(dtime, 1.0f)) {
1002 u32 damage_per_second = 0;
1003 // Lowest and highest damage points are 0.1 within collisionbox
1004 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1006 // Sequence of damage points, starting 0.1 above feet and progressing
1007 // upwards in 1 node intervals, stopping below top damage point.
1008 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1009 v3s16 p = floatToInt(m_base_position +
1010 v3f(0.0f, dam_height * BS, 0.0f), BS);
1011 MapNode n = m_env->getMap().getNodeNoEx(p);
1012 damage_per_second = std::max(damage_per_second,
1013 m_env->getGameDef()->ndef()->get(n).damage_per_second);
1017 v3s16 ptop = floatToInt(m_base_position +
1018 v3f(0.0f, dam_top * BS, 0.0f), BS);
1019 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1020 damage_per_second = std::max(damage_per_second,
1021 m_env->getGameDef()->ndef()->get(ntop).damage_per_second);
1023 if (damage_per_second != 0 && m_hp > 0) {
1024 s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second);
1025 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE);
1026 setHP(newhp, reason);
1027 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1031 if (!m_properties_sent) {
1032 m_properties_sent = true;
1033 std::string str = getPropertyPacket();
1034 // create message and add to list
1035 ActiveObjectMessage aom(getId(), true, str);
1036 m_messages_out.push(aom);
1039 // If attached, check that our parent is still there. If it isn't, detach.
1040 if (m_attachment_parent_id && !isAttached()) {
1041 m_attachment_parent_id = 0;
1042 m_attachment_bone = "";
1043 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1044 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1045 setBasePosition(m_last_good_position);
1046 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1049 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1051 // Set lag pool maximums based on estimated lag
1052 const float LAG_POOL_MIN = 5.0f;
1053 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1054 if(lag_pool_max < LAG_POOL_MIN)
1055 lag_pool_max = LAG_POOL_MIN;
1056 m_dig_pool.setMax(lag_pool_max);
1057 m_move_pool.setMax(lag_pool_max);
1059 // Increment cheat prevention timers
1060 m_dig_pool.add(dtime);
1061 m_move_pool.add(dtime);
1062 m_time_from_last_teleport += dtime;
1063 m_time_from_last_punch += dtime;
1064 m_nocheat_dig_time += dtime;
1066 // Each frame, parent position is copied if the object is attached,
1067 // otherwise it's calculated normally.
1068 // If the object gets detached this comes into effect automatically from
1069 // the last known origin.
1071 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1072 m_last_good_position = pos;
1073 setBasePosition(pos);
1076 if (!send_recommended)
1079 // If the object is attached client-side, don't waste bandwidth sending its
1080 // position or rotation to clients.
1081 if (m_position_not_sent && !isAttached()) {
1082 m_position_not_sent = false;
1083 float update_interval = m_env->getSendRecommendedInterval();
1085 if (isAttached()) // Just in case we ever do send attachment position too
1086 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1088 pos = m_base_position;
1090 std::string str = gob_cmd_update_position(
1092 v3f(0.0f, 0.0f, 0.0f),
1093 v3f(0.0f, 0.0f, 0.0f),
1099 // create message and add to list
1100 ActiveObjectMessage aom(getId(), false, str);
1101 m_messages_out.push(aom);
1104 if (!m_armor_groups_sent) {
1105 m_armor_groups_sent = true;
1106 std::string str = gob_cmd_update_armor_groups(
1108 // create message and add to list
1109 ActiveObjectMessage aom(getId(), true, str);
1110 m_messages_out.push(aom);
1113 if (!m_physics_override_sent) {
1114 m_physics_override_sent = true;
1115 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1116 m_physics_override_jump, m_physics_override_gravity,
1117 m_physics_override_sneak, m_physics_override_sneak_glitch,
1118 m_physics_override_new_move);
1119 // create message and add to list
1120 ActiveObjectMessage aom(getId(), true, str);
1121 m_messages_out.push(aom);
1124 if (!m_animation_sent) {
1125 m_animation_sent = true;
1126 std::string str = gob_cmd_update_animation(
1127 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1128 // create message and add to list
1129 ActiveObjectMessage aom(getId(), true, str);
1130 m_messages_out.push(aom);
1133 if (!m_bone_position_sent) {
1134 m_bone_position_sent = true;
1135 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1136 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1137 std::string str = gob_cmd_update_bone_position((*ii).first,
1138 (*ii).second.X, (*ii).second.Y);
1139 // create message and add to list
1140 ActiveObjectMessage aom(getId(), true, str);
1141 m_messages_out.push(aom);
1145 if (!m_attachment_sent) {
1146 m_attachment_sent = true;
1147 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1148 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1149 // create message and add to list
1150 ActiveObjectMessage aom(getId(), true, str);
1151 m_messages_out.push(aom);
1155 void PlayerSAO::setBasePosition(const v3f &position)
1157 if (m_player && position != m_base_position)
1158 m_player->setDirty(true);
1160 // This needs to be ran for attachments too
1161 ServerActiveObject::setBasePosition(position);
1163 // Updating is not wanted/required for player migration
1165 m_position_not_sent = true;
1169 void PlayerSAO::setPos(const v3f &pos)
1174 setBasePosition(pos);
1175 // Movement caused by this command is always valid
1176 m_last_good_position = pos;
1177 m_move_pool.empty();
1178 m_time_from_last_teleport = 0.0;
1179 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1182 void PlayerSAO::moveTo(v3f pos, bool continuous)
1187 setBasePosition(pos);
1188 // Movement caused by this command is always valid
1189 m_last_good_position = pos;
1190 m_move_pool.empty();
1191 m_time_from_last_teleport = 0.0;
1192 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1195 void PlayerSAO::setPlayerYaw(const float yaw)
1197 v3f rotation(0, yaw, 0);
1198 if (m_player && yaw != m_rotation.Y)
1199 m_player->setDirty(true);
1201 // Set player model yaw, not look view
1202 UnitSAO::setRotation(rotation);
1205 void PlayerSAO::setFov(const float fov)
1207 if (m_player && fov != m_fov)
1208 m_player->setDirty(true);
1213 void PlayerSAO::setWantedRange(const s16 range)
1215 if (m_player && range != m_wanted_range)
1216 m_player->setDirty(true);
1218 m_wanted_range = range;
1221 void PlayerSAO::setPlayerYawAndSend(const float yaw)
1224 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1227 void PlayerSAO::setLookPitch(const float pitch)
1229 if (m_player && pitch != m_pitch)
1230 m_player->setDirty(true);
1235 void PlayerSAO::setLookPitchAndSend(const float pitch)
1237 setLookPitch(pitch);
1238 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1241 int PlayerSAO::punch(v3f dir,
1242 const ToolCapabilities *toolcap,
1243 ServerActiveObject *puncher,
1244 float time_from_last_punch)
1249 // No effect if PvP disabled
1250 if (!g_settings->getBool("enable_pvp")) {
1251 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1252 std::string str = gob_cmd_punched(0, getHP());
1253 // create message and add to list
1254 ActiveObjectMessage aom(getId(), true, str);
1255 m_messages_out.push(aom);
1260 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1261 time_from_last_punch);
1263 std::string punchername = "nil";
1266 punchername = puncher->getDescription();
1268 PlayerSAO *playersao = m_player->getPlayerSAO();
1270 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1271 puncher, time_from_last_punch, toolcap, dir,
1274 if (!damage_handled) {
1275 setHP(getHP() - hitparams.hp,
1276 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1277 } else { // override client prediction
1278 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1279 std::string str = gob_cmd_punched(0, getHP());
1280 // create message and add to list
1281 ActiveObjectMessage aom(getId(), true, str);
1282 m_messages_out.push(aom);
1287 actionstream << "Player " << m_player->getName() << " punched by "
1289 if (!damage_handled) {
1290 actionstream << ", damage " << hitparams.hp << " HP";
1292 actionstream << ", damage handled by lua";
1294 actionstream << std::endl;
1296 return hitparams.wear;
1299 s16 PlayerSAO::readDamage()
1301 s16 damage = m_damage;
1306 void PlayerSAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
1310 s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1313 hp = oldhp + hp_change;
1317 else if (hp > m_prop.hp_max)
1320 if (hp < oldhp && !g_settings->getBool("enable_damage")) {
1327 m_damage += (oldhp - hp);
1329 // Update properties on death
1330 if ((hp == 0) != (oldhp == 0))
1331 m_properties_sent = false;
1334 void PlayerSAO::setBreath(const u16 breath, bool send)
1336 if (m_player && breath != m_breath)
1337 m_player->setDirty(true);
1339 m_breath = MYMIN(breath, m_prop.breath_max);
1342 m_env->getGameDef()->SendPlayerBreath(this);
1345 Inventory* PlayerSAO::getInventory()
1349 const Inventory* PlayerSAO::getInventory() const
1354 InventoryLocation PlayerSAO::getInventoryLocation() const
1356 InventoryLocation loc;
1357 loc.setPlayer(m_player->getName());
1361 std::string PlayerSAO::getWieldList() const
1366 ItemStack PlayerSAO::getWieldedItem() const
1368 const Inventory *inv = getInventory();
1370 const InventoryList *mlist = inv->getList(getWieldList());
1371 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1372 ret = mlist->getItem(getWieldIndex());
1376 ItemStack PlayerSAO::getWieldedItemOrHand() const
1378 const Inventory *inv = getInventory();
1380 const InventoryList *mlist = inv->getList(getWieldList());
1381 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1382 ret = mlist->getItem(getWieldIndex());
1383 if (ret.name.empty()) {
1384 const InventoryList *hlist = inv->getList("hand");
1386 ret = hlist->getItem(0);
1391 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1393 Inventory *inv = getInventory();
1395 InventoryList *mlist = inv->getList(getWieldList());
1397 mlist->changeItem(getWieldIndex(), item);
1404 int PlayerSAO::getWieldIndex() const
1406 return m_wield_index;
1409 void PlayerSAO::setWieldIndex(int i)
1411 if(i != m_wield_index) {
1416 void PlayerSAO::disconnected()
1419 m_pending_removal = true;
1422 void PlayerSAO::unlinkPlayerSessionAndSave()
1424 assert(m_player->getPlayerSAO() == this);
1425 m_player->setPeerId(PEER_ID_INEXISTENT);
1426 m_env->savePlayer(m_player);
1427 m_player->setPlayerSAO(NULL);
1428 m_env->removePlayer(m_player);
1431 std::string PlayerSAO::getPropertyPacket()
1433 m_prop.is_visible = (true);
1434 return gob_cmd_set_properties(m_prop);
1437 bool PlayerSAO::checkMovementCheat()
1439 if (isAttached() || m_is_singleplayer ||
1440 g_settings->getBool("disable_anticheat")) {
1441 m_last_good_position = m_base_position;
1445 bool cheated = false;
1447 Check player movements
1449 NOTE: Actually the server should handle player physics like the
1450 client does and compare player's position to what is calculated
1451 on our side. This is required when eg. players fly due to an
1452 explosion. Altough a node-based alternative might be possible
1453 too, and much more lightweight.
1456 float player_max_walk = 0; // horizontal movement
1457 float player_max_jump = 0; // vertical upwards movement
1459 if (m_privs.count("fast") != 0)
1460 player_max_walk = m_player->movement_speed_fast; // Fast speed
1462 player_max_walk = m_player->movement_speed_walk; // Normal speed
1463 player_max_walk *= m_physics_override_speed;
1464 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1465 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1466 // until this can be verified correctly, tolerate higher jumping speeds
1467 player_max_jump *= 2.0;
1469 // Don't divide by zero!
1470 if (player_max_walk < 0.0001f)
1471 player_max_walk = 0.0001f;
1472 if (player_max_jump < 0.0001f)
1473 player_max_jump = 0.0001f;
1475 v3f diff = (m_base_position - m_last_good_position);
1476 float d_vert = diff.Y;
1478 float d_horiz = diff.getLength();
1479 float required_time = d_horiz / player_max_walk;
1481 // FIXME: Checking downwards movement is not easily possible currently,
1482 // the server could calculate speed differences to examine the gravity
1484 // In certain cases (water, ladders) walking speed is applied vertically
1485 float s = MYMAX(player_max_jump, player_max_walk);
1486 required_time = MYMAX(required_time, d_vert / s);
1489 if (m_move_pool.grab(required_time)) {
1490 m_last_good_position = m_base_position;
1492 const float LAG_POOL_MIN = 5.0;
1493 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1494 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1495 if (m_time_from_last_teleport > lag_pool_max) {
1496 actionstream << "Player " << m_player->getName()
1497 << " moved too fast; resetting position"
1501 setBasePosition(m_last_good_position);
1506 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1508 //update collision box
1509 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1510 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1512 toset->MinEdge += m_base_position;
1513 toset->MaxEdge += m_base_position;
1517 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1519 if (!m_prop.is_visible || !m_prop.pointable) {
1523 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1524 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1529 float PlayerSAO::getZoomFOV() const
1531 return m_prop.zoom_fov;