X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fcontent_sao.cpp;h=1fb74263ba4b4cb701b3ed5a2bccfcd652832e8b;hb=2153965cf92ab61b0d7e095cf3c2755924fdc221;hp=e58aa4b6e6ea909a350de0fbde11dd3fc9c87c13;hpb=ced6d20295a8263757d57c02a07ffcb66688a163;p=minetest.git diff --git a/src/content_sao.cpp b/src/content_sao.cpp index e58aa4b6e..1fb74263b 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -19,20 +19,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_sao.h" #include "util/serialize.h" -#include "util/mathconstants.h" #include "collision.h" #include "environment.h" -#include "settings.h" -#include "main.h" // For g_profiler -#include "profiler.h" -#include "serialization.h" // For compressZlib #include "tool.h" // For ToolCapabilities #include "gamedef.h" -#include "player.h" +#include "nodedef.h" +#include "remoteplayer.h" #include "server.h" -#include "scripting_game.h" +#include "scripting_server.h" #include "genericobject.h" -#include "log.h" +#include "settings.h" +#include std::map ServerActiveObject::m_types; @@ -64,7 +61,7 @@ class TestSAO : public ServerActiveObject m_age += dtime; if(m_age > 10) { - m_removed = true; + m_pending_removal = true; return; } @@ -72,7 +69,7 @@ class TestSAO : public ServerActiveObject if(m_base_position.Y > 8*BS) m_base_position.Y = 2*BS; - if(send_recommended == false) + if (!send_recommended) return; m_timer1 -= dtime; @@ -95,13 +92,11 @@ class TestSAO : public ServerActiveObject } } - bool getCollisionBox(aabb3f *toset) { - return false; - } + bool getCollisionBox(aabb3f *toset) const { return false; } - bool collideWithObjects() { - return false; - } + virtual bool getSelectionBox(aabb3f *toset) const { return false; } + + bool collideWithObjects() const { return false; } private: float m_timer1; @@ -111,6 +106,127 @@ class TestSAO : public ServerActiveObject // Prototype (registers item for deserialization) TestSAO proto_TestSAO(NULL, v3f(0,0,0)); +/* + UnitSAO + */ + +UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos): + ServerActiveObject(env, pos) +{ + // Initialize something to armor groups + m_armor_groups["fleshy"] = 100; +} + +bool UnitSAO::isAttached() const +{ + if (!m_attachment_parent_id) + return false; + // Check if the parent still exists + ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id); + if (obj) + return true; + return false; +} + +void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups) +{ + m_armor_groups = armor_groups; + m_armor_groups_sent = false; +} + +const ItemGroupList &UnitSAO::getArmorGroups() +{ + return m_armor_groups; +} + +void UnitSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop) +{ + // store these so they can be updated to clients + m_animation_range = frame_range; + m_animation_speed = frame_speed; + m_animation_blend = frame_blend; + m_animation_loop = frame_loop; + m_animation_sent = false; +} + +void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop) +{ + *frame_range = m_animation_range; + *frame_speed = m_animation_speed; + *frame_blend = m_animation_blend; + *frame_loop = m_animation_loop; +} + +void UnitSAO::setAnimationSpeed(float frame_speed) +{ + m_animation_speed = frame_speed; + m_animation_speed_sent = false; +} + +void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation) +{ + // store these so they can be updated to clients + m_bone_position[bone] = core::vector2d(position, rotation); + m_bone_position_sent = false; +} + +void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation) +{ + *position = m_bone_position[bone].X; + *rotation = m_bone_position[bone].Y; +} + +void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation) +{ + // Attachments need to be handled on both the server and client. + // If we just attach on the server, we can only copy the position of the parent. Attachments + // are still sent to clients at an interval so players might see them lagging, plus we can't + // read and attach to skeletal bones. + // If we just attach on the client, the server still sees the child at its original location. + // This breaks some things so we also give the server the most accurate representation + // even if players only see the client changes. + + m_attachment_parent_id = parent_id; + m_attachment_bone = bone; + m_attachment_position = position; + m_attachment_rotation = rotation; + m_attachment_sent = false; +} + +void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position, + v3f *rotation) +{ + *parent_id = m_attachment_parent_id; + *bone = m_attachment_bone; + *position = m_attachment_position; + *rotation = m_attachment_rotation; +} + +void UnitSAO::addAttachmentChild(int child_id) +{ + m_attachment_child_ids.insert(child_id); +} + +void UnitSAO::removeAttachmentChild(int child_id) +{ + m_attachment_child_ids.erase(child_id); +} + +const std::unordered_set &UnitSAO::getAttachmentChildIds() +{ + return m_attachment_child_ids; +} + +ObjectProperties* UnitSAO::accessObjectProperties() +{ + return &m_prop; +} + +void UnitSAO::notifyObjectPropertiesModified() +{ + m_properties_sent = false; +} + /* LuaEntitySAO */ @@ -120,36 +236,15 @@ LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", ""); LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name, const std::string &state): - ServerActiveObject(env, pos), + UnitSAO(env, pos), m_init_name(name), - m_init_state(state), - m_registered(false), - m_hp(-1), - m_velocity(0,0,0), - m_acceleration(0,0,0), - m_yaw(0), - m_properties_sent(true), - m_last_sent_yaw(0), - m_last_sent_position(0,0,0), - m_last_sent_velocity(0,0,0), - m_last_sent_position_timer(0), - m_last_sent_move_precision(0), - m_armor_groups_sent(false), - m_animation_speed(0), - m_animation_blend(0), - m_animation_sent(false), - m_bone_position_sent(false), - m_attachment_parent_id(0), - m_attachment_sent(false) + m_init_state(state) { // Only register type if no environment supplied if(env == NULL){ ServerActiveObject::registerType(getType(), create); return; } - - // Initialize something to armor groups - m_armor_groups["fleshy"] = 100; } LuaEntitySAO::~LuaEntitySAO() @@ -157,6 +252,10 @@ LuaEntitySAO::~LuaEntitySAO() if(m_registered){ m_env->getScriptIface()->luaentity_Remove(m_id); } + + for (u32 attached_particle_spawner : m_attached_particle_spawners) { + m_env->deleteParticleSpawner(attached_particle_spawner, false); + } } void LuaEntitySAO::addedToEnvironment(u32 dtime_s) @@ -175,7 +274,9 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s) m_hp = m_prop.hp_max; // Activate entity, supplying serialized state m_env->getScriptIface()-> - luaentity_Activate(m_id, m_init_state.c_str(), dtime_s); + luaentity_Activate(m_id, m_init_state, dtime_s); + } else { + m_prop.infotext = m_init_name; } } @@ -187,7 +288,7 @@ ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos, s16 hp = 1; v3f velocity; float yaw = 0; - if(data != ""){ + if (!data.empty()) { std::istringstream is(data, std::ios::binary); // read version u8 version = readU8(is); @@ -214,17 +315,6 @@ ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos, return sao; } -bool LuaEntitySAO::isAttached() -{ - if(!m_attachment_parent_id) - return false; - // Check if the parent still exists - ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id); - if(obj) - return true; - return false; -} - void LuaEntitySAO::step(float dtime, bool send_recommended) { if(!m_properties_sent) @@ -260,7 +350,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) else { if(m_prop.physical){ - core::aabbox3d box = m_prop.collisionbox; + aabb3f box = m_prop.collisionbox; box.MinEdge *= BS; box.MaxEdge *= BS; collisionMoveResult moveresult; @@ -268,9 +358,9 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) v3f p_pos = m_base_position; v3f p_velocity = m_velocity; v3f p_acceleration = m_acceleration; - moveresult = collisionMoveSimple(m_env,m_env->getGameDef(), + moveresult = collisionMoveSimple(m_env, m_env->getGameDef(), pos_max_d, box, m_prop.stepheight, dtime, - p_pos, p_velocity, p_acceleration, + &p_pos, &p_velocity, p_acceleration, this, m_prop.collideWithObjects); // Apply results @@ -283,9 +373,21 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) m_velocity += dtime * m_acceleration; } - if((m_prop.automatic_face_movement_dir) && - (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)){ - m_yaw = atan2(m_velocity.Z,m_velocity.X) * 180 / M_PI + m_prop.automatic_face_movement_dir_offset; + if (m_prop.automatic_face_movement_dir && + (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) { + + float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI + + m_prop.automatic_face_movement_dir_offset; + float max_rotation_delta = + dtime * m_prop.automatic_face_movement_max_rotation_per_sec; + float delta = wrapDegrees_0_360(target_yaw - m_yaw); + + if (delta > max_rotation_delta && 360 - delta > max_rotation_delta) { + m_yaw += (delta < 180) ? max_rotation_delta : -max_rotation_delta; + m_yaw = wrapDegrees_0_360(m_yaw); + } else { + m_yaw = target_yaw; + } } } @@ -293,7 +395,20 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) m_env->getScriptIface()->luaentity_Step(m_id, dtime); } - if(send_recommended == false) + // Remove LuaEntity beyond terrain edges + { + ServerMap *map = dynamic_cast(&m_env->getMap()); + assert(map); + if (!m_pending_removal && + map->saoPositionOverLimit(m_base_position)) { + infostream << "Removing SAO " << m_id << "(" << m_init_name + << "), outside of limits" << std::endl; + m_pending_removal = true; + return; + } + } + + if (!send_recommended) return; if(!isAttached()) @@ -314,7 +429,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) } } - if(m_armor_groups_sent == false){ + if (!m_armor_groups_sent) { m_armor_groups_sent = true; std::string str = gob_cmd_update_armor_groups( m_armor_groups); @@ -323,25 +438,36 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) m_messages_out.push(aom); } - if(m_animation_sent == false){ + if (!m_animation_sent) { m_animation_sent = true; - std::string str = gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend); + std::string str = gob_cmd_update_animation( + m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + + if (!m_animation_speed_sent) { + m_animation_speed_sent = true; + std::string str = gob_cmd_update_animation_speed(m_animation_speed); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } - if(m_bone_position_sent == false){ + if (!m_bone_position_sent) { m_bone_position_sent = true; - for(std::map >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ - std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); + for (std::unordered_map>::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + std::string str = gob_cmd_update_bone_position((*ii).first, + (*ii).second.X, (*ii).second.Y); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } } - if(m_attachment_sent == false){ + if (!m_attachment_sent) { m_attachment_sent = true; std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); // create message and add to list @@ -354,45 +480,50 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) { std::ostringstream os(std::ios::binary); - if(protocol_version >= 14) - { - writeU8(os, 1); // version - os< >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ - os<= 14 + writeU8(os, 1); // version + os << serializeString(""); // name + writeU8(os, 0); // is_player + writeS16(os, getId()); //id + writeV3F1000(os, m_base_position); + writeF1000(os, m_yaw); + writeS16(os, m_hp); + + std::ostringstream msg_os(std::ios::binary); + msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + msg_os << serializeLongString(gob_cmd_update_animation( + m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3 + for (std::unordered_map>::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first, + (*ii).second.X, (*ii).second.Y)); // m_bone_position.size } - else - { - writeU8(os, 0); // version - os<::const_iterator ii = m_attachment_child_ids.begin(); + (ii != m_attachment_child_ids.end()); ++ii) { + if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) { + message_count++; + msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(), + obj->getClientInitializationData(protocol_version))); + } } + msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier)); + message_count++; + + writeU8(os, message_count); + os.write(msg_os.str().c_str(), msg_os.str().size()); + // return result return os.str(); } -std::string LuaEntitySAO::getStaticData() +void LuaEntitySAO::getStaticData(std::string *result) const { - verbosestream<<__FUNCTION_NAME<getWieldedItem(); punchitem = &punchitem_static; } @@ -443,48 +574,47 @@ int LuaEntitySAO::punch(v3f dir, punchitem, time_from_last_punch); - if(result.did_punch) - { - setHP(getHP() - result.damage); - + bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher, + time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0); - std::string punchername = "nil"; + if (!damage_handled) { + if (result.did_punch) { + setHP(getHP() - result.damage); - if ( puncher != 0 ) - punchername = puncher->getDescription(); + if (result.damage > 0) { + std::string punchername = puncher ? puncher->getDescription() : "nil"; - actionstream<getScriptIface()->luaentity_Punch(m_id, puncher, - time_from_last_punch, toolcap, dir); + if (getHP() == 0) { + m_pending_removal = true; + m_env->getScriptIface()->luaentity_on_death(m_id, puncher); + } return result.wear; } void LuaEntitySAO::rightClick(ServerActiveObject *clicker) { - if(!m_registered) + if (!m_registered) return; // It's best that attachments cannot be clicked - if(isAttached()) + if (isAttached()) return; m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker); } -void LuaEntitySAO::setPos(v3f pos) +void LuaEntitySAO::setPos(const v3f &pos) { if(isAttached()) return; @@ -528,53 +658,6 @@ s16 LuaEntitySAO::getHP() const return m_hp; } -void LuaEntitySAO::setArmorGroups(const ItemGroupList &armor_groups) -{ - m_armor_groups = armor_groups; - m_armor_groups_sent = false; -} - -void LuaEntitySAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend) -{ - m_animation_range = frame_range; - m_animation_speed = frame_speed; - m_animation_blend = frame_blend; - m_animation_sent = false; -} - -void LuaEntitySAO::setBonePosition(std::string bone, v3f position, v3f rotation) -{ - m_bone_position[bone] = core::vector2d(position, rotation); - m_bone_position_sent = false; -} - -void LuaEntitySAO::setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) -{ - // Attachments need to be handled on both the server and client. - // If we just attach on the server, we can only copy the position of the parent. Attachments - // are still sent to clients at an interval so players might see them lagging, plus we can't - // read and attach to skeletal bones. - // If we just attach on the client, the server still sees the child at its original location. - // This breaks some things so we also give the server the most accurate representation - // even if players only see the client changes. - - m_attachment_parent_id = parent_id; - m_attachment_bone = bone; - m_attachment_position = position; - m_attachment_rotation = rotation; - m_attachment_sent = false; -} - -ObjectProperties* LuaEntitySAO::accessObjectProperties() -{ - return &m_prop; -} - -void LuaEntitySAO::notifyObjectPropertiesModified() -{ - m_properties_sent = false; -} - void LuaEntitySAO::setVelocity(v3f velocity) { m_velocity = velocity; @@ -595,24 +678,20 @@ v3f LuaEntitySAO::getAcceleration() return m_acceleration; } -void LuaEntitySAO::setYaw(float yaw) -{ - m_yaw = yaw; -} - -float LuaEntitySAO::getYaw() -{ - return m_yaw; -} - void LuaEntitySAO::setTextureMod(const std::string &mod) { std::string str = gob_cmd_set_texture_mod(mod); + m_current_texture_modifier = mod; // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } +std::string LuaEntitySAO::getTextureMod() const +{ + return m_current_texture_modifier; +} + void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, bool select_horiz_by_yawpitch) { @@ -667,7 +746,8 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) m_messages_out.push(aom); } -bool LuaEntitySAO::getCollisionBox(aabb3f *toset) { +bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const +{ if (m_prop.physical) { //update collision box @@ -683,7 +763,20 @@ bool LuaEntitySAO::getCollisionBox(aabb3f *toset) { return false; } -bool LuaEntitySAO::collideWithObjects(){ +bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const +{ + if (!m_prop.is_visible || !m_prop.pointable) { + return false; + } + + toset->MinEdge = m_prop.selectionbox.MinEdge * BS; + toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS; + + return true; +} + +bool LuaEntitySAO::collideWithObjects() const +{ return m_prop.collideWithObjects; } @@ -693,66 +786,59 @@ bool LuaEntitySAO::collideWithObjects(){ // No prototype, PlayerSAO does not need to be deserialized -PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, - const std::set &privs, bool is_singleplayer): - ServerActiveObject(env_, v3f(0,0,0)), +PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_, + bool is_singleplayer): + UnitSAO(env_, v3f(0,0,0)), m_player(player_), m_peer_id(peer_id_), - m_inventory(NULL), - m_damage(0), - m_last_good_position(0,0,0), - m_time_from_last_punch(0), - m_nocheat_dig_pos(32767, 32767, 32767), - m_nocheat_dig_time(0), - m_wield_index(0), - m_position_not_sent(false), - m_armor_groups_sent(false), - m_properties_sent(true), - m_privs(privs), - m_is_singleplayer(is_singleplayer), - m_animation_speed(0), - m_animation_blend(0), - m_animation_sent(false), - m_bone_position_sent(false), - m_attachment_parent_id(0), - m_attachment_sent(false), - // public - m_physics_override_speed(1), - m_physics_override_jump(1), - m_physics_override_gravity(1), - m_physics_override_sneak(true), - m_physics_override_sneak_glitch(true), - m_physics_override_sent(false) -{ - assert(m_player); // pre-condition + m_is_singleplayer(is_singleplayer) +{ assert(m_peer_id != 0); // pre-condition - setBasePosition(m_player->getPosition()); - m_inventory = &m_player->inventory; - m_armor_groups["fleshy"] = 100; - m_prop.hp_max = PLAYER_MAX_HP; + m_prop.hp_max = PLAYER_MAX_HP_DEFAULT; + m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT; m_prop.physical = false; m_prop.weight = 75; - m_prop.collisionbox = core::aabbox3d(-1/3.,-1.0,-1/3., 1/3.,1.0,1/3.); - // start of default appearance, this should be overwritten by LUA + m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); + m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); + m_prop.pointable = true; + // Start of default appearance, this should be overwritten by Lua m_prop.visual = "upright_sprite"; m_prop.visual_size = v2f(1, 2); m_prop.textures.clear(); - m_prop.textures.push_back("player.png"); - m_prop.textures.push_back("player_back.png"); + m_prop.textures.emplace_back("player.png"); + m_prop.textures.emplace_back("player_back.png"); m_prop.colors.clear(); - m_prop.colors.push_back(video::SColor(255, 255, 255, 255)); + m_prop.colors.emplace_back(255, 255, 255, 255); m_prop.spritediv = v2s16(1,1); - // end of default appearance + m_prop.eye_height = 1.625f; + // End of default appearance m_prop.is_visible = true; m_prop.makes_footstep_sound = true; + m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS; + m_hp = m_prop.hp_max; + m_breath = m_prop.breath_max; + // Disable zoom in survival mode using a value of 0 + m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f; } PlayerSAO::~PlayerSAO() { if(m_inventory != &m_player->inventory) delete m_inventory; +} + +void PlayerSAO::finalize(RemotePlayer *player, const std::set &privs) +{ + assert(player); + m_player = player; + m_privs = privs; + m_inventory = &m_player->inventory; +} +v3f PlayerSAO::getEyeOffset() const +{ + return v3f(0, BS * m_prop.eye_height, 0); } std::string PlayerSAO::getDescription() @@ -764,94 +850,136 @@ std::string PlayerSAO::getDescription() void PlayerSAO::addedToEnvironment(u32 dtime_s) { ServerActiveObject::addedToEnvironment(dtime_s); - ServerActiveObject::setBasePosition(m_player->getPosition()); + ServerActiveObject::setBasePosition(m_base_position); m_player->setPlayerSAO(this); - m_player->peer_id = m_peer_id; - m_last_good_position = m_player->getPosition(); + m_player->setPeerId(m_peer_id); + m_last_good_position = m_base_position; } // Called before removing from environment void PlayerSAO::removingFromEnvironment() { ServerActiveObject::removingFromEnvironment(); - if(m_player->getPlayerSAO() == this) - { - m_player->setPlayerSAO(NULL); - m_player->peer_id = 0; - m_env->savePlayer(m_player->getName()); - m_env->removePlayer(m_player->getName()); + if (m_player->getPlayerSAO() == this) { + unlinkPlayerSessionAndSave(); + for (u32 attached_particle_spawner : m_attached_particle_spawners) { + m_env->deleteParticleSpawner(attached_particle_spawner, false); + } } } -bool PlayerSAO::isStaticAllowed() const -{ - return false; -} - std::string PlayerSAO::getClientInitializationData(u16 protocol_version) { std::ostringstream os(std::ios::binary); - if(protocol_version >= 15) - { - writeU8(os, 1); // version - os<getName()); // name - writeU8(os, 1); // is_player - writeS16(os, getId()); //id - writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); - writeF1000(os, m_player->getYaw()); - writeS16(os, getHP()); - - writeU8(os, 5 + m_bone_position.size()); // number of messages stuffed in here - os< >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ - os<= 15 + writeU8(os, 1); // version + os << serializeString(m_player->getName()); // name + writeU8(os, 1); // is_player + writeS16(os, getId()); //id + writeV3F1000(os, m_base_position); + writeF1000(os, m_yaw); + writeS16(os, getHP()); + + std::ostringstream msg_os(std::ios::binary); + msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 + msg_os << serializeLongString(gob_cmd_update_animation( + m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3 + for (std::unordered_map>::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first, + (*ii).second.X, (*ii).second.Y)); // m_bone_position.size } - else - { - writeU8(os, 0); // version - os<getName()); // name - writeU8(os, 1); // is_player - writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); - writeF1000(os, m_player->getYaw()); - writeS16(os, getHP()); - writeU8(os, 2); // number of messages stuffed in here - os<::const_iterator ii = m_attachment_child_ids.begin(); + ii != m_attachment_child_ids.end(); ++ii) { + if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) { + message_count++; + msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(), + obj->getClientInitializationData(protocol_version))); + } } + writeU8(os, message_count); + os.write(msg_os.str().c_str(), msg_os.str().size()); + // return result return os.str(); } -std::string PlayerSAO::getStaticData() -{ - FATAL_ERROR("Deprecated function (?)"); - return ""; -} - -bool PlayerSAO::isAttached() +void PlayerSAO::getStaticData(std::string * result) const { - if(!m_attachment_parent_id) - return false; - // Check if the parent still exists - ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id); - if(obj) - return true; - return false; + FATAL_ERROR("Deprecated function"); } void PlayerSAO::step(float dtime, bool send_recommended) { - if(!m_properties_sent) - { + if (m_drowning_interval.step(dtime, 2.0f)) { + // Get nose/mouth position, approximate with eye position + v3s16 p = floatToInt(getEyePosition(), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); + // If node generates drown + if (c.drowning > 0 && m_hp > 0) { + if (m_breath > 0) + setBreath(m_breath - 1); + + // No more breath, damage player + if (m_breath == 0) { + setHP(m_hp - c.drowning); + m_env->getGameDef()->SendPlayerHPOrDie(this); + } + } + } + + if (m_breathing_interval.step(dtime, 0.5f)) { + // Get nose/mouth position, approximate with eye position + v3s16 p = floatToInt(getEyePosition(), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); + // If player is alive & no drowning, breathe + if (m_hp > 0 && m_breath < m_prop.breath_max && c.drowning == 0) + setBreath(m_breath + 1); + } + + if (m_node_hurt_interval.step(dtime, 1.0f)) { + u32 damage_per_second = 0; + // Lowest and highest damage points are 0.1 within collisionbox + float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f; + + // Sequence of damage points, starting 0.1 above feet and progressing + // upwards in 1 node intervals, stopping below top damage point. + for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) { + v3s16 p = floatToInt(m_base_position + + v3f(0.0f, dam_height * BS, 0.0f), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + damage_per_second = std::max(damage_per_second, + m_env->getGameDef()->ndef()->get(n).damage_per_second); + } + + // Top damage point + v3s16 ptop = floatToInt(m_base_position + + v3f(0.0f, dam_top * BS, 0.0f), BS); + MapNode ntop = m_env->getMap().getNodeNoEx(ptop); + damage_per_second = std::max(damage_per_second, + m_env->getGameDef()->ndef()->get(ntop).damage_per_second); + + if (damage_per_second != 0 && m_hp > 0) { + s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second); + setHP(newhp); + m_env->getGameDef()->SendPlayerHPOrDie(this); + } + } + + if (!m_properties_sent) { m_properties_sent = true; std::string str = getPropertyPacket(); // create message and add to list @@ -860,21 +988,20 @@ void PlayerSAO::step(float dtime, bool send_recommended) } // If attached, check that our parent is still there. If it isn't, detach. - if(m_attachment_parent_id && !isAttached()) - { + if (m_attachment_parent_id && !isAttached()) { m_attachment_parent_id = 0; m_attachment_bone = ""; - m_attachment_position = v3f(0,0,0); - m_attachment_rotation = v3f(0,0,0); - m_player->setPosition(m_last_good_position); - ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); + m_attachment_position = v3f(0.0f, 0.0f, 0.0f); + m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f); + setBasePosition(m_last_good_position); + m_env->getGameDef()->SendMovePlayer(m_peer_id); } //dstream<<"PlayerSAO::step: dtime: "<getMaxLagEstimate() * 2.0; + const float LAG_POOL_MIN = 5.0f; + float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f; if(lag_pool_max < LAG_POOL_MIN) lag_pool_max = LAG_POOL_MIN; m_dig_pool.setMax(lag_pool_max); @@ -883,36 +1010,39 @@ void PlayerSAO::step(float dtime, bool send_recommended) // Increment cheat prevention timers m_dig_pool.add(dtime); m_move_pool.add(dtime); + m_time_from_last_teleport += dtime; m_time_from_last_punch += dtime; m_nocheat_dig_time += dtime; - // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally - // If the object gets detached this comes into effect automatically from the last known origin - if(isAttached()) - { + // Each frame, parent position is copied if the object is attached, + // otherwise it's calculated normally. + // If the object gets detached this comes into effect automatically from + // the last known origin. + if (isAttached()) { v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); m_last_good_position = pos; - m_player->setPosition(pos); + setBasePosition(pos); } - if(send_recommended == false) + if (!send_recommended) return; - // If the object is attached client-side, don't waste bandwidth sending its position to clients - if(m_position_not_sent && !isAttached()) - { + // If the object is attached client-side, don't waste bandwidth sending its + // position to clients. + if (m_position_not_sent && !isAttached()) { m_position_not_sent = false; float update_interval = m_env->getSendRecommendedInterval(); v3f pos; - if(isAttached()) // Just in case we ever do send attachment position too + if (isAttached()) // Just in case we ever do send attachment position too pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); else - pos = m_player->getPosition() + v3f(0,BS*1,0); + pos = m_base_position; + std::string str = gob_cmd_update_position( pos, - v3f(0,0,0), - v3f(0,0,0), - m_player->getYaw(), + v3f(0.0f, 0.0f, 0.0f), + v3f(0.0f, 0.0f, 0.0f), + m_yaw, true, false, update_interval @@ -922,7 +1052,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_messages_out.push(aom); } - if(m_armor_groups_sent == false) { + if (!m_armor_groups_sent) { m_armor_groups_sent = true; std::string str = gob_cmd_update_armor_groups( m_armor_groups); @@ -931,37 +1061,42 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_messages_out.push(aom); } - if(m_physics_override_sent == false){ + if (!m_physics_override_sent) { m_physics_override_sent = true; std::string str = gob_cmd_update_physics_override(m_physics_override_speed, m_physics_override_jump, m_physics_override_gravity, - m_physics_override_sneak, m_physics_override_sneak_glitch); + m_physics_override_sneak, m_physics_override_sneak_glitch, + m_physics_override_new_move); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } - if(m_animation_sent == false){ + if (!m_animation_sent) { m_animation_sent = true; - std::string str = gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend); + std::string str = gob_cmd_update_animation( + m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } - if(m_bone_position_sent == false){ + if (!m_bone_position_sent) { m_bone_position_sent = true; - for(std::map >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ - std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); + for (std::unordered_map>::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + std::string str = gob_cmd_update_bone_position((*ii).first, + (*ii).second.X, (*ii).second.Y); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } } - if(m_attachment_sent == false){ + if (!m_attachment_sent) { m_attachment_sent = true; - std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); + std::string str = gob_cmd_update_attachment(m_attachment_parent_id, + m_attachment_bone, m_attachment_position, m_attachment_rotation); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); @@ -970,41 +1105,82 @@ void PlayerSAO::step(float dtime, bool send_recommended) void PlayerSAO::setBasePosition(const v3f &position) { + if (m_player && position != m_base_position) + m_player->setDirty(true); + // This needs to be ran for attachments too ServerActiveObject::setBasePosition(position); m_position_not_sent = true; } -void PlayerSAO::setPos(v3f pos) +void PlayerSAO::setPos(const v3f &pos) { if(isAttached()) return; - m_player->setPosition(pos); + + setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; - ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); + m_move_pool.empty(); + m_time_from_last_teleport = 0.0; + m_env->getGameDef()->SendMovePlayer(m_peer_id); } void PlayerSAO::moveTo(v3f pos, bool continuous) { if(isAttached()) return; - m_player->setPosition(pos); + + setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; - ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); + m_move_pool.empty(); + m_time_from_last_teleport = 0.0; + m_env->getGameDef()->SendMovePlayer(m_peer_id); +} + +void PlayerSAO::setYaw(const float yaw) +{ + if (m_player && yaw != m_yaw) + m_player->setDirty(true); + + UnitSAO::setYaw(yaw); +} + +void PlayerSAO::setFov(const float fov) +{ + if (m_player && fov != m_fov) + m_player->setDirty(true); + + m_fov = fov; } -void PlayerSAO::setYaw(float yaw) +void PlayerSAO::setWantedRange(const s16 range) { - m_player->setYaw(yaw); - ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); + if (m_player && range != m_wanted_range) + m_player->setDirty(true); + + m_wanted_range = range; } -void PlayerSAO::setPitch(float pitch) +void PlayerSAO::setYawAndSend(const float yaw) { - m_player->setPitch(pitch); - ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); + setYaw(yaw); + m_env->getGameDef()->SendMovePlayer(m_peer_id); +} + +void PlayerSAO::setPitch(const float pitch) +{ + if (m_player && pitch != m_pitch) + m_player->setDirty(true); + + m_pitch = pitch; +} + +void PlayerSAO::setPitchAndSend(const float pitch) +{ + setPitch(pitch); + m_env->getGameDef()->SendMovePlayer(m_peer_id); } int PlayerSAO::punch(v3f dir, @@ -1013,15 +1189,15 @@ int PlayerSAO::punch(v3f dir, float time_from_last_punch) { // It's best that attachments cannot be punched - if(isAttached()) + if (isAttached()) return 0; - if(!toolcap) + if (!toolcap) return 0; // No effect if PvP disabled - if(g_settings->getBool("enable_pvp") == false){ - if(puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER){ + if (!g_settings->getBool("enable_pvp")) { + if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { std::string str = gob_cmd_punched(0, getHP()); // create message and add to list ActiveObjectMessage aom(getId(), true, str); @@ -1035,25 +1211,37 @@ int PlayerSAO::punch(v3f dir, std::string punchername = "nil"; - if ( puncher != 0 ) + if (puncher != 0) punchername = puncher->getDescription(); - actionstream<<"Player "<getName()<<" punched by " - <getPlayerSAO(); - setHP(getHP() - hitparams.hp); + bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao, + puncher, time_from_last_punch, toolcap, dir, + hitparams.hp); - return hitparams.wear; -} + if (!damage_handled) { + setHP(getHP() - hitparams.hp); + } else { // override client prediction + if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + std::string str = gob_cmd_punched(0, getHP()); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + } -void PlayerSAO::rightClick(ServerActiveObject *clicker) -{ -} -s16 PlayerSAO::getHP() const -{ - return m_player->hp; + actionstream << "Player " << m_player->getName() << " punched by " + << punchername; + if (!damage_handled) { + actionstream << ", damage " << hitparams.hp << " HP"; + } else { + actionstream << ", damage handled by lua"; + } + actionstream << std::endl; + + return hitparams.wear; } s16 PlayerSAO::readDamage() @@ -1065,14 +1253,23 @@ s16 PlayerSAO::readDamage() void PlayerSAO::setHP(s16 hp) { - s16 oldhp = m_player->hp; + s16 oldhp = m_hp; + + s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp); + if (hp_change == 0) + return; + hp = oldhp + hp_change; if (hp < 0) hp = 0; - else if (hp > PLAYER_MAX_HP) - hp = PLAYER_MAX_HP; + else if (hp > m_prop.hp_max) + hp = m_prop.hp_max; + + if (hp < oldhp && !g_settings->getBool("enable_damage")) { + return; + } - m_player->hp = hp; + m_hp = hp; if (oldhp > hp) m_damage += (oldhp - hp); @@ -1082,63 +1279,15 @@ void PlayerSAO::setHP(s16 hp) m_properties_sent = false; } -u16 PlayerSAO::getBreath() const +void PlayerSAO::setBreath(const u16 breath, bool send) { - return m_player->getBreath(); -} - -void PlayerSAO::setBreath(u16 breath) -{ - m_player->setBreath(breath); -} + if (m_player && breath != m_breath) + m_player->setDirty(true); -void PlayerSAO::setArmorGroups(const ItemGroupList &armor_groups) -{ - m_armor_groups = armor_groups; - m_armor_groups_sent = false; -} + m_breath = MYMIN(breath, m_prop.breath_max); -void PlayerSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend) -{ - // store these so they can be updated to clients - m_animation_range = frame_range; - m_animation_speed = frame_speed; - m_animation_blend = frame_blend; - m_animation_sent = false; -} - -void PlayerSAO::setBonePosition(std::string bone, v3f position, v3f rotation) -{ - // store these so they can be updated to clients - m_bone_position[bone] = core::vector2d(position, rotation); - m_bone_position_sent = false; -} - -void PlayerSAO::setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) -{ - // Attachments need to be handled on both the server and client. - // If we just attach on the server, we can only copy the position of the parent. Attachments - // are still sent to clients at an interval so players might see them lagging, plus we can't - // read and attach to skeletal bones. - // If we just attach on the client, the server still sees the child at its original location. - // This breaks some things so we also give the server the most accurate representation - // even if players only see the client changes. - - m_attachment_parent_id = parent_id; - m_attachment_bone = bone; - m_attachment_position = position; - m_attachment_rotation = rotation; - m_attachment_sent = false; -} - -ObjectProperties* PlayerSAO::accessObjectProperties() -{ - return &m_prop; -} - -void PlayerSAO::notifyObjectPropertiesModified() -{ - m_properties_sent = false; + if (send) + m_env->getGameDef()->SendPlayerBreath(this); } Inventory* PlayerSAO::getInventory() @@ -1162,6 +1311,44 @@ std::string PlayerSAO::getWieldList() const return "main"; } +ItemStack PlayerSAO::getWieldedItem() const +{ + const Inventory *inv = getInventory(); + ItemStack ret; + const InventoryList *mlist = inv->getList(getWieldList()); + if (mlist && getWieldIndex() < (s32)mlist->getSize()) + ret = mlist->getItem(getWieldIndex()); + return ret; +} + +ItemStack PlayerSAO::getWieldedItemOrHand() const +{ + const Inventory *inv = getInventory(); + ItemStack ret; + const InventoryList *mlist = inv->getList(getWieldList()); + if (mlist && getWieldIndex() < (s32)mlist->getSize()) + ret = mlist->getItem(getWieldIndex()); + if (ret.name.empty()) { + const InventoryList *hlist = inv->getList("hand"); + if (hlist) + ret = hlist->getItem(0); + } + return ret; +} + +bool PlayerSAO::setWieldedItem(const ItemStack &item) +{ + Inventory *inv = getInventory(); + if (inv) { + InventoryList *mlist = inv->getList(getWieldList()); + if (mlist) { + mlist->changeItem(getWieldIndex(), item); + return true; + } + } + return false; +} + int PlayerSAO::getWieldIndex() const { return m_wield_index; @@ -1177,12 +1364,16 @@ void PlayerSAO::setWieldIndex(int i) void PlayerSAO::disconnected() { m_peer_id = 0; - m_removed = true; - if(m_player->getPlayerSAO() == this) - { - m_player->setPlayerSAO(NULL); - m_player->peer_id = 0; - } + m_pending_removal = true; +} + +void PlayerSAO::unlinkPlayerSessionAndSave() +{ + assert(m_player->getPlayerSAO() == this); + m_player->setPeerId(PEER_ID_INEXISTENT); + m_env->savePlayer(m_player); + m_player->setPlayerSAO(NULL); + m_env->removePlayer(m_player); } std::string PlayerSAO::getPropertyPacket() @@ -1193,67 +1384,80 @@ std::string PlayerSAO::getPropertyPacket() bool PlayerSAO::checkMovementCheat() { + if (isAttached() || m_is_singleplayer || + g_settings->getBool("disable_anticheat")) { + m_last_good_position = m_base_position; + return false; + } + bool cheated = false; - if(isAttached() || m_is_singleplayer || - g_settings->getBool("disable_anticheat")) - { - m_last_good_position = m_player->getPosition(); + /* + Check player movements + + NOTE: Actually the server should handle player physics like the + client does and compare player's position to what is calculated + on our side. This is required when eg. players fly due to an + explosion. Altough a node-based alternative might be possible + too, and much more lightweight. + */ + + float player_max_speed = 0; + + if (m_privs.count("fast") != 0) { + // Fast speed + player_max_speed = m_player->movement_speed_fast * m_physics_override_speed; + } else { + // Normal speed + player_max_speed = m_player->movement_speed_walk * m_physics_override_speed; } - else - { - /* - Check player movements - - NOTE: Actually the server should handle player physics like the - client does and compare player's position to what is calculated - on our side. This is required when eg. players fly due to an - explosion. Altough a node-based alternative might be possible - too, and much more lightweight. - */ - - float player_max_speed = 0; - if(m_privs.count("fast") != 0){ - // Fast speed - player_max_speed = m_player->movement_speed_fast; - } else { - // Normal speed - player_max_speed = m_player->movement_speed_walk; - } - // Tolerance. With the lag pool we shouldn't need it. - //player_max_speed *= 2.5; - //player_max_speed_up *= 2.5; - - v3f diff = (m_player->getPosition() - m_last_good_position); - float d_vert = diff.Y; - diff.Y = 0; - float d_horiz = diff.getLength(); - float required_time = d_horiz/player_max_speed; - if(d_vert > 0 && d_vert/player_max_speed > required_time) - required_time = d_vert/player_max_speed; - if(m_move_pool.grab(required_time)){ - m_last_good_position = m_player->getPosition(); - } else { - actionstream<<"Player "<getName() - <<" moved too fast; resetting position" - <setPosition(m_last_good_position); + // Tolerance. The lag pool does this a bit. + //player_max_speed *= 2.5; + + v3f diff = (m_base_position - m_last_good_position); + float d_vert = diff.Y; + diff.Y = 0; + float d_horiz = diff.getLength(); + float required_time = d_horiz / player_max_speed; + + if (d_vert > 0 && d_vert / player_max_speed > required_time) + required_time = d_vert / player_max_speed; // Moving upwards + + if (m_move_pool.grab(required_time)) { + m_last_good_position = m_base_position; + } else { + const float LAG_POOL_MIN = 5.0; + float lag_pool_max = m_env->getMaxLagEstimate() * 2.0; + lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN); + if (m_time_from_last_teleport > lag_pool_max) { + actionstream << "Player " << m_player->getName() + << " moved too fast; resetting position" + << std::endl; cheated = true; } + setBasePosition(m_last_good_position); } return cheated; } -bool PlayerSAO::getCollisionBox(aabb3f *toset) { +bool PlayerSAO::getCollisionBox(aabb3f *toset) const +{ //update collision box - *toset = m_player->getCollisionbox(); + toset->MinEdge = m_prop.collisionbox.MinEdge * BS; + toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; toset->MinEdge += m_base_position; toset->MaxEdge += m_base_position; - return true; } -bool PlayerSAO::collideWithObjects(){ +bool PlayerSAO::getSelectionBox(aabb3f *toset) const +{ + if (!m_prop.is_visible || !m_prop.pointable) { + return false; + } + + toset->MinEdge = m_prop.selectionbox.MinEdge * BS; + toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS; + return true; } -