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);
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;
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_yaw = wrapDegrees_0_360(m_yaw);
447 wrappedApproachShortest(m_yaw, 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_yaw - m_last_sent_yaw) > 1.0) {
472 sendPosition(true, false);
476 if (!m_armor_groups_sent) {
477 m_armor_groups_sent = true;
478 std::string str = gob_cmd_update_armor_groups(
480 // create message and add to list
481 ActiveObjectMessage aom(getId(), true, str);
482 m_messages_out.push(aom);
485 if (!m_animation_sent) {
486 m_animation_sent = true;
487 std::string str = gob_cmd_update_animation(
488 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
489 // create message and add to list
490 ActiveObjectMessage aom(getId(), true, str);
491 m_messages_out.push(aom);
494 if (!m_animation_speed_sent) {
495 m_animation_speed_sent = true;
496 std::string str = gob_cmd_update_animation_speed(m_animation_speed);
497 // create message and add to list
498 ActiveObjectMessage aom(getId(), true, str);
499 m_messages_out.push(aom);
502 if (!m_bone_position_sent) {
503 m_bone_position_sent = true;
504 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
505 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
506 std::string str = gob_cmd_update_bone_position((*ii).first,
507 (*ii).second.X, (*ii).second.Y);
508 // create message and add to list
509 ActiveObjectMessage aom(getId(), true, str);
510 m_messages_out.push(aom);
514 if (!m_attachment_sent) {
515 m_attachment_sent = true;
516 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
517 // create message and add to list
518 ActiveObjectMessage aom(getId(), true, str);
519 m_messages_out.push(aom);
523 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
525 std::ostringstream os(std::ios::binary);
528 writeU8(os, 1); // version
529 os << serializeString(""); // name
530 writeU8(os, 0); // is_player
531 writeS16(os, getId()); //id
532 writeV3F1000(os, m_base_position);
533 writeF1000(os, m_yaw);
536 std::ostringstream msg_os(std::ios::binary);
537 msg_os << serializeLongString(getPropertyPacket()); // message 1
538 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
539 msg_os << serializeLongString(gob_cmd_update_animation(
540 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
541 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
542 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
543 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
544 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
546 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
547 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
548 int message_count = 4 + m_bone_position.size();
549 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
550 (ii != m_attachment_child_ids.end()); ++ii) {
551 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
553 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
554 obj->getClientInitializationData(protocol_version)));
558 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
561 writeU8(os, message_count);
562 os.write(msg_os.str().c_str(), msg_os.str().size());
568 void LuaEntitySAO::getStaticData(std::string *result) const
570 verbosestream<<FUNCTION_NAME<<std::endl;
571 std::ostringstream os(std::ios::binary);
575 os<<serializeString(m_init_name);
578 std::string state = m_env->getScriptIface()->
579 luaentity_GetStaticdata(m_id);
580 os<<serializeLongString(state);
582 os<<serializeLongString(m_init_state);
587 writeV3F1000(os, m_velocity);
589 writeF1000(os, m_yaw);
593 int LuaEntitySAO::punch(v3f dir,
594 const ToolCapabilities *toolcap,
595 ServerActiveObject *puncher,
596 float time_from_last_punch)
599 // Delete unknown LuaEntities when punched
600 m_pending_removal = true;
604 ItemStack *punchitem = NULL;
605 ItemStack punchitem_static;
607 punchitem_static = puncher->getWieldedItem();
608 punchitem = &punchitem_static;
611 PunchDamageResult result = getPunchDamage(
615 time_from_last_punch);
617 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
618 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
620 if (!damage_handled) {
621 if (result.did_punch) {
622 setHP(getHP() - result.damage,
623 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
625 if (result.damage > 0) {
626 std::string punchername = puncher ? puncher->getDescription() : "nil";
628 actionstream << getDescription() << " punched by "
629 << punchername << ", damage " << result.damage
630 << " hp, health now " << getHP() << " hp" << std::endl;
633 std::string str = gob_cmd_punched(result.damage, getHP());
634 // create message and add to list
635 ActiveObjectMessage aom(getId(), true, str);
636 m_messages_out.push(aom);
640 if (getHP() == 0 && !isGone()) {
641 m_pending_removal = true;
642 clearParentAttachment();
643 clearChildAttachments();
644 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
650 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
655 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
658 void LuaEntitySAO::setPos(const v3f &pos)
662 m_base_position = pos;
663 sendPosition(false, true);
666 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
670 m_base_position = pos;
672 sendPosition(true, true);
675 float LuaEntitySAO::getMinimumSavedMovement()
680 std::string LuaEntitySAO::getDescription()
682 std::ostringstream os(std::ios::binary);
683 os<<"LuaEntitySAO at (";
684 os<<(m_base_position.X/BS)<<",";
685 os<<(m_base_position.Y/BS)<<",";
686 os<<(m_base_position.Z/BS);
691 void LuaEntitySAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
698 s16 LuaEntitySAO::getHP() const
703 void LuaEntitySAO::setVelocity(v3f velocity)
705 m_velocity = velocity;
708 v3f LuaEntitySAO::getVelocity()
713 void LuaEntitySAO::setAcceleration(v3f acceleration)
715 m_acceleration = acceleration;
718 v3f LuaEntitySAO::getAcceleration()
720 return m_acceleration;
723 void LuaEntitySAO::setTextureMod(const std::string &mod)
725 std::string str = gob_cmd_set_texture_mod(mod);
726 m_current_texture_modifier = mod;
727 // create message and add to list
728 ActiveObjectMessage aom(getId(), true, str);
729 m_messages_out.push(aom);
732 std::string LuaEntitySAO::getTextureMod() const
734 return m_current_texture_modifier;
737 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
738 bool select_horiz_by_yawpitch)
740 std::string str = gob_cmd_set_sprite(
744 select_horiz_by_yawpitch
746 // create message and add to list
747 ActiveObjectMessage aom(getId(), true, str);
748 m_messages_out.push(aom);
751 std::string LuaEntitySAO::getName()
756 std::string LuaEntitySAO::getPropertyPacket()
758 return gob_cmd_set_properties(m_prop);
761 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
763 // If the object is attached client-side, don't waste bandwidth sending its position to clients
767 m_last_sent_move_precision = m_base_position.getDistanceFrom(
768 m_last_sent_position);
769 m_last_sent_position_timer = 0;
770 m_last_sent_yaw = m_yaw;
771 m_last_sent_position = m_base_position;
772 m_last_sent_velocity = m_velocity;
773 //m_last_sent_acceleration = m_acceleration;
775 float update_interval = m_env->getSendRecommendedInterval();
777 std::string str = gob_cmd_update_position(
786 // create message and add to list
787 ActiveObjectMessage aom(getId(), false, str);
788 m_messages_out.push(aom);
791 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
795 //update collision box
796 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
797 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
799 toset->MinEdge += m_base_position;
800 toset->MaxEdge += m_base_position;
808 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
810 if (!m_prop.is_visible || !m_prop.pointable) {
814 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
815 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
820 bool LuaEntitySAO::collideWithObjects() const
822 return m_prop.collideWithObjects;
829 // No prototype, PlayerSAO does not need to be deserialized
831 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
832 bool is_singleplayer):
833 UnitSAO(env_, v3f(0,0,0)),
836 m_is_singleplayer(is_singleplayer)
838 assert(m_peer_id != 0); // pre-condition
840 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
841 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
842 m_prop.physical = false;
844 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
845 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
846 m_prop.pointable = true;
847 // Start of default appearance, this should be overwritten by Lua
848 m_prop.visual = "upright_sprite";
849 m_prop.visual_size = v2f(1, 2);
850 m_prop.textures.clear();
851 m_prop.textures.emplace_back("player.png");
852 m_prop.textures.emplace_back("player_back.png");
853 m_prop.colors.clear();
854 m_prop.colors.emplace_back(255, 255, 255, 255);
855 m_prop.spritediv = v2s16(1,1);
856 m_prop.eye_height = 1.625f;
857 // End of default appearance
858 m_prop.is_visible = true;
859 m_prop.backface_culling = false;
860 m_prop.makes_footstep_sound = true;
861 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
862 m_hp = m_prop.hp_max;
863 m_breath = m_prop.breath_max;
864 // Disable zoom in survival mode using a value of 0
865 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
868 PlayerSAO::~PlayerSAO()
870 if(m_inventory != &m_player->inventory)
874 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
879 m_inventory = &m_player->inventory;
882 v3f PlayerSAO::getEyeOffset() const
884 return v3f(0, BS * m_prop.eye_height, 0);
887 std::string PlayerSAO::getDescription()
889 return std::string("player ") + m_player->getName();
892 // Called after id has been set and has been inserted in environment
893 void PlayerSAO::addedToEnvironment(u32 dtime_s)
895 ServerActiveObject::addedToEnvironment(dtime_s);
896 ServerActiveObject::setBasePosition(m_base_position);
897 m_player->setPlayerSAO(this);
898 m_player->setPeerId(m_peer_id);
899 m_last_good_position = m_base_position;
902 // Called before removing from environment
903 void PlayerSAO::removingFromEnvironment()
905 ServerActiveObject::removingFromEnvironment();
906 if (m_player->getPlayerSAO() == this) {
907 unlinkPlayerSessionAndSave();
908 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
909 m_env->deleteParticleSpawner(attached_particle_spawner, false);
914 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
916 std::ostringstream os(std::ios::binary);
919 writeU8(os, 1); // version
920 os << serializeString(m_player->getName()); // name
921 writeU8(os, 1); // is_player
922 writeS16(os, getId()); //id
923 writeV3F1000(os, m_base_position);
924 writeF1000(os, m_yaw);
925 writeS16(os, getHP());
927 std::ostringstream msg_os(std::ios::binary);
928 msg_os << serializeLongString(getPropertyPacket()); // message 1
929 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
930 msg_os << serializeLongString(gob_cmd_update_animation(
931 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
932 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
933 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
934 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
935 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
937 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
938 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
939 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
940 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
941 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
942 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
943 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
944 int message_count = 6 + m_bone_position.size();
945 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
946 ii != m_attachment_child_ids.end(); ++ii) {
947 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
949 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
950 obj->getClientInitializationData(protocol_version)));
954 writeU8(os, message_count);
955 os.write(msg_os.str().c_str(), msg_os.str().size());
961 void PlayerSAO::getStaticData(std::string * result) const
963 FATAL_ERROR("Deprecated function");
966 void PlayerSAO::step(float dtime, bool send_recommended)
968 if (m_drowning_interval.step(dtime, 2.0f)) {
969 // Get nose/mouth position, approximate with eye position
970 v3s16 p = floatToInt(getEyePosition(), BS);
971 MapNode n = m_env->getMap().getNodeNoEx(p);
972 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
973 // If node generates drown
974 if (c.drowning > 0 && m_hp > 0) {
976 setBreath(m_breath - 1);
978 // No more breath, damage player
980 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
981 setHP(m_hp - c.drowning, reason);
982 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
987 if (m_breathing_interval.step(dtime, 0.5f)) {
988 // Get nose/mouth position, approximate with eye position
989 v3s16 p = floatToInt(getEyePosition(), BS);
990 MapNode n = m_env->getMap().getNodeNoEx(p);
991 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
992 // If player is alive & no drowning, breathe
993 if (m_hp > 0 && m_breath < m_prop.breath_max && c.drowning == 0)
994 setBreath(m_breath + 1);
997 if (m_node_hurt_interval.step(dtime, 1.0f)) {
998 u32 damage_per_second = 0;
999 // Lowest and highest damage points are 0.1 within collisionbox
1000 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1002 // Sequence of damage points, starting 0.1 above feet and progressing
1003 // upwards in 1 node intervals, stopping below top damage point.
1004 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1005 v3s16 p = floatToInt(m_base_position +
1006 v3f(0.0f, dam_height * BS, 0.0f), BS);
1007 MapNode n = m_env->getMap().getNodeNoEx(p);
1008 damage_per_second = std::max(damage_per_second,
1009 m_env->getGameDef()->ndef()->get(n).damage_per_second);
1013 v3s16 ptop = floatToInt(m_base_position +
1014 v3f(0.0f, dam_top * BS, 0.0f), BS);
1015 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1016 damage_per_second = std::max(damage_per_second,
1017 m_env->getGameDef()->ndef()->get(ntop).damage_per_second);
1019 if (damage_per_second != 0 && m_hp > 0) {
1020 s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second);
1021 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE);
1022 setHP(newhp, reason);
1023 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1027 if (!m_properties_sent) {
1028 m_properties_sent = true;
1029 std::string str = getPropertyPacket();
1030 // create message and add to list
1031 ActiveObjectMessage aom(getId(), true, str);
1032 m_messages_out.push(aom);
1035 // If attached, check that our parent is still there. If it isn't, detach.
1036 if (m_attachment_parent_id && !isAttached()) {
1037 m_attachment_parent_id = 0;
1038 m_attachment_bone = "";
1039 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1040 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1041 setBasePosition(m_last_good_position);
1042 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1045 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1047 // Set lag pool maximums based on estimated lag
1048 const float LAG_POOL_MIN = 5.0f;
1049 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1050 if(lag_pool_max < LAG_POOL_MIN)
1051 lag_pool_max = LAG_POOL_MIN;
1052 m_dig_pool.setMax(lag_pool_max);
1053 m_move_pool.setMax(lag_pool_max);
1055 // Increment cheat prevention timers
1056 m_dig_pool.add(dtime);
1057 m_move_pool.add(dtime);
1058 m_time_from_last_teleport += dtime;
1059 m_time_from_last_punch += dtime;
1060 m_nocheat_dig_time += dtime;
1062 // Each frame, parent position is copied if the object is attached,
1063 // otherwise it's calculated normally.
1064 // If the object gets detached this comes into effect automatically from
1065 // the last known origin.
1067 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1068 m_last_good_position = pos;
1069 setBasePosition(pos);
1072 if (!send_recommended)
1075 // If the object is attached client-side, don't waste bandwidth sending its
1076 // position to clients.
1077 if (m_position_not_sent && !isAttached()) {
1078 m_position_not_sent = false;
1079 float update_interval = m_env->getSendRecommendedInterval();
1081 if (isAttached()) // Just in case we ever do send attachment position too
1082 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1084 pos = m_base_position;
1086 std::string str = gob_cmd_update_position(
1088 v3f(0.0f, 0.0f, 0.0f),
1089 v3f(0.0f, 0.0f, 0.0f),
1095 // create message and add to list
1096 ActiveObjectMessage aom(getId(), false, str);
1097 m_messages_out.push(aom);
1100 if (!m_armor_groups_sent) {
1101 m_armor_groups_sent = true;
1102 std::string str = gob_cmd_update_armor_groups(
1104 // create message and add to list
1105 ActiveObjectMessage aom(getId(), true, str);
1106 m_messages_out.push(aom);
1109 if (!m_physics_override_sent) {
1110 m_physics_override_sent = true;
1111 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1112 m_physics_override_jump, m_physics_override_gravity,
1113 m_physics_override_sneak, m_physics_override_sneak_glitch,
1114 m_physics_override_new_move);
1115 // create message and add to list
1116 ActiveObjectMessage aom(getId(), true, str);
1117 m_messages_out.push(aom);
1120 if (!m_animation_sent) {
1121 m_animation_sent = true;
1122 std::string str = gob_cmd_update_animation(
1123 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1124 // create message and add to list
1125 ActiveObjectMessage aom(getId(), true, str);
1126 m_messages_out.push(aom);
1129 if (!m_bone_position_sent) {
1130 m_bone_position_sent = true;
1131 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1132 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1133 std::string str = gob_cmd_update_bone_position((*ii).first,
1134 (*ii).second.X, (*ii).second.Y);
1135 // create message and add to list
1136 ActiveObjectMessage aom(getId(), true, str);
1137 m_messages_out.push(aom);
1141 if (!m_attachment_sent) {
1142 m_attachment_sent = true;
1143 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1144 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1145 // create message and add to list
1146 ActiveObjectMessage aom(getId(), true, str);
1147 m_messages_out.push(aom);
1151 void PlayerSAO::setBasePosition(const v3f &position)
1153 if (m_player && position != m_base_position)
1154 m_player->setDirty(true);
1156 // This needs to be ran for attachments too
1157 ServerActiveObject::setBasePosition(position);
1159 // Updating is not wanted/required for player migration
1161 m_position_not_sent = true;
1165 void PlayerSAO::setPos(const v3f &pos)
1170 setBasePosition(pos);
1171 // Movement caused by this command is always valid
1172 m_last_good_position = pos;
1173 m_move_pool.empty();
1174 m_time_from_last_teleport = 0.0;
1175 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1178 void PlayerSAO::moveTo(v3f pos, bool continuous)
1183 setBasePosition(pos);
1184 // Movement caused by this command is always valid
1185 m_last_good_position = pos;
1186 m_move_pool.empty();
1187 m_time_from_last_teleport = 0.0;
1188 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1191 void PlayerSAO::setYaw(const float yaw)
1193 if (m_player && yaw != m_yaw)
1194 m_player->setDirty(true);
1196 UnitSAO::setYaw(yaw);
1199 void PlayerSAO::setFov(const float fov)
1201 if (m_player && fov != m_fov)
1202 m_player->setDirty(true);
1207 void PlayerSAO::setWantedRange(const s16 range)
1209 if (m_player && range != m_wanted_range)
1210 m_player->setDirty(true);
1212 m_wanted_range = range;
1215 void PlayerSAO::setYawAndSend(const float yaw)
1218 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1221 void PlayerSAO::setPitch(const float pitch)
1223 if (m_player && pitch != m_pitch)
1224 m_player->setDirty(true);
1229 void PlayerSAO::setPitchAndSend(const float pitch)
1232 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1235 int PlayerSAO::punch(v3f dir,
1236 const ToolCapabilities *toolcap,
1237 ServerActiveObject *puncher,
1238 float time_from_last_punch)
1243 // No effect if PvP disabled
1244 if (!g_settings->getBool("enable_pvp")) {
1245 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1246 std::string str = gob_cmd_punched(0, getHP());
1247 // create message and add to list
1248 ActiveObjectMessage aom(getId(), true, str);
1249 m_messages_out.push(aom);
1254 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1255 time_from_last_punch);
1257 std::string punchername = "nil";
1260 punchername = puncher->getDescription();
1262 PlayerSAO *playersao = m_player->getPlayerSAO();
1264 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1265 puncher, time_from_last_punch, toolcap, dir,
1268 if (!damage_handled) {
1269 setHP(getHP() - hitparams.hp,
1270 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1271 } else { // override client prediction
1272 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1273 std::string str = gob_cmd_punched(0, getHP());
1274 // create message and add to list
1275 ActiveObjectMessage aom(getId(), true, str);
1276 m_messages_out.push(aom);
1281 actionstream << "Player " << m_player->getName() << " punched by "
1283 if (!damage_handled) {
1284 actionstream << ", damage " << hitparams.hp << " HP";
1286 actionstream << ", damage handled by lua";
1288 actionstream << std::endl;
1290 return hitparams.wear;
1293 s16 PlayerSAO::readDamage()
1295 s16 damage = m_damage;
1300 void PlayerSAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
1304 s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1307 hp = oldhp + hp_change;
1311 else if (hp > m_prop.hp_max)
1314 if (hp < oldhp && !g_settings->getBool("enable_damage")) {
1321 m_damage += (oldhp - hp);
1323 // Update properties on death
1324 if ((hp == 0) != (oldhp == 0))
1325 m_properties_sent = false;
1328 void PlayerSAO::setBreath(const u16 breath, bool send)
1330 if (m_player && breath != m_breath)
1331 m_player->setDirty(true);
1333 m_breath = MYMIN(breath, m_prop.breath_max);
1336 m_env->getGameDef()->SendPlayerBreath(this);
1339 Inventory* PlayerSAO::getInventory()
1343 const Inventory* PlayerSAO::getInventory() const
1348 InventoryLocation PlayerSAO::getInventoryLocation() const
1350 InventoryLocation loc;
1351 loc.setPlayer(m_player->getName());
1355 std::string PlayerSAO::getWieldList() const
1360 ItemStack PlayerSAO::getWieldedItem() const
1362 const Inventory *inv = getInventory();
1364 const InventoryList *mlist = inv->getList(getWieldList());
1365 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1366 ret = mlist->getItem(getWieldIndex());
1370 ItemStack PlayerSAO::getWieldedItemOrHand() const
1372 const Inventory *inv = getInventory();
1374 const InventoryList *mlist = inv->getList(getWieldList());
1375 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1376 ret = mlist->getItem(getWieldIndex());
1377 if (ret.name.empty()) {
1378 const InventoryList *hlist = inv->getList("hand");
1380 ret = hlist->getItem(0);
1385 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1387 Inventory *inv = getInventory();
1389 InventoryList *mlist = inv->getList(getWieldList());
1391 mlist->changeItem(getWieldIndex(), item);
1398 int PlayerSAO::getWieldIndex() const
1400 return m_wield_index;
1403 void PlayerSAO::setWieldIndex(int i)
1405 if(i != m_wield_index) {
1410 void PlayerSAO::disconnected()
1413 m_pending_removal = true;
1416 void PlayerSAO::unlinkPlayerSessionAndSave()
1418 assert(m_player->getPlayerSAO() == this);
1419 m_player->setPeerId(PEER_ID_INEXISTENT);
1420 m_env->savePlayer(m_player);
1421 m_player->setPlayerSAO(NULL);
1422 m_env->removePlayer(m_player);
1425 std::string PlayerSAO::getPropertyPacket()
1427 m_prop.is_visible = (true);
1428 return gob_cmd_set_properties(m_prop);
1431 bool PlayerSAO::checkMovementCheat()
1433 if (isAttached() || m_is_singleplayer ||
1434 g_settings->getBool("disable_anticheat")) {
1435 m_last_good_position = m_base_position;
1439 bool cheated = false;
1441 Check player movements
1443 NOTE: Actually the server should handle player physics like the
1444 client does and compare player's position to what is calculated
1445 on our side. This is required when eg. players fly due to an
1446 explosion. Altough a node-based alternative might be possible
1447 too, and much more lightweight.
1450 float player_max_walk = 0; // horizontal movement
1451 float player_max_jump = 0; // vertical upwards movement
1453 if (m_privs.count("fast") != 0)
1454 player_max_walk = m_player->movement_speed_fast; // Fast speed
1456 player_max_walk = m_player->movement_speed_walk; // Normal speed
1457 player_max_walk *= m_physics_override_speed;
1458 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1459 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1460 // until this can be verified correctly, tolerate higher jumping speeds
1461 player_max_jump *= 2.0;
1463 // Don't divide by zero!
1464 if (player_max_walk < 0.0001f)
1465 player_max_walk = 0.0001f;
1466 if (player_max_jump < 0.0001f)
1467 player_max_jump = 0.0001f;
1469 v3f diff = (m_base_position - m_last_good_position);
1470 float d_vert = diff.Y;
1472 float d_horiz = diff.getLength();
1473 float required_time = d_horiz / player_max_walk;
1475 // FIXME: Checking downwards movement is not easily possible currently,
1476 // the server could calculate speed differences to examine the gravity
1478 // In certain cases (water, ladders) walking speed is applied vertically
1479 float s = MYMAX(player_max_jump, player_max_walk);
1480 required_time = MYMAX(required_time, d_vert / s);
1483 if (m_move_pool.grab(required_time)) {
1484 m_last_good_position = m_base_position;
1486 const float LAG_POOL_MIN = 5.0;
1487 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1488 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1489 if (m_time_from_last_teleport > lag_pool_max) {
1490 actionstream << "Player " << m_player->getName()
1491 << " moved too fast; resetting position"
1495 setBasePosition(m_last_good_position);
1500 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1502 //update collision box
1503 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1504 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1506 toset->MinEdge += m_base_position;
1507 toset->MaxEdge += m_base_position;
1511 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1513 if (!m_prop.is_visible || !m_prop.pointable) {
1517 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1518 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1523 float PlayerSAO::getZoomFOV() const
1525 return m_prop.zoom_fov;