/** * @file llavataractions.cpp * @brief Friend-related actions (add, remove, offer teleport, etc) * * $LicenseInfo:firstyear=2009&license=viewergpl$ * * Copyright (c) 2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llavataractions.h" #include "boost/lambda/lambda.hpp" // for lambda::constant #include "llsd.h" #include "lldarray.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "roles_constants.h" // for GP_MEMBER_INVITE #include "llagent.h" #include "llappviewer.h" // for gLastVersionChannel #include "llcachename.h" #include "llcallingcard.h" // for LLAvatarTracker #include "llfloateravatarpicker.h" // for LLFloaterAvatarPicker #include "llfloatergroupinvite.h" #include "llfloatergroups.h" #include "llfloaterreg.h" #include "llfloaterpay.h" #include "llfloaterworldmap.h" #include "llgiveinventory.h" #include "llinventorymodel.h" // for gInventory.findCategoryUUIDForType #include "llinventorypanel.h" #include "llimview.h" // for gIMMgr #include "llmutelist.h" #include "llnotificationsutil.h" // for LLNotificationsUtil #include "llpaneloutfitedit.h" #include "llrecentpeople.h" #include "llsidetray.h" #include "lltrans.h" #include "llviewerobjectlist.h" #include "llviewermessage.h" // for handle_lure #include "llviewerregion.h" #include "llimfloater.h" #include "lltrans.h" #include "llcallingcard.h" // static void LLAvatarActions::requestFriendshipDialog(const LLUUID& id, const std::string& name) { if(id == gAgentID) { LLNotificationsUtil::add("AddSelfFriend"); return; } LLSD args; args["NAME"] = name; LLSD payload; payload["id"] = id; payload["name"] = name; // Look for server versions like: Second Life Server 1.24.4.95600 if (gLastVersionChannel.find(" 1.24.") != std::string::npos) { // Old and busted server version, doesn't support friend // requests with messages. LLNotificationsUtil::add("AddFriend", args, payload, &callbackAddFriend); } else { LLNotificationsUtil::add("AddFriendWithMessage", args, payload, &callbackAddFriendWithMessage); } // add friend to recent people list LLRecentPeople::instance().add(id); } // static void LLAvatarActions::requestFriendshipDialog(const LLUUID& id) { if(id.isNull()) { return; } std::string full_name; gCacheName->getFullName(id, full_name); requestFriendshipDialog(id, full_name); } // static void LLAvatarActions::removeFriendDialog(const LLUUID& id) { if (id.isNull()) return; uuid_vec_t ids; ids.push_back(id); removeFriendsDialog(ids); } // static void LLAvatarActions::removeFriendsDialog(const uuid_vec_t& ids) { if(ids.size() == 0) return; LLSD args; std::string msgType; if(ids.size() == 1) { LLUUID agent_id = ids[0]; std::string first, last; if(gCacheName->getName(agent_id, first, last)) { args["FIRST_NAME"] = first; args["LAST_NAME"] = last; } msgType = "RemoveFromFriends"; } else { msgType = "RemoveMultipleFromFriends"; } LLSD payload; for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) { payload["ids"].append(*it); } LLNotificationsUtil::add(msgType, args, payload, &handleRemove); } // static void LLAvatarActions::offerTeleport(const LLUUID& invitee) { if (invitee.isNull()) return; //waiting until Name Cache gets updated with corresponding avatar name std::string just_to_request_name; if (!gCacheName->getFullName(invitee, just_to_request_name)) { gCacheName->get(invitee, FALSE, boost::bind((void (*)(const LLUUID&)) &LLAvatarActions::offerTeleport, invitee)); return; } LLDynamicArray<LLUUID> ids; ids.push_back(invitee); offerTeleport(ids); } // static void LLAvatarActions::offerTeleport(const uuid_vec_t& ids) { if (ids.size() == 0) return; handle_lure(ids); } // static void LLAvatarActions::startIM(const LLUUID& id) { if (id.isNull()) return; std::string name; if (!gCacheName->getFullName(id, name)) { gCacheName->get(id, FALSE, boost::bind(&LLAvatarActions::startIM, id)); return; } LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, id); if (session_id != LLUUID::null) { LLIMFloater::show(session_id); } make_ui_sound("UISndStartIM"); } // static void LLAvatarActions::endIM(const LLUUID& id) { if (id.isNull()) return; LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, id); if (session_id != LLUUID::null) { gIMMgr->leaveSession(session_id); } } // static void LLAvatarActions::startCall(const LLUUID& id) { if (id.isNull()) { return; } std::string name; gCacheName->getFullName(id, name); LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, id, true); if (session_id != LLUUID::null) { gIMMgr->startCall(session_id); } make_ui_sound("UISndStartIM"); } // static void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids) { if (ids.size() == 0) { return; } // convert vector into LLDynamicArray for addSession LLDynamicArray<LLUUID> id_array; for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) { id_array.push_back(*it); } // create the new ad hoc voice session const std::string title = LLTrans::getString("conference-title"); LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, true); if (session_id == LLUUID::null) { return; } gIMMgr->autoStartCallOnStartup(session_id); make_ui_sound("UISndStartIM"); } /* AD *TODO: Is this function needed any more? I fixed it a bit(added check for canCall), but it appears that it is not used anywhere. Maybe it should be removed? // static bool LLAvatarActions::isCalling(const LLUUID &id) { if (id.isNull() || !canCall()) { return false; } LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, id); return (LLIMModel::getInstance()->findIMSession(session_id) != NULL); }*/ //static bool LLAvatarActions::canCall() { return LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); } // static void LLAvatarActions::startConference(const uuid_vec_t& ids) { // *HACK: Copy into dynamic array LLDynamicArray<LLUUID> id_array; for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) { id_array.push_back(*it); } const std::string title = LLTrans::getString("conference-title"); LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array); if (session_id != LLUUID::null) { LLIMFloater::show(session_id); } make_ui_sound("UISndStartIM"); } // static void LLAvatarActions::showProfile(const LLUUID& id) { if (id.notNull()) { LLSD params; params["id"] = id; params["open_tab_name"] = "panel_profile"; //Show own profile if(gAgent.getID() == id) { LLSideTray::getInstance()->showPanel("panel_me", params); } //Show other user profile else { LLSideTray::getInstance()->showPanel("panel_profile_view", params); } } } // static void LLAvatarActions::showOnMap(const LLUUID& id) { std::string name; if (!gCacheName->getFullName(id, name)) { gCacheName->get(id, FALSE, boost::bind(&LLAvatarActions::showOnMap, id)); return; } gFloaterWorldMap->trackAvatar(id, name); LLFloaterReg::showInstance("world_map"); } // static void LLAvatarActions::pay(const LLUUID& id) { LLNotification::Params params("BusyModePay"); params.functor.function(boost::bind(&LLAvatarActions::handlePay, _1, _2, id)); if (gAgent.getBusy()) { // warn users of being in busy mode during a transaction LLNotifications::instance().add(params); } else { LLNotifications::instance().forceResponse(params, 1); } } // static void LLAvatarActions::kick(const LLUUID& id) { LLSD payload; payload["avatar_id"] = id; LLNotifications::instance().add("KickUser", LLSD(), payload, handleKick); } // static void LLAvatarActions::freeze(const LLUUID& id) { LLSD payload; payload["avatar_id"] = id; LLNotifications::instance().add("FreezeUser", LLSD(), payload, handleFreeze); } // static void LLAvatarActions::unfreeze(const LLUUID& id) { LLSD payload; payload["avatar_id"] = id; LLNotifications::instance().add("UnFreezeUser", LLSD(), payload, handleUnfreeze); } //static void LLAvatarActions::csr(const LLUUID& id, std::string name) { if (name.empty()) return; std::string url = "http://csr.lindenlab.com/agent/"; // slow and stupid, but it's late S32 len = name.length(); for (S32 i = 0; i < len; i++) { if (name[i] == ' ') { url += "%20"; } else { url += name[i]; } } LLWeb::loadURL(url); } //static void LLAvatarActions::share(const LLUUID& id) { LLSD key; LLSideTray::getInstance()->showPanel("sidepanel_inventory", key); LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL,id); if (!gIMMgr->hasSession(session_id)) { startIM(id); } if (gIMMgr->hasSession(session_id)) { // we should always get here, but check to verify anyways LLIMModel::getInstance()->addMessage(session_id, SYSTEM_FROM, LLUUID::null, LLTrans::getString("share_alert"), false); } } namespace action_give_inventory { typedef std::set<LLUUID> uuid_set_t; /** * Returns a pointer to 'Add More' inventory panel of Edit Outfit SP. */ static LLInventoryPanel* get_outfit_editor_inventory_panel() { LLPanelOutfitEdit* panel_outfit_edit = dynamic_cast<LLPanelOutfitEdit*>(LLSideTray::getInstance()->getPanel("panel_outfit_edit")); if (NULL == panel_outfit_edit) return NULL; LLInventoryPanel* inventory_panel = panel_outfit_edit->findChild<LLInventoryPanel>("folder_view"); return inventory_panel; } /** * Checks My Inventory visibility. */ static bool is_give_inventory_acceptable() { LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); if (!active_panel) { active_panel = get_outfit_editor_inventory_panel(); if (!active_panel) return false; } // check selection in the panel const uuid_set_t inventory_selected_uuids = active_panel->getRootFolder()->getSelectionList(); if (inventory_selected_uuids.empty()) return false; // nothing selected bool acceptable = false; uuid_set_t::const_iterator it = inventory_selected_uuids.begin(); const uuid_set_t::const_iterator it_end = inventory_selected_uuids.end(); for (; it != it_end; ++it) { LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); // any category can be offered. if (inv_cat) { acceptable = true; continue; } LLViewerInventoryItem* inv_item = gInventory.getItem(*it); // check if inventory item can be given if (LLGiveInventory::isInventoryGiveAcceptable(inv_item)) { acceptable = true; continue; } // there are neither item nor category in inventory acceptable = false; break; } return acceptable; } static void build_residents_string(const std::vector<std::string>& avatar_names, std::string& residents_string) { llassert(avatar_names.size() > 0); const std::string& separator = LLTrans::getString("words_separator"); for (std::vector<std::string>::const_iterator it = avatar_names.begin(); ; ) { residents_string.append(*it); if (++it == avatar_names.end()) { break; } residents_string.append(separator); } } static void build_items_string(const uuid_set_t& inventory_selected_uuids , std::string& items_string) { llassert(inventory_selected_uuids.size() > 0); const std::string& separator = LLTrans::getString("words_separator"); for (uuid_set_t::const_iterator it = inventory_selected_uuids.begin(); ; ) { LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); if (NULL != inv_cat) { items_string = inv_cat->getName(); break; } LLViewerInventoryItem* inv_item = gInventory.getItem(*it); if (NULL != inv_item) { items_string.append(inv_item->getName()); } if(++it == inventory_selected_uuids.end()) { break; } items_string.append(separator); } } struct LLShareInfo : public LLSingleton<LLShareInfo> { std::vector<std::string> mAvatarNames; uuid_vec_t mAvatarUuids; }; static void give_inventory_cb(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); // if Cancel pressed if (option == 1) { return; } LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); if (NULL == active_panel) { return; } const uuid_set_t inventory_selected_uuids = active_panel->getRootFolder()->getSelectionList(); if (inventory_selected_uuids.empty()) { return; } S32 count = LLShareInfo::instance().mAvatarNames.size(); bool shared = false; // iterate through avatars for(S32 i = 0; i < count; ++i) { const LLUUID& avatar_uuid = LLShareInfo::instance().mAvatarUuids[i]; // We souldn't open IM session, just calculate session ID for logging purpose. See EXT-6710 const LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, avatar_uuid); uuid_set_t::const_iterator it = inventory_selected_uuids.begin(); const uuid_set_t::const_iterator it_end = inventory_selected_uuids.end(); const std::string& separator = LLTrans::getString("words_separator"); std::string noncopy_item_names; LLSD noncopy_items = LLSD::emptyArray(); // iterate through selected inventory objects for (; it != it_end; ++it) { LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); if (inv_cat) { LLGiveInventory::doGiveInventoryCategory(avatar_uuid, inv_cat, session_id); shared = true; break; } LLViewerInventoryItem* inv_item = gInventory.getItem(*it); if (!inv_item->getPermissions().allowCopyBy(gAgentID)) { if (!noncopy_item_names.empty()) { noncopy_item_names.append(separator); } noncopy_item_names.append(inv_item->getName()); noncopy_items.append(*it); } else { LLGiveInventory::doGiveInventoryItem(avatar_uuid, inv_item, session_id); shared = true; } } if (noncopy_items.beginArray() != noncopy_items.endArray()) { LLSD substitutions; substitutions["ITEMS"] = noncopy_item_names; LLSD payload; payload["agent_id"] = avatar_uuid; payload["items"] = noncopy_items; LLNotificationsUtil::add("CannotCopyWarning", substitutions, payload, &LLGiveInventory::handleCopyProtectedItem); break; } } if (shared) { LLFloaterReg::hideInstance("avatar_picker"); LLNotificationsUtil::add("ItemsShared"); } } /** * Performs "give inventory" operations for provided avatars. * * Sends one requests to give all selected inventory items for each passed avatar. * Avatars are represent by two vectors: names and UUIDs which must be sychronized with each other. * * @param avatar_names - avatar names request to be sent. * @param avatar_uuids - avatar names request to be sent. */ static void give_inventory(const std::vector<std::string>& avatar_names, const uuid_vec_t& avatar_uuids) { llassert(avatar_names.size() == avatar_uuids.size()); LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); if (!active_panel) { active_panel = get_outfit_editor_inventory_panel(); if (!active_panel) return; } const uuid_set_t inventory_selected_uuids = active_panel->getRootFolder()->getSelectionList(); if (inventory_selected_uuids.empty()) { return; } std::string residents; build_residents_string(avatar_names, residents); std::string items; build_items_string(inventory_selected_uuids, items); LLSD substitutions; substitutions["RESIDENTS"] = residents; substitutions["ITEMS"] = items; LLShareInfo::instance().mAvatarNames = avatar_names; LLShareInfo::instance().mAvatarUuids = avatar_uuids; LLNotificationsUtil::add("ShareItemsConfirmation", substitutions, LLSD(), &give_inventory_cb); } } //static void LLAvatarActions::shareWithAvatars() { using namespace action_give_inventory; LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(give_inventory, _1, _2), TRUE, FALSE); picker->setOkBtnEnableCb(boost::bind(is_give_inventory_acceptable)); picker->openFriendsTab(); LLNotificationsUtil::add("ShareNotification"); } // static void LLAvatarActions::toggleBlock(const LLUUID& id) { std::string name; gCacheName->getFullName(id, name); LLMute mute(id, name, LLMute::AGENT); if (LLMuteList::getInstance()->isMuted(mute.mID, mute.mName)) { LLMuteList::getInstance()->remove(mute); } else { LLMuteList::getInstance()->add(mute); } } // static bool LLAvatarActions::canOfferTeleport(const LLUUID& id) { // First use LLAvatarTracker::isBuddy() // If LLAvatarTracker::instance().isBuddyOnline function only is used // then for avatars that are online and not a friend it will return false. // But we should give an ability to offer a teleport for such avatars. if(LLAvatarTracker::instance().isBuddy(id)) { return LLAvatarTracker::instance().isBuddyOnline(id); } return true; } void LLAvatarActions::inviteToGroup(const LLUUID& id) { LLFloaterGroupPicker* widget = LLFloaterReg::showTypedInstance<LLFloaterGroupPicker>("group_picker", LLSD(id)); if (widget) { widget->center(); widget->setPowersMask(GP_MEMBER_INVITE); widget->removeNoneOption(); widget->setSelectGroupCallback(boost::bind(callback_invite_to_group, _1, id)); } } //== private methods ======================================================================================== // static bool LLAvatarActions::handleRemove(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); const LLSD& ids = notification["payload"]["ids"]; for (LLSD::array_const_iterator itr = ids.beginArray(); itr != ids.endArray(); ++itr) { LLUUID id = itr->asUUID(); const LLRelationship* ip = LLAvatarTracker::instance().getBuddyInfo(id); if (ip) { switch (option) { case 0: // YES if( ip->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)) { LLAvatarTracker::instance().empower(id, FALSE); LLAvatarTracker::instance().notifyObservers(); } LLAvatarTracker::instance().terminateBuddy(id); LLAvatarTracker::instance().notifyObservers(); break; case 1: // NO default: llinfos << "No removal performed." << llendl; break; } } } return false; } // static bool LLAvatarActions::handlePay(const LLSD& notification, const LLSD& response, LLUUID avatar_id) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (option == 0) { gAgent.clearBusy(); } LLFloaterPayUtil::payDirectly(&give_money, avatar_id, /*is_group=*/false); return false; } // static void LLAvatarActions::callback_invite_to_group(LLUUID group_id, LLUUID id) { uuid_vec_t agent_ids; agent_ids.push_back(id); LLFloaterGroupInvite::showForGroup(group_id, &agent_ids); } // static bool LLAvatarActions::callbackAddFriendWithMessage(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (option == 0) { requestFriendship(notification["payload"]["id"].asUUID(), notification["payload"]["name"].asString(), response["message"].asString()); } return false; } // static bool LLAvatarActions::handleKick(const LLSD& notification, const LLSD& response) { S32 option = LLNotification::getSelectedOption(notification, response); if (option == 0) { LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_GodKickUser); msg->nextBlockFast(_PREHASH_UserInfo); msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); msg->addU32("KickFlags", KICK_FLAGS_DEFAULT ); msg->addStringFast(_PREHASH_Reason, response["message"].asString() ); gAgent.sendReliableMessage(); } return false; } bool LLAvatarActions::handleFreeze(const LLSD& notification, const LLSD& response) { S32 option = LLNotification::getSelectedOption(notification, response); if (option == 0) { LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_GodKickUser); msg->nextBlockFast(_PREHASH_UserInfo); msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); msg->addU32("KickFlags", KICK_FLAGS_FREEZE ); msg->addStringFast(_PREHASH_Reason, response["message"].asString() ); gAgent.sendReliableMessage(); } return false; } bool LLAvatarActions::handleUnfreeze(const LLSD& notification, const LLSD& response) { S32 option = LLNotification::getSelectedOption(notification, response); std::string text = response["message"].asString(); if (option == 0) { LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_GodKickUser); msg->nextBlockFast(_PREHASH_UserInfo); msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); msg->addU32("KickFlags", KICK_FLAGS_UNFREEZE ); msg->addStringFast(_PREHASH_Reason, text ); gAgent.sendReliableMessage(); } return false; } // static bool LLAvatarActions::callbackAddFriend(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (option == 0) { // Servers older than 1.25 require the text of the message to be the // calling card folder ID for the offering user. JC LLUUID calling_card_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); std::string message = calling_card_folder_id.asString(); requestFriendship(notification["payload"]["id"].asUUID(), notification["payload"]["name"].asString(), message); } return false; } // static void LLAvatarActions::requestFriendship(const LLUUID& target_id, const std::string& target_name, const std::string& message) { const LLUUID calling_card_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); send_improved_im(target_id, target_name, message, IM_ONLINE, IM_FRIENDSHIP_OFFERED, calling_card_folder_id); LLSD args; args["TO_NAME"] = target_name; LLSD payload; payload["from_id"] = target_id; payload["SESSION_NAME"] = target_name; payload["SUPPRESS_TOAST"] = true; LLNotificationsUtil::add("FriendshipOffered", args, payload); } //static bool LLAvatarActions::isFriend(const LLUUID& id) { return ( NULL != LLAvatarTracker::instance().getBuddyInfo(id) ); } // static bool LLAvatarActions::isBlocked(const LLUUID& id) { std::string name; gCacheName->getFullName(id, name); return LLMuteList::getInstance()->isMuted(id, name); } // static bool LLAvatarActions::canBlock(const LLUUID& id) { std::string firstname, lastname; gCacheName->getName(id, firstname, lastname); bool is_linden = !LLStringUtil::compareStrings(lastname, "Linden"); bool is_self = id == gAgentID; return !is_self && !is_linden; }