diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 5e0679095eb92c9838cc1060a36369dbf2cbc00d..429881f911d014f8b0734668c704b1c4671ea1e7 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -110,11 +110,16 @@ include_directories(SYSTEM ) set(viewer_SOURCE_FILES + alaoengine.cpp + alaoset.cpp alavataractions.cpp alchatcommand.cpp alcontrolcache.cpp + alfloaterao.cpp alfloaterparticleeditor.cpp alfloaterregiontracker.cpp + alpanelaomini.cpp + alpanelaopulldown.cpp alpanelquicksettings.cpp alpanelquicksettingspulldown.cpp alunzip.cpp @@ -759,11 +764,16 @@ set(VIEWER_BINARY_NAME "alchemy-bin" CACHE STRING set(viewer_HEADER_FILES CMakeLists.txt ViewerInstall.cmake + alaoengine.h + alaoset.h alavataractions.h alchatcommand.h alcontrolcache.h + alfloaterao.h alfloaterparticleeditor.h alfloaterregiontracker.h + alpanelaomini.h + alpanelaopulldown.h alpanelquicksettings.h alpanelquicksettingspulldown.h alunzip.h diff --git a/indra/newview/alaoengine.cpp b/indra/newview/alaoengine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cb12ea3911c076cee2e7e2d38ff64ea836c340a0 --- /dev/null +++ b/indra/newview/alaoengine.cpp @@ -0,0 +1,2012 @@ +/** + * @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 "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "llnotificationsutil.h" +#include "llvfs.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->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) +{ + LL_DEBUGS("AOEngine") << "override(" << pMotion << "," << start << ")" << LL_ENDL; + + LLUUID animation = LLUUID::null; + + LLUUID motion = pMotion; + + if (!mEnabled) + { + if (start && mCurrentSet) + { + const ALAOSet::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 animation; + } + + if (mSets.empty()) + { + LL_DEBUGS("AOEngine") << "No sets loaded. Skipping overrider." << LL_ENDL; + return animation; + } + + if (!mCurrentSet) + { + LL_DEBUGS("AOEngine") << "No current AO set chosen. Skipping overrider." << LL_ENDL; + 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) + { + LL_DEBUGS("AOEngine") << "No current AO state for motion " << motion << " (" << gAnimLibrary.animationName(motion) << ")." << LL_ENDL; + return animation; + } + + mAnimationChangedSignal(LLUUID::null); + + 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->getMouselookDisable() && + motion == ANIM_AGENT_STAND && + mInMouselook) + { + LL_DEBUGS("AOEngine") << "(enabled AO, mouselook stand stopped) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << LL_ENDL; + return animation; + } + + // 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 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) + { + LL_DEBUGS("AOEngine") << "Ground sit animation playing but sitting on a prim - disabling overrider." << LL_ENDL; + return animation; + } + } + + 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); + } + + mUnderWater = gAgentAvatarp->mBelowWater; + if (mUnderWater) + { + animation = mapSwimming(motion); + } + + if (animation.isNull()) + { + 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_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(); + } + + LL_DEBUGS("AOEngine") << "stopping cycle timer for motion " << gAnimLibrary.animationName(motion) << + " using animation " << animation << + " in state " << state->mName << LL_ENDL; + } + + 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); + } + } +} + +LLUUID ALAOEngine::addSet(const std::string& name, const bool reload) +{ + if (mAOFolder.isNull()) + { + LL_WARNS("AOEngine") << ROOT_AO_FOLDER << " folder not there yet. Requesting recreation." << LL_ENDL; + tick(); + return LLUUID::null; + } + + LL_DEBUGS("AOEngine") << "adding set folder " << name << LL_ENDL; + LLUUID newUUID = gInventory.createNewCategory(mAOFolder, LLFolderType::FT_NONE, name); + + if (reload) + { + mTimerCollection.setReloadTimer(true); + } + return newUUID; +} + +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); + for (auto& cat : *cats) + { + // recurse into subfolders + if (findForeignItems(cat->getUUID())) + { + moved = true; + } + } + + // count backwards in case we have to remove 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; + LLInventoryModel* model = &gInventory; + model->changeItemParent(item, gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND), FALSE); + LL_DEBUGS("AOEngine") << item->getName() << " moved to lost and found!" << LL_ENDL; + } + } + + 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) + { + LL_DEBUGS("AOEngine") << "Found animation link " << item->LLInventoryItem::getName() + << " desc " << item->LLInventoryItem::getDescription() + << " asset " << item->getAssetUUID() << LL_ENDL; + + 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; + } + + 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(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) + { + LL_DEBUGS("AOEngine") << "inserting at index " << index << LL_ENDL; + state->mAnimations.emplace(state->mAnimations.begin() + index, + linkedItem->LLInventoryItem::getName(), item->getAssetUUID(), item->getUUID(), sortOrder); + inserted = true; + break; + } + } + if (!inserted) + { + LL_DEBUGS("AOEngine") << "not inserted yet, appending to the list instead" << LL_ENDL; + state->mAnimations.emplace_back(linkedItem->LLInventoryItem::getName(), item->getAssetUUID(), item->getUUID(), sortOrder); + } + } + LL_DEBUGS("AOEngine") << "Animation count now: " << state->mAnimations.size() << LL_ENDL; + } + + 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; + 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; + + LLUUID const& category_id = gInventory.findCategoryUUIDForNameInRoot(ROOT_AO_FOLDER, true, gInventory.getRootFolderID()); + + if (category_id.notNull()) + { + mAOFolder = category_id; + LL_INFOS("AOEngine") << "AO basic folder structure intact." << LL_ENDL; + 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(LLVFS* vfs, 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; + + S32 notecardSize = vfs->getSize(assetUUID, type); + auto buffer = std::make_unique<char[]>(notecardSize + 1); + buffer[notecardSize] = '\0'; + S32 ret = vfs->getData(assetUUID, type, reinterpret_cast<U8*>(buffer.get()), 0, notecardSize); + if (ret > 0) + { + 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); + + 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 (!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::processImport(bool aFromTimer) +{ + if (mImportCategory.isNull()) + { + mImportCategory = addSet(mImportSet->getName(), false); + 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); + } + + bool allComplete = true; + for (S32 index = 0; index < ALAOSet::AOSTATES_MAX; ++index) + { + ALAOSet::AOState* state = mImportSet->getState(index); + if (!state->mAnimations.empty()) + { + 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); + } +} + +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 new file mode 100644 index 0000000000000000000000000000000000000000..5f1bea9d8d44df6261bc8c3d422e14707ca2a89e --- /dev/null +++ b/indra/newview/alaoengine.h @@ -0,0 +1,223 @@ +/** + * @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" + +class ALAOTimerCollection : 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 : 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; + + LLUUID addSet(const std::string& name, bool reload = true); + 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); + + 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(LLVFS* vfs, 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.cpp b/indra/newview/alaoset.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f3c9b2c0c9793ad9e5ffc74ac4da212a9a08cacc --- /dev/null +++ b/indra/newview/alaoset.cpp @@ -0,0 +1,222 @@ +/** + * @file alaoset.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 + * 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 "alaoengine.h" +#include "alaoset.h" +#include "llanimationstates.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) +{ + 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|Stand.1|Stand.2|Stand.3", + "Walking|Walk.N", + "Running", + "Sitting|Sit.N", + "Sitting On Ground|Sit.G", + "Crouching|Crouch", + "Crouch Walking|Walk.C", + "Landing|Land.N", + "Soft Landing", + "Standing Up|Stand.U", + "Falling", + "Flying Down|Hover.D", + "Flying Up|Hover.U", + "Flying|Fly.N", + "Flying Slow", + "Hovering|Hover.N", + "Jumping|Jump.N", + "Pre Jumping|Jump.P", + "Turning Right|Turn.R", + "Turning Left|Turn.L", + "Typing", + "Floating|Swim.H", + "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 {{ + ANIM_AGENT_STAND, + ANIM_AGENT_WALK, + ANIM_AGENT_RUN, + ANIM_AGENT_SIT, + ANIM_AGENT_SIT_GROUND_CONSTRAINED, + ANIM_AGENT_CROUCH, + ANIM_AGENT_CROUCHWALK, + ANIM_AGENT_LAND, + ANIM_AGENT_MEDIUM_LAND, + ANIM_AGENT_STANDUP, + ANIM_AGENT_FALLDOWN, + ANIM_AGENT_HOVER_DOWN, + ANIM_AGENT_HOVER_UP, + ANIM_AGENT_FLY, + ANIM_AGENT_FLYSLOW, + ANIM_AGENT_HOVER, + ANIM_AGENT_JUMP, + ANIM_AGENT_PRE_JUMP, + ANIM_AGENT_TURNRIGHT, + ANIM_AGENT_TURNLEFT, + ANIM_AGENT_TYPE, + ANIM_AGENT_HOVER, // needs special treatment + 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, "|"); + + 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].mInventoryUUID = LLUUID::null; + mStates[index].mCurrentAnimation = 0; + mStates[index].mCurrentAnimationID = LLUUID::null; + mStates[index].mCycle = false; + mStates[index].mRandom = false; + mStates[index].mCycleTime = 0.0f; + mStates[index].mDirty = false; + mStateNames.push_back(stateNameList[0]); + } + stopTimer(); +} + +ALAOSet::~ALAOSet() +{ + LL_DEBUGS("AOEngine") << "Set deleted: " << this << LL_ENDL; +} + +ALAOSet::AOState* ALAOSet::getState(S32 name) +{ + return &mStates[name]; +} + +ALAOSet::AOState* ALAOSet::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) + { + if (state->mAlternateNames[names].compare(name) == 0) + { + return state; + } + } + } + return nullptr; +} + +ALAOSet::AOState* ALAOSet::getStateByRemapID(const LLUUID& id) +{ + LLUUID remap_id = id; + if (remap_id == ANIM_AGENT_SIT_GROUND) + { + remap_id = ANIM_AGENT_SIT_GROUND_CONSTRAINED; + } + + for (S32 index = 0; index < AOSTATES_MAX; ++index) + { + if (mStates[index].mRemapID == remap_id) + { + return &mStates[index]; + } + } + return nullptr; +} + +const LLUUID& ALAOSet::getAnimationForState(AOState* state) const +{ + if (!state) return LLUUID::null; + + size_t num_animations = state->mAnimations.size(); + if (num_animations) + { + if (state->mCycle) + { + if (state->mRandom) + { + state->mCurrentAnimation = ll_frand() * num_animations; + LL_DEBUGS("AOEngine") << "randomly chosen " << state->mCurrentAnimation << " of " << num_animations << LL_ENDL; + } + else + { + state->mCurrentAnimation++; + if (state->mCurrentAnimation >= state->mAnimations.size()) + { + state->mCurrentAnimation = 0; + } + LL_DEBUGS("AOEngine") << "cycle " << state->mCurrentAnimation << " of " << num_animations << 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) +{ + mEventTimer.stop(); + mPeriod = timeout; + mEventTimer.start(); + LL_DEBUGS("AOEngine") << "Starting state timer for " << getName() << " at " << timeout << LL_ENDL; +} + +void ALAOSet::stopTimer() +{ + LL_DEBUGS("AOEngine") << "State timer for " << getName() << " stopped." << LL_ENDL; + mEventTimer.stop(); +} + +BOOL ALAOSet::tick() +{ + ALAOEngine::instance().cycleTimeout(this); + return FALSE; +} diff --git a/indra/newview/alaoset.h b/indra/newview/alaoset.h new file mode 100644 index 0000000000000000000000000000000000000000..db84ee88e707088a7b1e900960ed218cc9c0a3a1 --- /dev/null +++ b/indra/newview/alaoset.h @@ -0,0 +1,151 @@ +/** + * @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 <utility> +#include "lleventtimer.h" + +class ALAOSet : 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 3c723014a0fa0fe7399a717ef7582ee939fd5f04..41dfa26d5d99fbc3c4f9fe5412aaf577bee94352 100644 --- a/indra/newview/alchatcommand.cpp +++ b/indra/newview/alchatcommand.cpp @@ -29,10 +29,10 @@ #include "object_flags.h" // viewer includes +#include "alaoengine.h" #include "llagent.h" #include "llagentcamera.h" #include "llagentui.h" -//#include "llaoengine.h" #include "llcommandhandler.h" #include "llfloaterimnearbychat.h" #include "llfloaterreg.h" @@ -297,44 +297,44 @@ bool ALChatCommand::parseCommand(std::string data) } return true; } - //else if (cmd == utf8str_tolower(sAOCommand)) - //{ - // std::string subcmd; - // if (input >> subcmd) - // { - // if (subcmd == "on") - // { - // gSavedPerAccountSettings.setBOOL("UseAO", TRUE); - // return true; - // } - // else if (subcmd == "off") - // { - // gSavedPerAccountSettings.setBOOL("UseAO", FALSE); - // return true; - // } - // else if (subcmd == "sit") - // { - // auto ao_set = LLAOEngine::instance().getSetByName(LLAOEngine::instance().getCurrentSetName()); - // if (input >> subcmd) - // { - // if (subcmd == "on") - // { - // LLAOEngine::instance().setOverrideSits(ao_set, true); + else if (cmd == utf8str_tolower(sAOCommand)) + { + std::string subcmd; + if (input >> subcmd) + { + if (subcmd == "on") + { + gSavedPerAccountSettings.setBOOL("AlchemyAOEnable", TRUE); + return true; + } + else if (subcmd == "off") + { + gSavedPerAccountSettings.setBOOL("AlchemyAOEnable", FALSE); + return true; + } + else if (subcmd == "sit") + { + auto ao_set = ALAOEngine::instance().getSetByName(ALAOEngine::instance().getCurrentSetName()); + if (input >> subcmd) + { + if (subcmd == "on") + { + ALAOEngine::instance().setOverrideSits(ao_set, true); - // } - // else if (subcmd == "off") - // { - // LLAOEngine::instance().setOverrideSits(ao_set, false); - // } - // } - // else - // { - // LLAOEngine::instance().setOverrideSits(ao_set, !ao_set->getSitOverride()); - // } - // return true; - // } - // } - //} + } + else if (subcmd == "off") + { + ALAOEngine::instance().setOverrideSits(ao_set, false); + } + } + else + { + ALAOEngine::instance().setOverrideSits(ao_set, !ao_set->getSitOverride()); + } + return true; + } + } + } } return false; } diff --git a/indra/newview/alfloaterao.cpp b/indra/newview/alfloaterao.cpp new file mode 100644 index 0000000000000000000000000000000000000000..61ed286ab8c3282a7fd9424e244f44ef41cc310e --- /dev/null +++ b/indra/newview/alfloaterao.cpp @@ -0,0 +1,740 @@ +/** + * @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) + { + if (ALAOEngine::instance().addSet(new_name).notNull()) + { + 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 new file mode 100644 index 0000000000000000000000000000000000000000..a51446200da5d4e85f0b3133897e40a7963ba6e6 --- /dev/null +++ b/indra/newview/alfloaterao.h @@ -0,0 +1,146 @@ +/** + * @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 new file mode 100644 index 0000000000000000000000000000000000000000..4d5b046849cbfcf0937f99cfb3bde89f453eb27b --- /dev/null +++ b/indra/newview/alpanelaomini.cpp @@ -0,0 +1,132 @@ +/* + * @file alpanelaomini.cpp + * @brief Animation overrides mini control panel + * + * Copyright (c) 2016, Cinder Roxley <cinder@sdf.org> + * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "llviewerprecompiledheaders.h" +#include "alpanelaomini.h" + +#include "alaoengine.h" +#include "alfloaterao.h" +#include "llfloaterreg.h" +#include "llcombobox.h" + +static LLPanelInjector<ALPanelAOMini> t_ao_mini("ao_mini"); + +ALPanelAOMini::ALPanelAOMini() + : LLPanel() + , mSetList(nullptr) +{ + mCommitCallbackRegistrar.add("AO.SitOverride", boost::bind(&ALPanelAOMini::onClickSit, this, _2)); + mCommitCallbackRegistrar.add("AO.NextAnim", boost::bind(&ALPanelAOMini::onClickNext, this)); + mCommitCallbackRegistrar.add("AO.PrevAnim", boost::bind(&ALPanelAOMini::onClickPrevious, this)); + mCommitCallbackRegistrar.add("AO.OpenFloater", boost::bind(&ALPanelAOMini::openAOFloater, this)); + + //mEnableCallbackRegistrar.add("AO.EnableSet", boost::bind()); + //mEnableCallbackRegistrar.add("AO.EnableState", boost::bind()); +} + +ALPanelAOMini::~ALPanelAOMini() +{ + if (mReloadCallback.connected()) + mReloadCallback.disconnect(); + if (mSetChangedCallback.connected()) + mSetChangedCallback.disconnect(); +} + +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)); + + return TRUE; +} + +///////////////////////////////////// +// Callback receivers + +void ALPanelAOMini::updateSetList() +{ + std::vector<ALAOSet*> list = ALAOEngine::getInstance()->getSetList(); + if (list.empty()) + { + return; + } + mSetList->removeall(); + for (ALAOSet* set : list) + { + mSetList->add(set->getName(), &set, ADD_BOTTOM, true); + } + const std::string& current_set = ALAOEngine::instance().getCurrentSetName(); + mSetList->selectByValue(LLSD(current_set)); +} + +void ALPanelAOMini::onSetChanged(const std::string& set_name) +{ + mSetList->selectByValue(LLSD(set_name)); +} + +//////////////////////////////////// +// Control actions + +void ALPanelAOMini::onSelectSet(const LLSD& userdata) +{ + ALAOSet* selected_set = ALAOEngine::instance().getSetByName(userdata.asString()); + if (selected_set) + { + ALAOEngine::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); + if (selected_set) + { + ALAOEngine::instance().setOverrideSits(selected_set, userdata.asBoolean()); + } +} + +void ALPanelAOMini::onClickNext() +{ + ALAOEngine::instance().cycle(ALAOEngine::CycleNext); +} + +void ALPanelAOMini::onClickPrevious() +{ + ALAOEngine::instance().cycle(ALAOEngine::CyclePrevious); +} + +void ALPanelAOMini::openAOFloater() +{ + LLFloaterReg::showInstance("ao"); +} diff --git a/indra/newview/alpanelaomini.h b/indra/newview/alpanelaomini.h new file mode 100644 index 0000000000000000000000000000000000000000..57be0ccef7c2b24f36dd5e9e0f4cd5fa9f1d2156 --- /dev/null +++ b/indra/newview/alpanelaomini.h @@ -0,0 +1,65 @@ +/* + * @file alpanelaomini.h + * @brief Animation overrides mini control panel + * + * Copyright (c) 2016, Cinder Roxley <cinder@sdf.org> + * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef AL_PANELAOMINI_H +#define AL_PANELAOMINI_H + +#include "llpanel.h" + +class LLComboBox; +//class LLUICtrl; + +class ALPanelAOMini final : public LLPanel +{ +public: + ALPanelAOMini(); + + BOOL postBuild() override; + +protected: + ~ALPanelAOMini(); + +private: + void onSelectSet(const LLSD& userdata); + void onClickSit(const LLSD& userdata); + void onClickNext(); + void onClickPrevious(); + void openAOFloater(); + void updateSetList(); + void onSetChanged(const std::string& set_name); + + LLComboBox* mSetList; + + boost::signals2::connection mReloadCallback; + boost::signals2::connection mSetChangedCallback; +}; + +#endif // LL_PANELAOMINI_H diff --git a/indra/newview/alpanelaopulldown.cpp b/indra/newview/alpanelaopulldown.cpp new file mode 100644 index 0000000000000000000000000000000000000000..02b47c70408cac429105b2430a95d321b569d17a --- /dev/null +++ b/indra/newview/alpanelaopulldown.cpp @@ -0,0 +1,40 @@ +/* + * @file alpanelaopulldown.cpp + * @brief Animation overrides flyout + * + * Copyright (c) 2014, Cinder Roxley <cinder@sdf.org> + * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "llviewerprecompiledheaders.h" +#include "alpanelaopulldown.h" + +ALPanelAOPulldown::ALPanelAOPulldown() + : LLPanelPulldown() +{ + buildFromFile("panel_ao_pulldown.xml"); +} + diff --git a/indra/newview/alpanelaopulldown.h b/indra/newview/alpanelaopulldown.h new file mode 100644 index 0000000000000000000000000000000000000000..3df6005bd3ffd05123ffb7c2caebe2c4fd1ae46f --- /dev/null +++ b/indra/newview/alpanelaopulldown.h @@ -0,0 +1,45 @@ +/* + * @file alpanelaopulldown.h + * @brief Animation overrides flyout + * + * Copyright (c) 2014-2017, Cinder Roxley <cinder@sdf.org> + * Copyright (C) 2020, Rye Mutt <rye@alchemyviewer.org> + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef AL_PANELAOFLYOUT_H +#define AL_PANELAOFLYOUT_H + +#include "llpanelpulldown.h" + +class LLFrameTimer; + +class ALPanelAOPulldown final : public LLPanelPulldown +{ +public: + ALPanelAOPulldown(); +}; + +#endif // LL_PANELAOFLYOUT_H diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index 7d6c6705e2819e666ce97e10e36db1743c7cdfde..c09055369772d337437ab21a9f8958f26411e2ff 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.xml b/indra/newview/app_settings/settings_per_account.xml index 358abc7786a852b2b896761a5ad0e2b89c201c28..f631dbaf29a2e60b0d2a7ee74f5f4f4a7b01688f 100644 --- a/indra/newview/app_settings/settings_per_account.xml +++ b/indra/newview/app_settings/settings_per_account.xml @@ -1,5 +1,9 @@ <llsd> <map> + <key>Include</key> + <array> + <string>settings_per_account_alchemy.xml</string> + </array> <key>RLVaLoginLastLocation</key> <map> <key>Comment</key> diff --git a/indra/newview/app_settings/settings_per_account_alchemy.xml b/indra/newview/app_settings/settings_per_account_alchemy.xml new file mode 100644 index 0000000000000000000000000000000000000000..9d0267fad7d821e00b67ec06b8a68aedaf204cba --- /dev/null +++ b/indra/newview/app_settings/settings_per_account_alchemy.xml @@ -0,0 +1,15 @@ +<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> + </map> +</llsd> diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index b5e6df70e00ef6dee510aa8b8b0552cca2d6e00c..646029e5de242dd065cc9c1bcb533714f49164ee 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -29,6 +29,7 @@ #include "pipeline.h" +#include "alaoengine.h" #include "llagent.h" #include "llanimationstates.h" #include "llfloatercamera.h" @@ -2320,6 +2321,7 @@ void LLAgentCamera::changeCameraToMouselook(BOOL animate) mCameraMode = CAMERA_MODE_MOUSELOOK; const U32 old_flags = gAgent.getControlFlags(); gAgent.setControlFlags(AGENT_CONTROL_MOUSELOOK); + ALAOEngine::getInstance()->inMouselook(true); if (old_flags != gAgent.getControlFlags()) { gAgent.setFlagsDirty(); @@ -2384,6 +2386,7 @@ void LLAgentCamera::changeCameraToFollow(BOOL animate) updateLastCamera(); mCameraMode = CAMERA_MODE_FOLLOW; + ALAOEngine::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); @@ -2464,6 +2467,7 @@ void LLAgentCamera::changeCameraToThirdPerson(BOOL animate) } updateLastCamera(); mCameraMode = CAMERA_MODE_THIRD_PERSON; + ALAOEngine::getInstance()->inMouselook(false); gAgent.clearControlFlags(AGENT_CONTROL_MOUSELOOK); } diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 5289e64d4f074c04d99732cdecee85eec86bf361..2dc9461a0d722dbe8d85f84012ed84629a0ae1fb 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -31,6 +31,7 @@ #include "lltransfersourceasset.h" #include "llavatarnamecache.h" // IDEVO +#include "alaoengine.h" #include "llagent.h" #include "llagentcamera.h" #include "llagentwearables.h" @@ -3527,7 +3528,14 @@ LLFolderType::EType LLFolderBridge::getPreferredType() const LLViewerInventoryCategory* cat = getCategory(); if(cat) { - preferred_type = cat->getPreferredType(); + const std::string& cat_name(cat->getName()); + if (cat_name == ROOT_AO_FOLDER) + preferred_type = LLFolderType::FT_ANIM_OVERRIDES; + else if (cat_name == "#Firestorm" || cat_name == "#Phoenix" + || cat_name == "#DV3" || cat_name == "#Kokua") + preferred_type = LLFolderType::FT_TOXIC; + else + preferred_type = cat->getPreferredType(); } return preferred_type; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 0ba2b8db0fcce313aebff3649bf4b453d514d738..ef5f0d19bb3e9657a1bad465a2b1cf7173bdbbdc 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -512,6 +512,39 @@ const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( return rv; } +const LLUUID LLInventoryModel::findCategoryUUIDForNameInRoot(std::string const& folder_name, bool create_folder, LLUUID const& root_id) +{ + LLUUID rv = LLUUID::null; + if (root_id.notNull()) + { + cat_array_t* cats = nullptr; + cats = get_ptr_in_map(mParentChildCategoryTree, root_id); + if (cats) + { + U32 count = cats->size(); + for (U32 i = 0; i < count; ++i) + { + if (cats->at(i)->getName() == folder_name) + { + LLUUID const& folder_id = cats->at(i)->getUUID(); + if (rv.isNull() || folder_id < rv) + { + rv = folder_id; + } + } + } + } + } + if (rv.isNull() && isInventoryUsable() && create_folder) + { + if (root_id.notNull()) + { + return createNewCategory(root_id, LLFolderType::FT_NONE, folder_name); + } + } + return rv; +} + // findCategoryUUIDForType() returns the uuid of the category that // specifies 'type' as what it defaults to containing. The category is // not necessarily only for that type. *NOTE: This will create a new @@ -562,6 +595,25 @@ const LLUUID LLInventoryModel::findLibraryCategoryUUIDForType(LLFolderType::ETyp return findCategoryUUIDForTypeInRoot(preferred_type, create_folder, gInventory.getLibraryRootFolderID()); } +const LLUUID LLInventoryModel::findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string& name) const +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + LLNameCategoryCollector has_name(name); + gInventory.collectDescendentsIf(parent_id, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + if (cat_array.empty()) + return LLUUID::null; + LLViewerInventoryCategory *cat = cat_array.at(0); + if (cat) + return cat->getUUID(); + LL_WARNS() << "null cat" << LL_ENDL; + return LLUUID::null; +} + // Convenience function to create a new category. You could call // updateCategory() with a newly generated UUID category, but this // version will take care of details like what the name should be diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index bd31b02a757a2c5eed8acf05e7cb97a3953d7f9a..cd2877d6f309a700ad666cdf8ec4f49a85fdcb5d 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -282,6 +282,10 @@ class LLInventoryModel bool create_folder, const LLUUID& root_id); + const LLUUID findCategoryUUIDForNameInRoot(std::string const& folder_name, + bool create_folder, + LLUUID const& root_id); + // Returns the uuid of the category that specifies 'type' as what it // defaults to containing. The category is not necessarily only for that type. // NOTE: If create_folder is true, this will create a new inventory category @@ -295,6 +299,10 @@ class LLInventoryModel // Returns user specified category for uploads, returns default id if there are no // user specified one or it does not exist, creates default category if it is missing. const LLUUID findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type); + + // Returns the uuid of the category if found, LLUUID::null is not + const LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, + const std::string& name) const; // Get whatever special folder this object is a child of, if any. const LLViewerInventoryCategory *getFirstNondefaultParent(const LLUUID& obj_id) const; diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp index 84d97efb995de5a2a28ca43ac6db788787d46afc..193b3540bc84869d2b366c717876269640dc0bbf 100644 --- a/indra/newview/llstatusbar.cpp +++ b/indra/newview/llstatusbar.cpp @@ -29,6 +29,7 @@ #include "llstatusbar.h" // viewer includes +#include "alpanelaopulldown.h" #include "alpanelquicksettingspulldown.h" #include "llagent.h" #include "llagentcamera.h" @@ -113,6 +114,7 @@ LLStatusBar::LLStatusBar(const LLRect& rect) mSGPacketLoss(NULL), mPanelPopupHolder(nullptr), mBtnQuickSettings(nullptr), + mBtnAO(nullptr), mBtnVolume(NULL), mBoxBalance(NULL), mBalance(0), @@ -186,8 +188,13 @@ 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 + mBtnVolume = getChild<LLButton>( "volume_btn" ); - mBtnVolume->setClickedCallback( onClickVolume, this ); + mBtnVolume->setClickedCallback(&LLStatusBar::onClickVolume, this ); mBtnVolume->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterVolume, this)); mMediaToggle = getChild<LLButton>("media_toggle_btn"); @@ -197,6 +204,7 @@ BOOL LLStatusBar::postBuild() LLHints::getInstance()->registerHintTarget("linden_balance", getChild<LLView>("balance_bg")->getHandle()); gSavedSettings.getControl("MuteAudio")->getSignal()->connect(boost::bind(&LLStatusBar::onVolumeChanged, this, _2)); + gSavedPerAccountSettings.getControl("AlchemyAOEnable")->getCommitSignal()->connect(boost::bind(&LLStatusBar::onAOStateChanged, this)); // Adding Net Stat Graph S32 x = getRect().getWidth() - 2; @@ -254,6 +262,11 @@ 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); + mPanelQuickSettingsPulldown = new ALPanelQuickSettingsPulldown(); addChild(mPanelQuickSettingsPulldown); mPanelQuickSettingsPulldown->setFollows(FOLLOWS_TOP | FOLLOWS_RIGHT); @@ -362,6 +375,7 @@ void LLStatusBar::setVisibleForMouselook(bool visible) getChild<LLUICtrl>("balance_bg")->setVisible(visible); mBoxBalance->setVisible(visible); mBtnQuickSettings->setVisible(visible); + mBtnAO->setVisible(visible); mBtnVolume->setVisible(visible); mMediaToggle->setVisible(visible); mSGBandwidth->setVisible(visible); @@ -579,6 +593,29 @@ void LLStatusBar::onMouseEnterQuickSettings() 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); + + mPanelNearByMedia->setVisible(FALSE); + mPanelVolumePulldown->setVisible(FALSE); + mPanelQuickSettingsPulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(TRUE); + //mPanelAvatarComplexityPulldown->setVisible(FALSE); +} + void LLStatusBar::onMouseEnterVolume() { LLButton* volbtn = getChild<LLButton>( "volume_btn" ); @@ -601,6 +638,7 @@ void LLStatusBar::onMouseEnterVolume() mPanelPresetsPulldown->setVisible(FALSE); mPanelNearByMedia->setVisible(FALSE); mPanelQuickSettingsPulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(FALSE); mPanelVolumePulldown->setVisible(TRUE); } @@ -626,10 +664,18 @@ void LLStatusBar::onMouseEnterNearbyMedia() mPanelPresetsPulldown->setVisible(FALSE); mPanelQuickSettingsPulldown->setVisible(FALSE); mPanelVolumePulldown->setVisible(FALSE); + mPanelAOPulldown->setVisible(FALSE); mPanelNearByMedia->setVisible(TRUE); } +// static +void LLStatusBar::onClickAOBtn(void* data) +{ + gSavedPerAccountSettings.set("AlchemyAOEnable", !gSavedPerAccountSettings.getBOOL("AlchemyAOEnable")); +} + +// static void LLStatusBar::onClickVolume(void* data) { // toggle the master mute setting @@ -654,6 +700,11 @@ void LLStatusBar::onClickMediaToggle(void* data) LLViewerMedia::getInstance()->setAllMediaPaused(pause); } +void LLStatusBar::onAOStateChanged() +{ + mBtnAO->setToggleState(gSavedPerAccountSettings.getBOOL("AlchemyAOEnable")); +} + BOOL can_afford_transaction(S32 cost) { return((cost <= 0)||((gStatusBar) && (gStatusBar->getBalance() >=cost))); diff --git a/indra/newview/llstatusbar.h b/indra/newview/llstatusbar.h index 307f42499636aa66dd53e1b5cb2dc47f80e3c3c4..ac807ec82f6c178668eaed49bd6cc7f4ac29d61b 100644 --- a/indra/newview/llstatusbar.h +++ b/indra/newview/llstatusbar.h @@ -41,6 +41,7 @@ class LLUICtrl; class LLUUID; class LLFrameTimer; class LLStatGraph; +class ALPanelAOPulldown; class ALPanelQuickSettingsPulldown; class LLPanelPresetsCameraPulldown; class LLPanelPresetsPulldown; @@ -104,10 +105,12 @@ class LLStatusBar final void onMouseEnterPresetsCamera(); void onMouseEnterPresets(); void onMouseEnterQuickSettings(); + void onMouseEnterAO(); void onMouseEnterVolume(); void onMouseEnterNearbyMedia(); void onClickScreen(S32 x, S32 y); + static void onClickAOBtn(void* data); static void onClickVolume(void* data); static void onClickMediaToggle(void* data); static void onClickBalance(void* data); @@ -122,6 +125,7 @@ class LLStatusBar final void updateMenuSearchPosition(); // depends onto balance position void updateBalancePanelPosition(); + void onAOStateChanged(); LLTextBox *mTextTime; @@ -132,6 +136,7 @@ class LLStatusBar final LLIconCtrl *mIconPresetsCamera; LLIconCtrl *mIconPresetsGraphic; LLButton *mBtnQuickSettings; + LLButton *mBtnAO; LLButton *mBtnVolume; LLTextBox *mBoxBalance; LLButton *mMediaToggle; @@ -145,6 +150,7 @@ class LLStatusBar final LLFrameTimer* mHealthTimer; LLPanelPresetsCameraPulldown* mPanelPresetsCameraPulldown; LLPanelPresetsPulldown* mPanelPresetsPulldown; + ALPanelAOPulldown* mPanelAOPulldown; ALPanelQuickSettingsPulldown* mPanelQuickSettingsPulldown; LLPanelVolumePulldown* mPanelVolumePulldown; LLPanelNearByMedia* mPanelNearByMedia; diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index ea4931c5c9248c8417bd4ac2826ff807b8a57627..24211307efc83863aef875a28d4e3d8136d54a49 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -30,6 +30,7 @@ #include "llfloaterreg.h" #include "llviewerfloaterreg.h" +#include "alfloaterao.h" #include "alfloaterparticleeditor.h" #include "alfloaterregiontracker.h" #include "llcommandhandler.h" @@ -390,6 +391,7 @@ void LLViewerFloaterReg::registerFloaters() // *NOTE: Please keep these alphabetized for easier merges + LLFloaterReg::add("ao", "floater_ao.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterAO>); LLFloaterReg::add("particle_editor", "floater_particle_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterParticleEditor>); LLFloaterReg::add("quick_settings", "floater_quick_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloater>); LLFloaterReg::add("region_tracker", "floater_region_tracker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterRegionTracker>); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index eb90c9dbdaa66b54ea2bd8c80a7300fdf06856e2..bfa4c3c0cf4efcc2517a21f814d3ddeb4c272ed1 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -37,6 +37,7 @@ #include "sound_ids.h" #include "raytrace.h" +#include "alaoengine.h" #include "llagent.h" // Get state values from here #include "llagentbenefits.h" #include "llagentcamera.h" @@ -2916,6 +2917,8 @@ void LLVOAvatar::idleUpdateLoadingEffect() { LL_INFOS("Avatar") << avString() << "self isFullyLoaded, mFirstFullyVisible" << LL_ENDL; LLAppearanceMgr::instance().onFirstFullyVisible(); + + ALAOEngine::instance().onLoginComplete(); } else { @@ -3532,11 +3535,12 @@ LLColor4 LLVOAvatar::getNameTagColor(bool is_friend) void LLVOAvatar::idleUpdateBelowWater() { F32 avatar_height = (F32)(getPositionGlobal().mdV[VZ]); + F32 water_height = getRegion()->getWaterHeight(); - F32 water_height; - water_height = getRegion()->getWaterHeight(); - - mBelowWater = avatar_height < water_height; + BOOL was_below_water = mBelowWater; + mBelowWater = avatar_height < water_height; + if (isSelf() && mBelowWater != was_below_water) + ALAOEngine::instance().checkBelowWater(mBelowWater); } void LLVOAvatar::slamPosition() @@ -5903,9 +5907,21 @@ LLUUID LLVOAvatar::remapMotionID(const LLUUID& id) BOOL LLVOAvatar::startMotion(const LLUUID& id, F32 time_offset) { LL_DEBUGS("Motion") << "motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; - - LLUUID remap_id = remapMotionID(id); - + + LLUUID remap_id; + if(isSelf()) + { + remap_id = ALAOEngine::getInstance()->override(id, true); + if(remap_id.isNull()) + remap_id = remapMotionID(id); + else + gAgent.sendAnimationRequest(remap_id, ANIM_REQUEST_START); + } + else + { + remap_id = remapMotionID(id); + } + if (remap_id != id) { LL_DEBUGS("Motion") << "motion resultant " << remap_id.asString() << " " << gAnimLibrary.animationName(remap_id) << LL_ENDL; @@ -5926,7 +5942,19 @@ BOOL LLVOAvatar::stopMotion(const LLUUID& id, BOOL stop_immediate) { LL_DEBUGS("Motion") << "Motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; - LLUUID remap_id = remapMotionID(id); + LLUUID remap_id; + if(isSelf()) + { + remap_id = ALAOEngine::getInstance()->override(id, false); + if(remap_id.isNull()) + remap_id = remapMotionID(id); + else + gAgent.sendAnimationRequest(remap_id, ANIM_REQUEST_STOP); + } + else + { + remap_id = remapMotionID(id); + } if (remap_id != id) { diff --git a/indra/newview/skins/default/textures/icons/move.png b/indra/newview/skins/default/textures/icons/move.png new file mode 100644 index 0000000000000000000000000000000000000000..1cce104e3fff8e843583d10f853cb70f5b999bf7 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/move.png differ diff --git a/indra/newview/skins/default/textures/icons/move_off.png b/indra/newview/skins/default/textures/icons/move_off.png new file mode 100644 index 0000000000000000000000000000000000000000..76d9476768ec23eb079244130a64b8072984ca52 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/move_off.png differ diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index b12246d97e60c4be950885298e999d867e3f6d89..836dabfe55284a8c602275b304a32d4d0c9da76a 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -63,6 +63,9 @@ with the same filename but different name <texture name="Audio_Off" file_name="icons/Audio_Off.png" preload="false" /> <texture name="Audio_Press" file_name="icons/Audio_Press.png" preload="false" /> + <texture name="Move" file_name="icons/move.png" preload="false" /> + <texture name="Move_Off" file_name="icons/move_off.png" preload="false" /> + <texture name="Avaline_Icon" file_name="icons/avaline_default_icon.jpg" preload="true" /> <texture name="BackArrow_Off" file_name="icons/BackArrow_Off.png" preload="false" /> diff --git a/indra/newview/skins/default/xui/en/floater_ao.xml b/indra/newview/skins/default/xui/en/floater_ao.xml new file mode 100644 index 0000000000000000000000000000000000000000..7fdd6f0b92137266a5af7579516b22bba1290b46 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_ao.xml @@ -0,0 +1,31 @@ +<?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="320" + min_width="200" + layout="topleft" + name="ao" + save_rect="true" + save_visibility="true" + single_instance="true" + 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> +</floater> diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 2d1bf04953ba755db84fed19876a1f0f56702b0a..cd9f3f3a95bfea5573d768c72ab96bc1c5d670f6 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -11781,6 +11781,166 @@ the region "[REGION]"? Auto-accepted [ITEM] from [NAME] and placed in inventory. </notification> + <notification + icon="alertmodal.tga" + name="NewAOSet" + type="alertmodal"> +Specify a name for the new AO set: +(The name may contain any ASCII character, except for ":" or "|") + <form name="form"> + <input name="message" type="text" default="true"> +New AO Set + </input> + <button + default="true" + index="0" + name="OK" + text="OK"/> + <button + index="1" + name="Cancel" + text="Cancel"/> + </form> + </notification> + + <notification + icon="alertmodal.tga" + name="NewAOCantContainNonASCII" + type="alertmodal"> +Could not create new AO set "[AO_SET_NAME]". +The name may only contain ASCII characters, excluding ":" and "|". + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="RenameAOMustBeASCII" + type="alertmodal"> +Could not rename AO set "[AO_SET_NAME]". +The name may only contain ASCII characters, excluding ":" and "|". + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + +<notification + icon="alertmodal.tga" + name="RemoveAOSet" + type="alertmodal"> +Remove AO set "[AO_SET_NAME]" from the list? + <usetemplate + name="okcancelbuttons" + notext="Cancel" + yestext="Remove"/> + </notification> + + <notification + icon="notifytip.tga" + name="AOForeignItemsFound" + type="alertmodal"> +The animation overrider found at least one item that did not belong in the configuration. Please check your "Lost and Found" folder for items that were moved out of the animation overrider configuration. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportSetAlreadyExists" + type="notifytip"> +An animation set with this name already exists. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportPermissionDenied" + type="notifytip"> +Insufficient permissions to read notecard. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportCreateSetFailed" + type="notifytip"> +Error while creating import set. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportDownloadFailed" + type="notifytip"> +Could not download notecard. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportNoText" + type="notifytip"> +Notecard is empty or unreadable. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportNoFolder" + type="notifytip"> +Couldn't find folder to read the animations. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportNoStatePrefix" + type="notifytip"> +Notecard line [LINE] has no valid [ state prefix. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportNoValidDelimiter" + type="notifytip"> +Notecard line [LINE] has no valid ] delimiter. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportStateNameNotFound" + type="notifytip"> +State name [NAME] not found. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportAnimationNotFound" + type="notifytip"> +Couldn't find animation [NAME]. Please make sure it's present in the same folder as the import notecard. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportInvalid" + type="notifytip"> +Notecard didn't contain any usable data. Aborting import. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportRetryCreateSet" + type="notifytip"> +Could not create import folder for animation set [NAME]. Retrying ... + </notification> + + <notification + icon="notifytip.tga" + name="AOImportAbortCreateSet" + type="notifytip"> +Could not create import folder for animation set [NAME]. Giving up. + </notification> + + <notification + icon="notifytip.tga" + name="AOImportLinkFailed" + type="notifytip"> +Creating animation link for animation "[NAME]" failed! + </notification> + <notification icon="alertmodal.tga" name="ParticleSaveChanges" diff --git a/indra/newview/skins/default/xui/en/panel_ao.xml b/indra/newview/skins/default/xui/en/panel_ao.xml new file mode 100644 index 0000000000000000000000000000000000000000..d07bc1a00fb6fb95b39510b30beba8e4ac1475fe --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_ao.xml @@ -0,0 +1,412 @@ +<?xml version="1.0" encoding="utf-8"?> +<panel + name="animation_overrider_outer_panel" + left="0" + top="0" + width="200" + height="314" + follows="all" + visible="true" + layout="topleft"> +<!-- Main Panel --> + <panel + name="animation_overrider_panel" + left="10" + top="4" + width="180" + height="306" + follows="all" + visible="true" + layout="topleft"> + <combo_box + name="ao_set_selection_combo" + tool_tip="Select animation set to edit." + left="0" + top="4" + 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"> + <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="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> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_ao_mini.xml b/indra/newview/skins/default/xui/en/panel_ao_mini.xml new file mode 100644 index 0000000000000000000000000000000000000000..24a8d4bf847e9fc3b87e6cf98e763c6116475236 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_ao_mini.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<panel + name="ao_mini" + class="ao_mini" + left="0" + top="0" + width="120" + height="70" + follows="all" + visible="true" + layout="topleft"> + <combo_box + height="23" + layout="topleft" + left="3" + right="-28" + top="1" + name="set_list" /> + <button + follows="top|right" + height="23" + image_overlay="Edit_Wrench" + label="" + layout="topleft" + left_pad="2" + name="ao_wrench" + tool_tip="Open the main AO controls" + top="1" + width="23"> + <button.commit_callback + function="AO.OpenFloater" /> + </button> + <button + name="ao_prev" + image_overlay="BackArrow_Off" + tool_tip="Switch to next animation of the current state." + left="2" + top_pad="2" + width="57" + height="20" + follows="all" + layout="topleft"> + <button.commit_callback + function="AO.PrevAnim" /> + </button> + <button + name="ao_next" + image_overlay="ForwardArrow_Off" + tool_tip="Switch to next animation of the current state." + left_pad="2" + top_delta="0" + width="57" + height="20" + follows="all" + layout="topleft"> + <button.commit_callback + function="AO.NextAnim" /> + </button> +</panel> \ No newline at end of file diff --git a/indra/newview/skins/default/xui/en/panel_ao_pulldown.xml b/indra/newview/skins/default/xui/en/panel_ao_pulldown.xml new file mode 100644 index 0000000000000000000000000000000000000000..c510946fb4e40a7dc1e69a8740132b5a8b634873 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_ao_pulldown.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + bg_opaque_image="Volume_Background" + bg_alpha_image="Volume_Background" + background_opaque="true" + background_visible="true" + layout="topleft" + width="120" + height="70" + name="ao_pulldown"> + <panel + name="ao_mini" + class="ao_mini" + filename="panel_ao_mini.xml" + layout="topleft" /> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_progress.xml b/indra/newview/skins/default/xui/en/panel_progress.xml index e77d097d5f6db9cbec8669a19ff03b7c44a178d0..2d87e94426dc610567dd9c34fd48bfb33ced5b79 100644 --- a/indra/newview/skins/default/xui/en/panel_progress.xml +++ b/indra/newview/skins/default/xui/en/panel_progress.xml @@ -163,7 +163,7 @@ line_spacing.pixels="2" name="logos_lbl" text_color="LoginProgressBoxTextColor"> - Second Life uses + [APP_NAME] uses </text> </layout_panel> </layout_stack> 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 b5130cc01a5bfbd1d8570723c3e19153aa57c9ee..86a5c2dcfe0e3f44404be8fcb1e7f3e64fb40c83 100644 --- a/indra/newview/skins/default/xui/en/panel_status_bar.xml +++ b/indra/newview/skins/default/xui/en/panel_status_bar.xml @@ -161,6 +161,16 @@ top="4" name="presets_icon_graphic" 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" @@ -170,7 +180,7 @@ image_pressed_selected="Play_Press" is_toggle="true" left_pad="5" - top="1" + top="2" name="media_toggle_btn" tool_tip="Start/Stop All Media (Music, Video, Web pages)" width="16" > diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index f400a07921a8b3619def9e7af48b80773a09517c..b2386afe837139ed069c9b2a3c291471384a4c5b 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -4117,6 +4117,7 @@ Try enclosing path to the editor with double quotes. <!-- commands --> <string name="Command_AboutLand_Label">About land</string> + <string name="Command_AnimationOverride_Label">AO</string> <string name="Command_Appearance_Label">Appearance</string> <string name="Command_Avatar_Label">Avatar</string> <string name="Command_Build_Label">Build</string>