/** * @file llgiveinventory.cpp * @brief LLGiveInventory class implementation * * $LicenseInfo:firstyear=2010&license=viewergpl$ * * Copyright (c) 2010, 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 "llgiveinventory.h" // library includes #include "llnotificationsutil.h" #include "lltrans.h" // newview includes #include "llagent.h" #include "llagentdata.h" #include "llagentui.h" #include "llagentwearables.h" #include "llfloatertools.h" // for gFloaterTool #include "llhudeffecttrail.h" #include "llhudmanager.h" #include "llimview.h" #include "llinventory.h" #include "llinventoryfunctions.h" #include "llmutelist.h" #include "llrecentpeople.h" #include "llviewerobjectlist.h" #include "llvoavatarself.h" // MAX ITEMS is based on (sizeof(uuid)+2) * count must be < MTUBYTES // or 18 * count < 1200 => count < 1200/18 => 66. I've cut it down a // bit from there to give some pad. const S32 MAX_ITEMS = 42; class LLGiveable : public LLInventoryCollectFunctor { public: LLGiveable() : mCountLosing(0) {} virtual ~LLGiveable() {} virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); S32 countNoCopy() const { return mCountLosing; } protected: S32 mCountLosing; }; bool LLGiveable::operator()(LLInventoryCategory* cat, LLInventoryItem* item) { // All categories can be given. if (cat) return true; bool allowed = false; if (item) { allowed = itemTransferCommonlyAllowed(item); if (allowed && !item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) { allowed = FALSE; } if (allowed && !item->getPermissions().allowCopyBy(gAgent.getID())) { ++mCountLosing; } } return allowed; } class LLUncopyableItems : public LLInventoryCollectFunctor { public: LLUncopyableItems() {} virtual ~LLUncopyableItems() {} virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); }; bool LLUncopyableItems::operator()(LLInventoryCategory* cat, LLInventoryItem* item) { bool uncopyable = false; if (item) { if (itemTransferCommonlyAllowed(item) && !item->getPermissions().allowCopyBy(gAgent.getID())) { uncopyable = true; } } return uncopyable; } // static bool LLGiveInventory::isInventoryGiveAcceptable(const LLInventoryItem* item) { if (!item) return false; if (!isAgentAvatarValid()) return false; if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID)) { return false; } bool acceptable = true; switch(item->getType()) { case LLAssetType::AT_OBJECT: if (gAgentAvatarp->isWearingAttachment(item->getUUID())) { acceptable = false; } break; case LLAssetType::AT_BODYPART: case LLAssetType::AT_CLOTHING: { BOOL copyable = false; if (item->getPermissions().allowCopyBy(gAgentID)) copyable = true; if (!copyable && gAgentWearables.isWearingItem(item->getUUID())) { acceptable = false; } } break; default: break; } return acceptable; } // static bool LLGiveInventory::isInventoryGroupGiveAcceptable(const LLInventoryItem* item) { if (!item) return false; if (!isAgentAvatarValid()) return false; // These permissions are double checked in the simulator in // LLGroupNoticeInventoryItemFetch::result(). if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID)) { return false; } if (!item->getPermissions().allowCopyBy(gAgent.getID())) { return false; } bool acceptable = true; switch(item->getType()) { case LLAssetType::AT_OBJECT: if (gAgentAvatarp->isWearingAttachment(item->getUUID())) { acceptable = false; } break; default: break; } return acceptable; } // static bool LLGiveInventory::doGiveInventoryItem(const LLUUID& to_agent, const LLInventoryItem* item, const LLUUID& im_session_id/* = LLUUID::null*/) { bool res = true; llinfos << "LLGiveInventory::giveInventory()" << llendl; if (!isInventoryGiveAcceptable(item)) { return false; } if (item->getPermissions().allowCopyBy(gAgentID)) { // just give it away. LLGiveInventory::commitGiveInventoryItem(to_agent, item, im_session_id); } else { // ask if the agent is sure. LLSD substitutions; substitutions["ITEMS"] = item->getName(); LLSD payload; payload["agent_id"] = to_agent; LLSD items = LLSD::emptyArray(); items.append(item->getUUID()); payload["items"] = items; LLNotificationsUtil::add("CannotCopyWarning", substitutions, payload, &LLGiveInventory::handleCopyProtectedItem); res = false; } return res; } void LLGiveInventory::doGiveInventoryCategory(const LLUUID& to_agent, const LLInventoryCategory* cat, const LLUUID& im_session_id) { if (!cat) return; llinfos << "LLGiveInventory::giveInventoryCategory() - " << cat->getUUID() << llendl; if (!isAgentAvatarValid()) return; // Test out how many items are being given. LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; LLGiveable giveable; gInventory.collectDescendentsIf (cat->getUUID(), cats, items, LLInventoryModel::EXCLUDE_TRASH, giveable); S32 count = cats.count(); bool complete = true; for(S32 i = 0; i < count; ++i) { if (!gInventory.isCategoryComplete(cats.get(i)->getUUID())) { complete = false; break; } } if (!complete) { LLNotificationsUtil::add("IncompleteInventory"); return; } count = items.count() + cats.count(); if (count > MAX_ITEMS) { LLNotificationsUtil::add("TooManyItems"); return; } else if (count == 0) { LLNotificationsUtil::add("NoItems"); return; } else { if (0 == giveable.countNoCopy()) { LLGiveInventory::commitGiveInventoryCategory(to_agent, cat, im_session_id); } else { LLSD args; args["COUNT"] = llformat("%d",giveable.countNoCopy()); LLSD payload; payload["agent_id"] = to_agent; payload["folder_id"] = cat->getUUID(); LLNotificationsUtil::add("CannotCopyCountItems", args, payload, &LLGiveInventory::handleCopyProtectedCategory); } } } ////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS ////////////////////////////////////////////////////////////////////////// //static void LLGiveInventory::logInventoryOffer(const LLUUID& to_agent, const LLUUID &im_session_id) { // compute id of possible IM session with agent that has "to_agent" id LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, to_agent); // If this item was given by drag-and-drop into an IM panel, log this action in the IM panel chat. LLSD args; args["user_id"] = to_agent; if (im_session_id.notNull()) { gIMMgr->addSystemMessage(im_session_id, "inventory_item_offered", args); } // If this item was given by drag-and-drop on avatar while IM panel was open, log this action in the IM panel chat. else if (LLIMModel::getInstance()->findIMSession(session_id)) { gIMMgr->addSystemMessage(session_id, "inventory_item_offered", args); } // If this item was given by drag-and-drop on avatar while IM panel wasn't open, log this action to IM history. else { std::string full_name; if (gCacheName->getFullName(to_agent, full_name)) { LLIMModel::instance().logToFile(full_name, LLTrans::getString("SECOND_LIFE"), im_session_id, LLTrans::getString("inventory_item_offered-im")); } } } // static bool LLGiveInventory::handleCopyProtectedItem(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); LLSD itmes = notification["payload"]["items"]; LLInventoryItem* item = NULL; switch(option) { case 0: // "Yes" for (LLSD::array_iterator it = itmes.beginArray(); it != itmes.endArray(); it++) { item = gInventory.getItem((*it).asUUID()); if (item) { LLGiveInventory::commitGiveInventoryItem(notification["payload"]["agent_id"].asUUID(), item); // delete it for now - it will be deleted on the server // quickly enough. gInventory.deleteObject(item->getUUID()); gInventory.notifyObservers(); } else { LLNotificationsUtil::add("CannotGiveItem"); } } break; default: // no, cancel, whatever, who cares, not yes. LLNotificationsUtil::add("TransactionCancelled"); break; } return false; } // static void LLGiveInventory::commitGiveInventoryItem(const LLUUID& to_agent, const LLInventoryItem* item, const LLUUID& im_session_id) { if (!item) return; std::string name; LLAgentUI::buildFullname(name); LLUUID transaction_id; transaction_id.generate(); const S32 BUCKET_SIZE = sizeof(U8) + UUID_BYTES; U8 bucket[BUCKET_SIZE]; bucket[0] = (U8)item->getType(); memcpy(&bucket[1], &(item->getUUID().mData), UUID_BYTES); /* Flawfinder: ignore */ pack_instant_message( gMessageSystem, gAgentID, FALSE, gAgentSessionID, to_agent, name, item->getName(), IM_ONLINE, IM_INVENTORY_OFFERED, transaction_id, 0, LLUUID::null, gAgent.getPositionAgent(), NO_TIMESTAMP, bucket, BUCKET_SIZE); gAgent.sendReliableMessage(); // VEFFECT: giveInventory LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, TRUE); effectp->setSourceObject(gAgentAvatarp); effectp->setTargetObject(gObjectList.findObject(to_agent)); effectp->setDuration(LL_HUD_DUR_SHORT); effectp->setColor(LLColor4U(gAgent.getEffectColor())); gFloaterTools->dirty(); LLMuteList::getInstance()->autoRemove(to_agent, LLMuteList::AR_INVENTORY); logInventoryOffer(to_agent, im_session_id); // add buddy to recent people list LLRecentPeople::instance().add(to_agent); } // static bool LLGiveInventory::handleCopyProtectedCategory(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); LLInventoryCategory* cat = NULL; switch(option) { case 0: // "Yes" cat = gInventory.getCategory(notification["payload"]["folder_id"].asUUID()); if (cat) { LLGiveInventory::commitGiveInventoryCategory(notification["payload"]["agent_id"].asUUID(), cat); LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; LLUncopyableItems remove; gInventory.collectDescendentsIf (cat->getUUID(), cats, items, LLInventoryModel::EXCLUDE_TRASH, remove); S32 count = items.count(); for(S32 i = 0; i < count; ++i) { gInventory.deleteObject(items.get(i)->getUUID()); } gInventory.notifyObservers(); } else { LLNotificationsUtil::add("CannotGiveCategory"); } break; default: // no, cancel, whatever, who cares, not yes. LLNotificationsUtil::add("TransactionCancelled"); break; } return false; } // static void LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, const LLInventoryCategory* cat, const LLUUID& im_session_id) { if (!cat) return; llinfos << "LLGiveInventory::commitGiveInventoryCategory() - " << cat->getUUID() << llendl; // add buddy to recent people list LLRecentPeople::instance().add(to_agent); // Test out how many items are being given. LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; LLGiveable giveable; gInventory.collectDescendentsIf (cat->getUUID(), cats, items, LLInventoryModel::EXCLUDE_TRASH, giveable); // MAX ITEMS is based on (sizeof(uuid)+2) * count must be < // MTUBYTES or 18 * count < 1200 => count < 1200/18 => // 66. I've cut it down a bit from there to give some pad. S32 count = items.count() + cats.count(); if (count > MAX_ITEMS) { LLNotificationsUtil::add("TooManyItems"); return; } else if (count == 0) { LLNotificationsUtil::add("NoItems"); return; } else { std::string name; LLAgentUI::buildFullname(name); LLUUID transaction_id; transaction_id.generate(); S32 bucket_size = (sizeof(U8) + UUID_BYTES) * (count + 1); U8* bucket = new U8[bucket_size]; U8* pos = bucket; U8 type = (U8)cat->getType(); memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ pos += sizeof(U8); memcpy(pos, &(cat->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ pos += UUID_BYTES; S32 i; count = cats.count(); for(i = 0; i < count; ++i) { memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ pos += sizeof(U8); memcpy(pos, &(cats.get(i)->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ pos += UUID_BYTES; } count = items.count(); for(i = 0; i < count; ++i) { type = (U8)items.get(i)->getType(); memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ pos += sizeof(U8); memcpy(pos, &(items.get(i)->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ pos += UUID_BYTES; } pack_instant_message( gMessageSystem, gAgent.getID(), FALSE, gAgent.getSessionID(), to_agent, name, cat->getName(), IM_ONLINE, IM_INVENTORY_OFFERED, transaction_id, 0, LLUUID::null, gAgent.getPositionAgent(), NO_TIMESTAMP, bucket, bucket_size); gAgent.sendReliableMessage(); delete[] bucket; // VEFFECT: giveInventoryCategory LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, TRUE); effectp->setSourceObject(gAgentAvatarp); effectp->setTargetObject(gObjectList.findObject(to_agent)); effectp->setDuration(LL_HUD_DUR_SHORT); effectp->setColor(LLColor4U(gAgent.getEffectColor())); gFloaterTools->dirty(); LLMuteList::getInstance()->autoRemove(to_agent, LLMuteList::AR_INVENTORY); logInventoryOffer(to_agent, im_session_id); } } // EOF