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"
32 std::map<u16, ServerActiveObject::Factory> ServerActiveObject::m_types;
38 class TestSAO : public ServerActiveObject
41 TestSAO(ServerEnvironment *env, v3f pos):
42 ServerActiveObject(env, pos),
46 ServerActiveObject::registerType(getType(), create);
48 ActiveObjectType getType() const
49 { return ACTIVEOBJECT_TYPE_TEST; }
51 static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
52 const std::string &data)
54 return new TestSAO(env, pos);
57 void step(float dtime, bool send_recommended)
66 m_base_position.Y += dtime * BS * 2;
67 if(m_base_position.Y > 8*BS)
68 m_base_position.Y = 2*BS;
70 if(send_recommended == false)
80 data += itos(0); // 0 = position
82 data += itos(m_base_position.X);
84 data += itos(m_base_position.Y);
86 data += itos(m_base_position.Z);
88 ActiveObjectMessage aom(getId(), false, data);
89 m_messages_out.push(aom);
93 bool getCollisionBox(aabb3f *toset) const { return false; }
95 virtual bool getSelectionBox(aabb3f *toset) const { return false; }
97 bool collideWithObjects() const { return false; }
104 // Prototype (registers item for deserialization)
105 TestSAO proto_TestSAO(NULL, v3f(0,0,0));
111 UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos):
112 ServerActiveObject(env, pos)
114 // Initialize something to armor groups
115 m_armor_groups["fleshy"] = 100;
118 bool UnitSAO::isAttached() const
120 if (!m_attachment_parent_id)
122 // Check if the parent still exists
123 ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id);
129 void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups)
131 m_armor_groups = armor_groups;
132 m_armor_groups_sent = false;
135 const ItemGroupList &UnitSAO::getArmorGroups()
137 return m_armor_groups;
140 void UnitSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop)
142 // store these so they can be updated to clients
143 m_animation_range = frame_range;
144 m_animation_speed = frame_speed;
145 m_animation_blend = frame_blend;
146 m_animation_loop = frame_loop;
147 m_animation_sent = false;
150 void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop)
152 *frame_range = m_animation_range;
153 *frame_speed = m_animation_speed;
154 *frame_blend = m_animation_blend;
155 *frame_loop = m_animation_loop;
158 void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation)
160 // store these so they can be updated to clients
161 m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
162 m_bone_position_sent = false;
165 void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation)
167 *position = m_bone_position[bone].X;
168 *rotation = m_bone_position[bone].Y;
171 void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation)
173 // Attachments need to be handled on both the server and client.
174 // If we just attach on the server, we can only copy the position of the parent. Attachments
175 // are still sent to clients at an interval so players might see them lagging, plus we can't
176 // read and attach to skeletal bones.
177 // If we just attach on the client, the server still sees the child at its original location.
178 // This breaks some things so we also give the server the most accurate representation
179 // even if players only see the client changes.
181 m_attachment_parent_id = parent_id;
182 m_attachment_bone = bone;
183 m_attachment_position = position;
184 m_attachment_rotation = rotation;
185 m_attachment_sent = false;
188 void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
191 *parent_id = m_attachment_parent_id;
192 *bone = m_attachment_bone;
193 *position = m_attachment_position;
194 *rotation = m_attachment_rotation;
197 void UnitSAO::addAttachmentChild(int child_id)
199 m_attachment_child_ids.insert(child_id);
202 void UnitSAO::removeAttachmentChild(int child_id)
204 m_attachment_child_ids.erase(child_id);
207 const std::unordered_set<int> &UnitSAO::getAttachmentChildIds()
209 return m_attachment_child_ids;
212 ObjectProperties* UnitSAO::accessObjectProperties()
217 void UnitSAO::notifyObjectPropertiesModified()
219 m_properties_sent = false;
226 // Prototype (registers item for deserialization)
227 LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", "");
229 LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
230 const std::string &name, const std::string &state):
235 // Only register type if no environment supplied
237 ServerActiveObject::registerType(getType(), create);
242 LuaEntitySAO::~LuaEntitySAO()
245 m_env->getScriptIface()->luaentity_Remove(m_id);
248 for (std::unordered_set<u32>::iterator it = m_attached_particle_spawners.begin();
249 it != m_attached_particle_spawners.end(); ++it) {
250 m_env->deleteParticleSpawner(*it, false);
254 void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
256 ServerActiveObject::addedToEnvironment(dtime_s);
258 // Create entity from name
259 m_registered = m_env->getScriptIface()->
260 luaentity_Add(m_id, m_init_name.c_str());
264 m_env->getScriptIface()->
265 luaentity_GetProperties(m_id, &m_prop);
266 // Initialize HP from properties
267 m_hp = m_prop.hp_max;
268 // Activate entity, supplying serialized state
269 m_env->getScriptIface()->
270 luaentity_Activate(m_id, m_init_state.c_str(), dtime_s);
272 m_prop.infotext = m_init_name;
276 ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
277 const std::string &data)
285 std::istringstream is(data, std::ios::binary);
287 u8 version = readU8(is);
288 // check if version is supported
290 name = deSerializeString(is);
291 state = deSerializeLongString(is);
293 else if(version == 1){
294 name = deSerializeString(is);
295 state = deSerializeLongString(is);
297 velocity = readV3F1000(is);
302 infostream<<"LuaEntitySAO::create(name=\""<<name<<"\" state=\""
303 <<state<<"\")"<<std::endl;
304 LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
306 sao->m_velocity = velocity;
311 void LuaEntitySAO::step(float dtime, bool send_recommended)
313 if(!m_properties_sent)
315 m_properties_sent = true;
316 std::string str = getPropertyPacket();
317 // create message and add to list
318 ActiveObjectMessage aom(getId(), true, str);
319 m_messages_out.push(aom);
322 // If attached, check that our parent is still there. If it isn't, detach.
323 if(m_attachment_parent_id && !isAttached())
325 m_attachment_parent_id = 0;
326 m_attachment_bone = "";
327 m_attachment_position = v3f(0,0,0);
328 m_attachment_rotation = v3f(0,0,0);
329 sendPosition(false, true);
332 m_last_sent_position_timer += dtime;
334 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
335 // If the object gets detached this comes into effect automatically from the last known origin
338 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
339 m_base_position = pos;
340 m_velocity = v3f(0,0,0);
341 m_acceleration = v3f(0,0,0);
346 aabb3f box = m_prop.collisionbox;
349 collisionMoveResult moveresult;
350 f32 pos_max_d = BS*0.25; // Distance per iteration
351 v3f p_pos = m_base_position;
352 v3f p_velocity = m_velocity;
353 v3f p_acceleration = m_acceleration;
354 moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
355 pos_max_d, box, m_prop.stepheight, dtime,
356 &p_pos, &p_velocity, p_acceleration,
357 this, m_prop.collideWithObjects);
360 m_base_position = p_pos;
361 m_velocity = p_velocity;
362 m_acceleration = p_acceleration;
364 m_base_position += dtime * m_velocity + 0.5 * dtime
365 * dtime * m_acceleration;
366 m_velocity += dtime * m_acceleration;
369 if((m_prop.automatic_face_movement_dir) &&
370 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001))
372 float optimal_yaw = atan2(m_velocity.Z,m_velocity.X) * 180 / M_PI
373 + m_prop.automatic_face_movement_dir_offset;
374 float max_rotation_delta =
375 dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
377 if ((m_prop.automatic_face_movement_max_rotation_per_sec > 0) &&
378 (fabs(m_yaw - optimal_yaw) > max_rotation_delta)) {
380 m_yaw = optimal_yaw < m_yaw ? m_yaw - max_rotation_delta : m_yaw + max_rotation_delta;
388 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
391 // Remove LuaEntity beyond terrain edges
393 ServerMap *map = dynamic_cast<ServerMap *>(&m_env->getMap());
395 if (!m_pending_deactivation &&
396 map->saoPositionOverLimit(m_base_position)) {
397 infostream << "Remove SAO " << m_id << "(" << m_init_name
398 << "), outside of limits" << std::endl;
399 m_pending_deactivation = true;
405 if(send_recommended == false)
410 // TODO: force send when acceleration changes enough?
411 float minchange = 0.2*BS;
412 if(m_last_sent_position_timer > 1.0){
414 } else if(m_last_sent_position_timer > 0.2){
417 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
418 move_d += m_last_sent_move_precision;
419 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
420 if(move_d > minchange || vel_d > minchange ||
421 fabs(m_yaw - m_last_sent_yaw) > 1.0){
422 sendPosition(true, false);
426 if(m_armor_groups_sent == false){
427 m_armor_groups_sent = true;
428 std::string str = gob_cmd_update_armor_groups(
430 // create message and add to list
431 ActiveObjectMessage aom(getId(), true, str);
432 m_messages_out.push(aom);
435 if(m_animation_sent == false){
436 m_animation_sent = true;
437 std::string str = gob_cmd_update_animation(
438 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
439 // create message and add to list
440 ActiveObjectMessage aom(getId(), true, str);
441 m_messages_out.push(aom);
444 if(m_bone_position_sent == false){
445 m_bone_position_sent = true;
446 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
447 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
448 std::string str = gob_cmd_update_bone_position((*ii).first,
449 (*ii).second.X, (*ii).second.Y);
450 // create message and add to list
451 ActiveObjectMessage aom(getId(), true, str);
452 m_messages_out.push(aom);
456 if(m_attachment_sent == false){
457 m_attachment_sent = true;
458 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
459 // create message and add to list
460 ActiveObjectMessage aom(getId(), true, str);
461 m_messages_out.push(aom);
465 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
467 std::ostringstream os(std::ios::binary);
470 writeU8(os, 1); // version
471 os << serializeString(""); // name
472 writeU8(os, 0); // is_player
473 writeS16(os, getId()); //id
474 writeV3F1000(os, m_base_position);
475 writeF1000(os, m_yaw);
478 std::ostringstream msg_os(std::ios::binary);
479 msg_os << serializeLongString(getPropertyPacket()); // message 1
480 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
481 msg_os << serializeLongString(gob_cmd_update_animation(
482 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
483 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
484 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
485 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
486 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
488 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
489 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
490 int message_count = 4 + m_bone_position.size();
491 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
492 (ii != m_attachment_child_ids.end()); ++ii) {
493 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
495 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
496 obj->getClientInitializationData(protocol_version)));
500 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
503 writeU8(os, message_count);
504 os.write(msg_os.str().c_str(), msg_os.str().size());
510 void LuaEntitySAO::getStaticData(std::string *result) const
512 verbosestream<<FUNCTION_NAME<<std::endl;
513 std::ostringstream os(std::ios::binary);
517 os<<serializeString(m_init_name);
520 std::string state = m_env->getScriptIface()->
521 luaentity_GetStaticdata(m_id);
522 os<<serializeLongString(state);
524 os<<serializeLongString(m_init_state);
529 writeV3F1000(os, m_velocity);
531 writeF1000(os, m_yaw);
535 int LuaEntitySAO::punch(v3f dir,
536 const ToolCapabilities *toolcap,
537 ServerActiveObject *puncher,
538 float time_from_last_punch)
541 // Delete unknown LuaEntities when punched
546 // It's best that attachments cannot be punched
550 ItemStack *punchitem = NULL;
551 ItemStack punchitem_static;
553 punchitem_static = puncher->getWieldedItem();
554 punchitem = &punchitem_static;
557 PunchDamageResult result = getPunchDamage(
561 time_from_last_punch);
563 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
564 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
566 if (!damage_handled) {
567 if (result.did_punch) {
568 setHP(getHP() - result.damage);
570 if (result.damage > 0) {
571 std::string punchername = puncher ? puncher->getDescription() : "nil";
573 actionstream << getDescription() << " punched by "
574 << punchername << ", damage " << result.damage
575 << " hp, health now " << getHP() << " hp" << std::endl;
578 std::string str = gob_cmd_punched(result.damage, getHP());
579 // create message and add to list
580 ActiveObjectMessage aom(getId(), true, str);
581 m_messages_out.push(aom);
587 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
595 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
599 // It's best that attachments cannot be clicked
602 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
605 void LuaEntitySAO::setPos(const v3f &pos)
609 m_base_position = pos;
610 sendPosition(false, true);
613 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
617 m_base_position = pos;
619 sendPosition(true, true);
622 float LuaEntitySAO::getMinimumSavedMovement()
627 std::string LuaEntitySAO::getDescription()
629 std::ostringstream os(std::ios::binary);
630 os<<"LuaEntitySAO at (";
631 os<<(m_base_position.X/BS)<<",";
632 os<<(m_base_position.Y/BS)<<",";
633 os<<(m_base_position.Z/BS);
638 void LuaEntitySAO::setHP(s16 hp)
644 s16 LuaEntitySAO::getHP() const
649 void LuaEntitySAO::setVelocity(v3f velocity)
651 m_velocity = velocity;
654 v3f LuaEntitySAO::getVelocity()
659 void LuaEntitySAO::setAcceleration(v3f acceleration)
661 m_acceleration = acceleration;
664 v3f LuaEntitySAO::getAcceleration()
666 return m_acceleration;
669 void LuaEntitySAO::setTextureMod(const std::string &mod)
671 std::string str = gob_cmd_set_texture_mod(mod);
672 m_current_texture_modifier = mod;
673 // create message and add to list
674 ActiveObjectMessage aom(getId(), true, str);
675 m_messages_out.push(aom);
678 std::string LuaEntitySAO::getTextureMod() const
680 return m_current_texture_modifier;
683 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
684 bool select_horiz_by_yawpitch)
686 std::string str = gob_cmd_set_sprite(
690 select_horiz_by_yawpitch
692 // create message and add to list
693 ActiveObjectMessage aom(getId(), true, str);
694 m_messages_out.push(aom);
697 std::string LuaEntitySAO::getName()
702 std::string LuaEntitySAO::getPropertyPacket()
704 return gob_cmd_set_properties(m_prop);
707 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
709 // If the object is attached client-side, don't waste bandwidth sending its position to clients
713 m_last_sent_move_precision = m_base_position.getDistanceFrom(
714 m_last_sent_position);
715 m_last_sent_position_timer = 0;
716 m_last_sent_yaw = m_yaw;
717 m_last_sent_position = m_base_position;
718 m_last_sent_velocity = m_velocity;
719 //m_last_sent_acceleration = m_acceleration;
721 float update_interval = m_env->getSendRecommendedInterval();
723 std::string str = gob_cmd_update_position(
732 // create message and add to list
733 ActiveObjectMessage aom(getId(), false, str);
734 m_messages_out.push(aom);
737 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
741 //update collision box
742 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
743 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
745 toset->MinEdge += m_base_position;
746 toset->MaxEdge += m_base_position;
754 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
756 if (!m_prop.is_visible) {
760 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
761 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
766 bool LuaEntitySAO::collideWithObjects() const
768 return m_prop.collideWithObjects;
775 // No prototype, PlayerSAO does not need to be deserialized
777 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_,
778 bool is_singleplayer):
779 UnitSAO(env_, v3f(0,0,0)),
782 m_is_singleplayer(is_singleplayer)
784 assert(m_peer_id != 0); // pre-condition
786 m_prop.hp_max = PLAYER_MAX_HP;
787 m_prop.physical = false;
789 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
790 // start of default appearance, this should be overwritten by LUA
791 m_prop.visual = "upright_sprite";
792 m_prop.visual_size = v2f(1, 2);
793 m_prop.textures.clear();
794 m_prop.textures.push_back("player.png");
795 m_prop.textures.push_back("player_back.png");
796 m_prop.colors.clear();
797 m_prop.colors.push_back(video::SColor(255, 255, 255, 255));
798 m_prop.spritediv = v2s16(1,1);
799 // end of default appearance
800 m_prop.is_visible = true;
801 m_prop.makes_footstep_sound = true;
802 m_hp = PLAYER_MAX_HP;
805 PlayerSAO::~PlayerSAO()
807 if(m_inventory != &m_player->inventory)
811 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
816 m_inventory = &m_player->inventory;
819 v3f PlayerSAO::getEyeOffset() const
821 return v3f(0, BS * 1.625f, 0);
824 std::string PlayerSAO::getDescription()
826 return std::string("player ") + m_player->getName();
829 // Called after id has been set and has been inserted in environment
830 void PlayerSAO::addedToEnvironment(u32 dtime_s)
832 ServerActiveObject::addedToEnvironment(dtime_s);
833 ServerActiveObject::setBasePosition(m_base_position);
834 m_player->setPlayerSAO(this);
835 m_player->peer_id = m_peer_id;
836 m_last_good_position = m_base_position;
839 // Called before removing from environment
840 void PlayerSAO::removingFromEnvironment()
842 ServerActiveObject::removingFromEnvironment();
843 if (m_player->getPlayerSAO() == this) {
844 unlinkPlayerSessionAndSave();
845 for (std::unordered_set<u32>::iterator it = m_attached_particle_spawners.begin();
846 it != m_attached_particle_spawners.end(); ++it) {
847 m_env->deleteParticleSpawner(*it, false);
852 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
854 std::ostringstream os(std::ios::binary);
857 writeU8(os, 1); // version
858 os << serializeString(m_player->getName()); // name
859 writeU8(os, 1); // is_player
860 writeS16(os, getId()); //id
861 writeV3F1000(os, m_base_position);
862 writeF1000(os, m_yaw);
863 writeS16(os, getHP());
865 std::ostringstream msg_os(std::ios::binary);
866 msg_os << serializeLongString(getPropertyPacket()); // message 1
867 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
868 msg_os << serializeLongString(gob_cmd_update_animation(
869 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
870 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
871 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
872 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
873 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
875 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
876 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
877 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
878 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
879 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
880 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
881 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
882 int message_count = 6 + m_bone_position.size();
883 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
884 ii != m_attachment_child_ids.end(); ++ii) {
885 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
887 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
888 obj->getClientInitializationData(protocol_version)));
892 writeU8(os, message_count);
893 os.write(msg_os.str().c_str(), msg_os.str().size());
899 void PlayerSAO::getStaticData(std::string *result) const
901 FATAL_ERROR("Deprecated function");
904 void PlayerSAO::step(float dtime, bool send_recommended)
906 if (m_drowning_interval.step(dtime, 2.0)) {
908 v3s16 p = floatToInt(m_base_position + v3f(0, BS * 1.6, 0), BS);
909 MapNode n = m_env->getMap().getNodeNoEx(p);
910 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
911 // If node generates drown
912 if (c.drowning > 0 && m_hp > 0) {
914 setBreath(m_breath - 1);
916 // No more breath, damage player
918 setHP(m_hp - c.drowning);
919 m_env->getGameDef()->SendPlayerHPOrDie(this);
924 if (m_breathing_interval.step(dtime, 0.5)) {
926 v3s16 p = floatToInt(m_base_position + v3f(0, BS * 1.6, 0), BS);
927 MapNode n = m_env->getMap().getNodeNoEx(p);
928 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
929 // If player is alive & no drowning, breath
930 if (m_hp > 0 && m_breath < PLAYER_MAX_BREATH && c.drowning == 0)
931 setBreath(m_breath + 1);
934 if (m_node_hurt_interval.step(dtime, 1.0)) {
935 // Feet, middle and head
936 v3s16 p1 = floatToInt(m_base_position + v3f(0, BS*0.1, 0), BS);
937 MapNode n1 = m_env->getMap().getNodeNoEx(p1);
938 v3s16 p2 = floatToInt(m_base_position + v3f(0, BS*0.8, 0), BS);
939 MapNode n2 = m_env->getMap().getNodeNoEx(p2);
940 v3s16 p3 = floatToInt(m_base_position + v3f(0, BS*1.6, 0), BS);
941 MapNode n3 = m_env->getMap().getNodeNoEx(p3);
943 u32 damage_per_second = 0;
944 damage_per_second = MYMAX(damage_per_second,
945 m_env->getGameDef()->ndef()->get(n1).damage_per_second);
946 damage_per_second = MYMAX(damage_per_second,
947 m_env->getGameDef()->ndef()->get(n2).damage_per_second);
948 damage_per_second = MYMAX(damage_per_second,
949 m_env->getGameDef()->ndef()->get(n3).damage_per_second);
951 if (damage_per_second != 0 && m_hp > 0) {
952 s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second);
954 m_env->getGameDef()->SendPlayerHPOrDie(this);
958 if (!m_properties_sent) {
959 m_properties_sent = true;
960 std::string str = getPropertyPacket();
961 // create message and add to list
962 ActiveObjectMessage aom(getId(), true, str);
963 m_messages_out.push(aom);
966 // If attached, check that our parent is still there. If it isn't, detach.
967 if(m_attachment_parent_id && !isAttached())
969 m_attachment_parent_id = 0;
970 m_attachment_bone = "";
971 m_attachment_position = v3f(0,0,0);
972 m_attachment_rotation = v3f(0,0,0);
973 setBasePosition(m_last_good_position);
974 m_env->getGameDef()->SendMovePlayer(m_peer_id);
977 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
979 // Set lag pool maximums based on estimated lag
980 const float LAG_POOL_MIN = 5.0;
981 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
982 if(lag_pool_max < LAG_POOL_MIN)
983 lag_pool_max = LAG_POOL_MIN;
984 m_dig_pool.setMax(lag_pool_max);
985 m_move_pool.setMax(lag_pool_max);
987 // Increment cheat prevention timers
988 m_dig_pool.add(dtime);
989 m_move_pool.add(dtime);
990 m_time_from_last_teleport += dtime;
991 m_time_from_last_punch += dtime;
992 m_nocheat_dig_time += dtime;
994 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
995 // If the object gets detached this comes into effect automatically from the last known origin
997 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
998 m_last_good_position = pos;
999 setBasePosition(pos);
1002 if (!send_recommended)
1005 // If the object is attached client-side, don't waste bandwidth sending its position to clients
1006 if(m_position_not_sent && !isAttached())
1008 m_position_not_sent = false;
1009 float update_interval = m_env->getSendRecommendedInterval();
1011 if(isAttached()) // Just in case we ever do send attachment position too
1012 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1014 pos = m_base_position;
1015 std::string str = gob_cmd_update_position(
1024 // create message and add to list
1025 ActiveObjectMessage aom(getId(), false, str);
1026 m_messages_out.push(aom);
1029 if (!m_armor_groups_sent) {
1030 m_armor_groups_sent = true;
1031 std::string str = gob_cmd_update_armor_groups(
1033 // create message and add to list
1034 ActiveObjectMessage aom(getId(), true, str);
1035 m_messages_out.push(aom);
1038 if (!m_physics_override_sent) {
1039 m_physics_override_sent = true;
1040 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1041 m_physics_override_jump, m_physics_override_gravity,
1042 m_physics_override_sneak, m_physics_override_sneak_glitch,
1043 m_physics_override_new_move);
1044 // create message and add to list
1045 ActiveObjectMessage aom(getId(), true, str);
1046 m_messages_out.push(aom);
1049 if (!m_animation_sent) {
1050 m_animation_sent = true;
1051 std::string str = gob_cmd_update_animation(
1052 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1053 // create message and add to list
1054 ActiveObjectMessage aom(getId(), true, str);
1055 m_messages_out.push(aom);
1058 if (!m_bone_position_sent) {
1059 m_bone_position_sent = true;
1060 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1061 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1062 std::string str = gob_cmd_update_bone_position((*ii).first,
1063 (*ii).second.X, (*ii).second.Y);
1064 // create message and add to list
1065 ActiveObjectMessage aom(getId(), true, str);
1066 m_messages_out.push(aom);
1070 if (!m_attachment_sent){
1071 m_attachment_sent = true;
1072 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1073 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1074 // create message and add to list
1075 ActiveObjectMessage aom(getId(), true, str);
1076 m_messages_out.push(aom);
1080 void PlayerSAO::setBasePosition(const v3f &position)
1082 if (m_player && position != m_base_position)
1083 m_player->setDirty(true);
1085 // This needs to be ran for attachments too
1086 ServerActiveObject::setBasePosition(position);
1087 m_position_not_sent = true;
1090 void PlayerSAO::setPos(const v3f &pos)
1095 setBasePosition(pos);
1096 // Movement caused by this command is always valid
1097 m_last_good_position = pos;
1098 m_move_pool.empty();
1099 m_time_from_last_teleport = 0.0;
1100 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1103 void PlayerSAO::moveTo(v3f pos, bool continuous)
1108 setBasePosition(pos);
1109 // Movement caused by this command is always valid
1110 m_last_good_position = pos;
1111 m_move_pool.empty();
1112 m_time_from_last_teleport = 0.0;
1113 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1116 void PlayerSAO::setYaw(const float yaw)
1118 if (m_player && yaw != m_yaw)
1119 m_player->setDirty(true);
1121 UnitSAO::setYaw(yaw);
1124 void PlayerSAO::setFov(const float fov)
1126 if (m_player && fov != m_fov)
1127 m_player->setDirty(true);
1132 void PlayerSAO::setWantedRange(const s16 range)
1134 if (m_player && range != m_wanted_range)
1135 m_player->setDirty(true);
1137 m_wanted_range = range;
1140 void PlayerSAO::setYawAndSend(const float yaw)
1143 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1146 void PlayerSAO::setPitch(const float pitch)
1148 if (m_player && pitch != m_pitch)
1149 m_player->setDirty(true);
1154 void PlayerSAO::setPitchAndSend(const float pitch)
1157 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1160 int PlayerSAO::punch(v3f dir,
1161 const ToolCapabilities *toolcap,
1162 ServerActiveObject *puncher,
1163 float time_from_last_punch)
1165 // It's best that attachments cannot be punched
1172 // No effect if PvP disabled
1173 if (g_settings->getBool("enable_pvp") == false) {
1174 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1175 std::string str = gob_cmd_punched(0, getHP());
1176 // create message and add to list
1177 ActiveObjectMessage aom(getId(), true, str);
1178 m_messages_out.push(aom);
1183 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1184 time_from_last_punch);
1186 std::string punchername = "nil";
1189 punchername = puncher->getDescription();
1191 PlayerSAO *playersao = m_player->getPlayerSAO();
1193 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1194 puncher, time_from_last_punch, toolcap, dir,
1197 if (!damage_handled) {
1198 setHP(getHP() - hitparams.hp);
1199 } else { // override client prediction
1200 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1201 std::string str = gob_cmd_punched(0, getHP());
1202 // create message and add to list
1203 ActiveObjectMessage aom(getId(), true, str);
1204 m_messages_out.push(aom);
1209 actionstream << "Player " << m_player->getName() << " punched by "
1211 if (!damage_handled) {
1212 actionstream << ", damage " << hitparams.hp << " HP";
1214 actionstream << ", damage handled by lua";
1216 actionstream << std::endl;
1218 return hitparams.wear;
1221 s16 PlayerSAO::readDamage()
1223 s16 damage = m_damage;
1228 void PlayerSAO::setHP(s16 hp)
1232 s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp);
1235 hp = oldhp + hp_change;
1239 else if (hp > PLAYER_MAX_HP)
1242 if (hp < oldhp && !g_settings->getBool("enable_damage")) {
1249 m_damage += (oldhp - hp);
1251 // Update properties on death
1252 if ((hp == 0) != (oldhp == 0))
1253 m_properties_sent = false;
1256 void PlayerSAO::setBreath(const u16 breath, bool send)
1258 if (m_player && breath != m_breath)
1259 m_player->setDirty(true);
1261 m_breath = MYMIN(breath, PLAYER_MAX_BREATH);
1264 m_env->getGameDef()->SendPlayerBreath(this);
1267 Inventory* PlayerSAO::getInventory()
1271 const Inventory* PlayerSAO::getInventory() const
1276 InventoryLocation PlayerSAO::getInventoryLocation() const
1278 InventoryLocation loc;
1279 loc.setPlayer(m_player->getName());
1283 std::string PlayerSAO::getWieldList() const
1288 ItemStack PlayerSAO::getWieldedItem() const
1290 const Inventory *inv = getInventory();
1292 const InventoryList *mlist = inv->getList(getWieldList());
1293 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1294 ret = mlist->getItem(getWieldIndex());
1298 ItemStack PlayerSAO::getWieldedItemOrHand() const
1300 const Inventory *inv = getInventory();
1302 const InventoryList *mlist = inv->getList(getWieldList());
1303 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1304 ret = mlist->getItem(getWieldIndex());
1305 if (ret.name.empty()) {
1306 const InventoryList *hlist = inv->getList("hand");
1308 ret = hlist->getItem(0);
1313 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1315 Inventory *inv = getInventory();
1317 InventoryList *mlist = inv->getList(getWieldList());
1319 mlist->changeItem(getWieldIndex(), item);
1326 int PlayerSAO::getWieldIndex() const
1328 return m_wield_index;
1331 void PlayerSAO::setWieldIndex(int i)
1333 if(i != m_wield_index) {
1338 // Erase the peer id and make the object for removal
1339 void PlayerSAO::disconnected()
1345 void PlayerSAO::unlinkPlayerSessionAndSave()
1347 assert(m_player->getPlayerSAO() == this);
1348 m_player->peer_id = 0;
1349 m_env->savePlayer(m_player);
1350 m_player->setPlayerSAO(NULL);
1351 m_env->removePlayer(m_player);
1354 std::string PlayerSAO::getPropertyPacket()
1356 m_prop.is_visible = (true);
1357 return gob_cmd_set_properties(m_prop);
1360 bool PlayerSAO::checkMovementCheat()
1362 if (isAttached() || m_is_singleplayer ||
1363 g_settings->getBool("disable_anticheat")) {
1364 m_last_good_position = m_base_position;
1368 bool cheated = false;
1370 Check player movements
1372 NOTE: Actually the server should handle player physics like the
1373 client does and compare player's position to what is calculated
1374 on our side. This is required when eg. players fly due to an
1375 explosion. Altough a node-based alternative might be possible
1376 too, and much more lightweight.
1379 float player_max_speed = 0;
1381 if (m_privs.count("fast") != 0) {
1383 player_max_speed = m_player->movement_speed_fast * m_physics_override_speed;
1386 player_max_speed = m_player->movement_speed_walk * m_physics_override_speed;
1388 // Tolerance. The lag pool does this a bit.
1389 //player_max_speed *= 2.5;
1391 v3f diff = (m_base_position - m_last_good_position);
1392 float d_vert = diff.Y;
1394 float d_horiz = diff.getLength();
1395 float required_time = d_horiz / player_max_speed;
1397 if (d_vert > 0 && d_vert / player_max_speed > required_time)
1398 required_time = d_vert / player_max_speed; // Moving upwards
1400 if (m_move_pool.grab(required_time)) {
1401 m_last_good_position = m_base_position;
1403 const float LAG_POOL_MIN = 5.0;
1404 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1405 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1406 if (m_time_from_last_teleport > lag_pool_max) {
1407 actionstream << "Player " << m_player->getName()
1408 << " moved too fast; resetting position"
1412 setBasePosition(m_last_good_position);
1417 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1419 //update collision box
1420 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1421 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1423 toset->MinEdge += m_base_position;
1424 toset->MaxEdge += m_base_position;
1428 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1430 if (!m_prop.is_visible) {
1434 getCollisionBox(toset);