From 5a09ab68671ca1341becd6e5fb928dcf5832f9d9 Mon Sep 17 00:00:00 2001
From: Rye Mutt <rye@alchemyviewer.org>
Date: Mon, 29 Jun 2020 21:01:52 -0400
Subject: [PATCH] Dump in ye olde viewer ao

---
 indra/newview/CMakeLists.txt                  |   10 +
 indra/newview/alaoengine.cpp                  | 2012 +++++++++++++++++
 indra/newview/alaoengine.h                    |  223 ++
 indra/newview/alaoset.cpp                     |  222 ++
 indra/newview/alaoset.h                       |  151 ++
 indra/newview/alchatcommand.cpp               |   76 +-
 indra/newview/alfloaterao.cpp                 |  740 ++++++
 indra/newview/alfloaterao.h                   |  146 ++
 indra/newview/alpanelaomini.cpp               |  132 ++
 indra/newview/alpanelaomini.h                 |   65 +
 indra/newview/alpanelaopulldown.cpp           |   40 +
 indra/newview/alpanelaopulldown.h             |   45 +
 indra/newview/app_settings/commands.xml       |   10 +
 .../app_settings/settings_per_account.xml     |    4 +
 .../settings_per_account_alchemy.xml          |   15 +
 indra/newview/llagentcamera.cpp               |    4 +
 indra/newview/llinventorybridge.cpp           |   10 +-
 indra/newview/llinventorymodel.cpp            |   52 +
 indra/newview/llinventorymodel.h              |    8 +
 indra/newview/llstatusbar.cpp                 |   53 +-
 indra/newview/llstatusbar.h                   |    6 +
 indra/newview/llviewerfloaterreg.cpp          |    2 +
 indra/newview/llvoavatar.cpp                  |   44 +-
 .../skins/default/textures/icons/move.png     |  Bin 0 -> 15346 bytes
 .../skins/default/textures/icons/move_off.png |  Bin 0 -> 16102 bytes
 .../skins/default/textures/textures.xml       |    3 +
 .../skins/default/xui/en/floater_ao.xml       |   31 +
 .../skins/default/xui/en/notifications.xml    |  160 ++
 .../newview/skins/default/xui/en/panel_ao.xml |  412 ++++
 .../skins/default/xui/en/panel_ao_mini.xml    |   59 +
 .../default/xui/en/panel_ao_pulldown.xml      |   16 +
 .../skins/default/xui/en/panel_progress.xml   |    2 +-
 .../skins/default/xui/en/panel_status_bar.xml |   12 +-
 .../newview/skins/default/xui/en/strings.xml  |    1 +
 34 files changed, 4716 insertions(+), 50 deletions(-)
 create mode 100644 indra/newview/alaoengine.cpp
 create mode 100644 indra/newview/alaoengine.h
 create mode 100644 indra/newview/alaoset.cpp
 create mode 100644 indra/newview/alaoset.h
 create mode 100644 indra/newview/alfloaterao.cpp
 create mode 100644 indra/newview/alfloaterao.h
 create mode 100644 indra/newview/alpanelaomini.cpp
 create mode 100644 indra/newview/alpanelaomini.h
 create mode 100644 indra/newview/alpanelaopulldown.cpp
 create mode 100644 indra/newview/alpanelaopulldown.h
 create mode 100644 indra/newview/app_settings/settings_per_account_alchemy.xml
 create mode 100644 indra/newview/skins/default/textures/icons/move.png
 create mode 100644 indra/newview/skins/default/textures/icons/move_off.png
 create mode 100644 indra/newview/skins/default/xui/en/floater_ao.xml
 create mode 100644 indra/newview/skins/default/xui/en/panel_ao.xml
 create mode 100644 indra/newview/skins/default/xui/en/panel_ao_mini.xml
 create mode 100644 indra/newview/skins/default/xui/en/panel_ao_pulldown.xml

diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 5e0679095eb..429881f911d 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 00000000000..cb12ea3911c
--- /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 00000000000..5f1bea9d8d4
--- /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 00000000000..f3c9b2c0c97
--- /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 00000000000..db84ee88e70
--- /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 3c723014a0f..41dfa26d5d9 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 00000000000..61ed286ab8c
--- /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 00000000000..a51446200da
--- /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 00000000000..4d5b046849c
--- /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 00000000000..57be0ccef7c
--- /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 00000000000..02b47c70408
--- /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 00000000000..3df6005bd3f
--- /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 7d6c6705e28..c0905536977 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 358abc7786a..f631dbaf29a 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 00000000000..9d0267fad7d
--- /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 b5e6df70e00..646029e5de2 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 5289e64d4f0..2dc9461a0d7 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 0ba2b8db0fc..ef5f0d19bb3 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 bd31b02a757..cd2877d6f30 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 84d97efb995..193b3540bc8 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 307f4249963..ac807ec82f6 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 ea4931c5c92..24211307efc 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 eb90c9dbdaa..bfa4c3c0cf4 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
GIT binary patch
literal 15346
zcmeI3dvF`Y9mm(WyxajoaGQ|OsiL?{l193Rtk+c%l`RL0LB``K!O7qz=ew1CSh|z%
z&XMdwOA|;73{7JKr8E;D6G)k9=)<&ShP3Hpm`>s;E~OzcZp))&OfsZETQ|v2#@*9%
zElXK<hCf#`l78C#{=R$r+25}2pS!=a<L0XxmNftXu8Outy3qGx>wU##=yT)C-@YAv
zU72p*WB~B#>#TPjczo|t0Q~!vc(>UdyH$|1lv9+oUg*rG(nt+J;HGR^lm?(_>xF%a
z8nnOv;t{(|k%RWz-7z+nZiW3y`-l$Lk95SPkpams+iwar1hN7$NI_GyWm8Gj5VApg
z&R0OO)y&v!If*$Cw1=$$ZQZd>TdSr+o7?GjNG#9UynZL=c6+`2S{u)D%?!&ioXf%a
zh2~~~Wo?C*J=B1rfG#J5uE@H=aOhXi-fx;|fnhS4j5FhMYI+~T`Tc%|<r$uLAceyi
zR!uSMP>q#&lcG-q8j`N0O+`~}mao{W4Vgi^-5RL$D$FaDE)Ap_1$KxclNHkp=VX~0
zNwL_)(^9FbnTFW~%QLGwpb;NVL#7KF+K?_mBwtzW5)89#8=6u9(E^Oxwn5dbY9&i0
zpY)KP%z>6A1}0$&r5K0;S9LDwe$CX3eys+;Qg=;^rjn?^!s@A+2D3_yG^;0{maD1!
zQU_2W3#~d7O-+w$S~8R`*UsYA+ge+5<!M_L6D38prnEZ1R3?>6r|btKq6tH&wA~Jl
zb8y}`#|f-YV7(63iwYbw$tx>7Vw$WZhKn8^2g{?q5$9Ne_XwOH_rR2u9%w7dqA4!O
z1=r5?iN%DdYM7!b!Du9e=I&G!S#Ysl4-Y-8!^L^rj%Jtab@)UXO;!>U%?Z)N_ex@J
zl5^ANSw}Q!$SS3RwT!I8Zg&gk<-={QK0n8`u|5|c@r8YUHq3|p5k6eB#Zu?nLS5Mg
z<#oE2%I)&Bs2kAQJVASL_mqx>LMi07pP<a2YT<gkwXbJhYWaGQc_7~{*PN-3<cy`3
zol6+XQ?00Mg#I>NK?l2-#8_D?`2S$8Vu4)*azn$kD+by@!`S%NyshJPJsh0dTI@(F
z3tX7e@}NUa5GCv24jCd^+yIk1=!$LSi^^wM1LXx()^uUzJ1@QV7@2;kTHC$WR?S=G
zRJBbEEn#Lv9fteRj#$`Pl=q#RSbG4eUsU^`9Ab*>iVc-10mfRqq%wEy@~v~1v{;7o
zOCTUiLPFD1q8U<BVjpDEYF~gUH<rf?l>QHdRxPRN=uCqlSAeP3jAdE>1XhJ3SYoa$
zxBxS+ty14@?f>LZPVePe^kO*cvyh5+SEg>dcq-mqnOe@=pekmFFN{{vjAdDWS9uug
z(JdMbdE9Q7JHS*nS7vl7S(xmODCkHvtc}9?3OSV>RTDSWO2j$}iTP5%Zl-dTl5a(~
zYUnPJDcvT{Euzxy{Dgz@j6Pvw+>Q={(;&ta7ZDLIptx`v#F*kDBEkg}7fyp1Q(Qzu
zxPaoqX%J(Ii--spP+T|-VoY%n5#a)g3#UPhDJ~)+TtIQ*G>9?9MMQ)PC@!1^F{ZeP
zh;RYLh0`F$6c-T@E}*z@8pN35A|k>C6c<i|7*kwCM7V(B!f6m=ii?N{7f@U{4Ps1j
z5fR}6iVLSfj43W6B3wXm;WUUb#YIGf3n(s}1~I0%h=_0j#f8%##uOJ35iX#(a2mvz
z;vyo#1r!%fgBVj>L`1lN;=*YV<BQ^IC_i`!RrIuF20d1J&AFK|^hl;nYVV2xF!BWe
z?%4srg<14{8h{}dfHPYG5FP_yi8i|BrF8(*9g9Z7@$Bn+_XG!~IvaPsG4<NeVEt?H
z)(vmnnEmrLY|rL^{DC|RUVXMRGxPQ@cU)<{`_9viZ@;;C)nn?@*ULZs%;D<~pWm_c
zz5~1MlOro4FQ47-y!PZ}{#&-ZF#guQ>3zGtdi8eOm$RPs`X|>efAr(+wr+Q%-}}VG
z?6Jhy8`?K!FTe9=n=fx#^7GV353T$A-Hq@5W~bQ*eiYwuyzW~vf8ocQ52<ZEUu&#8
z|3l^VvEHAYk6iKLZwL1tc{YBgWyA5?c27KaaO0}RzkTnS;aJz`;T5Y>Q(uX%dE^Q3
z@00F3e=&7))2aUEWe?x+mrG8ieTUb4cl=1*f58X;>G<PT);HL5Li2q8n@zLd*)uko
z{Ol#sV;^Skdj9Ig<9}V;a?d+Y!A-mFTQqs=jbrt1o|gI5r*!|Nzg`ZQx*HC>dRkjP
z@F_4e^6|=}uF*d=|6!o##5vD#&za8$-#_=!!6q#*@vrDDzhj+scfGLW2k|v`uUOr4
zVkY{leG}8p=;UehnR9i@PVtV>J&(fA)q|sF*_Te9-SS}KOXlMp2dA6hp^a0!E<H0d
zGClsvz4i-_9C&Zri;1<1j$SwV#aHhAXJ8_8!?eBOd4Ax5{qIE{8eP=u-Q6Df$FjlS
fw>)_0>-AvE_|nN4agob<DmU8J5qYj<>-PTvg<|bS

literal 0
HcmV?d00001

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
GIT binary patch
literal 16102
zcmeI3d2kcw6~NanP{)MBz)XMukp-S$lGPrPENPJik_E9FWQ=W!-9W=?wUW1%v_e{A
zVIYM-00(Svib|lkO)xlxgsa4%;b=HYLsE{CA(&u7(>AFKNeP6jBoy~s9hUsr+A!1n
z(dn#abiD6<@4bD$@4ct}XKRZJpX!%BJRL!heg*l~V(>jke5dsRpFccRKLdO{<jXG)
zAV}ZA;yVS|uyH7YjQ4S+WkQ*43dQgq746{dtSad7f!zpVoDlTUOeHHQ?5vCP<|@D3
z|Bh0@IdYXVv^L!4%VR6J{2D)7Qd3yU)KoGChjM}`-58{R0}m_EilE2s4N$>cW!NtT
zev8YPQW4%FROTwpVn9Witw@o_`&ornr9~N>BosP>iqL9xI`T;ci4z(OCon>d5(Y}6
zp>SLgeJM@p;Fr<wa8kwA$<c5?%2ieff{((m>gsA$wOYmdT^M077%-f~ND>7*(7-&e
zKnGE8V06Su%#W1~Fn-P_aJ*L``laoBm5{4cih(*`(Y!pq&OqKklpUam1!*5fsBkQa
zk<BJ;=JE7s84!xucxF8Y43y6Eu~;!1;H&%$3$~B$=@J6Mq`4raD53{&G-)pD6?$~#
zU^@Nys{HOSXa|F_Zq@@#0)PV1V=lf5Uf=^2d=h}2>ZBM2&Y6UTs7Y$Z2Dv0=q9$S%
zuBpgT8$lscd484_cz-F+yG@aDEs9;eA}=poo{F(Hn&G@+O4&vXHj0-{+#hSD1=a*g
zTZ<9|O6W=ng2MF_u0wGhDDVU)k)4na8}Hzp^I|@7P@Dw4QA*$xnL`m>d?f6IeSlVU
z&;tEHUK00q^<%S91>S%_dl|OCY67{dIL<*CoNAKIaS*6ZLo=wxX~$7oO){ujOB*zH
zht{sw)8Qn$ns;+;<(Vq6lz{8V3~}%baNk)V6pO1#k~DzXptI9}rOtrTPA!452HK&`
z(HcmHorncWaMnE#>*waOj>&%B6YhH-?GLbGJLM{4{nR-qqQwzzNQ&#8yjiQY5IRyf
zDNk=8h)K9!O<MJ4y#Y6qW`mV98?kPt@#_K3jS)3SHI}=G)dBL3de(oN=LFNZ=S~dq
zRH0f2hcVU9fl*Jp6IemP&B=p<ojtoqxQNJ}d@cZbYF<KoF>i6;maucWuErE@?g0;`
z^LW6Rqi9AP<)#1)4p-!!uol0lh=mv^vZ04FwQE=d0si4oJ;~Zp4WZYgc8!5XorIpT
z;~E{AqX%`?%{+OG>I&8?4uFK_C2~Oj#s)ydRd`o&Cjs6mRMURe>;gUVVACn$x2s|D
z089n#b+HZ;7RxQR0Gk*w@$lT-|Kiy_*f9F<&aTVg{l7T7$cc7s$Jhwz-o8c$LpgcB
zhZannhjy`;&+9T`@x^$(7~{h`k9YHa@DIhB)JCkQ<peA7e_fnTpwQ`XOM`anwg&Gn
z6KA)(D!gAu;^2i5;%C$}Q4O1xOPeODVbi$fbT22E$Y?ZZIl)T2`JlouO27>_Ys%4T
z)mkG4FT)l^T#$8_Svl|r84!mYp^w_YiXIKilNlx`qK1)DNVqi)cX?zL+<1d~eysDR
zzuN%l9IoHxk9bDkrB}$890UR)Ey}p0`9NA37X(CFlyOP(fwVF%2#B;O<C5kBX=Pjx
z5NT1yCCvxY%D5mP(xQw@nh&IvaX~<&MH!bgA4n_Xf`CYiGA?O8kXFV80g)DET+)0X
zt&9r-A}z|er1?Nv85aaZT9k1~^MSN7E(nOUDC3gm18HSk5D;lm#wE=M(#p6XAkw0Y
zOPUX)m2p8pq(vE*G#^MS<AQ)li!v^0K9E+%1p$#3Wn9vHAgzoG0wOKSxTN_&S{WAv
zL|T+_N%MiUGA;;+v?$|}<^ySETo4dxQ4&{r{4rYA3!a^=1`o+TicT*F57;W0{9+q|
z)MO#Zq9q7&w*!1%Mvy8TL9Wb35b9+Fd4zxI*<+ItB=uT>)m$39uy*aV{E}%y-|cYi
zZ<`gUIlb|m`{bjc;xR<VAJZ+pnr^;oUi|9<r8WCsn--g1II{%HJ^QpZgPV3Rt=9fZ
z{{{beb@_UWWlWj9C@`bZwc~Qt`2DXv5;{_Td&z+Qk1g-Gdw;=C`)62Y54NPuJ=9mx
zXOSmoXd7jjHss}N{Hb-JQ0}zpx2`XnU9)uHGokuRmoBxh+TG&QAGq-G#|v-Vsm*yh
z?RsnN*hT75t6xh)8rY96UcC6l`OCj~bIGBFJ6ref-L~!h$!UgNyLPSF)%MTK)$a!Q
z)^p>=joWeh<jG^hn9o-cjl^4{`Y&%CK79D4)Ug*@TAG`ipLlUo{)Xm--%}t1Gna1N
zw{PFY_8SLNuBA-WZ8>Ys?!85GQ2VRXGY%Q1_D)@V9jH$fja~cAl`~C_KYY4*`wKTl
zHxGXN+EM2d3n+p<G3P$AXjxtB{o@(T7q{-+JMf`>_mK1Mx0<F><I%fKO}jQ1uj!kP
zWUkrx%RcKipIsRWxl(5F8J017%bV7oyM6QK0{`1HuiiO&^n=3<8OLjT{k*Yo%Es~E
z-|~9j9m@w~wzvOcCpDtZ^m@yXi5G5txpVvWgMB8R`TFY>j-kih?xmG;GOpf#ZdLD8
zMd9NcM~+;xbdBoxS9Nb%o_nXGe%rQh^zGU6_4ht=752Y9^FZ2`?fu@~u<XEL^QyY~
z)bjEb?SCKs<2`rXR3=|p`FR$CKm4~n+3Pj>_CHmP{Az1S(S^2#Chq(M{fa}^@Ike`
z)}@`<`_SEg-*3yx%1UoHvMoQt+8a%t=7zLO{rY8&TAgQ_H|YKC&-hT4W#rIBgX%V9
zy~4Mhd;ahzca9u6GGbQipQqYvp-QcGe(jUXHw_!s{N7*fj;-U6Lpi2;=CKJDgh>7L
zLliyMG4RF7P0yzMHf6+sK1=q5LZRUL##!k<;~rktu=~o~m1nQ~<V42Gk1~Hxrkpza
zlv3UHk}W&en%z?J1D9_8t%m%8%l`773O%^<jh$1SJKmTwF!So5Pkal<kb6Rh8-KUD
d<Fi&|^MskBKd4DxCq9W@FsabG!!rB%{{Z!J|JVQk

literal 0
HcmV?d00001

diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml
index b12246d97e6..836dabfe552 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 00000000000..7fdd6f0b921
--- /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 2d1bf04953b..cd9f3f3a95b 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 &quot;[REGION]&quot;?
     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 &quot;Lost and Found&quot; 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 00000000000..d07bc1a00fb
--- /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 00000000000..24a8d4bf847
--- /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 00000000000..c510946fb4e
--- /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 e77d097d5f6..2d87e94426d 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 b5130cc01a5..86a5c2dcfe0 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 f400a07921a..b2386afe837 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>
-- 
GitLab