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_properties_sent = false;
297 // Prototype (registers item for deserialization)
298 LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", "");
300 LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
301 const std::string &name, const std::string &state):
306 // Only register type if no environment supplied
308 ServerActiveObject::registerType(getType(), create);
313 LuaEntitySAO::~LuaEntitySAO()
316 m_env->getScriptIface()->luaentity_Remove(m_id);
319 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
320 m_env->deleteParticleSpawner(attached_particle_spawner, false);
324 void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
326 ServerActiveObject::addedToEnvironment(dtime_s);
328 // Create entity from name
329 m_registered = m_env->getScriptIface()->
330 luaentity_Add(m_id, m_init_name.c_str());
334 m_env->getScriptIface()->
335 luaentity_GetProperties(m_id, &m_prop);
336 // Initialize HP from properties
337 m_hp = m_prop.hp_max;
338 // Activate entity, supplying serialized state
339 m_env->getScriptIface()->
340 luaentity_Activate(m_id, m_init_state, dtime_s);
342 m_prop.infotext = m_init_name;
346 ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
347 const std::string &data)
355 std::istringstream is(data, std::ios::binary);
357 u8 version = readU8(is);
358 // check if version is supported
360 name = deSerializeString(is);
361 state = deSerializeLongString(is);
363 else if(version == 1){
364 name = deSerializeString(is);
365 state = deSerializeLongString(is);
367 velocity = readV3F1000(is);
372 infostream<<"LuaEntitySAO::create(name=\""<<name<<"\" state=\""
373 <<state<<"\")"<<std::endl;
374 LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
376 sao->m_velocity = velocity;
381 void LuaEntitySAO::step(float dtime, bool send_recommended)
383 if(!m_properties_sent)
385 m_properties_sent = true;
386 std::string str = getPropertyPacket();
387 // create message and add to list
388 ActiveObjectMessage aom(getId(), true, str);
389 m_messages_out.push(aom);
392 // If attached, check that our parent is still there. If it isn't, detach.
393 if(m_attachment_parent_id && !isAttached())
395 m_attachment_parent_id = 0;
396 m_attachment_bone = "";
397 m_attachment_position = v3f(0,0,0);
398 m_attachment_rotation = v3f(0,0,0);
399 sendPosition(false, true);
402 m_last_sent_position_timer += dtime;
404 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
405 // If the object gets detached this comes into effect automatically from the last known origin
408 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
409 m_base_position = pos;
410 m_velocity = v3f(0,0,0);
411 m_acceleration = v3f(0,0,0);
416 aabb3f box = m_prop.collisionbox;
419 collisionMoveResult moveresult;
420 f32 pos_max_d = BS*0.25; // Distance per iteration
421 v3f p_pos = m_base_position;
422 v3f p_velocity = m_velocity;
423 v3f p_acceleration = m_acceleration;
424 moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
425 pos_max_d, box, m_prop.stepheight, dtime,
426 &p_pos, &p_velocity, p_acceleration,
427 this, m_prop.collideWithObjects);
430 m_base_position = p_pos;
431 m_velocity = p_velocity;
432 m_acceleration = p_acceleration;
434 m_base_position += dtime * m_velocity + 0.5 * dtime
435 * dtime * m_acceleration;
436 m_velocity += dtime * m_acceleration;
439 if (m_prop.automatic_face_movement_dir &&
440 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
442 float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
443 + m_prop.automatic_face_movement_dir_offset;
444 float max_rotation_delta =
445 dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
446 float delta = wrapDegrees_0_360(target_yaw - m_yaw);
448 if (delta > max_rotation_delta && 360 - delta > max_rotation_delta) {
449 m_yaw += (delta < 180) ? max_rotation_delta : -max_rotation_delta;
450 m_yaw = wrapDegrees_0_360(m_yaw);
458 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
461 if (!send_recommended)
466 // TODO: force send when acceleration changes enough?
467 float minchange = 0.2*BS;
468 if(m_last_sent_position_timer > 1.0){
470 } else if(m_last_sent_position_timer > 0.2){
473 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
474 move_d += m_last_sent_move_precision;
475 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
476 if (move_d > minchange || vel_d > minchange ||
477 std::fabs(m_yaw - m_last_sent_yaw) > 1.0) {
478 sendPosition(true, false);
482 if (!m_armor_groups_sent) {
483 m_armor_groups_sent = true;
484 std::string str = gob_cmd_update_armor_groups(
486 // create message and add to list
487 ActiveObjectMessage aom(getId(), true, str);
488 m_messages_out.push(aom);
491 if (!m_animation_sent) {
492 m_animation_sent = true;
493 std::string str = gob_cmd_update_animation(
494 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
495 // create message and add to list
496 ActiveObjectMessage aom(getId(), true, str);
497 m_messages_out.push(aom);
500 if (!m_animation_speed_sent) {
501 m_animation_speed_sent = true;
502 std::string str = gob_cmd_update_animation_speed(m_animation_speed);
503 // create message and add to list
504 ActiveObjectMessage aom(getId(), true, str);
505 m_messages_out.push(aom);
508 if (!m_bone_position_sent) {
509 m_bone_position_sent = true;
510 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
511 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
512 std::string str = gob_cmd_update_bone_position((*ii).first,
513 (*ii).second.X, (*ii).second.Y);
514 // create message and add to list
515 ActiveObjectMessage aom(getId(), true, str);
516 m_messages_out.push(aom);
520 if (!m_attachment_sent) {
521 m_attachment_sent = true;
522 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
523 // create message and add to list
524 ActiveObjectMessage aom(getId(), true, str);
525 m_messages_out.push(aom);
529 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
531 std::ostringstream os(std::ios::binary);
534 writeU8(os, 1); // version
535 os << serializeString(""); // name
536 writeU8(os, 0); // is_player
537 writeS16(os, getId()); //id
538 writeV3F1000(os, m_base_position);
539 writeF1000(os, m_yaw);
542 std::ostringstream msg_os(std::ios::binary);
543 msg_os << serializeLongString(getPropertyPacket()); // message 1
544 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
545 msg_os << serializeLongString(gob_cmd_update_animation(
546 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
547 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
548 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
549 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
550 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
552 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
553 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
554 int message_count = 4 + m_bone_position.size();
555 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
556 (ii != m_attachment_child_ids.end()); ++ii) {
557 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
559 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
560 obj->getClientInitializationData(protocol_version)));
564 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
567 writeU8(os, message_count);
568 os.write(msg_os.str().c_str(), msg_os.str().size());
574 void LuaEntitySAO::getStaticData(std::string *result) const
576 verbosestream<<FUNCTION_NAME<<std::endl;
577 std::ostringstream os(std::ios::binary);
581 os<<serializeString(m_init_name);
584 std::string state = m_env->getScriptIface()->
585 luaentity_GetStaticdata(m_id);
586 os<<serializeLongString(state);
588 os<<serializeLongString(m_init_state);
593 writeV3F1000(os, m_velocity);
595 writeF1000(os, m_yaw);
599 int LuaEntitySAO::punch(v3f dir,
600 const ToolCapabilities *toolcap,
601 ServerActiveObject *puncher,
602 float time_from_last_punch)
605 // Delete unknown LuaEntities when punched
606 m_pending_removal = true;
610 ItemStack *punchitem = NULL;
611 ItemStack punchitem_static;
613 punchitem_static = puncher->getWieldedItem();
614 punchitem = &punchitem_static;
617 PunchDamageResult result = getPunchDamage(
621 time_from_last_punch);
623 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
624 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
626 if (!damage_handled) {
627 if (result.did_punch) {
628 setHP(getHP() - result.damage,
629 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
631 if (result.damage > 0) {
632 std::string punchername = puncher ? puncher->getDescription() : "nil";
634 actionstream << getDescription() << " punched by "
635 << punchername << ", damage " << result.damage
636 << " hp, health now " << getHP() << " hp" << std::endl;
639 std::string str = gob_cmd_punched(result.damage, getHP());
640 // create message and add to list
641 ActiveObjectMessage aom(getId(), true, str);
642 m_messages_out.push(aom);
646 if (getHP() == 0 && !isGone()) {
647 m_pending_removal = true;
648 clearParentAttachment();
649 clearChildAttachments();
650 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
656 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
661 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
664 void LuaEntitySAO::setPos(const v3f &pos)
668 m_base_position = pos;
669 sendPosition(false, true);
672 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
676 m_base_position = pos;
678 sendPosition(true, true);
681 float LuaEntitySAO::getMinimumSavedMovement()
686 std::string LuaEntitySAO::getDescription()
688 std::ostringstream os(std::ios::binary);
689 os<<"LuaEntitySAO at (";
690 os<<(m_base_position.X/BS)<<",";
691 os<<(m_base_position.Y/BS)<<",";
692 os<<(m_base_position.Z/BS);
697 void LuaEntitySAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
704 s16 LuaEntitySAO::getHP() const
709 void LuaEntitySAO::setVelocity(v3f velocity)
711 m_velocity = velocity;
714 v3f LuaEntitySAO::getVelocity()
719 void LuaEntitySAO::setAcceleration(v3f acceleration)
721 m_acceleration = acceleration;
724 v3f LuaEntitySAO::getAcceleration()
726 return m_acceleration;
729 void LuaEntitySAO::setTextureMod(const std::string &mod)
731 std::string str = gob_cmd_set_texture_mod(mod);
732 m_current_texture_modifier = mod;
733 // create message and add to list
734 ActiveObjectMessage aom(getId(), true, str);
735 m_messages_out.push(aom);
738 std::string LuaEntitySAO::getTextureMod() const
740 return m_current_texture_modifier;
743 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
744 bool select_horiz_by_yawpitch)
746 std::string str = gob_cmd_set_sprite(
750 select_horiz_by_yawpitch
752 // create message and add to list
753 ActiveObjectMessage aom(getId(), true, str);
754 m_messages_out.push(aom);
757 std::string LuaEntitySAO::getName()
762 std::string LuaEntitySAO::getPropertyPacket()
764 return gob_cmd_set_properties(m_prop);
767 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
769 // If the object is attached client-side, don't waste bandwidth sending its position to clients
773 m_last_sent_move_precision = m_base_position.getDistanceFrom(
774 m_last_sent_position);
775 m_last_sent_position_timer = 0;
776 m_last_sent_yaw = m_yaw;
777 m_last_sent_position = m_base_position;
778 m_last_sent_velocity = m_velocity;
779 //m_last_sent_acceleration = m_acceleration;
781 float update_interval = m_env->getSendRecommendedInterval();
783 std::string str = gob_cmd_update_position(
792 // create message and add to list
793 ActiveObjectMessage aom(getId(), false, str);
794 m_messages_out.push(aom);
797 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
801 //update collision box
802 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
803 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
805 toset->MinEdge += m_base_position;
806 toset->MaxEdge += m_base_position;
814 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
816 if (!m_prop.is_visible || !m_prop.pointable) {
820 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
821 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
826 bool LuaEntitySAO::collideWithObjects() const
828 return m_prop.collideWithObjects;
835 // No prototype, PlayerSAO does not need to be deserialized
837 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
838 bool is_singleplayer):
839 UnitSAO(env_, v3f(0,0,0)),
842 m_is_singleplayer(is_singleplayer)
844 assert(m_peer_id != 0); // pre-condition
846 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
847 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
848 m_prop.physical = false;
850 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
851 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
852 m_prop.pointable = true;
853 // Start of default appearance, this should be overwritten by Lua
854 m_prop.visual = "upright_sprite";
855 m_prop.visual_size = v2f(1, 2);
856 m_prop.textures.clear();
857 m_prop.textures.emplace_back("player.png");
858 m_prop.textures.emplace_back("player_back.png");
859 m_prop.colors.clear();
860 m_prop.colors.emplace_back(255, 255, 255, 255);
861 m_prop.spritediv = v2s16(1,1);
862 m_prop.eye_height = 1.625f;
863 // End of default appearance
864 m_prop.is_visible = true;
865 m_prop.backface_culling = false;
866 m_prop.makes_footstep_sound = true;
867 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
868 m_hp = m_prop.hp_max;
869 m_breath = m_prop.breath_max;
870 // Disable zoom in survival mode using a value of 0
871 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
874 PlayerSAO::~PlayerSAO()
876 if(m_inventory != &m_player->inventory)
880 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
885 m_inventory = &m_player->inventory;
888 v3f PlayerSAO::getEyeOffset() const
890 return v3f(0, BS * m_prop.eye_height, 0);
893 std::string PlayerSAO::getDescription()
895 return std::string("player ") + m_player->getName();
898 // Called after id has been set and has been inserted in environment
899 void PlayerSAO::addedToEnvironment(u32 dtime_s)
901 ServerActiveObject::addedToEnvironment(dtime_s);
902 ServerActiveObject::setBasePosition(m_base_position);
903 m_player->setPlayerSAO(this);
904 m_player->setPeerId(m_peer_id);
905 m_last_good_position = m_base_position;
908 // Called before removing from environment
909 void PlayerSAO::removingFromEnvironment()
911 ServerActiveObject::removingFromEnvironment();
912 if (m_player->getPlayerSAO() == this) {
913 unlinkPlayerSessionAndSave();
914 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
915 m_env->deleteParticleSpawner(attached_particle_spawner, false);
920 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
922 std::ostringstream os(std::ios::binary);
925 writeU8(os, 1); // version
926 os << serializeString(m_player->getName()); // name
927 writeU8(os, 1); // is_player
928 writeS16(os, getId()); //id
929 writeV3F1000(os, m_base_position);
930 writeF1000(os, m_yaw);
931 writeS16(os, getHP());
933 std::ostringstream msg_os(std::ios::binary);
934 msg_os << serializeLongString(getPropertyPacket()); // message 1
935 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
936 msg_os << serializeLongString(gob_cmd_update_animation(
937 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
938 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
939 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
940 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
941 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
943 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
944 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
945 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
946 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
947 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
948 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
949 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
950 int message_count = 6 + m_bone_position.size();
951 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
952 ii != m_attachment_child_ids.end(); ++ii) {
953 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
955 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
956 obj->getClientInitializationData(protocol_version)));
960 writeU8(os, message_count);
961 os.write(msg_os.str().c_str(), msg_os.str().size());
967 void PlayerSAO::getStaticData(std::string * result) const
969 FATAL_ERROR("Deprecated function");
972 void PlayerSAO::step(float dtime, bool send_recommended)
974 if (m_drowning_interval.step(dtime, 2.0f)) {
975 // Get nose/mouth position, approximate with eye position
976 v3s16 p = floatToInt(getEyePosition(), BS);
977 MapNode n = m_env->getMap().getNodeNoEx(p);
978 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
979 // If node generates drown
980 if (c.drowning > 0 && m_hp > 0) {
982 setBreath(m_breath - 1);
984 // No more breath, damage player
986 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
987 setHP(m_hp - c.drowning, reason);
988 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
993 if (m_breathing_interval.step(dtime, 0.5f)) {
994 // Get nose/mouth position, approximate with eye position
995 v3s16 p = floatToInt(getEyePosition(), BS);
996 MapNode n = m_env->getMap().getNodeNoEx(p);
997 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
998 // If player is alive & no drowning, breathe
999 if (m_hp > 0 && m_breath < m_prop.breath_max && c.drowning == 0)
1000 setBreath(m_breath + 1);
1003 if (m_node_hurt_interval.step(dtime, 1.0f)) {
1004 u32 damage_per_second = 0;
1005 // Lowest and highest damage points are 0.1 within collisionbox
1006 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1008 // Sequence of damage points, starting 0.1 above feet and progressing
1009 // upwards in 1 node intervals, stopping below top damage point.
1010 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1011 v3s16 p = floatToInt(m_base_position +
1012 v3f(0.0f, dam_height * BS, 0.0f), BS);
1013 MapNode n = m_env->getMap().getNodeNoEx(p);
1014 damage_per_second = std::max(damage_per_second,
1015 m_env->getGameDef()->ndef()->get(n).damage_per_second);
1019 v3s16 ptop = floatToInt(m_base_position +
1020 v3f(0.0f, dam_top * BS, 0.0f), BS);
1021 MapNode ntop = m_env->getMap().getNodeNoEx(ptop);
1022 damage_per_second = std::max(damage_per_second,
1023 m_env->getGameDef()->ndef()->get(ntop).damage_per_second);
1025 if (damage_per_second != 0 && m_hp > 0) {
1026 s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second);
1027 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE);
1028 setHP(newhp, reason);
1029 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1033 if (!m_properties_sent) {
1034 m_properties_sent = true;
1035 std::string str = getPropertyPacket();
1036 // create message and add to list
1037 ActiveObjectMessage aom(getId(), true, str);
1038 m_messages_out.push(aom);
1041 // If attached, check that our parent is still there. If it isn't, detach.
1042 if (m_attachment_parent_id && !isAttached()) {
1043 m_attachment_parent_id = 0;
1044 m_attachment_bone = "";
1045 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1046 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1047 setBasePosition(m_last_good_position);
1048 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1051 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1053 // Set lag pool maximums based on estimated lag
1054 const float LAG_POOL_MIN = 5.0f;
1055 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1056 if(lag_pool_max < LAG_POOL_MIN)
1057 lag_pool_max = LAG_POOL_MIN;
1058 m_dig_pool.setMax(lag_pool_max);
1059 m_move_pool.setMax(lag_pool_max);
1061 // Increment cheat prevention timers
1062 m_dig_pool.add(dtime);
1063 m_move_pool.add(dtime);
1064 m_time_from_last_teleport += dtime;
1065 m_time_from_last_punch += dtime;
1066 m_nocheat_dig_time += dtime;
1068 // Each frame, parent position is copied if the object is attached,
1069 // otherwise it's calculated normally.
1070 // If the object gets detached this comes into effect automatically from
1071 // the last known origin.
1073 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1074 m_last_good_position = pos;
1075 setBasePosition(pos);
1078 if (!send_recommended)
1081 // If the object is attached client-side, don't waste bandwidth sending its
1082 // position to clients.
1083 if (m_position_not_sent && !isAttached()) {
1084 m_position_not_sent = false;
1085 float update_interval = m_env->getSendRecommendedInterval();
1087 if (isAttached()) // Just in case we ever do send attachment position too
1088 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1090 pos = m_base_position;
1092 std::string str = gob_cmd_update_position(
1094 v3f(0.0f, 0.0f, 0.0f),
1095 v3f(0.0f, 0.0f, 0.0f),
1101 // create message and add to list
1102 ActiveObjectMessage aom(getId(), false, str);
1103 m_messages_out.push(aom);
1106 if (!m_armor_groups_sent) {
1107 m_armor_groups_sent = true;
1108 std::string str = gob_cmd_update_armor_groups(
1110 // create message and add to list
1111 ActiveObjectMessage aom(getId(), true, str);
1112 m_messages_out.push(aom);
1115 if (!m_physics_override_sent) {
1116 m_physics_override_sent = true;
1117 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1118 m_physics_override_jump, m_physics_override_gravity,
1119 m_physics_override_sneak, m_physics_override_sneak_glitch,
1120 m_physics_override_new_move);
1121 // create message and add to list
1122 ActiveObjectMessage aom(getId(), true, str);
1123 m_messages_out.push(aom);
1126 if (!m_animation_sent) {
1127 m_animation_sent = true;
1128 std::string str = gob_cmd_update_animation(
1129 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1130 // create message and add to list
1131 ActiveObjectMessage aom(getId(), true, str);
1132 m_messages_out.push(aom);
1135 if (!m_bone_position_sent) {
1136 m_bone_position_sent = true;
1137 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1138 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1139 std::string str = gob_cmd_update_bone_position((*ii).first,
1140 (*ii).second.X, (*ii).second.Y);
1141 // create message and add to list
1142 ActiveObjectMessage aom(getId(), true, str);
1143 m_messages_out.push(aom);
1147 if (!m_attachment_sent) {
1148 m_attachment_sent = true;
1149 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1150 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1151 // create message and add to list
1152 ActiveObjectMessage aom(getId(), true, str);
1153 m_messages_out.push(aom);
1157 void PlayerSAO::setBasePosition(const v3f &position)
1159 if (m_player && position != m_base_position)
1160 m_player->setDirty(true);
1162 // This needs to be ran for attachments too
1163 ServerActiveObject::setBasePosition(position);
1165 // Updating is not wanted/required for player migration
1167 m_position_not_sent = true;
1171 void PlayerSAO::setPos(const v3f &pos)
1176 setBasePosition(pos);
1177 // Movement caused by this command is always valid
1178 m_last_good_position = pos;
1179 m_move_pool.empty();
1180 m_time_from_last_teleport = 0.0;
1181 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1184 void PlayerSAO::moveTo(v3f pos, bool continuous)
1189 setBasePosition(pos);
1190 // Movement caused by this command is always valid
1191 m_last_good_position = pos;
1192 m_move_pool.empty();
1193 m_time_from_last_teleport = 0.0;
1194 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1197 void PlayerSAO::setYaw(const float yaw)
1199 if (m_player && yaw != m_yaw)
1200 m_player->setDirty(true);
1202 UnitSAO::setYaw(yaw);
1205 void PlayerSAO::setFov(const float fov)
1207 if (m_player && fov != m_fov)
1208 m_player->setDirty(true);
1213 void PlayerSAO::setWantedRange(const s16 range)
1215 if (m_player && range != m_wanted_range)
1216 m_player->setDirty(true);
1218 m_wanted_range = range;
1221 void PlayerSAO::setYawAndSend(const float yaw)
1224 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1227 void PlayerSAO::setPitch(const float pitch)
1229 if (m_player && pitch != m_pitch)
1230 m_player->setDirty(true);
1235 void PlayerSAO::setPitchAndSend(const float pitch)
1238 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1241 int PlayerSAO::punch(v3f dir,
1242 const ToolCapabilities *toolcap,
1243 ServerActiveObject *puncher,
1244 float time_from_last_punch)
1249 // No effect if PvP disabled
1250 if (!g_settings->getBool("enable_pvp")) {
1251 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1252 std::string str = gob_cmd_punched(0, getHP());
1253 // create message and add to list
1254 ActiveObjectMessage aom(getId(), true, str);
1255 m_messages_out.push(aom);
1260 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1261 time_from_last_punch);
1263 std::string punchername = "nil";
1266 punchername = puncher->getDescription();
1268 PlayerSAO *playersao = m_player->getPlayerSAO();
1270 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1271 puncher, time_from_last_punch, toolcap, dir,
1274 if (!damage_handled) {
1275 setHP(getHP() - hitparams.hp,
1276 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1277 } else { // override client prediction
1278 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1279 std::string str = gob_cmd_punched(0, getHP());
1280 // create message and add to list
1281 ActiveObjectMessage aom(getId(), true, str);
1282 m_messages_out.push(aom);
1287 actionstream << "Player " << m_player->getName() << " punched by "
1289 if (!damage_handled) {
1290 actionstream << ", damage " << hitparams.hp << " HP";
1292 actionstream << ", damage handled by lua";
1294 actionstream << std::endl;
1296 return hitparams.wear;
1299 s16 PlayerSAO::readDamage()
1301 s16 damage = m_damage;
1306 void PlayerSAO::setHP(s16 hp, const PlayerHPChangeReason &reason)
1310 s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1313 hp = oldhp + hp_change;
1317 else if (hp > m_prop.hp_max)
1320 if (hp < oldhp && !g_settings->getBool("enable_damage")) {
1327 m_damage += (oldhp - hp);
1329 // Update properties on death
1330 if ((hp == 0) != (oldhp == 0))
1331 m_properties_sent = false;
1334 void PlayerSAO::setBreath(const u16 breath, bool send)
1336 if (m_player && breath != m_breath)
1337 m_player->setDirty(true);
1339 m_breath = MYMIN(breath, m_prop.breath_max);
1342 m_env->getGameDef()->SendPlayerBreath(this);
1345 Inventory* PlayerSAO::getInventory()
1349 const Inventory* PlayerSAO::getInventory() const
1354 InventoryLocation PlayerSAO::getInventoryLocation() const
1356 InventoryLocation loc;
1357 loc.setPlayer(m_player->getName());
1361 std::string PlayerSAO::getWieldList() const
1366 ItemStack PlayerSAO::getWieldedItem() const
1368 const Inventory *inv = getInventory();
1370 const InventoryList *mlist = inv->getList(getWieldList());
1371 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1372 ret = mlist->getItem(getWieldIndex());
1376 ItemStack PlayerSAO::getWieldedItemOrHand() const
1378 const Inventory *inv = getInventory();
1380 const InventoryList *mlist = inv->getList(getWieldList());
1381 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1382 ret = mlist->getItem(getWieldIndex());
1383 if (ret.name.empty()) {
1384 const InventoryList *hlist = inv->getList("hand");
1386 ret = hlist->getItem(0);
1391 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1393 Inventory *inv = getInventory();
1395 InventoryList *mlist = inv->getList(getWieldList());
1397 mlist->changeItem(getWieldIndex(), item);
1404 int PlayerSAO::getWieldIndex() const
1406 return m_wield_index;
1409 void PlayerSAO::setWieldIndex(int i)
1411 if(i != m_wield_index) {
1416 void PlayerSAO::disconnected()
1419 m_pending_removal = true;
1422 void PlayerSAO::unlinkPlayerSessionAndSave()
1424 assert(m_player->getPlayerSAO() == this);
1425 m_player->setPeerId(PEER_ID_INEXISTENT);
1426 m_env->savePlayer(m_player);
1427 m_player->setPlayerSAO(NULL);
1428 m_env->removePlayer(m_player);
1431 std::string PlayerSAO::getPropertyPacket()
1433 m_prop.is_visible = (true);
1434 return gob_cmd_set_properties(m_prop);
1437 bool PlayerSAO::checkMovementCheat()
1439 if (isAttached() || m_is_singleplayer ||
1440 g_settings->getBool("disable_anticheat")) {
1441 m_last_good_position = m_base_position;
1445 bool cheated = false;
1447 Check player movements
1449 NOTE: Actually the server should handle player physics like the
1450 client does and compare player's position to what is calculated
1451 on our side. This is required when eg. players fly due to an
1452 explosion. Altough a node-based alternative might be possible
1453 too, and much more lightweight.
1456 float player_max_walk = 0; // horizontal movement
1457 float player_max_jump = 0; // vertical upwards movement
1459 if (m_privs.count("fast") != 0)
1460 player_max_walk = m_player->movement_speed_fast; // Fast speed
1462 player_max_walk = m_player->movement_speed_walk; // Normal speed
1463 player_max_walk *= m_physics_override_speed;
1464 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1465 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1466 // until this can be verified correctly, tolerate higher jumping speeds
1467 player_max_jump *= 2.0;
1469 // Don't divide by zero!
1470 if (player_max_walk < 0.0001f)
1471 player_max_walk = 0.0001f;
1472 if (player_max_jump < 0.0001f)
1473 player_max_jump = 0.0001f;
1475 v3f diff = (m_base_position - m_last_good_position);
1476 float d_vert = diff.Y;
1478 float d_horiz = diff.getLength();
1479 float required_time = d_horiz / player_max_walk;
1481 // FIXME: Checking downwards movement is not easily possible currently,
1482 // the server could calculate speed differences to examine the gravity
1484 // In certain cases (water, ladders) walking speed is applied vertically
1485 float s = MYMAX(player_max_jump, player_max_walk);
1486 required_time = MYMAX(required_time, d_vert / s);
1489 if (m_move_pool.grab(required_time)) {
1490 m_last_good_position = m_base_position;
1492 const float LAG_POOL_MIN = 5.0;
1493 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1494 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1495 if (m_time_from_last_teleport > lag_pool_max) {
1496 actionstream << "Player " << m_player->getName()
1497 << " moved too fast; resetting position"
1501 setBasePosition(m_last_good_position);
1506 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1508 //update collision box
1509 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1510 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1512 toset->MinEdge += m_base_position;
1513 toset->MaxEdge += m_base_position;
1517 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1519 if (!m_prop.is_visible || !m_prop.pointable) {
1523 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1524 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
1529 float PlayerSAO::getZoomFOV() const
1531 return m_prop.zoom_fov;