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() const
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() const
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, this, &m_prop);
335 // Initialize HP from properties
336 m_hp = m_prop.hp_max;
337 // Activate entity, supplying serialized state
338 m_env->getScriptIface()->
339 luaentity_Activate(m_id, m_init_state, dtime_s);
341 m_prop.infotext = m_init_name;
345 ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
346 const std::string &data)
354 while (!data.empty()) { // breakable, run for one iteration
355 std::istringstream is(data, std::ios::binary);
356 // 'version' does not allow to incrementally extend the parameter list thus
357 // we need another variable to build on top of 'version=1'. Ugly hack but worksâ„¢
359 u8 version = readU8(is);
361 name = deSerializeString(is);
362 state = deSerializeLongString(is);
368 velocity = readV3F1000(is);
369 // yaw must be yaw to be backwards-compatible
370 rotation.Y = readF1000(is);
372 if (is.good()) // EOF for old formats
373 version2 = readU8(is);
375 if (version2 < 1) // PROTOCOL_VERSION < 37
379 rotation.X = readF1000(is);
380 rotation.Z = readF1000(is);
388 infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\""
389 << state << "\")" << std::endl;
390 LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
392 sao->m_velocity = velocity;
393 sao->m_rotation = rotation;
397 void LuaEntitySAO::step(float dtime, bool send_recommended)
399 if(!m_properties_sent)
401 m_properties_sent = true;
402 std::string str = getPropertyPacket();
403 // create message and add to list
404 ActiveObjectMessage aom(getId(), true, str);
405 m_messages_out.push(aom);
408 // If attached, check that our parent is still there. If it isn't, detach.
409 if(m_attachment_parent_id && !isAttached())
411 m_attachment_parent_id = 0;
412 m_attachment_bone = "";
413 m_attachment_position = v3f(0,0,0);
414 m_attachment_rotation = v3f(0,0,0);
415 sendPosition(false, true);
418 m_last_sent_position_timer += dtime;
420 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
421 // If the object gets detached this comes into effect automatically from the last known origin
424 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
425 m_base_position = pos;
426 m_velocity = v3f(0,0,0);
427 m_acceleration = v3f(0,0,0);
432 aabb3f box = m_prop.collisionbox;
435 collisionMoveResult moveresult;
436 f32 pos_max_d = BS*0.25; // Distance per iteration
437 v3f p_pos = m_base_position;
438 v3f p_velocity = m_velocity;
439 v3f p_acceleration = m_acceleration;
440 moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
441 pos_max_d, box, m_prop.stepheight, dtime,
442 &p_pos, &p_velocity, p_acceleration,
443 this, m_prop.collideWithObjects);
446 m_base_position = p_pos;
447 m_velocity = p_velocity;
448 m_acceleration = p_acceleration;
450 m_base_position += dtime * m_velocity + 0.5 * dtime
451 * dtime * m_acceleration;
452 m_velocity += dtime * m_acceleration;
455 if (m_prop.automatic_face_movement_dir &&
456 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
457 float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
458 + m_prop.automatic_face_movement_dir_offset;
459 float max_rotation_per_sec =
460 m_prop.automatic_face_movement_max_rotation_per_sec;
462 if (max_rotation_per_sec > 0) {
463 m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
464 wrappedApproachShortest(m_rotation.Y, target_yaw,
465 dtime * max_rotation_per_sec, 360.f);
467 // Negative values of max_rotation_per_sec mean disabled.
468 m_rotation.Y = target_yaw;
474 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
477 if (!send_recommended)
482 // TODO: force send when acceleration changes enough?
483 float minchange = 0.2*BS;
484 if(m_last_sent_position_timer > 1.0){
486 } else if(m_last_sent_position_timer > 0.2){
489 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
490 move_d += m_last_sent_move_precision;
491 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
492 if (move_d > minchange || vel_d > minchange ||
493 std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f ||
494 std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f ||
495 std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) {
497 sendPosition(true, false);
501 if (!m_armor_groups_sent) {
502 m_armor_groups_sent = true;
503 std::string str = gob_cmd_update_armor_groups(
505 // create message and add to list
506 ActiveObjectMessage aom(getId(), true, str);
507 m_messages_out.push(aom);
510 if (!m_animation_sent) {
511 m_animation_sent = true;
512 std::string str = gob_cmd_update_animation(
513 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
514 // create message and add to list
515 ActiveObjectMessage aom(getId(), true, str);
516 m_messages_out.push(aom);
519 if (!m_animation_speed_sent) {
520 m_animation_speed_sent = true;
521 std::string str = gob_cmd_update_animation_speed(m_animation_speed);
522 // create message and add to list
523 ActiveObjectMessage aom(getId(), true, str);
524 m_messages_out.push(aom);
527 if (!m_bone_position_sent) {
528 m_bone_position_sent = true;
529 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
530 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
531 std::string str = gob_cmd_update_bone_position((*ii).first,
532 (*ii).second.X, (*ii).second.Y);
533 // create message and add to list
534 ActiveObjectMessage aom(getId(), true, str);
535 m_messages_out.push(aom);
539 if (!m_attachment_sent) {
540 m_attachment_sent = true;
541 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
542 // create message and add to list
543 ActiveObjectMessage aom(getId(), true, str);
544 m_messages_out.push(aom);
548 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
550 std::ostringstream os(std::ios::binary);
552 // PROTOCOL_VERSION >= 37
553 writeU8(os, 1); // version
554 os << serializeString(""); // name
555 writeU8(os, 0); // is_player
556 writeU16(os, getId()); //id
557 writeV3F32(os, m_base_position);
558 writeV3F32(os, m_rotation);
561 std::ostringstream msg_os(std::ios::binary);
562 msg_os << serializeLongString(getPropertyPacket()); // message 1
563 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
564 msg_os << serializeLongString(gob_cmd_update_animation(
565 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
566 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
567 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
568 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
569 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
571 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
572 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
573 int message_count = 4 + m_bone_position.size();
574 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
575 (ii != m_attachment_child_ids.end()); ++ii) {
576 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
578 // TODO after a protocol bump: only send the object initialization data
579 // to older clients (superfluous since this message exists)
580 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
581 obj->getClientInitializationData(protocol_version)));
585 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
588 writeU8(os, message_count);
589 os.write(msg_os.str().c_str(), msg_os.str().size());
595 void LuaEntitySAO::getStaticData(std::string *result) const
597 verbosestream<<FUNCTION_NAME<<std::endl;
598 std::ostringstream os(std::ios::binary);
599 // version must be 1 to keep backwards-compatibility. See version2
602 os<<serializeString(m_init_name);
605 std::string state = m_env->getScriptIface()->
606 luaentity_GetStaticdata(m_id);
607 os<<serializeLongString(state);
609 os<<serializeLongString(m_init_state);
612 writeV3F1000(os, m_velocity);
614 writeF1000(os, m_rotation.Y);
616 // version2. Increase this variable for new values
617 writeU8(os, 1); // PROTOCOL_VERSION >= 37
619 writeF1000(os, m_rotation.X);
620 writeF1000(os, m_rotation.Z);
622 // <write new values>
627 u16 LuaEntitySAO::punch(v3f dir,
628 const ToolCapabilities *toolcap,
629 ServerActiveObject *puncher,
630 float time_from_last_punch)
633 // Delete unknown LuaEntities when punched
634 m_pending_removal = true;
638 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
640 s32 old_hp = getHP();
641 ItemStack selected_item, hand_item;
642 ItemStack tool_item = puncher->getWieldedItem(&selected_item, &hand_item);
644 PunchDamageResult result = getPunchDamage(
648 time_from_last_punch);
650 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
651 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
653 if (!damage_handled) {
654 if (result.did_punch) {
655 setHP((s32)getHP() - result.damage,
656 PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
658 std::string str = gob_cmd_punched(getHP());
659 // create message and add to list
660 ActiveObjectMessage aom(getId(), true, str);
661 m_messages_out.push(aom);
665 if (getHP() == 0 && !isGone()) {
666 m_pending_removal = true;
667 clearParentAttachment();
668 clearChildAttachments();
669 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
672 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
673 ", hp=" << puncher->getHP() << ") punched " <<
674 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
675 "), damage=" << (old_hp - (s32)getHP()) <<
676 (damage_handled ? " (handled by Lua)" : "") << std::endl;
681 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
686 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
689 void LuaEntitySAO::setPos(const v3f &pos)
693 m_base_position = pos;
694 sendPosition(false, true);
697 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
701 m_base_position = pos;
703 sendPosition(true, true);
706 float LuaEntitySAO::getMinimumSavedMovement()
711 std::string LuaEntitySAO::getDescription()
713 std::ostringstream os(std::ios::binary);
714 os<<"LuaEntitySAO at (";
715 os<<(m_base_position.X/BS)<<",";
716 os<<(m_base_position.Y/BS)<<",";
717 os<<(m_base_position.Z/BS);
722 void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
724 m_hp = rangelim(hp, 0, U16_MAX);
727 u16 LuaEntitySAO::getHP() const
732 void LuaEntitySAO::setVelocity(v3f velocity)
734 m_velocity = velocity;
737 v3f LuaEntitySAO::getVelocity()
742 void LuaEntitySAO::setAcceleration(v3f acceleration)
744 m_acceleration = acceleration;
747 v3f LuaEntitySAO::getAcceleration()
749 return m_acceleration;
752 void LuaEntitySAO::setTextureMod(const std::string &mod)
754 std::string str = gob_cmd_set_texture_mod(mod);
755 m_current_texture_modifier = mod;
756 // create message and add to list
757 ActiveObjectMessage aom(getId(), true, str);
758 m_messages_out.push(aom);
761 std::string LuaEntitySAO::getTextureMod() const
763 return m_current_texture_modifier;
766 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
767 bool select_horiz_by_yawpitch)
769 std::string str = gob_cmd_set_sprite(
773 select_horiz_by_yawpitch
775 // create message and add to list
776 ActiveObjectMessage aom(getId(), true, str);
777 m_messages_out.push(aom);
780 std::string LuaEntitySAO::getName()
785 std::string LuaEntitySAO::getPropertyPacket()
787 return gob_cmd_set_properties(m_prop);
790 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
792 // If the object is attached client-side, don't waste bandwidth sending its position to clients
796 m_last_sent_move_precision = m_base_position.getDistanceFrom(
797 m_last_sent_position);
798 m_last_sent_position_timer = 0;
799 m_last_sent_position = m_base_position;
800 m_last_sent_velocity = m_velocity;
801 //m_last_sent_acceleration = m_acceleration;
802 m_last_sent_rotation = m_rotation;
804 float update_interval = m_env->getSendRecommendedInterval();
806 std::string str = gob_cmd_update_position(
815 // create message and add to list
816 ActiveObjectMessage aom(getId(), false, str);
817 m_messages_out.push(aom);
820 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
824 //update collision box
825 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
826 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
828 toset->MinEdge += m_base_position;
829 toset->MaxEdge += m_base_position;
837 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
839 if (!m_prop.is_visible || !m_prop.pointable) {
843 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
844 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
849 bool LuaEntitySAO::collideWithObjects() const
851 return m_prop.collideWithObjects;
858 // No prototype, PlayerSAO does not need to be deserialized
860 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
861 bool is_singleplayer):
862 UnitSAO(env_, v3f(0,0,0)),
865 m_is_singleplayer(is_singleplayer)
867 assert(m_peer_id != 0); // pre-condition
869 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
870 m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
871 m_prop.physical = false;
872 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
873 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
874 m_prop.pointable = true;
875 // Start of default appearance, this should be overwritten by Lua
876 m_prop.visual = "upright_sprite";
877 m_prop.visual_size = v3f(1, 2, 1);
878 m_prop.textures.clear();
879 m_prop.textures.emplace_back("player.png");
880 m_prop.textures.emplace_back("player_back.png");
881 m_prop.colors.clear();
882 m_prop.colors.emplace_back(255, 255, 255, 255);
883 m_prop.spritediv = v2s16(1,1);
884 m_prop.eye_height = 1.625f;
885 // End of default appearance
886 m_prop.is_visible = true;
887 m_prop.backface_culling = false;
888 m_prop.makes_footstep_sound = true;
889 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
890 m_hp = m_prop.hp_max;
891 m_breath = m_prop.breath_max;
892 // Disable zoom in survival mode using a value of 0
893 m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
895 if (!g_settings->getBool("enable_damage"))
896 m_armor_groups["immortal"] = 1;
899 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
906 v3f PlayerSAO::getEyeOffset() const
908 return v3f(0, BS * m_prop.eye_height, 0);
911 std::string PlayerSAO::getDescription()
913 return std::string("player ") + m_player->getName();
916 // Called after id has been set and has been inserted in environment
917 void PlayerSAO::addedToEnvironment(u32 dtime_s)
919 ServerActiveObject::addedToEnvironment(dtime_s);
920 ServerActiveObject::setBasePosition(m_base_position);
921 m_player->setPlayerSAO(this);
922 m_player->setPeerId(m_peer_id);
923 m_last_good_position = m_base_position;
926 // Called before removing from environment
927 void PlayerSAO::removingFromEnvironment()
929 ServerActiveObject::removingFromEnvironment();
930 if (m_player->getPlayerSAO() == this) {
931 unlinkPlayerSessionAndSave();
932 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
933 m_env->deleteParticleSpawner(attached_particle_spawner, false);
938 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
940 std::ostringstream os(std::ios::binary);
943 writeU8(os, 1); // version
944 os << serializeString(m_player->getName()); // name
945 writeU8(os, 1); // is_player
946 writeS16(os, getId()); // id
947 writeV3F32(os, m_base_position);
948 writeV3F32(os, m_rotation);
949 writeU16(os, getHP());
951 std::ostringstream msg_os(std::ios::binary);
952 msg_os << serializeLongString(getPropertyPacket()); // message 1
953 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
954 msg_os << serializeLongString(gob_cmd_update_animation(
955 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
956 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
957 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
958 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
959 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
961 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
962 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
963 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
964 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
965 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
966 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
967 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
968 int message_count = 6 + m_bone_position.size();
969 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
970 ii != m_attachment_child_ids.end(); ++ii) {
971 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
973 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
974 obj->getClientInitializationData(protocol_version)));
978 writeU8(os, message_count);
979 os.write(msg_os.str().c_str(), msg_os.str().size());
985 void PlayerSAO::getStaticData(std::string * result) const
987 FATAL_ERROR("Obsolete function");
990 void PlayerSAO::step(float dtime, bool send_recommended)
992 if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
993 // Get nose/mouth position, approximate with eye position
994 v3s16 p = floatToInt(getEyePosition(), BS);
995 MapNode n = m_env->getMap().getNode(p);
996 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
997 // If node generates drown
998 if (c.drowning > 0 && m_hp > 0) {
1000 setBreath(m_breath - 1);
1002 // No more breath, damage player
1003 if (m_breath == 0) {
1004 PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
1005 setHP(m_hp - c.drowning, reason);
1006 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1011 if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
1012 // Get nose/mouth position, approximate with eye position
1013 v3s16 p = floatToInt(getEyePosition(), BS);
1014 MapNode n = m_env->getMap().getNode(p);
1015 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1016 // If player is alive & not drowning & not in ignore & not immortal, breathe
1017 if (m_breath < m_prop.breath_max && c.drowning == 0 &&
1018 n.getContent() != CONTENT_IGNORE && m_hp > 0)
1019 setBreath(m_breath + 1);
1022 if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
1023 u32 damage_per_second = 0;
1024 std::string nodename;
1025 // Lowest and highest damage points are 0.1 within collisionbox
1026 float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
1028 // Sequence of damage points, starting 0.1 above feet and progressing
1029 // upwards in 1 node intervals, stopping below top damage point.
1030 for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
1031 v3s16 p = floatToInt(m_base_position +
1032 v3f(0.0f, dam_height * BS, 0.0f), BS);
1033 MapNode n = m_env->getMap().getNode(p);
1034 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
1035 if (c.damage_per_second > damage_per_second) {
1036 damage_per_second = c.damage_per_second;
1042 v3s16 ptop = floatToInt(m_base_position +
1043 v3f(0.0f, dam_top * BS, 0.0f), BS);
1044 MapNode ntop = m_env->getMap().getNode(ptop);
1045 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
1046 if (c.damage_per_second > damage_per_second) {
1047 damage_per_second = c.damage_per_second;
1051 if (damage_per_second != 0 && m_hp > 0) {
1052 s32 newhp = (s32)m_hp - (s32)damage_per_second;
1053 PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
1054 setHP(newhp, reason);
1055 m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
1059 if (!m_properties_sent) {
1060 m_properties_sent = true;
1061 std::string str = getPropertyPacket();
1062 // create message and add to list
1063 ActiveObjectMessage aom(getId(), true, str);
1064 m_messages_out.push(aom);
1065 m_env->getScriptIface()->player_event(this, "properties_changed");
1068 // If attached, check that our parent is still there. If it isn't, detach.
1069 if (m_attachment_parent_id && !isAttached()) {
1070 m_attachment_parent_id = 0;
1071 m_attachment_bone = "";
1072 m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
1073 m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
1074 setBasePosition(m_last_good_position);
1075 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1078 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
1080 // Set lag pool maximums based on estimated lag
1081 const float LAG_POOL_MIN = 5.0f;
1082 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
1083 if(lag_pool_max < LAG_POOL_MIN)
1084 lag_pool_max = LAG_POOL_MIN;
1085 m_dig_pool.setMax(lag_pool_max);
1086 m_move_pool.setMax(lag_pool_max);
1088 // Increment cheat prevention timers
1089 m_dig_pool.add(dtime);
1090 m_move_pool.add(dtime);
1091 m_time_from_last_teleport += dtime;
1092 m_time_from_last_punch += dtime;
1093 m_nocheat_dig_time += dtime;
1094 m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f);
1096 // Each frame, parent position is copied if the object is attached,
1097 // otherwise it's calculated normally.
1098 // If the object gets detached this comes into effect automatically from
1099 // the last known origin.
1101 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1102 m_last_good_position = pos;
1103 setBasePosition(pos);
1106 if (!send_recommended)
1109 if (m_position_not_sent) {
1110 m_position_not_sent = false;
1111 float update_interval = m_env->getSendRecommendedInterval();
1113 // When attached, the position is only sent to clients where the
1114 // parent isn't known
1116 pos = m_last_good_position;
1118 pos = m_base_position;
1120 std::string str = gob_cmd_update_position(
1122 v3f(0.0f, 0.0f, 0.0f),
1123 v3f(0.0f, 0.0f, 0.0f),
1129 // create message and add to list
1130 ActiveObjectMessage aom(getId(), false, str);
1131 m_messages_out.push(aom);
1134 if (!m_armor_groups_sent) {
1135 m_armor_groups_sent = true;
1136 std::string str = gob_cmd_update_armor_groups(
1138 // create message and add to list
1139 ActiveObjectMessage aom(getId(), true, str);
1140 m_messages_out.push(aom);
1143 if (!m_physics_override_sent) {
1144 m_physics_override_sent = true;
1145 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1146 m_physics_override_jump, m_physics_override_gravity,
1147 m_physics_override_sneak, m_physics_override_sneak_glitch,
1148 m_physics_override_new_move);
1149 // create message and add to list
1150 ActiveObjectMessage aom(getId(), true, str);
1151 m_messages_out.push(aom);
1154 if (!m_animation_sent) {
1155 m_animation_sent = true;
1156 std::string str = gob_cmd_update_animation(
1157 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1158 // create message and add to list
1159 ActiveObjectMessage aom(getId(), true, str);
1160 m_messages_out.push(aom);
1163 if (!m_bone_position_sent) {
1164 m_bone_position_sent = true;
1165 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1166 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1167 std::string str = gob_cmd_update_bone_position((*ii).first,
1168 (*ii).second.X, (*ii).second.Y);
1169 // create message and add to list
1170 ActiveObjectMessage aom(getId(), true, str);
1171 m_messages_out.push(aom);
1175 if (!m_attachment_sent) {
1176 m_attachment_sent = true;
1177 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1178 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1179 // create message and add to list
1180 ActiveObjectMessage aom(getId(), true, str);
1181 m_messages_out.push(aom);
1185 void PlayerSAO::setBasePosition(const v3f &position)
1187 if (m_player && position != m_base_position)
1188 m_player->setDirty(true);
1190 // This needs to be ran for attachments too
1191 ServerActiveObject::setBasePosition(position);
1193 // Updating is not wanted/required for player migration
1195 m_position_not_sent = true;
1199 void PlayerSAO::setPos(const v3f &pos)
1204 // Send mapblock of target location
1205 v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
1206 m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
1208 setBasePosition(pos);
1209 // Movement caused by this command is always valid
1210 m_last_good_position = pos;
1211 m_move_pool.empty();
1212 m_time_from_last_teleport = 0.0;
1213 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1216 void PlayerSAO::moveTo(v3f pos, bool continuous)
1221 setBasePosition(pos);
1222 // Movement caused by this command is always valid
1223 m_last_good_position = pos;
1224 m_move_pool.empty();
1225 m_time_from_last_teleport = 0.0;
1226 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1229 void PlayerSAO::setPlayerYaw(const float yaw)
1231 v3f rotation(0, yaw, 0);
1232 if (m_player && yaw != m_rotation.Y)
1233 m_player->setDirty(true);
1235 // Set player model yaw, not look view
1236 UnitSAO::setRotation(rotation);
1239 void PlayerSAO::setFov(const float fov)
1241 if (m_player && fov != m_fov)
1242 m_player->setDirty(true);
1247 void PlayerSAO::setWantedRange(const s16 range)
1249 if (m_player && range != m_wanted_range)
1250 m_player->setDirty(true);
1252 m_wanted_range = range;
1255 void PlayerSAO::setPlayerYawAndSend(const float yaw)
1258 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1261 void PlayerSAO::setLookPitch(const float pitch)
1263 if (m_player && pitch != m_pitch)
1264 m_player->setDirty(true);
1269 void PlayerSAO::setLookPitchAndSend(const float pitch)
1271 setLookPitch(pitch);
1272 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1275 u16 PlayerSAO::punch(v3f dir,
1276 const ToolCapabilities *toolcap,
1277 ServerActiveObject *puncher,
1278 float time_from_last_punch)
1283 FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
1285 // No effect if PvP disabled or if immortal
1286 if (isImmortal() || !g_settings->getBool("enable_pvp")) {
1287 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1288 std::string str = gob_cmd_punched(getHP());
1289 // create message and add to list
1290 ActiveObjectMessage aom(getId(), true, str);
1291 m_messages_out.push(aom);
1296 s32 old_hp = getHP();
1297 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1298 time_from_last_punch);
1300 PlayerSAO *playersao = m_player->getPlayerSAO();
1302 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1303 puncher, time_from_last_punch, toolcap, dir,
1306 if (!damage_handled) {
1307 setHP((s32)getHP() - (s32)hitparams.hp,
1308 PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
1309 } else { // override client prediction
1310 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1311 std::string str = gob_cmd_punched(getHP());
1312 // create message and add to list
1313 ActiveObjectMessage aom(getId(), true, str);
1314 m_messages_out.push(aom);
1318 actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
1319 ", hp=" << puncher->getHP() << ") punched " <<
1320 getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
1321 "), damage=" << (old_hp - (s32)getHP()) <<
1322 (damage_handled ? " (handled by Lua)" : "") << std::endl;
1324 return hitparams.wear;
1327 void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
1331 hp = rangelim(hp, 0, m_prop.hp_max);
1334 s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
1338 hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
1341 if (hp < oldhp && isImmortal())
1346 // Update properties on death
1347 if ((hp == 0) != (oldhp == 0))
1348 m_properties_sent = false;
1351 void PlayerSAO::setBreath(const u16 breath, bool send)
1353 if (m_player && breath != m_breath)
1354 m_player->setDirty(true);
1356 m_breath = rangelim(breath, 0, m_prop.breath_max);
1359 m_env->getGameDef()->SendPlayerBreath(this);
1362 Inventory *PlayerSAO::getInventory() const
1364 return m_player ? &m_player->inventory : nullptr;
1367 InventoryLocation PlayerSAO::getInventoryLocation() const
1369 InventoryLocation loc;
1370 loc.setPlayer(m_player->getName());
1374 u16 PlayerSAO::getWieldIndex() const
1376 return m_player->getWieldIndex();
1379 ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
1381 return m_player->getWieldedItem(selected, hand);
1384 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1386 InventoryList *mlist = m_player->inventory.getList(getWieldList());
1388 mlist->changeItem(m_player->getWieldIndex(), item);
1394 void PlayerSAO::disconnected()
1397 m_pending_removal = true;
1400 void PlayerSAO::unlinkPlayerSessionAndSave()
1402 assert(m_player->getPlayerSAO() == this);
1403 m_player->setPeerId(PEER_ID_INEXISTENT);
1404 m_env->savePlayer(m_player);
1405 m_player->setPlayerSAO(NULL);
1406 m_env->removePlayer(m_player);
1409 std::string PlayerSAO::getPropertyPacket()
1411 m_prop.is_visible = (true);
1412 return gob_cmd_set_properties(m_prop);
1415 void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
1417 if (m_max_speed_override_time == 0.0f)
1418 m_max_speed_override = vel;
1420 m_max_speed_override += vel;
1422 float accel = MYMIN(m_player->movement_acceleration_default,
1423 m_player->movement_acceleration_air);
1424 m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
1428 bool PlayerSAO::checkMovementCheat()
1430 if (isAttached() || m_is_singleplayer ||
1431 g_settings->getBool("disable_anticheat")) {
1432 m_last_good_position = m_base_position;
1436 bool cheated = false;
1438 Check player movements
1440 NOTE: Actually the server should handle player physics like the
1441 client does and compare player's position to what is calculated
1442 on our side. This is required when eg. players fly due to an
1443 explosion. Altough a node-based alternative might be possible
1444 too, and much more lightweight.
1447 float override_max_H, override_max_V;
1448 if (m_max_speed_override_time > 0.0f) {
1449 override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
1450 override_max_V = fabs(m_max_speed_override.Y);
1452 override_max_H = override_max_V = 0.0f;
1455 float player_max_walk = 0; // horizontal movement
1456 float player_max_jump = 0; // vertical upwards movement
1458 if (m_privs.count("fast") != 0)
1459 player_max_walk = m_player->movement_speed_fast; // Fast speed
1461 player_max_walk = m_player->movement_speed_walk; // Normal speed
1462 player_max_walk *= m_physics_override_speed;
1463 player_max_walk = MYMAX(player_max_walk, override_max_H);
1465 player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
1466 // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
1467 // until this can be verified correctly, tolerate higher jumping speeds
1468 player_max_jump *= 2.0;
1469 player_max_jump = MYMAX(player_max_jump, override_max_V);
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;