diff options
author | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2023-10-14 02:40:59 +0300 |
---|---|---|
committer | akleshchev <117672381+akleshchev@users.noreply.github.com> | 2023-10-17 21:26:39 +0300 |
commit | a62f5bfac1e55455c67e1c36eb388af99795dab6 (patch) | |
tree | 46fed0ab92d4b9e34c2d7e7c6abfdd7c7498a238 /indra/newview | |
parent | 19ac7d18e811748c9f9c756a7830429714bc8ca2 (diff) |
SL-20232 Allow deletion of worn items #1
Diffstat (limited to 'indra/newview')
-rw-r--r-- | indra/newview/llappearancemgr.cpp | 9 | ||||
-rw-r--r-- | indra/newview/llappearancemgr.h | 4 | ||||
-rw-r--r-- | indra/newview/llcofwearables.cpp | 4 | ||||
-rw-r--r-- | indra/newview/llconversationmodel.h | 2 | ||||
-rw-r--r-- | indra/newview/llinventorybridge.cpp | 15 | ||||
-rw-r--r-- | indra/newview/llinventorybridge.h | 4 | ||||
-rw-r--r-- | indra/newview/llinventoryfunctions.cpp | 87 | ||||
-rw-r--r-- | indra/newview/llinventoryfunctions.h | 2 | ||||
-rw-r--r-- | indra/newview/llinventorygallery.cpp | 6 | ||||
-rw-r--r-- | indra/newview/llinventorygallerymenu.cpp | 2 | ||||
-rw-r--r-- | indra/newview/llpanelobjectinventory.cpp | 8 | ||||
-rw-r--r-- | indra/newview/llpanelwearing.cpp | 4 | ||||
-rw-r--r-- | indra/newview/llwearableitemslist.cpp | 6 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/notifications.xml | 27 |
14 files changed, 136 insertions, 44 deletions
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 8010b84c20..61b86d6a4a 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -3176,6 +3176,7 @@ void LLAppearanceMgr::removeCOFItemLinks(const LLUUID& item_id, LLPointer<LLInve { // Immediate delete remove_inventory_item(item->getUUID(), cb, true); + remove_inventory_item(item->getUUID(), cb, true); gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); } else @@ -4117,7 +4118,7 @@ void LLAppearanceMgr::wearBaseOutfit() updateCOF(base_outfit_id); } -void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove) +void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove, nullary_func_t post_update_func) { LL_DEBUGS("UIUsage") << "removeItemsFromAvatar" << LL_ENDL; LLUIUsage::instance().logCommand("Avatar.RemoveItem"); @@ -4127,7 +4128,7 @@ void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove) LL_WARNS() << "called with empty list, nothing to do" << LL_ENDL; return; } - LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy; + LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy(true, true, post_update_func); for (uuid_vec_t::const_iterator it = ids_to_remove.begin(); it != ids_to_remove.end(); ++it) { const LLUUID& id_to_remove = *it; @@ -4146,11 +4147,11 @@ void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove) } } -void LLAppearanceMgr::removeItemFromAvatar(const LLUUID& id_to_remove) +void LLAppearanceMgr::removeItemFromAvatar(const LLUUID& id_to_remove, nullary_func_t post_update_func) { uuid_vec_t ids_to_remove; ids_to_remove.push_back(id_to_remove); - removeItemsFromAvatar(ids_to_remove); + removeItemsFromAvatar(ids_to_remove, post_update_func); } diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 43839e47a6..80d6587ad3 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -195,8 +195,8 @@ public: bool updateBaseOutfit(); //Remove clothing or detach an object from the agent (a bodypart cannot be removed) - void removeItemsFromAvatar(const uuid_vec_t& item_ids); - void removeItemFromAvatar(const LLUUID& item_id); + void removeItemsFromAvatar(const uuid_vec_t& item_ids, nullary_func_t post_update_func = no_op); + void removeItemFromAvatar(const LLUUID& item_id, nullary_func_t post_update_func = no_op); void onOutfitFolderCreated(const LLUUID& folder_id, bool show_panel); diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index aa2ba752b7..b138db48b5 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -142,7 +142,7 @@ protected: registrar.add("Attachment.Touch", boost::bind(handleMultiple, handle_attachment_touch, mUUIDs)); registrar.add("Attachment.Edit", boost::bind(handleMultiple, handle_item_edit, mUUIDs)); - registrar.add("Attachment.Detach", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs)); + registrar.add("Attachment.Detach", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; enable_registrar.add("Attachment.OnEnable", boost::bind(&CofAttachmentContextMenu::onEnable, this, _2)); @@ -195,7 +195,7 @@ protected: LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; LLUUID selected_id = mUUIDs.back(); - registrar.add("Clothing.TakeOff", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs)); + registrar.add("Clothing.TakeOff", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); registrar.add("Clothing.Replace", boost::bind(replaceWearable, selected_id)); registrar.add("Clothing.Edit", boost::bind(LLAgentWearables::editWearable, selected_id)); registrar.add("Clothing.Create", boost::bind(&CofClothingContextMenu::createNew, this, selected_id)); diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index 3f607d434e..6b2fecdc61 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -82,7 +82,7 @@ public: virtual BOOL isItemRenameable() const { return TRUE; } virtual BOOL renameItem(const std::string& new_name) { mName = new_name; mNeedsRefresh = true; return TRUE; } virtual BOOL isItemMovable( void ) const { return FALSE; } - virtual BOOL isItemRemovable( void ) const { return FALSE; } + virtual BOOL isItemRemovable(bool check_worn = true) const { return FALSE; } virtual BOOL isItemInTrash( void) const { return FALSE; } virtual BOOL removeItem() { return FALSE; } virtual void removeBatch(std::vector<LLFolderViewModelItem*>& batch) { } diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index c41a4c5686..4d7fc41ae3 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -309,9 +309,9 @@ void LLInvFVBridge::setCreationDate(time_t creation_date_utc) // Can be destroyed (or moved to trash) -BOOL LLInvFVBridge::isItemRemovable() const +BOOL LLInvFVBridge::isItemRemovable(bool check_worn) const { - return get_is_item_removable(getInventoryModel(), mUUID); + return get_is_item_removable(getInventoryModel(), mUUID, check_worn); } // Can be moved to another folder @@ -1063,7 +1063,7 @@ void LLInvFVBridge::addDeleteContextMenuOptions(menuentry_vec_t &items, items.push_back(std::string("Delete")); - if (!isItemRemovable() || isPanelActive("Favorite Items")) + if (!isItemRemovable(false) || isPanelActive("Favorite Items")) { disabled_items.push_back(std::string("Delete")); } @@ -2405,20 +2405,21 @@ void LLFolderBridge::update() class LLIsItemRemovable : public LLFolderViewFunctor { public: - LLIsItemRemovable() : mPassed(TRUE) {} + LLIsItemRemovable(bool check_worn = true) : mPassed(TRUE), mCheckWorn(check_worn) {} virtual void doFolder(LLFolderViewFolder* folder) { - mPassed &= folder->getViewModelItem()->isItemRemovable(); + mPassed &= folder->getViewModelItem()->isItemRemovable(mCheckWorn); } virtual void doItem(LLFolderViewItem* item) { - mPassed &= item->getViewModelItem()->isItemRemovable(); + mPassed &= item->getViewModelItem()->isItemRemovable(mCheckWorn); } BOOL mPassed; + bool mCheckWorn; }; // Can be destroyed (or moved to trash) -BOOL LLFolderBridge::isItemRemovable() const +BOOL LLFolderBridge::isItemRemovable(bool check_worn) const { if (!get_is_category_removable(getInventoryModel(), mUUID)) { diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index decaee7db3..62e26b2f28 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -114,7 +114,7 @@ public: virtual BOOL isItemRenameable() const { return TRUE; } virtual BOOL isMultiPreviewAllowed() { return TRUE; } //virtual BOOL renameItem(const std::string& new_name) {} - virtual BOOL isItemRemovable() const; + virtual BOOL isItemRemovable(bool check_worn = true) const; virtual BOOL isItemMovable() const; virtual BOOL isItemInTrash() const; virtual bool isItemInOutfits() const; @@ -321,7 +321,7 @@ public: void* cargo_data, std::string& tooltip_msg); - virtual BOOL isItemRemovable() const; + virtual BOOL isItemRemovable(bool check_worn = true) const; virtual BOOL isItemMovable() const ; virtual BOOL isUpToDate() const; virtual bool isItemCopyable(bool can_copy_as_link = true) const; diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 4aeacae6ed..109dffae6b 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -682,17 +682,17 @@ BOOL get_can_item_be_worn(const LLUUID& id) return FALSE; } -BOOL get_is_item_removable(const LLInventoryModel* model, const LLUUID& id) +bool get_is_item_removable(const LLInventoryModel* model, const LLUUID& id, bool check_worn) { if (!model) { - return FALSE; + return false; } // Can't delete an item that's in the library. if (!model->isObjectDescendentOf(id, gInventory.getRootFolderID())) { - return FALSE; + return false; } // Disable delete from COF folder; have users explicitly choose "detach/take off", @@ -701,20 +701,20 @@ BOOL get_is_item_removable(const LLInventoryModel* model, const LLUUID& id) { if (get_is_item_worn(id)) { - return FALSE; + return false; } } const LLInventoryObject *obj = model->getItem(id); if (obj && obj->getIsLinkType()) { - return TRUE; + return true; } - if (get_is_item_worn(id)) + if (check_worn && get_is_item_worn(id)) { - return FALSE; + return false; } - return TRUE; + return true; } bool get_is_item_editable(const LLUUID& inv_item_id) @@ -2759,7 +2759,7 @@ bool LLFindNonRemovableObjects::operator()(LLInventoryCategory* cat, LLInventory { if (item) { - return !get_is_item_removable(&gInventory, item->getUUID()); + return !get_is_item_removable(&gInventory, item->getUUID(), true); } if (cat) { @@ -3024,6 +3024,8 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root { const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); bool marketplacelistings_item = false; + bool worn_item = false; + bool needs_replacement = false; LLAllDescendentsPassedFilter f; for (std::set<LLFolderViewItem*>::iterator it = selected_items.begin(); (it != selected_items.end()) && (f.allDescendentsPassedFilter()); ++it) { @@ -3037,9 +3039,32 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root marketplacelistings_item = true; break; } + if (get_is_item_worn(viewModel->getUUID())) + { + worn_item = true; + LLWearableType::EType type = viewModel->getWearableType(); + if (type == LLWearableType::WT_SHAPE + || type == LLWearableType::WT_SKIN + || type == LLWearableType::WT_HAIR + || type == LLWearableType::WT_EYES) + { + needs_replacement = true; + break; + } + } } // Fall through to the generic confirmation if the user choose to ignore the specialized one - if ( (!f.allDescendentsPassedFilter()) && !marketplacelistings_item && (!LLNotifications::instance().getIgnored("DeleteFilteredItems")) ) + if (needs_replacement) + { + LLNotificationsUtil::add("CantDeleteRequiredClothing"); + } + else if (worn_item) + { + LLSD payload; + payload["has_worn"] = true; + LLNotificationsUtil::add("DeleteWornItems", LLSD(), payload, boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); + } + else if ( (!f.allDescendentsPassedFilter()) && !marketplacelistings_item && (!LLNotifications::instance().getIgnored("DeleteFilteredItems")) ) { LLNotificationsUtil::add("DeleteFilteredItems", LLSD(), LLSD(), boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); } @@ -3356,17 +3381,55 @@ void LLInventoryAction::removeItemFromDND(LLFolderView* root) } } } - void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle<LLFolderView> root) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (option == 0 && !root.isDead() && !root.get()->isDead()) { + bool has_worn = notification["payload"]["has_worn"].asBoolean(); LLFolderView* folder_root = root.get(); //Need to remove item from DND before item is removed from root folder view //because once removed from root folder view the item is no longer a selected item removeItemFromDND(folder_root); - folder_root->removeSelectedItems(); + + // removeSelectedItems will change selection, collect worn items beforehand + uuid_vec_t worn; + if (has_worn) + { + //Get selected items + LLFolderView::selected_items_t selectedItems = folder_root->getSelectedItems(); + + //If user is in DND and deletes item, make sure the notification is not displayed by removing the notification + //from DND history and .xml file. Once this is done, upon exit of DND mode the item deleted will not show a notification. + for (LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) + { + LLObjectBridge* view_model = dynamic_cast<LLObjectBridge*>((*it)->getViewModelItem()); + + if (view_model && get_is_item_worn(view_model->getUUID())) + { + worn.push_back(view_model->getUUID()); + } + } + } + + // removeSelectedItems will check if items are worn before deletion, + // don't 'unwear' yet to prevent a race condition from unwearing + // and removing simultaneously + folder_root->removeSelectedItems(); + + // unwear then delete the rest + if (!worn.empty()) + { + // should fire once after every item gets detached + LLAppearanceMgr::instance().removeItemsFromAvatar(worn, + [worn]() + { + for (const LLUUID& id : worn) + { + remove_inventory_item(id, NULL); + } + }); + } // Update the marketplace listings that have been affected by the operation updateMarketplaceFolders(); diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index 925217dda3..cac1d77f5e 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -51,7 +51,7 @@ BOOL get_is_item_worn(const LLUUID& id); // Could this item be worn (correct type + not already being worn) BOOL get_can_item_be_worn(const LLUUID& id); -BOOL get_is_item_removable(const LLInventoryModel* model, const LLUUID& id); +bool get_is_item_removable(const LLInventoryModel* model, const LLUUID& id, bool check_worn); // Performs the appropiate edit action (if one exists) for this item bool get_is_item_editable(const LLUUID& inv_item_id); diff --git a/indra/newview/llinventorygallery.cpp b/indra/newview/llinventorygallery.cpp index d4ca58f778..9074c8466a 100644 --- a/indra/newview/llinventorygallery.cpp +++ b/indra/newview/llinventorygallery.cpp @@ -1685,7 +1685,7 @@ BOOL LLInventoryGallery::canCut() const return FALSE; } } - else if (!get_is_item_removable(&gInventory, id)) + else if (!get_is_item_removable(&gInventory, id, true)) { return FALSE; } @@ -1880,7 +1880,7 @@ void LLInventoryGallery::onDelete(const LLSD& notification, const LLSD& response } else { - if (get_is_item_removable(&gInventory, id)) + if (get_is_item_removable(&gInventory, id, true)) { gInventory.removeItem(id); } @@ -1925,7 +1925,7 @@ bool LLInventoryGallery::canDeleteSelection() return false; } } - else if (!get_is_item_removable(&gInventory, id)) + else if (!get_is_item_removable(&gInventory, id, true)) { return false; } diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp index 5f4b816b99..50eefc7d25 100644 --- a/indra/newview/llinventorygallerymenu.cpp +++ b/indra/newview/llinventorygallerymenu.cpp @@ -577,7 +577,7 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men { items.push_back(std::string("Delete")); } - if(!get_is_item_removable(&gInventory, selected_id)) + if(!get_is_item_removable(&gInventory, selected_id, true)) { disabled_items.push_back(std::string("Delete")); disabled_items.push_back(std::string("Cut")); diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index a3bbd00601..922dccf7be 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -129,7 +129,7 @@ public: virtual BOOL isItemRenameable() const; virtual BOOL renameItem(const std::string& new_name); virtual BOOL isItemMovable() const; - virtual BOOL isItemRemovable() const; + virtual BOOL isItemRemovable(bool check_worn = true) const; virtual BOOL removeItem(); virtual void removeBatch(std::vector<LLFolderViewModelItem*>& batch); virtual void move(LLFolderViewModelItem* parent_listener); @@ -335,7 +335,7 @@ BOOL LLTaskInvFVBridge::isItemMovable() const return TRUE; } -BOOL LLTaskInvFVBridge::isItemRemovable() const +BOOL LLTaskInvFVBridge::isItemRemovable(bool check_worn) const { const LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); if(object @@ -587,7 +587,7 @@ public: virtual BOOL isItemRenameable() const; // virtual BOOL isItemCopyable() const { return FALSE; } virtual BOOL renameItem(const std::string& new_name); - virtual BOOL isItemRemovable() const; + virtual BOOL isItemRemovable(bool check_worn = true) const; virtual void buildContextMenu(LLMenuGL& menu, U32 flags); virtual bool hasChildren() const; virtual BOOL startDrag(EDragAndDropType* type, LLUUID* id) const; @@ -647,7 +647,7 @@ BOOL LLTaskCategoryBridge::renameItem(const std::string& new_name) return FALSE; } -BOOL LLTaskCategoryBridge::isItemRemovable() const +BOOL LLTaskCategoryBridge::isItemRemovable(bool check_worn) const { return FALSE; } diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index bfdb0fbc88..35eba16afe 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -110,9 +110,9 @@ protected: registrar.add("Wearing.EditOutfit", boost::bind(&edit_outfit)); registrar.add("Wearing.ShowOriginal", boost::bind(show_item_original, mUUIDs.front())); registrar.add("Wearing.TakeOff", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs)); + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); registrar.add("Wearing.Detach", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs)); + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); LLContextMenu* menu = createFromFile("menu_wearing_tab.xml"); updateMenuItemsVisibility(menu); diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index 948fe55e0d..49c35c7ad5 100644 --- a/indra/newview/llwearableitemslist.cpp +++ b/indra/newview/llwearableitemslist.cpp @@ -925,17 +925,17 @@ LLContextMenu* LLWearableItemsList::ContextMenu::createMenu() registrar.add("Wearable.CreateNew", boost::bind(createNewWearable, selected_id)); registrar.add("Wearable.ShowOriginal", boost::bind(show_item_original, selected_id)); registrar.add("Wearable.TakeOffDetach", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids)); + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); // Register handlers for clothing. registrar.add("Clothing.TakeOff", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids)); + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); // Register handlers for body parts. // Register handlers for attachments. registrar.add("Attachment.Detach", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids)); + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); registrar.add("Attachment.Touch", boost::bind(handle_attachment_touch, selected_id)); registrar.add("Attachment.Profile", boost::bind(show_item_profile, selected_id)); registrar.add("Object.Attach", boost::bind(LLViewerAttachMenu::attachObjects, ids, _2)); diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index ac63786b15..5115bcb27f 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -6153,6 +6153,33 @@ Are you sure you want to delete them? notext="Cancel" yestext="OK"/> </notification> + + <notification + icon="alertmodal.tga" + name="DeleteWornItems" + type="alertmodal"> + <unique/> +Some item(s) you wish to delete are being worn on your avatar. +Remove these items from your avatar? + <tag>confirm</tag> + <usetemplate + name="okcancelbuttons" + notext="Cancel" + yestext="Remove item(s) and delete"/> + </notification> + + <notification + icon="alertmodal.tga" + name="CantDeleteRequiredClothing" + type="alertmodal"> + <unique/> +Some item(s) you wish to delete are required clothing layers (skin, shape, hair, eyes). +You must replace those layers before deleting them. + <tag>confirm</tag> + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> <notification icon="alertmodal.tga" |