summaryrefslogtreecommitdiff
path: root/indra/newview/llappearancemgr.cpp
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2026-09-01 18:35:23 +0800
committerErik Kundiman <erik@megapahit.org>2026-09-04 21:58:53 +0800
commit00bb3bb6f07660bd914d29a1389342bb3060d254 (patch)
tree431f574922494d2201718f13085f17bc6bd7fb42 /indra/newview/llappearancemgr.cpp
parenta8636720129952c10cf49ab80da21bf8b340b96c (diff)
parent4ef9f8f14b35ed388c294d53767a0dca0e890924 (diff)
Merge remote-tracking branch 'secondlife/release/26.4' into 26.4
Diffstat (limited to 'indra/newview/llappearancemgr.cpp')
-rw-r--r--indra/newview/llappearancemgr.cpp130
1 files changed, 82 insertions, 48 deletions
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index 9636db21c1..2c53a6c433 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -980,7 +980,9 @@ void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, L
if (!holder->isMostRecent())
{
LL_WARNS() << "HP " << holder->index() << " skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL;
- // runway skip here?
+
+ // If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves
+ return;
}
LL_INFOS("Avatar") << "HP " << holder->index() << " recovered item link for type " << type << LL_ENDL;
@@ -1020,7 +1022,6 @@ void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLView
{
if (!holder->isMostRecent())
{
- // runway skip here?
LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL;
// If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves
@@ -4270,77 +4271,110 @@ bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_b
{
if (!item || !item->isWearableType()) return false;
if (item->getType() != LLAssetType::AT_CLOTHING) return false;
- if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false;
S32 pos = gAgentWearables.getWearableIdxFromItem(item);
if (pos < 0) return false; // Not found
+ if (closer_to_body && pos == 0) return false; // already closest to the body
- U32 count = gAgentWearables.getWearableCount(item->getWearableType());
- if (count < 2) return false; // Nothing to swap with
- if (closer_to_body)
+ return reorderWearable(item, closer_to_body ? (U32)(pos - 1) : (U32)(pos + 1));
+}
+
+bool LLAppearanceMgr::reorderWearable(LLViewerInventoryItem* item, U32 new_index)
+{
+ if (!item || !item->isWearableType()) return false;
+ if (item->getType() != LLAssetType::AT_CLOTHING) return false;
+ if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false;
+
+ LLWearableType::EType type = item->getWearableType();
+ U32 count = gAgentWearables.getWearableCount(type);
+ if (count < 2) return false; // nothing to reorder against
+
+ if (new_index >= count) new_index = count - 1;
+
+ S32 cur = gAgentWearables.getWearableIdxFromItem(item);
+ if (cur < 0) return false;
+ if ((U32)cur == new_index) return false; // already in place
+
+ // Update the live layer order first; bail before touching inventory if it fails.
+ if (!gAgentWearables.moveWearableToIndex(item, new_index)) return false;
+
+ persistWearableOrder(type);
+ return true;
+}
+
+bool LLAppearanceMgr::reorderWearableGroup(LLWearableType::EType type, const uuid_vec_t& ordered_link_ids)
+{
+ U32 count = gAgentWearables.getWearableCount(type);
+ if (count < 2) return false;
+ if (ordered_link_ids.size() != count) return false; // order must cover the whole group
+
+ // Validate everything before mutating, so a bad link can't leave it half-reordered.
+ for (const LLUUID& link_id : ordered_link_ids)
{
- if (pos == 0) return false; // already first
+ LLViewerInventoryItem* link = gInventory.getItem(link_id);
+ if (!link || link->getWearableType() != type) return false;
}
- else
+
+ // ordered_link_ids runs furthest-to-closest; body index 0 is closest to the body.
+ // Place each target at its body index, leaving already-placed lower indices untouched.
+ for (U32 body_index = 0; body_index < count; ++body_index)
{
- if (pos == count - 1) return false; // already last
+ const LLUUID& link_id = ordered_link_ids[count - 1 - body_index];
+ LLViewerInventoryItem* link = gInventory.getItem(link_id);
+ if (!link || link->getWearableType() != type) return false;
+ if (!gAgentWearables.moveWearableToIndex(link, body_index)) return false;
}
- U32 old_pos = (U32)pos;
- U32 swap_with = closer_to_body ? old_pos - 1 : old_pos + 1;
- LLUUID swap_item_id = gAgentWearables.getWearableItemID(item->getWearableType(), swap_with);
+ persistWearableOrder(type);
+ return true;
+}
+
+void LLAppearanceMgr::persistWearableOrder(LLWearableType::EType type)
+{
+ U32 count = gAgentWearables.getWearableCount(type);
- // Find link item from item id.
+ // Rewrite the sort-index descriptions for the whole type group in one pass so
+ // the order survives relog, trusting gAgentWearables over existing descriptions.
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
- LLFindWearablesOfType filter_wearables_of_type(item->getWearableType());
+ LLFindWearablesOfType filter_wearables_of_type(type);
gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type);
- if (items.empty()) return false;
- LLViewerInventoryItem* swap_item = nullptr;
- for (auto iter : items)
+ for (U32 i = 0; i < count; ++i)
{
- if (iter->getLinkedUUID() == swap_item_id)
+ LLUUID linked_id = gAgentWearables.getWearableItemID(type, i);
+ if (linked_id.isNull()) continue;
+
+ LLViewerInventoryItem* link = nullptr;
+ for (auto iter : items)
{
- swap_item = iter.get();
- break;
+ if (iter->getLinkedUUID() == linked_id)
+ {
+ link = iter.get();
+ break;
+ }
}
- }
- if (!swap_item)
- {
- return false;
- }
-
- // Description is supposed to hold sort index, but user could have changed
- // order rapidly and there might be a state mismatch between description
- // and gAgentWearables, trust gAgentWearables over description.
- // Generate new description.
- std::string new_desc = build_order_string(item->getWearableType(), old_pos);
- swap_item->setDescription(new_desc);
- new_desc = build_order_string(item->getWearableType(), swap_with);
- item->setDescription(new_desc);
+ if (!link) continue;
- item->setComplete(true);
- item->updateServer(false);
- gInventory.updateItem(item);
+ std::string new_desc = build_order_string(type, i);
+ if (new_desc == link->getActualDescription()) continue;
- swap_item->setComplete(true);
- swap_item->updateServer(false);
- gInventory.updateItem(swap_item);
+ // Keep the local cache consistent immediately (so the COF list does not
+ // flicker back on a refresh), and persist durably via AISv3, matching
+ // updateClothingOrderingInfo() rather than the legacy UDP updateServer().
+ link->setDescription(new_desc);
+ LLSD updates;
+ updates["desc"] = new_desc;
+ update_inventory_item(link->getUUID(), updates, NULL);
+ }
- //to cause appearance of the agent to be updated
- bool result = false;
- if ((result = gAgentWearables.moveWearable(item, closer_to_body)))
+ if (isAgentAvatarValid())
{
- gAgentAvatarp->wearableUpdated(item->getWearableType());
+ gAgentAvatarp->wearableUpdated(type);
}
setOutfitDirty(true);
-
- //*TODO do we need to notify observers here in such a way?
gInventory.notifyObservers();
-
- return result;
}
//static