From b537e7cb9db18f73bbbe8391168382ea1aff0775 Mon Sep 17 00:00:00 2001 From: Rye Mutt <rye@alchemyviewer.org> Date: Sun, 31 Dec 2023 00:01:01 -0500 Subject: [PATCH] Smash the ao back in --- indra/newview/CMakeLists.txt | 16 +- indra/newview/alaoengine.cpp | 2108 -------------- indra/newview/alaoengine.h | 227 -- indra/newview/alaoset.h | 152 - indra/newview/alchatcommand.cpp | 12 +- indra/newview/alfloaterao.cpp | 740 ----- indra/newview/alfloaterao.h | 146 - indra/newview/alpanelaomini.cpp | 28 +- indra/newview/ao.cpp | 904 ++++++ indra/newview/ao.h | 159 ++ indra/newview/aoengine.cpp | 2446 +++++++++++++++++ indra/newview/aoengine.h | 234 ++ indra/newview/{alaoset.cpp => aoset.cpp} | 206 +- indra/newview/aoset.h | 145 + indra/newview/app_settings/commands.xml | 10 + .../settings_per_account_alchemy.xml | 100 +- indra/newview/llagentcamera.cpp | 8 +- indra/newview/llinventorybridge.cpp | 5 +- indra/newview/llinventorymodel.cpp | 7 +- indra/newview/llstatusbar.cpp | 90 +- indra/newview/llstatusbar.h | 6 +- indra/newview/llviewerfloaterreg.cpp | 4 +- indra/newview/llvoavatar.cpp | 28 +- .../skins/default/xui/en/floater_ao.xml | 72 +- .../newview/skins/default/xui/en/panel_ao.xml | 903 +++--- .../skins/default/xui/en/panel_status_bar.xml | 11 +- 26 files changed, 4769 insertions(+), 3998 deletions(-) delete mode 100644 indra/newview/alaoengine.cpp delete mode 100644 indra/newview/alaoengine.h delete mode 100644 indra/newview/alaoset.h delete mode 100644 indra/newview/alfloaterao.cpp delete mode 100644 indra/newview/alfloaterao.h create mode 100644 indra/newview/ao.cpp create mode 100644 indra/newview/ao.h create mode 100644 indra/newview/aoengine.cpp create mode 100644 indra/newview/aoengine.h rename indra/newview/{alaoset.cpp => aoset.cpp} (52%) create mode 100644 indra/newview/aoset.h diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 19fbcd57fab..94cf4419dec 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -84,19 +84,19 @@ endif() set(viewer_SOURCE_FILES alcinematicmode.cpp - #alaoengine.cpp - #alaoset.cpp + ao.cpp + aoengine.cpp + aoset.cpp alavataractions.cpp alavatargroups.cpp alchatcommand.cpp alcontrolcache.cpp - #alfloaterao.cpp alfloaterexploresounds.cpp alfloatergenerictext.cpp alfloaterlightbox.cpp alfloaterparticleeditor.cpp alfloaterregiontracker.cpp - #alpanelaomini.cpp + alpanelaomini.cpp alpanelaopulldown.cpp alpanelmusicticker.cpp alpanelradaralert.cpp @@ -815,19 +815,19 @@ set(viewer_HEADER_FILES CMakeLists.txt ViewerInstall.cmake alcinematicmode.h - #alaoengine.h - #alaoset.h + ao.h + aoengine.h + aoset.h alavataractions.h alavatargroups.h alchatcommand.h alcontrolcache.h - #alfloaterao.h alfloaterexploresounds.h alfloatergenerictext.h alfloaterlightbox.h alfloaterparticleeditor.h alfloaterregiontracker.h - #alpanelaomini.h + alpanelaomini.h alpanelaopulldown.h alpanelmusicticker.h alpanelradaralert.h diff --git a/indra/newview/alaoengine.cpp b/indra/newview/alaoengine.cpp deleted file mode 100644 index 2b662c62367..00000000000 --- a/indra/newview/alaoengine.cpp +++ /dev/null @@ -1,2108 +0,0 @@ -/** - * @file alaoengine.cpp - * @brief The core Animation Overrider engine - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Zi Ree @ Second Life - * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "roles_constants.h" - -#include "alaoengine.h" -#include "alaoset.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llanimationstates.h" -#include "llassetstorage.h" -#include "llfilesystem.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llnotificationsutil.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewerobjectlist.h" -#include "llvoavatarself.h" - -const F32 INVENTORY_POLLING_INTERVAL = 5.0f; - -const std::string ROOT_AO_FOLDER = LLStringExplicit("Animation Overrides"); - -ALAOEngine::ALAOEngine() -: mEnabled(false) -, mInMouselook(false) -, mUnderWater(false) -, mAOFolder(LLUUID::null) -, mLastMotion(ANIM_AGENT_STAND) -, mLastOverriddenMotion(ANIM_AGENT_STAND) -, mCurrentSet(nullptr) -, mDefaultSet(nullptr) -, mImportSet(nullptr) -, mImportCategory(LLUUID::null) -, mImportRetryCount(0) -{ - gSavedPerAccountSettings.getControl("AlchemyAOEnable")->getCommitSignal()->connect(boost::bind(&ALAOEngine::onToggleAOControl, this)); - - mRegionChangeConnection = gAgent.addRegionChangedCallback(boost::bind(&ALAOEngine::onRegionChange, this)); -} - -ALAOEngine::~ALAOEngine() -{ - clear(false); - - if (mRegionChangeConnection.connected()) - { - mRegionChangeConnection.disconnect(); - } -} - -void ALAOEngine::init() -{ - enable(gSavedPerAccountSettings.getBool("AlchemyAOEnable")); -} - -// static -void ALAOEngine::onLoginComplete() -{ - ALAOEngine::instance().init(); -} - -void ALAOEngine::onToggleAOControl() -{ - enable(gSavedPerAccountSettings.getBool("AlchemyAOEnable")); -} - -void ALAOEngine::clear(bool aFromTimer) -{ - mOldSets.insert(mOldSets.end(), mSets.begin(), mSets.end()); - mSets.clear(); - - mCurrentSet = nullptr; - - if (!aFromTimer) - { - std::for_each(mOldSets.begin(), mOldSets.end(), DeletePointer()); - mOldSets.clear(); - - std::for_each(mOldImportSets.begin(), mOldImportSets.end(), DeletePointer()); - mOldImportSets.clear(); - } -} - -void ALAOEngine::stopAllStandVariants() -{ - LL_DEBUGS("AOEngine") << "stopping all STAND variants." << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_STAND_1, ANIM_REQUEST_STOP); - gAgent.sendAnimationRequest(ANIM_AGENT_STAND_2, ANIM_REQUEST_STOP); - gAgent.sendAnimationRequest(ANIM_AGENT_STAND_3, ANIM_REQUEST_STOP); - gAgent.sendAnimationRequest(ANIM_AGENT_STAND_4, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_1); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_2); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_3); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_4); -} - -void ALAOEngine::stopAllSitVariants() -{ - LL_DEBUGS("AOEngine") << "stopping all SIT variants." << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_FEMALE, ANIM_REQUEST_STOP); - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_FEMALE); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GENERIC); - - // scripted seats that use ground_sit as animation need special treatment - const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(gAgentAvatarp->getRoot()); - if (agentRoot && agentRoot->getID() != gAgentID) - { - LL_DEBUGS("AOEngine") << "Not stopping ground sit animations while sitting on a prim." << LL_ENDL; - return; - } - - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GROUND, ANIM_REQUEST_STOP); - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GROUND_CONSTRAINED, ANIM_REQUEST_STOP); - - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GROUND); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GROUND_CONSTRAINED); -} - -void ALAOEngine::setLastMotion(const LLUUID& motion) -{ - if (motion != ANIM_AGENT_TYPE) - { - mLastMotion = motion; - } -} - -void ALAOEngine::setLastOverriddenMotion(const LLUUID& motion) -{ - if (motion != ANIM_AGENT_TYPE) - { - mLastOverriddenMotion = motion; - } -} - -bool ALAOEngine::foreignAnimations(const LLUUID& seat) -{ - LL_DEBUGS("AOEngine") << "Checking for foreign animation on seat " << seat << LL_ENDL; - - for (auto& animation_source : gAgentAvatarp->mAnimationSources) - { - LL_DEBUGS("AOEngine") << "Source " << animation_source.first << " runs animation " << animation_source.second << LL_ENDL; - - if (animation_source.first != gAgentID) - { - // special case when the AO gets disabled while sitting - if (seat.isNull()) - { - return true; - } - - // find the source object where the animation came from - LLViewerObject* source = gObjectList.findObject(animation_source.first); - - // proceed if it's not an attachment - if(source && !source->isAttachment()) - { - // get the source's root prim - LLViewerObject* sourceRoot=dynamic_cast<LLViewerObject*>(source->getRoot()); - - // if the root prim is the same as the animation source, report back as TRUE - if (sourceRoot && source->getID() == seat) - { - LL_DEBUGS("AOEngine") << "foreign animation " << animation_source.second << " found on seat." << LL_ENDL; - return true; - } - } - } - } - return false; -} - -const LLUUID& ALAOEngine::mapSwimming(const LLUUID& motion) const -{ - S32 name; - - if (motion == ANIM_AGENT_HOVER) - { - name = ALAOSet::Floating; - } - else if (motion == ANIM_AGENT_FLY) - { - name = ALAOSet::SwimmingForward; - } - else if (motion == ANIM_AGENT_HOVER_UP) - { - name = ALAOSet::SwimmingUp; - } - else if (motion == ANIM_AGENT_HOVER_DOWN) - { - name = ALAOSet::SwimmingDown; - } - else - { - return LLUUID::null; - } - - ALAOSet::AOState* state = mCurrentSet->getState(name); - return mCurrentSet->getAnimationForState(state); -} - -void ALAOEngine::checkBelowWater(const bool under) -{ - if (mUnderWater == under) return; - - // only restart underwater/above water motion if the overridden motion is the one currently playing - if (mLastMotion != mLastOverriddenMotion) return; - - gAgent.sendAnimationRequest(override(mLastOverriddenMotion, false), ANIM_REQUEST_STOP); - mUnderWater = under; - gAgent.sendAnimationRequest(override(mLastOverriddenMotion, true), ANIM_REQUEST_START); -} - -void ALAOEngine::enable(const bool enable) -{ - LL_DEBUGS("AOEngine") << "using " << mLastMotion << " enable " << enable << LL_ENDL; - mEnabled = enable; - - if (!mCurrentSet) - { - LL_DEBUGS("AOEngine") << "enable(" << enable << ") without animation set loaded." << LL_ENDL; - return; - } - - ALAOSet::AOState* state = mCurrentSet->getStateByRemapID(mLastMotion); - if (mEnabled) - { - if (state && !state->mAnimations.empty()) - { - LL_DEBUGS("AOEngine") << "Enabling animation state " << state->mName << LL_ENDL; - - // do not stop underlying ground sit when re-enabling the AO - if (mLastOverriddenMotion != ANIM_AGENT_SIT_GROUND_CONSTRAINED) - { - gAgent.sendAnimationRequest(mLastOverriddenMotion, ANIM_REQUEST_STOP); - } - - LLUUID animation = override(mLastMotion, true); - if (animation.isNull()) return; - - if (mLastMotion == ANIM_AGENT_STAND) - { - stopAllStandVariants(); - } - else if (mLastMotion == ANIM_AGENT_WALK) - { - LL_DEBUGS("AOEngine") << "Last motion was a WALK, stopping all variants." << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_WALK_NEW, ANIM_REQUEST_STOP); - gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_WALK, ANIM_REQUEST_STOP); - gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_WALK_NEW, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_WALK_NEW); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_WALK); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_WALK_NEW); - } - else if (mLastMotion == ANIM_AGENT_RUN) - { - LL_DEBUGS("AOEngine") << "Last motion was a RUN, stopping all variants." << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_RUN_NEW, ANIM_REQUEST_STOP); - gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_RUN_NEW, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_RUN_NEW); - gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_RUN_NEW); - } - else if (mLastMotion == ANIM_AGENT_SIT) - { - stopAllSitVariants(); - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_START); - } - else - { - LL_WARNS("AOEngine") << "Unhandled last motion id " << mLastMotion << LL_ENDL; - } - - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_START); - mAnimationChangedSignal(state->mAnimations[state->mCurrentAnimation].mInventoryUUID); - } - } - else - { - mAnimationChangedSignal(LLUUID::null); - - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); - // stop all overriders, catch leftovers - for (U32 index = 0; index < ALAOSet::AOSTATES_MAX; ++index) - { - state = mCurrentSet->getState(index); - if (state) - { - LLUUID animation = state->mCurrentAnimationID; - if (animation.notNull()) - { - LL_DEBUGS("AOEngine") << "Stopping leftover animation from state " << state->mName << LL_ENDL; - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(animation); - state->mCurrentAnimationID.setNull(); - } - } - else - { - LL_DEBUGS("AOEngine") << "state "<< index <<" returned NULL." << LL_ENDL; - } - } - - if (!foreignAnimations(LLUUID::null)) - { - gAgent.sendAnimationRequest(mLastMotion, ANIM_REQUEST_START); - } - - mCurrentSet->stopTimer(); - } -} - -void ALAOEngine::setStateCycleTimer(const ALAOSet::AOState* state) -{ - F32 timeout = state->mCycleTime; - LL_DEBUGS("AOEngine") << "Setting cycle timeout for state " << state->mName << " of " << timeout << LL_ENDL; - if (timeout > 0.0f) - { - mCurrentSet->startTimer(timeout); - } -} - -const LLUUID ALAOEngine::override(const LLUUID& pMotion, const bool start) -{ -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "override(" << pMotion << "," << start << ")" << LL_ENDL; -#endif - - LLUUID animation = LLUUID::null; - - LLUUID motion = pMotion; - - if (!mEnabled) - { - if (start && mCurrentSet) - { - const ALAOSet::AOState* state = mCurrentSet->getStateByRemapID(motion); - if (state) - { - setLastMotion(motion); -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "(disabled AO) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; -#endif - if (!state->mAnimations.empty()) - { - setLastOverriddenMotion(motion); -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "(disabled AO) setting last overridden motion id to " << gAnimLibrary.animationName(mLastOverriddenMotion) << LL_ENDL; -#endif - } - } - } - return animation; - } - - if (mSets.empty()) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "No sets loaded. Skipping overrider." << LL_ENDL; -#endif - return animation; - } - - if (!mCurrentSet) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "No current AO set chosen. Skipping overrider." << LL_ENDL; -#endif - return animation; - } - - // we don't distinguish between these two - if (motion == ANIM_AGENT_SIT_GROUND) - { - motion = ANIM_AGENT_SIT_GROUND_CONSTRAINED; - } - - ALAOSet::AOState* state = mCurrentSet->getStateByRemapID(motion); - if (!state) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "No current AO state for motion " << motion << " (" << gAnimLibrary.animationName(motion) << ")." << LL_ENDL; -#endif - return animation; - } - - mAnimationChangedSignal(LLUUID::null); - - mCurrentSet->stopTimer(); - if (start) - { - setLastMotion(motion); -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "(enabled AO) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; -#endif - - // Disable start stands in Mouselook - if (mCurrentSet->getMouselookDisable() && - motion == ANIM_AGENT_STAND && - mInMouselook) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "(enabled AO, mouselook stand stopped) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; -#endif - return animation; - } - - // Do not start override sits if not selected - if (!mCurrentSet->getSitOverride() && motion == ANIM_AGENT_SIT) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "(enabled AO, sit override stopped) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; -#endif - return animation; - } - - // scripted seats that use ground_sit as animation need special treatment - if (motion == ANIM_AGENT_SIT_GROUND_CONSTRAINED) - { - const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(gAgentAvatarp->getRoot()); - if (agentRoot && agentRoot->getID() != gAgentID) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "Ground sit animation playing but sitting on a prim - disabling overrider." << LL_ENDL; -#endif - return animation; - } - } - - if (!state->mAnimations.empty()) - { - setLastOverriddenMotion(motion); -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "(enabled AO) setting last overridden motion id to " << gAnimLibrary.animationName(mLastOverriddenMotion) << LL_ENDL; -#endif - } - - // do not remember typing as set-wide motion - if (motion != ANIM_AGENT_TYPE) - { - mCurrentSet->setMotion(motion); - } - - mUnderWater = gAgentAvatarp->mBelowWater; - if (mUnderWater) - { - animation = mapSwimming(motion); - } - - if (animation.isNull()) - { - animation = mCurrentSet->getAnimationForState(state); - } - - if (state->mCurrentAnimationID.notNull()) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "Previous animation for state " - << gAnimLibrary.animationName(motion) - << " was not stopped, but we were asked to start a new one. Killing old animation." << LL_ENDL; -#endif - gAgent.sendAnimationRequest(state->mCurrentAnimationID, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(state->mCurrentAnimationID); - } - - state->mCurrentAnimationID = animation; -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "overriding " << gAnimLibrary.animationName(motion) - << " with " << animation - << " in state " << state->mName - << " of set " << mCurrentSet->getName() - << " (" << mCurrentSet << ")" << LL_ENDL; -#endif - - if (animation.notNull() && state->mCurrentAnimation < state->mAnimations.size()) - { - mAnimationChangedSignal(state->mAnimations[state->mCurrentAnimation].mInventoryUUID); - } - - setStateCycleTimer(state); - - if (motion == ANIM_AGENT_SIT) - { - // Use ANIM_AGENT_SIT_GENERIC, so we don't create an overrider loop with ANIM_AGENT_SIT - // while still having a base sitting pose to cover up cycle points - gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_START); - if (mCurrentSet->getSmart()) - { - mSitCancelTimer.oneShot(); - } - } - // special treatment for "transient animations" because the viewer needs the Linden animation to know the agent's state - else if (motion == ANIM_AGENT_SIT_GROUND_CONSTRAINED || - motion == ANIM_AGENT_PRE_JUMP || - motion == ANIM_AGENT_STANDUP || - motion == ANIM_AGENT_LAND || - motion == ANIM_AGENT_MEDIUM_LAND) - { - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_START); - return LLUUID::null; - } - } - else - { - animation = state->mCurrentAnimationID; - state->mCurrentAnimationID.setNull(); - - // for typing animaiton, just return the stored animation, reset the state timer, and don't memorize anything else - if (motion == ANIM_AGENT_TYPE) - { - ALAOSet::AOState* previousState = mCurrentSet->getStateByRemapID(mLastMotion); - if (previousState) - { - setStateCycleTimer(previousState); - } - return animation; - } - - if (motion != mCurrentSet->getMotion()) - { - LL_WARNS("AOEngine") << "trying to stop-override motion " << gAnimLibrary.animationName(motion) - << " but the current set has motion " << gAnimLibrary.animationName(mCurrentSet->getMotion()) << LL_ENDL; - return animation; - } - - mCurrentSet->setMotion(LLUUID::null); - - // again, special treatment for "transient" animations to make sure our own animation gets stopped properly - if (motion == ANIM_AGENT_SIT_GROUND_CONSTRAINED || - motion == ANIM_AGENT_PRE_JUMP || - motion == ANIM_AGENT_STANDUP || - motion == ANIM_AGENT_LAND || - motion == ANIM_AGENT_MEDIUM_LAND) - { - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(animation); - setStateCycleTimer(state); - return LLUUID::null; - } - - // stop the underlying Linden Lab motion, in case it's still running. - // frequently happens with sits, so we keep it only for those currently. - if (mLastMotion == ANIM_AGENT_SIT) - { - stopAllSitVariants(); - } -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "stopping cycle timer for motion " << gAnimLibrary.animationName(motion) << - " using animation " << animation << - " in state " << state->mName << LL_ENDL; -#endif - } - - return animation; -} - -void ALAOEngine::checkSitCancel() -{ - LLUUID seat; - - const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(gAgentAvatarp->getRoot()); - if (agentRoot) - { - seat = agentRoot->getID(); - } - - if (foreignAnimations(seat)) - { - ALAOSet::AOState* aoState = mCurrentSet->getStateByRemapID(ANIM_AGENT_SIT); - if (aoState) - { - 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(); - } - } - } -} - -void ALAOEngine::cycleTimeout(const ALAOSet* set) -{ - if (!mEnabled) - { - return; - } - - if (set != mCurrentSet) - { - LL_WARNS("AOEngine") << "cycleTimeout for set " << set->getName() << " but current set is " << mCurrentSet->getName() << LL_ENDL; - return; - } - - cycle(CycleAny); -} - -void ALAOEngine::cycle(eCycleMode cycleMode) -{ - if (!mCurrentSet) - { - LL_DEBUGS("AOEngine") << "cycle without set." << LL_ENDL; - return; - } - - LLUUID motion = mCurrentSet->getMotion(); - - // assume stand if no motion is registered, happens after login when the avatar hasn't moved at all yet - // or if the agent has said something in local chat while sitting - if (motion.isNull()) - { - if (gAgentAvatarp->isSitting()) - { - motion = ANIM_AGENT_SIT; - } - else - { - motion = ANIM_AGENT_STAND; - } - } - - // do not cycle if we're sitting and sit-override is off - else if (motion == ANIM_AGENT_SIT && !mCurrentSet->getSitOverride()) - { - return; - } - // do not cycle if we're standing and mouselook stand override is disabled while being in mouselook - else if (motion == ANIM_AGENT_STAND && mCurrentSet->getMouselookDisable() && mInMouselook) - { - return; - } - - ALAOSet::AOState* state = mCurrentSet->getStateByRemapID(motion); - if (!state) - { - LL_DEBUGS("AOEngine") << "cycle without state." << LL_ENDL; - return; - } - - if (state->mAnimations.empty()) - { - LL_DEBUGS("AOEngine") << "cycle without animations in state." << LL_ENDL; - return; - } - - // make sure we disable cycling only for timed cycle, so manual cycling still works, even with cycling switched off - if (!state->mCycle && cycleMode == CycleAny) - { - LL_DEBUGS("AOEngine") << "cycle timeout, but state is set to not cycling." << LL_ENDL; - return; - } - - LLUUID oldAnimation = state->mCurrentAnimationID; - LLUUID animation; - - if (cycleMode == CycleAny) - { - animation = mCurrentSet->getAnimationForState(state); - } - else - { - if (cycleMode == CyclePrevious) - { - if (state->mCurrentAnimation == 0) - { - state->mCurrentAnimation = state->mAnimations.size() - 1; - } - else - { - state->mCurrentAnimation--; - } - } - else if (cycleMode == CycleNext) - { - state->mCurrentAnimation++; - if (state->mCurrentAnimation == state->mAnimations.size()) - { - state->mCurrentAnimation = 0; - } - } - animation = state->mAnimations[state->mCurrentAnimation].mAssetUUID; - } - - // don't do anything if the animation didn't change - if (animation == oldAnimation) - { - return; - } - - mAnimationChangedSignal(LLUUID::null); - - state->mCurrentAnimationID = animation; - if (animation.notNull()) - { - LL_DEBUGS("AOEngine") << "requesting animation start for motion " << gAnimLibrary.animationName(motion) << ": " << animation << LL_ENDL; - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_START); - mAnimationChangedSignal(state->mAnimations[state->mCurrentAnimation].mInventoryUUID); - } - else - { - LL_DEBUGS("AOEngine") << "overrider came back with NULL animation for motion " << gAnimLibrary.animationName(motion) << "." << LL_ENDL; - } - - if (oldAnimation.notNull()) - { - LL_DEBUGS("AOEngine") << "Cycling state " << state->mName << " - stopping animation " << oldAnimation << LL_ENDL; - gAgent.sendAnimationRequest(oldAnimation, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(oldAnimation); - } -} - -void ALAOEngine::updateSortOrder(ALAOSet::AOState* state) -{ - for (U32 index = 0; index < state->mAnimations.size(); ++index) - { - auto& anim = state->mAnimations[index]; - U32 sortOrder = anim.mSortOrder; - - if (sortOrder != index) - { - std::ostringstream numStr(""); - numStr << index; - - LL_DEBUGS("AOEngine") << "sort order is " << sortOrder << " but index is " << index - << ", setting sort order description: " << numStr.str() << LL_ENDL; - - anim.mSortOrder = index; - - LLViewerInventoryItem* item = gInventory.getItem(anim.mInventoryUUID); - if (!item) - { - LL_WARNS("AOEngine") << "NULL inventory item found while trying to copy " << anim.mInventoryUUID << LL_ENDL; - continue; - } - LLPointer<LLViewerInventoryItem> newItem = new LLViewerInventoryItem(item); - - newItem->setDescription(numStr.str()); - newItem->setComplete(TRUE); - newItem->updateServer(FALSE); - - gInventory.updateItem(newItem); - } - } -} - -void ALAOEngine::addSet(const std::string& name, const bool reload, inventory_func_type callback) -{ - if (mAOFolder.isNull()) - { - LL_WARNS("AOEngine") << ROOT_AO_FOLDER << " folder not there yet. Requesting recreation." << LL_ENDL; - tick(); - return; - } - - LL_DEBUGS("AOEngine") << "adding set folder " << name << LL_ENDL; - gInventory.createNewCategory(mAOFolder, LLFolderType::FT_NONE, name, callback); - - if (reload) - { - mTimerCollection.setReloadTimer(true); - } -} - -bool ALAOEngine::createAnimationLink(const ALAOSet* set, ALAOSet::AOState* state, const LLInventoryItem* item) -{ - LL_DEBUGS("AOEngine") << "Asset ID " << item->getAssetUUID() << " inventory id " - << item->getUUID() << " category id " << state->mInventoryUUID << LL_ENDL; - if (state->mInventoryUUID.isNull()) - { - LL_DEBUGS("AOEngine") << "no " << state->mName << " folder yet. Creating ..." << LL_ENDL; - gInventory.createNewCategory(set->getInventoryUUID(), LLFolderType::FT_NONE, state->mName); - - LL_DEBUGS("AOEngine") << "looking for folder to get UUID ..." << LL_ENDL; - - LLInventoryModel::item_array_t* items; - LLInventoryModel::cat_array_t* cats; - gInventory.getDirectDescendentsOf(set->getInventoryUUID(), cats, items); - - if (cats) - { - for (auto& cat : *cats) - { - if (cat->getName() == state->mName) - { - LL_DEBUGS("AOEngine") << "UUID found!" << LL_ENDL; - state->mInventoryUUID = cat->getUUID(); - break; - } - } - } - } - - if (state->mInventoryUUID.isNull()) - { - LL_DEBUGS("AOEngine") << "state inventory UUID not found, failing." << LL_ENDL; - return FALSE; - } - - LLInventoryObject::const_object_list_t obj_array; - obj_array.emplace_back(LLConstPointer<LLInventoryObject>(item)); - link_inventory_array(state->mInventoryUUID, - obj_array, - LLPointer<LLInventoryCallback>(NULL)); - - return TRUE; -} - -bool ALAOEngine::addAnimation(const ALAOSet* set, ALAOSet::AOState* state, - const LLInventoryItem* item, const bool reload) -{ - state->mAnimations.emplace_back(item->getName(), item->getAssetUUID(), item->getUUID(), state->mAnimations.size() + 1); - - createAnimationLink(set, state, item); - - if (reload) - { - mTimerCollection.setReloadTimer(true); - } - return TRUE; -} - -bool ALAOEngine::findForeignItems(const LLUUID& uuid) const -{ - bool moved = false; - - LLInventoryModel::item_array_t* items; - LLInventoryModel::cat_array_t* cats; - - gInventory.getDirectDescendentsOf(uuid, cats, items); - if (cats) - { - for (auto& cat : *cats) - { - // recurse into subfolders - if (findForeignItems(cat->getUUID())) - { - moved = true; - } - } - } - - // count backwards in case we have to remove items - if (items) - { - for (S32 index = items->size() - 1; index >= 0; --index) - { - bool move = false; - - LLPointer<LLViewerInventoryItem> item = items->at(index); - if (item->getIsLinkType()) - { - if (item->getInventoryType() != LLInventoryType::IT_ANIMATION) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << item->getName() << " is a link but does not point to an animation." << LL_ENDL; -#endif - move = true; - } -#ifdef SHOW_DEBUG - else - { - LL_DEBUGS("AOEngine") << item->getName() << " is an animation link." << LL_ENDL; - } -#endif - } - else - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << item->getName() << " is not a link!" << LL_ENDL; -#endif - move = true; - } - - if (move) - { - moved = true; - LLInventoryModel* model = &gInventory; - model->changeItemParent(item, gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND), FALSE); -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << item->getName() << " moved to lost and found!" << LL_ENDL; -#endif - } - } - } - - return moved; -} - -// needs a three-step process, since purge of categories only seems to work from trash -void ALAOEngine::purgeFolder(const LLUUID& uuid) const -{ - // move everything that's not an animation link to "lost and found" - if (findForeignItems(uuid)) - { - LLNotificationsUtil::add("AOForeignItemsFound", LLSD()); - } - - // trash it - gInventory.removeCategory(uuid); - - // clean it - purge_descendents_of(uuid, NULL); - gInventory.notifyObservers(); - - // purge it - remove_inventory_object(uuid, NULL); - gInventory.notifyObservers(); -} - -bool ALAOEngine::removeSet(ALAOSet* set) -{ - purgeFolder(set->getInventoryUUID()); - - mTimerCollection.setReloadTimer(true); - return true; -} - -bool ALAOEngine::removeAnimation(const ALAOSet* set, ALAOSet::AOState* state, S32 index) -{ - // Protect against negative index - if (index <= -1) return false; - - size_t numOfAnimations = state->mAnimations.size(); - if (!numOfAnimations) return false; - - LLViewerInventoryItem* item = gInventory.getItem(state->mAnimations[index].mInventoryUUID); - - // check if this item is actually an animation link - bool move = (item->getIsLinkType() && item->getInventoryType() == LLInventoryType::IT_ANIMATION) ? false : true; - - // this item was not an animation link, move it to lost and found - if (move) - { - LLInventoryModel* model = &gInventory; - model->changeItemParent(item, gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND), false); - LLNotificationsUtil::add("AOForeignItemsFound", LLSD()); - update(); - return false; - } - - // purge the item from inventory - LL_DEBUGS("AOEngine") << __LINE__ << " purging: " << state->mAnimations[index].mInventoryUUID << LL_ENDL; - remove_inventory_object(state->mAnimations[index].mInventoryUUID, NULL); // item->getUUID()); - gInventory.notifyObservers(); - - state->mAnimations.erase(state->mAnimations.begin() + index); - - if (state->mAnimations.empty()) - { - LL_DEBUGS("AOEngine") << "purging folder " << state->mName << " from inventory because it's empty." << LL_ENDL; - - LLInventoryModel::item_array_t* items; - LLInventoryModel::cat_array_t* cats; - gInventory.getDirectDescendentsOf(set->getInventoryUUID(), cats, items); - - for (auto& it : *cats) - { - LLPointer<LLInventoryCategory> cat = it; - std::vector<std::string> params; - LLStringUtil::getTokens(cat->getName(), params, ":"); - std::string stateName = params[0]; - - if (state->mName.compare(stateName) == 0) - { - LL_DEBUGS("AOEngine") << "folder found: " << cat->getName() << " purging uuid " << cat->getUUID() << LL_ENDL; - - purgeFolder(cat->getUUID()); - state->mInventoryUUID.setNull(); - break; - } - } - } - else - { - updateSortOrder(state); - } - - return true; -} - -bool ALAOEngine::swapWithPrevious(ALAOSet::AOState* state, S32 index) -{ - S32 numOfAnimations = state->mAnimations.size(); - if (numOfAnimations < 2 || index == 0) - { - return false; - } - - ALAOSet::AOAnimation tmpAnim = state->mAnimations[index]; - state->mAnimations.erase(state->mAnimations.begin() + index); - state->mAnimations.insert(state->mAnimations.begin() + index - 1, tmpAnim); - - updateSortOrder(state); - - return true; -} - -bool ALAOEngine::swapWithNext(ALAOSet::AOState* state, S32 index) -{ - S32 numOfAnimations = state->mAnimations.size(); - if (numOfAnimations < 2 || index == (numOfAnimations - 1)) - { - return false; - } - - ALAOSet::AOAnimation tmpAnim = state->mAnimations[index]; - state->mAnimations.erase(state->mAnimations.begin() + index); - state->mAnimations.insert(state->mAnimations.begin() + index + 1, tmpAnim); - - updateSortOrder(state); - - return true; -} - -void ALAOEngine::reloadStateAnimations(ALAOSet::AOState* state) -{ - LLInventoryModel::item_array_t* items; - LLInventoryModel::cat_array_t* dummy; - - state->mAnimations.clear(); - - gInventory.getDirectDescendentsOf(state->mInventoryUUID, dummy, items); - for (auto& item : *items) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "Found animation link " << item->LLInventoryItem::getName() - << " desc " << item->LLInventoryItem::getDescription() - << " asset " << item->getAssetUUID() << LL_ENDL; -#endif - - LLViewerInventoryItem* linkedItem = item->getLinkedItem(); - if (!linkedItem) - { - LL_WARNS("AOEngine") << "linked item for link " << item->LLInventoryItem::getName() << " not found (broken link). Skipping." << LL_ENDL; - continue; - } - - S32 sortOrder; - if (!LLStringUtil::convertToS32(item->LLInventoryItem::getDescription(), sortOrder)) - { - sortOrder = -1; - } - -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "current sort order is " << sortOrder << LL_ENDL; -#endif - - if (sortOrder == -1) - { - LL_WARNS("AOEngine") << "sort order was unknown so append to the end of the list" << LL_ENDL; - state->mAnimations.emplace_back(linkedItem->LLInventoryItem::getName(), item->getAssetUUID(), item->getUUID(), sortOrder); - } - else - { - bool inserted = false; - for (U32 index = 0; index < state->mAnimations.size(); ++index) - { - if (state->mAnimations[index].mSortOrder > sortOrder) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "inserting at index " << index << LL_ENDL; -#endif - state->mAnimations.emplace(state->mAnimations.begin() + index, - linkedItem->LLInventoryItem::getName(), item->getAssetUUID(), item->getUUID(), sortOrder); - inserted = true; - break; - } - } - if (!inserted) - { -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "not inserted yet, appending to the list instead" << LL_ENDL; -#endif - state->mAnimations.emplace_back(linkedItem->LLInventoryItem::getName(), item->getAssetUUID(), item->getUUID(), sortOrder); - } - } -#ifdef SHOW_DEBUG - LL_DEBUGS("AOEngine") << "Animation count now: " << state->mAnimations.size() << LL_ENDL; -#endif - } - - updateSortOrder(state); -} - -void ALAOEngine::update() -{ - if (mAOFolder.isNull()) return; - - // move everything that's not an animation link to "lost and found" - if (findForeignItems(mAOFolder)) - { - LLNotificationsUtil::add("AOForeignItemsFound", LLSD()); - } - - LLInventoryModel::cat_array_t* categories; - LLInventoryModel::item_array_t* items; - - bool allComplete = true; - mTimerCollection.setSettingsTimer(false); - - gInventory.getDirectDescendentsOf(mAOFolder, categories, items); - for (auto& categorie : *categories) - { - LLViewerInventoryCategory* currentCategory = categorie; - const std::string& setFolderName = currentCategory->getName(); - - if (setFolderName.empty()) - { - LL_WARNS("AOEngine") << "Folder with emtpy name in AO folder" << LL_ENDL; - continue; - } - - std::vector<std::string> params; - LLStringUtil::getTokens(setFolderName, params, ":"); - - ALAOSet* newSet = getSetByName(params[0]); - if (!newSet) - { - LL_DEBUGS("AOEngine") << "Adding set " << setFolderName << " to AO." << LL_ENDL; - newSet = new ALAOSet(currentCategory->getUUID()); - newSet->setName(params[0]); - mSets.emplace_back(newSet); - } - else - { - if (newSet->getComplete()) - { - LL_DEBUGS("AOEngine") << "Set " << params[0] << " already complete. Skipping." << LL_ENDL; - continue; - } - LL_DEBUGS("AOEngine") << "Updating set " << setFolderName << " in AO." << LL_ENDL; - } - allComplete = FALSE; - - for (U32 num = 1; num < params.size(); ++num) - { - if (params[num].size() != 2) - { - LL_WARNS("AOEngine") << "Unknown AO set option " << params[num] << LL_ENDL; - } - else if (params[num] == "SO") - { - newSet->setSitOverride(TRUE); - } - else if (params[num] == "SM") - { - newSet->setSmart(TRUE); - } - else if (params[num] == "DM") - { - newSet->setMouselookDisable(TRUE); - } - else if (params[num] == "**") - { - mDefaultSet = newSet; - mCurrentSet = newSet; - } - else - { - LL_WARNS("AOEngine") << "Unknown AO set option " << params[num] << LL_ENDL; - } - } - - if (gInventory.isCategoryComplete(currentCategory->getUUID())) - { - LL_DEBUGS("AOEngine") << "Set " << params[0] << " is complete, reading states ..." << LL_ENDL; - - LLInventoryModel::cat_array_t* stateCategories; - gInventory.getDirectDescendentsOf(currentCategory->getUUID(), stateCategories, items); - newSet->setComplete(TRUE); - - for (auto& stateCategorie : *stateCategories) - { - std::vector<std::string> state_params; - LLStringUtil::getTokens(stateCategorie->getName(), state_params, ":"); - std::string stateName = state_params[0]; - - ALAOSet::AOState* state = newSet->getStateByName(stateName); - if (!state) - { - LL_WARNS("AOEngine") << "Unknown state " << stateName << ". Skipping." << LL_ENDL; - continue; - } - LL_DEBUGS("AOEngine") << "Reading state " << stateName << LL_ENDL; - - state->mInventoryUUID = stateCategorie->getUUID(); - for (U32 num = 1; num < state_params.size(); ++num) - { - if (state_params[num] == "CY") - { - state->mCycle = TRUE; - LL_DEBUGS("AOEngine") << "Cycle on" << LL_ENDL; - } - else if (state_params[num] == "RN") - { - state->mRandom = TRUE; - LL_DEBUGS("AOEngine") << "Random on" << LL_ENDL; - } - else if (state_params[num].substr(0, 2) == "CT") - { - LLStringUtil::convertToS32(state_params[num].substr(2, state_params[num].size() - 2), state->mCycleTime); - LL_DEBUGS("AOEngine") << "Cycle Time specified:" << state->mCycleTime << LL_ENDL; - } - else - { - LL_WARNS("AOEngine") << "Unknown AO set option " << state_params[num] << LL_ENDL; - } - } - - if (!gInventory.isCategoryComplete(state->mInventoryUUID)) - { - LL_DEBUGS("AOEngine") << "State category " << stateName << " is incomplete, fetching descendents" << LL_ENDL; - gInventory.fetchDescendentsOf(state->mInventoryUUID); - allComplete = FALSE; - newSet->setComplete(FALSE); - continue; - } - reloadStateAnimations(state); - } - } - else - { - LL_DEBUGS("AOEngine") << "Set " << params[0] << " is incomplete, fetching descendents" << LL_ENDL; - gInventory.fetchDescendentsOf(currentCategory->getUUID()); - } - } - - if (allComplete) - { - mEnabled = gSavedPerAccountSettings.getBOOL("AlchemyAOEnable"); - - if (!mCurrentSet && !mSets.empty()) - { - LL_DEBUGS("AOEngine") << "No default set defined, choosing the first in the list." << LL_ENDL; - selectSet(mSets[0]); - } - - mTimerCollection.setInventoryTimer(false); - mTimerCollection.setSettingsTimer(true); - - LL_INFOS("AOEngine") << "sending update signal" << LL_ENDL; - mUpdatedSignal(); - enable(mEnabled); - } -} - -void ALAOEngine::reload(bool aFromTimer) -{ - BOOL wasEnabled = mEnabled; - - mTimerCollection.setReloadTimer(false); - - if (wasEnabled) - { - enable(false); - } - - gAgent.stopCurrentAnimations(); - mLastOverriddenMotion = ANIM_AGENT_STAND; - - clear(aFromTimer); - mAOFolder.setNull(); - mTimerCollection.setInventoryTimer(true); - tick(); - - if (wasEnabled) - { - enable(false); - } -} - -ALAOSet* ALAOEngine::getSetByName(const std::string& name) const -{ - ALAOSet* found = NULL; - for (auto set : mSets) - { - if (set->getName() == name) - { - found = set; - break; - } - } - return found; -} - -const std::string& ALAOEngine::getCurrentSetName() const -{ - if(mCurrentSet) - { - return mCurrentSet->getName(); - } - return LLStringUtil::null; -} - -const ALAOSet* ALAOEngine::getDefaultSet() const -{ - return mDefaultSet; -} - -void ALAOEngine::selectSet(ALAOSet* set) -{ - if (mEnabled && mCurrentSet) - { - ALAOSet::AOState* state = mCurrentSet->getStateByRemapID(mLastOverriddenMotion); - if (state) - { - gAgent.sendAnimationRequest(state->mCurrentAnimationID, ANIM_REQUEST_STOP); - state->mCurrentAnimationID.setNull(); - mCurrentSet->stopTimer(); - } - } - - mCurrentSet = set; - if(mCurrentSet) - { - mSetChangedSignal(mCurrentSet->getName()); - } - - if (mEnabled) - { - LL_DEBUGS("AOEngine") << "enabling with motion " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; - gAgent.sendAnimationRequest(override(mLastMotion, TRUE), ANIM_REQUEST_START); - } -} - -ALAOSet* ALAOEngine::selectSetByName(const std::string& name) -{ - ALAOSet* set = getSetByName(name); - if (set) - { - selectSet(set); - return set; - } - LL_WARNS("AOEngine") << "Could not find AO set " << name << LL_ENDL; - return nullptr; -} - -const std::vector<ALAOSet*>& ALAOEngine::getSetList() const -{ - return mSets; -} - -void ALAOEngine::saveSet(const ALAOSet* set) -{ - if (!set) return; - - std::string setParams=set->getName(); - if (set->getSitOverride()) - { - setParams += ":SO"; - } - if (set->getSmart()) - { - setParams += ":SM"; - } - if (set->getMouselookDisable()) - { - setParams += ":DM"; - } - if (set == mDefaultSet) - { - setParams += ":**"; - } - - rename_category(&gInventory, set->getInventoryUUID(), setParams); - - LL_INFOS("AOEngine") << "sending update signal" << LL_ENDL; - mUpdatedSignal(); -} - -bool ALAOEngine::renameSet(ALAOSet* set, const std::string& name) -{ - if (name.empty() || name.find(':') != std::string::npos) - { - return false; - } - set->setName(name); - set->setDirty(true); - - return true; -} - -void ALAOEngine::saveState(const ALAOSet::AOState* state) -{ - std::string stateParams = state->mName; - F32 time = state->mCycleTime; - if (time > 0.0f) - { - std::ostringstream timeStr; - timeStr << ":CT" << state->mCycleTime; - stateParams += timeStr.str(); - } - if (state->mCycle) - { - stateParams += ":CY"; - } - if (state->mRandom) - { - stateParams += ":RN"; - } - - rename_category(&gInventory, state->mInventoryUUID, stateParams); -} - -void ALAOEngine::saveSettings() -{ - for (auto set : mSets) - { - if (set->getDirty()) - { - saveSet(set); - LL_INFOS("AOEngine") << "dirty set saved " << set->getName() << LL_ENDL; - set->setDirty(FALSE); - } - - for (S32 stateIndex = 0; stateIndex < ALAOSet::AOSTATES_MAX; ++stateIndex) - { - ALAOSet::AOState* state = set->getState(stateIndex); - if (state->mDirty) - { - saveState(state); - LL_INFOS("AOEngine") << "dirty state saved " << state->mName << LL_ENDL; - state->mDirty = false; - } - } - } -} - -void ALAOEngine::inMouselook(const bool in_mouselook) -{ - if (mInMouselook == in_mouselook) return; - - mInMouselook = in_mouselook; - - if (!mCurrentSet || !mCurrentSet->getMouselookDisable()) - { - return; - } - - if (!mEnabled) - { - return; - } - - if (mLastMotion != ANIM_AGENT_STAND) - { - return; - } - - if (in_mouselook) - { - ALAOSet::AOState* state = mCurrentSet->getState(ALAOSet::Standing); - if (!state) - { - return; - } - - LLUUID animation = state->mCurrentAnimationID; - if (animation.notNull()) - { - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(animation); - state->mCurrentAnimationID.setNull(); - LL_DEBUGS("AOEngine") << " stopped animation " << animation << " in state " << state->mName << LL_ENDL; - } - gAgent.sendAnimationRequest(ANIM_AGENT_STAND, ANIM_REQUEST_START); - } - else - { - stopAllStandVariants(); - gAgent.sendAnimationRequest(override(ANIM_AGENT_STAND, TRUE), ANIM_REQUEST_START); - } -} - -void ALAOEngine::setDefaultSet(ALAOSet* set) -{ - mDefaultSet = set; - for (auto& ao_set : mSets) - { - ao_set->setDirty(TRUE); - } -} - -void ALAOEngine::setOverrideSits(ALAOSet* set, const bool yes) -{ - set->setSitOverride(yes); - set->setDirty(TRUE); - - if (mCurrentSet != set) - { - return; - } - - if (mLastMotion != ANIM_AGENT_SIT) - { - return; - } - - if (yes) - { - stopAllSitVariants(); - gAgent.sendAnimationRequest(override(ANIM_AGENT_SIT, TRUE), ANIM_REQUEST_START); - } - else - { - ALAOSet::AOState* state = mCurrentSet->getState(ALAOSet::Sitting); - if (!state) - { - return; - } - - LLUUID animation = state->mCurrentAnimationID; - if (animation.notNull()) - { - gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); - gAgentAvatarp->LLCharacter::stopMotion(animation); - state->mCurrentAnimationID.setNull(); - } - - gAgent.sendAnimationRequest(ANIM_AGENT_SIT, ANIM_REQUEST_START); - } -} - -void ALAOEngine::setSmart(ALAOSet* set, const bool smart) -{ - set->setSmart(smart); - set->setDirty(TRUE); - - if (smart) - { - // make sure to restart the sit cancel timer to fix sit overrides when the object we are - // sitting on is playing its own animation - const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(gAgentAvatarp->getRoot()); - if (agentRoot && agentRoot->getID() != gAgentID) - { - mSitCancelTimer.oneShot(); - } - } -} - -void ALAOEngine::setDisableStands(ALAOSet* set, const bool disable) -{ - set->setMouselookDisable(disable); - set->setDirty(true); - - if (mCurrentSet != set) - { - return; - } - - // make sure an update happens if needed - mInMouselook = !gAgentCamera.cameraMouselook(); - inMouselook(!mInMouselook); -} - -void ALAOEngine::setCycle(ALAOSet::AOState* state, const bool cycle) -{ - state->mCycle = cycle; - state->mDirty = true; -} - -void ALAOEngine::setRandomize(ALAOSet::AOState* state, const bool randomize) -{ - state->mRandom = randomize; - state->mDirty = true; -} - -void ALAOEngine::setCycleTime(ALAOSet::AOState* state, F32 time) -{ - state->mCycleTime = time; - state->mDirty = true; -} - -void ALAOEngine::tick() -{ - if (!isAgentAvatarValid()) return; - - if (mAOFolder.isNull()) - { - gInventory.findCategoryUUIDForNameInRoot(ROOT_AO_FOLDER, gInventory.getRootFolderID(), true, - [&](const LLUUID& category_id) - { mAOFolder = category_id; - LL_INFOS("AOEngine") << "AO basic folder structure intact." << LL_ENDL; - update(); - }); - } - else - { - update(); - } -} - -bool ALAOEngine::importNotecard(const LLInventoryItem* item) -{ - if (item) - { - LL_INFOS("AOEngine") << "importing AO notecard: " << item->getName() << LL_ENDL; - if (getSetByName(item->getName())) - { - LLNotificationsUtil::add("AOImportSetAlreadyExists", LLSD()); - return false; - } - - if (!gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE) && !gAgent.isGodlike()) - { - LLNotificationsUtil::add("AOImportPermissionDenied", LLSD()); - return false; - } - - if (item->getAssetUUID().notNull()) - { - mImportSet = new ALAOSet(item->getParentUUID()); - if (!mImportSet) - { - LLNotificationsUtil::add("AOImportCreateSetFailed", LLSD()); - return false; - } - mImportSet->setName(item->getName()); - - LLUUID* newUUID = new LLUUID(item->getAssetUUID()); - const LLHost sourceSim = LLHost(); - - gAssetStorage->getInvItemAsset( - sourceSim, - gAgent.getID(), - gAgent.getSessionID(), - item->getPermissions().getOwner(), - LLUUID::null, - item->getUUID(), - item->getAssetUUID(), - item->getType(), - &onNotecardLoadComplete, - (void*) newUUID, - TRUE); - - return true; - } - } - return false; -} - -// static -void ALAOEngine::onNotecardLoadComplete(const LLUUID& assetUUID, LLAssetType::EType type, - void* userdata, S32 status, LLExtStat extStatus) -{ - if (status != LL_ERR_NOERR) - { - // AOImportDownloadFailed - LLNotificationsUtil::add("AOImportDownloadFailed", LLSD()); - // NULL tells the importer to cancel all operations and free the import set memory - ALAOEngine::instance().parseNotecard(nullptr); - return; - } - LL_DEBUGS("AOEngine") << "Downloading import notecard complete." << LL_ENDL; - - LLFileSystem file(assetUUID, type, LLFileSystem::READ); - - S32 notecardSize = file.getSize(); - auto buffer = std::make_unique<char[]>(notecardSize + 1); - buffer[notecardSize] = '\0'; - - if (file.read((U8*)buffer.get(), notecardSize) != FALSE) - { - ALAOEngine::instance().parseNotecard(std::move(buffer)); - } - else - { - ALAOEngine::instance().parseNotecard(nullptr); - } -} - -void ALAOEngine::parseNotecard(std::unique_ptr<char[]> buffer) -{ - LL_DEBUGS("AOEngine") << "parsing import notecard" << LL_ENDL; - - bool isValid = false; - - if (!buffer) - { - LL_WARNS("AOEngine") << "buffer==NULL - aborting import" << LL_ENDL; - // NOTE: cleanup is always the same, needs streamlining - delete mImportSet; - mImportSet = nullptr; - mUpdatedSignal(); - return; - } - - std::string text(buffer.get()); - - std::vector<std::string> lines; - LLStringUtil::getTokens(text, lines, "\n"); - - S32 found = -1; - for (U32 index = 0; index < lines.size(); ++index) - { - if (lines[index].find("Text length ") == 0) - { - found = index; - break; - } - } - - if (found == -1) - { - LLNotificationsUtil::add("AOImportNoText", LLSD()); - delete mImportSet; - mImportSet = nullptr; - mUpdatedSignal(); - return; - } - - LLViewerInventoryCategory* importCategory = gInventory.getCategory(mImportSet->getInventoryUUID()); - if (!importCategory) - { - LLNotificationsUtil::add("AOImportNoFolder", LLSD()); - delete mImportSet; - mImportSet = 0; - mUpdatedSignal(); - return; - } - - std::map<std::string, LLUUID> animationMap; - LLInventoryModel::cat_array_t* dummy; - LLInventoryModel::item_array_t* items; - - gInventory.getDirectDescendentsOf(mImportSet->getInventoryUUID(), dummy, items); - for (const auto& inv_item : *items) - { - animationMap[inv_item->getName()] = inv_item->getUUID(); - LL_DEBUGS("AOEngine") << "animation " << inv_item->getName() << - " has inventory UUID " << animationMap[inv_item->getName()] << LL_ENDL; - } - - // [ State ]Anim1|Anim2|Anim3 - for (U32 index = found + 1; index < lines.size(); ++index) - { - std::string line = lines[index]; - - // cut off the trailing } of a notecard asset's text portion in the last line - if (index == lines.size() - 1) - { - line = line.substr(0, line.size() - 1); - } - - LLStringUtil::trim(line); - - if (line.empty() || line[0] == '#') continue; - - if (line.find('[') != 0) - { - LLSD args; - args["LINE"] = (S32)index; - LLNotificationsUtil::add("AOImportNoStatePrefix", args); - continue; - } - - size_t endTag = line.find(']'); - if (endTag == std::string::npos) - { - LLSD args; - args["LINE"] = (S32)index; - LLNotificationsUtil::add("AOImportNoValidDelimiter", args); - continue; - } - - - std::string stateName = line.substr(1, endTag - 1); - LLStringUtil::trim(stateName); - - // Recognized but not currently implemented. Handled here to avoid misleading "state not found" notification. - if ("Standing mode 2" == stateName || "Standing Calm" == stateName) - { - continue; - } - - ALAOSet::AOState* newState = mImportSet->getStateByName(stateName); - if (!newState) - { - LLSD args; - args["NAME"] = stateName; - LLNotificationsUtil::add("AOImportStateNameNotFound", args); - continue; - } - - std::string animationLine = line.substr(endTag + 1); - std::vector<std::string> animationList; - LLStringUtil::getTokens(animationLine, animationList, "|,"); - - for (U32 animIndex = 0; animIndex < animationList.size(); ++animIndex) - { - ALAOSet::AOAnimation animation; - animation.mName = animationList[animIndex]; - animation.mInventoryUUID = animationMap[animation.mName]; - if (animation.mInventoryUUID.isNull()) - { - LLSD args; - args["NAME"] = animation.mName; - LLNotificationsUtil::add("AOImportAnimationNotFound", args); - continue; - } - animation.mSortOrder = animIndex; - newState->mAnimations.push_back(animation); - isValid = true; - } - - if ("Standing mode 1" == stateName) - { - newState->mCycle = true; - newState->mCycleTime = 30.0f; - newState->mDirty = true; - } - } - - if (!isValid) - { - LLNotificationsUtil::add("AOImportInvalid", LLSD()); - // NOTE: cleanup is always the same, needs streamlining - delete mImportSet; - mImportSet = nullptr; - mUpdatedSignal(); - return; - } - - mTimerCollection.setImportTimer(true); - mImportRetryCount = 0; - processImport(false); -} - -void ALAOEngine::processImportInternal(bool aFromTimer) -{ - bool allComplete = true; - for (S32 index = 0; index < ALAOSet::AOSTATES_MAX; ++index) - { - ALAOSet::AOState* state = mImportSet->getState(index); - if (!state->mAnimations.empty()) - { - if (state->mCycleTime) - { - const std::string oldName = state->mName + ":CT"; - state->mName = llformat("%s%d", oldName.c_str(), state->mCycleTime); - } - if (state->mCycle) - { - const std::string& oldName = state->mName; - state->mName = llformat("%s%s", oldName.c_str(), ":CY"); - } - allComplete = false; - LL_DEBUGS("AOEngine") << "state " << state->mName << " still has animations to link." << LL_ENDL; - - for (S32 animationIndex = state->mAnimations.size() - 1; animationIndex >= 0; --animationIndex) - { - LL_DEBUGS("AOEngine") << "linking animation " << state->mAnimations[animationIndex].mName << LL_ENDL; - if (createAnimationLink(mImportSet, state, gInventory.getItem(state->mAnimations[animationIndex].mInventoryUUID))) - { - LL_DEBUGS("AOEngine") << "link success, size " << state->mAnimations.size() << ", removing animation " - << (*(state->mAnimations.begin() + animationIndex)).mName << " from import state" << LL_ENDL; - state->mAnimations.erase(state->mAnimations.begin() + animationIndex); - LL_DEBUGS("AOEngine") << "deleted, size now: " << state->mAnimations.size() << LL_ENDL; - } - else - { - LLSD args; - args["NAME"] = state->mAnimations[animationIndex].mName; - LLNotificationsUtil::add("AOImportLinkFailed", args); - } - } - } - } - - if (allComplete) - { - mTimerCollection.setImportTimer(false); - mOldImportSets.push_back(mImportSet); - mImportSet = nullptr; - mImportCategory.setNull(); - reload(aFromTimer); - } -} - -void ALAOEngine::processImportNewCat(const LLUUID& id, bool process) -{ - mImportCategory = id; - if (mImportCategory.isNull()) - { - mImportRetryCount++; - if (mImportRetryCount == 5) - { - // NOTE: cleanup is the same as at the end of this function. Needs streamlining. - mTimerCollection.setImportTimer(false); - delete mImportSet; - mImportSet = NULL; - mImportCategory.setNull(); - mUpdatedSignal(); - LLSD args; - args["NAME"] = mImportSet->getName(); - LLNotificationsUtil::add("AOImportAbortCreateSet", args); - } - else - { - LLSD args; - args["NAME"] = mImportSet->getName(); - LLNotificationsUtil::add("AOImportRetryCreateSet", args); - } - return; - } - mImportSet->setInventoryUUID(mImportCategory); - - processImportInternal(process); -} - -void ALAOEngine::processImport(bool aFromTimer) -{ - if (mImportCategory.isNull()) - { - addSet(mImportSet->getName(), false, [this, aFromTimer](const LLUUID& newid) { processImportNewCat(newid, aFromTimer); }); - } - else - { - processImportInternal(aFromTimer); - } -} - -const LLUUID& ALAOEngine::getAOFolder() const -{ - return mAOFolder; -} - -void ALAOEngine::onRegionChange() -{ - // do nothing if the AO is off - if (!mEnabled) return; - - // catch errors without crashing - if (!mCurrentSet) - { - LL_DEBUGS("AOEngine") << "Current set was NULL" << LL_ENDL; - return; - } - - // sitting needs special attention - if (mCurrentSet->getMotion() == ANIM_AGENT_SIT) - { - // do nothing if sit overrides was disabled - if (!mCurrentSet->getSitOverride()) - { - return; - } - - // do nothing if the last overridden motion wasn't a sit. - // happens when sit override is enabled but there were no - // sit animations added to the set yet - if (mLastOverriddenMotion != ANIM_AGENT_SIT) - { - return; - } - - // do nothing if smart sit is enabled because we have no - // animation running from the AO - if (mCurrentSet->getSmart()) - { - return; - } - } - - // restart current animation on region crossing - gAgent.sendAnimationRequest(mLastMotion, ANIM_REQUEST_START); -} - -// ---------------------------------------------------- - -ALAOSitCancelTimer::ALAOSitCancelTimer() -: LLEventTimer(0.1f), - mTickCount(0) -{ - mEventTimer.stop(); -} - -void ALAOSitCancelTimer::oneShot() -{ - mTickCount = 0; - mEventTimer.start(); -} - -void ALAOSitCancelTimer::stop() -{ - mEventTimer.stop(); -} - -BOOL ALAOSitCancelTimer::tick() -{ - mTickCount++; - ALAOEngine::instance().checkSitCancel(); - if (mTickCount == 10) - { - mEventTimer.stop(); - } - return FALSE; -} - -// ---------------------------------------------------- - -ALAOTimerCollection::ALAOTimerCollection() -: LLEventTimer(INVENTORY_POLLING_INTERVAL), - mInventoryTimer(true), - mSettingsTimer(false), - mReloadTimer(false), - mImportTimer(false) -{ - updateTimers(); -} - -BOOL ALAOTimerCollection::tick() -{ - if (mInventoryTimer) - { - LL_DEBUGS("AOEngine") << "Inventory timer tick()" << LL_ENDL; - ALAOEngine::instance().tick(); - } - if (mSettingsTimer) - { - LL_DEBUGS("AOEngine") << "Settings timer tick()" << LL_ENDL; - ALAOEngine::instance().saveSettings(); - } - if (mReloadTimer) - { - LL_DEBUGS("AOEngine") << "Reload timer tick()" << LL_ENDL; - ALAOEngine::instance().reload(true); - } - if (mImportTimer) - { - LL_DEBUGS("AOEngine") << "Import timer tick()" << LL_ENDL; - ALAOEngine::instance().processImport(true); - } - - // always return FALSE or the LLEventTimer will be deleted -> crash - return FALSE; -} - -void ALAOTimerCollection::setInventoryTimer(const bool enable) -{ - mInventoryTimer = enable; - updateTimers(); -} - -void ALAOTimerCollection::setSettingsTimer(const bool enable) -{ - mSettingsTimer = enable; - updateTimers(); -} - -void ALAOTimerCollection::setReloadTimer(const bool enable) -{ - mReloadTimer = enable; - updateTimers(); -} - -void ALAOTimerCollection::setImportTimer(const bool enable) -{ - mImportTimer = enable; - updateTimers(); -} - -void ALAOTimerCollection::updateTimers() -{ - if (!mInventoryTimer && !mSettingsTimer && !mReloadTimer && !mImportTimer) - { - LL_DEBUGS("AOEngine") << "no timer needed, stopping internal timer." << LL_ENDL; - mEventTimer.stop(); - } - else - { - LL_DEBUGS("AOEngine") << "timer needed, starting internal timer." << LL_ENDL; - mEventTimer.start(); - } -} diff --git a/indra/newview/alaoengine.h b/indra/newview/alaoengine.h deleted file mode 100644 index 6568f71b559..00000000000 --- a/indra/newview/alaoengine.h +++ /dev/null @@ -1,227 +0,0 @@ -/** - * @file alaoengine.h - * @brief The core Animation Overrider engine - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Zi Ree @ Second Life - * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * $/LicenseInfo$ - */ - -#ifndef AL_AOENGINE_H -#define AL_AOENGINE_H - -#include <boost/signals2.hpp> - -#include "alaoset.h" - -#include "llassettype.h" -#include "lleventtimer.h" - -#include "llextendedstatus.h" -#include "llsingleton.h" -#include "llviewerinventory.h" - -class ALAOTimerCollection final : public LLEventTimer -{ -public: - ALAOTimerCollection(); - ~ALAOTimerCollection() = default; - - BOOL tick() override; - - void setInventoryTimer(const bool enable); - void setSettingsTimer(const bool enable); - void setReloadTimer(const bool enable); - void setImportTimer(const bool enable); - -protected: - void updateTimers(); - - bool mInventoryTimer; - bool mSettingsTimer; - bool mReloadTimer; - bool mImportTimer; -}; - -// ---------------------------------------------------- - -class ALAOSitCancelTimer final : public LLEventTimer -{ -public: - ALAOSitCancelTimer(); - ~ALAOSitCancelTimer() = default; - - void oneShot(); - void stop(); - - BOOL tick() override; - -protected: - S32 mTickCount; -}; - -// ---------------------------------------------------- - -class LLInventoryItem; -class LLVFS; - -class ALAOEngine final : public LLSingleton<ALAOEngine> -{ - LLSINGLETON(ALAOEngine); - ~ALAOEngine(); - -public: - typedef enum e_cycle_mode - { - CycleAny, - CycleNext, - CyclePrevious - } eCycleMode; - - void enable(const bool enable); - const LLUUID override(const LLUUID& motion, const bool start); - void tick(); - void update(); - void reload(const bool reload); - void reloadStateAnimations(ALAOSet::AOState* state); - void clear(const bool clear); - - const LLUUID& getAOFolder() const; - - void addSet(const std::string& name, bool reload = true, inventory_func_type callback = {}); - bool removeSet(ALAOSet* set); - - bool addAnimation(const ALAOSet* set, ALAOSet::AOState* state, - const LLInventoryItem* item, bool reload = true); - bool removeAnimation(const ALAOSet* set, ALAOSet::AOState* state, S32 index); - void checkSitCancel(); - void checkBelowWater(const bool under); - - bool importNotecard(const LLInventoryItem* item); - void processImport(const bool process); - void processImportInternal(const bool process); - void processImportNewCat(const LLUUID&, bool process); - - bool swapWithPrevious(ALAOSet::AOState* state, S32 index); - bool swapWithNext(ALAOSet::AOState* state, S32 index); - - void cycleTimeout(const ALAOSet* set); - void cycle(eCycleMode cycleMode); - - void inMouselook(const bool in_mouselook); - void selectSet(ALAOSet* set); - ALAOSet* selectSetByName(const std::string& name); - ALAOSet* getSetByName(const std::string& name) const; - - // callback from LLAppViewer - static void onLoginComplete(); - - const std::vector<ALAOSet*>& getSetList() const; - const std::string& getCurrentSetName() const; - const ALAOSet* getDefaultSet() const; - bool renameSet(ALAOSet* set, const std::string& name); - - void setDefaultSet(ALAOSet* set); - void setOverrideSits(ALAOSet* set, const bool override_sits); - void setSmart(ALAOSet* set, const bool smart); - void setDisableStands(ALAOSet* set, const bool disable); - void setCycle(ALAOSet::AOState* set, const bool cycle); - void setRandomize(ALAOSet::AOState* state, const bool randomize); - void setCycleTime(ALAOSet::AOState* state, const F32 time); - - void saveSettings(); - - typedef boost::signals2::signal<void ()> updated_signal_t; - boost::signals2::connection setReloadCallback(const updated_signal_t::slot_type& cb) - { - return mUpdatedSignal.connect(cb); - }; - - typedef boost::signals2::signal<void (const LLUUID&)> animation_changed_signal_t; - boost::signals2::connection setAnimationChangedCallback(const animation_changed_signal_t::slot_type& cb) - { - return mAnimationChangedSignal.connect(cb); - }; - - typedef boost::signals2::signal<void (const std::string&)> set_changed_signal_t; - boost::signals2::connection setSetChangedCallback(const set_changed_signal_t::slot_type& cb) - { - return mSetChangedSignal.connect(cb); - } - -protected: - void init(); - - void setLastMotion(const LLUUID& motion); - void setLastOverriddenMotion(const LLUUID& motion); - void setStateCycleTimer(const ALAOSet::AOState* state); - - void stopAllStandVariants(); - void stopAllSitVariants(); - - bool foreignAnimations(const LLUUID& seat); - const LLUUID& mapSwimming(const LLUUID& motion) const; - - void updateSortOrder(ALAOSet::AOState* state); - void saveSet(const ALAOSet* set); - void saveState(const ALAOSet::AOState* state); - - bool createAnimationLink(const ALAOSet* set, ALAOSet::AOState* state, const LLInventoryItem* item); - bool findForeignItems(const LLUUID& uuid) const; - void purgeFolder(const LLUUID& uuid) const; - - void onRegionChange(); - - void onToggleAOControl(); - static void onNotecardLoadComplete(const LLUUID& assetUUID, LLAssetType::EType type, - void* userdata, S32 status, LLExtStat extStatus); - - void parseNotecard(std::unique_ptr<char[]> buffer); - - updated_signal_t mUpdatedSignal; - animation_changed_signal_t mAnimationChangedSignal; - set_changed_signal_t mSetChangedSignal; - - ALAOTimerCollection mTimerCollection; - ALAOSitCancelTimer mSitCancelTimer; - - bool mEnabled; - bool mInMouselook; - bool mUnderWater; - - LLUUID mAOFolder; - LLUUID mLastMotion; - LLUUID mLastOverriddenMotion; - - std::vector<ALAOSet*> mSets; - std::vector<ALAOSet*> mOldSets; - ALAOSet* mCurrentSet; - ALAOSet* mDefaultSet; - - ALAOSet* mImportSet; - std::vector<ALAOSet*> mOldImportSets; - LLUUID mImportCategory; - S32 mImportRetryCount; - - boost::signals2::connection mRegionChangeConnection; -}; - -extern const std::string ROOT_AO_FOLDER; - -#endif // LL_AOENGINE_H diff --git a/indra/newview/alaoset.h b/indra/newview/alaoset.h deleted file mode 100644 index 5d40068bc9b..00000000000 --- a/indra/newview/alaoset.h +++ /dev/null @@ -1,152 +0,0 @@ -/** - * @file alaoset.h - * @brief Implementation of an Animation Overrider set of animations - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Zi Ree @ Second Life - * Copyright (C) 2016, Cinder Roxley <cinder@sdf.org> - * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * $/LicenseInfo$ - */ - -#ifndef AL_AOSET_H -#define AL_AOSET_H - -#include <array> -#include <utility> -#include "lleventtimer.h" - -class ALAOSet final : public LLEventTimer -{ -public: - ALAOSet(const LLUUID& inventoryID); - ~ALAOSet(); - - // keep number and order in sync with list of names in the constructor - enum - { - Start = 0, // convenience, so we don't have to know the name of the first state - Standing = 0, - Walking, - Running, - Sitting, - SittingOnGround, - Crouching, - CrouchWalking, - Landing, - SoftLanding, - StandingUp, - Falling, - FlyingDown, - FlyingUp, - Flying, - FlyingSlow, - Hovering, - Jumping, - PreJumping, - TurningRight, - TurningLeft, - Typing, - Floating, - SwimmingForward, - SwimmingUp, - SwimmingDown, - AOSTATES_MAX - }; - - struct AOAnimation - { - AOAnimation(): mSortOrder( 0 ) {} - AOAnimation(std::string name, const LLUUID& asset_id, const LLUUID& inv_id, S32 sort_order) - : mName(std::move(name)) - , mAssetUUID(asset_id) - , mInventoryUUID(inv_id) - , mSortOrder(sort_order) - {} - std::string mName; - LLUUID mAssetUUID; - LLUUID mInventoryUUID; - S32 mSortOrder; - }; - - struct AOState - { - std::string mName; - std::vector<std::string> mAlternateNames; - LLUUID mRemapID; - bool mCycle; - bool mRandom; - S32 mCycleTime; - std::vector<AOAnimation> mAnimations; - U32 mCurrentAnimation; - LLUUID mCurrentAnimationID; - LLUUID mInventoryUUID; - bool mDirty; - }; - - // getters and setters - const LLUUID& getInventoryUUID() const { return mInventoryID; } - void setInventoryUUID(const LLUUID& inv_id) { mInventoryID = inv_id; } - - const std::string& getName() const { return mName; } - void setName(const std::string& name) { mName = name; } - - bool getSitOverride() const { return mSitOverride; } - void setSitOverride(const bool sit_override) { mSitOverride = sit_override; } - - bool getSmart() const { return mSmart; } - void setSmart(const bool smart) { mSmart = smart; } - - bool getMouselookDisable() const { return mMouselookDisable; } - void setMouselookDisable(const bool disable) { mMouselookDisable = disable; } - - bool getComplete() const { return mComplete; } - void setComplete(const bool complete) { mComplete = complete; } - - const LLUUID& getMotion() const { return mCurrentMotion; } - void setMotion(const LLUUID& motion) { mCurrentMotion = motion; } - - bool getDirty() const { return mDirty; } - void setDirty(const bool dirty) { mDirty = dirty; } - - AOState* getState(S32 name); - AOState* getStateByName(const std::string& name); - AOState* getStateByRemapID(const LLUUID& id); - const LLUUID& getAnimationForState(AOState* state) const; - - void startTimer(F32 timeout); - void stopTimer(); - virtual BOOL tick() override; - - std::vector<std::string> mStateNames; - -private: - LLUUID mInventoryID; - - std::string mName; - bool mSitOverride; - bool mSmart; - bool mMouselookDisable; - bool mComplete; - LLUUID mCurrentMotion; - bool mDirty; - - std::array<AOState, AOSTATES_MAX> mStates; -}; - -#endif // LL_AOSET_H diff --git a/indra/newview/alchatcommand.cpp b/indra/newview/alchatcommand.cpp index eee0a53329c..40786a66f0c 100644 --- a/indra/newview/alchatcommand.cpp +++ b/indra/newview/alchatcommand.cpp @@ -29,7 +29,7 @@ #include "object_flags.h" // viewer includes -//#include "alaoengine.h" +#include "aoengine.h" #include "llagent.h" #include "llagentcamera.h" #include "llagentui.h" @@ -287,7 +287,6 @@ bool ALChatCommand::parseCommand(std::string data) } return true; } -#if 0 else if (cmd == utf8str_tolower(sAOCommand())) { std::string subcmd; @@ -305,28 +304,27 @@ bool ALChatCommand::parseCommand(std::string data) } else if (subcmd == "sit") { - auto ao_set = ALAOEngine::instance().getSetByName(ALAOEngine::instance().getCurrentSetName()); + auto ao_set = AOEngine::instance().getSetByName(AOEngine::instance().getCurrentSetName()); if (input >> subcmd) { if (subcmd == "on") { - ALAOEngine::instance().setOverrideSits(ao_set, true); + AOEngine::instance().setOverrideSits(ao_set, true); } else if (subcmd == "off") { - ALAOEngine::instance().setOverrideSits(ao_set, false); + AOEngine::instance().setOverrideSits(ao_set, false); } } else { - ALAOEngine::instance().setOverrideSits(ao_set, !ao_set->getSitOverride()); + AOEngine::instance().setOverrideSits(ao_set, !ao_set->getSitOverride()); } return true; } } } -#endif else if (cmd == "/sendmenu") { S32 channel; diff --git a/indra/newview/alfloaterao.cpp b/indra/newview/alfloaterao.cpp deleted file mode 100644 index e62848d4c89..00000000000 --- a/indra/newview/alfloaterao.cpp +++ /dev/null @@ -1,740 +0,0 @@ -/** - * @file alfloaterao.cpp - * @brief Animation overrider controls - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * Copyright (C) 2011, Zi Ree @ Second Life - * Copyright (C) 2016, Cinder <cinder@sdf.org> - * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "alfloaterao.h" -#include "alaoengine.h" -#include "alaoset.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llfloaterreg.h" -#include "llnotificationsutil.h" -#include "llspinctrl.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" - -ALFloaterAO::ALFloaterAO(const LLSD& key) : LLTransientDockableFloater(nullptr, true, key) -, LLEventTimer(10.f) -, mSetList() -, mSelectedSet(nullptr) -, mSelectedState(nullptr) -, mReloadCoverPanel(nullptr) -, mMainInterfacePanel(nullptr) -, mSetSelector(nullptr) -, mActivateSetButton(nullptr) -, mAddButton(nullptr) -, mRemoveButton(nullptr) -, mDefaultCheckBox(nullptr) -, mOverrideSitsCheckBox(nullptr) -, mSmartCheckBox(nullptr) -, mDisableMouselookCheckBox(nullptr) -, mStateSelector(nullptr) -, mAnimationList(nullptr) -, mCurrentBoldItem(nullptr) -, mMoveUpButton(nullptr) -, mMoveDownButton(nullptr) -, mTrashButton(nullptr) -, mCycleCheckBox(nullptr) -, mRandomizeCheckBox(nullptr) -, mCycleTimeTextLabel(nullptr) -, mCycleTimeSpinner(nullptr) -, mReloadButton(nullptr) -, mPreviousButton(nullptr) -, mNextButton(nullptr) -, mCanDragAndDrop(false) -, mImportRunning(false) -{ - mEventTimer.stop(); - - mCommitCallbackRegistrar.add("AO.Reload", boost::bind(&ALFloaterAO::onClickReload, this)); - mCommitCallbackRegistrar.add("AO.ActivateSet", boost::bind(&ALFloaterAO::onClickActivate, this)); - mCommitCallbackRegistrar.add("AO.AddSet", boost::bind(&ALFloaterAO::onClickAdd, this)); - mCommitCallbackRegistrar.add("AO.RemoveSet", boost::bind(&ALFloaterAO::onClickRemove, this)); - mCommitCallbackRegistrar.add("AO.SelectSet", boost::bind(&ALFloaterAO::onSelectSet, this, _2)); - mCommitCallbackRegistrar.add("AO.SelectState", boost::bind(&ALFloaterAO::onSelectState, this)); - mCommitCallbackRegistrar.add("AO.NextAnim", boost::bind(&ALFloaterAO::onClickNext, this)); - mCommitCallbackRegistrar.add("AO.PrevAnim", boost::bind(&ALFloaterAO::onClickPrevious, this)); - mCommitCallbackRegistrar.add("AO.SetCycle", boost::bind(&ALFloaterAO::onCheckCycle, this)); - mCommitCallbackRegistrar.add("AO.SetCycleTime", boost::bind(&ALFloaterAO::onChangeCycleTime, this)); - mCommitCallbackRegistrar.add("AO.SetDefault", boost::bind(&ALFloaterAO::onCheckDefault, this)); - mCommitCallbackRegistrar.add("AO.SetRandomize", boost::bind(&ALFloaterAO::onCheckRandomize, this)); - mCommitCallbackRegistrar.add("AO.SetSitOverride", boost::bind(&ALFloaterAO::onCheckOverrideSits, this)); - mCommitCallbackRegistrar.add("AO.SetSmart", boost::bind(&ALFloaterAO::onCheckSmart, this)); - mCommitCallbackRegistrar.add("AO.DisableStandsML", boost::bind(&ALFloaterAO::onCheckDisableStands, this)); - mCommitCallbackRegistrar.add("AO.SelectAnim", boost::bind(&ALFloaterAO::onChangeAnimationSelection, this)); - mCommitCallbackRegistrar.add("AO.RemoveAnim", boost::bind(&ALFloaterAO::onClickTrash, this)); - mCommitCallbackRegistrar.add("AO.MoveAnimUp", boost::bind(&ALFloaterAO::onClickMoveUp, this)); - mCommitCallbackRegistrar.add("AO.MoveAnimDown", boost::bind(&ALFloaterAO::onClickMoveDown, this)); - - //mEnableCallbackRegistrar.add("AO.EnableSet", boost::bind()); - //mEnableCallbackRegistrar.add("AO.EnableState", boost::bind()); -} - -ALFloaterAO::~ALFloaterAO() -{ - if (mReloadCallback.connected()) - mReloadCallback.disconnect(); - if (mAnimationChangedCallback.connected()) - mAnimationChangedCallback.disconnect(); -} - -void ALFloaterAO::reloading(const bool reload) -{ - if (reload) - mEventTimer.start(); - else - mEventTimer.stop(); - - mReloadCoverPanel->setVisible(reload); - enableSetControls(!reload); - enableStateControls(!reload); -} - -BOOL ALFloaterAO::tick() -{ - // reloading took too long, probably missed the signal, so we hide the reload cover - LL_WARNS("AOEngine") << "AO reloading timeout." << LL_ENDL; - updateList(); - return FALSE; -} - -void ALFloaterAO::updateSetParameters() -{ - mOverrideSitsCheckBox->setValue(mSelectedSet->getSitOverride()); - mSmartCheckBox->setValue(mSelectedSet->getSmart()); - mDisableMouselookCheckBox->setValue(mSelectedSet->getMouselookDisable()); - BOOL isDefault = (mSelectedSet == ALAOEngine::instance().getDefaultSet()) ? TRUE : FALSE; - mDefaultCheckBox->setValue(isDefault); - mDefaultCheckBox->setEnabled(!isDefault); - updateSmart(); -} - -void ALFloaterAO::updateAnimationList() -{ - S32 currentStateSelected = mStateSelector->getCurrentIndex(); - - mStateSelector->removeall(); - onChangeAnimationSelection(); - - if (!mSelectedSet) - { - mStateSelector->setEnabled(FALSE); - mStateSelector->add(getString("ao_no_animations_loaded")); - return; - } - - for (auto const& stateName : mSelectedSet->mStateNames) - { - ALAOSet::AOState* state = mSelectedSet->getStateByName(stateName); - mStateSelector->add(stateName, state, ADD_BOTTOM, TRUE); - } - - enableStateControls(TRUE); - - if (currentStateSelected == -1) - { - mStateSelector->selectFirstItem(); - } - else - { - mStateSelector->selectNthItem(currentStateSelected); - } - - onSelectState(); -} - -void ALFloaterAO::updateList() -{ - mReloadButton->setEnabled(TRUE); - mImportRunning = false; - - std::string currentSetName = mSetSelector->getSelectedItemLabel(); - if (currentSetName.empty()) - { - currentSetName = ALAOEngine::instance().getCurrentSetName(); - } - - mSetList = ALAOEngine::instance().getSetList(); - mSetSelector->removeall(); - mSetSelector->clear(); - - mAnimationList->deleteAllItems(); - mCurrentBoldItem = nullptr; - reloading(false); - - if (mSetList.empty()) - { - LL_DEBUGS("AOEngine") << "empty set list" << LL_ENDL; - mSetSelector->add(getString("ao_no_sets_loaded")); - mSetSelector->selectNthItem(0); - enableSetControls(false); - return; - } - - for (U32 index = 0; index < mSetList.size(); ++index) - { - std::string setName = mSetList[index]->getName(); - mSetSelector->add(setName, &mSetList[index], ADD_BOTTOM, TRUE); - if (setName.compare(currentSetName) == 0) - { - mSelectedSet = ALAOEngine::instance().selectSetByName(currentSetName); - mSetSelector->selectNthItem(index); - updateSetParameters(); - updateAnimationList(); - } - } - enableSetControls(true); -} - -BOOL ALFloaterAO::postBuild() -{ - LLPanel* panel = getChild<LLPanel>("animation_overrider_outer_panel"); - mMainInterfacePanel = panel->getChild<LLPanel>("animation_overrider_panel"); - mReloadCoverPanel = panel->getChild<LLPanel>("ao_reload_cover"); - - mSetSelector = mMainInterfacePanel->getChild<LLComboBox>("ao_set_selection_combo"); - mActivateSetButton = mMainInterfacePanel->getChild<LLButton>("ao_activate"); - mAddButton = mMainInterfacePanel->getChild<LLButton>("ao_add"); - mRemoveButton = mMainInterfacePanel->getChild<LLButton>("ao_remove"); - mDefaultCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_default"); - mOverrideSitsCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_sit_override"); - mSmartCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_smart"); - mDisableMouselookCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_disable_stands_in_mouselook"); - - mStateSelector = mMainInterfacePanel->getChild<LLComboBox>("ao_state_selection_combo"); - mAnimationList = mMainInterfacePanel->getChild<LLScrollListCtrl>("ao_state_animation_list"); - mMoveUpButton = mMainInterfacePanel->getChild<LLButton>("ao_move_up"); - mMoveDownButton = mMainInterfacePanel->getChild<LLButton>("ao_move_down"); - mTrashButton = mMainInterfacePanel->getChild<LLButton>("ao_trash"); - mCycleCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_cycle"); - mRandomizeCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_randomize"); - mCycleTimeTextLabel = mMainInterfacePanel->getChild<LLTextBox>("ao_cycle_time_seconds_label"); - mCycleTimeSpinner = mMainInterfacePanel->getChild<LLSpinCtrl>("ao_cycle_time"); - - mReloadButton = mMainInterfacePanel->getChild<LLButton>("ao_reload"); - mPreviousButton = mMainInterfacePanel->getChild<LLButton>("ao_previous"); - mNextButton = mMainInterfacePanel->getChild<LLButton>("ao_next"); - - mAnimationList->setCommitOnSelectionChange(TRUE); - - updateSmart(); - - mReloadCallback = ALAOEngine::instance().setReloadCallback(boost::bind(&ALFloaterAO::updateList, this)); - mAnimationChangedCallback = ALAOEngine::instance().setAnimationChangedCallback(boost::bind(&ALFloaterAO::onAnimationChanged, this, _1)); - - onChangeAnimationSelection(); - mMainInterfacePanel->setVisible(TRUE); - reloading(true); - - updateList(); - - return LLDockableFloater::postBuild(); -} - -// static -ALFloaterAO* ALFloaterAO::getInstance() -{ - return LLFloaterReg::getTypedInstance<ALFloaterAO>("ao"); -} - -void ALFloaterAO::enableSetControls(const bool enable) -{ - mSetSelector->setEnabled(enable); - mActivateSetButton->setEnabled(enable); - mRemoveButton->setEnabled(enable); - mDefaultCheckBox->setEnabled(enable); - mOverrideSitsCheckBox->setEnabled(enable); - mDisableMouselookCheckBox->setEnabled(enable); - - if (!enable) - { - enableStateControls(enable); - } -} - -void ALFloaterAO::enableStateControls(const bool enable) -{ - mStateSelector->setEnabled(enable); - mAnimationList->setEnabled(enable); - mCycleCheckBox->setEnabled(enable); - if (enable) - { - updateCycleParameters(); - } - else - { - mRandomizeCheckBox->setEnabled(enable); - mCycleTimeTextLabel->setEnabled(enable); - mCycleTimeSpinner->setEnabled(enable); - } - mPreviousButton->setEnabled(enable); - mNextButton->setEnabled(enable); - mCanDragAndDrop = enable; -} - -void ALFloaterAO::onSelectSet(const LLSD& userdata) -{ - ALAOSet* set = ALAOEngine::instance().getSetByName(userdata.asString()); - if (!set) - { - onRenameSet(); - return; - } - - mSelectedSet = set; - - updateSetParameters(); - updateAnimationList(); -} - -void ALFloaterAO::onRenameSet() -{ - if (!mSelectedSet) - { - LL_WARNS("AOEngine") << "Rename AO set without set selected." << LL_ENDL; - return; - } - - std::string name = mSetSelector->getSimple(); - LLStringUtil::trim(name); - - LLUIString new_set_name = name; - - if (!name.empty()) - { - if ( - LLTextValidate::validateASCIIPrintableNoPipe(new_set_name.getWString()) && // only allow ASCII - name.find_first_of(":|") == std::string::npos) // don't allow : or | - { - if (ALAOEngine::instance().renameSet(mSelectedSet, name)) - { - reloading(true); - return; - } - } - else - { - LLNotificationsUtil::add("RenameAOMustBeASCII", LLSD().with("AO_SET_NAME", name)); - } - } - mSetSelector->setSimple(mSelectedSet->getName()); -} - -void ALFloaterAO::onClickActivate() -{ - LL_DEBUGS("AOEngine") << "Set activated: " << mSetSelector->getSelectedItemLabel() << LL_ENDL; - ALAOEngine::instance().selectSet(mSelectedSet); -} - -LLScrollListItem* ALFloaterAO::addAnimation(const std::string& name) -{ - LLSD row; - row["columns"][0]["column"] = "icon"; - row["columns"][0]["type"] = "icon"; - row["columns"][0]["value"] = "Inv_Animation"; - - row["columns"][1]["column"] = "animation_name"; - row["columns"][1]["type"] = "text"; - row["columns"][1]["value"] = name; - - return mAnimationList->addElement(row); -} - -void ALFloaterAO::onSelectState() -{ - mAnimationList->deleteAllItems(); - mCurrentBoldItem = nullptr; - mAnimationList->setCommentText(getString("ao_no_animations_loaded")); - mAnimationList->setEnabled(FALSE); - - onChangeAnimationSelection(); - - if (!mSelectedSet) return; - - mSelectedState = mSelectedSet->getStateByName(mStateSelector->getSelectedItemLabel()); - if (!mSelectedState) return; - - mSelectedState = static_cast<ALAOSet::AOState*>(mStateSelector->getCurrentUserdata()); - if (!mSelectedState->mAnimations.empty()) - { - for (auto& ao_animation : mSelectedState->mAnimations) - { - LLScrollListItem* item = addAnimation(ao_animation.mName); - if (item) - { - item->setUserdata(&ao_animation.mInventoryUUID); - } - } - - mAnimationList->setCommentText(LLStringUtil::null); - mAnimationList->setEnabled(TRUE); - } - - mCycleCheckBox->setValue(mSelectedState->mCycle); - mRandomizeCheckBox->setValue(mSelectedState->mRandom); - mCycleTimeSpinner->setValue(mSelectedState->mCycleTime); - - updateCycleParameters(); -} - -void ALFloaterAO::onClickReload() -{ - reloading(true); - - mSelectedSet = nullptr; - mSelectedState = nullptr; - - ALAOEngine::instance().reload(false); - updateList(); -} - -void ALFloaterAO::onClickAdd() -{ - LLNotificationsUtil::add("NewAOSet", LLSD(), LLSD(), - boost::bind(&ALFloaterAO::newSetCallback, this, _1, _2)); -} - -BOOL ALFloaterAO::newSetCallback(const LLSD& notification, const LLSD& response) -{ - std::string new_name = response["message"].asString(); - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - LLStringUtil::trim(new_name); - - LLUIString new_set_name = new_name; - - if (new_name.empty()) - { - return FALSE; - } - else if (!LLTextValidate::validateASCIIPrintableNoPipe(new_set_name.getWString()) // only allow ASCII - || new_name.find_first_of(":|") != std::string::npos) // don't allow : or | - { - LLNotificationsUtil::add("NewAOCantContainNonASCII", LLSD().with("AO_SET_NAME", new_name)); - return FALSE; - } - - if (option == 0) - { - ALAOEngine::instance().addSet(new_name); - { - reloading(true); - return TRUE; - } - } - return FALSE; -} - -void ALFloaterAO::onClickRemove() -{ - if (!mSelectedSet) return; - - LLNotificationsUtil::add("RemoveAOSet", LLSD().with("AO_SET_NAME", mSelectedSet->getName()), - LLSD(), boost::bind(&ALFloaterAO::removeSetCallback, this, _1, _2)); -} - -BOOL ALFloaterAO::removeSetCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (option == 0) - { - if (ALAOEngine::instance().removeSet(mSelectedSet)) - { - reloading(true); - // to prevent snapping back to deleted set - mSetSelector->removeall(); - // visually indicate there are no items left - mSetSelector->clear(); - mAnimationList->deleteAllItems(); - mCurrentBoldItem = nullptr; - return TRUE; - } - } - return FALSE; -} - -void ALFloaterAO::onCheckDefault() -{ - if (mSelectedSet) - { - ALAOEngine::instance().setDefaultSet(mSelectedSet); - } -} - -void ALFloaterAO::onCheckOverrideSits() -{ - if (mSelectedSet) - { - ALAOEngine::instance().setOverrideSits(mSelectedSet, mOverrideSitsCheckBox->getValue().asBoolean()); - } - updateSmart(); -} - -void ALFloaterAO::updateSmart() -{ - mSmartCheckBox->setEnabled(mOverrideSitsCheckBox->getValue()); -} - -void ALFloaterAO::onCheckSmart() -{ - if (mSelectedSet) - { - ALAOEngine::instance().setSmart(mSelectedSet, mSmartCheckBox->getValue().asBoolean()); - } -} - -void ALFloaterAO::onCheckDisableStands() -{ - if (mSelectedSet) - { - ALAOEngine::instance().setDisableStands(mSelectedSet, mDisableMouselookCheckBox->getValue().asBoolean()); - } -} - -void ALFloaterAO::onChangeAnimationSelection() -{ - std::vector<LLScrollListItem*> list = mAnimationList->getAllSelected(); - LL_DEBUGS("AOEngine") << "Selection count: " << list.size() << LL_ENDL; - - BOOL resortEnable = FALSE; - BOOL trashEnable = FALSE; - - // Linden Lab bug: scroll lists still select the first item when you click on them, even when they are disabled. - // The control does not memorize it's enabled/disabled state, so mAnimationList->mEnabled() doesn't seem to work. - // So we need to safeguard against it. - if (!mCanDragAndDrop) - { - mAnimationList->deselectAllItems(); - LL_DEBUGS("AOEngine") << "Selection count now: " << list.size() << LL_ENDL; - } - else if (!list.empty()) - { - if (list.size() == 1) - { - resortEnable = TRUE; - } - trashEnable = TRUE; - } - - mMoveDownButton->setEnabled(resortEnable); - mMoveUpButton->setEnabled(resortEnable); - mTrashButton->setEnabled(trashEnable); -} - -void ALFloaterAO::onClickMoveUp() -{ - if (!mSelectedState) - { - return; - } - - std::vector<LLScrollListItem*> list = mAnimationList->getAllSelected(); - if (list.size() != 1) - { - return; - } - - S32 currentIndex = mAnimationList->getFirstSelectedIndex(); - if (currentIndex == -1) - { - return; - } - - if (ALAOEngine::instance().swapWithPrevious(mSelectedState, currentIndex)) - { - mAnimationList->swapWithPrevious(currentIndex); - } -} - -void ALFloaterAO::onClickMoveDown() -{ - if (!mSelectedState) - { - return; - } - - std::vector<LLScrollListItem*> list = mAnimationList->getAllSelected(); - if (list.size() != 1) return; - - S32 currentIndex = mAnimationList->getFirstSelectedIndex(); - if (currentIndex >= (mAnimationList->getItemCount() - 1)) - { - return; - } - - if (ALAOEngine::instance().swapWithNext(mSelectedState, currentIndex)) - { - mAnimationList->swapWithNext(currentIndex); - } -} - -void ALFloaterAO::onClickTrash() -{ - if (!mSelectedState) return; - - std::vector<LLScrollListItem*> list = mAnimationList->getAllSelected(); - if (list.empty()) return; - - for (auto iter = list.crbegin(), iter_end = list.crend(); iter != iter_end; ++iter) - { - ALAOEngine::instance().removeAnimation(mSelectedSet, mSelectedState, mAnimationList->getItemIndex(*iter)); - } - - mAnimationList->deleteSelectedItems(); - mCurrentBoldItem = nullptr; -} - -void ALFloaterAO::updateCycleParameters() -{ - BOOL cycle = mCycleCheckBox->getValue().asBoolean(); - mRandomizeCheckBox->setEnabled(cycle); - mCycleTimeTextLabel->setEnabled(cycle); - mCycleTimeSpinner->setEnabled(cycle); -} - -void ALFloaterAO::onCheckCycle() -{ - if (mSelectedState) - { - bool cycle = mCycleCheckBox->getValue().asBoolean(); - ALAOEngine::instance().setCycle(mSelectedState, cycle); - updateCycleParameters(); - } -} - -void ALFloaterAO::onCheckRandomize() -{ - if (mSelectedState) - { - ALAOEngine::instance().setRandomize(mSelectedState, mRandomizeCheckBox->getValue().asBoolean()); - } -} - -void ALFloaterAO::onChangeCycleTime() -{ - if (mSelectedState) - { - ALAOEngine::instance().setCycleTime(mSelectedState, mCycleTimeSpinner->getValueF32()); - } -} - -void ALFloaterAO::onClickPrevious() -{ - ALAOEngine::instance().cycle(ALAOEngine::CyclePrevious); -} - -void ALFloaterAO::onClickNext() -{ - ALAOEngine::instance().cycle(ALAOEngine::CycleNext); -} - -void ALFloaterAO::onAnimationChanged(const LLUUID& animation) -{ - LL_DEBUGS("AOEngine") << "Received animation change to " << animation << LL_ENDL; - - if (mCurrentBoldItem) - { - LLScrollListText* column = static_cast<LLScrollListText*>(mCurrentBoldItem->getColumn(1)); - column->setFontStyle(LLFontGL::NORMAL); - - mCurrentBoldItem = nullptr; - } - - if (animation.isNull()) return; - - std::vector<LLScrollListItem*> item_list = mAnimationList->getAllData(); - for (LLScrollListItem* item : item_list) - { - LLUUID* id = static_cast<LLUUID*>(item->getUserdata()); - - if (id == &animation) - { - mCurrentBoldItem = item; - - LLScrollListText* column = static_cast<LLScrollListText*>(mCurrentBoldItem->getColumn(1)); - column->setFontStyle(LLFontGL::BOLD); - - return; - } - } -} - -// virtual -BOOL ALFloaterAO::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType type, void* data, - EAcceptance* accept, std::string& tooltipMsg) -{ - LLInventoryItem* item = (LLInventoryItem*)data; - - if (type == DAD_NOTECARD) - { - if (mImportRunning) - { - *accept = ACCEPT_NO; - return TRUE; - } - *accept = ACCEPT_YES_SINGLE; - if (item && drop) - { - if (ALAOEngine::instance().importNotecard(item)) - { - reloading(true); - mReloadButton->setEnabled(FALSE); - mImportRunning = true; - } - } - } - else if (type == DAD_ANIMATION) - { - if (!drop && (!mSelectedSet || !mSelectedState || !mCanDragAndDrop)) - { - *accept = ACCEPT_NO; - return TRUE; - } - *accept = ACCEPT_YES_MULTI; - if (item && drop) - { - if (ALAOEngine::instance().addAnimation(mSelectedSet, mSelectedState, item)) - { - addAnimation(item->getName()); - - // TODO: this would be the right thing to do, but it blocks multi drop - // before final release this must be resolved - reloading(true); - } - } - } - else - { - *accept = ACCEPT_NO; - } - - return TRUE; -} diff --git a/indra/newview/alfloaterao.h b/indra/newview/alfloaterao.h deleted file mode 100644 index a51446200da..00000000000 --- a/indra/newview/alfloaterao.h +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @file alfloaterao.h - * @brief Animation overrider controls - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * Copyright (C) 2011, Zi Ree @ Second Life - * Copyright (C) 2016, Cinder <cinder@sdf.org> - * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef AL_FLOATERAO_H -#define AL_FLOATERAO_H - -#include "lleventtimer.h" -#include "lltransientdockablefloater.h" -#include "alaoset.h" - -class LLButton; -class LLComboBox; -class LLCheckBoxCtrl; -class LLScrollListCtrl; -class LLScrollListItem; -class LLSpinCtrl; -class LLTextBox; - -class ALFloaterAO final : public LLTransientDockableFloater, public LLEventTimer -{ - friend class LLFloaterReg; - friend class LLPanelAOMini; - -private: - ALFloaterAO(const LLSD& key); - ~ALFloaterAO(); - -public: - BOOL postBuild() override; - void updateList(); - void updateSetParameters(); - void updateAnimationList(); - static ALFloaterAO* getInstance(); - - BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, - EAcceptance* accept, std::string& tooltip_msg) override; - -protected: - void onClickPrevious(); - void onClickNext(); - ALAOSet* getSelectedSet() const { return mSelectedSet; } - -private: - LLScrollListItem* addAnimation(const std::string& name); - void onAnimationChanged(const LLUUID& animation); - void reloading(const bool reload); - - void onSelectSet(const LLSD& userdata); - void onRenameSet(); - void onSelectState(); - void onChangeAnimationSelection(); - void onClickReload(); - void onClickAdd(); - void onClickRemove(); - void onClickActivate(); - void onCheckDefault(); - void onCheckOverrideSits(); - void onCheckSmart(); - void onCheckDisableStands(); - void onClickMoveUp(); - void onClickMoveDown(); - void onClickTrash(); - void onCheckCycle(); - void onCheckRandomize(); - void onChangeCycleTime(); - - void updateSmart(); - void updateCycleParameters(); - - void enableSetControls(const bool enable); - void enableStateControls(const bool enable); - - BOOL newSetCallback(const LLSD& notification, const LLSD& response); - BOOL removeSetCallback(const LLSD& notification, const LLSD& response); - - virtual BOOL tick() override; - - std::vector<ALAOSet*> mSetList; - ALAOSet* mSelectedSet; - ALAOSet::AOState* mSelectedState; - - LLPanel* mReloadCoverPanel; - - // Full interface - - LLPanel* mMainInterfacePanel; - - LLComboBox* mSetSelector; - LLButton* mActivateSetButton; - LLButton* mAddButton; - LLButton* mRemoveButton; - LLCheckBoxCtrl* mDefaultCheckBox; - LLCheckBoxCtrl* mOverrideSitsCheckBox; - LLCheckBoxCtrl* mSmartCheckBox; - LLCheckBoxCtrl* mDisableMouselookCheckBox; - - LLComboBox* mStateSelector; - LLScrollListCtrl* mAnimationList; - LLScrollListItem* mCurrentBoldItem; - LLButton* mMoveUpButton; - LLButton* mMoveDownButton; - LLButton* mTrashButton; - LLCheckBoxCtrl* mCycleCheckBox; - LLCheckBoxCtrl* mRandomizeCheckBox; - LLTextBox* mCycleTimeTextLabel; - LLSpinCtrl* mCycleTimeSpinner; - - LLButton* mReloadButton; - - LLButton* mPreviousButton; - LLButton* mNextButton; - - bool mCanDragAndDrop; - bool mImportRunning; - - boost::signals2::connection mReloadCallback; - boost::signals2::connection mAnimationChangedCallback; -}; - -#endif // LL_FLOATERAO_H diff --git a/indra/newview/alpanelaomini.cpp b/indra/newview/alpanelaomini.cpp index d238ccef1f6..677841827a1 100644 --- a/indra/newview/alpanelaomini.cpp +++ b/indra/newview/alpanelaomini.cpp @@ -32,8 +32,8 @@ #include "llviewerprecompiledheaders.h" #include "alpanelaomini.h" -#include "alaoengine.h" -#include "alfloaterao.h" +#include "aoengine.h" +#include "ao.h" #include "llfloaterreg.h" #include "llcombobox.h" @@ -64,8 +64,8 @@ BOOL ALPanelAOMini::postBuild() { mSetList = getChild<LLComboBox>("set_list"); mSetList->setCommitCallback(boost::bind(&ALPanelAOMini::onSelectSet, this, _2)); - mReloadCallback = ALAOEngine::instance().setReloadCallback(boost::bind(&ALPanelAOMini::updateSetList, this)); - mSetChangedCallback = ALAOEngine::instance().setSetChangedCallback(boost::bind(&ALPanelAOMini::onSetChanged, this, _1)); + mReloadCallback = AOEngine::instance().setReloadCallback(boost::bind(&ALPanelAOMini::updateSetList, this)); + mSetChangedCallback = AOEngine::instance().setSetChangedCallback(boost::bind(&ALPanelAOMini::onSetChanged, this, _1)); return TRUE; } @@ -75,17 +75,17 @@ BOOL ALPanelAOMini::postBuild() void ALPanelAOMini::updateSetList() { - const std::vector<ALAOSet*>& list = ALAOEngine::getInstance()->getSetList(); + const std::vector<AOSet*>& list = AOEngine::getInstance()->getSetList(); if (list.empty()) { return; } mSetList->removeall(); - for (ALAOSet* set : list) + for (AOSet* set : list) { mSetList->add(set->getName(), &set, ADD_BOTTOM, true); } - const std::string& current_set = ALAOEngine::instance().getCurrentSetName(); + const std::string& current_set = AOEngine::instance().getCurrentSetName(); mSetList->selectByValue(LLSD(current_set)); } @@ -104,31 +104,31 @@ void ALPanelAOMini::onSetChanged(const std::string& set_name) void ALPanelAOMini::onSelectSet(const LLSD& userdata) { - ALAOSet* selected_set = ALAOEngine::instance().getSetByName(userdata.asString()); + AOSet* selected_set = AOEngine::instance().getSetByName(userdata.asString()); if (selected_set) { - ALAOEngine::instance().selectSet(selected_set); + AOEngine::instance().selectSet(selected_set); } } void ALPanelAOMini::onClickSit(const LLSD& userdata) { - const std::string& current_set = ALAOEngine::instance().getCurrentSetName(); - ALAOSet* selected_set = ALAOEngine::instance().getSetByName(current_set); + const std::string& current_set = AOEngine::instance().getCurrentSetName(); + AOSet* selected_set = AOEngine::instance().getSetByName(current_set); if (selected_set) { - ALAOEngine::instance().setOverrideSits(selected_set, userdata.asBoolean()); + AOEngine::instance().setOverrideSits(selected_set, userdata.asBoolean()); } } void ALPanelAOMini::onClickNext() { - ALAOEngine::instance().cycle(ALAOEngine::CycleNext); + AOEngine::instance().cycle(AOEngine::CycleNext); } void ALPanelAOMini::onClickPrevious() { - ALAOEngine::instance().cycle(ALAOEngine::CyclePrevious); + AOEngine::instance().cycle(AOEngine::CyclePrevious); } void ALPanelAOMini::openAOFloater() diff --git a/indra/newview/ao.cpp b/indra/newview/ao.cpp new file mode 100644 index 00000000000..bc5505b38cf --- /dev/null +++ b/indra/newview/ao.cpp @@ -0,0 +1,904 @@ +/** + * @file ao.cpp + * @brief Anything concerning the Viewer Side Animation Overrider GUI + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2011, Zi Ree @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "ao.h" +#include "aoengine.h" +#include "aoset.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llnotificationsutil.h" +#include "llspinctrl.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" + +FloaterAO::FloaterAO(const LLSD& key) +: LLTransientDockableFloater(nullptr, true, key), LLEventTimer(10.f), + mSetList(0), + mSelectedSet(0), + mSelectedState(0), + mCanDragAndDrop(false), + mImportRunning(false), + mCurrentBoldItem(nullptr), + mMore(true) +{ + mEventTimer.stop(); +} + +FloaterAO::~FloaterAO() +{ +} + +void FloaterAO::reloading(bool reload) +{ + if (reload) + { + mEventTimer.start(); + } + else + { + mEventTimer.stop(); + } + + mReloadCoverPanel->setVisible(reload); + enableSetControls(!reload); + enableStateControls(!reload); +} + +BOOL FloaterAO::tick() +{ + // reloading took too long, probably missed the signal, so we hide the reload cover + LL_WARNS("AOEngine") << "AO reloading timeout." << LL_ENDL; + updateList(); + return FALSE; +} + +void FloaterAO::updateSetParameters() +{ + mOverrideSitsCheckBox->setValue(mSelectedSet->getSitOverride()); + mOverrideSitsCheckBoxSmall->setValue(mSelectedSet->getSitOverride()); + mSmartCheckBox->setValue(mSelectedSet->getSmart()); + mDisableMouselookCheckBox->setValue(mSelectedSet->getMouselookStandDisable()); + BOOL isDefault = (mSelectedSet == AOEngine::instance().getDefaultSet()); + mDefaultCheckBox->setValue(isDefault); + mDefaultCheckBox->setEnabled(!isDefault); + updateSmart(); +} + +void FloaterAO::updateAnimationList() +{ + S32 currentStateSelected = mStateSelector->getCurrentIndex(); + + mStateSelector->removeall(); + onChangeAnimationSelection(); + + if (!mSelectedSet) + { + mStateSelector->setEnabled(FALSE); + mStateSelector->add(getString("ao_no_animations_loaded")); + return; + } + + for (auto index = 0; index < mSelectedSet->mStateNames.size(); ++index) + { + const std::string& stateName = mSelectedSet->mStateNames[index]; + AOSet::AOState* state = mSelectedSet->getStateByName(stateName); + mStateSelector->add(stateName, state, ADD_BOTTOM, TRUE); + } + + enableStateControls(true); + + if (currentStateSelected == -1) + { + mStateSelector->selectFirstItem(); + } + else + { + mStateSelector->selectNthItem(currentStateSelected); + } + + onSelectState(); +} + +void FloaterAO::updateList() +{ + mReloadButton->setEnabled(TRUE); + mImportRunning = false; + + // Lambda provides simple Alpha sorting, note this is case sensitive. + auto sortRuleLambda = [](const AOSet* s1, const AOSet* s2) -> bool + { + return s1->getName() < s2->getName(); + }; + + mSetList = AOEngine::instance().getSetList(); + std::sort(mSetList.begin(), mSetList.end(), sortRuleLambda); + + // remember currently selected animation set name + std::string currentSetName = mSetSelector->getSelectedItemLabel(); + + mSetSelector->removeall(); + mSetSelectorSmall->removeall(); + mSetSelector->clear(); + mSetSelectorSmall->clear(); + + mAnimationList->deleteAllItems(); + mCurrentBoldItem = nullptr; + reloading(false); + + if (mSetList.empty()) + { + LL_DEBUGS("AOEngine") << "empty set list" << LL_ENDL; + mSetSelector->add(getString("ao_no_sets_loaded")); + mSetSelectorSmall->add(getString("ao_no_sets_loaded")); + mSetSelector->selectNthItem(0); + mSetSelectorSmall->selectNthItem(0); + enableSetControls(FALSE); + return; + } + + // make sure we have an animation set name to display + if (currentSetName.empty()) + { + // selected animation set was empty, get the currently active animation set from the engine + currentSetName = AOEngine::instance().getCurrentSetName(); + LL_DEBUGS("AOEngine") << "Current set name was empty, fetched name \"" << currentSetName << "\" from AOEngine" << LL_ENDL; + + if(currentSetName.empty()) + { + // selected animation set was empty, get the name of the first animation set in the list + currentSetName = mSetList[0]->getName(); + LL_DEBUGS("AOEngine") << "Current set name still empty, fetched first set's name \"" << currentSetName << "\"" << LL_ENDL; + } + } + + size_t selected_index = 0; + for (auto index = 0; index < mSetList.size(); ++index) + { + std::string setName = mSetList[index]->getName(); + mSetSelector->add(setName, &mSetList[index], ADD_BOTTOM, TRUE); + mSetSelectorSmall->add(setName, &mSetList[index], ADD_BOTTOM, TRUE); + if (setName.compare(currentSetName) == 0) + { + selected_index = index; + mSelectedSet = AOEngine::instance().selectSetByName(currentSetName); + updateSetParameters(); + updateAnimationList(); + } + } + + mSetSelector->selectNthItem(selected_index); + mSetSelectorSmall->selectNthItem(selected_index); + + enableSetControls(TRUE); + if (mSetSelector->getSelectedItemLabel().empty()) + { + onClickReload(); + } +} + +BOOL FloaterAO::postBuild() +{ + LLPanel* aoPanel = getChild<LLPanel>("animation_overrider_outer_panel"); + mMainInterfacePanel = aoPanel->getChild<LLPanel>("animation_overrider_panel"); + mSmallInterfacePanel = aoPanel->getChild<LLPanel>("animation_overrider_panel_small"); + mReloadCoverPanel = aoPanel->getChild<LLPanel>("ao_reload_cover"); + + mSetSelector = mMainInterfacePanel->getChild<LLComboBox>("ao_set_selection_combo"); + mActivateSetButton = mMainInterfacePanel->getChild<LLButton>("ao_activate"); + mAddButton = mMainInterfacePanel->getChild<LLButton>("ao_add"); + mRemoveButton = mMainInterfacePanel->getChild<LLButton>("ao_remove"); + mDefaultCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_default"); + mOverrideSitsCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_sit_override"); + mSmartCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_smart"); + mDisableMouselookCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_disable_stands_in_mouselook"); + + mStateSelector = mMainInterfacePanel->getChild<LLComboBox>("ao_state_selection_combo"); + mAnimationList = mMainInterfacePanel->getChild<LLScrollListCtrl>("ao_state_animation_list"); + mMoveUpButton = mMainInterfacePanel->getChild<LLButton>("ao_move_up"); + mMoveDownButton = mMainInterfacePanel->getChild<LLButton>("ao_move_down"); + mTrashButton = mMainInterfacePanel->getChild<LLButton>("ao_trash"); + mCycleCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_cycle"); + mRandomizeCheckBox = mMainInterfacePanel->getChild<LLCheckBoxCtrl>("ao_randomize"); + mCycleTimeTextLabel = mMainInterfacePanel->getChild<LLTextBox>("ao_cycle_time_seconds_label"); + mCycleTimeSpinner = mMainInterfacePanel->getChild<LLSpinCtrl>("ao_cycle_time"); + + mReloadButton = mMainInterfacePanel->getChild<LLButton>("ao_reload"); + mPreviousButton = mMainInterfacePanel->getChild<LLButton>("ao_previous"); + mNextButton = mMainInterfacePanel->getChild<LLButton>("ao_next"); + mLessButton = mMainInterfacePanel->getChild<LLButton>("ao_less"); + + mSetSelectorSmall = mSmallInterfacePanel->getChild<LLComboBox>("ao_set_selection_combo_small"); + mMoreButton = mSmallInterfacePanel->getChild<LLButton>("ao_more"); + mPreviousButtonSmall = mSmallInterfacePanel->getChild<LLButton>("ao_previous_small"); + mNextButtonSmall = mSmallInterfacePanel->getChild<LLButton>("ao_next_small"); + mOverrideSitsCheckBoxSmall = mSmallInterfacePanel->getChild<LLCheckBoxCtrl>("ao_sit_override_small"); + + mSetSelector->setCommitCallback(boost::bind(&FloaterAO::onSelectSet, this)); + mSetSelector->setFocusLostCallback(boost::bind(&FloaterAO::onSelectSet, this)); + mActivateSetButton->setCommitCallback(boost::bind(&FloaterAO::onClickActivate, this)); + mAddButton->setCommitCallback(boost::bind(&FloaterAO::onClickAdd, this)); + mRemoveButton->setCommitCallback(boost::bind(&FloaterAO::onClickRemove, this)); + mDefaultCheckBox->setCommitCallback(boost::bind(&FloaterAO::onCheckDefault, this)); + mOverrideSitsCheckBox->setCommitCallback(boost::bind(&FloaterAO::onCheckOverrideSits, this)); + mSmartCheckBox->setCommitCallback(boost::bind(&FloaterAO::onCheckSmart, this)); + mDisableMouselookCheckBox->setCommitCallback(boost::bind(&FloaterAO::onCheckDisableStands, this)); + + mAnimationList->setCommitOnSelectionChange(TRUE); + + mStateSelector->setCommitCallback(boost::bind(&FloaterAO::onSelectState, this)); + mAnimationList->setCommitCallback(boost::bind(&FloaterAO::onChangeAnimationSelection, this)); + mMoveUpButton->setCommitCallback(boost::bind(&FloaterAO::onClickMoveUp, this)); + mMoveDownButton->setCommitCallback(boost::bind(&FloaterAO::onClickMoveDown, this)); + mTrashButton->setCommitCallback(boost::bind(&FloaterAO::onClickTrash, this)); + mCycleCheckBox->setCommitCallback(boost::bind(&FloaterAO::onCheckCycle, this)); + mRandomizeCheckBox->setCommitCallback(boost::bind(&FloaterAO::onCheckRandomize, this)); + mCycleTimeSpinner->setCommitCallback(boost::bind(&FloaterAO::onChangeCycleTime, this)); + + mReloadButton->setCommitCallback(boost::bind(&FloaterAO::onClickReload, this)); + mPreviousButton->setCommitCallback(boost::bind(&FloaterAO::onClickPrevious, this)); + mNextButton->setCommitCallback(boost::bind(&FloaterAO::onClickNext, this)); + mLessButton->setCommitCallback(boost::bind(&FloaterAO::onClickLess, this)); + mOverrideSitsCheckBoxSmall->setCommitCallback(boost::bind(&FloaterAO::onCheckOverrideSitsSmall, this)); + + mSetSelectorSmall->setCommitCallback(boost::bind(&FloaterAO::onSelectSetSmall, this)); + mMoreButton->setCommitCallback(boost::bind(&FloaterAO::onClickMore, this)); + mPreviousButtonSmall->setCommitCallback(boost::bind(&FloaterAO::onClickPrevious, this)); + mNextButtonSmall->setCommitCallback(boost::bind(&FloaterAO::onClickNext, this)); + + updateSmart(); + + AOEngine::instance().setReloadCallback(boost::bind(&FloaterAO::updateList, this)); + AOEngine::instance().setAnimationChangedCallback(boost::bind(&FloaterAO::onAnimationChanged, this, _1)); + + onChangeAnimationSelection(); + mMainInterfacePanel->setVisible(TRUE); + mSmallInterfacePanel->setVisible(FALSE); + reloading(true); + + updateList(); + + if (gSavedPerAccountSettings.getBOOL("UseFullAOInterface")) + { + onClickMore(); + } + else + { + onClickLess(); + } + + return LLDockableFloater::postBuild(); +} + +void FloaterAO::enableSetControls(BOOL enable) +{ + mSetSelector->setEnabled(enable); + mSetSelectorSmall->setEnabled(enable); + mActivateSetButton->setEnabled(enable); + mRemoveButton->setEnabled(enable); + mDefaultCheckBox->setEnabled(enable && (mSelectedSet != AOEngine::instance().getDefaultSet())); + mOverrideSitsCheckBox->setEnabled(enable); + mOverrideSitsCheckBoxSmall->setEnabled(enable); + mDisableMouselookCheckBox->setEnabled(enable); + + if (!enable) + { + enableStateControls(enable); + } +} + +void FloaterAO::enableStateControls(BOOL enable) +{ + mStateSelector->setEnabled(enable); + mAnimationList->setEnabled(enable); + mCycleCheckBox->setEnabled(enable); + if (enable) + { + updateCycleParameters(); + } + else + { + mRandomizeCheckBox->setEnabled(enable); + mCycleTimeTextLabel->setEnabled(enable); + mCycleTimeSpinner->setEnabled(enable); + } + mPreviousButton->setEnabled(enable); + mPreviousButtonSmall->setEnabled(enable); + mNextButton->setEnabled(enable); + mNextButtonSmall->setEnabled(enable); + mCanDragAndDrop = enable; +} + +void FloaterAO::onOpen(const LLSD& key) +{ + LLDockableFloater::onOpen(key); +} + +void FloaterAO::onClose(bool app_quitting) +{ + LLDockableFloater::onClose(app_quitting); +} + +void FloaterAO::onSelectSet() +{ + AOSet* set = AOEngine::instance().getSetByName(mSetSelector->getSelectedItemLabel()); + if (!set) + { + onRenameSet(); + return; + } + + // only update the interface when we actually selected a different set - FIRE-29542 + if (mSelectedSet != set) + { + mSelectedSet=set; + + updateSetParameters(); + updateAnimationList(); + } +} + +void FloaterAO::onSelectSetSmall() +{ + // sync main set selector with small set selector + mSetSelector->selectNthItem(mSetSelectorSmall->getCurrentIndex()); + + mSelectedSet = AOEngine::instance().getSetByName(mSetSelectorSmall->getSelectedItemLabel()); + if (mSelectedSet) + { + updateSetParameters(); + updateAnimationList(); + + // small selector activates the selected set immediately + onClickActivate(); + } +} + +void FloaterAO::onRenameSet() +{ + if (!mSelectedSet) + { + LL_WARNS("AOEngine") << "Rename AO set without set selected." << LL_ENDL; + return; + } + + std::string name = mSetSelector->getSimple(); + LLStringUtil::trim(name); + + LLUIString new_set_name = name; + + if (!name.empty()) + { + if ( + LLTextValidate::validateASCIIPrintableNoPipe(new_set_name.getWString()) && // only allow ASCII + name.find_first_of(":|") == std::string::npos) // don't allow : or | + { + if (AOEngine::instance().renameSet(mSelectedSet, name)) + { + reloading(TRUE); + return; + } + } + else + { + LLSD args; + args["AO_SET_NAME"] = name; + LLNotificationsUtil::add("RenameAOMustBeASCII", args); + } + } + mSetSelector->setSimple(mSelectedSet->getName()); +} + +void FloaterAO::onClickActivate() +{ + // sync small set selector with main set selector + mSetSelectorSmall->selectNthItem(mSetSelector->getCurrentIndex()); + + LL_DEBUGS("AOEngine") << "Set activated: " << mSetSelector->getSelectedItemLabel() << LL_ENDL; + AOEngine::instance().selectSet(mSelectedSet); +} + +LLScrollListItem* FloaterAO::addAnimation(const std::string& name) +{ + LLSD row; + row["columns"][0]["column"] = "icon"; + row["columns"][0]["type"] = "icon"; + row["columns"][0]["value"] = "Stop_Off"; + + row["columns"][1]["column"] = "animation_name"; + row["columns"][1]["type"] = "text"; + row["columns"][1]["value"] = name; + + return mAnimationList->addElement(row); +} + +void FloaterAO::onSelectState() +{ + mAnimationList->deleteAllItems(); + mCurrentBoldItem = nullptr; + mAnimationList->setCommentText(getString("ao_no_animations_loaded")); + mAnimationList->setEnabled(FALSE); + + onChangeAnimationSelection(); + + if (!mSelectedSet) + { + return; + } + + mSelectedState = mSelectedSet->getStateByName(mStateSelector->getSelectedItemLabel()); + if (!mSelectedState) + { + return; + } + + mSelectedState = (AOSet::AOState*)mStateSelector->getCurrentUserdata(); + if (mSelectedState->mAnimations.size()) + { + for (auto index = 0; index < mSelectedState->mAnimations.size(); ++index) + { + LLScrollListItem* item = addAnimation(mSelectedState->mAnimations[index].mName); + if (item) + { + item->setUserdata(&mSelectedState->mAnimations[index].mInventoryUUID); + + // update currently playing animation if we are looking at the currently running state in the UI + if (mSelectedSet->getMotion() == mSelectedState->mRemapID && + mSelectedState->mCurrentAnimationID == mSelectedState->mAnimations[index].mAssetUUID) + { + mCurrentBoldItem = item; + ((LLScrollListIcon*)item->getColumn(0))->setValue("Play_Over"); + ((LLScrollListText*)item->getColumn(1))->setFontStyle(LLFontGL::BOLD); + } + } + } + + mAnimationList->setCommentText(""); + mAnimationList->setEnabled(TRUE); + } + + mCycleCheckBox->setValue(mSelectedState->mCycle); + mRandomizeCheckBox->setValue(mSelectedState->mRandom); + mCycleTimeSpinner->setValue(mSelectedState->mCycleTime); + + updateCycleParameters(); +} + +void FloaterAO::onClickReload() +{ + reloading(true); + + mSelectedSet = nullptr; + mSelectedState = nullptr; + + AOEngine::instance().reload(false); + updateList(); +} + +void FloaterAO::onClickAdd() +{ + LLNotificationsUtil::add("NewAOSet", LLSD(), LLSD(), boost::bind(&FloaterAO::newSetCallback, this, _1, _2)); +} + +bool FloaterAO::newSetCallback(const LLSD& notification, const LLSD& response) +{ + std::string newSetName = response["message"].asString(); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + LLStringUtil::trim(newSetName); + + LLUIString new_set_name = newSetName; + + if (newSetName.empty()) + { + return false; + } + else if ( + !LLTextValidate::validateASCIIPrintableNoPipe(new_set_name.getWString()) || // only allow ASCII + newSetName.find_first_of(":|") != std::string::npos) // don't allow : or | + { + LLSD args; + args["AO_SET_NAME"] = newSetName; + LLNotificationsUtil::add("NewAOCantContainNonASCII", args); + return false; + } + + if (option == 0) + { + AOEngine::instance().addSet(newSetName, [this](const LLUUID& new_cat_id) + { + reloading(true); + }); + } + return false; +} + +void FloaterAO::onClickRemove() +{ + if (!mSelectedSet) + { + return; + } + + LLSD args; + args["AO_SET_NAME"] = mSelectedSet->getName(); + LLNotificationsUtil::add("RemoveAOSet", args, LLSD(), boost::bind(&FloaterAO::removeSetCallback, this, _1, _2)); +} + +bool FloaterAO::removeSetCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (option ==0) + { + if (AOEngine::instance().removeSet(mSelectedSet)) + { + reloading(true); + // to prevent snapping back to deleted set + mSetSelector->removeall(); + mSetSelectorSmall->removeall(); + // visually indicate there are no items left + mSetSelector->clear(); + mSetSelectorSmall->clear(); + mAnimationList->deleteAllItems(); + mCurrentBoldItem = nullptr; + return true; + } + } + return false; +} + +void FloaterAO::onCheckDefault() +{ + if (mSelectedSet) + { + AOEngine::instance().setDefaultSet(mSelectedSet); + } +} + +void FloaterAO::onCheckOverrideSits() +{ + mOverrideSitsCheckBoxSmall->setValue(mOverrideSitsCheckBox->getValue()); + if (mSelectedSet) + { + AOEngine::instance().setOverrideSits(mSelectedSet, mOverrideSitsCheckBox->getValue().asBoolean()); + } + updateSmart(); +} + +void FloaterAO::onCheckOverrideSitsSmall() +{ + mOverrideSitsCheckBox->setValue(mOverrideSitsCheckBoxSmall->getValue()); + onCheckOverrideSits(); +} + +void FloaterAO::updateSmart() +{ + mSmartCheckBox->setEnabled(mOverrideSitsCheckBox->getValue()); +} + +void FloaterAO::onCheckSmart() +{ + if (mSelectedSet) + { + AOEngine::instance().setSmart(mSelectedSet, mSmartCheckBox->getValue().asBoolean()); + } +} + +void FloaterAO::onCheckDisableStands() +{ + if (mSelectedSet) + { + AOEngine::instance().setDisableMouselookStands(mSelectedSet, mDisableMouselookCheckBox->getValue().asBoolean()); + } +} + +void FloaterAO::onChangeAnimationSelection() +{ + std::vector<LLScrollListItem*> list = mAnimationList->getAllSelected(); + LL_DEBUGS("AOEngine") << "Selection count: " << list.size() << LL_ENDL; + + BOOL resortEnable = FALSE; + BOOL trashEnable = FALSE; + + // Linden Lab bug: scroll lists still select the first item when you click on them, even when they are disabled. + // The control does not memorize it's enabled/disabled state, so mAnimationList->mEnabled() doesn't seem to work. + // So we need to safeguard against it. + if (!mCanDragAndDrop) + { + mAnimationList->deselectAllItems(); + LL_DEBUGS("AOEngine") << "Selection count now: " << list.size() << LL_ENDL; + } + else if (!list.empty()) + { + if (list.size() == 1) + { + resortEnable = TRUE; + } + trashEnable = TRUE; + } + + mMoveDownButton->setEnabled(resortEnable); + mMoveUpButton->setEnabled(resortEnable); + mTrashButton->setEnabled(trashEnable); +} + +void FloaterAO::onClickMoveUp() +{ + if (!mSelectedState) + { + return; + } + + std::vector<LLScrollListItem*> list = mAnimationList->getAllSelected(); + if (list.size() != 1) + { + return; + } + + S32 currentIndex = mAnimationList->getFirstSelectedIndex(); + if (currentIndex == -1) + { + return; + } + + if (AOEngine::instance().swapWithPrevious(mSelectedState, currentIndex)) + { + mAnimationList->swapWithPrevious(currentIndex); + } +} + +void FloaterAO::onClickMoveDown() +{ + if (!mSelectedState) + { + return; + } + + std::vector<LLScrollListItem*> list = mAnimationList->getAllSelected(); + if (list.size() != 1) + { + return; + } + + S32 currentIndex = mAnimationList->getFirstSelectedIndex(); + if (currentIndex >= (mAnimationList->getItemCount() - 1)) + { + return; + } + + if (AOEngine::instance().swapWithNext(mSelectedState, currentIndex)) + { + mAnimationList->swapWithNext(currentIndex); + } +} + +void FloaterAO::onClickTrash() +{ + if (!mSelectedState) + { + return; + } + + std::vector<LLScrollListItem*> list = mAnimationList->getAllSelected(); + if (list.empty()) + { + return; + } + + for (auto index = list.size() - 1; index != -1; --index) + { + AOEngine::instance().removeAnimation(mSelectedSet, mSelectedState, mAnimationList->getItemIndex(list[index])); + } + + mAnimationList->deleteSelectedItems(); + mCurrentBoldItem = nullptr; +} + +void FloaterAO::updateCycleParameters() +{ + BOOL enabled = mCycleCheckBox->getValue().asBoolean(); + mRandomizeCheckBox->setEnabled(enabled); + mCycleTimeTextLabel->setEnabled(enabled); + mCycleTimeSpinner->setEnabled(enabled); +} + +void FloaterAO::onCheckCycle() +{ + if (mSelectedState) + { + bool cycle = mCycleCheckBox->getValue().asBoolean(); + AOEngine::instance().setCycle(mSelectedState, cycle); + updateCycleParameters(); + } +} + +void FloaterAO::onCheckRandomize() +{ + if (mSelectedState) + { + AOEngine::instance().setRandomize(mSelectedState, mRandomizeCheckBox->getValue().asBoolean()); + } +} + +void FloaterAO::onChangeCycleTime() +{ + if (mSelectedState) + { + AOEngine::instance().setCycleTime(mSelectedState, mCycleTimeSpinner->getValueF32()); + } +} + +void FloaterAO::onClickPrevious() +{ + AOEngine::instance().cycle(AOEngine::CyclePrevious); +} + +void FloaterAO::onClickNext() +{ + AOEngine::instance().cycle(AOEngine::CycleNext); +} + +void FloaterAO::onClickMore() +{ + LLRect fullSize = gSavedPerAccountSettings.getRect("floater_rect_ao_full"); + + if (fullSize.getHeight() < getMinHeight()) + { + fullSize.setOriginAndSize(fullSize.mLeft, fullSize.mBottom, fullSize.getWidth(), getRect().getHeight()); + } + + if (fullSize.getWidth() < getMinWidth()) + { + fullSize.setOriginAndSize(fullSize.mLeft, fullSize.mBottom, getRect().getWidth(), fullSize.getHeight()); + } + + mMore = true; + + mSmallInterfacePanel->setVisible(FALSE); + mMainInterfacePanel->setVisible(TRUE); + enableResizeCtrls(true, true, true); + + gSavedPerAccountSettings.setBOOL("UseFullAOInterface", TRUE); + + reshape(getRect().getWidth(), fullSize.getHeight()); +} + +void FloaterAO::onClickLess() +{ + LLRect fullSize = getRect(); + LLRect smallSize = mSmallInterfacePanel->getRect(); + smallSize.setLeftTopAndSize(0, 0, smallSize.getWidth(), smallSize.getHeight() + getHeaderHeight()); + + gSavedPerAccountSettings.setRect("floater_rect_ao_full", fullSize); + + mMore = false; + + mSmallInterfacePanel->setVisible(TRUE); + mMainInterfacePanel->setVisible(FALSE); + enableResizeCtrls(true, true, false); + + gSavedPerAccountSettings.setBOOL("UseFullAOInterface", FALSE); + + reshape(getRect().getWidth(), smallSize.getHeight()); + + // save current size and position + gSavedPerAccountSettings.setRect("floater_rect_ao_full", fullSize); +} + +void FloaterAO::onAnimationChanged(const LLUUID& animation) +{ + LL_DEBUGS("AOEngine") << "Received animation change to " << animation << LL_ENDL; + + if (mCurrentBoldItem) + { + ((LLScrollListIcon*)mCurrentBoldItem->getColumn(0))->setValue("Stop_Off"); + ((LLScrollListText*)mCurrentBoldItem->getColumn(1))->setFontStyle(LLFontGL::NORMAL); + + mCurrentBoldItem = nullptr; + } + + if (animation.isNull()) + { + return; + } + + // why do we have no LLScrollListCtrl::getItemByUserdata() ? -Zi + for (auto item : mAnimationList->getAllData()) + { + LLUUID* id = (LLUUID*)item->getUserdata(); + + if (id == &animation) + { + mCurrentBoldItem = item; + + ((LLScrollListIcon*)mCurrentBoldItem->getColumn(0))->setValue("Play_Over"); + ((LLScrollListText*)mCurrentBoldItem->getColumn(1))->setFontStyle(LLFontGL::BOLD); + + return; + } + } +} + +// virtual +BOOL FloaterAO::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType type, void* data, + EAcceptance* accept, std::string& tooltipMsg) +{ + // no drag & drop on small interface + if (!mMore) + { + tooltipMsg = getString("ao_dnd_only_on_full_interface"); + *accept = ACCEPT_NO; + return TRUE; + } + + LLInventoryItem* item = (LLInventoryItem*)data; + + if (type == DAD_NOTECARD) + { + if (mImportRunning) + { + *accept = ACCEPT_NO; + return TRUE; + } + *accept = ACCEPT_YES_SINGLE; + if (item && drop) + { + if (AOEngine::instance().importNotecard(item)) + { + reloading(TRUE); + mReloadButton->setEnabled(FALSE); + mImportRunning = TRUE; + } + } + } + else if (type == DAD_ANIMATION) + { + if (!drop && (!mSelectedSet || !mSelectedState || !mCanDragAndDrop)) + { + *accept = ACCEPT_NO; + return TRUE; + } + *accept = ACCEPT_YES_MULTI; + if (item && drop) + { + AOEngine::instance().addAnimation(mSelectedSet, mSelectedState, item); + addAnimation(item->getName()); + + // TODO: this would be the right thing to do, but it blocks multi drop + // before final release this must be resolved + reloading(true); + } + } + else + { + *accept = ACCEPT_NO; + } + + return TRUE; +} diff --git a/indra/newview/ao.h b/indra/newview/ao.h new file mode 100644 index 00000000000..45482bab453 --- /dev/null +++ b/indra/newview/ao.h @@ -0,0 +1,159 @@ +/** + * @file ao.h + * @brief Anything concerning the Viewer Side Animation Overrider GUI + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2011, Zi Ree @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef AO_H +#define AO_H + +#include "lleventtimer.h" +#include "lltransientdockablefloater.h" +#include "aoset.h" + +class LLButton; +class LLComboBox; +class LLCheckBoxCtrl; +class LLScrollListCtrl; +class LLScrollListItem; +class LLSpinCtrl; +class LLTextBox; + +class FloaterAO +: public LLTransientDockableFloater, + public LLEventTimer +{ + friend class LLFloaterReg; + + private: + FloaterAO(const LLSD& key); + ~FloaterAO(); + + public: + /*virtual*/ BOOL postBuild(); + virtual void onOpen(const LLSD& key); + virtual void onClose(bool app_quitting); + void updateList(); + void updateSetParameters(); + void updateAnimationList(); + + BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, + EAcceptance* accept, std::string& tooltip_msg); + + protected: + LLScrollListItem* addAnimation(const std::string& name); + + void onSelectSet(); + void onSelectSetSmall(); + void onRenameSet(); + void onSelectState(); + void onChangeAnimationSelection(); + void onClickReload(); + void onClickAdd(); + void onClickRemove(); + void onClickActivate(); + void onCheckDefault(); + void onCheckOverrideSits(); + void onCheckOverrideSitsSmall(); + void onCheckSmart(); + void onCheckDisableStands(); + void onClickMoveUp(); + void onClickMoveDown(); + void onClickTrash(); + void onCheckCycle(); + void onCheckRandomize(); + void onChangeCycleTime(); + void onClickPrevious(); + void onClickNext(); + + void onClickMore(); + void onClickLess(); + + void onAnimationChanged(const LLUUID& animation); + + void reloading(bool reload); + + void updateSmart(); + void updateCycleParameters(); + + void enableSetControls(BOOL enable); + void enableStateControls(BOOL enable); + + bool newSetCallback(const LLSD& notification, const LLSD& response); + bool removeSetCallback(const LLSD& notification, const LLSD& response); + + virtual BOOL tick(); + + std::vector<AOSet*> mSetList; + AOSet* mSelectedSet; + AOSet::AOState* mSelectedState; + + LLPanel* mReloadCoverPanel; + + // Full interface + + LLPanel* mMainInterfacePanel; + + LLComboBox* mSetSelector; + LLButton* mActivateSetButton; + LLButton* mAddButton; + LLButton* mRemoveButton; + LLCheckBoxCtrl* mDefaultCheckBox; + LLCheckBoxCtrl* mOverrideSitsCheckBox; + LLCheckBoxCtrl* mSmartCheckBox; + LLCheckBoxCtrl* mDisableMouselookCheckBox; + + LLComboBox* mStateSelector; + LLScrollListCtrl* mAnimationList; + LLScrollListItem* mCurrentBoldItem; + LLButton* mMoveUpButton; + LLButton* mMoveDownButton; + LLButton* mTrashButton; + LLCheckBoxCtrl* mCycleCheckBox; + LLCheckBoxCtrl* mRandomizeCheckBox; + LLTextBox* mCycleTimeTextLabel; + LLSpinCtrl* mCycleTimeSpinner; + + LLButton* mReloadButton; + + LLButton* mPreviousButton; + LLButton* mNextButton; + LLButton* mLessButton; + + // Small interface + + LLPanel* mSmallInterfacePanel; + + LLComboBox* mSetSelectorSmall; + LLButton* mMoreButton; + LLButton* mPreviousButtonSmall; + LLButton* mNextButtonSmall; + LLCheckBoxCtrl* mOverrideSitsCheckBoxSmall; + + bool mCanDragAndDrop; + bool mImportRunning; + bool mMore; +}; + +#endif // AO_H diff --git a/indra/newview/aoengine.cpp b/indra/newview/aoengine.cpp new file mode 100644 index 00000000000..683a54f7e1e --- /dev/null +++ b/indra/newview/aoengine.cpp @@ -0,0 +1,2446 @@ +/** + * @file aoengine.cpp + * @brief The core Animation Overrider engine + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Zi Ree @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + + +#include "roles_constants.h" + +#include "aoengine.h" +#include "aoset.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llanimationstates.h" +#include "llassetstorage.h" +#include "llfilesystem.h" +#include "llinventoryfunctions.h" // for ROOT_FIRESTORM_FOLDER +#include "llinventorymodel.h" +#include "llnotificationsutil.h" +#include "llstring.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "llvoavatarself.h" + +constexpr F32 INVENTORY_POLLING_INTERVAL = 5.0f; + +AOEngine::AOEngine() : + LLSingleton<AOEngine>(), + mCurrentSet(nullptr), + mDefaultSet(nullptr), + mEnabled(false), + mEnabledStands(false), + mInMouselook(false), + mUnderWater(false), + mImportSet(nullptr), + mAOFolder(LLUUID::null), + mLastMotion(ANIM_AGENT_STAND), + mLastOverriddenMotion(ANIM_AGENT_STAND) +{ + gSavedPerAccountSettings.getControl("AlchemyAOEnable")->getCommitSignal()->connect(boost::bind(&AOEngine::onToggleAOControl, this)); + gSavedPerAccountSettings.getControl("UseAOStands")->getCommitSignal()->connect(boost::bind(&AOEngine::onToggleAOStandsControl, this)); + gSavedPerAccountSettings.getControl("PauseAO")->getCommitSignal()->connect(boost::bind(&AOEngine::onPauseAO, this)); + + mRegionChangeConnection = gAgent.addRegionChangedCallback(boost::bind(&AOEngine::onRegionChange, this)); +} + +AOEngine::~AOEngine() +{ + clear(false); + + if (mRegionChangeConnection.connected()) + { + mRegionChangeConnection.disconnect(); + } +} + +void AOEngine::init() +{ + BOOL do_enable = gSavedPerAccountSettings.getBOOL("AlchemyAOEnable"); + BOOL do_enable_stands = gSavedPerAccountSettings.getBOOL("UseAOStands"); + if (do_enable) + { + // enable_stands() calls enable(), but we need to set the + // mEnabled variable properly + mEnabled = true; + // Enabling the AO always enables stands to start with + enableStands(true); + } + else + { + enableStands(do_enable_stands); + enable(false); + } +} + +// static +void AOEngine::onLoginComplete() +{ + AOEngine::instance().init(); +} + +void AOEngine::onToggleAOControl() +{ + enable(gSavedPerAccountSettings.getBOOL("AlchemyAOEnable")); + if (mEnabled) + { + // Enabling the AO always enables stands to start with + gSavedPerAccountSettings.setBOOL("UseAOStands", TRUE); + } +} + +void AOEngine::onToggleAOStandsControl() +{ + enableStands(gSavedPerAccountSettings.getBOOL("UseAOStands")); +} + +void AOEngine::onPauseAO() +{ + // can't use mEnabled here as that gets switched over by enable() + if (gSavedPerAccountSettings.getBOOL("AlchemyAOEnable")) + { + enable(!gSavedPerAccountSettings.getBOOL("PauseAO")); + } +} + +void AOEngine::clear(bool from_timer) +{ + std::move(mSets.begin(), mSets.end(), std::back_inserter(mOldSets)); + mSets.clear(); + + mCurrentSet = nullptr; + mSetChangedSignal(mCurrentSet->getName()); + + //<ND/> FIRE-3801; We cannot delete any AOSet object if we're called from a timer tick. AOSet is derived from LLEventTimer and destruction will + // fail in ~LLInstanceTracker when a destructor runs during iteration. + if (!from_timer) + { + std::for_each(mOldSets.begin(), mOldSets.end(), DeletePointer()); + mOldSets.clear(); + + std::for_each(mOldImportSets.begin(), mOldImportSets.end(), DeletePointer()); + mOldImportSets.clear(); + } +} + +void AOEngine::stopAllStandVariants() +{ + LL_DEBUGS("AOEngine") << "stopping all STAND variants." << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_STAND_1, ANIM_REQUEST_STOP); + gAgent.sendAnimationRequest(ANIM_AGENT_STAND_2, ANIM_REQUEST_STOP); + gAgent.sendAnimationRequest(ANIM_AGENT_STAND_3, ANIM_REQUEST_STOP); + gAgent.sendAnimationRequest(ANIM_AGENT_STAND_4, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_1); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_2); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_3); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_4); +} + +void AOEngine::stopAllSitVariants() +{ + LL_DEBUGS("AOEngine") << "stopping all SIT variants." << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_FEMALE, ANIM_REQUEST_STOP); + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_FEMALE); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GENERIC); + + // scripted seats that use ground_sit as animation need special treatment + const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(gAgentAvatarp->getRoot()); + if (agentRoot && agentRoot->getID() != gAgentID) + { + LL_DEBUGS("AOEngine") << "Not stopping ground sit animations while sitting on a prim." << LL_ENDL; + return; + } + + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GROUND, ANIM_REQUEST_STOP); + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GROUND_CONSTRAINED, ANIM_REQUEST_STOP); + + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GROUND); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GROUND_CONSTRAINED); +} + +void AOEngine::setLastMotion(const LLUUID& motion) +{ + if (motion != ANIM_AGENT_TYPE) + { + mLastMotion = motion; + } +} + +void AOEngine::setLastOverriddenMotion(const LLUUID& motion) +{ + if (motion != ANIM_AGENT_TYPE) + { + mLastOverriddenMotion = motion; + } +} + +bool AOEngine::foreignAnimations() +{ + // checking foreign animations only makes sense when smart sit is enabled + if (!mCurrentSet->getSmart()) + { + return false; + } + + // get the seat the avatar is sitting on + const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(gAgentAvatarp->getRoot()); + if (!agentRoot) + { + // this should not happen, ever + return false; + } + + const LLUUID& seat = agentRoot->getID(); + if (seat == gAgentID) + { + LL_DEBUGS("AOEngine") << "Not checking for foreign animation when not sitting." << LL_ENDL; + return false; + } + + LL_DEBUGS("AOEngine") << "Checking for foreign animation on seat " << seat << LL_ENDL; + + for (const auto& [source_id, animation_id] : gAgentAvatarp->mAnimationSources) + { + // skip animations run by the avatar itself + if (source_id != gAgentID) + { + // find the source object where the animation came from + LLViewerObject* source = gObjectList.findObject(source_id); + + // proceed if it's not an attachment + if (source && !source->isAttachment()) + { + LL_DEBUGS("AOEngine") << "Source " << source_id << " is running animation " << animation_id << LL_ENDL; + + // get the source's root prim + LLViewerObject* sourceRoot = dynamic_cast<LLViewerObject*>(source->getRoot()); + + // if the root prim is the same as the animation source, report back as TRUE + if (sourceRoot && sourceRoot->getID() == seat) + { + LL_DEBUGS("AOEngine") << "foreign animation " << animation_id << " found on seat." << LL_ENDL; + return true; + } + } + } + } + + return false; +} + +// map motion to underwater state, return nullptr if not applicable +AOSet::AOState* AOEngine::mapSwimming(const LLUUID& motion) const +{ + S32 stateNum; + + if (motion == ANIM_AGENT_HOVER) + { + stateNum = AOSet::Floating; + } + else if (motion == ANIM_AGENT_FLY) + { + stateNum = AOSet::SwimmingForward; + } + else if (motion == ANIM_AGENT_HOVER_UP) + { + stateNum = AOSet::SwimmingUp; + } + else if (motion == ANIM_AGENT_HOVER_DOWN) + { + stateNum = AOSet::SwimmingDown; + } + else + { + // motion not applicable for underwater mapping + return nullptr; + } + + return mCurrentSet->getState(stateNum); +} + +// switch between swimming and flying on transition in and out of Linden region water +void AOEngine::checkBelowWater(bool check_underwater) +{ + // there was no transition, do nothing + if (mUnderWater == check_underwater) + { + return; + } + + // only applies to motions that actually change underwater and have animations inside + AOSet::AOState* mapped = mapSwimming(mLastMotion); + if (!mapped || mapped->mAnimations.empty()) + { + // set underwater status but do nothing else + mUnderWater = check_underwater; + return; + } + + // find animation id to stop when transitioning + LLUUID id = override(mLastMotion, false); + if (id.isNull()) + { + // no animation in overrider for this state, use Linden Lab motion + id = mLastMotion; + } + + // stop currently running animation + gAgent.sendAnimationRequest(id, ANIM_REQUEST_STOP); + + if (!mUnderWater) + { + // remember which animation we stopped while going under water, to catch the stop + // request later in the overrider - this prevents the overrider from triggering itself + // after the region comes back with the stop request for Linden Lab motion ids + mTransitionId = id; + } + + // set requested underwater status for overrider + mUnderWater = check_underwater; + + // find animation id to start when transitioning + id = override(mLastMotion, true); + if (id.isNull()) + { + // no animation in overrider for this state, use Linden Lab motion + id = mLastMotion; + } + + // start new animation + gAgent.sendAnimationRequest(id, ANIM_REQUEST_START); +} + +// find the correct animation state for the requested motion, mapping flying to +// swimming where necessary +AOSet::AOState* AOEngine::getStateForMotion(const LLUUID& motion) const +{ + // get default state for this motion + AOSet::AOState* defaultState = mCurrentSet->getStateByRemapID(motion); + if (!mUnderWater) + { + return defaultState; + } + + // get state for underwater motion + AOSet::AOState* mapped = mapSwimming(motion); + if (!mapped) + { + // not applicable for underwater motion, so use default state + return defaultState; + } + + // check if the underwater state has any animations to play + if (mapped->mAnimations.empty()) + { + // no animations in underwater state, return default + return defaultState; + } + return mapped; +} + +void AOEngine::enableStands(bool enable_stands) +{ + mEnabledStands = enable_stands; + // let the main enable routine decide if we need to change animations + // but don't actually change the state of the enabled flag + enable(mEnabled); +} + +void AOEngine::enable(bool enable) +{ + LL_DEBUGS("AOEngine") << "using " << gAnimLibrary.animationName(mLastMotion) << " enable " << enable << LL_ENDL; + mEnabled = enable; + + if (!mCurrentSet) + { + LL_DEBUGS("AOEngine") << "enable(" << enable << ") without animation set loaded." << LL_ENDL; + return; + } + + AOSet::AOState* state = mCurrentSet->getStateByRemapID(mLastMotion); + if (mEnabled) + { + if (state && !state->mAnimations.empty()) + { + LL_DEBUGS("AOEngine") << "Enabling animation state " << state->mName << LL_ENDL; + + // do not stop underlying sit animations when re-enabling the AO + if (mLastOverriddenMotion != ANIM_AGENT_SIT_GROUND && + mLastOverriddenMotion != ANIM_AGENT_SIT_GROUND_CONSTRAINED && + mLastOverriddenMotion != ANIM_AGENT_SIT) + { + gAgent.sendAnimationRequest(mLastOverriddenMotion, ANIM_REQUEST_STOP); + } + + LLUUID animation = override(mLastMotion, true); + if (animation.isNull()) + { + return; + } + + if (mLastMotion == ANIM_AGENT_STAND) + { + if (!mEnabledStands) + { + LL_DEBUGS("AOEngine") << "Last motion was a STAND, but disabled for stands, ignoring." << LL_ENDL; + return; + } + stopAllStandVariants(); + } + else if (mLastMotion == ANIM_AGENT_WALK) + { + LL_DEBUGS("AOEngine") << "Last motion was a WALK, stopping all variants." << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_WALK_NEW, ANIM_REQUEST_STOP); + gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_WALK, ANIM_REQUEST_STOP); + gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_WALK_NEW, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_WALK_NEW); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_WALK); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_WALK_NEW); + } + else if (mLastMotion == ANIM_AGENT_RUN) + { + LL_DEBUGS("AOEngine") << "Last motion was a RUN, stopping all variants." << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_RUN_NEW, ANIM_REQUEST_STOP); + gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_RUN_NEW, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_RUN_NEW); + gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_RUN_NEW); + } + else if (mLastMotion == ANIM_AGENT_SIT) + { + stopAllSitVariants(); + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_START); + } + else + { + LL_WARNS("AOEngine") << "Unhandled last motion id " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; + } + + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_START); + mAnimationChangedSignal(state->mAnimations[state->mCurrentAnimation].mInventoryUUID); + + // remember to ignore this motion once in the overrider so stopping the Linden motion + // will not trigger a stop of the override animation + mIgnoreMotionStopOnce = mLastMotion; + } + } + else + { + mAnimationChangedSignal(LLUUID::null); + + if (mLastOverriddenMotion == ANIM_AGENT_SIT) + { + // remove sit cycle cover up + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); + } + + // stop all overriders, catch leftovers + for (S32 index = 0; index < AOSet::AOSTATES_MAX; ++index) + { + state = mCurrentSet->getState(index); + if (state) + { + LLUUID animation = state->mCurrentAnimationID; + if (animation.notNull()) + { + LL_DEBUGS("AOEngine") << "Stopping leftover animation from state " << state->mName << LL_ENDL; + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(animation); + state->mCurrentAnimationID.setNull(); + } + } + else + { + LL_DEBUGS("AOEngine") << "state "<< index <<" returned NULL." << LL_ENDL; + } + } + + // restore Linden animation if applicable + if (mLastOverriddenMotion != ANIM_AGENT_SIT || !foreignAnimations()) + { + gAgent.sendAnimationRequest(mLastMotion, ANIM_REQUEST_START); + } + + mCurrentSet->stopTimer(); + } +} + +void AOEngine::setStateCycleTimer(const AOSet::AOState* state) +{ + F32 timeout = state->mCycleTime; + LL_DEBUGS("AOEngine") << "Setting cycle timeout for state " << state->mName << " of " << timeout << LL_ENDL; + if (timeout > 0.0f) + { + mCurrentSet->startTimer(timeout); + } +} + +const LLUUID AOEngine::override(const LLUUID& motion, bool start) +{ + LL_DEBUGS("AOEngine") << "override(" << gAnimLibrary.animationName(motion) << "," << start << ")" << LL_ENDL; + + if (!mEnabled) + { + if (start && mCurrentSet) + { + const AOSet::AOState* state = mCurrentSet->getStateByRemapID(motion); + if (state) + { + setLastMotion(motion); + LL_DEBUGS("AOEngine") << "(disabled AO) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; + if (!state->mAnimations.empty()) + { + setLastOverriddenMotion(motion); + LL_DEBUGS("AOEngine") << "(disabled AO) setting last overridden motion id to " << gAnimLibrary.animationName(mLastOverriddenMotion) << LL_ENDL; + } + } + } + return LLUUID::null; + } + + if (mSets.empty()) + { + LL_DEBUGS("AOEngine") << "No sets loaded. Skipping overrider." << LL_ENDL; + return LLUUID::null; + } + + if (!mCurrentSet) + { + LL_DEBUGS("AOEngine") << "No current AO set chosen. Skipping overrider." << LL_ENDL; + return LLUUID::null; + } + + // ignore stopping this motion once so we can stop the Linden animation + // without killing our overrider when logging in or re-enabling + if (!start && motion == mIgnoreMotionStopOnce) + { + LL_DEBUGS("AOEngine") << "Not stop-overriding motion " << gAnimLibrary.animationName(motion) + << " within same state." << LL_ENDL; + + mIgnoreMotionStopOnce = LLUUID::null; + + // when stopping a sit motion make sure to stop the cycle point cover-up animation + if (motion == ANIM_AGENT_SIT) + { + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); + } + + return LLUUID::null; + } + + // map the requested motion to an animation state, taking underwater + // swimming into account where applicable + AOSet::AOState* state = getStateForMotion(motion); + if (!state) + { + LL_DEBUGS("AOEngine") << "No current AO state for motion " << gAnimLibrary.animationName(motion) << LL_ENDL; +// This part of the code was added to capture an edge case where animations got stuck +// However, it seems it isn't needed anymore and breaks other, more important cases. +// So we disable this code for now, unless bad things happen and the stuck animations +// come back again. -Zi +// if (!gAnimLibrary.animStateToString(motion) && !start) +// { +// state = mCurrentSet->getStateByRemapID(mLastOverriddenMotion); +// if (state && state->mCurrentAnimationID == motion) +// { +// LL_DEBUGS("AOEngine") << "Stop requested for current overridden animation UUID " << motion << " - Skipping." << LL_ENDL; +// } +// else +// { +// LL_DEBUGS("AOEngine") << "Stop requested for unknown UUID " << motion << " - Stopping it just in case." << LL_ENDL; +// gAgent.sendAnimationRequest(motion, ANIM_REQUEST_STOP); +// gAgentAvatarp->LLCharacter::stopMotion(motion); +// } +// } + return LLUUID::null; + } + + mAnimationChangedSignal(LLUUID::null); + + // clean up stray animations as an additional safety measure + // unless current motion is ANIM_AGENT_TYPE, which is a special + // case, as it plays at the same time as other motions + if (motion != ANIM_AGENT_TYPE) + { + constexpr S32 cleanupStates[]= + { + AOSet::Standing, + AOSet::Walking, + AOSet::Running, + AOSet::Sitting, + AOSet::SittingOnGround, + AOSet::Crouching, + AOSet::CrouchWalking, + AOSet::Falling, + AOSet::FlyingDown, + AOSet::FlyingUp, + AOSet::Flying, + AOSet::FlyingSlow, + AOSet::Hovering, + AOSet::Jumping, + AOSet::TurningRight, + AOSet::TurningLeft, + AOSet::Floating, + AOSet::SwimmingForward, + AOSet::SwimmingUp, + AOSet::SwimmingDown, + AOSet::AOSTATES_MAX // end marker, guaranteed to be different from any other entry + }; + + S32 index = 0; + S32 stateNum; + + // loop through the list of states + 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) + { + // 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 + gAgent.sendAnimationRequest(stateToCheck->mCurrentAnimationID, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(stateToCheck->mCurrentAnimationID); + + // mark the state as clean + stateToCheck->mCurrentAnimationID.setNull(); + } + } + index++; + } + } + + LLUUID animation; + + mCurrentSet->stopTimer(); + if (start) + { + setLastMotion(motion); + LL_DEBUGS("AOEngine") << "(enabled AO) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; + + // Disable start stands in Mouselook + if (mCurrentSet->getMouselookStandDisable() && + motion == ANIM_AGENT_STAND && + mInMouselook) + { + LL_DEBUGS("AOEngine") << "(enabled AO, mouselook stand stopped) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; + return LLUUID::null; + } + + // Don't override start and turning stands if stand override is disabled + if (!mEnabledStands && + (motion == ANIM_AGENT_STAND || motion == ANIM_AGENT_TURNRIGHT || motion == ANIM_AGENT_TURNLEFT)) + { + LL_DEBUGS("AOEngine") << "(enabled AO, stands disabled) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; + return LLUUID::null; + } + + // Do not start override sits if not selected + if (!mCurrentSet->getSitOverride() && motion == ANIM_AGENT_SIT) + { + LL_DEBUGS("AOEngine") << "(enabled AO, sit override stopped) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; + return LLUUID::null; + } + + // scripted seats that use ground_sit as animation need special treatment + if (motion == ANIM_AGENT_SIT_GROUND || motion == ANIM_AGENT_SIT_GROUND_CONSTRAINED) + { + const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(gAgentAvatarp->getRoot()); + if (agentRoot && agentRoot->getID() != gAgentID) + { + LL_DEBUGS("AOEngine") << "Ground sit animation playing but sitting on a prim - disabling overrider." << LL_ENDL; + return LLUUID::null; + } + } + + if (!state->mAnimations.empty()) + { + setLastOverriddenMotion(motion); + LL_DEBUGS("AOEngine") << "(enabled AO) setting last overridden motion id to " << gAnimLibrary.animationName(mLastOverriddenMotion) << LL_ENDL; + } + + // do not remember typing as set-wide motion + if (motion != ANIM_AGENT_TYPE) + { + mCurrentSet->setMotion(motion); + } + + animation = mCurrentSet->getAnimationForState(state); + + if (state->mCurrentAnimationID.notNull()) + { + LL_DEBUGS("AOEngine") << "Previous animation for state " + << gAnimLibrary.animationName(motion) + << " was not stopped, but we were asked to start a new one. Killing old animation." << LL_ENDL; + gAgent.sendAnimationRequest(state->mCurrentAnimationID, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(state->mCurrentAnimationID); + } + + state->mCurrentAnimationID = animation; + LL_DEBUGS("AOEngine") << "overriding " << gAnimLibrary.animationName(motion) + << " with " << animation + << " in state " << state->mName + << " of set " << mCurrentSet->getName() + << " (" << mCurrentSet << ")" << LL_ENDL; + + if (animation.notNull() && state->mCurrentAnimation < state->mAnimations.size()) + { + mAnimationChangedSignal(state->mAnimations[state->mCurrentAnimation].mInventoryUUID); + } + + setStateCycleTimer(state); + + if (motion == ANIM_AGENT_SIT) + { + // Use ANIM_AGENT_SIT_GENERIC, so we don't create an overrider loop with ANIM_AGENT_SIT + // while still having a base sitting pose to cover up cycle points + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_START); + if (mCurrentSet->getSmart()) + { + mSitCancelTimer.oneShot(); + } + } + // special treatment for "transient animations" because the viewer needs the Linden animation to know the agent's state + else if (motion == ANIM_AGENT_SIT_GROUND || + motion == ANIM_AGENT_SIT_GROUND_CONSTRAINED || + motion == ANIM_AGENT_PRE_JUMP || + motion == ANIM_AGENT_STANDUP || + motion == ANIM_AGENT_LAND || + motion == ANIM_AGENT_MEDIUM_LAND) + { + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_START); + return LLUUID::null; + } + } + else + { + // check for previously remembered transition motion from/to underwater movement and don't + // allow the overrider to stop it, or it will cancel the transition to underwater motion + // immediately after starting it + if (motion == mTransitionId) + { + // clear transition motion id and return a null UUID to allow the stock Linden animation + // system to take over + mTransitionId.setNull(); + return LLUUID::null; + } + + // clear transition motion id here as well, to make sure there is no stray id left behind + mTransitionId.setNull(); + + animation = state->mCurrentAnimationID; + state->mCurrentAnimationID.setNull(); + + // for typing animaiton, just return the stored animation, reset the state timer, and don't memorize anything else + if (motion == ANIM_AGENT_TYPE) + { + AOSet::AOState* previousState = mCurrentSet->getStateByRemapID(mLastMotion); + if (previousState) + { + setStateCycleTimer(previousState); + } + return animation; + } + + // stop the underlying Linden Lab motion, in case it's still running. + // frequently happens with sits, so we keep it only for those currently. + if (motion == ANIM_AGENT_SIT) + { + stopAllSitVariants(); + } + + if (motion != mCurrentSet->getMotion()) + { + LL_WARNS("AOEngine") << "trying to stop-override motion " << gAnimLibrary.animationName(motion) + << " but the current set has motion " << gAnimLibrary.animationName(mCurrentSet->getMotion()) << LL_ENDL; + return animation; + } + + mCurrentSet->setMotion(LLUUID::null); + + // again, special treatment for "transient" animations to make sure our own animation gets stopped properly + if (motion == ANIM_AGENT_SIT_GROUND || + motion == ANIM_AGENT_SIT_GROUND_CONSTRAINED || + motion == ANIM_AGENT_PRE_JUMP || + motion == ANIM_AGENT_STANDUP || + motion == ANIM_AGENT_LAND || + motion == ANIM_AGENT_MEDIUM_LAND) + { + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(animation); + setStateCycleTimer(state); + return LLUUID::null; + } + } + + return animation; +} + +void AOEngine::checkSitCancel() +{ + if (foreignAnimations()) + { + if (AOSet::AOState* aoState = mCurrentSet->getStateByRemapID(ANIM_AGENT_SIT); aoState) + { + 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(); + } + } + } +} + +void AOEngine::cycleTimeout(const AOSet* set) +{ + if (!mEnabled) + { + return; + } + + if (set != mCurrentSet) + { + LL_WARNS("AOEngine") << "cycleTimeout for set " << set->getName() << " but current set is " << mCurrentSet->getName() << LL_ENDL; + return; + } + + cycle(CycleAny); +} + +void AOEngine::cycle(eCycleMode cycleMode) +{ + if (!mEnabled) + { + return; + } + + if (!mCurrentSet) + { + LL_DEBUGS("AOEngine") << "cycle without set." << LL_ENDL; + return; + } + + // do not cycle if we're sitting and sit-override is off + if (mLastMotion == ANIM_AGENT_SIT && !mCurrentSet->getSitOverride()) + { + return; + } + // do not cycle if we're standing and mouselook stand override is disabled while being in mouselook + else if (mLastMotion == ANIM_AGENT_STAND && mCurrentSet->getMouselookStandDisable() && mInMouselook) + { + return; + } + + AOSet::AOState* state = mCurrentSet->getStateByRemapID(mLastMotion); + if (!state) + { + LL_DEBUGS("AOEngine") << "cycle without state." << LL_ENDL; + return; + } + + if (!state->mAnimations.size()) + { + LL_DEBUGS("AOEngine") << "cycle without animations in state." << LL_ENDL; + return; + } + + // make sure we disable cycling only for timed cycle, so manual cycling still works, even with cycling switched off + if (!state->mCycle && cycleMode == CycleAny) + { + LL_DEBUGS("AOEngine") << "cycle timeout, but state is set to not cycling." << LL_ENDL; + return; + } + + LLUUID oldAnimation = state->mCurrentAnimationID; + LLUUID animation; + + if (cycleMode == CycleAny) + { + animation = mCurrentSet->getAnimationForState(state); + } + else + { + if (cycleMode == CyclePrevious) + { + if (state->mCurrentAnimation == 0) + { + state->mCurrentAnimation = state->mAnimations.size() - 1; + } + else + { + state->mCurrentAnimation--; + } + } + else if (cycleMode == CycleNext) + { + state->mCurrentAnimation++; + if (state->mCurrentAnimation == state->mAnimations.size()) + { + state->mCurrentAnimation = 0; + } + } + + AOSet::AOAnimation& anim = state->mAnimations[state->mCurrentAnimation]; + + if (anim.mAssetUUID.isNull()) + { + 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) + { + LL_DEBUGS("AOEngine") << "Found asset UUID for cycled animation: " << item->getAssetUUID() << " - Updating AOAnimation.mAssetUUID" << LL_ENDL; + anim.mAssetUUID = item->getAssetUUID(); + } + else + { + LL_DEBUGS("AOEngine") << "Inventory UUID " << anim.mOriginalUUID << " for cycled animation " << anim.mName << " still returns no asset." << LL_ENDL; + } + } + + animation = anim.mAssetUUID; + } + + // don't do anything if the animation didn't change + if (animation == oldAnimation) + { + return; + } + + mAnimationChangedSignal(LLUUID::null); + + state->mCurrentAnimationID = animation; + if (animation.notNull()) + { + LL_DEBUGS("AOEngine") << "requesting animation start for motion " << gAnimLibrary.animationName(mLastMotion) << ": " << animation << LL_ENDL; + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_START); + mAnimationChangedSignal(state->mAnimations[state->mCurrentAnimation].mInventoryUUID); + } + else + { + LL_DEBUGS("AOEngine") << "overrider came back with NULL animation for motion " << gAnimLibrary.animationName(mLastMotion) << "." << LL_ENDL; + } + + if (oldAnimation.notNull()) + { + LL_DEBUGS("AOEngine") << "Cycling state " << state->mName << " - stopping animation " << oldAnimation << LL_ENDL; + gAgent.sendAnimationRequest(oldAnimation, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(oldAnimation); + } +} + +void AOEngine::updateSortOrder(AOSet::AOState* state) +{ + for (U32 index = 0; index < state->mAnimations.size(); ++index) + { + U32 sortOrder = state->mAnimations[index].mSortOrder; + + if (sortOrder != index) + { + std::ostringstream numStr(""); + numStr << 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; + + LLViewerInventoryItem* item = gInventory.getItem(state->mAnimations[index].mInventoryUUID); + if (!item) + { + LL_WARNS("AOEngine") << "NULL inventory item found while trying to copy " << state->mAnimations[index].mInventoryUUID << LL_ENDL; + continue; + } + LLPointer<LLViewerInventoryItem> newItem = new LLViewerInventoryItem(item); + + newItem->setDescription(numStr.str()); + newItem->setComplete(TRUE); + newItem->updateServer(FALSE); + + gInventory.updateItem(newItem); + } + } +} + +void AOEngine::addSet(const std::string& name, inventory_func_type callback, bool reload) +{ + if (mAOFolder.isNull()) + { + LL_WARNS("AOEngine") << ROOT_AO_FOLDER << " folder not there yet. Requesting recreation." << LL_ENDL; + tick(); + return; + } + + BOOL wasProtected = gSavedPerAccountSettings.getBOOL("LockAOFolders"); + gSavedPerAccountSettings.setBOOL("LockAOFolders", FALSE); + LL_DEBUGS("AOEngine") << "adding set folder " << name << LL_ENDL; + gInventory.createNewCategory(mAOFolder, LLFolderType::FT_NONE, name, [callback, wasProtected](const LLUUID &new_cat_id) + { + gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); + + callback(new_cat_id); + }); + + if (reload) + { + mTimerCollection.enableReloadTimer(true); + } +} + +bool AOEngine::createAnimationLink(AOSet::AOState* state, const LLInventoryItem* item) +{ + LL_DEBUGS("AOEngine") << "Asset ID " << item->getAssetUUID() << " inventory id " << item->getUUID() << " category id " << state->mInventoryUUID << LL_ENDL; + LL_DEBUGS("AOEngine") << "state " << state->mName << " item " << item->getName() << LL_ENDL; + + if (state->mInventoryUUID.isNull()) + { + LL_DEBUGS("AOEngine") << "state inventory UUID not found, failing." << LL_ENDL; + return false; + } + + LLInventoryObject::const_object_list_t obj_array{ LLConstPointer<LLInventoryObject>(item) }; + link_inventory_array(state->mInventoryUUID, + obj_array, + LLPointer<LLInventoryCallback>(nullptr)); + + return true; +} + +void AOEngine::addAnimation(const AOSet* set, AOSet::AOState* state, const LLInventoryItem* item, bool reload) +{ + AOSet::AOAnimation anim; + anim.mAssetUUID = item->getAssetUUID(); + anim.mInventoryUUID = item->getUUID(); + anim.mOriginalUUID = item->getLinkedUUID(); + anim.mName = item->getName(); + anim.mSortOrder = state->mAnimations.size() + 1; + state->mAnimations.emplace_back(std::move(anim)); + + BOOL wasProtected = gSavedPerAccountSettings.getBOOL("LockAOFolders"); + gSavedPerAccountSettings.setBOOL("LockAOFolders", FALSE); + bool success = createAnimationLink(state, item); + gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); + + if(success) + { + if (reload) + { + mTimerCollection.enableReloadTimer(true); + } + return; + } + + // creating the animation link failed, so we need to create a new folder for this state - + // add the animation asset to the queue of animations to insert into the state - this takes + // care of multi animation drag & drop that come in faster than the viewer can create a new + // inventory folder + state->mAddQueue.push_back(item); + + // if this is the first queued animation for this state, create the folder asyncronously + if(state->mAddQueue.size() == 1) + { + gInventory.createNewCategory(set->getInventoryUUID(), LLFolderType::FT_NONE, state->mName, [this, state, reload, wasProtected](const LLUUID &new_cat_id) + { + state->mInventoryUUID = new_cat_id; + gSavedPerAccountSettings.setBOOL("LockAOFolders", FALSE); + + // add all queued animations to this state's folder and then clear the queue + for (const auto item : state->mAddQueue) + { + createAnimationLink(state, item); + } + state->mAddQueue.clear(); + + gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); + + if (reload) + { + mTimerCollection.enableReloadTimer(true); + } + }); + } +} + +bool AOEngine::findForeignItems(const LLUUID& uuid) const +{ + bool moved = false; + + LLInventoryModel::item_array_t* items; + LLInventoryModel::cat_array_t* cats; + + gInventory.getDirectDescendentsOf(uuid, cats, items); + + if (cats) + { + for (const auto& cat : *cats) + { + if (findForeignItems(cat->getUUID())) + { + moved = true; + } + } + } + + // count backwards in case we have to remove items + BOOL wasProtected = gSavedPerAccountSettings.getBOOL("LockAOFolders"); + gSavedPerAccountSettings.setBOOL("LockAOFolders", FALSE); + + if (items) + { + for (S32 index = items->size() - 1; index >= 0; --index) + { + bool move = false; + + LLPointer<LLViewerInventoryItem> item = items->at(index); + if (item->getIsLinkType()) + { + if (item->getInventoryType() != LLInventoryType::IT_ANIMATION) + { + LL_DEBUGS("AOEngine") << item->getName() << " is a link but does not point to an animation." << LL_ENDL; + move = true; + } + else + { + LL_DEBUGS("AOEngine") << item->getName() << " is an animation link." << LL_ENDL; + } + } + else + { + LL_DEBUGS("AOEngine") << item->getName() << " is not a link!" << LL_ENDL; + move = true; + } + + if (move) + { + moved = true; + gInventory.changeItemParent(item, gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND), false); + LL_DEBUGS("AOEngine") << item->getName() << " moved to lost and found!" << LL_ENDL; + } + } + } + + gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); + + return moved; +} + +// needs a three-step process, since purge of categories only seems to work from trash +void AOEngine::purgeFolder(const LLUUID& uuid) const +{ + // unprotect it + BOOL wasProtected = gSavedPerAccountSettings.getBOOL("LockAOFolders"); + gSavedPerAccountSettings.setBOOL("LockAOFolders", FALSE); + + // move everything that's not an animation link to "lost and found" + if (findForeignItems(uuid)) + { + LLNotificationsUtil::add("AOForeignItemsFound", LLSD()); + } + + // trash it + gInventory.removeCategory(uuid); + + // clean it + purge_descendents_of(uuid, nullptr); + gInventory.notifyObservers(); + + // purge it + remove_inventory_object(uuid, nullptr); + gInventory.notifyObservers(); + + // protect it + gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); +} + +bool AOEngine::removeSet(AOSet* set) +{ + purgeFolder(set->getInventoryUUID()); + + mTimerCollection.enableReloadTimer(true); + return true; +} + +bool AOEngine::removeAnimation(const AOSet* set, AOSet::AOState* state, S32 index) +{ + if (index < 0) + { + return false; + } + + auto numOfAnimations = state->mAnimations.size(); + if (numOfAnimations == 0) + { + return false; + } + + LLViewerInventoryItem* item = gInventory.getItem(state->mAnimations[index].mInventoryUUID); + + // check if this item is actually an animation link + bool move = true; + if (item->getIsLinkType()) + { + if (item->getInventoryType() == LLInventoryType::IT_ANIMATION) + { + // it is an animation link, so mark it to be purged + move = false; + } + } + + // this item was not an animation link, move it to lost and found + if (move) + { + gInventory.changeItemParent(item, gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND), false); + LLNotificationsUtil::add("AOForeignItemsFound", LLSD()); + update(); + return false; + } + + // purge the item from inventory + LL_DEBUGS("AOEngine") << __LINE__ << " purging: " << state->mAnimations[index].mInventoryUUID << LL_ENDL; + remove_inventory_object(state->mAnimations[index].mInventoryUUID, nullptr); // item->getUUID()); + gInventory.notifyObservers(); + + state->mAnimations.erase(state->mAnimations.begin() + index); + + if (state->mAnimations.empty()) + { + LL_DEBUGS("AOEngine") << "purging folder " << state->mName << " from inventory because it's empty." << LL_ENDL; + + LLInventoryModel::item_array_t* items; + LLInventoryModel::cat_array_t* cats; + gInventory.getDirectDescendentsOf(set->getInventoryUUID(), cats, items); + + if (cats) + { + for (const auto& cat : *cats) + { + std::vector<std::string> params; + LLStringUtil::getTokens(cat->getName(), params, ":"); + + if (params.empty()) + { + LL_WARNS("AOEngine") << "Unexpected folder found in ao set folder: " << cat->getName() << LL_ENDL; + return false; + } + + const std::string& stateName = params[0]; + + if (state->mName.compare(stateName) == 0) + { + LL_DEBUGS("AOEngine") << "folder found: " << cat->getName() << " purging uuid " << cat->getUUID() << LL_ENDL; + + purgeFolder(cat->getUUID()); + state->mInventoryUUID.setNull(); + break; + } + } + } + } + else + { + updateSortOrder(state); + } + + return true; +} + +bool AOEngine::swapWithPrevious(AOSet::AOState* state, S32 index) +{ + auto numOfAnimations = state->mAnimations.size(); + if (numOfAnimations < 2 || index == 0) + { + return false; + } + + AOSet::AOAnimation tmpAnim = state->mAnimations[index]; + state->mAnimations.erase(state->mAnimations.begin() + index); + state->mAnimations.insert(state->mAnimations.begin() + index - 1, tmpAnim); + + updateSortOrder(state); + + return true; +} + +bool AOEngine::swapWithNext(AOSet::AOState* state, S32 index) +{ + auto numOfAnimations = state->mAnimations.size(); + if (numOfAnimations < 2 || index == (numOfAnimations - 1)) + { + return false; + } + + AOSet::AOAnimation tmpAnim = state->mAnimations[index]; + state->mAnimations.erase(state->mAnimations.begin() + index); + state->mAnimations.insert(state->mAnimations.begin() + index + 1, tmpAnim); + + updateSortOrder(state); + + return true; +} + +void AOEngine::reloadStateAnimations(AOSet::AOState* state) +{ + LLInventoryModel::item_array_t* items; + LLInventoryModel::cat_array_t* dummy; + + state->mAnimations.clear(); + + gInventory.getDirectDescendentsOf(state->mInventoryUUID, dummy, items); + + if (items) + { + for (const auto& item : *items) + { + LL_DEBUGS("AOEngine") << "Found animation link " << item->LLInventoryItem::getName() + << " desc " << item->LLInventoryItem::getDescription() + << " asset " << item->getAssetUUID() << LL_ENDL; + + AOSet::AOAnimation anim; + anim.mName = item->LLInventoryItem::getName(); + anim.mInventoryUUID = item->getUUID(); + anim.mOriginalUUID = item->getLinkedUUID(); + + anim.mAssetUUID = LLUUID::null; + + // 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() + if (item->getLinkedItem()) + { + anim.mAssetUUID = item->getAssetUUID(); + } + + S32 sortOrder; + if (!LLStringUtil::convertToS32(item->LLInventoryItem::getDescription(), sortOrder)) + { + sortOrder = -1; + } + anim.mSortOrder = sortOrder; + + LL_DEBUGS("AOEngine") << "current sort order is " << sortOrder << LL_ENDL; + + if (sortOrder == -1) + { + LL_WARNS("AOEngine") << "sort order was unknown so append to the end of the list" << LL_ENDL; + state->mAnimations.emplace_back(std::move(anim)); + } + else + { + bool inserted = false; + for (auto index = 0; index < state->mAnimations.size(); ++index) + { + if (state->mAnimations[index].mSortOrder > sortOrder) + { + LL_DEBUGS("AOEngine") << "inserting at index " << index << LL_ENDL; + state->mAnimations.insert(state->mAnimations.begin() + index, anim); + inserted = true; + break; + } + } + if (!inserted) + { + LL_DEBUGS("AOEngine") << "not inserted yet, appending to the list instead" << LL_ENDL; + state->mAnimations.emplace_back(std::move(anim)); + } + } + LL_DEBUGS("AOEngine") << "Animation count now: " << state->mAnimations.size() << LL_ENDL; + } + } + + updateSortOrder(state); +} + +void AOEngine::update() +{ + if (mAOFolder.isNull()) + { + return; + } + + if (!gInventory.isCategoryComplete(mAOFolder)) + { + LL_DEBUGS("AOEngine") << "#AO folder hasn't fully fetched yet, try again next timer tick." << LL_ENDL; + gInventory.fetchDescendentsOf(mAOFolder); + return; + } + + // move everything that's not an animation link to "lost and found" + if (findForeignItems(mAOFolder)) + { + LLNotificationsUtil::add("AOForeignItemsFound", LLSD()); + } + + LLInventoryModel::cat_array_t* categories; + LLInventoryModel::item_array_t* items; + + bool allComplete = true; + mTimerCollection.enableSettingsTimer(false); + + gInventory.getDirectDescendentsOf(mAOFolder, categories, items); + + if (categories) + { + for (const auto& currentCategory : *categories) + { + const std::string& setFolderName = currentCategory->getName(); + + if (setFolderName.empty()) + { + LL_WARNS("AOEngine") << "Folder with emtpy name in AO folder" << LL_ENDL; + continue; + } + + std::vector<std::string> params; + LLStringUtil::getTokens(setFolderName, params, ":"); + if (params.empty()) + { + LL_WARNS("AOEngine") << "Unexpected folder found in ao set folder: " << currentCategory->getName() << LL_ENDL; + continue; + } + + AOSet* newSet = getSetByName(params[0]); + if (!newSet) + { + LL_DEBUGS("AOEngine") << "Adding set " << setFolderName << " to AO." << LL_ENDL; + newSet = new AOSet(currentCategory->getUUID()); + newSet->setName(params[0]); + mSets.emplace_back(newSet); + } + else + { + if (newSet->getComplete()) + { + LL_DEBUGS("AOEngine") << "Set " << params[0] << " already complete. Skipping." << LL_ENDL; + continue; + } + LL_DEBUGS("AOEngine") << "Updating set " << setFolderName << " in AO." << LL_ENDL; + } + allComplete = false; + + for (auto num = 1; num < params.size(); ++num) + { + if (params[num].size() != 2) + { + LL_WARNS("AOEngine") << "Unknown AO set option " << params[num] << LL_ENDL; + } + else if (params[num] == "SO") + { + newSet->setSitOverride(true); + } + else if (params[num] == "SM") + { + newSet->setSmart(true); + } + else if (params[num] == "DM") + { + newSet->setMouselookStandDisable(true); + } + else if (params[num] == "**") + { + mDefaultSet = newSet; + mCurrentSet = newSet; + mSetChangedSignal(mCurrentSet->getName()); + } + else + { + LL_WARNS("AOEngine") << "Unknown AO set option " << params[num] << LL_ENDL; + } + } + + if (gInventory.isCategoryComplete(currentCategory->getUUID())) + { + LL_DEBUGS("AOEngine") << "Set " << params[0] << " is complete, reading states ..." << LL_ENDL; + + LLInventoryModel::cat_array_t* stateCategories; + gInventory.getDirectDescendentsOf(currentCategory->getUUID(), stateCategories, items); + newSet->setComplete(true); + + for (const auto& stateCategory : *stateCategories) + { + std::vector<std::string> state_params; + LLStringUtil::getTokens(stateCategory->getName(), state_params, ":"); + if (params.empty()) + { + LL_WARNS("AOEngine") << "Unexpected state folder found in ao set: " << stateCategory->getName() << LL_ENDL; + continue; + } + const std::string& stateName = state_params[0]; + + AOSet::AOState* state = newSet->getStateByName(stateName); + if (!state) + { + LL_WARNS("AOEngine") << "Unknown state " << stateName << ". Skipping." << LL_ENDL; + continue; + } + LL_DEBUGS("AOEngine") << "Reading state " << stateName << LL_ENDL; + + state->mInventoryUUID = stateCategory->getUUID(); + for (auto num = 1; num < state_params.size(); ++num) + { + if (state_params[num] == "CY") + { + state->mCycle = true; + LL_DEBUGS("AOEngine") << "Cycle on" << LL_ENDL; + } + else if (state_params[num] == "RN") + { + state->mRandom = true; + LL_DEBUGS("AOEngine") << "Random on" << LL_ENDL; + } + else if (state_params[num].substr(0, 2) == "CT") + { + LLStringUtil::convertToS32(state_params[num].substr(2, state_params[num].size() - 2), state->mCycleTime); + LL_DEBUGS("AOEngine") << "Cycle Time specified:" << state->mCycleTime << LL_ENDL; + } + else + { + LL_WARNS("AOEngine") << "Unknown AO set option " << state_params[num] << LL_ENDL; + } + } + + if (!gInventory.isCategoryComplete(state->mInventoryUUID)) + { + LL_DEBUGS("AOEngine") << "State category " << stateName << " is incomplete, fetching descendents" << LL_ENDL; + gInventory.fetchDescendentsOf(state->mInventoryUUID); + allComplete = false; + newSet->setComplete(false); + continue; + } + reloadStateAnimations(state); + } + } + else + { + LL_DEBUGS("AOEngine") << "Set " << params[0] << " is incomplete, fetching descendents" << LL_ENDL; + gInventory.fetchDescendentsOf(currentCategory->getUUID()); + } + } + } + + if (allComplete) + { + mEnabled = gSavedPerAccountSettings.getBOOL("AlchemyAOEnable"); + + if (!mCurrentSet && !mSets.empty()) + { + LL_DEBUGS("AOEngine") << "No default set defined, choosing the first in the list." << LL_ENDL; + selectSet(mSets[0]); + } + + mTimerCollection.enableInventoryTimer(false); + mTimerCollection.enableSettingsTimer(true); + + LL_INFOS("AOEngine") << "sending update signal" << LL_ENDL; + mUpdatedSignal(); + enable(mEnabled); + } +} + +void AOEngine::reload(bool aFromTimer) +{ + bool wasEnabled = mEnabled; + + mTimerCollection.enableReloadTimer(false); + + if (wasEnabled) + { + enable(false); + } + + gAgent.stopCurrentAnimations(); + mLastOverriddenMotion = ANIM_AGENT_STAND; + + clear(aFromTimer); + mAOFolder.setNull(); + mTimerCollection.enableInventoryTimer(true); + tick(); + + if (wasEnabled) + { + enable(true); + } +} + +AOSet* AOEngine::getSetByName(const std::string& name) const +{ + AOSet* found = nullptr; + for (auto set : mSets) + { + if (set->getName().compare(name) == 0) + { + found = set; + break; + } + } + return found; +} + +const std::string AOEngine::getCurrentSetName() const +{ + if (mCurrentSet) + { + return mCurrentSet->getName(); + } + return std::string(); +} + +const AOSet* AOEngine::getDefaultSet() const +{ + return mDefaultSet; +} + +void AOEngine::selectSet(AOSet* set) +{ + if (mEnabled && mCurrentSet) + { + AOSet::AOState* state = mCurrentSet->getStateByRemapID(mLastOverriddenMotion); + if (state) + { + gAgent.sendAnimationRequest(state->mCurrentAnimationID, ANIM_REQUEST_STOP); + state->mCurrentAnimationID.setNull(); + mCurrentSet->stopTimer(); + } + } + + mCurrentSet = set; + mSetChangedSignal(mCurrentSet->getName()); + + if (mEnabled) + { + LL_DEBUGS("AOEngine") << "enabling with motion " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; + gAgent.sendAnimationRequest(override(mLastMotion, true), ANIM_REQUEST_START); + } +} + +AOSet* AOEngine::selectSetByName(const std::string& name) +{ + AOSet* set = getSetByName(name); + if (set) + { + selectSet(set); + return set; + } + LL_WARNS("AOEngine") << "Could not find AO set " << name << LL_ENDL; + return nullptr; +} + +const std::vector<AOSet*> AOEngine::getSetList() const +{ + return mSets; +} + +void AOEngine::saveSet(const AOSet* set) +{ + if (!set) + { + return; + } + + std::string setParams=set->getName(); + if (set->getSitOverride()) + { + setParams += ":SO"; + } + if (set->getSmart()) + { + setParams += ":SM"; + } + if (set->getMouselookStandDisable()) + { + setParams += ":DM"; + } + if (set == mDefaultSet) + { + setParams += ":**"; + } + +/* + // This works fine, but LL seems to have added a few helper functions in llinventoryfunctions.h + // so let's make use of them. This code is just for reference + + LLViewerInventoryCategory* cat=gInventory.getCategory(set->getInventoryUUID()); + LL_WARNS("AOEngine") << cat << LL_ENDL; + cat->rename(setParams); + cat->updateServer(FALSE); + gInventory.addChangedMask(LLInventoryObserver::LABEL, cat->getUUID()); + gInventory.notifyObservers(); +*/ + BOOL wasProtected = gSavedPerAccountSettings.getBOOL("LockAOFolders"); + gSavedPerAccountSettings.setBOOL("LockAOFolders", FALSE); + rename_category(&gInventory, set->getInventoryUUID(), setParams); + gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); + + LL_INFOS("AOEngine") << "sending update signal" << LL_ENDL; + mUpdatedSignal(); +} + +bool AOEngine::renameSet(AOSet* set, const std::string& name) +{ + if (name.empty() || name.find(":") != std::string::npos) + { + return false; + } + set->setName(name); + set->setDirty(true); + + return true; +} + +void AOEngine::saveState(const AOSet::AOState* state) +{ + std::string stateParams = state->mName; + F32 time = state->mCycleTime; + if (time > 0.0f) + { + std::ostringstream timeStr; + timeStr << ":CT" << state->mCycleTime; + stateParams += timeStr.str(); + } + if (state->mCycle) + { + stateParams += ":CY"; + } + if (state->mRandom) + { + stateParams += ":RN"; + } + + BOOL wasProtected = gSavedPerAccountSettings.getBOOL("LockAOFolders"); + gSavedPerAccountSettings.setBOOL("LockAOFolders", FALSE); + rename_category(&gInventory, state->mInventoryUUID, stateParams); + gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); +} + +void AOEngine::saveSettings() +{ + for (auto set : mSets) + { + if (set->getDirty()) + { + saveSet(set); + LL_INFOS("AOEngine") << "dirty set saved " << set->getName() << LL_ENDL; + set->setDirty(false); + } + + for (S32 stateIndex = 0; stateIndex < AOSet::AOSTATES_MAX; ++stateIndex) + { + AOSet::AOState* state = set->getState(stateIndex); + if (state->mDirty) + { + saveState(state); + LL_INFOS("AOEngine") << "dirty state saved " << state->mName << LL_ENDL; + state->mDirty = false; + } + } + } +} + +void AOEngine::inMouselook(bool mouselook) +{ + if (mInMouselook == mouselook) + { + return; + } + + mInMouselook = mouselook; + + if (!mCurrentSet) + { + return; + } + + if (!mCurrentSet->getMouselookStandDisable()) + { + return; + } + + if (!mEnabled) + { + return; + } + + if (mLastMotion != ANIM_AGENT_STAND) + { + return; + } + + if (mouselook) + { + AOSet::AOState* state = mCurrentSet->getState(AOSet::Standing); + if (!state) + { + return; + } + + LLUUID animation = state->mCurrentAnimationID; + if (animation.notNull()) + { + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); + gAgentAvatarp->LLCharacter::stopMotion(animation); + state->mCurrentAnimationID.setNull(); + LL_DEBUGS("AOEngine") << " stopped animation " << animation << " in state " << state->mName << LL_ENDL; + } + gAgent.sendAnimationRequest(ANIM_AGENT_STAND, ANIM_REQUEST_START); + } + else + { + stopAllStandVariants(); + gAgent.sendAnimationRequest(override(ANIM_AGENT_STAND, true), ANIM_REQUEST_START); + } +} + +void AOEngine::setDefaultSet(AOSet* set) +{ + mDefaultSet = set; + for (auto set : mSets) + { + set->setDirty(true); + } +} + +void AOEngine::setOverrideSits(AOSet* set, bool override_sit) +{ + set->setSitOverride(override_sit); + set->setDirty(true); + + if (mCurrentSet != set) + { + return; + } + + if (mLastMotion != ANIM_AGENT_SIT) + { + return; + } + + if (!mEnabled) + { + return; + } + + if (override_sit) + { + stopAllSitVariants(); + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_START); + } + else + { + // remove sit cycle cover up + gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC, ANIM_REQUEST_STOP); + + AOSet::AOState* state = mCurrentSet->getState(AOSet::Sitting); + if (state) + { + LLUUID animation = state->mCurrentAnimationID; + if (animation.notNull()) + { + gAgent.sendAnimationRequest(animation, ANIM_REQUEST_STOP); + state->mCurrentAnimationID.setNull(); + } + } + + if (!foreignAnimations()) + { + gAgent.sendAnimationRequest(ANIM_AGENT_SIT, ANIM_REQUEST_START); + } + } +} + +void AOEngine::setSmart(AOSet* set, bool smart) +{ + set->setSmart(smart); + set->setDirty(true); + + if (!mEnabled) + { + return; + } + + if (smart) + { + // make sure to restart the sit cancel timer to fix sit overrides when the object we are + // sitting on is playing its own animation + const LLViewerObject* agentRoot = dynamic_cast<LLViewerObject*>(gAgentAvatarp->getRoot()); + if (agentRoot && agentRoot->getID() != gAgentID) + { + mSitCancelTimer.oneShot(); + } + } +} + +void AOEngine::setDisableMouselookStands(AOSet* set, bool disabled) +{ + set->setMouselookStandDisable(disabled); + set->setDirty(true); + + if (mCurrentSet != set) + { + return; + } + + if (!mEnabled) + { + return; + } + + // make sure an update happens if needed + mInMouselook = !gAgentCamera.cameraMouselook(); + inMouselook(!mInMouselook); +} + +void AOEngine::setCycle(AOSet::AOState* state, bool cycle) +{ + state->mCycle = cycle; + state->mDirty = true; +} + +void AOEngine::setRandomize(AOSet::AOState* state, bool randomize) +{ + state->mRandom = randomize; + state->mDirty = true; +} + +void AOEngine::setCycleTime(AOSet::AOState* state, F32 time) +{ + state->mCycleTime = time; + state->mDirty = true; +} + +void AOEngine::tick() +{ + // <FS:ND> make sure agent is alive and kicking before doing anything + if (!isAgentAvatarValid()) + { + return; + } + // </FS:ND> + + const LLUUID categoryID = gInventory.findCategoryUUIDForNameInRoot(ROOT_AO_FOLDER, gInventory.getRootFolderID()); + + if (categoryID.isNull()) + { + LL_WARNS("AOEngine") << "no " << ROOT_AO_FOLDER << " folder yet. Creating ..." << LL_ENDL; + gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, ROOT_AO_FOLDER); + mAOFolder.setNull(); + } + else + { + mAOFolder = categoryID; + update(); + } +} + +bool AOEngine::importNotecard(const LLInventoryItem* item) +{ + if (item) + { + LL_INFOS("AOEngine") << "importing AO notecard: " << item->getName() << LL_ENDL; + if (getSetByName(item->getName())) + { + LLNotificationsUtil::add("AOImportSetAlreadyExists", LLSD()); + return false; + } + + if (!gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE) && !gAgent.isGodlike()) + { + LLNotificationsUtil::add("AOImportPermissionDenied", LLSD()); + return false; + } + + if (item->getAssetUUID().notNull()) + { + // create the new set with the folder UUID where the notecard is in, so we can reference it + // in the notecard reader, this will later be cleared to make room for the real #AO subfolder + mImportSet = new AOSet(item->getParentUUID()); + mImportSet->setName(item->getName()); + + LLUUID* newUUID = new LLUUID(item->getAssetUUID()); + const LLHost sourceSim; + + gAssetStorage->getInvItemAsset( + sourceSim, + gAgentID, + gAgentSessionID, + item->getPermissions().getOwner(), + LLUUID::null, + item->getUUID(), + item->getAssetUUID(), + item->getType(), + &onNotecardLoadComplete, + (void*) newUUID, + TRUE + ); + + return true; + } + } + return false; +} + +// static +void AOEngine::onNotecardLoadComplete(const LLUUID& assetUUID, LLAssetType::EType type, + void* userdata, S32 status, LLExtStat extStatus) +{ + if (status != LL_ERR_NOERR) + { + // AOImportDownloadFailed + LLNotificationsUtil::add("AOImportDownloadFailed", LLSD()); + // NULL tells the importer to cancel all operations and free the import set memory + AOEngine::instance().parseNotecard(nullptr); + return; + } + LL_DEBUGS("AOEngine") << "Downloading import notecard complete." << LL_ENDL; + + LLFileSystem file(assetUUID, type, LLFileSystem::READ); + + S32 notecardSize = file.getSize(); + char* buffer = new char[notecardSize + 1]; + buffer[notecardSize] = 0; + + BOOL ret = file.read((U8*)buffer, notecardSize); + if (ret) + { + AOEngine::instance().parseNotecard(buffer); + } + else + { + delete[] buffer; + } +} + +void AOEngine::parseNotecard(const char* buffer) +{ + LL_DEBUGS("AOEngine") << "parsing import notecard" << LL_ENDL; + + bool isValid = false; + + if (!buffer) + { + LL_WARNS("AOEngine") << "buffer==NULL - aborting import" << LL_ENDL; + // NOTE: cleanup is always the same, needs streamlining + delete mImportSet; + mImportSet = nullptr; + mUpdatedSignal(); + return; + } + + std::string text(buffer); + delete[] buffer; + + std::vector<std::string> lines; + LLStringUtil::getTokens(text, lines, "\n"); + + auto it = std::find_if(lines.begin(), lines.end(), [](const std::string& line) { + return line.find("Text length ") == 0; + }); + + if (it == lines.end()) { + // Line not found + LLNotificationsUtil::add("AOImportNoText", LLSD()); + delete mImportSet; + mImportSet = nullptr; + mUpdatedSignal(); + return; + } + + // Line found, 'it' points to the found element + std::size_t found = std::distance(lines.begin(), it) + 1; + + // mImportSet->getInventoryUUID() right now contains the folder UUID where the notecard is in + LLViewerInventoryCategory* importCategory = gInventory.getCategory(mImportSet->getInventoryUUID()); + if (!importCategory) + { + LLNotificationsUtil::add("AOImportNoFolder", LLSD()); + delete mImportSet; + mImportSet = nullptr; + mUpdatedSignal(); + return; + } + + std::map<std::string, LLUUID> animationMap; + LLInventoryModel::cat_array_t* dummy; + LLInventoryModel::item_array_t* items; + + gInventory.getDirectDescendentsOf(mImportSet->getInventoryUUID(), dummy, items); + for (auto& item : *items) + { + animationMap[item->getName()] = item->getUUID(); + LL_DEBUGS("AOEngine") << "animation " << item->getName() << " has inventory UUID " << animationMap[item->getName()] << LL_ENDL; + } + + // [ State ]Anim1|Anim2|Anim3 + for (auto index = found; index < lines.size(); ++index) + { + std::string line = lines[index]; + + // cut off the trailing } of a notecard asset's text portion in the last line + if (index == lines.size() - 1) + { + line = line.substr(0, line.size() - 1); + } + + LLStringUtil::trim(line); + + if (line.empty()) + { + continue; + } + + if (line[0] == '#') // <ND/> FIRE-3801; skip comments to reduce spam to local chat. + { + continue; + } + + if (line.find("[") != 0) + { + LLSD args; + args["LINE"] = (S32)index; + LLNotificationsUtil::add("AOImportNoStatePrefix", args); + continue; + } + + if (line.find("]") == std::string::npos) + { + LLSD args; + args["LINE"] = (S32)index; + LLNotificationsUtil::add("AOImportNoValidDelimiter", args); + continue; + } + auto endTag = line.find("]"); + + std::string stateName = line.substr(1, endTag - 1); + LLStringUtil::trim(stateName); + + AOSet::AOState* newState = mImportSet->getStateByName(stateName); + if (!newState) + { + LLSD args; + args["NAME"] = stateName; + LLNotificationsUtil::add("AOImportStateNameNotFound", args); + continue; + } + + std::string animationLine = line.substr(endTag + 1); + std::vector<std::string> animationList; + LLStringUtil::getTokens(animationLine, animationList, "|,"); + + for (auto animIndex = 0; animIndex < animationList.size(); ++animIndex) + { + AOSet::AOAnimation animation; + animation.mName = animationList[animIndex]; + animation.mInventoryUUID = animationMap[animation.mName]; + if (animation.mInventoryUUID.isNull()) + { + LLSD args; + args["NAME"] = animation.mName; + LLNotificationsUtil::add("AOImportAnimationNotFound", args); + continue; + } + animation.mSortOrder = animIndex; + newState->mAnimations.push_back(animation); + isValid = true; + } + } + + if (!isValid) + { + LLNotificationsUtil::add("AOImportInvalid", LLSD()); + // NOTE: cleanup is always the same, needs streamlining + delete mImportSet; + mImportSet = nullptr; + mUpdatedSignal(); + return; + } + + // clear out set UUID so processImport() knows we need to create a new folder for it + mImportSet->setInventoryUUID(LLUUID::null); + mTimerCollection.enableImportTimer(true); + mImportRetryCount = 0; + processImport(false); +} + +void AOEngine::processImport(bool from_timer) +{ + if (mImportSet->getInventoryUUID().isNull()) + { + // create new inventory folder for this AO set, the next timer tick should pick it up + addSet(mImportSet->getName(), [this](const LLUUID& new_cat_id) + { + mImportSet->setInventoryUUID(new_cat_id); + }, false); + + mImportRetryCount++; + + // if it takes this long to create a new inventoey category, there might be something wrong, + // so give some user feedback at first + if (mImportRetryCount == 2) + { + LLSD args; + args["NAME"] = mImportSet->getName(); + LLNotificationsUtil::add("AOImportRetryCreateSet", args); + return; + } + // by now there clearly is something wrong, so stop trying + else if (mImportRetryCount == 5) + { + // NOTE: cleanup is the same as at the end of this function. Needs streamlining. + mTimerCollection.enableImportTimer(false); + delete mImportSet; + mImportSet = nullptr; + mUpdatedSignal(); + LLSD args; + args["NAME"] = mImportSet->getName(); + LLNotificationsUtil::add("AOImportAbortCreateSet", args); + } + + return; + } + + bool allComplete = true; + for (S32 index = 0; index < AOSet::AOSTATES_MAX; ++index) + { + AOSet::AOState* state = mImportSet->getState(index); + if (!state->mAnimations.empty()) + { + allComplete = false; + LL_DEBUGS("AOEngine") << "state " << state->mName << " still has animations to link." << LL_ENDL; + + gInventory.createNewCategory(mImportSet->getInventoryUUID(), LLFolderType::FT_NONE, state->mName, [this, state](const LLUUID& new_cat_id) + { + LL_DEBUGS("AOEngine") << "new_cat_id: " << new_cat_id << LL_ENDL; + state->mInventoryUUID = new_cat_id; + + S32 animationIndex = state->mAnimations.size() - 1; + while (!state->mAnimations.empty()) + { + LL_DEBUGS("AOEngine") << "linking animation " << state->mAnimations[animationIndex].mName << LL_ENDL; + if (createAnimationLink(state, gInventory.getItem(state->mAnimations[animationIndex].mInventoryUUID))) + { + LL_DEBUGS("AOEngine") << "link success, size " << state->mAnimations.size() << ", removing animation " + << state->mAnimations[animationIndex].mName << " from import state" << LL_ENDL; + state->mAnimations.pop_back(); + LL_DEBUGS("AOEngine") << "deleted, size now: " << state->mAnimations.size() << LL_ENDL; + } + else + { + LLSD args; + args["NAME"] = state->mAnimations[animationIndex].mName; + LLNotificationsUtil::add("AOImportLinkFailed", args); + } + animationIndex--; + } + + LL_DEBUGS("AOEngine") << "exiting lambda" << LL_ENDL; + }); + } + } + + if (allComplete) + { + mTimerCollection.enableImportTimer(false); + mOldImportSets.push_back(mImportSet); //<ND/> FIRE-3801; Cannot delete here, or LLInstanceTracker gets upset. Just remember and delete mOldImportSets once we can. + mImportSet = nullptr; + reload(from_timer); + LLNotificationsUtil::add("AOImportComplete"); + } +} + +const LLUUID& AOEngine::getAOFolder() const +{ + return mAOFolder; +} + +void AOEngine::onRegionChange() +{ + // do nothing if the AO is off + if (!mEnabled) + { + return; + } + + // catch errors without crashing + if (!mCurrentSet) + { + LL_DEBUGS("AOEngine") << "Current set was NULL" << LL_ENDL; + return; + } + + // sitting needs special attention + if (mLastMotion == ANIM_AGENT_SIT) + { + // do nothing if sit overrides was disabled + if (!mCurrentSet->getSitOverride()) + { + return; + } + + // do nothing if the last overridden motion wasn't a sit. + // happens when sit override is enabled but there were no + // sit animations added to the set yet + if (mLastOverriddenMotion != ANIM_AGENT_SIT) + { + return; + } + + AOSet::AOState* state = mCurrentSet->getState(AOSet::Sitting); + if (!state) + { + return; + } + + // do nothing if no AO animation is playing (e.g. smart sit cancel) + if (LLUUID animation = state->mCurrentAnimationID; animation.isNull()) + { + return; + } + } + + // restart current animation on region crossing + gAgent.sendAnimationRequest(mLastMotion, ANIM_REQUEST_START); +} + +// ---------------------------------------------------- + +AOSitCancelTimer::AOSitCancelTimer() +: LLEventTimer(0.1f), + mTickCount(0) +{ + mEventTimer.stop(); +} + +AOSitCancelTimer::~AOSitCancelTimer() +{ +} + +void AOSitCancelTimer::oneShot() +{ + mTickCount = 0; + mEventTimer.start(); +} + +void AOSitCancelTimer::stop() +{ + mEventTimer.stop(); +} + +BOOL AOSitCancelTimer::tick() +{ + mTickCount++; + AOEngine::instance().checkSitCancel(); + if (mTickCount == 10) + { + mEventTimer.stop(); + } + return FALSE; +} + +// ---------------------------------------------------- + +AOTimerCollection::AOTimerCollection() +: LLEventTimer(INVENTORY_POLLING_INTERVAL), + mInventoryTimer(true), + mSettingsTimer(false), + mReloadTimer(false), + mImportTimer(false) +{ + updateTimers(); +} + +AOTimerCollection::~AOTimerCollection() +{ +} + +BOOL AOTimerCollection::tick() +{ + if (mInventoryTimer) + { + LL_DEBUGS("AOEngine") << "Inventory timer tick()" << LL_ENDL; + AOEngine::instance().tick(); + } + if (mSettingsTimer) + { + LL_DEBUGS("AOEngine") << "Settings timer tick()" << LL_ENDL; + AOEngine::instance().saveSettings(); + } + if (mReloadTimer) + { + LL_DEBUGS("AOEngine") << "Reload timer tick()" << LL_ENDL; + AOEngine::instance().reload(true); + } + if (mImportTimer) + { + LL_DEBUGS("AOEngine") << "Import timer tick()" << LL_ENDL; + AOEngine::instance().processImport(true); + } + + // always return FALSE or the LLEventTimer will be deleted -> crash + return FALSE; +} + +void AOTimerCollection::enableInventoryTimer(bool enable) +{ + mInventoryTimer = enable; + updateTimers(); +} + +void AOTimerCollection::enableSettingsTimer(bool enable) +{ + mSettingsTimer = enable; + updateTimers(); +} + +void AOTimerCollection::enableReloadTimer(bool enable) +{ + mReloadTimer = enable; + updateTimers(); +} + +void AOTimerCollection::enableImportTimer(bool enable) +{ + mImportTimer = enable; + updateTimers(); +} + +void AOTimerCollection::updateTimers() +{ + if (!mInventoryTimer && !mSettingsTimer && !mReloadTimer && !mImportTimer) + { + LL_DEBUGS("AOEngine") << "no timer needed, stopping internal timer." << LL_ENDL; + mEventTimer.stop(); + } + else + { + LL_DEBUGS("AOEngine") << "timer needed, starting internal timer." << LL_ENDL; + mEventTimer.start(); + } +} diff --git a/indra/newview/aoengine.h b/indra/newview/aoengine.h new file mode 100644 index 00000000000..aa4cbf7e43a --- /dev/null +++ b/indra/newview/aoengine.h @@ -0,0 +1,234 @@ +/** + * @file aoengine.h + * @brief The core Animation Overrider engine + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Zi Ree @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AOENGINE_H +#define AOENGINE_H + +#define ROOT_AO_FOLDER "Animation Overrides" + +#include "aoset.h" + +#include "llassettype.h" +#include "lleventtimer.h" +#include "llextendedstatus.h" +#include "llsingleton.h" +#include "llviewerinventory.h" +#include <boost/signals2.hpp> + +class AOTimerCollection +: public LLEventTimer +{ + public: + AOTimerCollection(); + ~AOTimerCollection(); + + virtual BOOL tick(); + + void enableInventoryTimer(bool enable); + void enableSettingsTimer(bool enable); + void enableReloadTimer(bool enable); + void enableImportTimer(bool enable); + + protected: + void updateTimers(); + + bool mInventoryTimer; + bool mSettingsTimer; + bool mReloadTimer; + bool mImportTimer; +}; + +// ---------------------------------------------------- + +class AOSitCancelTimer +: public LLEventTimer +{ + public: + AOSitCancelTimer(); + ~AOSitCancelTimer(); + + void oneShot(); + void stop(); + + virtual BOOL tick(); + + protected: + S32 mTickCount; +}; + +// ---------------------------------------------------- + +class AOState; +class LLInventoryItem; +class LLVFS; + +class AOEngine +: public LLSingleton<AOEngine> +{ + LLSINGLETON(AOEngine); + ~AOEngine(); + + public: + enum eCycleMode + { + CycleAny, + CycleNext, + CyclePrevious + }; + + void enable(bool enable); + void enableStands(bool enable_stands); + const LLUUID override(const LLUUID& motion, bool start); + void tick(); + void update(); + void reload(bool); + void reloadStateAnimations(AOSet::AOState* state); + void clear(bool from_timer); + + const LLUUID& getAOFolder() const; + + void addSet(const std::string& name, inventory_func_type callback, bool reload = true); + bool removeSet(AOSet* set); + + void addAnimation(const AOSet* set, AOSet::AOState* state, const LLInventoryItem* item, bool reload = true); + bool removeAnimation(const AOSet* set, AOSet::AOState* state, S32 index); + void checkSitCancel(); + void checkBelowWater(bool check_underwater); + + bool importNotecard(const LLInventoryItem* item); + void processImport(bool from_timer); + + bool swapWithPrevious(AOSet::AOState* state, S32 index); + bool swapWithNext(AOSet::AOState* state, S32 index); + + void cycleTimeout(const AOSet* set); + void cycle(eCycleMode cycleMode); + + void inMouselook(bool mouselook); + void selectSet(AOSet* set); + AOSet* selectSetByName(const std::string& name); + AOSet* getSetByName(const std::string& name) const; + + // callback from LLAppViewer + static void onLoginComplete(); + + const std::vector<AOSet*> getSetList() const; + const std::string getCurrentSetName() const; + const AOSet* getDefaultSet() const; + bool renameSet(AOSet* set, const std::string& name); + + void setDefaultSet(AOSet* set); + void setOverrideSits(AOSet* set, bool override_sit); + void setSmart(AOSet* set, bool smart); + void setDisableMouselookStands(AOSet* set, bool disabled); + void setCycle(AOSet::AOState* set, bool cycle); + void setRandomize(AOSet::AOState* state, bool randomize); + void setCycleTime(AOSet::AOState* state, F32 time); + + void saveSettings(); + + typedef boost::signals2::signal<void ()> updated_signal_t; + boost::signals2::connection setReloadCallback(const updated_signal_t::slot_type& cb) + { + return mUpdatedSignal.connect(cb); + }; + + typedef boost::signals2::signal<void (const LLUUID&)> animation_changed_signal_t; + boost::signals2::connection setAnimationChangedCallback(const animation_changed_signal_t::slot_type& cb) + { + return mAnimationChangedSignal.connect(cb); + }; + + typedef boost::signals2::signal<void(const std::string&)> set_changed_signal_t; + boost::signals2::connection setSetChangedCallback(const set_changed_signal_t::slot_type& cb) + { + return mSetChangedSignal.connect(cb); + } + + protected: + void init(); + + void setLastMotion(const LLUUID& motion); + void setLastOverriddenMotion(const LLUUID& motion); + void setStateCycleTimer(const AOSet::AOState* state); + + void stopAllStandVariants(); + void stopAllSitVariants(); + + bool foreignAnimations(); + AOSet::AOState* mapSwimming(const LLUUID& motion) const; + AOSet::AOState* getStateForMotion(const LLUUID& motion) const; + + void updateSortOrder(AOSet::AOState* state); + void saveSet(const AOSet* set); + void saveState(const AOSet::AOState* state); + + bool createAnimationLink(AOSet::AOState* state, const LLInventoryItem* item); + bool findForeignItems(const LLUUID& uuid) const; + void purgeFolder(const LLUUID& uuid) const; + + void onRegionChange(); + + void onToggleAOControl(); + void onToggleAOStandsControl(); + void onPauseAO(); + + static void onNotecardLoadComplete(const LLUUID& assetUUID, LLAssetType::EType type, + void* userdata, S32 status, LLExtStat extStatus); + void parseNotecard(const char* buffer); + + updated_signal_t mUpdatedSignal; + animation_changed_signal_t mAnimationChangedSignal; + set_changed_signal_t mSetChangedSignal; + + AOTimerCollection mTimerCollection; + AOSitCancelTimer mSitCancelTimer; + + bool mEnabled; + bool mEnabledStands; + bool mInMouselook; + bool mUnderWater; + + LLUUID mAOFolder; + LLUUID mLastMotion; + LLUUID mLastOverriddenMotion; + LLUUID mTransitionId; + + // this motion will be ignored once in the overrider when stopping, fixes a case + // where the AO doesn't correctly start up on login or when getting enabled manually + LLUUID mIgnoreMotionStopOnce; + + std::vector<AOSet*> mSets; + std::vector<AOSet*> mOldSets; + AOSet* mCurrentSet; + AOSet* mDefaultSet; + + AOSet* mImportSet; + std::vector<AOSet*> mOldImportSets; + S32 mImportRetryCount; + + boost::signals2::connection mRegionChangeConnection; +}; + +#endif // AOENGINE_H diff --git a/indra/newview/alaoset.cpp b/indra/newview/aoset.cpp similarity index 52% rename from indra/newview/alaoset.cpp rename to indra/newview/aoset.cpp index c3598b3461f..52ab2a07de6 100644 --- a/indra/newview/alaoset.cpp +++ b/indra/newview/aoset.cpp @@ -1,12 +1,10 @@ /** - * @file alaoset.cpp + * @file aoset.cpp * @brief Implementation of an Animation Overrider set of animations * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2011, Zi Ree @ Second Life - * Copyright (C) 2016, Cinder <cinder@sdf.org> - * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,29 +24,29 @@ #include "llviewerprecompiledheaders.h" -#include "alaoengine.h" -#include "alaoset.h" +#include "aoengine.h" +#include "aoset.h" #include "llanimationstates.h" +#include "llinventorymodel.h" -static const std::string sDefaultSetName = "** New AO Set **"; - -ALAOSet::ALAOSet(const LLUUID& inventoryID) -: LLEventTimer(10000.0f) -, mInventoryID(inventoryID) -, mName(sDefaultSetName) -, mSitOverride(false) -, mSmart(false) -, mMouselookDisable(false) -, mComplete(false) -, mCurrentMotion(LLUUID()) -, mDirty(false) +AOSet::AOSet(const LLUUID inventoryID) +: LLEventTimer(10000.0f), + mInventoryID(inventoryID), + mName("** New AO Set **"), + mSitOverride(false), + mSmart(false), + mMouselookStandDisable(false), + mComplete(false), + mDirty(false), + mCurrentMotion(LLUUID()) { LL_DEBUGS("AOEngine") << "Creating new AO set: " << this << LL_ENDL; // ZHAO names first, alternate names following, separated by | characters // keep number and order in sync with the enum in the declaration - static const std::array<std::string, AOSTATES_MAX> sStateNames {{ - "Standing|Standing mode 1|Stand.1|Stand.2|Stand.3", + const std::string stateNames[AOSTATES_MAX]= + { + "Standing|Stand.1|Stand.2|Stand.3", "Walking|Walk.N", "Running", "Sitting|Sit.N", @@ -73,10 +71,11 @@ ALAOSet::ALAOSet(const LLUUID& inventoryID) "Swimming Forward|Swim.N", "Swimming Up|Swim.U", "Swimming Down|Swim.D" - }}; + }; // keep number and order in sync with the enum in the declaration - static const std::array<LLUUID, AOSTATES_MAX> sStateUUIDs {{ + const LLUUID stateUUIDs[AOSTATES_MAX]= + { ANIM_AGENT_STAND, ANIM_AGENT_WALK, ANIM_AGENT_RUN, @@ -102,16 +101,16 @@ ALAOSet::ALAOSet(const LLUUID& inventoryID) ANIM_AGENT_FLY, // needs special treatment ANIM_AGENT_HOVER_UP, // needs special treatment ANIM_AGENT_HOVER_DOWN // needs special treatment - }}; + }; for (S32 index = 0; index < AOSTATES_MAX; ++index) { std::vector<std::string> stateNameList; - LLStringUtil::getTokens(sStateNames[index], stateNameList, "|"); + LLStringUtil::getTokens(stateNames[index], stateNameList, "|"); mStates[index].mName = stateNameList[0]; // for quick reference mStates[index].mAlternateNames = stateNameList; // to get all possible names, including mName - mStates[index].mRemapID = sStateUUIDs[index]; + mStates[index].mRemapID = stateUUIDs[index]; mStates[index].mInventoryUUID = LLUUID::null; mStates[index].mCurrentAnimation = 0; mStates[index].mCurrentAnimationID = LLUUID::null; @@ -119,27 +118,27 @@ ALAOSet::ALAOSet(const LLUUID& inventoryID) mStates[index].mRandom = false; mStates[index].mCycleTime = 0.0f; mStates[index].mDirty = false; - mStateNames.push_back(stateNameList[0]); + mStateNames.emplace_back(stateNameList[0]); } stopTimer(); } -ALAOSet::~ALAOSet() +AOSet::~AOSet() { LL_DEBUGS("AOEngine") << "Set deleted: " << this << LL_ENDL; } -ALAOSet::AOState* ALAOSet::getState(S32 name) +AOSet::AOState* AOSet::getState(S32 eName) { - return &mStates[name]; + return &mStates[eName]; } -ALAOSet::AOState* ALAOSet::getStateByName(const std::string& name) +AOSet::AOState* AOSet::getStateByName(const std::string& name) { for (S32 index = 0; index < AOSTATES_MAX; ++index) { AOState* state = &mStates[index]; - for (U32 names = 0; names < state->mAlternateNames.size(); ++names) + for (auto names = 0; names < state->mAlternateNames.size(); ++names) { if (state->mAlternateNames[names].compare(name) == 0) { @@ -150,7 +149,7 @@ ALAOSet::AOState* ALAOSet::getStateByName(const std::string& name) return nullptr; } -ALAOSet::AOState* ALAOSet::getStateByRemapID(const LLUUID& id) +AOSet::AOState* AOSet::getStateByRemapID(const LLUUID& id) { LLUUID remap_id = id; if (remap_id == ANIM_AGENT_SIT_GROUND) @@ -168,40 +167,59 @@ ALAOSet::AOState* ALAOSet::getStateByRemapID(const LLUUID& id) return nullptr; } -const LLUUID& ALAOSet::getAnimationForState(AOState* state) const +const LLUUID& AOSet::getAnimationForState(AOState* state) const { - if (!state) return LLUUID::null; - - size_t num_animations = state->mAnimations.size(); - if (num_animations) + if (state) { - if (state->mCycle) + auto numOfAnimations = state->mAnimations.size(); + if (numOfAnimations) { - if (state->mRandom) + if (state->mCycle) { - state->mCurrentAnimation = ll_frand() * num_animations; - LL_DEBUGS("AOEngine") << "randomly chosen " << state->mCurrentAnimation << " of " << num_animations << LL_ENDL; + if (state->mRandom) + { + state->mCurrentAnimation = ll_frand() * numOfAnimations; + LL_DEBUGS("AOEngine") << "randomly chosen " << state->mCurrentAnimation << " of " << numOfAnimations << LL_ENDL; + } + else + { + state->mCurrentAnimation++; + if (state->mCurrentAnimation >= state->mAnimations.size()) + { + state->mCurrentAnimation = 0; + } + LL_DEBUGS("AOEngine") << "cycle " << state->mCurrentAnimation << " of " << numOfAnimations << LL_ENDL; + } } - else + + AOAnimation& anim = state->mAnimations[state->mCurrentAnimation]; + + if (anim.mAssetUUID.isNull()) { - state->mCurrentAnimation++; - if (state->mCurrentAnimation >= state->mAnimations.size()) + 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) + { + LL_DEBUGS("AOEngine") << "Found asset UUID for chosen animation: " << item->getAssetUUID() << " - Updating AOAnimation.mAssetUUID" << LL_ENDL; + anim.mAssetUUID = item->getAssetUUID(); + } + else { - state->mCurrentAnimation = 0; + LL_DEBUGS("AOEngine") << "Inventory UUID " << anim.mInventoryUUID << " for chosen animation " << anim.mName << " still returns no asset." << LL_ENDL; } - LL_DEBUGS("AOEngine") << "cycle " << state->mCurrentAnimation << " of " << num_animations << LL_ENDL; } + + return anim.mAssetUUID; + } + else + { + LL_DEBUGS("AOEngine") << "animation state has no animations assigned" << LL_ENDL; } - return state->mAnimations[state->mCurrentAnimation].mAssetUUID; - } - else - { - LL_DEBUGS("AOEngine") << "animation state has no animations assigned" << LL_ENDL; } return LLUUID::null; } -void ALAOSet::startTimer(F32 timeout) +void AOSet::startTimer(F32 timeout) { mEventTimer.stop(); mPeriod = timeout; @@ -209,14 +227,94 @@ void ALAOSet::startTimer(F32 timeout) LL_DEBUGS("AOEngine") << "Starting state timer for " << getName() << " at " << timeout << LL_ENDL; } -void ALAOSet::stopTimer() +void AOSet::stopTimer() { LL_DEBUGS("AOEngine") << "State timer for " << getName() << " stopped." << LL_ENDL; mEventTimer.stop(); } -BOOL ALAOSet::tick() +BOOL AOSet::tick() { - ALAOEngine::instance().cycleTimeout(this); + AOEngine::instance().cycleTimeout(this); return FALSE; } + +const LLUUID& AOSet::getInventoryUUID() const +{ + return mInventoryID; +} + +void AOSet::setInventoryUUID(const LLUUID& inventoryID) +{ + mInventoryID = inventoryID; +} + +const std::string& AOSet::getName() const +{ + return mName; +} + +void AOSet::setName(const std::string& name) +{ + mName=name; +} + +bool AOSet::getSitOverride() const +{ + return mSitOverride; +} + +void AOSet::setSitOverride(bool override_sit) +{ + mSitOverride = override_sit; +} + +bool AOSet::getSmart() const +{ + return mSmart; +} + +void AOSet::setSmart(bool smart) +{ + mSmart = smart; +} + +bool AOSet::getMouselookStandDisable() const +{ + return mMouselookStandDisable; +} + +void AOSet::setMouselookStandDisable(bool disable) +{ + mMouselookStandDisable = disable; +} + +bool AOSet::getComplete() const +{ + return mComplete; +} + +void AOSet::setComplete(bool complete) +{ + mComplete = complete; +} + +bool AOSet::getDirty() const +{ + return mDirty; +} + +void AOSet::setDirty(bool dirty) +{ + mDirty = dirty; +} + +void AOSet::setMotion(const LLUUID& motion) +{ + mCurrentMotion = motion; +} + +const LLUUID& AOSet::getMotion() const +{ + return mCurrentMotion; +} diff --git a/indra/newview/aoset.h b/indra/newview/aoset.h new file mode 100644 index 00000000000..878b36cedd6 --- /dev/null +++ b/indra/newview/aoset.h @@ -0,0 +1,145 @@ +/** + * @file aoset.h + * @brief Implementation of an Animation Overrider set of animations + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Zi Ree @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef AOSET_H +#define AOSET_H + +#include "lleventtimer.h" + +class LLInventoryItem; + +class AOSet +: public LLEventTimer +{ + public: + AOSet(const LLUUID inventoryID); + ~AOSet(); + + // keep number and order in sync with list of names in the constructor + enum + { + Start = 0, // convenience, so we don't have to know the name of the first state + Standing = 0, + Walking, + Running, + Sitting, + SittingOnGround, + Crouching, + CrouchWalking, + Landing, + SoftLanding, + StandingUp, + Falling, + FlyingDown, + FlyingUp, + Flying, + FlyingSlow, + Hovering, + Jumping, + PreJumping, + TurningRight, + TurningLeft, + Typing, + Floating, + SwimmingForward, + SwimmingUp, + SwimmingDown, + AOSTATES_MAX + }; + + struct AOAnimation + { + std::string mName; + LLUUID mAssetUUID; + LLUUID mInventoryUUID; + LLUUID mOriginalUUID; + S32 mSortOrder; + }; + + struct AOState + { + std::string mName; + std::vector<std::string> mAlternateNames; + std::vector<const LLInventoryItem*> mAddQueue; + LLUUID mRemapID; + bool mCycle; + bool mRandom; + S32 mCycleTime; + std::vector<AOAnimation> mAnimations; + U32 mCurrentAnimation; + LLUUID mCurrentAnimationID; + LLUUID mInventoryUUID; + bool mDirty; + }; + + const LLUUID& getInventoryUUID() const; + void setInventoryUUID(const LLUUID& inventoryID); + + const std::string& getName() const; + void setName(const std::string& name); + + bool getSitOverride() const; + void setSitOverride(bool override_sit); + + bool getSmart() const; + void setSmart(bool smart); + + bool getMouselookStandDisable() const; + void setMouselookStandDisable(bool disable); + + bool getComplete() const; + void setComplete(bool complete); + + const LLUUID& getMotion() const; + void setMotion(const LLUUID& motion); + + bool getDirty() const; + void setDirty(bool dirty); + + AOState* getState(S32 eName); + AOState* getStateByName(const std::string& name); + AOState* getStateByRemapID(const LLUUID& id); + const LLUUID& getAnimationForState(AOState* state) const; + + void startTimer(F32 timeout); + void stopTimer(); + virtual BOOL tick(); + + std::vector<std::string> mStateNames; + + protected: + LLUUID mInventoryID; + + std::string mName; + bool mSitOverride; + bool mSmart; + bool mMouselookStandDisable; + bool mComplete; + LLUUID mCurrentMotion; + bool mDirty; + + AOState mStates[AOSTATES_MAX]; +}; + +#endif // AOSET_H diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index 7bea0e194c4..a8c71425a28 100644 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -12,6 +12,16 @@ is_running_function="Floater.IsOpen" is_running_parameters="about_land" /> + <command name="ao" + available_in_toybox="true" + icon="Command_Move_Icon" + label_ref="Command_AnimationOverride_Label" + tooltip_ref="Command_AnimationOverride_Tooltip" + execute_function="Floater.ToggleOrBringToFront" + execute_parameters="ao" + is_running_function="Floater.IsOpen" + is_running_parameters="ao" + /> <command name="appearance" available_in_toybox="true" icon="Command_Appearance_Icon" diff --git a/indra/newview/app_settings/settings_per_account_alchemy.xml b/indra/newview/app_settings/settings_per_account_alchemy.xml index b9c20d99871..6da3284d14c 100644 --- a/indra/newview/app_settings/settings_per_account_alchemy.xml +++ b/indra/newview/app_settings/settings_per_account_alchemy.xml @@ -1,26 +1,84 @@ <llsd> <map> - <key>AlchemyAOEnable</key> - <map> - <key>Comment</key> - <string>Use Animation Overrides</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> - <key>ALDiscordIntegration</key> - <map> - <key>Comment</key> - <string>Enable discord integration</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>1</integer> + <key>AlchemyAOEnable</key> + <map> + <key>Comment</key> + <string>Use the viewer side Animation Overrider</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>PauseAO</key> + <map> + <key>Comment</key> + <string>Pause the viewer side Animation Overrider</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>UseAOStands</key> + <map> + <key>Comment</key> + <string>Use the viewer side Animation Overrider for standing and turning animations when enabled</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>UseFullAOInterface</key> + <map> + <key>Comment</key> + <string>Use the full Animation Overrider interface (TRUE) or the small, reduced interface (FALSE).</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>LockAOFolders</key> + <map> + <key>Comment</key> + <string>Keep the AO folders in Inventory safe from manual changes.</string> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>floater_rect_ao_full</key> + <map> + <key>Comment</key> + <string>Position and size for the full Animation Overrider interface. The defaults are just dummy values.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Rect</string> + <key>Value</key> + <array> + <integer>0</integer> + <integer>0</integer> + <integer>0</integer> + <integer>0</integer> + </array> + </map> + <key>ALDiscordIntegration</key> + <map> + <key>Comment</key> + <string>Enable discord integration</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> </map> <key>ALDiscordShareLocationRegion</key> <map> diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index cfaf599cebd..a704d045ba7 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -29,7 +29,7 @@ #include "pipeline.h" -//#include "alaoengine.h" +#include "aoengine.h" #include "llagent.h" #include "llanimationstates.h" #include "llfloatercamera.h" @@ -2442,9 +2442,9 @@ void LLAgentCamera::changeCameraToMouselook(BOOL animate) updateLastCamera(); mCameraMode = CAMERA_MODE_MOUSELOOK; + AOEngine::getInstance()->inMouselook(TRUE); const U32 old_flags = gAgent.getControlFlags(); gAgent.setControlFlags(AGENT_CONTROL_MOUSELOOK); - //ALAOEngine::getInstance()->inMouselook(true); if (old_flags != gAgent.getControlFlags()) { gAgent.setFlagsDirty(); @@ -2509,7 +2509,7 @@ void LLAgentCamera::changeCameraToFollow(BOOL animate) updateLastCamera(); mCameraMode = CAMERA_MODE_FOLLOW; - //ALAOEngine::getInstance()->inMouselook(false); + AOEngine::getInstance()->inMouselook(FALSE); // bang-in the current focus, position, and up vector of the follow cam mFollowCam.reset(mCameraPositionAgent, LLViewerCamera::getInstance()->getPointOfInterest(), LLVector3::z_axis); @@ -2590,7 +2590,7 @@ void LLAgentCamera::changeCameraToThirdPerson(BOOL animate) } updateLastCamera(); mCameraMode = CAMERA_MODE_THIRD_PERSON; - //ALAOEngine::getInstance()->inMouselook(false); + AOEngine::getInstance()->inMouselook(FALSE); gAgent.clearControlFlags(AGENT_CONTROL_MOUSELOOK); } diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 51c8f8177e3..6cb51f79dda 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -32,6 +32,7 @@ #include "lltransfersourceasset.h" #include "llavatarnamecache.h" // IDEVO +#include "aoengine.h" #include "llagent.h" #include "llagentcamera.h" #include "llagentwearables.h" @@ -3964,9 +3965,9 @@ LLFolderType::EType LLFolderBridge::getPreferredType() const if(cat) { const std::string& cat_name(cat->getName()); -/* if (cat_name == ROOT_AO_FOLDER) + if (cat_name == ROOT_AO_FOLDER) preferred_type = LLFolderType::FT_ANIM_OVERRIDES; - else */ + else if (cat_name == "#Firestorm" || cat_name == "#Phoenix" || cat_name == "#Kokua") preferred_type = LLFolderType::FT_TOXIC; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index f990360bd30..c8ba04708c3 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -960,12 +960,11 @@ const LLUUID LLInventoryModel::findCategoryUUIDForNameInRoot(std::string const& cats = get_ptr_in_map(mParentChildCategoryTree, root_id); if (cats) { - U32 count = cats->size(); - for (U32 i = 0; i < count; ++i) + for (const auto& cat : *cats) { - if (cats->at(i)->getName() == folder_name) + if (cat->getName() == folder_name) { - LLUUID const& folder_id = cats->at(i)->getUUID(); + LLUUID const& folder_id = cat->getUUID(); if (rv.isNull() || folder_id < rv) { rv = folder_id; diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp index eeed08334d1..25fb8594ab9 100644 --- a/indra/newview/llstatusbar.cpp +++ b/indra/newview/llstatusbar.cpp @@ -29,7 +29,7 @@ #include "llstatusbar.h" // viewer includes -//#include "alpanelaopulldown.h" +#include "alpanelaopulldown.h" #include "alpanelquicksettingspulldown.h" #include "llagent.h" #include "llagentcamera.h" @@ -113,7 +113,7 @@ LLStatusBar::LLStatusBar(const LLRect& rect) mSGPacketLoss(NULL), mPanelPopupHolder(nullptr), mBtnQuickSettings(nullptr), - //mBtnAO(nullptr), + mBtnAO(nullptr), mBtnVolume(NULL), mBoxBalance(NULL), mBalance(0), @@ -193,10 +193,10 @@ BOOL LLStatusBar::postBuild() mBtnQuickSettings = getChild<LLButton>("quick_settings_btn"); mBtnQuickSettings->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterQuickSettings, this)); - //mBtnAO = getChild<LLButton>("ao_btn"); - //mBtnAO->setClickedCallback(&LLStatusBar::onClickAOBtn, this); - //mBtnAO->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterAO, this)); - //mBtnAO->setToggleState(gSavedPerAccountSettings.getBOOL("AlchemyAOEnable")); // shunt it into correct state - ALCH-368 + mBtnAO = getChild<LLButton>("ao_btn"); + mBtnAO->setClickedCallback(&LLStatusBar::onClickAOBtn, this); + mBtnAO->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterAO, this)); + mBtnAO->setToggleState(gSavedPerAccountSettings.getBOOL("AlchemyAOEnable")); // shunt it into correct state - ALCH-368 mBtnVolume = getChild<LLButton>( "volume_btn" ); mBtnVolume->setClickedCallback(&LLStatusBar::onClickVolume, this ); @@ -210,7 +210,7 @@ BOOL LLStatusBar::postBuild() LLHints::getInstance()->registerHintTarget("linden_balance", mBalanceBG->getHandle()); gSavedSettings.getControl("MuteAudio")->getSignal()->connect(boost::bind(&LLStatusBar::onVolumeChanged, this, _2)); - //gSavedPerAccountSettings.getControl("AlchemyAOEnable")->getCommitSignal()->connect(boost::bind(&LLStatusBar::onAOStateChanged, this)); + gSavedPerAccountSettings.getControl("AlchemyAOEnable")->getCommitSignal()->connect(boost::bind(&LLStatusBar::onAOStateChanged, this)); mTextFPS = getChild<LLTextBox>("FPSText"); @@ -277,10 +277,10 @@ BOOL LLStatusBar::postBuild() mPanelVolumePulldown->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); mPanelVolumePulldown->setVisible(FALSE); - //mPanelAOPulldown = new ALPanelAOPulldown(); - //addChild(mPanelAOPulldown); - //mPanelAOPulldown->setFollows(FOLLOWS_TOP | FOLLOWS_RIGHT); - //mPanelAOPulldown->setVisible(FALSE); + mPanelAOPulldown = new ALPanelAOPulldown(); + addChild(mPanelAOPulldown); + mPanelAOPulldown->setFollows(FOLLOWS_TOP | FOLLOWS_RIGHT); + mPanelAOPulldown->setVisible(FALSE); mPanelQuickSettingsPulldown = new ALPanelQuickSettingsPulldown(); addChild(mPanelQuickSettingsPulldown); @@ -402,7 +402,7 @@ void LLStatusBar::setVisibleForMouselook(bool visible) mBalanceBG->setVisible(visible); mBoxBalance->setVisible(visible); mBtnQuickSettings->setVisible(visible); - //mBtnAO->setVisible(visible); + mBtnAO->setVisible(visible); mBtnVolume->setVisible(visible); mMediaToggle->setVisible(visible); mSGBandwidth->setVisible(visible && show_net_stats); @@ -585,7 +585,7 @@ void LLStatusBar::onMouseEnterPresetsCamera() mPanelNearByMedia->setVisible(FALSE); mPanelVolumePulldown->setVisible(FALSE); mPanelPresetsPulldown->setVisible(FALSE); - //mPanelAOPulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(FALSE); // mPanelAvatarComplexityPulldown->setVisible(FALSE); mPanelQuickSettingsPulldown->setVisible(FALSE); mPanelPresetsCameraPulldown->setVisible(TRUE); @@ -612,7 +612,7 @@ void LLStatusBar::onMouseEnterPresets() mPanelPresetsCameraPulldown->setVisible(FALSE); mPanelNearByMedia->setVisible(FALSE); mPanelVolumePulldown->setVisible(FALSE); - //mPanelAOPulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(FALSE); // mPanelAvatarComplexityPulldown->setVisible(FALSE); mPanelQuickSettingsPulldown->setVisible(FALSE); mPanelPresetsPulldown->setVisible(TRUE); @@ -639,35 +639,35 @@ void LLStatusBar::onMouseEnterQuickSettings() mPanelPresetsPulldown->setVisible(FALSE); mPanelNearByMedia->setVisible(FALSE); mPanelVolumePulldown->setVisible(FALSE); - //mPanelAOPulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(FALSE); //mPanelAvatarComplexityPulldown->setVisible(FALSE); mPanelQuickSettingsPulldown->setVisible(TRUE); } -//void LLStatusBar::onMouseEnterAO() -//{ -// LLRect qs_rect = mPanelAOPulldown->getRect(); -// LLRect qs_btn_rect = mBtnAO->getRect(); -// qs_rect.setLeftTopAndSize(qs_btn_rect.mLeft - -// (qs_rect.getWidth() - qs_btn_rect.getWidth()) / 2, -// qs_btn_rect.mBottom, -// qs_rect.getWidth(), -// qs_rect.getHeight()); -// // force onscreen -// qs_rect.translate(mPanelPopupHolder->getRect().getWidth() - qs_rect.mRight, 0); -// -// mPanelAOPulldown->setShape(qs_rect); -// LLUI::getInstance()->clearPopups(); -// LLUI::getInstance()->addPopup(mPanelAOPulldown); -// -// mPanelPresetsCameraPulldown->setVisible(FALSE); -// mPanelPresetsPulldown->setVisible(FALSE); -// mPanelNearByMedia->setVisible(FALSE); -// mPanelVolumePulldown->setVisible(FALSE); -// mPanelQuickSettingsPulldown->setVisible(FALSE); -// mPanelAOPulldown->setVisible(TRUE); -// //mPanelAvatarComplexityPulldown->setVisible(FALSE); -//} +void LLStatusBar::onMouseEnterAO() +{ + LLRect qs_rect = mPanelAOPulldown->getRect(); + LLRect qs_btn_rect = mBtnAO->getRect(); + qs_rect.setLeftTopAndSize(qs_btn_rect.mLeft - + (qs_rect.getWidth() - qs_btn_rect.getWidth()) / 2, + qs_btn_rect.mBottom, + qs_rect.getWidth(), + qs_rect.getHeight()); + // force onscreen + qs_rect.translate(mPanelPopupHolder->getRect().getWidth() - qs_rect.mRight, 0); + + mPanelAOPulldown->setShape(qs_rect); + LLUI::getInstance()->clearPopups(); + LLUI::getInstance()->addPopup(mPanelAOPulldown); + + mPanelPresetsCameraPulldown->setVisible(FALSE); + mPanelPresetsPulldown->setVisible(FALSE); + mPanelNearByMedia->setVisible(FALSE); + mPanelVolumePulldown->setVisible(FALSE); + mPanelQuickSettingsPulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(TRUE); + //mPanelAvatarComplexityPulldown->setVisible(FALSE); +} void LLStatusBar::onMouseEnterVolume() { @@ -691,7 +691,7 @@ void LLStatusBar::onMouseEnterVolume() mPanelPresetsPulldown->setVisible(FALSE); mPanelNearByMedia->setVisible(FALSE); mPanelQuickSettingsPulldown->setVisible(FALSE); - //mPanelAOPulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(FALSE); mPanelVolumePulldown->setVisible(TRUE); } @@ -717,7 +717,7 @@ void LLStatusBar::onMouseEnterNearbyMedia() mPanelPresetsPulldown->setVisible(FALSE); mPanelQuickSettingsPulldown->setVisible(FALSE); mPanelVolumePulldown->setVisible(FALSE); - //mPanelAOPulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(FALSE); mPanelNearByMedia->setVisible(TRUE); } @@ -753,10 +753,10 @@ void LLStatusBar::onClickMediaToggle(void* data) LLViewerMedia::getInstance()->setAllMediaPaused(pause); } -//void LLStatusBar::onAOStateChanged() -//{ -// mBtnAO->setToggleState(gSavedPerAccountSettings.getBOOL("AlchemyAOEnable")); -//} +void LLStatusBar::onAOStateChanged() +{ + mBtnAO->setToggleState(gSavedPerAccountSettings.getBOOL("AlchemyAOEnable")); +} BOOL can_afford_transaction(S32 cost) { diff --git a/indra/newview/llstatusbar.h b/indra/newview/llstatusbar.h index bc58f444cdd..f447bf3b66d 100644 --- a/indra/newview/llstatusbar.h +++ b/indra/newview/llstatusbar.h @@ -106,7 +106,7 @@ class LLStatusBar final void onMouseEnterPresetsCamera(); void onMouseEnterPresets(); void onMouseEnterQuickSettings(); - //void onMouseEnterAO(); + void onMouseEnterAO(); void onMouseEnterVolume(); void onMouseEnterNearbyMedia(); void onClickScreen(S32 x, S32 y); @@ -138,7 +138,7 @@ class LLStatusBar final LLIconCtrl *mIconPresetsCamera; LLIconCtrl *mIconPresetsGraphic; LLButton *mBtnQuickSettings; - //LLButton *mBtnAO; + LLButton *mBtnAO; LLButton *mBtnVolume; LLTextBox *mBoxBalance; LLButton *mBtnBuyL; @@ -155,7 +155,7 @@ class LLStatusBar final LLFrameTimer* mHealthTimer; LLPanelPresetsCameraPulldown* mPanelPresetsCameraPulldown; LLPanelPresetsPulldown* mPanelPresetsPulldown; - //ALPanelAOPulldown* mPanelAOPulldown; + ALPanelAOPulldown* mPanelAOPulldown; ALPanelQuickSettingsPulldown* mPanelQuickSettingsPulldown; LLPanelVolumePulldown* mPanelVolumePulldown; LLPanelNearByMedia* mPanelNearByMedia; diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 6467cae5501..0475141db0a 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -31,7 +31,7 @@ #include "llfloaterreg.h" #include "llviewerfloaterreg.h" -//#include "alfloaterao.h" +#include "ao.h" #include "alfloaterexploresounds.h" #include "alfloatergenerictext.h" #include "alfloaterlightbox.h" @@ -552,7 +552,7 @@ void LLViewerFloaterReg::registerFloaters() // *NOTE: Please keep these alphabetized for easier merges - //LLFloaterReg::add("ao", "floater_ao.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterAO>); + LLFloaterReg::add("ao", "floater_ao.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FloaterAO>); LLFloaterReg::add("asset_hex_editor", "floater_hex_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterHexEditor>); LLFloaterReg::add("chatbar", "floater_chatbar.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLChatBar>); LLFloaterReg::add("delete_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterDeleteQueue>); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 17c662e44c4..c9d2fd9c652 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -38,7 +38,7 @@ #include "raytrace.h" #include "alavatargroups.h" -//#include "alaoengine.h" +#include "aoengine.h" #include "llagent.h" // Get state values from here #include "llagentbenefits.h" #include "llagentcamera.h" @@ -3147,7 +3147,7 @@ void LLVOAvatar::idleUpdateLoadingEffect() LL_INFOS("Avatar") << avString() << "self isFullyLoaded, mFirstFullyVisible" << LL_ENDL; LLAppearanceMgr::instance().onFirstFullyVisible(); - //ALAOEngine::instance().onLoginComplete(); + AOEngine::instance().onLoginComplete(); } else { @@ -3799,10 +3799,12 @@ void LLVOAvatar::idleUpdateBelowWater() F32 avatar_height = (F32)(getPositionGlobal().mdV[VZ]); F32 water_height = getRegion()->getWaterHeight(); -// BOOL was_below_water = mBelowWater; - mBelowWater = avatar_height < water_height; -// if (isSelf() && mBelowWater != was_below_water) -// ALAOEngine::instance().checkBelowWater(mBelowWater); + BOOL wasBelowWater = mBelowWater; + mBelowWater = avatar_height < water_height; + if (isSelf() && wasBelowWater != mBelowWater) + { + AOEngine::instance().checkBelowWater(mBelowWater); + } } void LLVOAvatar::slamPosition() @@ -6286,12 +6288,12 @@ BOOL LLVOAvatar::startMotion(const LLUUID& id, F32 time_offset) #ifdef SHOW_DEBUG LL_DEBUGS("Motion") << "motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; #endif -#if 0 +#if 1 LLUUID remap_id; - if(isSelf()) + if (isSelf()) { - remap_id = ALAOEngine::getInstance()->override(id, true); - if(remap_id.isNull()) + remap_id = AOEngine::getInstance()->override(id, true); + if (remap_id.isNull()) { remap_id = remapMotionID(id); } @@ -6336,11 +6338,11 @@ BOOL LLVOAvatar::stopMotion(const LLUUID& id, BOOL stop_immediate) LL_DEBUGS("Motion") << "Motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; #endif -#if 0 +#if 1 LLUUID remap_id; - if(isSelf()) + if (isSelf()) { - remap_id = ALAOEngine::getInstance()->override(id, false); + remap_id = AOEngine::getInstance()->override(id, false); if (remap_id.isNull()) { remap_id = remapMotionID(id); diff --git a/indra/newview/skins/default/xui/en/floater_ao.xml b/indra/newview/skins/default/xui/en/floater_ao.xml index dca10029fd7..986c685aaf8 100644 --- a/indra/newview/skins/default/xui/en/floater_ao.xml +++ b/indra/newview/skins/default/xui/en/floater_ao.xml @@ -1,32 +1,46 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <floater - positioning="centered" - legacy_header_height="18" - can_resize="true" - can_dock="false" - can_close="true" - height="352" - min_height="352" - min_width="200" - layout="topleft" - name="ao" - save_rect="true" - save_visibility="true" - single_instance="true" - reuse_instance="false" - save_dock_state="false" - title="Animation Overrider" - width="200"> - <floater.string name="ao_no_sets_loaded">No Sets Loaded</floater.string> - <floater.string name="ao_no_animations_loaded">No Animations Loaded</floater.string> - <panel - name="animation_overrider_outer_panel" - filename="panel_ao.xml" - left="0" - top="16" - width="200" - height="334" - follows="all" - layout="topleft"> - </panel> + positioning="centered" + legacy_header_height="18" + can_resize="true" + can_dock="false" + can_close="true" + height="352" + min_height="320" + min_width="200" + layout="topleft" + name="ao" + help_topic="animation_overrider" + save_rect="true" + save_visibility="true" + single_instance="true" + reuse_instance="false" + save_dock_state="false" + title="Animation Overrider" + width="200"> + + <floater.string + name="ao_no_sets_loaded"> +No Sets Loaded + </floater.string> + + <floater.string + name="ao_no_animations_loaded"> +No Animations Loaded + </floater.string> + <floater.string + name="ao_dnd_only_on_full_interface"> +You can only drag and drop an animation notecard on the full interface window. Click on the wrench icon to open it. + </floater.string> + + <panel + name="animation_overrider_outer_panel" + filename="panel_ao.xml" + left="0" + top="16" + width="200" + height="334" + follows="all" + layout="topleft"> + </panel> </floater> diff --git a/indra/newview/skins/default/xui/en/panel_ao.xml b/indra/newview/skins/default/xui/en/panel_ao.xml index 7f585bcad5e..3b893beac78 100644 --- a/indra/newview/skins/default/xui/en/panel_ao.xml +++ b/indra/newview/skins/default/xui/en/panel_ao.xml @@ -1,423 +1,490 @@ <?xml version="1.0" encoding="utf-8"?> <panel - name="animation_overrider_outer_panel" - left="0" - top="0" - width="200" - height="334" - follows="all" - visible="true" - layout="topleft"> + name="animation_overrider_outer_panel" + left="0" + top="0" + width="200" + height="324" + follows="all" + visible="true" + layout="topleft"> + <!-- Main Panel --> - <panel - name="animation_overrider_panel" - left="10" - top="4" - width="180" - height="320" - follows="all" - visible="true" - layout="topleft"> - <check_box - name="ao_enable" - label="Enable AO" - tool_tip="Enable or disable the Animation Overrider" - control_name="AlchemyAOEnable" - left="0" - top="4" - width="20" - height="20" - follows="left|top" - layout="topleft" /> - <combo_box - name="ao_set_selection_combo" - tool_tip="Select animation set to edit." - left="0" - top_pad="2" - right="-24" - height="20" - allow_text_entry="true" - max_chars="256" - follows="left|right|top" - layout="topleft"> - <combo_box.commit_callback - function="AO.SelectSet" /> - </combo_box> - <button - name="ao_activate" - tool_tip="Activate this animation set now." - left_pad="4" - width="20" - height="20" - follows="right|top" - layout="topleft"> - <button.commit_callback - function="AO.ActivateSet" /> - </button> - <icon - left_delta="4" - top_delta="4" - width="12" - height="12" - image_name="Activate_Checkmark" - follows="right|top" - layout="topleft" /> - <check_box - name="ao_default" - label="Default" - tool_tip="Make this animation set the default set that plays when you log in." - left="0" - top_pad="8" - width="20" - height="20" - follows="left|top" - layout="topleft"> - <check_box.commit_callback - function="AO.SetDefault" /> - </check_box> - <button - name="ao_add" - tool_tip="Create a new animation set." - top_delta="0" - right="-24" - width="20" - height="20" - image_overlay="AddItem_Press" - follows="right|top" - layout="topleft"> - <button.commit_callback - function="AO.AddSet" /> - </button> - <button - name="ao_remove" - tool_tip="Remove this animation set." - left_pad="4" - width="20" - height="20" - image_overlay="TrashItem_Press" - follows="right|top" - layout="topleft"> - <button.commit_callback - function="AO.RemoveSet" /> - </button> - <check_box - name="ao_sit_override" - label="Override Sits" - tool_tip="Check this if you want sit animation overrides." - left="0" - top_pad="4" - width="100" - height="16" - follows="left|top" - layout="topleft"> - <check_box.commit_callback - function="AO.SetSitOverride" /> - </check_box> - <check_box - name="ao_smart" - label="Be smart" - tool_tip="Smart mode tries to determine if the sit override would clash with the object's own animation and disables the overrider temporarily." - left_pad="4" - right="-1" - height="16" - follows="left|top" - layout="topleft"> - <check_box.commit_callback - function="AO.SetSmart" /> - </check_box> - <check_box - name="ao_disable_stands_in_mouselook" - label="Disable Stands in Mouselook" - tool_tip="If you need to preserve your custom stand animation in mouselook, check this box." - left="0" - top_pad="4" - width="100" - height="16" - follows="left|top" - layout="topleft"> - <check_box.commit_callback - function="AO.DisableStandsML" /> - </check_box> - <combo_box - name="ao_state_selection_combo" - tool_tip="Select animation state to edit." - left="0" - top_pad="4" - right="-1" - height="20" - follows="left|right|top" - layout="topleft"> - <combo_box.commit_callback - function="AO.SelectState" /> - </combo_box> - <scroll_list - name="ao_state_animation_list" - top_pad="4" - right="-24" - height="98" - multi_select="true" - follows="all" - layout="topleft"> - <scroll_list.columns - name="icon" - dynamic_width="false" - width="20" /> - <scroll_list.columns - name="animation_name" - dynamic_width="true" /> - <scroll_list.commit_callback - function="AO.SelectAnim" /> - </scroll_list> - <panel - name="ao_animation_move_trash_panel" - left_pad="4" - right="-1" - height="98" - follows="right|top|bottom" - layout="topleft"> - <button - name="ao_move_up" - tool_tip="Move the selected animation up in the list." - left="0" - top="0" - width="20" - height="32" - image_overlay="Arrow_Up" - follows="left|top" - layout="topleft"> - <button.commit_callback - function="AO.MoveAnimUp" /> - </button> - <button - name="ao_move_down" - tool_tip="Move the selected animation down in the list." - top_pad="4" - width="20" - height="32" - image_overlay="Arrow_Down" - follows="left|top" - layout="topleft"> - <button.commit_callback - function="AO.MoveAnimDown" /> - </button> - <button - name="ao_trash" - tool_tip="Remove the selected animation from the list." - left_delta="0" - bottom="-1" - width="20" - height="20" - image_overlay="TrashItem_Press" - follows="left|bottom" - layout="topleft"> - <button.commit_callback - function="AO.RemoveAnim" /> - </button> - </panel> - <check_box - name="ao_cycle" - label="Cycle" - tool_tip="Play a different animation from the list every time the animation state is called." - left="0" - top_pad="4" - width="55" - height="16" - follows="left|bottom" - layout="topleft"> - <check_box.commit_callback - function="AO.SetCycle" /> - </check_box> - <check_box - name="ao_randomize" - label="Randomize order" - tool_tip="Randomize order of animations in cycle mode." - left_pad="4" - width="100" - height="16" - follows="left|bottom" - layout="topleft"> - <check_box.commit_callback - function="AO.SetRandomize" /> - </check_box> - <text - name="ao_cycle_time_seconds_label" - value="Cycle time (seconds):" - left="0" - top_pad="4" - width="120" - follows="left|right|bottom" - layout="topleft" /> - <spinner - name="ao_cycle_time" - tool_tip="Time before switching to the next animation in the list. Set this to 0 to disable automatic animation cycling." - top_delta="-6" - left_pad="8" - height="16" - right="-1" - decimal_digits="0" - initial_value="0" - min_val="0" - max_val="999" - increment="1" - follows="right|bottom" - layout="topleft"> - <spinner.commit_callback - function="AO.SetCycleTime" /> - </spinner> - <button - name="ao_reload" - label="Reload" - tool_tip="Reload animation overrider configuration." - left="0" - top_pad="8" - right="-1" - height="20" - follows="left|right|bottom" - layout="topleft"> - <button.commit_callback - function="AO.Reload" /> - </button> - <layout_stack - name="next_previous_buttons_stack" - left="0" - top_pad="4" - right="-1" - height="20" - orientation="horizontal" - follows="left|right|bottom" - layout="topleft"> - <layout_panel - name="next_previous_buttons_stack_left" - width="90" - height="20" - user_resize="false" - follows="all" - layout="topleft"> - <button - name="ao_previous" - image_overlay="BackArrow_Off" - tool_tip="Switch to previous animation of the current state." - left="0" - top="0" - width="90" - height="20" - follows="all" - layout="topleft"> - <button.commit_callback - function="AO.PrevAnim" /> - </button> - </layout_panel> - <layout_panel - name="next_previous_buttons_stack_right" - width="90" - height="20" - user_resize="false" - follows="all" - layout="topleft"> + + <panel + name="animation_overrider_panel" + left="10" + top="0" + width="180" + height="322" + follows="all" + visible="true" + layout="topleft"> + <check_box + name="ao_enable" + label="Enable AO" + tool_tip="Enable or disable the Animation Overrider" + control_name="AlchemyAOEnable" + left="0" + top="0" + width="20" + height="20" + follows="left|top" + layout="topleft" /> + <combo_box + name="ao_set_selection_combo" + tool_tip="Select animation set to edit." + left="0" + top_pad="2" + right="-24" + height="20" + allow_text_entry="true" + max_chars="256" + follows="left|right|top" + force_disable_fulltext_search="true" + layout="topleft" /> + <button - name="ao_next" - image_overlay="ForwardArrow_Off" - tool_tip="Switch to next animation of the current state." - left="2" - top="0" - width="90" - height="20" - follows="all" - layout="topleft"> - <button.commit_callback - function="AO.NextAnim" /> - </button> - </layout_panel> - </layout_stack> - </panel> -<!-- Cute Reload Cover Panel --> - <panel - name="ao_reload_cover" - left="0" - top="0" - width="200" - height="325" - follows="all" - visible="false" - bg_alpha_color="Black_50" - bg_opaque_color="Black_50" - background_visible="true" - background_opaque="true" - mouse_opaque="true" - layout="topleft"> - <panel - name="ao_reload_text_panel" - left="30" - top="110" - right="-30" - bottom="-110" - bg_alpha_color="Black_50" - bg_opaque_color="Black_50" - background_visible="true" - background_opaque="true" - follows="left|right|top"> - <view_border - name="ao_reload_view_border" - left="0" - top="0" - right="-1" - bottom="-1" - follows="all" /> - <text - name="reload_label" - left_delta="0" - top_delta="16" - halign="center" - valign="center" - follows="all"> - Reloading Config - </text> - <text - name="wait_label" - v_pad="-4" - bottom="-16" - halign="center" - valign="center" - follows="all"> - Please Wait - </text> - <layout_stack - name="ao_reload_indicator_layout_stack" - left="0" - top="31" - right="-1" - height="32" - orientation="horizontal" - follows="top|left|right"> - <layout_panel - name="ao_reload_indicator_left_layout_panel" - width="160" - height="32" - auto_resize="true" - user_resize="false"> - </layout_panel> - <layout_panel - name="ao_reload_indicator_layout_panel" - width="32" - height="32" - auto_resize="false" - user_resize="false"> - <loading_indicator - left="0" - top="0" - width="32" - height="32" - follows="right|top" - name="ao_reload_indicator" /> - </layout_panel> - <layout_panel - name="ao_reload_indicator_right_layout_panel" - width="160" - height="32" - auto_resize="true" - user_resize="false"> - </layout_panel> - </layout_stack> - </panel> - </panel> + name="ao_activate" + tool_tip="Activate this animation set now." + left_pad="4" + width="20" + height="20" + follows="right|top" + layout="topleft" /> + + <icon + left_delta="4" + top_delta="4" + width="12" + height="12" + image_name="Activate_Checkmark" + follows="right|top" + layout="topleft" /> + + <check_box + name="ao_default" + label="Default" + tool_tip="Make this animation set the default set that plays when you log in." + left="0" + top_pad="8" + width="20" + height="20" + follows="left|top" + layout="topleft" /> + + <button + name="ao_add" + tool_tip="Create a new animation set." + top_delta="0" + right="-24" + width="20" + height="20" + image_overlay="AddItem_Press" + follows="right|top" + layout="topleft" /> + + <button + name="ao_remove" + tool_tip="Remove this animation set." + left_pad="4" + width="20" + height="20" + image_overlay="TrashItem_Press" + follows="right|top" + layout="topleft" /> + + <check_box + name="ao_sit_override" + label="Override Sits" + tool_tip="Check this if you want sit animation overrides." + left="0" + top_pad="4" + width="100" + height="16" + follows="left|top" + layout="topleft" /> + + <check_box + name="ao_smart" + label="Be smart" + tool_tip="Smart mode tries to determine if the sit override would clash with the object's own animation and disables the overrider temporarily." + left_pad="4" + right="-1" + height="16" + follows="left|top" + layout="topleft" /> + + <check_box + name="ao_disable_stands_in_mouselook" + label="Disable Stands in Mouselook" + tool_tip="If you need to preserve your custom stand animation in mouselook, check this box." + left="0" + top_pad="4" + width="100" + height="16" + follows="left|top" + layout="topleft" /> + + <combo_box + name="ao_state_selection_combo" + tool_tip="Select animation state to edit." + left="0" + top_pad="4" + right="-1" + height="20" + follows="left|right|top" + layout="topleft" /> + + <scroll_list + name="ao_state_animation_list" + top_pad="4" + right="-24" + height="98" + multi_select="true" + follows="all" + layout="topleft"> + <scroll_list.columns + name="icon" + dynamic_width="false" + width="20" /> + <scroll_list.columns + name="animation_name" + dynamic_width="true" /> + </scroll_list> + + <panel + name="ao_animation_move_trash_panel" + left_pad="4" + right="-1" + height="98" + follows="right|top|bottom" + layout="topleft"> + + <button + name="ao_move_up" + tool_tip="Move the selected animation up in the list." + left="0" + top="0" + width="20" + height="32" + image_overlay="Arrow_Up" + follows="left|top" + layout="topleft" /> + + <button + name="ao_move_down" + tool_tip="Move the selected animation down in the list." + top_pad="4" + width="20" + height="32" + image_overlay="Arrow_Down" + follows="left|top" + layout="topleft" /> + + <button + name="ao_trash" + tool_tip="Remove the selected animation from the list." + left_delta="0" + bottom="-1" + width="20" + height="20" + image_overlay="TrashItem_Press" + follows="left|bottom" + layout="topleft" /> + </panel> + + <check_box + name="ao_cycle" + label="Cycle" + tool_tip="Play a different animation from the list every time the animation state is called." + left="0" + top_pad="4" + width="55" + height="16" + follows="left|bottom" + layout="topleft" /> + + <check_box + name="ao_randomize" + label="Randomize order" + tool_tip="Randomize order of animations in cycle mode." + left_pad="4" + width="100" + height="16" + follows="left|bottom" + layout="topleft" /> + + <text + name="ao_cycle_time_seconds_label" + value="Cycle time (seconds):" + left="0" + top_pad="4" + width="120" + follows="left|right|bottom" + layout="topleft" /> + + <spinner + name="ao_cycle_time" + tool_tip="Time before switching to the next animation in the list. Set this to 0 to disable automatic animation cycling." + top_delta="-6" + left_pad="8" + height="16" + right="-1" + decimal_digits="0" + initial_value="0" + min_val="0" + max_val="999" + increment="1" + follows="right|bottom" + layout="topleft" /> + + <button + name="ao_reload" + label="Reload" + tool_tip="Reload animation overrider configuration." + left="0" + top_pad="8" + right="-1" + height="20" + follows="left|right|bottom" + layout="topleft" /> + + <layout_stack + name="next_previous_buttons_stack" + left="0" + top_pad="4" + right="-24" + height="20" + orientation="horizontal" + follows="left|right|bottom" + layout="topleft"> + + <layout_panel + name="next_previous_buttons_stack_left" + width="90" + height="20" + user_resize="false" + follows="all" + layout="topleft"> + + <button + name="ao_previous" + image_overlay="BackArrow_Off" + tool_tip="Switch to previous animation of the current state." + left="0" + top="0" + width="88" + height="20" + follows="all" + layout="topleft" /> + + </layout_panel> + <layout_panel + name="next_previous_buttons_stack_right" + width="90" + height="20" + user_resize="false" + follows="all" + layout="topleft"> + + <button + name="ao_next" + image_overlay="ForwardArrow_Off" + tool_tip="Switch to next animation of the current state." + left="2" + top="0" + width="88" + height="20" + follows="all" + layout="topleft" /> + + </layout_panel> + </layout_stack> + + <button + name="ao_less" + tool_tip="Show small Animation Overrider interface." + image_overlay="DisclosureArrow_Opened_Off" + left_pad="4" + width="20" + height="20" + follows="right|bottom" + layout="topleft" /> + + </panel> + +<!-- Reload Cover Panel --> + + <panel + name="ao_reload_cover" + left="0" + top="0" + width="200" + height="315" + follows="all" + visible="false" + bg_alpha_color="Black_50" + bg_opaque_color="Black_50" + background_visible="true" + background_opaque="true" + mouse_opaque="true" + layout="topleft"> + + <panel + name="ao_reload_text_panel" + left="30" + top="110" + right="-30" + bottom="-110" + bg_alpha_color="Black_50" + bg_opaque_color="Black_50" + background_visible="true" + background_opaque="true" + follows="left|right|top"> + + <view_border + name="ao_reload_view_border" + left="0" + top="0" + right="-1" + bottom="-1" + follows="all" /> + <text + name="reload_label" + left_delta="0" + top_delta="16" + halign="center" + valign="center" + follows="all"> + Reloading Config + </text> + <text + name="wait_label" + v_pad="-4" + bottom="-16" + halign="center" + valign="center" + follows="all"> + Please Wait + </text> + + <layout_stack + name="ao_reload_indicator_layout_stack" + left="0" + top="31" + right="-1" + height="32" + orientation="horizontal" + follows="top|left|right"> + <layout_panel + name="ao_reload_indicator_left_layout_panel" + width="160" + height="32" + auto_resize="true" + user_resize="false"> + </layout_panel> + <layout_panel + name="ao_reload_indicator_layout_panel" + width="32" + height="32" + auto_resize="false" + user_resize="false"> + <loading_indicator + left="0" + top="0" + width="32" + height="32" + follows="right|top" + name="ao_reload_indicator" /> + </layout_panel> + <layout_panel + name="ao_reload_indicator_right_layout_panel" + width="160" + height="32" + auto_resize="true" + user_resize="false"> + </layout_panel> + </layout_stack> + + </panel> + </panel> + +<!-- Small Panel --> + + <panel + name="animation_overrider_panel_small" + left="10" + top="6" + width="180" + height="65" + visible="false" + follows="left|right|top" + layout="topleft"> + + <check_box + name="ao_enable" + label="Enable AO" + tool_tip="Enable or disable the Animation Overrider" + control_name="AlchemyAOEnable" + left="0" + top="0" + width="20" + height="10" + follows="left|top" + layout="topleft" /> + + <combo_box + name="ao_set_selection_combo_small" + tool_tip="Select animation set to edit." + left="0" + top_pad="3" + right="-24" + height="20" + follows="left|right|top" + force_disable_fulltext_search="true" + layout="topleft" /> + + <button + name="ao_more" + tool_tip="Show full interface for configuration." + image_overlay="Edit_Wrench" + left_pad="4" + right="-1" + height="20" + follows="right|top" + layout="topleft" /> + + <button + name="ao_previous_small" + image_overlay="BackArrow_Off" + tool_tip="Switch to previous animation of the current state." + left="0" + top_pad="4" + width="62" + height="20" + follows="left|top" + layout="topleft" /> + + <button + name="ao_next_small" + image_overlay="ForwardArrow_Off" + tool_tip="Switch to next animation of the current state." + left_pad="4" + width="62" + height="20" + follows="left|top" + layout="topleft" /> + + <check_box + name="ao_sit_override_small" + label="Sits" + tool_tip="Check this if you want sit animation overrides." + left_pad="4" + width="45" + height="16" + follows="right|top" + layout="topleft" /> + + </panel> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_status_bar.xml b/indra/newview/skins/default/xui/en/panel_status_bar.xml index 256d03a1a90..fc16ba2c886 100644 --- a/indra/newview/skins/default/xui/en/panel_status_bar.xml +++ b/indra/newview/skins/default/xui/en/panel_status_bar.xml @@ -162,7 +162,16 @@ name="presets_icon_graphic" tool_tip="Graphics Presets" width="16" /> - + <button + follows="right|top" + height="16" + image_selected="Move" + image_unselected="Move_Off" + is_toggle="true" + left_pad="5" + top="2" + name="ao_btn" + width="16" /> <button follows="right|top" height="16" -- GitLab