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)
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 (u32 attached_particle_spawner : m_attached_particle_spawners) {
249 m_env->deleteParticleSpawner(attached_particle_spawner, false);
253 void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
255 ServerActiveObject::addedToEnvironment(dtime_s);
257 // Create entity from name
258 m_registered = m_env->getScriptIface()->
259 luaentity_Add(m_id, m_init_name.c_str());
263 m_env->getScriptIface()->
264 luaentity_GetProperties(m_id, &m_prop);
265 // Initialize HP from properties
266 m_hp = m_prop.hp_max;
267 // Activate entity, supplying serialized state
268 m_env->getScriptIface()->
269 luaentity_Activate(m_id, m_init_state, dtime_s);
271 m_prop.infotext = m_init_name;
275 ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
276 const std::string &data)
284 std::istringstream is(data, std::ios::binary);
286 u8 version = readU8(is);
287 // check if version is supported
289 name = deSerializeString(is);
290 state = deSerializeLongString(is);
292 else if(version == 1){
293 name = deSerializeString(is);
294 state = deSerializeLongString(is);
296 velocity = readV3F1000(is);
301 infostream<<"LuaEntitySAO::create(name=\""<<name<<"\" state=\""
302 <<state<<"\")"<<std::endl;
303 LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
305 sao->m_velocity = velocity;
310 void LuaEntitySAO::step(float dtime, bool send_recommended)
312 if(!m_properties_sent)
314 m_properties_sent = true;
315 std::string str = getPropertyPacket();
316 // create message and add to list
317 ActiveObjectMessage aom(getId(), true, str);
318 m_messages_out.push(aom);
321 // If attached, check that our parent is still there. If it isn't, detach.
322 if(m_attachment_parent_id && !isAttached())
324 m_attachment_parent_id = 0;
325 m_attachment_bone = "";
326 m_attachment_position = v3f(0,0,0);
327 m_attachment_rotation = v3f(0,0,0);
328 sendPosition(false, true);
331 m_last_sent_position_timer += dtime;
333 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
334 // If the object gets detached this comes into effect automatically from the last known origin
337 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
338 m_base_position = pos;
339 m_velocity = v3f(0,0,0);
340 m_acceleration = v3f(0,0,0);
345 aabb3f box = m_prop.collisionbox;
348 collisionMoveResult moveresult;
349 f32 pos_max_d = BS*0.25; // Distance per iteration
350 v3f p_pos = m_base_position;
351 v3f p_velocity = m_velocity;
352 v3f p_acceleration = m_acceleration;
353 moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
354 pos_max_d, box, m_prop.stepheight, dtime,
355 &p_pos, &p_velocity, p_acceleration,
356 this, m_prop.collideWithObjects);
359 m_base_position = p_pos;
360 m_velocity = p_velocity;
361 m_acceleration = p_acceleration;
363 m_base_position += dtime * m_velocity + 0.5 * dtime
364 * dtime * m_acceleration;
365 m_velocity += dtime * m_acceleration;
368 if((m_prop.automatic_face_movement_dir) &&
369 (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001))
371 float optimal_yaw = atan2(m_velocity.Z,m_velocity.X) * 180 / M_PI
372 + m_prop.automatic_face_movement_dir_offset;
373 float max_rotation_delta =
374 dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
376 if ((m_prop.automatic_face_movement_max_rotation_per_sec > 0) &&
377 (fabs(m_yaw - optimal_yaw) > max_rotation_delta)) {
379 m_yaw = optimal_yaw < m_yaw ? m_yaw - max_rotation_delta : m_yaw + max_rotation_delta;
387 m_env->getScriptIface()->luaentity_Step(m_id, dtime);
390 // Remove LuaEntity beyond terrain edges
392 ServerMap *map = dynamic_cast<ServerMap *>(&m_env->getMap());
394 if (!m_pending_deactivation &&
395 map->saoPositionOverLimit(m_base_position)) {
396 infostream << "Remove SAO " << m_id << "(" << m_init_name
397 << "), outside of limits" << std::endl;
398 m_pending_deactivation = true;
404 if (!send_recommended)
409 // TODO: force send when acceleration changes enough?
410 float minchange = 0.2*BS;
411 if(m_last_sent_position_timer > 1.0){
413 } else if(m_last_sent_position_timer > 0.2){
416 float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
417 move_d += m_last_sent_move_precision;
418 float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
419 if(move_d > minchange || vel_d > minchange ||
420 fabs(m_yaw - m_last_sent_yaw) > 1.0){
421 sendPosition(true, false);
425 if (!m_armor_groups_sent) {
426 m_armor_groups_sent = true;
427 std::string str = gob_cmd_update_armor_groups(
429 // create message and add to list
430 ActiveObjectMessage aom(getId(), true, str);
431 m_messages_out.push(aom);
434 if (!m_animation_sent) {
435 m_animation_sent = true;
436 std::string str = gob_cmd_update_animation(
437 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
438 // create message and add to list
439 ActiveObjectMessage aom(getId(), true, str);
440 m_messages_out.push(aom);
443 if (!m_bone_position_sent) {
444 m_bone_position_sent = true;
445 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
446 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
447 std::string str = gob_cmd_update_bone_position((*ii).first,
448 (*ii).second.X, (*ii).second.Y);
449 // create message and add to list
450 ActiveObjectMessage aom(getId(), true, str);
451 m_messages_out.push(aom);
455 if (!m_attachment_sent) {
456 m_attachment_sent = true;
457 std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
458 // create message and add to list
459 ActiveObjectMessage aom(getId(), true, str);
460 m_messages_out.push(aom);
464 std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
466 std::ostringstream os(std::ios::binary);
469 writeU8(os, 1); // version
470 os << serializeString(""); // name
471 writeU8(os, 0); // is_player
472 writeS16(os, getId()); //id
473 writeV3F1000(os, m_base_position);
474 writeF1000(os, m_yaw);
477 std::ostringstream msg_os(std::ios::binary);
478 msg_os << serializeLongString(getPropertyPacket()); // message 1
479 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
480 msg_os << serializeLongString(gob_cmd_update_animation(
481 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
482 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
483 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
484 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
485 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
487 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
488 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
489 int message_count = 4 + m_bone_position.size();
490 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
491 (ii != m_attachment_child_ids.end()); ++ii) {
492 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
494 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
495 obj->getClientInitializationData(protocol_version)));
499 msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
502 writeU8(os, message_count);
503 os.write(msg_os.str().c_str(), msg_os.str().size());
509 void LuaEntitySAO::getStaticData(std::string *result) const
511 verbosestream<<FUNCTION_NAME<<std::endl;
512 std::ostringstream os(std::ios::binary);
516 os<<serializeString(m_init_name);
519 std::string state = m_env->getScriptIface()->
520 luaentity_GetStaticdata(m_id);
521 os<<serializeLongString(state);
523 os<<serializeLongString(m_init_state);
528 writeV3F1000(os, m_velocity);
530 writeF1000(os, m_yaw);
534 int LuaEntitySAO::punch(v3f dir,
535 const ToolCapabilities *toolcap,
536 ServerActiveObject *puncher,
537 float time_from_last_punch)
540 // Delete unknown LuaEntities when punched
545 // It's best that attachments cannot be punched
549 ItemStack *punchitem = NULL;
550 ItemStack punchitem_static;
552 punchitem_static = puncher->getWieldedItem();
553 punchitem = &punchitem_static;
556 PunchDamageResult result = getPunchDamage(
560 time_from_last_punch);
562 bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
563 time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
565 if (!damage_handled) {
566 if (result.did_punch) {
567 setHP(getHP() - result.damage);
569 if (result.damage > 0) {
570 std::string punchername = puncher ? puncher->getDescription() : "nil";
572 actionstream << getDescription() << " punched by "
573 << punchername << ", damage " << result.damage
574 << " hp, health now " << getHP() << " hp" << std::endl;
577 std::string str = gob_cmd_punched(result.damage, getHP());
578 // create message and add to list
579 ActiveObjectMessage aom(getId(), true, str);
580 m_messages_out.push(aom);
586 m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
594 void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
598 // It's best that attachments cannot be clicked
601 m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
604 void LuaEntitySAO::setPos(const v3f &pos)
608 m_base_position = pos;
609 sendPosition(false, true);
612 void LuaEntitySAO::moveTo(v3f pos, bool continuous)
616 m_base_position = pos;
618 sendPosition(true, true);
621 float LuaEntitySAO::getMinimumSavedMovement()
626 std::string LuaEntitySAO::getDescription()
628 std::ostringstream os(std::ios::binary);
629 os<<"LuaEntitySAO at (";
630 os<<(m_base_position.X/BS)<<",";
631 os<<(m_base_position.Y/BS)<<",";
632 os<<(m_base_position.Z/BS);
637 void LuaEntitySAO::setHP(s16 hp)
643 s16 LuaEntitySAO::getHP() const
648 void LuaEntitySAO::setVelocity(v3f velocity)
650 m_velocity = velocity;
653 v3f LuaEntitySAO::getVelocity()
658 void LuaEntitySAO::setAcceleration(v3f acceleration)
660 m_acceleration = acceleration;
663 v3f LuaEntitySAO::getAcceleration()
665 return m_acceleration;
668 void LuaEntitySAO::setTextureMod(const std::string &mod)
670 std::string str = gob_cmd_set_texture_mod(mod);
671 m_current_texture_modifier = mod;
672 // create message and add to list
673 ActiveObjectMessage aom(getId(), true, str);
674 m_messages_out.push(aom);
677 std::string LuaEntitySAO::getTextureMod() const
679 return m_current_texture_modifier;
682 void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
683 bool select_horiz_by_yawpitch)
685 std::string str = gob_cmd_set_sprite(
689 select_horiz_by_yawpitch
691 // create message and add to list
692 ActiveObjectMessage aom(getId(), true, str);
693 m_messages_out.push(aom);
696 std::string LuaEntitySAO::getName()
701 std::string LuaEntitySAO::getPropertyPacket()
703 return gob_cmd_set_properties(m_prop);
706 void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
708 // If the object is attached client-side, don't waste bandwidth sending its position to clients
712 m_last_sent_move_precision = m_base_position.getDistanceFrom(
713 m_last_sent_position);
714 m_last_sent_position_timer = 0;
715 m_last_sent_yaw = m_yaw;
716 m_last_sent_position = m_base_position;
717 m_last_sent_velocity = m_velocity;
718 //m_last_sent_acceleration = m_acceleration;
720 float update_interval = m_env->getSendRecommendedInterval();
722 std::string str = gob_cmd_update_position(
731 // create message and add to list
732 ActiveObjectMessage aom(getId(), false, str);
733 m_messages_out.push(aom);
736 bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
740 //update collision box
741 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
742 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
744 toset->MinEdge += m_base_position;
745 toset->MaxEdge += m_base_position;
753 bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
755 if (!m_prop.is_visible || !m_prop.pointable) {
759 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
760 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
765 bool LuaEntitySAO::collideWithObjects() const
767 return m_prop.collideWithObjects;
774 // No prototype, PlayerSAO does not need to be deserialized
776 PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id_,
777 bool is_singleplayer):
778 UnitSAO(env_, v3f(0,0,0)),
781 m_is_singleplayer(is_singleplayer)
783 assert(m_peer_id != 0); // pre-condition
785 m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
786 m_prop.physical = false;
788 m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
789 m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
790 m_prop.pointable = true;
791 // start of default appearance, this should be overwritten by LUA
792 m_prop.visual = "upright_sprite";
793 m_prop.visual_size = v2f(1, 2);
794 m_prop.textures.clear();
795 m_prop.textures.emplace_back("player.png");
796 m_prop.textures.emplace_back("player_back.png");
797 m_prop.colors.clear();
798 m_prop.colors.emplace_back(255, 255, 255, 255);
799 m_prop.spritediv = v2s16(1,1);
800 // end of default appearance
801 m_prop.is_visible = true;
802 m_prop.makes_footstep_sound = true;
803 m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
804 m_hp = m_prop.hp_max;
807 PlayerSAO::~PlayerSAO()
809 if(m_inventory != &m_player->inventory)
813 void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
818 m_inventory = &m_player->inventory;
821 v3f PlayerSAO::getEyeOffset() const
823 return v3f(0, BS * 1.625f, 0);
826 std::string PlayerSAO::getDescription()
828 return std::string("player ") + m_player->getName();
831 // Called after id has been set and has been inserted in environment
832 void PlayerSAO::addedToEnvironment(u32 dtime_s)
834 ServerActiveObject::addedToEnvironment(dtime_s);
835 ServerActiveObject::setBasePosition(m_base_position);
836 m_player->setPlayerSAO(this);
837 m_player->peer_id = m_peer_id;
838 m_last_good_position = m_base_position;
841 // Called before removing from environment
842 void PlayerSAO::removingFromEnvironment()
844 ServerActiveObject::removingFromEnvironment();
845 if (m_player->getPlayerSAO() == this) {
846 unlinkPlayerSessionAndSave();
847 for (u32 attached_particle_spawner : m_attached_particle_spawners) {
848 m_env->deleteParticleSpawner(attached_particle_spawner, false);
853 std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
855 std::ostringstream os(std::ios::binary);
858 writeU8(os, 1); // version
859 os << serializeString(m_player->getName()); // name
860 writeU8(os, 1); // is_player
861 writeS16(os, getId()); //id
862 writeV3F1000(os, m_base_position);
863 writeF1000(os, m_yaw);
864 writeS16(os, getHP());
866 std::ostringstream msg_os(std::ios::binary);
867 msg_os << serializeLongString(getPropertyPacket()); // message 1
868 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
869 msg_os << serializeLongString(gob_cmd_update_animation(
870 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
871 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
872 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
873 msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
874 (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
876 msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
877 m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
878 msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
879 m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
880 m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
881 // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
882 msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
883 int message_count = 6 + m_bone_position.size();
884 for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
885 ii != m_attachment_child_ids.end(); ++ii) {
886 if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
888 msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
889 obj->getClientInitializationData(protocol_version)));
893 writeU8(os, message_count);
894 os.write(msg_os.str().c_str(), msg_os.str().size());
900 void PlayerSAO::getStaticData(std::string * result) const
902 FATAL_ERROR("Deprecated function");
905 void PlayerSAO::step(float dtime, bool send_recommended)
907 if (m_drowning_interval.step(dtime, 2.0)) {
909 v3s16 p = floatToInt(m_base_position + v3f(0, BS * 1.6, 0), BS);
910 MapNode n = m_env->getMap().getNodeNoEx(p);
911 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
912 // If node generates drown
913 if (c.drowning > 0 && m_hp > 0) {
915 setBreath(m_breath - 1);
917 // No more breath, damage player
919 setHP(m_hp - c.drowning);
920 m_env->getGameDef()->SendPlayerHPOrDie(this);
925 if (m_breathing_interval.step(dtime, 0.5)) {
927 v3s16 p = floatToInt(m_base_position + v3f(0, BS * 1.6, 0), BS);
928 MapNode n = m_env->getMap().getNodeNoEx(p);
929 const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
930 // If player is alive & no drowning, breath
931 if (m_hp > 0 && m_breath < PLAYER_MAX_BREATH && c.drowning == 0)
932 setBreath(m_breath + 1);
935 if (m_node_hurt_interval.step(dtime, 1.0)) {
936 // Feet, middle and head
937 v3s16 p1 = floatToInt(m_base_position + v3f(0, BS*0.1, 0), BS);
938 MapNode n1 = m_env->getMap().getNodeNoEx(p1);
939 v3s16 p2 = floatToInt(m_base_position + v3f(0, BS*0.8, 0), BS);
940 MapNode n2 = m_env->getMap().getNodeNoEx(p2);
941 v3s16 p3 = floatToInt(m_base_position + v3f(0, BS*1.6, 0), BS);
942 MapNode n3 = m_env->getMap().getNodeNoEx(p3);
944 u32 damage_per_second = 0;
945 damage_per_second = MYMAX(damage_per_second,
946 m_env->getGameDef()->ndef()->get(n1).damage_per_second);
947 damage_per_second = MYMAX(damage_per_second,
948 m_env->getGameDef()->ndef()->get(n2).damage_per_second);
949 damage_per_second = MYMAX(damage_per_second,
950 m_env->getGameDef()->ndef()->get(n3).damage_per_second);
952 if (damage_per_second != 0 && m_hp > 0) {
953 s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second);
955 m_env->getGameDef()->SendPlayerHPOrDie(this);
959 if (!m_properties_sent) {
960 m_properties_sent = true;
961 std::string str = getPropertyPacket();
962 // create message and add to list
963 ActiveObjectMessage aom(getId(), true, str);
964 m_messages_out.push(aom);
967 // If attached, check that our parent is still there. If it isn't, detach.
968 if(m_attachment_parent_id && !isAttached())
970 m_attachment_parent_id = 0;
971 m_attachment_bone = "";
972 m_attachment_position = v3f(0,0,0);
973 m_attachment_rotation = v3f(0,0,0);
974 setBasePosition(m_last_good_position);
975 m_env->getGameDef()->SendMovePlayer(m_peer_id);
978 //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
980 // Set lag pool maximums based on estimated lag
981 const float LAG_POOL_MIN = 5.0;
982 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
983 if(lag_pool_max < LAG_POOL_MIN)
984 lag_pool_max = LAG_POOL_MIN;
985 m_dig_pool.setMax(lag_pool_max);
986 m_move_pool.setMax(lag_pool_max);
988 // Increment cheat prevention timers
989 m_dig_pool.add(dtime);
990 m_move_pool.add(dtime);
991 m_time_from_last_teleport += dtime;
992 m_time_from_last_punch += dtime;
993 m_nocheat_dig_time += dtime;
995 // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
996 // If the object gets detached this comes into effect automatically from the last known origin
998 v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
999 m_last_good_position = pos;
1000 setBasePosition(pos);
1003 if (!send_recommended)
1006 // If the object is attached client-side, don't waste bandwidth sending its position to clients
1007 if(m_position_not_sent && !isAttached())
1009 m_position_not_sent = false;
1010 float update_interval = m_env->getSendRecommendedInterval();
1012 if(isAttached()) // Just in case we ever do send attachment position too
1013 pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
1015 pos = m_base_position;
1016 std::string str = gob_cmd_update_position(
1025 // create message and add to list
1026 ActiveObjectMessage aom(getId(), false, str);
1027 m_messages_out.push(aom);
1030 if (!m_armor_groups_sent) {
1031 m_armor_groups_sent = true;
1032 std::string str = gob_cmd_update_armor_groups(
1034 // create message and add to list
1035 ActiveObjectMessage aom(getId(), true, str);
1036 m_messages_out.push(aom);
1039 if (!m_physics_override_sent) {
1040 m_physics_override_sent = true;
1041 std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
1042 m_physics_override_jump, m_physics_override_gravity,
1043 m_physics_override_sneak, m_physics_override_sneak_glitch,
1044 m_physics_override_new_move);
1045 // create message and add to list
1046 ActiveObjectMessage aom(getId(), true, str);
1047 m_messages_out.push(aom);
1050 if (!m_animation_sent) {
1051 m_animation_sent = true;
1052 std::string str = gob_cmd_update_animation(
1053 m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
1054 // create message and add to list
1055 ActiveObjectMessage aom(getId(), true, str);
1056 m_messages_out.push(aom);
1059 if (!m_bone_position_sent) {
1060 m_bone_position_sent = true;
1061 for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
1062 ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
1063 std::string str = gob_cmd_update_bone_position((*ii).first,
1064 (*ii).second.X, (*ii).second.Y);
1065 // create message and add to list
1066 ActiveObjectMessage aom(getId(), true, str);
1067 m_messages_out.push(aom);
1071 if (!m_attachment_sent){
1072 m_attachment_sent = true;
1073 std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
1074 m_attachment_bone, m_attachment_position, m_attachment_rotation);
1075 // create message and add to list
1076 ActiveObjectMessage aom(getId(), true, str);
1077 m_messages_out.push(aom);
1081 void PlayerSAO::setBasePosition(const v3f &position)
1083 if (m_player && position != m_base_position)
1084 m_player->setDirty(true);
1086 // This needs to be ran for attachments too
1087 ServerActiveObject::setBasePosition(position);
1088 m_position_not_sent = true;
1091 void PlayerSAO::setPos(const v3f &pos)
1096 setBasePosition(pos);
1097 // Movement caused by this command is always valid
1098 m_last_good_position = pos;
1099 m_move_pool.empty();
1100 m_time_from_last_teleport = 0.0;
1101 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1104 void PlayerSAO::moveTo(v3f pos, bool continuous)
1109 setBasePosition(pos);
1110 // Movement caused by this command is always valid
1111 m_last_good_position = pos;
1112 m_move_pool.empty();
1113 m_time_from_last_teleport = 0.0;
1114 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1117 void PlayerSAO::setYaw(const float yaw)
1119 if (m_player && yaw != m_yaw)
1120 m_player->setDirty(true);
1122 UnitSAO::setYaw(yaw);
1125 void PlayerSAO::setFov(const float fov)
1127 if (m_player && fov != m_fov)
1128 m_player->setDirty(true);
1133 void PlayerSAO::setWantedRange(const s16 range)
1135 if (m_player && range != m_wanted_range)
1136 m_player->setDirty(true);
1138 m_wanted_range = range;
1141 void PlayerSAO::setYawAndSend(const float yaw)
1144 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1147 void PlayerSAO::setPitch(const float pitch)
1149 if (m_player && pitch != m_pitch)
1150 m_player->setDirty(true);
1155 void PlayerSAO::setPitchAndSend(const float pitch)
1158 m_env->getGameDef()->SendMovePlayer(m_peer_id);
1161 int PlayerSAO::punch(v3f dir,
1162 const ToolCapabilities *toolcap,
1163 ServerActiveObject *puncher,
1164 float time_from_last_punch)
1166 // It's best that attachments cannot be punched
1173 // No effect if PvP disabled
1174 if (!g_settings->getBool("enable_pvp")) {
1175 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1176 std::string str = gob_cmd_punched(0, getHP());
1177 // create message and add to list
1178 ActiveObjectMessage aom(getId(), true, str);
1179 m_messages_out.push(aom);
1184 HitParams hitparams = getHitParams(m_armor_groups, toolcap,
1185 time_from_last_punch);
1187 std::string punchername = "nil";
1190 punchername = puncher->getDescription();
1192 PlayerSAO *playersao = m_player->getPlayerSAO();
1194 bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
1195 puncher, time_from_last_punch, toolcap, dir,
1198 if (!damage_handled) {
1199 setHP(getHP() - hitparams.hp);
1200 } else { // override client prediction
1201 if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
1202 std::string str = gob_cmd_punched(0, getHP());
1203 // create message and add to list
1204 ActiveObjectMessage aom(getId(), true, str);
1205 m_messages_out.push(aom);
1210 actionstream << "Player " << m_player->getName() << " punched by "
1212 if (!damage_handled) {
1213 actionstream << ", damage " << hitparams.hp << " HP";
1215 actionstream << ", damage handled by lua";
1217 actionstream << std::endl;
1219 return hitparams.wear;
1222 s16 PlayerSAO::readDamage()
1224 s16 damage = m_damage;
1229 void PlayerSAO::setHP(s16 hp)
1233 s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp);
1236 hp = oldhp + hp_change;
1240 else if (hp > m_prop.hp_max)
1243 if (hp < oldhp && !g_settings->getBool("enable_damage")) {
1250 m_damage += (oldhp - hp);
1252 // Update properties on death
1253 if ((hp == 0) != (oldhp == 0))
1254 m_properties_sent = false;
1257 void PlayerSAO::setBreath(const u16 breath, bool send)
1259 if (m_player && breath != m_breath)
1260 m_player->setDirty(true);
1262 m_breath = MYMIN(breath, PLAYER_MAX_BREATH);
1265 m_env->getGameDef()->SendPlayerBreath(this);
1268 Inventory* PlayerSAO::getInventory()
1272 const Inventory* PlayerSAO::getInventory() const
1277 InventoryLocation PlayerSAO::getInventoryLocation() const
1279 InventoryLocation loc;
1280 loc.setPlayer(m_player->getName());
1284 std::string PlayerSAO::getWieldList() const
1289 ItemStack PlayerSAO::getWieldedItem() const
1291 const Inventory *inv = getInventory();
1293 const InventoryList *mlist = inv->getList(getWieldList());
1294 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1295 ret = mlist->getItem(getWieldIndex());
1299 ItemStack PlayerSAO::getWieldedItemOrHand() const
1301 const Inventory *inv = getInventory();
1303 const InventoryList *mlist = inv->getList(getWieldList());
1304 if (mlist && getWieldIndex() < (s32)mlist->getSize())
1305 ret = mlist->getItem(getWieldIndex());
1306 if (ret.name.empty()) {
1307 const InventoryList *hlist = inv->getList("hand");
1309 ret = hlist->getItem(0);
1314 bool PlayerSAO::setWieldedItem(const ItemStack &item)
1316 Inventory *inv = getInventory();
1318 InventoryList *mlist = inv->getList(getWieldList());
1320 mlist->changeItem(getWieldIndex(), item);
1327 int PlayerSAO::getWieldIndex() const
1329 return m_wield_index;
1332 void PlayerSAO::setWieldIndex(int i)
1334 if(i != m_wield_index) {
1339 // Erase the peer id and make the object for removal
1340 void PlayerSAO::disconnected()
1346 void PlayerSAO::unlinkPlayerSessionAndSave()
1348 assert(m_player->getPlayerSAO() == this);
1349 m_player->peer_id = 0;
1350 m_env->savePlayer(m_player);
1351 m_player->setPlayerSAO(NULL);
1352 m_env->removePlayer(m_player);
1355 std::string PlayerSAO::getPropertyPacket()
1357 m_prop.is_visible = (true);
1358 return gob_cmd_set_properties(m_prop);
1361 bool PlayerSAO::checkMovementCheat()
1363 if (isAttached() || m_is_singleplayer ||
1364 g_settings->getBool("disable_anticheat")) {
1365 m_last_good_position = m_base_position;
1369 bool cheated = false;
1371 Check player movements
1373 NOTE: Actually the server should handle player physics like the
1374 client does and compare player's position to what is calculated
1375 on our side. This is required when eg. players fly due to an
1376 explosion. Altough a node-based alternative might be possible
1377 too, and much more lightweight.
1380 float player_max_speed = 0;
1382 if (m_privs.count("fast") != 0) {
1384 player_max_speed = m_player->movement_speed_fast * m_physics_override_speed;
1387 player_max_speed = m_player->movement_speed_walk * m_physics_override_speed;
1389 // Tolerance. The lag pool does this a bit.
1390 //player_max_speed *= 2.5;
1392 v3f diff = (m_base_position - m_last_good_position);
1393 float d_vert = diff.Y;
1395 float d_horiz = diff.getLength();
1396 float required_time = d_horiz / player_max_speed;
1398 if (d_vert > 0 && d_vert / player_max_speed > required_time)
1399 required_time = d_vert / player_max_speed; // Moving upwards
1401 if (m_move_pool.grab(required_time)) {
1402 m_last_good_position = m_base_position;
1404 const float LAG_POOL_MIN = 5.0;
1405 float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
1406 lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
1407 if (m_time_from_last_teleport > lag_pool_max) {
1408 actionstream << "Player " << m_player->getName()
1409 << " moved too fast; resetting position"
1413 setBasePosition(m_last_good_position);
1418 bool PlayerSAO::getCollisionBox(aabb3f *toset) const
1420 //update collision box
1421 toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
1422 toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
1424 toset->MinEdge += m_base_position;
1425 toset->MaxEdge += m_base_position;
1429 bool PlayerSAO::getSelectionBox(aabb3f *toset) const
1431 if (!m_prop.is_visible || !m_prop.pointable) {
1435 toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
1436 toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;