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)) {
458 float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
459 + m_prop.automatic_face_movement_dir_offset;
460 float max_rotation_delta =
461 dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
463 m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
464 wrappedApproachShortest(m_rotation.Y, target_yaw, max_rotation_delta, 360.f);
469 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
472 if (!send_recommended)
477 // TODO: force send when acceleration changes enough?
478 float minchange = 0.2*BS;
479 if(m_last_sent_position_timer > 1.0){
481 } else if(m_last_sent_position_timer > 0.2){
484 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
485 move_d += m_last_sent_move_precision;
486 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
487 if (move_d > minchange || vel_d > minchange ||
488 std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f ||
489 std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f ||
490 std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) {
492 sendPosition(true, false);
496 if (!m_armor_groups_sent) {
497 m_armor_groups_sent = true;
498 std::string str = gob_cmd_update_armor_groups(
500 // create message and add to list
501 ActiveObjectMessage aom(getId(), true, str);
502 m_messages_out.push(aom);
505 if (!m_animation_sent) {
506 m_animation_sent = true;
507 std::string str = gob_cmd_update_animation(
508 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
509 // create message and add to list
510 ActiveObjectMessage aom(getId(), true, str);
511 m_messages_out.push(aom);
514 if (!m_animation_speed_sent) {
515 m_animation_speed_sent = true;
516 std::string str = gob_cmd_update_animation_speed(m_animation_speed);
517 // create message and add to list
518 ActiveObjectMessage aom(getId(), true, str);
519 m_messages_out.push(aom);
522 if (!m_bone_position_sent) {
523 m_bone_position_sent = true;
524 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
525 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
526 std::string str = gob_cmd_update_bone_position((*ii).first,
527 (*ii).second.X, (*ii).second.Y);
528 // create message and add to list
529 ActiveObjectMessage aom(getId(), true, str);
530 m_messages_out.push(aom);
534 if (!m_attachment_sent) {
535 m_attachment_sent = true;
536 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
537 // create message and add to list
538 ActiveObjectMessage aom(getId(), true, str);
539 m_messages_out.push(aom);
543 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
545 std::ostringstream os(std::ios::binary);
547 // PROTOCOL_VERSION >= 37
548 writeU8(os, 1); // version
549 os << serializeString(""); // name
550 writeU8(os, 0); // is_player
551 writeS16(os, getId()); //id
552 writeV3F32(os, m_base_position);
553 writeV3F32(os, m_rotation);
556 std::ostringstream msg_os(std::ios::binary);
557 msg_os << serializeLongString(getPropertyPacket()); // message 1
558 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
559 msg_os << serializeLongString(gob_cmd_update_animation(
560 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
561 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
562 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
563 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
564 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
566 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
567 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
568 int message_count = 4 + m_bone_position.size();
569 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
570 (ii != m_attachment_child_ids.end()); ++ii) {
571 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
573 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
574 obj->getClientInitializationData(protocol_version)));
578 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
581 writeU8(os, message_count);
582 os.write(msg_os.str().c_str(), msg_os.str().size());
588 void LuaEntitySAO::getStaticData(std::string *result) const
590 verbosestream<<FUNCTION_NAME<<std::endl;
591 std::ostringstream os(std::ios::binary);
592 // version must be 1 to keep backwards-compatibility. See version2
595 os<<serializeString(m_init_name);
598 std::string state = m_env->getScriptIface()->
599 luaentity_GetStaticdata(m_id);
600 os<<serializeLongString(state);
602 os<<serializeLongString(m_init_state);
605 writeV3F1000(os, m_velocity);
607 writeF1000(os, m_rotation.Y);
609 // version2. Increase this variable for new values
610 writeU8(os, 1); // PROTOCOL_VERSION >= 37
612 writeF1000(os, m_rotation.X);
613 writeF1000(os, m_rotation.Z);
615 // <write new values>
620 int LuaEntitySAO::punch(v3f dir,
621 const ToolCapabilities *toolcap,
622 ServerActiveObject *puncher,
623 float time_from_last_punch)
626 // Delete unknown LuaEntities when punched
627 m_pending_removal = true;
631 ItemStack *punchitem = NULL;
632 ItemStack punchitem_static;
634 punchitem_static = puncher->getWieldedItem();
635 punchitem = &punchitem_static;
638 PunchDamageResult result = getPunchDamage(
642 time_from_last_punch);
644 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
645 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
647 if (!damage_handled) {
648 if (result.did_punch) {
649 setHP(getHP() - result.damage,
650 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
652 if (result.damage > 0) {
653 std::string punchername = puncher ? puncher->getDescription() : "nil";
655 actionstream << getDescription() << " punched by "
656 << punchername << ", damage " << result.damage
657 << " hp, health now " << getHP() << " hp" << std::endl;
660 std::string str = gob_cmd_punched(result.damage, getHP());
661 // create message and add to list
662 ActiveObjectMessage aom(getId(), true, str);
663 m_messages_out.push(aom);
667 if (getHP() == 0 && !isGone()) {
668 m_pending_removal = true;
669 clearParentAttachment();
670 clearChildAttachments();
671 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
677 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
682 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
685 void LuaEntitySAO::setPos(const v3f &pos)
689 m_base_position = pos;
690 sendPosition(false, true);
693 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
697 m_base_position = pos;
699 sendPosition(true, true);
702 float LuaEntitySAO::getMinimumSavedMovement()
707 std::string LuaEntitySAO::getDescription()
709 std::ostringstream os(std::ios::binary);
710 os<<"LuaEntitySAO at (";
711 os<<(m_base_position.X/BS)<<",";
712 os<<(m_base_position.Y/BS)<<",";
713 os<<(m_base_position.Z/BS);
718 void LuaEntitySAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
725 s16 LuaEntitySAO::getHP() const
730 void LuaEntitySAO::setVelocity(v3f velocity)
732 m_velocity = velocity;
735 v3f LuaEntitySAO::getVelocity()
740 void LuaEntitySAO::setAcceleration(v3f acceleration)
742 m_acceleration = acceleration;
745 v3f LuaEntitySAO::getAcceleration()
747 return m_acceleration;
750 void LuaEntitySAO::setTextureMod(const std::string &mod)
752 std::string str = gob_cmd_set_texture_mod(mod);
753 m_current_texture_modifier = mod;
754 // create message and add to list
755 ActiveObjectMessage aom(getId(), true, str);
756 m_messages_out.push(aom);
759 std::string LuaEntitySAO::getTextureMod() const
761 return m_current_texture_modifier;
764 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
765 bool select_horiz_by_yawpitch)
767 std::string str = gob_cmd_set_sprite(
771 select_horiz_by_yawpitch
773 // create message and add to list
774 ActiveObjectMessage aom(getId(), true, str);
775 m_messages_out.push(aom);
778 std::string LuaEntitySAO::getName()
783 std::string LuaEntitySAO::getPropertyPacket()
785 return gob_cmd_set_properties(m_prop);
788 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
790 // If the object is attached client-side, don't waste bandwidth sending its position to clients
794 m_last_sent_move_precision = m_base_position.getDistanceFrom(
795 m_last_sent_position);
796 m_last_sent_position_timer = 0;
797 m_last_sent_position = m_base_position;
798 m_last_sent_velocity = m_velocity;
799 //m_last_sent_acceleration = m_acceleration;
800 m_last_sent_rotation = m_rotation;
802 float update_interval = m_env->getSendRecommendedInterval();
804 std::string str = gob_cmd_update_position(
813 // create message and add to list
814 ActiveObjectMessage aom(getId(), false, str);
815 m_messages_out.push(aom);
818 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
822 //update collision box
823 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
824 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
826 toset->MinEdge += m_base_position;
827 toset->MaxEdge += m_base_position;
835 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
837 if (!m_prop.is_visible || !m_prop.pointable) {
841 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
842 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
847 bool LuaEntitySAO::collideWithObjects() const
849 return m_prop.collideWithObjects;
856 // No prototype, PlayerSAO does not need to be deserialized
858 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
859 bool is_singleplayer):
860 UnitSAO(env_, v3f(0,0,0)),
863 m_is_singleplayer(is_singleplayer)
865 assert(m_peer_id != 0); // pre-condition
867 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
868 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
869 m_prop.physical = false;
871 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
872 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
873 m_prop.pointable = true;
874 // Start of default appearance, this should be overwritten by Lua
875 m_prop.visual = "upright_sprite";
876 m_prop.visual_size = v3f(1, 2, 1);
877 m_prop.textures.clear();
878 m_prop.textures.emplace_back("player.png");
879 m_prop.textures.emplace_back("player_back.png");
880 m_prop.colors.clear();
881 m_prop.colors.emplace_back(255, 255, 255, 255);
882 m_prop.spritediv = v2s16(1,1);
883 m_prop.eye_height = 1.625f;
884 // End of default appearance
885 m_prop.is_visible = true;
886 m_prop.backface_culling = false;
887 m_prop.makes_footstep_sound = true;
888 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
889 m_hp = m_prop.hp_max;
890 m_breath = m_prop.breath_max;
891 // Disable zoom in survival mode using a value of 0
892 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
895 PlayerSAO::~PlayerSAO()
897 if(m_inventory != &m_player->inventory)
901 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
906 m_inventory = &m_player->inventory;
909 v3f PlayerSAO::getEyeOffset() const
911 return v3f(0, BS * m_prop.eye_height, 0);
914 std::string PlayerSAO::getDescription()
916 return std::string("player ") + m_player->getName();
919 // Called after id has been set and has been inserted in environment
920 void PlayerSAO::addedToEnvironment(u32 dtime_s)
922 ServerActiveObject::addedToEnvironment(dtime_s);
923 ServerActiveObject::setBasePosition(m_base_position);
924 m_player->setPlayerSAO(this);
925 m_player->setPeerId(m_peer_id);
926 m_last_good_position = m_base_position;
929 // Called before removing from environment
930 void PlayerSAO::removingFromEnvironment()
932 ServerActiveObject::removingFromEnvironment();
933 if (m_player->getPlayerSAO() == this) {
934 unlinkPlayerSessionAndSave();
935 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
936 m_env->deleteParticleSpawner(attached_particle_spawner, false);
941 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
943 std::ostringstream os(std::ios::binary);
946 writeU8(os, 1); // version
947 os << serializeString(m_player->getName()); // name
948 writeU8(os, 1); // is_player
949 writeS16(os, getId()); // id
950 writeV3F32(os, m_base_position);
951 writeV3F32(os, m_rotation);
952 writeS16(os, getHP());
954 std::ostringstream msg_os(std::ios::binary);
955 msg_os << serializeLongString(getPropertyPacket()); // message 1
956 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
957 msg_os << serializeLongString(gob_cmd_update_animation(
958 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
959 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
960 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
961 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
962 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
964 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
965 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
966 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
967 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
968 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
969 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
970 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
971 int message_count = 6 + m_bone_position.size();
972 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
973 ii != m_attachment_child_ids.end(); ++ii) {
974 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
976 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
977 obj->getClientInitializationData(protocol_version)));
981 writeU8(os, message_count);
982 os.write(msg_os.str().c_str(), msg_os.str().size());
988 void PlayerSAO::getStaticData(std::string * result) const
990 FATAL_ERROR("Deprecated function");
993 void PlayerSAO::step(float dtime, bool send_recommended)
995 if (m_drowning_interval.step(dtime, 2.0f)) {
996 // Get nose/mouth position, approximate with eye position
997 v3s16 p = floatToInt(getEyePosition(), BS);
998 MapNode n = m_env->getMap().getNodeNoEx(p);
999 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1000 // If node generates drown
1001 if (c.drowning > 0 && m_hp > 0) {
1003 setBreath(m_breath - 1);
1005 // No more breath, damage player
1006 if (m_breath == 0) {
1007 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
1008 setHP(m_hp - c.drowning, reason);
1009 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1014 if (m_breathing_interval.step(dtime, 0.5f)) {
1015 // Get nose/mouth position, approximate with eye position
1016 v3s16 p = floatToInt(getEyePosition(), BS);
1017 MapNode n = m_env->getMap().getNodeNoEx(p);
1018 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1019 // If player is alive & no drowning, breathe
1020 if (m_hp > 0 && m_breath < m_prop.breath_max && c.drowning == 0)
1021 setBreath(m_breath + 1);
1024 if (m_node_hurt_interval.step(dtime, 1.0f)) {
1025 u32 damage_per_second = 0;
1026 // Lowest and highest damage points are 0.1 within collisionbox
1027 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1029 // Sequence of damage points, starting 0.1 above feet and progressing
1030 // upwards in 1 node intervals, stopping below top damage point.
1031 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1032 v3s16 p = floatToInt(m_base_position +
1033 v3f(0.0f, dam_height * BS, 0.0f), BS);
1034 MapNode n = m_env->getMap().getNodeNoEx(p);
1035 damage_per_second = std::max(damage_per_second,
1036 m_env->getGameDef()->ndef()->get(n).damage_per_second);
1040 v3s16 ptop = floatToInt(m_base_position +
1041 v3f(0.0f, dam_top * BS, 0.0f), BS);
1042 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1043 damage_per_second = std::max(damage_per_second,
1044 m_env->getGameDef()->ndef()->get(ntop).damage_per_second);
1046 if (damage_per_second != 0 && m_hp > 0) {
1047 s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second);
1048 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE);
1049 setHP(newhp, reason);
1050 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1054 if (!m_properties_sent) {
1055 m_properties_sent = true;
1056 std::string str = getPropertyPacket();
1057 // create message and add to list
1058 ActiveObjectMessage aom(getId(), true, str);
1059 m_messages_out.push(aom);
1062 // If attached, check that our parent is still there. If it isn't, detach.
1063 if (m_attachment_parent_id && !isAttached()) {
1064 m_attachment_parent_id = 0;
1065 m_attachment_bone = "";
1066 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1067 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1068 setBasePosition(m_last_good_position);
1069 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1072 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1074 // Set lag pool maximums based on estimated lag
1075 const float LAG_POOL_MIN = 5.0f;
1076 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1077 if(lag_pool_max < LAG_POOL_MIN)
1078 lag_pool_max = LAG_POOL_MIN;
1079 m_dig_pool.setMax(lag_pool_max);
1080 m_move_pool.setMax(lag_pool_max);
1082 // Increment cheat prevention timers
1083 m_dig_pool.add(dtime);
1084 m_move_pool.add(dtime);
1085 m_time_from_last_teleport += dtime;
1086 m_time_from_last_punch += dtime;
1087 m_nocheat_dig_time += dtime;
1089 // Each frame, parent position is copied if the object is attached,
1090 // otherwise it's calculated normally.
1091 // If the object gets detached this comes into effect automatically from
1092 // the last known origin.
1094 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1095 m_last_good_position = pos;
1096 setBasePosition(pos);
1099 if (!send_recommended)
1102 // If the object is attached client-side, don't waste bandwidth sending its
1103 // position or rotation to clients.
1104 if (m_position_not_sent && !isAttached()) {
1105 m_position_not_sent = false;
1106 float update_interval = m_env->getSendRecommendedInterval();
1108 if (isAttached()) // Just in case we ever do send attachment position too
1109 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1111 pos = m_base_position;
1113 std::string str = gob_cmd_update_position(
1115 v3f(0.0f, 0.0f, 0.0f),
1116 v3f(0.0f, 0.0f, 0.0f),
1122 // create message and add to list
1123 ActiveObjectMessage aom(getId(), false, str);
1124 m_messages_out.push(aom);
1127 if (!m_armor_groups_sent) {
1128 m_armor_groups_sent = true;
1129 std::string str = gob_cmd_update_armor_groups(
1131 // create message and add to list
1132 ActiveObjectMessage aom(getId(), true, str);
1133 m_messages_out.push(aom);
1136 if (!m_physics_override_sent) {
1137 m_physics_override_sent = true;
1138 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1139 m_physics_override_jump, m_physics_override_gravity,
1140 m_physics_override_sneak, m_physics_override_sneak_glitch,
1141 m_physics_override_new_move);
1142 // create message and add to list
1143 ActiveObjectMessage aom(getId(), true, str);
1144 m_messages_out.push(aom);
1147 if (!m_animation_sent) {
1148 m_animation_sent = true;
1149 std::string str = gob_cmd_update_animation(
1150 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1151 // create message and add to list
1152 ActiveObjectMessage aom(getId(), true, str);
1153 m_messages_out.push(aom);
1156 if (!m_bone_position_sent) {
1157 m_bone_position_sent = true;
1158 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1159 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1160 std::string str = gob_cmd_update_bone_position((*ii).first,
1161 (*ii).second.X, (*ii).second.Y);
1162 // create message and add to list
1163 ActiveObjectMessage aom(getId(), true, str);
1164 m_messages_out.push(aom);
1168 if (!m_attachment_sent) {
1169 m_attachment_sent = true;
1170 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1171 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1172 // create message and add to list
1173 ActiveObjectMessage aom(getId(), true, str);
1174 m_messages_out.push(aom);
1178 void PlayerSAO::setBasePosition(const v3f &position)
1180 if (m_player && position != m_base_position)
1181 m_player->setDirty(true);
1183 // This needs to be ran for attachments too
1184 ServerActiveObject::setBasePosition(position);
1186 // Updating is not wanted/required for player migration
1188 m_position_not_sent = true;
1192 void PlayerSAO::setPos(const v3f &pos)
1197 setBasePosition(pos);
1198 // Movement caused by this command is always valid
1199 m_last_good_position = pos;
1200 m_move_pool.empty();
1201 m_time_from_last_teleport = 0.0;
1202 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1205 void PlayerSAO::moveTo(v3f pos, bool continuous)
1210 setBasePosition(pos);
1211 // Movement caused by this command is always valid
1212 m_last_good_position = pos;
1213 m_move_pool.empty();
1214 m_time_from_last_teleport = 0.0;
1215 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1218 void PlayerSAO::setPlayerYaw(const float yaw)
1220 v3f rotation(0, yaw, 0);
1221 if (m_player && yaw != m_rotation.Y)
1222 m_player->setDirty(true);
1224 // Set player model yaw, not look view
1225 UnitSAO::setRotation(rotation);
1228 void PlayerSAO::setFov(const float fov)
1230 if (m_player && fov != m_fov)
1231 m_player->setDirty(true);
1236 void PlayerSAO::setWantedRange(const s16 range)
1238 if (m_player && range != m_wanted_range)
1239 m_player->setDirty(true);
1241 m_wanted_range = range;
1244 void PlayerSAO::setPlayerYawAndSend(const float yaw)
1247 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1250 void PlayerSAO::setLookPitch(const float pitch)
1252 if (m_player && pitch != m_pitch)
1253 m_player->setDirty(true);
1258 void PlayerSAO::setLookPitchAndSend(const float pitch)
1260 setLookPitch(pitch);
1261 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1264 int PlayerSAO::punch(v3f dir,
1265 const ToolCapabilities *toolcap,
1266 ServerActiveObject *puncher,
1267 float time_from_last_punch)
1272 // No effect if PvP disabled
1273 if (!g_settings->getBool("enable_pvp")) {
1274 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1275 std::string str = gob_cmd_punched(0, getHP());
1276 // create message and add to list
1277 ActiveObjectMessage aom(getId(), true, str);
1278 m_messages_out.push(aom);
1283 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1284 time_from_last_punch);
1286 std::string punchername = "nil";
1289 punchername = puncher->getDescription();
1291 PlayerSAO *playersao = m_player->getPlayerSAO();
1293 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1294 puncher, time_from_last_punch, toolcap, dir,
1297 if (!damage_handled) {
1298 setHP(getHP() - hitparams.hp,
1299 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1300 } else { // override client prediction
1301 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1302 std::string str = gob_cmd_punched(0, getHP());
1303 // create message and add to list
1304 ActiveObjectMessage aom(getId(), true, str);
1305 m_messages_out.push(aom);
1310 actionstream << "Player " << m_player->getName() << " punched by "
1312 if (!damage_handled) {
1313 actionstream << ", damage " << hitparams.hp << " HP";
1315 actionstream << ", damage handled by lua";
1317 actionstream << std::endl;
1319 return hitparams.wear;
1322 s16 PlayerSAO::readDamage()
1324 s16 damage = m_damage;
1329 void PlayerSAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
1333 s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1336 hp = oldhp + hp_change;
1340 else if (hp > m_prop.hp_max)
1343 if (hp < oldhp && !g_settings->getBool("enable_damage")) {
1350 m_damage += (oldhp - hp);
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 = MYMIN(breath, 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;