From 8da32f68dc927fb2d7ba31eb5fd8823cca1dbf5d Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sun, 30 Nov 2025 16:23:22 +0100 Subject: [PATCH] Make AO not only remember current set name, but also current animation for each state --- indra/newview/aoengine.cpp | 137 +++++++++++------- indra/newview/aoengine.h | 8 +- indra/newview/aoset.cpp | 7 +- .../app_settings/settings_per_account.xml | 8 +- indra/newview/llappviewer.cpp | 4 +- 5 files changed, 99 insertions(+), 65 deletions(-) diff --git a/indra/newview/aoengine.cpp b/indra/newview/aoengine.cpp index 08cbc41a1c..acb7e0348f 100644 --- a/indra/newview/aoengine.cpp +++ b/indra/newview/aoengine.cpp @@ -40,10 +40,11 @@ #include "llnotificationsutil.h" #include "llstring.h" #include "llviewercontrol.h" +#include "llxorcipher.h" -#define ROOT_AO_FOLDER "#AO" -#include +#define ROOT_AO_FOLDER "#AO" +static const LLUUID ENCRYPTION_MAGIC_ID("4b552ff5-fd63-408c-8288-cd09429852ba"); constexpr F32 INVENTORY_POLLING_INTERVAL = 5.0f; AOEngine::AOEngine() : @@ -68,6 +69,28 @@ AOEngine::AOEngine() : AOEngine::~AOEngine() { + LLSD currentState = LLSD().with("CurrentSet", mCurrentSet->getName()); + LLSD currentAnimations; + for (S32 index = 0; index < AOSet::AOSTATES_MAX; ++index) + { + if (auto state = mCurrentSet->getState(index); state && !state->mAnimations.empty()) + { + LL_DEBUGS("AOEngine") << "Storing current animation for state " << index << ": Animation index " << state->mCurrentAnimation << LL_ENDL; + LLUUID shadow_id{ state->mAnimations[state->mCurrentAnimation].mAssetUUID }; + LLXORCipher cipher(ENCRYPTION_MAGIC_ID.mData, UUID_BYTES); + cipher.encrypt(shadow_id.mData, UUID_BYTES); + currentAnimations.insert(state->mName, shadow_id); + } + else + { + LL_DEBUGS("AOEngine") << "No state " << index << " or no animations defined for this state" << LL_ENDL; + } + } + currentState.insert("CurrentStateAnimations", currentAnimations); + LL_DEBUGS("AOEngine") << "Stored AO state: " << currentState << LL_ENDL; + + gSavedPerAccountSettings.setLLSD("FSCurrentAOState", currentState); + clear(false); if (mRegionChangeConnection.connected()) @@ -458,11 +481,9 @@ void AOEngine::enable(bool enable) // stop all overriders, catch leftovers for (S32 index = 0; index < AOSet::AOSTATES_MAX; ++index) { - state = mCurrentSet->getState(index); - if (state) + if (auto state = mCurrentSet->getState(index)) { - LLUUID animation = state->mCurrentAnimationID; - if (animation.notNull()) + if (LLUUID animation = state->mCurrentAnimationID; animation.notNull()) { LL_DEBUGS("AOEngine") << "Stopping leftover animation from state " << state->mName << LL_ENDL; gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); @@ -488,7 +509,7 @@ void AOEngine::enable(bool enable) void AOEngine::setStateCycleTimer(const AOSet::AOState* state) { - F32 timeout = (F32)state->mCycleTime; + F32 timeout = state->mCycleTime; LL_DEBUGS("AOEngine") << "Setting cycle timeout for state " << state->mName << " of " << timeout << LL_ENDL; if (timeout > 0.0f) { @@ -583,7 +604,7 @@ const LLUUID AOEngine::override(const LLUUID& motion, bool start) // case, as it plays at the same time as other motions if (motion != ANIM_AGENT_TYPE) { - constexpr S32 cleanupStates[]= + constexpr S32 cleanupStates[] = { AOSet::Standing, AOSet::Walking, @@ -615,15 +636,14 @@ const LLUUID AOEngine::override(const LLUUID& motion, bool start) while ((stateNum = cleanupStates[index]) != AOSet::AOSTATES_MAX) { // check if the next state is the one we are currently animating and skip that - AOSet::AOState* stateToCheck = mCurrentSet->getState(stateNum); - if (stateToCheck != state) + if (AOSet::AOState* stateToCheck = mCurrentSet->getState(stateNum); stateToCheck != state) { // check if there is an animation left over for that state if (!stateToCheck->mCurrentAnimationID.isNull()) { LL_WARNS() << "cleaning up animation in state " << stateToCheck->mName << LL_ENDL; - // stop the leftover animation locally and in the region for everyone + // stop the leftover animation locally and in the region for everyone gAgent.sendAnimationRequest(stateToCheck->mCurrentAnimationID, ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(stateToCheck->mCurrentAnimationID); @@ -702,7 +722,7 @@ const LLUUID AOEngine::override(const LLUUID& motion, bool start) } state->mCurrentAnimationID = animation; - LL_DEBUGS("AOEngine") << "overriding " << gAnimLibrary.animationName(motion) + LL_DEBUGS("AOEngine") << "overriding " << gAnimLibrary.animationName(motion) << " with " << animation << " in state " << state->mName << " of set " << mCurrentSet->getName() @@ -803,22 +823,21 @@ const LLUUID AOEngine::override(const LLUUID& motion, bool start) void AOEngine::checkSitCancel() { - if (foreignAnimations()) + if (!foreignAnimations()) + return; + + if (AOSet::AOState* aoState = mCurrentSet->getStateByRemapID(ANIM_AGENT_SIT)) { - if (AOSet::AOState* aoState = mCurrentSet->getStateByRemapID(ANIM_AGENT_SIT); aoState) + if (LLUUID animation = aoState->mCurrentAnimationID; animation.notNull()) { - LLUUID animation = aoState->mCurrentAnimationID; - if (animation.notNull()) - { - LL_DEBUGS("AOEngine") << "Stopping sit animation due to foreign animations running" << LL_ENDL; - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); - // remove cycle point cover-up - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(animation); - mSitCancelTimer.stop(); - // stop cycle tiemr - mCurrentSet->stopTimer(); - } + LL_DEBUGS("AOEngine") << "Stopping sit animation due to foreign animations running" << LL_ENDL; + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); + // remove cycle point cover-up + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(animation); + mSitCancelTimer.stop(); + // stop cycle tiemr + mCurrentSet->stopTimer(); } } } @@ -918,7 +937,7 @@ void AOEngine::cycle(eCycleMode cycleMode) { LL_DEBUGS("AOEngine") << "Asset UUID for cycled animation " << anim.mName << " not yet known, try to find it." << LL_ENDL; - if(LLViewerInventoryItem* item = gInventory.getItem(anim.mOriginalUUID) ; item) + if (LLViewerInventoryItem* item = gInventory.getItem(anim.mOriginalUUID); item) { LL_DEBUGS("AOEngine") << "Found asset UUID for cycled animation: " << item->getAssetUUID() << " - Updating AOAnimation.mAssetUUID" << LL_ENDL; anim.mAssetUUID = item->getAssetUUID(); @@ -992,14 +1011,13 @@ void AOEngine::playAnimation(const LLUUID& animation) return; } - if (!state->mAnimations.size()) + if (state->mAnimations.empty()) { LL_DEBUGS("AOEngine") << "cycle without animations in state." << LL_ENDL; return; } LLViewerInventoryItem* item = gInventory.getItem(animation); - if (!item) { LL_WARNS("AOEngine") << "Inventory item for animation " << animation << " not found." << LL_ENDL; @@ -1007,10 +1025,10 @@ void AOEngine::playAnimation(const LLUUID& animation) } AOSet::AOAnimation anim; - anim.mName = item->LLInventoryItem::getName(); + anim.mName = item->getName(); anim.mInventoryUUID = item->getUUID(); anim.mOriginalUUID = item->getLinkedUUID(); - anim.mAssetUUID = LLUUID::null; + anim.mAssetUUID.setNull(); // if we can find the original animation already right here, save its asset ID, otherwise this will // be tried again in AOSet::getAnimationForState() and/or AOEngine::cycle() @@ -1100,7 +1118,7 @@ void AOEngine::updateSortOrder(AOSet::AOState* state) std::ostringstream numStr(""); numStr << index; - LL_DEBUGS("AOEngine") << "sort order is " << sortOrder << " but index is " << index + LL_DEBUGS("AOEngine") << "sort order is " << sortOrder << " but index is " << index << ", setting sort order description: " << numStr.str() << LL_ENDL; state->mAnimations[index].mSortOrder = index; @@ -1111,8 +1129,8 @@ void AOEngine::updateSortOrder(AOSet::AOState* state) LL_WARNS("AOEngine") << "NULL inventory item found while trying to copy " << state->mAnimations[index].mInventoryUUID << LL_ENDL; continue; } - LLPointer newItem = new LLViewerInventoryItem(item); + LLPointer newItem = new LLViewerInventoryItem(item); newItem->setDescription(numStr.str()); newItem->setComplete(true); newItem->updateServer(false); @@ -1181,7 +1199,7 @@ void AOEngine::addAnimation(const AOSet* set, AOSet::AOState* state, const LLInv bool success = createAnimationLink(state, item); gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); - if(success) + if (success) { if (reload) { @@ -1197,7 +1215,7 @@ void AOEngine::addAnimation(const AOSet* set, AOSet::AOState* state, const LLInv state->mAddQueue.push_back(item); // if this is the first queued animation for this state, create the folder asyncronously - if(state->mAddQueue.size() == 1) + if (state->mAddQueue.size() == 1) { gInventory.createNewCategory(set->getInventoryUUID(), LLFolderType::FT_NONE, state->mName, [this, state, reload, wasProtected](const LLUUID &new_cat_id) { @@ -1440,7 +1458,7 @@ bool AOEngine::swapWithNext(AOSet::AOState* state, S32 index) return true; } -void AOEngine::reloadStateAnimations(AOSet::AOState* state) +void AOEngine::reloadStateAnimations(AOSet* set, AOSet::AOState* state) { LLInventoryModel::item_array_t* items; LLInventoryModel::cat_array_t* dummy; @@ -1458,11 +1476,10 @@ void AOEngine::reloadStateAnimations(AOSet::AOState* state) << " asset " << item->getAssetUUID() << LL_ENDL; AOSet::AOAnimation anim; - anim.mName = item->LLInventoryItem::getName(); + anim.mName = item->getName(); anim.mInventoryUUID = item->getUUID(); anim.mOriginalUUID = item->getLinkedUUID(); - - anim.mAssetUUID = LLUUID::null; + anim.mAssetUUID.setNull(); // if we can find the original animation already right here, save its asset ID, otherwise this will // be tried again in AOSet::getAnimationForState() and/or AOEngine::cycle() @@ -1509,6 +1526,24 @@ void AOEngine::reloadStateAnimations(AOSet::AOState* state) } updateSortOrder(state); + + if (auto currentState = gSavedPerAccountSettings.getLLSD("FSCurrentAOState"); + currentState.has("CurrentSet") && currentState["CurrentSet"].asString() == set->getName() && + currentState.has("CurrentStateAnimations") && currentState["CurrentStateAnimations"].has(state->mName)) + { + auto currentStateAnimId{ currentState["CurrentStateAnimations"][state->mName].asUUID() }; + LLXORCipher cipher(ENCRYPTION_MAGIC_ID.mData, UUID_BYTES); + cipher.decrypt(currentStateAnimId.mData, UUID_BYTES); + + for (const auto& animation : state->mAnimations) + { + if (animation.mAssetUUID == currentStateAnimId) + { + state->mCurrentAnimation = animation.mSortOrder; + break; + } + } + } } void AOEngine::update() @@ -1568,9 +1603,10 @@ void AOEngine::update() newSet->setName(setName); mSets.emplace_back(newSet); - if (auto currentSetName = gSavedPerAccountSettings.getString("FSCurrentAOSet"); currentSetName == setName) + if (auto currentState = gSavedPerAccountSettings.getLLSD("FSCurrentAOState"); + currentState.has("CurrentSet") && currentState["CurrentSet"].asString() == setName) { - LL_DEBUGS("AOEngine") << "Selecting current set from settings: " << currentSetName << LL_ENDL; + LL_DEBUGS("AOEngine") << "Selecting current set from settings: " << setName << LL_ENDL; mCurrentSet = newSet; } } @@ -1677,7 +1713,7 @@ void AOEngine::update() newSet->setComplete(false); continue; } - reloadStateAnimations(state); + reloadStateAnimations(newSet, state); } } else @@ -1772,7 +1808,6 @@ void AOEngine::selectSet(AOSet* set) } mCurrentSet = set; - gSavedPerAccountSettings.setString("FSCurrentAOSet", mCurrentSet->getName()); if (mEnabled) { @@ -2185,8 +2220,7 @@ void AOEngine::onNotecardLoadComplete(const LLUUID& assetUUID, LLAssetType::ETyp char* buffer = new char[notecardSize + 1]; buffer[notecardSize] = 0; - bool ret = file.read((U8*)buffer, notecardSize); - if (ret) + if (file.read((U8*)buffer, notecardSize)) { AOEngine::instance().parseNotecard(buffer); } @@ -2487,6 +2521,11 @@ void AOEngine::onRegionChange() gAgent.sendAnimationRequest(mLastMotion, ANIM_REQUEST_START); } +void AOEngine::updatePersistedStateAnimations() +{ + +} + // ---------------------------------------------------- AOSitCancelTimer::AOSitCancelTimer() @@ -2496,10 +2535,6 @@ AOSitCancelTimer::AOSitCancelTimer() mEventTimer.stop(); } -AOSitCancelTimer::~AOSitCancelTimer() -{ -} - void AOSitCancelTimer::oneShot() { mTickCount = 0; @@ -2534,10 +2569,6 @@ AOTimerCollection::AOTimerCollection() updateTimers(); } -AOTimerCollection::~AOTimerCollection() -{ -} - bool AOTimerCollection::tick() { if (mInventoryTimer) diff --git a/indra/newview/aoengine.h b/indra/newview/aoengine.h index 438fe217b9..fc5b3f3bfd 100644 --- a/indra/newview/aoengine.h +++ b/indra/newview/aoengine.h @@ -39,7 +39,7 @@ class AOTimerCollection { public: AOTimerCollection(); - ~AOTimerCollection(); + ~AOTimerCollection() = default; virtual bool tick(); @@ -64,7 +64,7 @@ class AOSitCancelTimer { public: AOSitCancelTimer(); - ~AOSitCancelTimer(); + ~AOSitCancelTimer() = default; void oneShot(); void stop(); @@ -101,7 +101,7 @@ class AOEngine void tick(); void update(); void reload(bool); - void reloadStateAnimations(AOSet::AOState* state); + void reloadStateAnimations(AOSet* set, AOSet::AOState* state); void clear(bool from_timer); const LLUUID& getAOFolder() const; @@ -196,6 +196,8 @@ class AOEngine void* userdata, S32 status, LLExtStat extStatus); void parseNotecard(const char* buffer); + void updatePersistedStateAnimations(); + updated_signal_t mUpdatedSignal; animation_changed_signal_t mAnimationChangedSignal; diff --git a/indra/newview/aoset.cpp b/indra/newview/aoset.cpp index 8d5e8d17de..d44f9cd848 100644 --- a/indra/newview/aoset.cpp +++ b/indra/newview/aoset.cpp @@ -171,8 +171,7 @@ const LLUUID& AOSet::getAnimationForState(AOState* state) const { if (state) { - auto numOfAnimations = state->mAnimations.size(); - if (numOfAnimations) + if (auto numOfAnimations = state->mAnimations.size(); numOfAnimations > 0) { if (state->mCycle) { @@ -198,7 +197,7 @@ const LLUUID& AOSet::getAnimationForState(AOState* state) const { LL_DEBUGS("AOEngine") << "Asset UUID for chosen animation " << anim.mName << " not yet known, try to find it." << LL_ENDL; - if(LLViewerInventoryItem* item = gInventory.getItem(anim.mInventoryUUID) ; item) + if (LLViewerInventoryItem* item = gInventory.getItem(anim.mInventoryUUID)) { LL_DEBUGS("AOEngine") << "Found asset UUID for chosen animation: " << item->getAssetUUID() << " - Updating AOAnimation.mAssetUUID" << LL_ENDL; anim.mAssetUUID = item->getAssetUUID(); @@ -256,7 +255,7 @@ const std::string& AOSet::getName() const void AOSet::setName(const std::string& name) { - mName=name; + mName = name; } bool AOSet::getSitOverride() const diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml index 57a865e1f6..c95f78f244 100644 --- a/indra/newview/app_settings/settings_per_account.xml +++ b/indra/newview/app_settings/settings_per_account.xml @@ -1517,16 +1517,16 @@ Value 1 - FSCurrentAOSet + FSCurrentAOState Comment - The currently active AO set + The currently active AO set and animation per state Persist 1 Type - String + LLSD Value - + diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 4745730fe0..11f45f6732 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -292,10 +292,10 @@ using namespace LL; #include "nd/ndetw.h" // Windows Event Tracing, does nothing on OSX/Linux. #include "nd/ndlogthrottle.h" +#include "aoengine.h" #include "fsradar.h" #include "fsassetblacklist.h" #include "bugsplatattributes.h" -// #include "fstelemetry.h" // Tracy profiler support #if LL_LINUX && LL_GTK #include "glib.h" @@ -2270,6 +2270,8 @@ bool LLAppViewer::cleanup() LLEnvironment::getInstance()->saveToSettings(); } + AOEngine::deleteSingleton(); + // Must do this after all panels have been deleted because panels that have persistent rects // save their rects on delete. if(mSaveSettingsOnExit) // Backup Settings