diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 8694d28390804ebb97a3021f97ad99dfa71e7526..6cc236faae09e2bfd50104cb1e6f28afd1e822b7 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -718,6 +718,7 @@ set(viewer_SOURCE_FILES
     rlvinventory.cpp
     rlvextensions.cpp
     rlvfloaters.cpp
+    rlvmodifiers.cpp
     rlvui.cpp
     )
 
@@ -1343,6 +1344,7 @@ set(viewer_HEADER_FILES
     rlvinventory.h
     rlvextensions.h
     rlvfloaters.h
+    rlvmodifiers.h
     rlvui.h
     roles_constants.h
     VertexCache.h
diff --git a/indra/newview/rlvdefines.h b/indra/newview/rlvdefines.h
index 2a175ce659ac38e79846e353c855ceac69695250..f354bd002d0e47e85f7e0519948ef542977c3c5b 100644
--- a/indra/newview/rlvdefines.h
+++ b/indra/newview/rlvdefines.h
@@ -237,6 +237,7 @@ enum ERlvBehaviour {
 	RLV_BHVR_SETOVERLAY_TEXTURE,	// Changes the overlay texture
 	RLV_BHVR_SETOVERLAY_TINT,		// Changes the tint that's applied to the overlay texture
 	RLV_BHVR_SETOVERLAY_TOUCH,		// Block world interaction (=touching) based on the alpha channel of the overlay texture
+	RLV_BHVR_SETOVERLAY_TWEEN,		// Animate between the current overlay settings and the supplied values
 
 	RLV_BHVR_COUNT,
 	RLV_BHVR_UNKNOWN
diff --git a/indra/newview/rlvhandler.cpp b/indra/newview/rlvhandler.cpp
index 4dd0cc6b313435d8d844a0ee6618dad6a8363929..d998375444d7249acc5b98fbbbe26ccf06dcd261 100644
--- a/indra/newview/rlvhandler.cpp
+++ b/indra/newview/rlvhandler.cpp
@@ -60,6 +60,7 @@
 #include "rlvhelper.h"
 #include "rlvinventory.h"
 #include "rlvlocks.h"
+#include "rlvmodifiers.h"
 #include "rlvui.h"
 #include "rlvextensions.h"
 
@@ -2595,6 +2596,32 @@ ERlvCmdRet RlvForceHandler<RLV_BHVR_SETCAM_MODE>::onCommand(const RlvCommand& rl
 	return RLV_RET_SUCCESS;
 }
 
+// Handles: @setoverlay_tween:[<alpha>];[<tint>];<duration>=force
+template<> template<>
+ERlvCmdRet RlvForceHandler<RLV_BHVR_SETOVERLAY_TWEEN>::onCommand(const RlvCommand& rlvCmd)
+{
+	std::vector<std::string> optionList;
+	if ( (!RlvCommandOptionHelper::parseStringList(rlvCmd.getOption(), optionList)) || (3 != optionList.size()) )
+		return RLV_RET_FAILED_OPTION;
+
+	// Parse the duration first (required param)
+	float tweenDuration = .0f;
+	if (!RlvCommandOptionHelper::parseOption(optionList[2], tweenDuration))
+		return RLV_RET_FAILED_OPTION;
+
+	// Process the overlay alpha tween (if there is one and it is a valid value)
+	float overlayAlpha = .0f;
+	if (RlvCommandOptionHelper::parseOption(optionList[0], overlayAlpha))
+		RlvBehaviourModifierAnimator::instance().addTween(rlvCmd.getObjectID(), RLV_MODIFIER_OVERLAY_ALPHA, RlvBehaviourModifierAnimationType::Lerp, overlayAlpha, tweenDuration);
+
+	// Process the overlay tint tween (if there is one and it is a valid value)
+	LLVector3 overlayColor;
+	if (RlvCommandOptionHelper::parseOption(optionList[1], overlayColor))
+		RlvBehaviourModifierAnimator::instance().addTween(rlvCmd.getObjectID(), RLV_MODIFIER_OVERLAY_TINT, RlvBehaviourModifierAnimationType::Lerp, overlayColor, tweenDuration);
+
+	return RLV_RET_SUCCESS;
+}
+
 // Checked: 2010-08-30 (RLVa-1.2.1c) | Modified: RLVa-1.2.1c
 ERlvCmdRet RlvHandler::onForceWear(const LLViewerInventoryCategory* pFolder, U32 nFlags) const
 {
diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp
index 5e2e2084ae03590bcd9700bbeb409dbeb7b4efb4..22256d1da4bcf358eb5b0d24800e51521ae6549b 100644
--- a/indra/newview/rlvhelper.cpp
+++ b/indra/newview/rlvhelper.cpp
@@ -25,6 +25,7 @@
 #include "rlvhelper.h"
 #include "rlvhandler.h"
 #include "rlvinventory.h"
+#include "rlvmodifiers.h"
 
 #include <boost/algorithm/string.hpp>
 
@@ -215,6 +216,7 @@ RlvBehaviourDictionary::RlvBehaviourDictionary()
 				RLV_MODIFIER_OVERLAY_TINT, new RlvBehaviourModifier("Overlay - Tint", LLVector3(1.0f, 1.0f, 1.0f), false, new RlvBehaviourModifier_Comp()));
 	addModifier(new RlvBehaviourGenericProcessor<RLV_OPTION_NONE_OR_MODIFIER>("setoverlay_touch", RLV_BHVR_SETOVERLAY_TOUCH, RlvBehaviourInfo::BHVR_EXPERIMENTAL),
 				RLV_MODIFIER_OVERLAY_TOUCH, new RlvBehaviourModifier("Overlay - Touch", true, true, new RlvBehaviourModifier_Comp()));
+	addEntry(new RlvForceProcessor<RLV_BHVR_SETOVERLAY_TWEEN>("setoverlay_tween", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
 
 	//
 	// Force-wear
@@ -482,6 +484,7 @@ void RlvBehaviourModifier::clearValues(const LLUUID& idRlvObj)
 	                              [&idRlvObj](const RlvBehaviourModifierValueTuple& modValue) {
 									return (std::get<1>(modValue) == idRlvObj) && (std::get<2>(modValue) == RLV_BHVR_UNKNOWN);
 	                              }), m_Values.end());
+	RlvBehaviourModifierAnimator::instance().clearTweens(idRlvObj);
 	if (origCount != m_Values.size())
 	{
 		onValueChange();
@@ -501,6 +504,11 @@ bool RlvBehaviourModifier::hasValue() const {
 	return (!m_Values.empty()) ? std::get<1>(m_Values.front()) == m_pValueComparator->m_idPrimaryObject : false;
 }
 
+bool RlvBehaviourModifier::hasValue(const LLUUID& idRlvObj) const
+{
+	return m_Values.end() != std::find_if(m_Values.begin(), m_Values.end(), [&idRlvObj](const RlvBehaviourModifierValueTuple& cmpValue) { return std::get<1>(cmpValue) == idRlvObj; });
+}
+
 void RlvBehaviourModifier::removeValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idRlvObj, ERlvBehaviour eBhvr)
 {
 	if ( (modValue.which() == m_DefaultValue.which()) )
diff --git a/indra/newview/rlvhelper.h b/indra/newview/rlvhelper.h
index 67165de11ba4abfa2575fa519449022b4ad569e9..97116894d92fd62568b9712c512e6ed37ac38670 100644
--- a/indra/newview/rlvhelper.h
+++ b/indra/newview/rlvhelper.h
@@ -284,6 +284,7 @@ class RlvBehaviourModifier
 	const RlvBehaviourModifierValue& getValue() const { return (hasValue()) ? std::get<0>(m_Values.front()) : m_DefaultValue; }
 	template<typename T> const T&    getValue() const { return boost::get<T>(getValue()); }
 	bool                             hasValue() const;
+	bool                             hasValue(const LLUUID& idRlvObj) const;
 	void                             removeValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idRlvObj, ERlvBehaviour eBhvr = RLV_BHVR_UNKNOWN);
 	void                             setValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idRlvObj);
 	void                             setPrimaryObject(const LLUUID& idPrimaryObject);
diff --git a/indra/newview/rlvmodifiers.cpp b/indra/newview/rlvmodifiers.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8df8ca294c5233f92c906b08fd24268eac5bdf47
--- /dev/null
+++ b/indra/newview/rlvmodifiers.cpp
@@ -0,0 +1,124 @@
+/**
+ *
+ * Copyright (c) 2009-2018, Kitty Barnett
+ *
+ * The source code in this file is provided to you under the terms of the
+ * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
+ * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
+ *
+ * By copying, modifying or distributing this software, you acknowledge that
+ * you have read and understood your obligations described above, and agree to
+ * abide by those obligations.
+ *
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "rlvmodifiers.h"
+
+// ====================================================================================
+// RlvBehaviourModifierAnimator
+//
+
+RlvBehaviourModifierAnimator::~RlvBehaviourModifierAnimator()
+{
+	if (m_pTimer)
+	{
+		delete m_pTimer;
+		m_pTimer = nullptr;
+	}
+}
+
+void RlvBehaviourModifierAnimator::addTween(const LLUUID& idObject, ERlvBehaviourModifier eBhvrMod, RlvBehaviourModifierAnimationType eAnimType, const RlvBehaviourModifierValue& endValue, float nDuration)
+{
+	// Make sure we don't run two animations on the same modifier for the same object
+	const auto itTween = std::find_if(m_Tweens.begin(), m_Tweens.end(), [&idObject, eBhvrMod](const RlvBehaviourModifierTween& t) { return t.idObject == idObject && t.eBhvrMod == eBhvrMod; });
+	if (m_Tweens.end() != itTween)
+		m_Tweens.erase(itTween);
+
+	if (RlvBehaviourModifier* pBhvrModifier = RlvBehaviourDictionary::instance().getModifier(eBhvrMod))
+	{
+		RlvBehaviourModifierTween newTween;
+		newTween.idObject = idObject;
+		newTween.eBhvrMod = eBhvrMod;
+		newTween.eAnimType = RlvBehaviourModifierAnimationType::Lerp;
+		newTween.nStartTime = LLTimer::getElapsedSeconds();
+		newTween.nDuration = nDuration;
+		newTween.startValue = pBhvrModifier->getValue();
+		newTween.endValue = endValue;
+		if (newTween.startValue.which() == newTween.endValue.which())
+		{
+			if (!m_pTimer)
+				m_pTimer = new AnimationTimer();
+			m_Tweens.emplace_back(std::move(newTween));
+		}
+	}
+}
+
+void RlvBehaviourModifierAnimator::clearTweens(const LLUUID& idObject, ERlvBehaviourModifier eBhvrMod)
+{
+	m_Tweens.erase(std::remove_if(m_Tweens.begin(), m_Tweens.end(),
+	               [&idObject, eBhvrMod](const RlvBehaviourModifierTween& cmpTween)
+	               {
+		               return cmpTween.idObject == idObject && ((cmpTween.eBhvrMod == eBhvrMod) || (RLV_MODIFIER_UNKNOWN == eBhvrMod));
+	               }), m_Tweens.end());
+}
+
+// ====================================================================================
+// RlvBehaviourModifierAnimator timer
+//
+
+#define ANIMATION_FREQUENCY 10
+
+RlvBehaviourModifierAnimator::AnimationTimer::AnimationTimer()
+	: LLEventTimer(1.f / ANIMATION_FREQUENCY)
+{
+}
+
+BOOL RlvBehaviourModifierAnimator::AnimationTimer::tick()
+{
+	RlvBehaviourModifierAnimator& modAnimatior = RlvBehaviourModifierAnimator::instance();
+	const double curTime = LLTimer::getElapsedSeconds();
+
+	const auto activeTweens = modAnimatior.m_Tweens;
+	for (const auto& curTween : activeTweens)
+	{
+		if (RlvBehaviourModifier* pBhvrModifier = RlvBehaviourDictionary::instance().getModifier(curTween.eBhvrMod))
+		{
+			// Update the modifier's value
+			float curFactor = (curTime - curTween.nStartTime) / curTween.nDuration;
+			if (curFactor < 1.0)
+			{
+				const auto& valueType = curTween.startValue.type();
+				if (typeid(float) == valueType)
+					pBhvrModifier->setValue(lerp(boost::get<float>(curTween.startValue), boost::get<float>(curTween.endValue), curFactor), curTween.idObject);
+				else if (typeid(int) == valueType)
+					pBhvrModifier->setValue(lerp(boost::get<int>(curTween.startValue), boost::get<int>(curTween.endValue), curFactor), curTween.idObject);
+				else if (typeid(LLVector3) == valueType)
+					pBhvrModifier->setValue(lerp(boost::get<LLVector3>(curTween.startValue), boost::get<LLVector3>(curTween.endValue), curFactor), curTween.idObject);
+			}
+			else
+			{
+				pBhvrModifier->setValue(curTween.endValue, curTween.idObject);
+				auto itTween = std::find_if(modAnimatior.m_Tweens.begin(), modAnimatior.m_Tweens.end(),
+											[&curTween](const RlvBehaviourModifierTween& t)
+											{
+												// NOTE: implementation leak - taking advantage of the fact that we know there can only be one active tween per object/modifier/type combnination
+												return t.idObject == curTween.idObject && t.eBhvrMod == curTween.eBhvrMod && t.eAnimType == curTween.eAnimType;
+											});
+				modAnimatior.m_Tweens.erase(itTween);
+			}
+		}
+	}
+
+	if (modAnimatior.m_Tweens.empty())
+	{
+		modAnimatior.m_pTimer = nullptr;
+		return true;
+	}
+	return false;
+}
+
+ // ====================================================================================
diff --git a/indra/newview/rlvmodifiers.h b/indra/newview/rlvmodifiers.h
new file mode 100644
index 0000000000000000000000000000000000000000..5f36d310e2cce438d30a6481848263be32046c85
--- /dev/null
+++ b/indra/newview/rlvmodifiers.h
@@ -0,0 +1,75 @@
+/**
+ *
+ * Copyright (c) 2009-2018, Kitty Barnett
+ *
+ * The source code in this file is provided to you under the terms of the
+ * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
+ * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
+ *
+ * By copying, modifying or distributing this software, you acknowledge that
+ * you have read and understood your obligations described above, and agree to
+ * abide by those obligations.
+ *
+ */
+
+#pragma once
+
+#include "llsingleton.h"
+#include "rlvhelper.h"
+
+// ====================================================================================
+// RlvBehaviourModifierAnimator - Helper types
+//
+
+enum class RlvBehaviourModifierAnimationType { Lerp };
+
+struct RlvBehaviourModifierTween
+{
+	LLUUID idObject;
+	ERlvBehaviourModifier eBhvrMod;
+	RlvBehaviourModifierAnimationType eAnimType;
+	double nStartTime;
+	float nDuration;
+	RlvBehaviourModifierValue startValue;
+	RlvBehaviourModifierValue endValue;
+};
+
+// ====================================================================================
+// RlvBehaviourModifierAnimator - A class to animate behaviour modifiers
+//
+
+class RlvBehaviourModifierAnimator : public LLSingleton<RlvBehaviourModifierAnimator>
+{
+	friend class AnimationTimer;
+	LLSINGLETON_EMPTY_CTOR(RlvBehaviourModifierAnimator);
+public:
+	~RlvBehaviourModifierAnimator() override;
+
+	/*
+	 * Member functions
+	 */
+public:
+	void addTween(const LLUUID& idObject, ERlvBehaviourModifier eBhvrMod, RlvBehaviourModifierAnimationType eAnimType, const RlvBehaviourModifierValue& endValue, float nDuration);
+	void clearTweens(const LLUUID& idObject) { clearTweens(idObject, RLV_MODIFIER_UNKNOWN); }
+	void clearTweens(const LLUUID& idObject, ERlvBehaviourModifier eBhvrMod);
+
+	/*
+	 * Animation timer
+	 */
+	class AnimationTimer : LLEventTimer
+	{
+	public:
+		AnimationTimer();
+		BOOL tick() override;
+	};
+
+	/*
+	 * Member variables
+	 */
+	std::list<struct RlvBehaviourModifierTween> m_Tweens;
+	AnimationTimer*                             m_pTimer = nullptr;
+};
+
+// ====================================================================================