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 bool UnitSAO::isAttached() const
123 if (!m_attachment_parent_id)
125 // Check if the parent still exists
126 ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id);
132 void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups)
134 m_armor_groups = armor_groups;
135 m_armor_groups_sent = false;
138 const ItemGroupList &UnitSAO::getArmorGroups()
140 return m_armor_groups;
143 void UnitSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop)
145 // store these so they can be updated to clients
146 m_animation_range = frame_range;
147 m_animation_speed = frame_speed;
148 m_animation_blend = frame_blend;
149 m_animation_loop = frame_loop;
150 m_animation_sent = false;
153 void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop)
155 *frame_range = m_animation_range;
156 *frame_speed = m_animation_speed;
157 *frame_blend = m_animation_blend;
158 *frame_loop = m_animation_loop;
161 void UnitSAO::setAnimationSpeed(float frame_speed)
163 m_animation_speed = frame_speed;
164 m_animation_speed_sent = false;
167 void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation)
169 // store these so they can be updated to clients
170 m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
171 m_bone_position_sent = false;
174 void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation)
176 *position = m_bone_position[bone].X;
177 *rotation = m_bone_position[bone].Y;
180 void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation)
182 // Attachments need to be handled on both the server and client.
183 // If we just attach on the server, we can only copy the position of the parent. Attachments
184 // are still sent to clients at an interval so players might see them lagging, plus we can't
185 // read and attach to skeletal bones.
186 // If we just attach on the client, the server still sees the child at its original location.
187 // This breaks some things so we also give the server the most accurate representation
188 // even if players only see the client changes.
190 int old_parent = m_attachment_parent_id;
191 m_attachment_parent_id = parent_id;
192 m_attachment_bone = bone;
193 m_attachment_position = position;
194 m_attachment_rotation = rotation;
195 m_attachment_sent = false;
197 if (parent_id != old_parent) {
198 onDetach(old_parent);
203 void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
206 *parent_id = m_attachment_parent_id;
207 *bone = m_attachment_bone;
208 *position = m_attachment_position;
209 *rotation = m_attachment_rotation;
212 void UnitSAO::clearChildAttachments()
214 for (int child_id : m_attachment_child_ids) {
215 // Child can be NULL if it was deleted earlier
216 if (ServerActiveObject *child = m_env->getActiveObject(child_id))
217 child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
219 m_attachment_child_ids.clear();
222 void UnitSAO::clearParentAttachment()
224 ServerActiveObject *parent = nullptr;
225 if (m_attachment_parent_id) {
226 parent = m_env->getActiveObject(m_attachment_parent_id);
227 setAttachment(0, "", m_attachment_position, m_attachment_rotation);
229 setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
233 parent->removeAttachmentChild(m_id);
236 void UnitSAO::addAttachmentChild(int child_id)
238 m_attachment_child_ids.insert(child_id);
241 void UnitSAO::removeAttachmentChild(int child_id)
243 m_attachment_child_ids.erase(child_id);
246 const std::unordered_set<int> &UnitSAO::getAttachmentChildIds()
248 return m_attachment_child_ids;
251 void UnitSAO::onAttach(int parent_id)
256 ServerActiveObject *parent = m_env->getActiveObject(parent_id);
258 if (!parent || parent->isGone())
259 return; // Do not try to notify soon gone parent
261 if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) {
262 // Call parent's on_attach field
263 m_env->getScriptIface()->luaentity_on_attach_child(parent_id, this);
267 void UnitSAO::onDetach(int parent_id)
272 ServerActiveObject *parent = m_env->getActiveObject(parent_id);
273 if (getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
274 m_env->getScriptIface()->luaentity_on_detach(m_id, parent);
276 if (!parent || parent->isGone())
277 return; // Do not try to notify soon gone parent
279 if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
280 m_env->getScriptIface()->luaentity_on_detach_child(parent_id, this);
283 ObjectProperties* UnitSAO::accessObjectProperties()
288 void UnitSAO::notifyObjectPropertiesModified()
290 m_env->updateActiveObject(this);
291 m_properties_sent = false;
298 // Prototype (registers item for deserialization)
299 LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", "");
301 LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
302 const std::string &name, const std::string &state):
307 // Only register type if no environment supplied
309 ServerActiveObject::registerType(getType(), create);
314 LuaEntitySAO::~LuaEntitySAO()
317 m_env->getScriptIface()->luaentity_Remove(m_id);
320 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
321 m_env->deleteParticleSpawner(attached_particle_spawner, false);
325 void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
327 ServerActiveObject::addedToEnvironment(dtime_s);
329 // Create entity from name
330 m_registered = m_env->getScriptIface()->
331 luaentity_Add(m_id, m_init_name.c_str());
335 m_env->getScriptIface()->
336 luaentity_GetProperties(m_id, &m_prop);
337 // Notify the environment of the new properties
338 m_env->updateActiveObject(this);
339 // Initialize HP from properties
340 m_hp = m_prop.hp_max;
341 // Activate entity, supplying serialized state
342 m_env->getScriptIface()->
343 luaentity_Activate(m_id, m_init_state, dtime_s);
345 m_prop.infotext = m_init_name;
349 ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
350 const std::string &data)
358 std::istringstream is(data, std::ios::binary);
360 u8 version = readU8(is);
361 // check if version is supported
363 name = deSerializeString(is);
364 state = deSerializeLongString(is);
366 else if(version == 1){
367 name = deSerializeString(is);
368 state = deSerializeLongString(is);
370 velocity = readV3F1000(is);
375 infostream<<"LuaEntitySAO::create(name=\""<<name<<"\" state=\""
376 <<state<<"\")"<<std::endl;
377 LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
379 sao->m_velocity = velocity;
384 void LuaEntitySAO::step(float dtime, bool send_recommended)
386 if(!m_properties_sent)
388 m_properties_sent = true;
389 std::string str = getPropertyPacket();
390 // create message and add to list
391 ActiveObjectMessage aom(getId(), true, str);
392 m_messages_out.push(aom);
395 // If attached, check that our parent is still there. If it isn't, detach.
396 if(m_attachment_parent_id && !isAttached())
398 m_attachment_parent_id = 0;
399 m_attachment_bone = "";
400 m_attachment_position = v3f(0,0,0);
401 m_attachment_rotation = v3f(0,0,0);
402 sendPosition(false, true);
405 m_last_sent_position_timer += dtime;
407 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
408 // If the object gets detached this comes into effect automatically from the last known origin
411 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
412 m_base_position = pos;
413 m_velocity = v3f(0,0,0);
414 m_acceleration = v3f(0,0,0);
419 aabb3f box = m_prop.collisionbox;
422 collisionMoveResult moveresult;
423 f32 pos_max_d = BS*0.25; // Distance per iteration
424 v3f p_pos = m_base_position;
425 v3f p_velocity = m_velocity;
426 v3f p_acceleration = m_acceleration;
427 moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
428 pos_max_d, box, m_prop.stepheight, dtime,
429 &p_pos, &p_velocity, p_acceleration,
430 this, m_prop.collideWithObjects);
433 m_base_position = p_pos;
434 m_velocity = p_velocity;
435 m_acceleration = p_acceleration;
437 m_base_position += dtime * m_velocity + 0.5 * dtime
438 * dtime * m_acceleration;
439 m_velocity += dtime * m_acceleration;
442 if (m_prop.automatic_face_movement_dir &&
443 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
445 float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
446 + m_prop.automatic_face_movement_dir_offset;
447 float max_rotation_delta =
448 dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
449 float delta = wrapDegrees_0_360(target_yaw - m_yaw);
451 if (delta > max_rotation_delta && 360 - delta > max_rotation_delta) {
452 m_yaw += (delta < 180) ? max_rotation_delta : -max_rotation_delta;
453 m_yaw = wrapDegrees_0_360(m_yaw);
461 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
464 if (!send_recommended)
469 // TODO: force send when acceleration changes enough?
470 float minchange = 0.2*BS;
471 if(m_last_sent_position_timer > 1.0){
473 } else if(m_last_sent_position_timer > 0.2){
476 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
477 move_d += m_last_sent_move_precision;
478 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
479 if (move_d > minchange || vel_d > minchange ||
480 std::fabs(m_yaw - m_last_sent_yaw) > 1.0) {
481 sendPosition(true, false);
485 if (!m_armor_groups_sent) {
486 m_armor_groups_sent = true;
487 std::string str = gob_cmd_update_armor_groups(
489 // create message and add to list
490 ActiveObjectMessage aom(getId(), true, str);
491 m_messages_out.push(aom);
494 if (!m_animation_sent) {
495 m_animation_sent = true;
496 std::string str = gob_cmd_update_animation(
497 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
498 // create message and add to list
499 ActiveObjectMessage aom(getId(), true, str);
500 m_messages_out.push(aom);
503 if (!m_animation_speed_sent) {
504 m_animation_speed_sent = true;
505 std::string str = gob_cmd_update_animation_speed(m_animation_speed);
506 // create message and add to list
507 ActiveObjectMessage aom(getId(), true, str);
508 m_messages_out.push(aom);
511 if (!m_bone_position_sent) {
512 m_bone_position_sent = true;
513 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
514 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
515 std::string str = gob_cmd_update_bone_position((*ii).first,
516 (*ii).second.X, (*ii).second.Y);
517 // create message and add to list
518 ActiveObjectMessage aom(getId(), true, str);
519 m_messages_out.push(aom);
523 if (!m_attachment_sent) {
524 m_attachment_sent = true;
525 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
526 // create message and add to list
527 ActiveObjectMessage aom(getId(), true, str);
528 m_messages_out.push(aom);
532 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
534 std::ostringstream os(std::ios::binary);
537 writeU8(os, 1); // version
538 os << serializeString(""); // name
539 writeU8(os, 0); // is_player
540 writeS16(os, getId()); //id
541 writeV3F1000(os, m_base_position);
542 writeF1000(os, m_yaw);
545 std::ostringstream msg_os(std::ios::binary);
546 msg_os << serializeLongString(getPropertyPacket()); // message 1
547 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
548 msg_os << serializeLongString(gob_cmd_update_animation(
549 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
550 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
551 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
552 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
553 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
555 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
556 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
557 int message_count = 4 + m_bone_position.size();
558 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
559 (ii != m_attachment_child_ids.end()); ++ii) {
560 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
562 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
563 obj->getClientInitializationData(protocol_version)));
567 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
570 writeU8(os, message_count);
571 os.write(msg_os.str().c_str(), msg_os.str().size());
577 void LuaEntitySAO::getStaticData(std::string *result) const
579 verbosestream<<FUNCTION_NAME<<std::endl;
580 std::ostringstream os(std::ios::binary);
584 os<<serializeString(m_init_name);
587 std::string state = m_env->getScriptIface()->
588 luaentity_GetStaticdata(m_id);
589 os<<serializeLongString(state);
591 os<<serializeLongString(m_init_state);
596 writeV3F1000(os, m_velocity);
598 writeF1000(os, m_yaw);
602 int LuaEntitySAO::punch(v3f dir,
603 const ToolCapabilities *toolcap,
604 ServerActiveObject *puncher,
605 float time_from_last_punch)
608 // Delete unknown LuaEntities when punched
609 m_pending_removal = true;
613 ItemStack *punchitem = NULL;
614 ItemStack punchitem_static;
616 punchitem_static = puncher->getWieldedItem();
617 punchitem = &punchitem_static;
620 PunchDamageResult result = getPunchDamage(
624 time_from_last_punch);
626 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
627 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
629 if (!damage_handled) {
630 if (result.did_punch) {
631 setHP(getHP() - result.damage,
632 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
634 if (result.damage > 0) {
635 std::string punchername = puncher ? puncher->getDescription() : "nil";
637 actionstream << getDescription() << " punched by "
638 << punchername << ", damage " << result.damage
639 << " hp, health now " << getHP() << " hp" << std::endl;
642 std::string str = gob_cmd_punched(result.damage, getHP());
643 // create message and add to list
644 ActiveObjectMessage aom(getId(), true, str);
645 m_messages_out.push(aom);
649 if (getHP() == 0 && !isGone()) {
650 m_pending_removal = true;
651 clearParentAttachment();
652 clearChildAttachments();
653 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
659 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
664 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
667 void LuaEntitySAO::setPos(const v3f &pos)
671 m_base_position = pos;
672 m_env->updateActiveObject(this);
673 sendPosition(false, true);
676 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
680 m_base_position = pos;
681 m_env->updateActiveObject(this);
683 sendPosition(true, true);
686 float LuaEntitySAO::getMinimumSavedMovement()
691 std::string LuaEntitySAO::getDescription()
693 std::ostringstream os(std::ios::binary);
694 os<<"LuaEntitySAO at (";
695 os<<(m_base_position.X/BS)<<",";
696 os<<(m_base_position.Y/BS)<<",";
697 os<<(m_base_position.Z/BS);
702 void LuaEntitySAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
709 s16 LuaEntitySAO::getHP() const
714 void LuaEntitySAO::setVelocity(v3f velocity)
716 m_velocity = velocity;
719 v3f LuaEntitySAO::getVelocity()
724 void LuaEntitySAO::setAcceleration(v3f acceleration)
726 m_acceleration = acceleration;
729 v3f LuaEntitySAO::getAcceleration()
731 return m_acceleration;
734 void LuaEntitySAO::setTextureMod(const std::string &mod)
736 std::string str = gob_cmd_set_texture_mod(mod);
737 m_current_texture_modifier = mod;
738 // create message and add to list
739 ActiveObjectMessage aom(getId(), true, str);
740 m_messages_out.push(aom);
743 std::string LuaEntitySAO::getTextureMod() const
745 return m_current_texture_modifier;
748 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
749 bool select_horiz_by_yawpitch)
751 std::string str = gob_cmd_set_sprite(
755 select_horiz_by_yawpitch
757 // create message and add to list
758 ActiveObjectMessage aom(getId(), true, str);
759 m_messages_out.push(aom);
762 std::string LuaEntitySAO::getName()
767 std::string LuaEntitySAO::getPropertyPacket()
769 return gob_cmd_set_properties(m_prop);
772 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
774 // If the object is attached client-side, don't waste bandwidth sending its position to clients
778 m_last_sent_move_precision = m_base_position.getDistanceFrom(
779 m_last_sent_position);
780 m_last_sent_position_timer = 0;
781 m_last_sent_yaw = m_yaw;
782 m_last_sent_position = m_base_position;
783 m_last_sent_velocity = m_velocity;
784 //m_last_sent_acceleration = m_acceleration;
786 float update_interval = m_env->getSendRecommendedInterval();
788 std::string str = gob_cmd_update_position(
797 // create message and add to list
798 ActiveObjectMessage aom(getId(), false, str);
799 m_messages_out.push(aom);
802 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
806 //update collision box
807 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
808 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
810 toset->MinEdge += m_base_position;
811 toset->MaxEdge += m_base_position;
819 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
821 if (!m_prop.is_visible || !m_prop.pointable) {
825 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
826 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
831 bool LuaEntitySAO::collideWithObjects() const
833 return m_prop.collideWithObjects;
840 // No prototype, PlayerSAO does not need to be deserialized
842 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
843 bool is_singleplayer):
844 UnitSAO(env_, v3f(0,0,0)),
847 m_is_singleplayer(is_singleplayer)
849 assert(m_peer_id != 0); // pre-condition
851 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
852 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
853 m_prop.physical = false;
855 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
856 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
857 m_prop.pointable = true;
858 // Start of default appearance, this should be overwritten by Lua
859 m_prop.visual = "upright_sprite";
860 m_prop.visual_size = v2f(1, 2);
861 m_prop.textures.clear();
862 m_prop.textures.emplace_back("player.png");
863 m_prop.textures.emplace_back("player_back.png");
864 m_prop.colors.clear();
865 m_prop.colors.emplace_back(255, 255, 255, 255);
866 m_prop.spritediv = v2s16(1,1);
867 m_prop.eye_height = 1.625f;
868 // End of default appearance
869 m_prop.is_visible = true;
870 m_prop.backface_culling = false;
871 m_prop.makes_footstep_sound = true;
872 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
873 m_hp = m_prop.hp_max;
874 m_breath = m_prop.breath_max;
875 // Disable zoom in survival mode using a value of 0
876 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
879 PlayerSAO::~PlayerSAO()
881 if(m_inventory != &m_player->inventory)
885 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
890 m_inventory = &m_player->inventory;
893 v3f PlayerSAO::getEyeOffset() const
895 return v3f(0, BS * m_prop.eye_height, 0);
898 std::string PlayerSAO::getDescription()
900 return std::string("player ") + m_player->getName();
903 // Called after id has been set and has been inserted in environment
904 void PlayerSAO::addedToEnvironment(u32 dtime_s)
906 ServerActiveObject::addedToEnvironment(dtime_s);
907 ServerActiveObject::setBasePosition(m_base_position);
908 m_player->setPlayerSAO(this);
909 m_player->setPeerId(m_peer_id);
910 m_last_good_position = m_base_position;
913 // Called before removing from environment
914 void PlayerSAO::removingFromEnvironment()
916 ServerActiveObject::removingFromEnvironment();
917 if (m_player->getPlayerSAO() == this) {
918 unlinkPlayerSessionAndSave();
919 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
920 m_env->deleteParticleSpawner(attached_particle_spawner, false);
925 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
927 std::ostringstream os(std::ios::binary);
930 writeU8(os, 1); // version
931 os << serializeString(m_player->getName()); // name
932 writeU8(os, 1); // is_player
933 writeS16(os, getId()); //id
934 writeV3F1000(os, m_base_position);
935 writeF1000(os, m_yaw);
936 writeS16(os, getHP());
938 std::ostringstream msg_os(std::ios::binary);
939 msg_os << serializeLongString(getPropertyPacket()); // message 1
940 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
941 msg_os << serializeLongString(gob_cmd_update_animation(
942 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
943 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
944 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
945 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
946 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
948 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
949 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
950 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
951 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
952 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
953 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
954 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
955 int message_count = 6 + m_bone_position.size();
956 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
957 ii != m_attachment_child_ids.end(); ++ii) {
958 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
960 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
961 obj->getClientInitializationData(protocol_version)));
965 writeU8(os, message_count);
966 os.write(msg_os.str().c_str(), msg_os.str().size());
972 void PlayerSAO::getStaticData(std::string * result) const
974 FATAL_ERROR("Deprecated function");
977 void PlayerSAO::step(float dtime, bool send_recommended)
979 if (m_drowning_interval.step(dtime, 2.0f)) {
980 // Get nose/mouth position, approximate with eye position
981 v3s16 p = floatToInt(getEyePosition(), BS);
982 MapNode n = m_env->getMap().getNodeNoEx(p);
983 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
984 // If node generates drown
985 if (c.drowning > 0 && m_hp > 0) {
987 setBreath(m_breath - 1);
989 // No more breath, damage player
991 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
992 setHP(m_hp - c.drowning, reason);
993 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
998 if (m_breathing_interval.step(dtime, 0.5f)) {
999 // Get nose/mouth position, approximate with eye position
1000 v3s16 p = floatToInt(getEyePosition(), BS);
1001 MapNode n = m_env->getMap().getNodeNoEx(p);
1002 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1003 // If player is alive & no drowning, breathe
1004 if (m_hp > 0 && m_breath < m_prop.breath_max && c.drowning == 0)
1005 setBreath(m_breath + 1);
1008 if (m_node_hurt_interval.step(dtime, 1.0f)) {
1009 u32 damage_per_second = 0;
1010 // Lowest and highest damage points are 0.1 within collisionbox
1011 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1013 // Sequence of damage points, starting 0.1 above feet and progressing
1014 // upwards in 1 node intervals, stopping below top damage point.
1015 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1016 v3s16 p = floatToInt(m_base_position +
1017 v3f(0.0f, dam_height * BS, 0.0f), BS);
1018 MapNode n = m_env->getMap().getNodeNoEx(p);
1019 damage_per_second = std::max(damage_per_second,
1020 m_env->getGameDef()->ndef()->get(n).damage_per_second);
1024 v3s16 ptop = floatToInt(m_base_position +
1025 v3f(0.0f, dam_top * BS, 0.0f), BS);
1026 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1027 damage_per_second = std::max(damage_per_second,
1028 m_env->getGameDef()->ndef()->get(ntop).damage_per_second);
1030 if (damage_per_second != 0 && m_hp > 0) {
1031 s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second);
1032 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE);
1033 setHP(newhp, reason);
1034 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1038 if (!m_properties_sent) {
1039 m_properties_sent = true;
1040 std::string str = getPropertyPacket();
1041 // create message and add to list
1042 ActiveObjectMessage aom(getId(), true, str);
1043 m_messages_out.push(aom);
1046 // If attached, check that our parent is still there. If it isn't, detach.
1047 if (m_attachment_parent_id && !isAttached()) {
1048 m_attachment_parent_id = 0;
1049 m_attachment_bone = "";
1050 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1051 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1052 setBasePosition(m_last_good_position);
1053 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1056 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1058 // Set lag pool maximums based on estimated lag
1059 const float LAG_POOL_MIN = 5.0f;
1060 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1061 if(lag_pool_max < LAG_POOL_MIN)
1062 lag_pool_max = LAG_POOL_MIN;
1063 m_dig_pool.setMax(lag_pool_max);
1064 m_move_pool.setMax(lag_pool_max);
1066 // Increment cheat prevention timers
1067 m_dig_pool.add(dtime);
1068 m_move_pool.add(dtime);
1069 m_time_from_last_teleport += dtime;
1070 m_time_from_last_punch += dtime;
1071 m_nocheat_dig_time += dtime;
1073 // Each frame, parent position is copied if the object is attached,
1074 // otherwise it's calculated normally.
1075 // If the object gets detached this comes into effect automatically from
1076 // the last known origin.
1078 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1079 m_last_good_position = pos;
1080 setBasePosition(pos);
1083 if (!send_recommended)
1086 // If the object is attached client-side, don't waste bandwidth sending its
1087 // position to clients.
1088 if (m_position_not_sent && !isAttached()) {
1089 m_position_not_sent = false;
1090 float update_interval = m_env->getSendRecommendedInterval();
1092 if (isAttached()) // Just in case we ever do send attachment position too
1093 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1095 pos = m_base_position;
1097 std::string str = gob_cmd_update_position(
1099 v3f(0.0f, 0.0f, 0.0f),
1100 v3f(0.0f, 0.0f, 0.0f),
1106 // create message and add to list
1107 ActiveObjectMessage aom(getId(), false, str);
1108 m_messages_out.push(aom);
1111 if (!m_armor_groups_sent) {
1112 m_armor_groups_sent = true;
1113 std::string str = gob_cmd_update_armor_groups(
1115 // create message and add to list
1116 ActiveObjectMessage aom(getId(), true, str);
1117 m_messages_out.push(aom);
1120 if (!m_physics_override_sent) {
1121 m_physics_override_sent = true;
1122 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1123 m_physics_override_jump, m_physics_override_gravity,
1124 m_physics_override_sneak, m_physics_override_sneak_glitch,
1125 m_physics_override_new_move);
1126 // create message and add to list
1127 ActiveObjectMessage aom(getId(), true, str);
1128 m_messages_out.push(aom);
1131 if (!m_animation_sent) {
1132 m_animation_sent = true;
1133 std::string str = gob_cmd_update_animation(
1134 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1135 // create message and add to list
1136 ActiveObjectMessage aom(getId(), true, str);
1137 m_messages_out.push(aom);
1140 if (!m_bone_position_sent) {
1141 m_bone_position_sent = true;
1142 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1143 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1144 std::string str = gob_cmd_update_bone_position((*ii).first,
1145 (*ii).second.X, (*ii).second.Y);
1146 // create message and add to list
1147 ActiveObjectMessage aom(getId(), true, str);
1148 m_messages_out.push(aom);
1152 if (!m_attachment_sent) {
1153 m_attachment_sent = true;
1154 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1155 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1156 // create message and add to list
1157 ActiveObjectMessage aom(getId(), true, str);
1158 m_messages_out.push(aom);
1162 void PlayerSAO::setBasePosition(const v3f &position)
1164 if (m_player && position != m_base_position)
1165 m_player->setDirty(true);
1167 // This needs to be ran for attachments too
1168 ServerActiveObject::setBasePosition(position);
1169 m_env->updateActiveObject(this);
1170 m_position_not_sent = true;
1173 void PlayerSAO::setPos(const v3f &pos)
1178 setBasePosition(pos);
1179 // Movement caused by this command is always valid
1180 m_last_good_position = pos;
1181 m_move_pool.empty();
1182 m_time_from_last_teleport = 0.0;
1183 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1186 void PlayerSAO::moveTo(v3f pos, bool continuous)
1191 setBasePosition(pos);
1192 // Movement caused by this command is always valid
1193 m_last_good_position = pos;
1194 m_move_pool.empty();
1195 m_time_from_last_teleport = 0.0;
1196 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1199 void PlayerSAO::setYaw(const float yaw)
1201 if (m_player && yaw != m_yaw)
1202 m_player->setDirty(true);
1204 UnitSAO::setYaw(yaw);
1207 void PlayerSAO::setFov(const float fov)
1209 if (m_player && fov != m_fov)
1210 m_player->setDirty(true);
1215 void PlayerSAO::setWantedRange(const s16 range)
1217 if (m_player && range != m_wanted_range)
1218 m_player->setDirty(true);
1220 m_wanted_range = range;
1223 void PlayerSAO::setYawAndSend(const float yaw)
1226 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1229 void PlayerSAO::setPitch(const float pitch)
1231 if (m_player && pitch != m_pitch)
1232 m_player->setDirty(true);
1237 void PlayerSAO::setPitchAndSend(const float pitch)
1240 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1243 int PlayerSAO::punch(v3f dir,
1244 const ToolCapabilities *toolcap,
1245 ServerActiveObject *puncher,
1246 float time_from_last_punch)
1251 // No effect if PvP disabled
1252 if (!g_settings->getBool("enable_pvp")) {
1253 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1254 std::string str = gob_cmd_punched(0, getHP());
1255 // create message and add to list
1256 ActiveObjectMessage aom(getId(), true, str);
1257 m_messages_out.push(aom);
1262 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1263 time_from_last_punch);
1265 std::string punchername = "nil";
1268 punchername = puncher->getDescription();
1270 PlayerSAO *playersao = m_player->getPlayerSAO();
1272 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1273 puncher, time_from_last_punch, toolcap, dir,
1276 if (!damage_handled) {
1277 setHP(getHP() - hitparams.hp,
1278 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1279 } else { // override client prediction
1280 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1281 std::string str = gob_cmd_punched(0, getHP());
1282 // create message and add to list
1283 ActiveObjectMessage aom(getId(), true, str);
1284 m_messages_out.push(aom);
1289 actionstream << "Player " << m_player->getName() << " punched by "
1291 if (!damage_handled) {
1292 actionstream << ", damage " << hitparams.hp << " HP";
1294 actionstream << ", damage handled by lua";
1296 actionstream << std::endl;
1298 return hitparams.wear;
1301 s16 PlayerSAO::readDamage()
1303 s16 damage = m_damage;
1308 void PlayerSAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
1312 s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1315 hp = oldhp + hp_change;
1319 else if (hp > m_prop.hp_max)
1322 if (hp < oldhp && !g_settings->getBool("enable_damage")) {
1329 m_damage += (oldhp - hp);
1331 // Update properties on death
1332 if ((hp == 0) != (oldhp == 0))
1333 m_properties_sent = false;
1336 void PlayerSAO::setBreath(const u16 breath, bool send)
1338 if (m_player && breath != m_breath)
1339 m_player->setDirty(true);
1341 m_breath = MYMIN(breath, m_prop.breath_max);
1344 m_env->getGameDef()->SendPlayerBreath(this);
1347 Inventory* PlayerSAO::getInventory()
1351 const Inventory* PlayerSAO::getInventory() const
1356 InventoryLocation PlayerSAO::getInventoryLocation() const
1358 InventoryLocation loc;
1359 loc.setPlayer(m_player->getName());
1363 std::string PlayerSAO::getWieldList() const
1368 ItemStack PlayerSAO::getWieldedItem() const
1370 const Inventory *inv = getInventory();
1372 const InventoryList *mlist = inv->getList(getWieldList());
1373 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1374 ret = mlist->getItem(getWieldIndex());
1378 ItemStack PlayerSAO::getWieldedItemOrHand() const
1380 const Inventory *inv = getInventory();
1382 const InventoryList *mlist = inv->getList(getWieldList());
1383 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1384 ret = mlist->getItem(getWieldIndex());
1385 if (ret.name.empty()) {
1386 const InventoryList *hlist = inv->getList("hand");
1388 ret = hlist->getItem(0);
1393 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1395 Inventory *inv = getInventory();
1397 InventoryList *mlist = inv->getList(getWieldList());
1399 mlist->changeItem(getWieldIndex(), item);
1406 int PlayerSAO::getWieldIndex() const
1408 return m_wield_index;
1411 void PlayerSAO::setWieldIndex(int i)
1413 if(i != m_wield_index) {
1418 void PlayerSAO::disconnected()
1421 m_pending_removal = true;
1424 void PlayerSAO::unlinkPlayerSessionAndSave()
1426 assert(m_player->getPlayerSAO() == this);
1427 m_player->setPeerId(PEER_ID_INEXISTENT);
1428 m_env->savePlayer(m_player);
1429 m_player->setPlayerSAO(NULL);
1430 m_env->removePlayer(m_player);
1433 std::string PlayerSAO::getPropertyPacket()
1435 m_prop.is_visible = (true);
1436 return gob_cmd_set_properties(m_prop);
1439 bool PlayerSAO::checkMovementCheat()
1441 if (isAttached() || m_is_singleplayer ||
1442 g_settings->getBool("disable_anticheat")) {
1443 m_last_good_position = m_base_position;
1447 bool cheated = false;
1449 Check player movements
1451 NOTE: Actually the server should handle player physics like the
1452 client does and compare player's position to what is calculated
1453 on our side. This is required when eg. players fly due to an
1454 explosion. Altough a node-based alternative might be possible
1455 too, and much more lightweight.
1458 float player_max_walk = 0; // horizontal movement
1459 float player_max_jump = 0; // vertical upwards movement
1461 if (m_privs.count("fast") != 0)
1462 player_max_walk = m_player->movement_speed_fast; // Fast speed
1464 player_max_walk = m_player->movement_speed_walk; // Normal speed
1465 player_max_walk *= m_physics_override_speed;
1466 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1467 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1468 // until this can be verified correctly, tolerate higher jumping speeds
1469 player_max_jump *= 2.0;
1471 // Don't divide by zero!
1472 if (player_max_walk < 0.0001f)
1473 player_max_walk = 0.0001f;
1474 if (player_max_jump < 0.0001f)
1475 player_max_jump = 0.0001f;
1477 v3f diff = (m_base_position - m_last_good_position);
1478 float d_vert = diff.Y;
1480 float d_horiz = diff.getLength();
1481 float required_time = d_horiz / player_max_walk;
1483 // FIXME: Checking downwards movement is not easily possible currently,
1484 // the server could calculate speed differences to examine the gravity
1486 // In certain cases (water, ladders) walking speed is applied vertically
1487 float s = MYMAX(player_max_jump, player_max_walk);
1488 required_time = MYMAX(required_time, d_vert / s);
1491 if (m_move_pool.grab(required_time)) {
1492 m_last_good_position = m_base_position;
1494 const float LAG_POOL_MIN = 5.0;
1495 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1496 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1497 if (m_time_from_last_teleport > lag_pool_max) {
1498 actionstream << "Player " << m_player->getName()
1499 << " moved too fast; resetting position"
1503 setBasePosition(m_last_good_position);
1508 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1510 //update collision box
1511 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1512 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1514 toset->MinEdge += m_base_position;
1515 toset->MaxEdge += m_base_position;
1519 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1521 if (!m_prop.is_visible || !m_prop.pointable) {
1525 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1526 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1531 float PlayerSAO::getZoomFOV() const
1533 return m_prop.zoom_fov;