diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 3e18337d..613ec615 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -581,6 +581,10 @@ private: int m_frame_speed; int m_frame_blend; std::map > m_bone_posrot; + ClientActiveObject* m_attachment_parent; + std::string m_attachment_bone; + v3f m_attacmhent_position; + v3f m_attachment_rotation; int m_anim_frame; int m_anim_num_frames; float m_anim_framelength; @@ -615,6 +619,14 @@ public: m_tx_basepos(0,0), m_initial_tx_basepos_set(false), m_tx_select_horiz_by_yawpitch(false), + m_frames(v2f(0,0)), + m_frame_speed(15), + m_frame_blend(0), + // Nothing to do for m_bone_posrot + m_attachment_parent(NULL), + m_attachment_bone(""), + m_attacmhent_position(v3f(0,0,0)), + m_attachment_rotation(v3f(0,0,0)), m_anim_frame(0), m_anim_num_frames(1), m_anim_framelength(0.2), @@ -906,6 +918,9 @@ public: void updateNodePos() { + if(m_attachment_parent != NULL) + return; + if(m_meshnode){ m_meshnode->setPosition(pos_translator.vect_show); v3f rot = m_meshnode->getRotation(); @@ -933,51 +948,53 @@ public: addToScene(m_smgr, m_gamedef->tsrc(), m_irr); updateAnimations(); updateBonePosRot(); - updateAttachment(); } - if(m_prop.physical){ - core::aabbox3d box = m_prop.collisionbox; - box.MinEdge *= BS; - box.MaxEdge *= BS; - collisionMoveResult moveresult; - f32 pos_max_d = BS*0.125; // Distance per iteration - f32 stepheight = 0; - v3f p_pos = m_position; - v3f p_velocity = m_velocity; - v3f p_acceleration = m_acceleration; - IGameDef *gamedef = env->getGameDef(); - moveresult = collisionMoveSimple(&env->getMap(), gamedef, - pos_max_d, box, stepheight, dtime, - p_pos, p_velocity, p_acceleration); - // Apply results - m_position = p_pos; - m_velocity = p_velocity; - m_acceleration = p_acceleration; - - bool is_end_position = moveresult.collides; - pos_translator.update(m_position, is_end_position, dtime); - pos_translator.translate(dtime); - updateNodePos(); - } else { - m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; - m_velocity += dtime * m_acceleration; - pos_translator.update(m_position, pos_translator.aim_is_end, pos_translator.anim_time); - pos_translator.translate(dtime); - updateNodePos(); - } + if(m_attachment_parent == NULL) // Attachments should be glued to their parent by Irrlicht + { + if(m_prop.physical){ + core::aabbox3d box = m_prop.collisionbox; + box.MinEdge *= BS; + box.MaxEdge *= BS; + collisionMoveResult moveresult; + f32 pos_max_d = BS*0.125; // Distance per iteration + f32 stepheight = 0; + v3f p_pos = m_position; + v3f p_velocity = m_velocity; + v3f p_acceleration = m_acceleration; + IGameDef *gamedef = env->getGameDef(); + moveresult = collisionMoveSimple(&env->getMap(), gamedef, + pos_max_d, box, stepheight, dtime, + p_pos, p_velocity, p_acceleration); + // Apply results + m_position = p_pos; + m_velocity = p_velocity; + m_acceleration = p_acceleration; + + bool is_end_position = moveresult.collides; + pos_translator.update(m_position, is_end_position, dtime); + pos_translator.translate(dtime); + updateNodePos(); + } else { + m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; + m_velocity += dtime * m_acceleration; + pos_translator.update(m_position, pos_translator.aim_is_end, pos_translator.anim_time); + pos_translator.translate(dtime); + updateNodePos(); + } - float moved = lastpos.getDistanceFrom(pos_translator.vect_show); - m_step_distance_counter += moved; - if(m_step_distance_counter > 1.5*BS){ - m_step_distance_counter = 0; - if(!m_is_local_player && m_prop.makes_footstep_sound){ - INodeDefManager *ndef = m_gamedef->ndef(); - v3s16 p = floatToInt(getPosition() + v3f(0, - (m_prop.collisionbox.MinEdge.Y-0.5)*BS, 0), BS); - MapNode n = m_env->getMap().getNodeNoEx(p); - SimpleSoundSpec spec = ndef->get(n).sound_footstep; - m_gamedef->sound()->playSoundAt(spec, false, getPosition()); + float moved = lastpos.getDistanceFrom(pos_translator.vect_show); + m_step_distance_counter += moved; + if(m_step_distance_counter > 1.5*BS){ + m_step_distance_counter = 0; + if(!m_is_local_player && m_prop.makes_footstep_sound){ + INodeDefManager *ndef = m_gamedef->ndef(); + v3s16 p = floatToInt(getPosition() + v3f(0, + (m_prop.collisionbox.MinEdge.Y-0.5)*BS, 0), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + SimpleSoundSpec spec = ndef->get(n).sound_footstep; + m_gamedef->sound()->playSoundAt(spec, false, getPosition()); + } } } @@ -1193,10 +1210,38 @@ public: } } } - - void updateAttachment() + + void updateAttachments() { - // Code for attachments goes here + // REMAINING ATTACHMENT ISSUES: + // We get to this function when the object is an attachment that needs to + // be attached to its parent. If a bone is set we attach it to that skeletal + // bone, otherwise just to the object's origin. Attachments should not copy parent + // position as that's laggy... instead the Irrlicht function(s) to attach should + // be used. If the parent object is NULL that means this object should be detached. + // This function is only called whenever a GENERIC_CMD_SET_ATTACHMENT message is received. + + // We already attach our entity on the server too (copy position). Reason we attach + // to the client as well is first of all lag. The server sends the position + // of the child separately than that of the parent, so even on localhost + // you'd see the child lagging behind. Models are also client-side, so this is + // needed to read bone data and attach to joints. + + // Functions: + // - m_attachment_parent is ClientActiveObject* for the parent entity. + // - m_attachment_bone is std::string of the bone, "" means none. + // - m_attachment_position is v3f and represents the position offset of the attachment. + // - m_attachment_rotation is v3f and represents the rotation offset of the attachment. + + // Implementation information: + // From what I know, we need to get the AnimatedMeshSceneNode of m_attachment_parent then + // use parent_node->addChild(m_animated_meshnode) for position attachment. For skeletal + // attachment I don't know yet. Same must be used to detach when a NULL parent is received. + + //Useful links: + // http://irrlicht.sourceforge.net/forum/viewtopic.php?t=7514 + // http://www.irrlicht3d.org/wiki/index.php?n=Main.HowToUseTheNewAnimationSystem + // Irrlicht documentation: http://irrlicht.sourceforge.net/docu/ } void processMessage(const std::string &data) @@ -1225,6 +1270,8 @@ public: } else if(cmd == GENERIC_CMD_UPDATE_POSITION) { + // Not sent by the server if the object is an attachment + m_position = readV3F1000(is); m_velocity = readV3F1000(is); m_acceleration = readV3F1000(is); @@ -1238,7 +1285,7 @@ public: // the ground due to sucky collision detection... if(m_prop.physical) m_position += v3f(0,0.002,0); - + if(do_interpolate){ if(!m_prop.physical) pos_translator.update(m_position, is_end_position, update_interval); @@ -1287,12 +1334,18 @@ public: } else if(cmd == GENERIC_CMD_SET_ATTACHMENT) { - // Part of the attachment structure, not used yet! + ClientActiveObject *obj; + int parent_id = readS16(is); + if(parent_id > 0) + obj = m_env->getActiveObject(parent_id); + else + obj = NULL; + m_attachment_parent = obj; + m_attachment_bone = deSerializeString(is); + m_attacmhent_position = readV3F1000(is); + m_attachment_rotation = readV3F1000(is); - // Get properties here. - - updateAttachment(); - expireVisuals(); + updateAttachments(); } else if(cmd == GENERIC_CMD_PUNCHED) { diff --git a/src/content_sao.cpp b/src/content_sao.cpp index b23d287f..7787e33f 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -218,6 +218,7 @@ public: if(pos_f.getDistanceFrom(m_last_sent_position) > 0.05*BS) { + // TODO: We shouldn't be sending this when the object is attached, but we can't check m_parent here setBasePosition(pos_f); m_last_sent_position = pos_f; @@ -383,6 +384,7 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s) // Create entity from name lua_State *L = m_env->getLua(); m_registered = scriptapi_luaentity_add(L, m_id, m_init_name.c_str()); + m_parent = NULL; if(m_registered){ // Get properties @@ -442,28 +444,39 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) m_last_sent_position_timer += dtime; - if(m_prop.physical){ - core::aabbox3d box = m_prop.collisionbox; - box.MinEdge *= BS; - box.MaxEdge *= BS; - collisionMoveResult moveresult; - f32 pos_max_d = BS*0.25; // Distance per iteration - f32 stepheight = 0; // Maximum climbable step height - v3f p_pos = m_base_position; - v3f p_velocity = m_velocity; - v3f p_acceleration = m_acceleration; - IGameDef *gamedef = m_env->getGameDef(); - moveresult = collisionMoveSimple(&m_env->getMap(), gamedef, - pos_max_d, box, stepheight, dtime, - p_pos, p_velocity, p_acceleration); - // Apply results - m_base_position = p_pos; - m_velocity = p_velocity; - m_acceleration = p_acceleration; - } else { - m_base_position += dtime * m_velocity + 0.5 * dtime - * dtime * m_acceleration; - m_velocity += dtime * m_acceleration; + if(m_parent != NULL) + { + // REMAINING ATTACHMENT ISSUES: + // This is causing a segmentation fault, investigate why! + //m_base_position = m_parent->getBasePosition(); + m_velocity = v3f(0,0,0); + m_acceleration = v3f(0,0,0); + } + else + { + if(m_prop.physical){ + core::aabbox3d box = m_prop.collisionbox; + box.MinEdge *= BS; + box.MaxEdge *= BS; + collisionMoveResult moveresult; + f32 pos_max_d = BS*0.25; // Distance per iteration + f32 stepheight = 0; // Maximum climbable step height + v3f p_pos = m_base_position; + v3f p_velocity = m_velocity; + v3f p_acceleration = m_acceleration; + IGameDef *gamedef = m_env->getGameDef(); + moveresult = collisionMoveSimple(&m_env->getMap(), gamedef, + pos_max_d, box, stepheight, dtime, + p_pos, p_velocity, p_acceleration); + // Apply results + m_base_position = p_pos; + m_velocity = p_velocity; + m_acceleration = p_acceleration; + } else { + m_base_position += dtime * m_velocity + 0.5 * dtime + * dtime * m_acceleration; + m_velocity += dtime * m_acceleration; + } } if(m_registered){ @@ -551,6 +564,10 @@ int LuaEntitySAO::punch(v3f dir, m_removed = true; return 0; } + + // It's best that attachments cannot be punched + if(m_parent != NULL) + return 0; ItemStack *punchitem = NULL; ItemStack punchitem_static; @@ -601,12 +618,16 @@ void LuaEntitySAO::rightClick(ServerActiveObject *clicker) void LuaEntitySAO::setPos(v3f pos) { + if(m_parent != NULL) + return; m_base_position = pos; sendPosition(false, true); } void LuaEntitySAO::moveTo(v3f pos, bool continuous) { + if(m_parent != NULL) + return; m_base_position = pos; if(!continuous) sendPosition(true, true); @@ -661,14 +682,21 @@ void LuaEntitySAO::setBonePosRot(std::string bone, v3f position, v3f rotation) m_messages_out.push_back(aom); } -// Part of the attachment structure, not used yet! void LuaEntitySAO::setAttachment(ServerActiveObject *parent, std::string bone, v3f position, v3f rotation) { - // Parent should be translated from a ServerActiveObject into something - // the client will recognize (as a ClientActiveObject) then sent in - // gob_cmd_set_attachment that way. + // 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 would see them following the parent + // instead of sticking to it, 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 can break some things, so we also give the server the most accurate representation + // even if players will only see the client changes since they override server-sent position. - std::string str = gob_cmd_set_attachment(); // <- parameters here + // Server attachment: + m_parent = parent; + + // Client attachment: + std::string str = gob_cmd_set_attachment(parent->getId(), bone, position, rotation); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push_back(aom); @@ -748,6 +776,9 @@ std::string LuaEntitySAO::getPropertyPacket() void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) { + if(m_parent != NULL) + return; + m_last_sent_move_precision = m_base_position.getDistanceFrom( m_last_sent_position); m_last_sent_position_timer = 0; @@ -843,6 +874,7 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s) { ServerActiveObject::addedToEnvironment(dtime_s); ServerActiveObject::setBasePosition(m_player->getPosition()); + m_parent = NULL; m_player->setPlayerSAO(this); m_player->peer_id = m_peer_id; m_last_good_position = m_player->getPosition(); @@ -893,7 +925,7 @@ std::string PlayerSAO::getStaticData() } void PlayerSAO::step(float dtime, bool send_recommended) -{ +{ if(!m_properties_sent) { m_properties_sent = true; @@ -905,71 +937,82 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_time_from_last_punch += dtime; m_nocheat_dig_time += dtime; - - if(m_is_singleplayer || g_settings->getBool("disable_anticheat")) + + if(m_parent == NULL) { - m_last_good_position = m_player->getPosition(); - m_last_good_position_age = 0; - } - 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; - float player_max_speed_up = 0; - if(m_privs.count("fast") != 0){ - // Fast speed - player_max_speed = BS * 20; - player_max_speed_up = BS * 20; - } else { - // Normal speed - player_max_speed = BS * 4.0; - player_max_speed_up = BS * 4.0; - } - // Tolerance - player_max_speed *= 2.5; - player_max_speed_up *= 2.5; - - m_last_good_position_age += dtime; - if(m_last_good_position_age >= 1.0){ - float age = m_last_good_position_age; - v3f diff = (m_player->getPosition() - m_last_good_position); - float d_vert = diff.Y; - diff.Y = 0; - float d_horiz = diff.getLength(); - /*infostream<getName()<<"'s horizontal speed is " - <<(d_horiz/age)<getPosition(); - } else { - actionstream<<"Player "<getName() - <<" moved too fast; resetting position" - <setPosition(m_last_good_position); - m_teleported = true; - } + if(m_is_singleplayer || g_settings->getBool("disable_anticheat")) + { + m_last_good_position = m_player->getPosition(); m_last_good_position_age = 0; } + 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; + float player_max_speed_up = 0; + if(m_privs.count("fast") != 0){ + // Fast speed + player_max_speed = BS * 20; + player_max_speed_up = BS * 20; + } else { + // Normal speed + player_max_speed = BS * 4.0; + player_max_speed_up = BS * 4.0; + } + // Tolerance + player_max_speed *= 2.5; + player_max_speed_up *= 2.5; + + m_last_good_position_age += dtime; + if(m_last_good_position_age >= 1.0){ + float age = m_last_good_position_age; + v3f diff = (m_player->getPosition() - m_last_good_position); + float d_vert = diff.Y; + diff.Y = 0; + float d_horiz = diff.getLength(); + /*infostream<getName()<<"'s horizontal speed is " + <<(d_horiz/age)<getPosition(); + } else { + actionstream<<"Player "<getName() + <<" moved too fast; resetting position" + <setPosition(m_last_good_position); + m_teleported = true; + } + m_last_good_position_age = 0; + } + } } if(send_recommended == false) return; - if(m_position_not_sent) + // If the object is attached client-side, don't waste bandwidth and send its position to clients + if(m_position_not_sent && m_parent == NULL) { m_position_not_sent = false; float update_interval = m_env->getSendRecommendedInterval(); + v3f pos; + // REMAINING ATTACHMENT ISSUES: + // This is causing a segmentation fault, investigate why! + if(m_parent != NULL) + pos = m_parent->getBasePosition(); + else + pos = m_player->getPosition() + v3f(0,BS*1,0); std::string str = gob_cmd_update_position( - m_player->getPosition() + v3f(0,BS*1,0), + pos, v3f(0,0,0), v3f(0,0,0), m_player->getYaw(), @@ -1000,12 +1043,16 @@ void PlayerSAO::step(float dtime, bool send_recommended) void PlayerSAO::setBasePosition(const v3f &position) { + if(m_parent != NULL) + return; ServerActiveObject::setBasePosition(position); m_position_not_sent = true; } void PlayerSAO::setPos(v3f pos) { + if(m_parent != NULL) + return; m_player->setPosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; @@ -1016,6 +1063,8 @@ void PlayerSAO::setPos(v3f pos) void PlayerSAO::moveTo(v3f pos, bool continuous) { + if(m_parent != NULL) + return; m_player->setPosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; @@ -1029,6 +1078,10 @@ int PlayerSAO::punch(v3f dir, ServerActiveObject *puncher, float time_from_last_punch) { + // It's best that attachments cannot be punched + if(m_parent != NULL) + return 0; + if(!toolcap) return 0; @@ -1126,10 +1179,21 @@ void PlayerSAO::setBonePosRot(std::string bone, v3f position, v3f rotation) m_messages_out.push_back(aom); } -// Part of the attachment structure, not used yet! void PlayerSAO::setAttachment(ServerActiveObject *parent, std::string bone, v3f position, v3f rotation) -{ - std::string str = gob_cmd_set_attachment(); // <- parameters here +{ + // 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 would see them following the parent + // instead of sticking to it, 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 can break some things, so we also give the server the most accurate representation + // even if players will only see the client changes since they override server-sent position. + + // Server attachment: + m_parent = parent; + + // Client attachment: + std::string str = gob_cmd_set_attachment(parent->getId(), bone, position, rotation); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push_back(aom); diff --git a/src/content_sao.h b/src/content_sao.h index a89f2ddd..e4d81cd2 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -99,6 +99,7 @@ private: float m_last_sent_position_timer; float m_last_sent_move_precision; bool m_armor_groups_sent; + ServerActiveObject *m_parent; }; /* @@ -235,6 +236,7 @@ private: bool m_position_not_sent; ItemGroupList m_armor_groups; bool m_armor_groups_sent; + ServerActiveObject *m_parent; bool m_properties_sent; struct ObjectProperties m_prop; // Cached privileges for enforcement diff --git a/src/genericobject.cpp b/src/genericobject.cpp index 482dbbc7..480c4209 100644 --- a/src/genericobject.cpp +++ b/src/genericobject.cpp @@ -104,17 +104,6 @@ std::string gob_cmd_set_animations(v2f frames, float frame_speed, float frame_bl return os.str(); } -// Part of the attachment structure, not used yet! -std::string gob_cmd_set_attachment() // <- parameters here -{ - std::ostringstream os(std::ios::binary); - // command - writeU8(os, GENERIC_CMD_SET_ATTACHMENT); - // parameters - // Parameters go here - return os.str(); -} - std::string gob_cmd_set_bone_posrot(std::string bone, v3f position, v3f rotation) { std::ostringstream os(std::ios::binary); @@ -127,6 +116,19 @@ std::string gob_cmd_set_bone_posrot(std::string bone, v3f position, v3f rotation return os.str(); } +std::string gob_cmd_set_attachment(int parent_id, std::string bone, v3f position, v3f rotation) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SET_ATTACHMENT); + // parameters + writeS16(os, parent_id); + os<getId()); // Push id co->setAttachment(parent, bone, position, rotation); return 0;