diff --git a/.gitattributes b/.gitattributes
index 9fe54bf3d886f7405c63311d3655e4729f22654c..b47749b4fa1e237526365f5ad35a5d7ce1d7422b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -13,6 +13,8 @@
 # Documents
 *.txt      text
 *.xml      text
+indra/tools/vstool/README.txt -text
+indra/newview/installers/windows/FILES_ARE_UNICODE_UTF-16LE.txt -text
 
 # Graphics
 *.png      binary diff=exif
diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp
index 306c73292057c05f4da4fe5b2e481d6a8413cdb2..02dcd47a47c95de49ea6e3099d1059e513891828 100644
--- a/indra/llinventory/llsettingssky.cpp
+++ b/indra/llinventory/llsettingssky.cpp
@@ -40,7 +40,7 @@ namespace
     const LLUUID IMG_HALO("12149143-f599-91a7-77ac-b52a3c0f59cd");
 }
 
-namespace {
+//namespace {
     LLQuaternion convert_azimuth_and_altitude_to_quat(F32 azimuth, F32 altitude)
     {
         F32 sinTheta = sin(azimuth);
@@ -64,7 +64,7 @@ namespace {
 
         return quat;
     }
-}
+//}
 
 static LLTrace::BlockTimerStatHandle FTM_BLEND_SKYVALUES("Blending Sky Environment");
 static LLTrace::BlockTimerStatHandle FTM_RECALCULATE_SKYVALUES("Recalculate Sky");
diff --git a/indra/llmath/v3dmath.h b/indra/llmath/v3dmath.h
index 4938273d5bccf66af385b6dde824478da5e9d4b8..61feecc3eee821006c4be14dc59ef4ac3ee51086 100644
--- a/indra/llmath/v3dmath.h
+++ b/indra/llmath/v3dmath.h
@@ -115,6 +115,9 @@ class LLVector3d
 		friend LLVector3d operator*(const F64 k, const LLVector3d& a);				// Return a times scaler k
 		friend bool operator==(const LLVector3d& a, const LLVector3d& b);		// Return a == b
 		friend bool operator!=(const LLVector3d& a, const LLVector3d& b);		// Return a != b
+// [RLVa:KB] - RlvBehaviourModifierCompMin/Max
+		friend bool operator<(const LLVector3 &a, const LLVector3 &b);		// Return a < b
+// [/RLVa:KB]
 
 		friend const LLVector3d& operator+=(LLVector3d& a, const LLVector3d& b);	// Return vector a + b
 		friend const LLVector3d& operator-=(LLVector3d& a, const LLVector3d& b);	// Return vector a minus b
@@ -395,6 +398,17 @@ inline bool operator!=(const LLVector3d& a, const LLVector3d& b)
 			||(a.mdV[2] != b.mdV[2]));
 }
 
+// [RLVa:KB] - RlvBehaviourModifierCompMin/Max
+inline bool operator<(const LLVector3d& lhs, const LLVector3d& rhs)
+{
+	return (lhs.mdV[0] < rhs.mdV[0]
+			|| (lhs.mdV[0] == rhs.mdV[0]
+				&& (lhs.mdV[1] < rhs.mdV[1]
+					|| ((lhs.mdV[1] == rhs.mdV[1])
+						&& lhs.mdV[2] < rhs.mdV[2]))));
+}
+// [/RLVa:KB]
+
 inline const LLVector3d& operator+=(LLVector3d& a, const LLVector3d& b)
 {
 	a.mdV[0] += b.mdV[0];
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 08e220ba577fe94f17f036dfa31d714ec4d7e288..59f449a74dc75d06d735921cf1e4333ce0ff7566 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -739,6 +739,7 @@ set(viewer_SOURCE_FILES
     noise.cpp
     pipeline.cpp
     rlvactions.cpp
+    rlvenvironment.cpp
     rlvhandler.cpp
     rlvhelper.cpp
     rlvcommon.cpp
@@ -1382,6 +1383,7 @@ set(viewer_HEADER_FILES
     noise.h
     pipeline.h
     rlvactions.h
+    rlvenvironment.h
     rlvdefines.h
     rlvhandler.h
     rlvhelper.h
diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml
index 06acd82cf6de7a3116d6c8c24da63ec2257c6534..e8b76f67b98b57edcbf146c778418280452f7e23 100644
--- a/indra/newview/app_settings/commands.xml
+++ b/indra/newview/app_settings/commands.xml
@@ -287,6 +287,8 @@
          tooltip_ref="Command_Environments_Tooltip"
          execute_function="Floater.ToggleOrBringToFront"
          execute_parameters="my_environments"
+         is_enabled_function="RLV.EnableIfNot"
+         is_enabled_parameters="setenv"
          is_running_function="Floater.IsOpen"
          is_running_parameters="my_environments"
            />
diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp
index c107c4ed038ed2ca755e9f4ebe4818695ecafaa0..b5e6df70e00ef6dee510aa8b8b0552cca2d6e00c 100644
--- a/indra/newview/llagentcamera.cpp
+++ b/indra/newview/llagentcamera.cpp
@@ -214,17 +214,20 @@ void LLAgentCamera::init()
 	
 	mCameraPreset = (ECameraPreset) gSavedSettings.getU32("CameraPresetType");
 
-	mCameraOffsetInitial = gSavedSettings.getControl("CameraOffsetRearView");
-	mFocusOffsetInitial = gSavedSettings.getControl("FocusOffsetRearView");
-	
-	// [RLVa:KB] - Checked: RLVa-2.0.0
-	mCameraOffsetInitial[CAMERA_RLV_SETCAM_VIEW] = gSavedSettings.declareVec3("CameraOffsetRLVaView", LLVector3(mCameraOffsetInitial[CAMERA_PRESET_REAR_VIEW]->getDefault()), "Declared in code", LLControlVariable::PERSIST_NO);
-	mCameraOffsetInitial[CAMERA_RLV_SETCAM_VIEW]->setHiddenFromSettingsEditor(true);
-// [/RLVa:KB]
-
-// [RLVa:KB] - Checked: RLVa-2.0.0
-	mFocusOffsetInitial[CAMERA_RLV_SETCAM_VIEW] = gSavedSettings.declareVec3("FocusOffsetRLVaView", LLVector3(mFocusOffsetInitial[CAMERA_PRESET_REAR_VIEW]->getDefault()), "Declared in code", LLControlVariable::PERSIST_NO);
-	mFocusOffsetInitial[CAMERA_RLV_SETCAM_VIEW]->setHiddenFromSettingsEditor(true);
+//	mCameraOffsetInitial = gSavedSettings.getControl("CameraOffsetRearView");
+//	mFocusOffsetInitial = gSavedSettings.getControl("FocusOffsetRearView");
+// [RLVa:KB] - @setcam_eyeoffset, @setcam_focusoffset and @setcam_eyeoffsetscale
+	mCameraOffsetInitialControl = gSavedSettings.getControl("CameraOffsetRearView");
+	mFocusOffsetInitialControl = gSavedSettings.getControl("FocusOffsetRearView");
+	if (RlvActions::isRlvEnabled())
+	{
+		mRlvCameraOffsetInitialControl = gSavedSettings.declareVec3("CameraOffsetRLVaView", LLVector3::zero, "Declared in code", LLControlVariable::PERSIST_NO);
+		mRlvCameraOffsetInitialControl->setHiddenFromSettingsEditor(true);
+		mRlvCameraOffsetScaleControl = gSavedSettings.declareF32("CameraOffsetScaleRLVa", 0.0f, "Declared in code", LLControlVariable::PERSIST_NO);
+		mRlvCameraOffsetScaleControl->setHiddenFromSettingsEditor(true);
+		mRlvFocusOffsetInitialControl = gSavedSettings.declareVec3d("FocusOffsetRLVaView", LLVector3d::zero, "Declared in code", LLControlVariable::PERSIST_NO);
+		mRlvFocusOffsetInitialControl->setHiddenFromSettingsEditor(true);
+	}
 // [/RLVa:KB]
 
 	mCameraCollidePlane.clearVec();
@@ -996,7 +999,10 @@ void LLAgentCamera::cameraOrbitIn(const F32 meters)
 {
 	if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON)
 	{
-		F32 camera_offset_dist = llmax(0.001f, getCameraOffsetInitial().magVec() * mCameraOffsetScale);
+// [RLVa:KB] - @setcam_eyeoffsetscale
+		F32 camera_offset_dist = llmax(0.001f, getCameraOffsetInitial().magVec() * getCameraOffsetScale());
+// [/RLVa:KB]
+//		F32 camera_offset_dist = llmax(0.001f, getCameraOffsetInitial().magVec() * gSavedSettings.getF32("CameraOffsetScale"));
 		
 		mCameraZoomFraction = (mTargetCameraDistance - meters) / camera_offset_dist;
 
@@ -1700,7 +1706,10 @@ LLVector3d LLAgentCamera::calcThirdPersonFocusOffset()
 		agent_rot *= ((LLViewerObject*)(gAgentAvatarp->getParent()))->getRenderRotation();
 	}
 
-	focus_offset = convert_from_llsd<LLVector3d>(mFocusOffsetInitial->get(), TYPE_VEC3D, "");
+//	focus_offset = convert_from_llsd<LLVector3d>(mFocusOffsetInitial->get(), TYPE_VEC3D, "");
+// [RLVa:KB] - @setcam_focusoffset
+	focus_offset = getFocusOffsetInitial();
+// [/RLVa:KB]
 	return focus_offset * agent_rot;
 }
 
@@ -1838,7 +1847,10 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit)
 		}
 		else
 		{
-			local_camera_offset = mCameraZoomFraction * getCameraOffsetInitial() * mCameraOffsetScale;
+// [RLVa:KB] - @setcam_eyeoffsetscale
+			local_camera_offset = mCameraZoomFraction * getCameraOffsetInitial() * getCameraOffsetScale();
+// [/RLVa:KB]
+//			local_camera_offset = mCameraZoomFraction * getCameraOffsetInitial() * gSavedSettings.getF32("CameraOffsetScale");
 			
 			// are we sitting down?
 			if (isAgentAvatarValid() && gAgentAvatarp->getParent())
@@ -2032,7 +2044,10 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit)
 		// Check focus distance limits
 		if ( (fCamOriginDistClamped) && (!fCamAvDistLocked) )
 		{
-			const LLVector3 offsetCameraLocal = mCameraZoomFraction * getCameraOffsetInitial() * mCameraOffsetScale;
+//			const LLVector3 offsetCameraLocal = mCameraZoomFraction * getCameraOffsetInitial() * gSavedSettings.getF32("CameraOffsetScale");
+// [RLVa:KB] - @setcam_eyeoffsetscale
+			const LLVector3 offsetCameraLocal = mCameraZoomFraction * getCameraOffsetInitial() * getCameraOffsetScale();
+// [/RLVa:KB]
 			const LLVector3d offsetCamera(gAgent.getFrameAgent().rotateToAbsolute(offsetCameraLocal));
 			const LLVector3d posFocusCam = frame_center_global + head_offset + offsetCamera;
 			if (clampCameraPosition(camera_position_global, posFocusCam, nCamOriginDistLimitMin, nCamOriginDistLimitMax))
@@ -2139,13 +2154,26 @@ bool LLAgentCamera::isJoystickCameraUsed()
 
 LLVector3 LLAgentCamera::getCameraOffsetInitial()
 {
-	return convert_from_llsd<LLVector3>(mCameraOffsetInitial->get(), TYPE_VEC3, "");
+// [RLVa:KB] - @setcam_eyeoffset
+	return convert_from_llsd<LLVector3>( (ECameraPreset::CAMERA_RLV_SETCAM_VIEW != mCameraPreset) ? mCameraOffsetInitialControl->get() : mRlvCameraOffsetInitialControl->get(), TYPE_VEC3, "");
+// [/RLVa:KB]
+//	return convert_from_llsd<LLVector3>(mCameraOffsetInitial->get(), TYPE_VEC3, "");
 }
 
 LLVector3d LLAgentCamera::getFocusOffsetInitial()
 {
-	return convert_from_llsd<LLVector3d>(mFocusOffsetInitial->get(), TYPE_VEC3D, "");
+// [RLVa:KB] - @setcam_focusoffset
+	return convert_from_llsd<LLVector3d>( (ECameraPreset::CAMERA_RLV_SETCAM_VIEW != mCameraPreset) ? mFocusOffsetInitialControl->get() : mRlvFocusOffsetInitialControl->get(), TYPE_VEC3D, "");
+// [/RLVa:KB]
+//	return convert_from_llsd<LLVector3d>(mFocusOffsetInitial->get(), TYPE_VEC3D, "");
+}
+
+// [RLVa:KB] - @setcam_eyeoffsetscale
+F32 LLAgentCamera::getCameraOffsetScale() const
+{
+	return gSavedSettings.getF32( (ECameraPreset::CAMERA_RLV_SETCAM_VIEW != mCameraPreset) ? "CameraOffsetScale" : "CameraOffsetScaleRLVa");
 }
+// [/RLVa:KB]
 
 F32 LLAgentCamera::getCameraMaxZoomDistance()
 {
@@ -2197,10 +2225,16 @@ void LLAgentCamera::handleScrollWheel(S32 clicks)
 		{
 			F32 camera_offset_initial_mag = getCameraOffsetInitial().magVec();
 			
-			F32 current_zoom_fraction = mTargetCameraDistance / (camera_offset_initial_mag * mCameraOffsetScale);
+//			F32 current_zoom_fraction = mTargetCameraDistance / (camera_offset_initial_mag * gSavedSettings.getF32("CameraOffsetScale"));
+// [RLVa:KB] - @setcam_eyeoffsetscale
+			F32 current_zoom_fraction = mTargetCameraDistance / (camera_offset_initial_mag * getCameraOffsetScale());
+// [/RLVa:KB]
 			current_zoom_fraction *= 1.f - pow(ROOT_ROOT_TWO, clicks);
 			
-			cameraOrbitIn(current_zoom_fraction * camera_offset_initial_mag * mCameraOffsetScale);
+// [RLVa:KB] - @setcam_eyeoffsetscale
+			cameraOrbitIn(current_zoom_fraction * camera_offset_initial_mag * getCameraOffsetScale());
+// [/RLVa:KB]
+//			cameraOrbitIn(current_zoom_fraction * camera_offset_initial_mag * gSavedSettings.getF32("CameraOffsetScale"));
 		}
 		else
 		{
@@ -2532,22 +2566,27 @@ void LLAgentCamera::changeCameraToCustomizeAvatar()
 
 void LLAgentCamera::switchCameraPreset(ECameraPreset preset)
 {
-// [RLVa:KB] - Checked: RLVa-2.0.0
+// [RLVa:KB] - @setcam family
 	if (RlvActions::isRlvEnabled())
 	{
-		// Don't allow changing away from the our view if an object is restricting it
+		// Don't allow changing away from our view if an object is restricting it
 		if (RlvActions::isCameraPresetLocked())
 			preset = CAMERA_RLV_SETCAM_VIEW;
 
-		// Don't reset anything if our view is already current
-		if ( (CAMERA_RLV_SETCAM_VIEW == preset) && (CAMERA_RLV_SETCAM_VIEW == mCameraPreset) )
-			return;
-
-		// Reset our view when switching away
-		if (CAMERA_RLV_SETCAM_VIEW != preset)
+		if (CAMERA_RLV_SETCAM_VIEW == preset)
 		{
-			mCameraOffsetInitial[CAMERA_RLV_SETCAM_VIEW]->resetToDefault();
-			mFocusOffsetInitial[CAMERA_RLV_SETCAM_VIEW]->resetToDefault();
+			if (CAMERA_RLV_SETCAM_VIEW == mCameraPreset)
+			{
+				// Don't reset anything if our view is already current
+				return;
+			}
+			else
+			{
+				// When switching to our view, copy the current values
+				mRlvCameraOffsetInitialControl->setDefaultValue(convert_to_llsd(getCameraOffsetInitial()));
+				mRlvFocusOffsetInitialControl->setDefaultValue(convert_to_llsd(getFocusOffsetInitial()));
+				mRlvCameraOffsetScaleControl->setDefaultValue(getCameraOffsetScale());
+			}
 		}
 	}
 // [/RLVa:KB]
diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h
index e8b49e330f20fc887612fa827921c1b524447830..61b74c9932d7e663f47ee8f9998f7d72b23127a7 100644
--- a/indra/newview/llagentcamera.h
+++ b/indra/newview/llagentcamera.h
@@ -60,9 +60,11 @@ enum ECameraPreset
 
 	/** Current view when a preset is saved */
 	CAMERA_PRESET_CUSTOM,
-	
-	/** Current view when a preset is saved */
-	CAMERA_PRESET_CUSTOM
+
+// [RLVa:KB] - @setcam_eyeoffset and @setcam_focusoffset
+	/* Used by RLVa */
+	CAMERA_RLV_SETCAM_VIEW,
+// [/RLVa:KB]
 };
 
 //------------------------------------------------------------------------
@@ -114,9 +116,17 @@ class LLAgentCamera
 	// Preset
 	//--------------------------------------------------------------------
 public:
+// [RLVa:KB] - @setcam family
+	/** Determines default camera offset scale depending on the current camera preset */
+	ECameraPreset getCameraPreset() const { return mCameraPreset; }
+// [/RLVa:KB]
 	void switchCameraPreset(ECameraPreset preset);
 	/** Determines default camera offset depending on the current camera preset */
 	LLVector3 getCameraOffsetInitial();
+// [RLVa:KB] - @setcam_eyeoffsetscale
+	/** Determines default camera offset scale depending on the current camera preset */
+	F32 getCameraOffsetScale() const;
+// [/RLVa:KB]
 	/** Determines default focus offset depending on the current camera preset */
 	LLVector3d getFocusOffsetInitial();
 
@@ -135,10 +145,24 @@ class LLAgentCamera
 	ECameraPreset mCameraPreset; 
 
 	/** Initial camera offset */
-	LLPointer<LLControlVariable> mCameraOffsetInitial;
+//	LLPointer<LLControlVariable> mCameraOffsetInitial;
+// [RLVa:KB] - @setcam_eyeoffset
+	// Renamed to catch their uses
+	LLPointer<LLControlVariable> mCameraOffsetInitialControl;
+	LLPointer<LLControlVariable> mRlvCameraOffsetInitialControl;
+// [/RLVa:KB]
+
+// [RLVa:KB] - @setcam_eyeoffsetscale
+	LLPointer<LLControlVariable> mRlvCameraOffsetScaleControl;
+// [/RLVa:KB]
 
 	/** Initial focus offset */
-	LLPointer<LLControlVariable> mFocusOffsetInitial;
+//	LLPointer<LLControlVariable> mFocusOffsetInitial;
+// [RLVa:KB] - @setcam_focusoffset
+	// Renamed to catch their uses
+	LLPointer<LLControlVariable> mFocusOffsetInitialControl;
+	LLPointer<LLControlVariable> mRlvFocusOffsetInitialControl;
+// [/RLVa:KB]
 
 	LLQuaternion mInitSitRot;
 
diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp
index d8588528c03a1816aaf2c50eb317da8779a07a1a..0adc7a703a3d30bd40bd6e99bd1306f530a3f2b6 100644
--- a/indra/newview/llaisapi.cpp
+++ b/indra/newview/llaisapi.cpp
@@ -452,6 +452,11 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht
 					AISUpdate::parseUUIDArray(result, "_created_categories", ids);
 				}
 				break;
+			case UPDATECATEGORY:
+				{
+					AISUpdate::parseUUIDArray(result, "_updated_categories", ids);
+				}
+				break;
 			default:
 				break;
 		}
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index b50b590433cf18bf5b28f59ad56fed3440d42a35..edd1cd4f9568bd6a1e0e1dd8cfe28063abc45754 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -2241,7 +2241,9 @@ void errorCallback(const std::string &error_string)
 	LLError::crashAndLoop(error_string);
 #endif // LL_RELEASE_WITH_DEBUG_INFO && LL_WINDOWS
 // [/SL:KB]
+//#ifndef SHADER_CRASH_NONFATAL
 //	LLError::crashAndLoop(error_string);
+//#endif
 }
 
 void LLAppViewer::initLoggingAndGetLastDuration()
@@ -5479,6 +5481,10 @@ void LLAppViewer::disconnectViewer()
 	LLAppearanceMgr::instance().setAttachmentInvLinkEnable(false);
 // [/SL:KB]
 
+// [RLVa:KB] - Checked: RLVa-2.3 (Housekeeping)
+	SUBSYSTEM_CLEANUP(RlvHandler);
+// [/RLVa:KB]
+
 	gAgentWearables.cleanup();
 	gAgentCamera.cleanup();
 	// Also writes cached agent settings to gSavedSettings
diff --git a/indra/newview/llenvironment.cpp b/indra/newview/llenvironment.cpp
index 342ee3ccf51ed19c6e230bf305e1f0f3af47cf72..555836ed34a790d08143379b9bd0d0f903718571 100644
--- a/indra/newview/llenvironment.cpp
+++ b/indra/newview/llenvironment.cpp
@@ -65,6 +65,10 @@
 #include "llviewergenericmessage.h"
 #include "llexperiencelog.h"
 
+// [RLVa:KB] - Checked: RLVa-2.4 (@setenv)
+#include "rlvactions.h"
+// [/RLVa:KB]
+
 //=========================================================================
 namespace
 {
@@ -1057,6 +1061,13 @@ bool LLEnvironment::getIsMoonUp() const
 //-------------------------------------------------------------------------
 void LLEnvironment::setSelectedEnvironment(LLEnvironment::EnvSelection_t env, LLSettingsBase::Seconds transition, bool forced)
 {
+// [RLVa:KB] - Checked: RLVa-2.4 (@setenv)
+    if ( (!RlvActions::canChangeEnvironment()) && (LLEnvironment::ENV_EDIT != env) )
+    {
+        return;
+    }
+// [/RLVa:KB]
+
     mSelectedEnvironment = env;
     updateEnvironment(transition, forced);
 }
diff --git a/indra/newview/llfloatercamera.cpp b/indra/newview/llfloatercamera.cpp
index d574f1433f764e051d6195df8b3fe0d069dc0b9c..941902c1faf91c5befee220b48895a81723aeb85 100644
--- a/indra/newview/llfloatercamera.cpp
+++ b/indra/newview/llfloatercamera.cpp
@@ -45,6 +45,9 @@
 #include "llhints.h"
 #include "lltabcontainer.h"
 #include "llvoavatarself.h"
+// [RLVa:KB] - @setcam
+#include "rlvactions.h"
+// [/RLVa:KB]
 
 static LLDefaultChildRegistry::Register<LLPanelCameraItem> r("panel_camera_item");
 
@@ -543,6 +546,13 @@ void LLFloaterCamera::onClickCameraItem(const LLSD& param)
 /*static*/
 void LLFloaterCamera::switchToPreset(const std::string& name)
 {
+// [RLVa:KB] - @setcam family
+	if (RlvActions::isCameraPresetLocked())
+	{
+		return;
+	}
+// [/RLVa:KB]
+
 	sFreeCamera = false;
 	clear_camera_tool();
 	if (PRESETS_REAR_VIEW == name)
diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp
index de5032fb841b18eb56fd916763fc5649b0c6fbab..c2589178e5c4543042137fea8741f563f97acb58 100644
--- a/indra/newview/llfloaterpreference.cpp
+++ b/indra/newview/llfloaterpreference.cpp
@@ -114,7 +114,6 @@
 #include "llweb.h"
 // [RLVa:KB] - Checked: 2010-03-18 (RLVa-1.2.0a)
 #include "rlvactions.h"
-#include "rlvhandler.h"
 // [/RLVa:KB]
 
 #include "lllogininstance.h"        // to check if logged in yet
@@ -1593,7 +1592,11 @@ void LLFloaterPreference::refreshEnabledState()
 	}
 	else
 	{
-		ctrl_wind_light->setEnabled(TRUE);
+// [RLVa:KB] - Checked: 2010-03-18 (RLVa-1.2.0a) | Modified: RLVa-0.2.0a
+		// "Atmospheric Shaders" can't be disabled - but can be enabled - under @setenv=n
+		ctrl_wind_light->setEnabled( (RlvActions::canChangeEnvironment()) || (!gSavedSettings.getBOOL("WindLightUseAtmosShaders")));
+// [/RLVa:KB]
+//		ctrl_wind_light->setEnabled(TRUE);
 	}
 
 	//Deferred/SSAO/Shadows
@@ -1620,7 +1623,7 @@ void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledState()
 	LLTextBox* reflections_text = getChild<LLTextBox>("ReflectionsText");
 
 // [RLVa:KB] - Checked: 2013-05-11 (RLVa-1.4.9)
-	if (rlv_handler_t::isEnabled())
+	if (RlvActions::isRlvEnabled())
 	{
 		getChild<LLUICtrl>("do_not_disturb_response")->setEnabled(!RlvActions::hasBehaviour(RLV_BHVR_SENDIM));
 	}
@@ -1672,11 +1675,11 @@ void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledState()
     LLCheckBoxCtrl* ctrl_wind_light = getChild<LLCheckBoxCtrl>("WindLightUseAtmosShaders");
     LLSliderCtrl* sky = getChild<LLSliderCtrl>("SkyMeshDetail");
     LLTextBox* sky_text = getChild<LLTextBox>("SkyMeshDetailText");
-	//	ctrl_wind_light->setEnabled(TRUE);
 // [RLVa:KB] - Checked: 2010-03-18 (RLVa-1.2.0a) | Modified: RLVa-0.2.0a
 	// "Atmospheric Shaders" can't be disabled - but can be enabled - under @setenv=n
-	ctrl_wind_light->setEnabled(((!gRlvHandler.hasBehaviour(RLV_BHVR_SETENV)) || (!gSavedSettings.getBOOL("WindLightUseAtmosShaders"))) );
+	ctrl_wind_light->setEnabled( (RlvActions::canChangeEnvironment()) || (!gSavedSettings.getBOOL("WindLightUseAtmosShaders")));
 // [/RLVa:KB]
+//    ctrl_wind_light->setEnabled(TRUE);
     sky->setEnabled(TRUE);
     sky_text->setEnabled(TRUE);
 
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index 2871d2b62afc47334d2b9d946582f4b647c607af..5289e64d4f074c04d99732cdecee85eec86bf361 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -85,6 +85,7 @@
 #include "llparcel.h"
 
 #include "llenvironment.h"
+
 // [RLVa:KB] - Checked: 2011-05-22 (RLVa-1.3.1)
 #include "rlvactions.h"
 #include "rlvhandler.h"
diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp
index bf4e6b6542605e3e2a2f4b9af2c44ebeb8166936..eff069ae0ae8d15d230bf1e7eb25ae71516c5f45 100644
--- a/indra/newview/llinventoryfunctions.cpp
+++ b/indra/newview/llinventoryfunctions.cpp
@@ -381,7 +381,10 @@ void update_all_marketplace_count()
     return;
 }
 
-void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name)
+//void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name)
+// [RLVa:KB] - Checked: RLVa-2.3 (Give-to-#RLV)
+void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name, LLPointer<LLInventoryCallback> cb)
+// [/RLVa:KB]
 {
 	LLViewerInventoryCategory* cat;
 
@@ -395,7 +398,10 @@ void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::s
 
 	LLSD updates;
 	updates["name"] = new_name;
-	update_inventory_category(cat_id, updates, NULL);
+// [RLVa:KB] - Checked: RLVa-2.3 (Give-to-#RLV)
+	update_inventory_category(cat_id, updates, cb);
+// [/RLVa:KB]
+//	update_inventory_category(cat_id, updates, NULL);
 }
 
 void copy_inventory_category(LLInventoryModel* model,
diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h
index fd106bc2d855b3730b34b22159673ea0a4968087..cb44d0966de86c0d1fb65fb6bbe19298ca20ccf8 100644
--- a/indra/newview/llinventoryfunctions.h
+++ b/indra/newview/llinventoryfunctions.h
@@ -68,7 +68,10 @@ void update_marketplace_category(const LLUUID& cat_id, bool perform_consistency_
 // Nudge all listing categories to signal that their marketplace status changed
 void update_all_marketplace_count();
 
-void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name);
+// [RLVa:KB] - Checked: RLVa-2.3 (Give-to-#RLV)
+void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name, LLPointer<LLInventoryCallback> cb = nullptr);
+// [/RLVa:KB]
+//void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name);
 
 void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id = LLUUID::null, bool move_no_copy_items = false);
 
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index f9ed01839333da3b9fb7017d7516253fe35ae913..007fb0e33958d14f542a32bf0a78670194f0cd3a 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -76,6 +76,7 @@
 #include "llslurl.h"
 #include "llstartup.h"
 // [RLVa:KB] - Checked: 2015-12-27 (RLVa-1.5.0)
+#include "rlvactions.h"
 #include "rlvcommon.h"
 // [/RLVa:KB]
 
@@ -142,6 +143,15 @@ static bool handleAvatarHoverOffsetChanged(const LLSD& newvalue)
 
 static bool handleSetShaderChanged(const LLSD& newvalue)
 {
+// [RLVa:KB] - @setenv
+	if ( (!RlvActions::canChangeEnvironment()) && (LLFeatureManager::getInstance()->isFeatureAvailable("WindLightUseAtmosShaders")) && (!gSavedSettings.getBOOL("WindLightUseAtmosShaders")) )
+	{
+		gSavedSettings.setBOOL("WindLightUseAtmosShaders", TRUE);
+		return true;
+	}
+// [/RLVa:KB]
+
+
 	// changing shader level may invalidate existing cached bump maps, as the shader type determines the format of the bump map it expects - clear and repopulate the bump cache
 	gBumpImageList.destroyGL();
 	gBumpImageList.restoreGL();
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 51dc178fa8ee5854eb9eeb66b336659953a55297..b44d1431294e2c5af551fbd082609047cd38f090 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -7369,7 +7369,6 @@ BOOL object_selected_and_point_valid(const LLSD& sdParam)
 BOOL object_is_wearable()
 {
 	if (!isAgentAvatarValid())
-	if (!object_selected_and_point_valid(LLSD(0)))
 	{
 		return FALSE;
 	}
@@ -8946,8 +8945,8 @@ class LLWorldEnvSettings : public view_listener_t
 
 	bool handleEvent(const LLSD& userdata)
 	{
-// [RLVa:KB] - Checked: 2010-03-18 (RLVa-1.2.0a) | Modified: RLVa-1.0.0g
-		if (gRlvHandler.hasBehaviour(RLV_BHVR_SETENV))
+// [RLVa:KB] - @setenv
+		if (!RlvActions::canChangeEnvironment())
 			return true;
 // [/RLVa:KB]
 
@@ -9789,11 +9788,8 @@ void initialize_menus()
 
 // [RLVa:KB] - Checked: RLVa-2.0.0
 	enable.add("RLV.MainToggleVisible", boost::bind(&rlvMenuMainToggleVisible, _1));
-	if (RlvActions::isRlvEnabled())
-	{
-		enable.add("RLV.CanShowName", boost::bind(&rlvMenuCanShowName));
-		enable.add("RLV.EnableIfNot", boost::bind(&rlvMenuEnableIfNot, _2));
-	}
+	enable.add("RLV.CanShowName", boost::bind(&rlvMenuCanShowName));
+	enable.add("RLV.EnableIfNot", boost::bind(&rlvMenuEnableIfNot, _2));
 // [/RLVa:KB]
 
 	ALViewerMenu::initialize_menus();
diff --git a/indra/newview/rlvactions.cpp b/indra/newview/rlvactions.cpp
index f5c5dc52a6ef18ece4eae1823d320e27a0c9d77a..793824a316bbefb8f816d484fed051637398d95e 100644
--- a/indra/newview/rlvactions.cpp
+++ b/indra/newview/rlvactions.cpp
@@ -42,7 +42,7 @@ bool RlvActions::canChangeCameraPreset(const LLUUID& idRlvObject)
 	// NOTE: if an object has exclusive camera control then all other objects are locked out
 	return
 		( (!gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM)) || (gRlvHandler.hasBehaviour(idRlvObject, RLV_BHVR_SETCAM)) ) &&
-		(!gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_EYEOFFSET)) && (!gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOCUSOFFSET));
+		(!gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_EYEOFFSET)) && (!gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_EYEOFFSETSCALE)) && (!gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOCUSOFFSET));
 }
 
 bool RlvActions::canChangeToMouselook()
@@ -70,7 +70,9 @@ bool RlvActions::isCameraFOVClamped()
 
 bool RlvActions::isCameraPresetLocked()
 {
-	return (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM)) || (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_EYEOFFSET)) || (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOCUSOFFSET));
+	return
+		(gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM)) ||
+		(gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_EYEOFFSET)) || (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_EYEOFFSETSCALE)) || (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOCUSOFFSET));
 }
 
 bool RlvActions::getCameraAvatarDistanceLimits(float& nDistMin, float& nDistMax)
@@ -351,6 +353,15 @@ bool RlvActions::isLocalTp(const LLVector3d& posGlobal)
 	return nDistSq < RLV_MODIFIER_TPLOCAL_DEFAULT * RLV_MODIFIER_TPLOCAL_DEFAULT;
 }
 
+// ============================================================================
+// WindLight
+//
+
+bool RlvActions::canChangeEnvironment()
+{
+	return !gRlvHandler.hasBehaviour(RLV_BHVR_SETENV);
+}
+
 // ============================================================================
 // World interaction
 //
diff --git a/indra/newview/rlvactions.h b/indra/newview/rlvactions.h
index 90b1069f6bd27487233e6a058860efef9795b4df..be22f4ce6ae20a1b2aa9eb41f0680527b480f608 100644
--- a/indra/newview/rlvactions.h
+++ b/indra/newview/rlvactions.h
@@ -26,6 +26,7 @@
 
 class LLInventoryCategory;
 class LLInventoryItem;
+class LLViewerObject;
 
 // ============================================================================
 // RlvActions class declaration - developer-friendly non-RLVa code facing class, use in lieu of RlvHandler whenever possible
@@ -207,6 +208,16 @@ class RlvActions
 	 */
 	static bool isLocalTp(const LLVector3d& posGlobal);
 
+	// =========
+	// WindLight
+	// =========
+public:
+	/*
+	 * Returns true if the user can make changes to their WindLight environment 
+	 */
+	static bool canChangeEnvironment();
+
+
 	// =================
 	// World interaction
 	// =================
diff --git a/indra/newview/rlvcommon.cpp b/indra/newview/rlvcommon.cpp
index 2dad3a4d00123f9be09e85bab010c8a24f4a0d85..c6b1639919c5af073312126a8d07c8281de009ce 100644
--- a/indra/newview/rlvcommon.cpp
+++ b/indra/newview/rlvcommon.cpp
@@ -439,6 +439,11 @@ std::string RlvStrings::getVersionNum(const LLUUID& idRlvObject)
 		(!fCompatMode) ? RLV_VERSION_PATCH : RLV_VERSION_PATCH_COMPAT, (!fCompatMode) ? RLV_VERSION_BUILD : RLV_VERSION_BUILD_COMPAT);
 }
 
+std::string RlvStrings::getVersionImplNum()
+{
+	return llformat("%d%02d%02d%02d", RLVa_VERSION_MAJOR, RLVa_VERSION_MAJOR, RLVa_VERSION_PATCH, RLVa_IMPL_ID);
+}
+
 // Checked: 2011-11-08 (RLVa-1.5.0)
 bool RlvStrings::hasString(const std::string& strStringName, bool fCheckCustom)
 {
@@ -728,8 +733,13 @@ void rlvMenuToggleVisible()
 
 bool rlvMenuCanShowName()
 {
-  const LLVOAvatar* pAvatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject());
-  return (pAvatar) && (RlvActions::canShowName(RlvActions::SNC_DEFAULT, pAvatar->getID()));
+	bool fEnable = true;
+	if (rlv_handler_t::isEnabled())
+	{
+		const LLVOAvatar* pAvatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject());
+		fEnable = (pAvatar) && (RlvActions::canShowName(RlvActions::SNC_DEFAULT, pAvatar->getID()));
+	}
+	return fEnable;
 }
 
 // Checked: 2010-04-23 (RLVa-1.2.0g) | Modified: RLVa-1.2.0g
diff --git a/indra/newview/rlvcommon.h b/indra/newview/rlvcommon.h
index 47a8ebf1483cf92c87d1e554b06ef102eb01572b..82fca44f91b6772692f6708f4ee3a20e9d3a831d 100644
--- a/indra/newview/rlvcommon.h
+++ b/indra/newview/rlvcommon.h
@@ -56,7 +56,7 @@ class RlvObject;
 
 struct RlvException;
 typedef boost::variant<std::string, LLUUID, S32, ERlvBehaviour> RlvExceptionOption;
-typedef boost::variant<int, float, bool, LLVector3, LLUUID> RlvBehaviourModifierValue;
+typedef boost::variant<int, float, bool, LLVector3, LLVector3d, LLUUID> RlvBehaviourModifierValue;
 
 class RlvGCTimer;
 
@@ -151,6 +151,7 @@ class RlvStrings
 	static const std::string& getStringMapPath() { return m_StringMapPath; }
 	static std::string        getVersion(const LLUUID& idRlvObject, bool fLegacy = false);
 	static std::string        getVersionAbout();
+	static std::string        getVersionImplNum();
 	static std::string        getVersionNum(const LLUUID& idRlvObject);
 	static bool               hasString(const std::string& strStringName, bool fCheckCustom = false);
 	static void               setCustomString(const std::string& strStringName, const std::string& strStringValue);
diff --git a/indra/newview/rlvdefines.h b/indra/newview/rlvdefines.h
index 8026b59b57ccd590bd958b22525cba5ef20027d6..a3938876a00319e428565109050ea56bbe13322c 100644
--- a/indra/newview/rlvdefines.h
+++ b/indra/newview/rlvdefines.h
@@ -23,8 +23,8 @@
 
 // Version of the specifcation we report
 const S32 RLV_VERSION_MAJOR = 3;
-const S32 RLV_VERSION_MINOR = 2;
-const S32 RLV_VERSION_PATCH = 1;
+const S32 RLV_VERSION_MINOR = 3;
+const S32 RLV_VERSION_PATCH = 3;
 const S32 RLV_VERSION_BUILD = 0;
 
 // Version of the specifcation we report (in compatibility mode)
@@ -35,8 +35,9 @@ const S32 RLV_VERSION_BUILD_COMPAT = 0;
 
 // Implementation version
 const S32 RLVa_VERSION_MAJOR = 2;
-const S32 RLVa_VERSION_MINOR = 2;
-const S32 RLVa_VERSION_PATCH = 2;
+const S32 RLVa_VERSION_MINOR = 3;
+const S32 RLVa_VERSION_PATCH = 0;
+const S32 RLVa_IMPL_ID = 13;
 
 // Uncomment before a final release
 //#define RLV_RELEASE
@@ -214,6 +215,7 @@ enum ERlvBehaviour {
 	RLV_BHVR_SETCAM_ORIGINDISTMIN,	// Enforces a minimum distance from the camera origin (in m)
 	RLV_BHVR_SETCAM_ORIGINDISTMAX,	// Enforces a maximum distance from the camera origin (in m)
 	RLV_BHVR_SETCAM_EYEOFFSET,      // Changes the default camera offset
+	RLV_BHVR_SETCAM_EYEOFFSETSCALE, // Changes the default camera offset scale
 	RLV_BHVR_SETCAM_FOCUSOFFSET,    // Changes the default camera focus offset
 	RLV_BHVR_SETCAM_FOCUS,			// Forces the camera focus and/or position to a specific object, avatar or position
 	RLV_BHVR_SETCAM_FOV,			// Changes the current - vertical - field of view
@@ -267,6 +269,7 @@ enum ERlvBehaviourModifier
 	RLV_MODIFIER_SETCAM_ORIGINDISTMIN,	// Minimum distance between the camera position and the origin point (normal value)
 	RLV_MODIFIER_SETCAM_ORIGINDISTMAX,	// Maximum distance between the camera position and the origin point (normal value)
 	RLV_MODIFIER_SETCAM_EYEOFFSET,		// Specifies the default camera's offset from the camera (vector)
+	RLV_MODIFIER_SETCAM_EYEOFFSETSCALE,	// Specifies the default camera's offset scale (multiplier)
 	RLV_MODIFIER_SETCAM_FOCUSOFFSET,	// Specifies the default camera's focus (vector)
 	RLV_MODIFIER_SETCAM_FOVMIN,			// Minimum value for the camera's field of view (angle in radians)
 	RLV_MODIFIER_SETCAM_FOVMAX,			// Maximum value for the camera's field of view (angle in radians)
diff --git a/indra/newview/rlvenvironment.cpp b/indra/newview/rlvenvironment.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d723630fdb117e830ba48ba7d93ed96e27cfd6fb
--- /dev/null
+++ b/indra/newview/rlvenvironment.cpp
@@ -0,0 +1,698 @@
+/**
+ *
+ * Copyright (c) 2009-2020, 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 "llinventoryfunctions.h"
+#include "llsettingsvo.h"
+#include <boost/algorithm/string.hpp>
+
+#include "rlvactions.h"
+#include "rlvenvironment.h"
+#include "rlvhelper.h"
+
+// ================================================================================================
+// Constants and helper functions
+//
+
+namespace
+{
+	const F32 SLIDER_SCALE_BLUE_HORIZON_DENSITY(2.0f);
+	const F32 SLIDER_SCALE_DENSITY_MULTIPLIER(0.001f);
+	const F32 SLIDER_SCALE_GLOW_R(20.0f);
+	const F32 SLIDER_SCALE_GLOW_B(-5.0f);
+	const F32 SLIDER_SCALE_SUN_AMBIENT(3.0f);
+
+	const std::string RLV_GETENV_PREFIX = "getenv_";
+	const std::string RLV_SETENV_PREFIX = "setenv_";
+
+	U32 rlvGetColorComponentFromCharacter(char ch)
+	{
+		if ( ('r' == ch) || ('x' == ch) )      return VRED;
+		else if ( ('g' == ch) || ('y' == ch )) return VGREEN;
+		else if ( ('b' == ch) || ('d' == ch) ) return VBLUE;
+		else if ('i' == ch)                    return VALPHA;
+		return U32_MAX;
+	}
+
+	const LLUUID& rlvGetLibraryEnvironmentsFolder()
+	{
+		LLInventoryModel::cat_array_t cats;
+		LLInventoryModel::item_array_t items;
+		LLNameCategoryCollector f("Environments");
+		gInventory.collectDescendentsIf(gInventory.getLibraryRootFolderID(), cats, items, LLInventoryModel::EXCLUDE_TRASH, f);
+		return (!cats.empty()) ? cats.front()->getUUID() : LLUUID::null;
+	}
+
+	// Legacy WindLight values we need tend to be expressed as a fraction of the [0, 2PI[ domain
+	F32 normalize_angle_domain(F32 angle)
+	{
+		while (angle < 0)
+			angle += F_TWO_PI;
+		while (angle > F_TWO_PI)
+			angle -= F_TWO_PI;
+		return angle;
+	}
+}
+
+/*
+ * Reasoning (Reference - https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Azimuth-Altitude_schematic.svg/1024px-Azimuth-Altitude_schematic.svg.png)
+ *
+ * Given a(zimuth angle) and e(levation angle) - in the SL axis - we know that it calculates the quaternion as follows:
+ *
+ * | cos a	sin a	0 |   | cos e |   | cos a x cos e | = | x |
+ * | sin a	cos a	0 | x |     0 | = | sin a x cos e | = | y | (normalized direction vector identifying the sun position on a unit sphere)
+ * |     0	    0	1 |   | sin e |   |         sin e | = | z |
+ *
+ * As a result we can reverse the above by: quaternion -> rotate it around X-axis
+ *   x = cos a x cos e <=> cos a = x / cos e   \
+ *                                              | (if we divide them we can get rid of cos e)
+ *                                              | <=> sin a / cos a = y / x <=> tan a = y / x <=> a = atan2(y, x)
+ *   y = sin a x cos e <=> sin a = y / cos e   /
+ *   z = sin e         <=> e = asin z
+ *
+ * If we look at the resulting domain azimuth lies in ]-PI, PI] and elevation lies in [-PI/2, PI/2] which I actually prefer most. Going forward people should get the sun in a wind
+ * direction by manipulating the azimuth and then deal with the elevation (which ends up mimicking how a camera or an observer behave in real life).
+ *
+ * Special cases:
+ *   x = 0 => (1) cos e = 0 -> sin e = 1 so y = 0     and z = 1                                    => in (0, 0, 1) we loose all information about the azimuth since cos e = 0
+ *         OR (2) cos a = 0 -> sin a = 1 so y = cos e and z = sin e => tan e = z/y (with y != 0)   => in (0, Y, Z) azimuth is PI/2 (or 3PI/2) and elevation can have an extended domain of ]-PI, PI]
+ *         => When x = 0 (and y != 0) return PI/2 for azimuth and atan2(z, y) for elevation
+ *   y = 0 => (1) sin a = 0 -> cos a = 1 so x = cos e and z = sin e => tan e = z/x (with x != 0)   => in (X, 0, Z) azimuth is    0 (or PI)    and elevation can have an extended domain of ]-PI, PI]
+ *         OR (2) cos e = 0 -> see above
+		   => When y = 0 (and x != 0) return 0 for azimuth and atan2(z, x) for elevation
+ *   z = 0 =>     sin e = 0 -> cos e = 1 so x = cos a and y = sin a => tan a = y / x               => in (X, Y, 0) elevation is  0 (or PI)    and azimuth has its normal domain of ]-PI, PI]
+ *         => When z = 0 return 0 for elevation and a = atan2(y, x) for azimuth
+ *
+ * We still need to convert all that back/forth between legacy WindLight's odd choices:
+ *   east angle   = SL's azimuth rotates from E (1, 0, 0) to N (0, 1, 0) to W (-1, 0, 0) to S (0, -1, O) but the legacy east angle rotates the opposite way from E to S to W to N so invert the angle
+ *                  (the resulting number then needs to be positive and reported as a fraction of 2PI)
+ *   sunposition  = sun elevation reported as a fraction of 2PI
+ *   moonposition = the moon always has sun's azimuth but its negative elevation
+ *
+ * Pre-EEP both azimuth and elevation have a 2PI range which means that two different a and e value combinations could yield the same sun direction which causes us problems now since we
+ * can't differentiate between the two. Pre-EEP likely favoured elevation over azimuth since people might naturally get the time of day they're thinking of and then try to adjust the
+ * azimuth to get the sun in the correct wind direction; however I've already decided that we'll favour azimuth going forward (see above).
+ *
+ * Comparison of pre-EEP and post-EEP legacy values:
+ *   east angle = 0 (aka azimuth = 0)   -> y = 0 so e = atan2(z, x) -> elevation has a range of 2PI so we correctly report pre-EEP values
+ *   sunmoonpos = 0 (aka elevation = 0) -> z = 0 so a = atan2(y, x) -> azimuth has a range of 2PI so we correctly report pre-EEP values
+ *   -PI/2 < sunmoonpos < PI/2          -> general case             -> post-EEP ranges match pre-EEP ranges so we correctly report pre-EEP values
+ *   sunmoonpos > PI/2                  -> elevation went beyond our new maxium so the post-EEP sunmoonpos will actually be off by PI/2 (or 0.25)
+ *                                         (and the resulting east angle is off by PI or 0.5 - for example smp 0.375 and ea 0.875 are equivalent with smp 0.125 and ea 0.375)
+ *
+ * In reverse this means that when setting values through RLVa:
+ *   sunmoonpos without eastangle (=0) => always correct
+ *   eastangle without sunmoonpos (=0) => always correct
+ *   eastangle before sunmoonpos       => always correct
+ *   sunmoonpos before eastangle       => correct   for -0.25 <= sunmoonpos <= 0.25
+ *                                        incorrect for  0.75  > sunmoonpos  > 0.25
+ */
+F32 rlvGetAzimuthFromDirectionVector(const LLVector3& vecDir)
+{
+	if (is_zero(vecDir.mV[VY]))
+		return 0.f;
+	else if (is_zero(vecDir.mV[VX]))
+		return F_PI_BY_TWO;
+
+	F32 radAzimuth = atan2f(vecDir.mV[VY], vecDir.mV[VX]);
+	return (radAzimuth >= 0.f) ? radAzimuth : radAzimuth + F_TWO_PI;
+}
+
+F32 rlvGetElevationFromDirectionVector(const LLVector3& vecDir)
+{
+	if (is_zero(vecDir.mV[VZ]))
+		return 0.f;
+
+	F32 radElevation;
+	if ( (is_zero(vecDir.mV[VX])) && (!is_zero(vecDir.mV[VY])) )
+		radElevation = atan2f(vecDir.mV[VZ], vecDir.mV[VY]);
+	else if ( (!is_zero(vecDir.mV[VX])) && (is_zero(vecDir.mV[VY])) )
+		radElevation = atan2f(vecDir.mV[VZ], vecDir.mV[VX]);
+	else
+		radElevation = asinf(vecDir.mV[VZ]);
+	return (radElevation >= 0.f) ? radElevation : radElevation + F_TWO_PI;
+}
+
+// Defined in llsettingssky.cpp
+LLQuaternion convert_azimuth_and_altitude_to_quat(F32 azimuth, F32 altitude);
+
+// ================================================================================================
+// RlvIsOfSettingsType - Inventory collector for settings of a specific subtype
+//
+
+class RlvIsOfSettingsType : public LLInventoryCollectFunctor
+{
+public:
+	RlvIsOfSettingsType(LLSettingsType::type_e eSettingsType, const std::string& strNameMatch = LLStringUtil::null)
+		: m_eSettingsType(eSettingsType)
+		, m_strNameMatch(strNameMatch)
+	{
+	}
+
+	~RlvIsOfSettingsType() override
+	{
+	}
+
+	bool operator()(LLInventoryCategory*, LLInventoryItem* pItem) override
+	{
+		if ( (pItem) && (LLAssetType::AT_SETTINGS == pItem->getActualType()) )
+		{
+			return
+				(m_eSettingsType == LLSettingsType::fromInventoryFlags(pItem->getFlags())) &&
+				( (m_strNameMatch.empty()) || (boost::iequals(pItem->getName(), m_strNameMatch)) );
+		}
+		return false;
+	}
+
+protected:
+	LLSettingsType::type_e m_eSettingsType;
+	std::string            m_strNameMatch;
+};
+
+// ================================================================================================
+// RlvEnvironment
+//
+
+RlvEnvironment::RlvEnvironment()
+{
+	//
+	// Presets
+	//
+	registerSetEnvFn<LLUUID>("asset", [](LLEnvironment::EnvSelection_t env, const LLUUID& idAsset)
+		{
+			if (idAsset.isNull())
+				return RLV_RET_FAILED_OPTION;
+
+			LLEnvironment::instance().setEnvironment(env, idAsset);
+			return RLV_RET_SUCCESS;
+		});
+	// Deprecated
+	auto fnApplyLibraryPreset = [](LLEnvironment::EnvSelection_t env, const std::string& strPreset, LLSettingsType::type_e eSettingsType)
+		{
+			LLUUID idAsset(strPreset);
+			if (idAsset.isNull())
+			{
+				const LLUUID& idLibraryEnv = rlvGetLibraryEnvironmentsFolder();
+				LLInventoryModel::cat_array_t cats;
+				LLInventoryModel::item_array_t items;
+				RlvIsOfSettingsType f(eSettingsType, strPreset);
+				gInventory.collectDescendentsIf(idLibraryEnv, cats, items, LLInventoryModel::EXCLUDE_TRASH, f);
+				if (!items.empty())
+					idAsset = items.front()->getAssetUUID();
+			}
+
+			if (idAsset.isNull())
+				return RLV_RET_FAILED_OPTION;
+
+			LLEnvironment::instance().setEnvironment(env, idAsset);
+			return RLV_RET_SUCCESS;
+		};
+	registerSetEnvFn<std::string>("preset",   [&fnApplyLibraryPreset](LLEnvironment::EnvSelection_t env, const std::string& strPreset) { return fnApplyLibraryPreset(env, strPreset, LLSettingsType::ST_SKY); });
+	registerSetEnvFn<std::string>("daycycle", [&fnApplyLibraryPreset](LLEnvironment::EnvSelection_t env, const std::string& strPreset) { return fnApplyLibraryPreset(env, strPreset, LLSettingsType::ST_DAYCYCLE); });
+
+	//
+	// Atmosphere & Lighting tab
+	//
+
+	// SETTING_AMBIENT
+	registerSkyFn<LLColor3>("ambient",		[](LLSettingsSky::ptr_t pSky) { return pSky->getAmbientColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setAmbientColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });
+	registerLegacySkyFn<LLColor3>("ambient",[](LLSettingsSky::ptr_t pSky) { return pSky->getAmbientColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setAmbientColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });
+
+	// SETTING_BLUE_DENSITY
+	registerSkyFn<LLColor3>("bluedensity",	[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueDensity() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueDensity(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });
+	registerLegacySkyFn<LLColor3>("bluedensity",[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueDensity() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueDensity(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });
+
+	// SETTING_BLUE_HORIZON
+	registerSkyFn<LLColor3>("bluehorizon",	[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueHorizon() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueHorizon(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });
+	registerLegacySkyFn<LLColor3>("bluehorizon",[](LLSettingsSky::ptr_t pSky) { return pSky->getBlueHorizon() * (1.f / SLIDER_SCALE_BLUE_HORIZON_DENSITY); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setBlueHorizon(clrValue * SLIDER_SCALE_BLUE_HORIZON_DENSITY); });
+
+	// SETTING_DENSITY_MULTIPLIER
+	registerSkyFn<F32>("densitymultiplier",	[](LLSettingsSky::ptr_t pSky) { return pSky->getDensityMultiplier() / SLIDER_SCALE_DENSITY_MULTIPLIER; },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setDensityMultiplier(nValue * SLIDER_SCALE_DENSITY_MULTIPLIER); });
+
+	// SETTING_DISTANCE_MULTIPLIER
+	registerSkyFn<F32>("distancemultiplier",[](LLSettingsSky::ptr_t pSky) { return pSky->getDistanceMultiplier(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setDistanceMultiplier(nValue); });
+
+
+	// SETTING_SKY_DROPLET_RADIUS
+	registerSkyFn<F32>("dropletradius",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyDropletRadius(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyDropletRadius(nValue); });
+
+	// SETTING_HAZE_DENSITY
+	registerSkyFn<F32>("hazedensity",		[](LLSettingsSky::ptr_t pSky) { return pSky->getHazeDensity(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setHazeDensity(nValue); });
+
+	// SETTING_HAZE_HORIZON
+	registerSkyFn<F32>("hazehorizon",		[](LLSettingsSky::ptr_t pSky) { return pSky->getHazeHorizon(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setHazeHorizon(nValue); });
+
+	// SETTING_SKY_ICE_LEVEL
+	registerSkyFn<F32>("icelevel",			[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyIceLevel(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyIceLevel(nValue); });
+
+	// SETTING_MAX_Y
+	registerSkyFn<F32>("maxaltitude",		[](LLSettingsSky::ptr_t pSky) { return pSky->getMaxY(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMaxY(nValue); });
+
+	// SETTING_SKY_MOISTURE_LEVEL
+	registerSkyFn<F32>("moisturelevel",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSkyMoistureLevel(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setSkyMoistureLevel(nValue); });
+
+	//	SETTING_GAMMA
+	registerSkyFn<F32>("scenegamma",		[](LLSettingsSky::ptr_t pSky) { return pSky->getGamma(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGamma(nValue); });
+
+	//
+	// Clouds tab
+	//
+
+	// SETTING_CLOUD_COLOR
+	registerSkyFn<LLColor3>("cloudcolor",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudColor(); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudColor(clrValue); });
+	registerLegacySkyFn<LLColor3>("cloudcolor",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudColor(); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudColor(clrValue); });
+
+	// SETTING_CLOUD_SHADOW
+	registerSkyFn<F32>("cloudcoverage",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudShadow(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudShadow(nValue); });
+
+	// SETTING_CLOUD_POS_DENSITY1
+	registerSkyFn<LLColor3>("clouddensity",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity1(); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity1(clrValue); });
+	registerLegacySkyFn<LLColor3>("cloud",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity1(); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity1(clrValue); });
+
+	// SETTING_CLOUD_POS_DENSITY2
+	registerSkyFn<LLColor3>("clouddetail",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity2(); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity2(clrValue); });
+	registerLegacySkyFn<LLColor3>("clouddetail",[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudPosDensity2(); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setCloudPosDensity2(clrValue); });
+
+	// SETTING_CLOUD_SCALE
+	registerSkyFn<F32>("cloudscale",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScale(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudScale(nValue); });
+
+	// SETTING_CLOUD_SCROLL_RATE
+	registerSkyFn<LLVector2>("cloudscroll",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScrollRate(); },
+											[](LLSettingsSky::ptr_t pSky, const LLVector2& vecValue) { pSky->setCloudScrollRate(vecValue); });
+	registerLegacySkyFn<LLVector2>("cloudscroll", [](LLSettingsSky::ptr_t pSky) { return pSky->getCloudScrollRate(); },
+											[](LLSettingsSky::ptr_t pSky, const LLVector2& vecValue) { pSky->setCloudScrollRate(vecValue); });
+
+	// SETTING_CLOUD_TEXTUREID
+	registerSkyFn<LLUUID>("cloudtexture",	[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudNoiseTextureId();  },
+											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setCloudNoiseTextureId(idTexture);  });
+
+	// SETTING_CLOUD_VARIANCE
+	registerSkyFn<F32>("cloudvariance",		[](LLSettingsSky::ptr_t pSky) { return pSky->getCloudVariance(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setCloudVariance(nValue); });
+
+	//
+	// Sun & Moon
+	//
+
+	// SETTING_MOON_BRIGHTNESS
+	registerSkyFn<F32>("moonbrightness",	[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonBrightness(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMoonBrightness(nValue); });
+
+	// SETTING_MOON_SCALE
+	registerSkyFn<F32>("moonscale",			[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonScale(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setMoonScale(nValue); });
+
+	// SETTING_MOON_TEXTUREID
+	registerSkyFn<LLUUID>("moontexture",	[](LLSettingsSky::ptr_t pSky) { return pSky->getMoonTextureId(); },
+											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setMoonTextureId(idTexture); });
+
+	// SETTING_GLOW
+	registerSkyFn<float>("sunglowsize",		[](LLSettingsSky::ptr_t pSky) { return 2.0 - (pSky->getGlow().mV[VRED] / SLIDER_SCALE_GLOW_R); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGlow(LLColor3((2.0f - nValue) * SLIDER_SCALE_GLOW_R, .0f, pSky->getGlow().mV[VBLUE])); });
+	registerSkyFn<float>("sunglowfocus",	[](LLSettingsSky::ptr_t pSky) { return pSky->getGlow().mV[VBLUE] / SLIDER_SCALE_GLOW_B; },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setGlow(LLColor3(pSky->getGlow().mV[VRED], .0f, nValue * SLIDER_SCALE_GLOW_B)); });
+
+	// SETTING_SUNLIGHT_COLOR
+	registerSkyFn<LLColor3>("sunlightcolor",[](LLSettingsSky::ptr_t pSky) { return pSky->getSunlightColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setSunlightColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });
+	registerLegacySkyFn<LLColor3>("sunmooncolor", [](LLSettingsSky::ptr_t pSky) { return pSky->getSunlightColor() * (1.f / SLIDER_SCALE_SUN_AMBIENT); },
+											[](LLSettingsSky::ptr_t pSky, const LLColor3& clrValue) { pSky->setSunlightColor(clrValue * SLIDER_SCALE_SUN_AMBIENT); });
+
+	// SETTING_SUN_SCALE
+	registerSkyFn<float>("sunscale",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSunScale(); },
+											[](LLSettingsSky::ptr_t pSky, F32 nValue) { pSky->setSunScale(nValue); });
+
+	// SETTING_SUN_TEXTUREID
+	registerSkyFn<LLUUID>("suntexture",		[](LLSettingsSky::ptr_t pSky) { return pSky->getSunTextureId(); },
+											[](LLSettingsSky::ptr_t pSky, const LLUUID& idTexture) { pSky->setSunTextureId(idTexture); });
+
+	// SETTING_STAR_BRIGHTNESS
+	registerSkyFn<F32>("starbrightness",	[](LLSettingsSky::ptr_t pSky) { return pSky->getStarBrightness(); },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue) { pSky->setStarBrightness(nValue); });
+
+	// SETTING_SUN_ROTATION
+	registerSkyFn<F32>("sunazimuth",		[](LLSettingsSky::ptr_t pSky) { return rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()); },
+											[](LLSettingsSky::ptr_t pSky, const F32& radAzimuth) {
+												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, rlvGetElevationFromDirectionVector(LLVector3::x_axis* pSky->getSunRotation())));
+											});
+	registerSkyFn<F32>("sunelevation",		[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()); },
+											[](LLSettingsSky::ptr_t pSky, F32 radElevation) {
+												radElevation = llclamp(radElevation, -F_PI_BY_TWO, F_PI_BY_TWO);
+												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(rlvGetAzimuthFromDirectionVector(LLVector3::x_axis* pSky->getSunRotation()), radElevation));
+											});
+
+	// SETTING_MOON_ROTATION
+	registerSkyFn<F32>("moonazimuth",		[](LLSettingsSky::ptr_t pSky) { return rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getMoonRotation()); },
+											[](LLSettingsSky::ptr_t pSky, const F32& radAzimuth) {
+												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, rlvGetElevationFromDirectionVector(LLVector3::x_axis* pSky->getMoonRotation())));
+											});
+	registerSkyFn<F32>("moonelevation",		[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getMoonRotation()); },
+											[](LLSettingsSky::ptr_t pSky, F32 radElevation) {
+												radElevation = llclamp(radElevation, -F_PI_BY_TWO, F_PI_BY_TWO);
+												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(rlvGetAzimuthFromDirectionVector(LLVector3::x_axis* pSky->getMoonRotation()), radElevation));
+											});
+
+	// Legacy WindLight support (see remarks at the top of this file)
+	registerSkyFn<F32>("eastangle",			[](LLSettingsSky::ptr_t pSky) { return normalize_angle_domain(-rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation())) / F_TWO_PI; },
+											[](LLSettingsSky::ptr_t pSky, const F32& radEastAngle)
+											{
+												const F32 radAzimuth = -radEastAngle * F_TWO_PI;
+												const F32 radElevation = rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation());
+												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, radElevation));
+												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth + F_PI, -radElevation));
+											});
+
+	registerSkyFn<F32>("sunmoonposition",	[](LLSettingsSky::ptr_t pSky) { return rlvGetElevationFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation()) / F_TWO_PI; },
+											[](LLSettingsSky::ptr_t pSky, const F32& nValue)
+											{
+												const F32 radAzimuth = rlvGetAzimuthFromDirectionVector(LLVector3::x_axis * pSky->getSunRotation());
+												const F32 radElevation = nValue * F_TWO_PI;
+												pSky->setSunRotation(convert_azimuth_and_altitude_to_quat(radAzimuth, radElevation));
+												pSky->setMoonRotation(convert_azimuth_and_altitude_to_quat(radAzimuth + F_PI, -radElevation));
+											});
+
+	// Create a fixed sky from the nearest daycycle (local > experience > parcel > region)
+	registerSetEnvFn<F32>("daytime",		[](LLEnvironment::EnvSelection_t env, const F32& nValue)
+											{
+												if ((nValue >= 0.f) && (nValue <= 1.0f))
+												{
+													LLSettingsDay::ptr_t pDay;
+													if (LLEnvironment::ENV_EDIT != env)
+													{
+														LLEnvironment::EnvSelection_t envs[] = { LLEnvironment::ENV_LOCAL, LLEnvironment::ENV_PUSH, LLEnvironment::ENV_PARCEL, LLEnvironment::ENV_REGION };
+														for (size_t idxEnv = 0, cntEnv = sizeof(envs) / sizeof(LLEnvironment::EnvSelection_t); idxEnv < cntEnv && !pDay; idxEnv++)
+															pDay = LLEnvironment::instance().getEnvironmentDay(envs[idxEnv]);
+													}
+													else
+													{
+														pDay = LLEnvironment::instance().getEnvironmentDay(LLEnvironment::ENV_EDIT);
+													}
+
+													if (pDay)
+													{
+														auto pNewSky = LLSettingsVOSky::buildDefaultSky();
+														auto pSkyBlender = std::make_shared<LLTrackBlenderLoopingManual>(pNewSky, pDay, 1);
+														pSkyBlender->setPosition(nValue);
+
+														LLEnvironment::instance().setEnvironment(env, pNewSky);
+														LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
+													}
+												}
+												else if (nValue == -1)
+												{
+													LLEnvironment::instance().clearEnvironment(env);
+													LLEnvironment::instance().setSelectedEnvironment(env);
+													LLEnvironment::instance().updateEnvironment();
+//													defocusEnvFloaters();
+												}
+												else
+												{
+													return RLV_RET_FAILED_OPTION;
+												}
+
+												return RLV_RET_SUCCESS;
+											});
+	registerGetEnvFn("daytime",				[](LLEnvironment::EnvSelection_t env)
+											{
+												// I forgot how much I hate this command... it literally makes no sense since time of day only has any meaning in an
+												// actively animating day cycle (but in that case we have to return -1).
+												if (!LLEnvironment::instance().getEnvironmentFixedSky(env)) {
+													return std::to_string(-1.f);
+												}
+
+												// It's invalid input for @setenv_daytime (see above) so it can be fed in without changing the current environment
+												return std::to_string(2.f);
+											});
+}
+
+RlvEnvironment::~RlvEnvironment()
+{
+}
+
+// static
+LLEnvironment::EnvSelection_t RlvEnvironment::getTargetEnvironment()
+{
+	return RlvActions::canChangeEnvironment() ? LLEnvironment::ENV_LOCAL : LLEnvironment::ENV_EDIT;
+}
+
+// static
+bool RlvEnvironment::onHandleCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet, const std::string& strCmdPrefix, const handler_map_t& fnLookup, const legacy_handler_map_t& legacyFnLookup)
+{
+	if ( (rlvCmd.getBehaviour().length() > strCmdPrefix.length() + 2) && (boost::starts_with(rlvCmd.getBehaviour(), strCmdPrefix)) )
+	{
+		std::string strEnvCommand = rlvCmd.getBehaviour().substr(strCmdPrefix.length());
+
+		handler_map_t::const_iterator itFnEntry = fnLookup.find(strEnvCommand);
+		if (fnLookup.end() != itFnEntry)
+		{
+			cmdRet = itFnEntry->second((RLV_TYPE_FORCE == rlvCmd.getParamType()) ? rlvCmd.getOption() : rlvCmd.getParam());
+			return true;
+		}
+
+		// Legacy handling (blargh)
+		U32 idxComponent = rlvGetColorComponentFromCharacter(strEnvCommand.back());
+		if (idxComponent <= VALPHA)
+		{
+			strEnvCommand.pop_back();
+
+			legacy_handler_map_t::const_iterator itLegacyFnEntry = legacyFnLookup.find(strEnvCommand);
+			if (legacyFnLookup.end() != itLegacyFnEntry)
+			{
+				cmdRet = itLegacyFnEntry->second((RLV_TYPE_FORCE == rlvCmd.getParamType()) ? rlvCmd.getOption() : rlvCmd.getParam(), idxComponent);
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+bool RlvEnvironment::onReplyCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet)
+{
+	return onHandleCommand(rlvCmd, cmdRet, RLV_GETENV_PREFIX, m_GetFnLookup, m_LegacyGetFnLookup);
+}
+
+bool RlvEnvironment::onForceCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet)
+{
+	return onHandleCommand(rlvCmd, cmdRet, RLV_SETENV_PREFIX, m_SetFnLookup, m_LegacySetFnLookup);
+}
+
+template<>
+std::string RlvEnvironment::handleGetFn<float>(const std::function<float(LLSettingsSky::ptr_t)>& fn)
+{
+	LLSettingsSky::ptr_t pSky = LLEnvironment::instance().getCurrentSky();
+	return std::to_string(fn(pSky));
+}
+
+template<>
+std::string RlvEnvironment::handleGetFn<LLUUID>(const std::function<LLUUID(LLSettingsSky::ptr_t)>& fn)
+{
+	LLSettingsSky::ptr_t pSky = LLEnvironment::instance().getCurrentSky();
+	return fn(pSky).asString();
+}
+
+template<>
+std::string RlvEnvironment::handleGetFn<LLVector2>(const std::function<LLVector2(LLSettingsSky::ptr_t)>& fn)
+{
+	LLSettingsSky::ptr_t pSky = LLEnvironment::instance().getCurrentSky();
+	LLVector2 replyVec = fn(pSky);
+	return llformat("%f/%f", replyVec.mV[VX], replyVec.mV[VY]);
+}
+
+template<>
+std::string RlvEnvironment::handleGetFn<LLColor3>(const std::function<LLColor3(LLSettingsSky::ptr_t)>& fn)
+{
+	LLSettingsSky::ptr_t pSky = LLEnvironment::instance().getCurrentSky();
+	LLColor3 replyColor = fn(pSky);
+	return llformat("%f/%f/%f", replyColor.mV[VX], replyColor.mV[VY], replyColor.mV[VZ]);
+}
+
+template<typename T>
+ERlvCmdRet RlvEnvironment::handleSetFn(const std::string& strRlvOption, const std::function<void(LLSettingsSky::ptr_t, const T&)>& fn)
+{
+	T optionValue;
+	if (!RlvCommandOptionHelper::parseOption<T>(strRlvOption, optionValue))
+		return RLV_RET_FAILED_PARAM;
+
+	LLSettingsSky::ptr_t pSky = LLEnvironment::instance().getCurrentSky();
+	fn(pSky, optionValue);
+	pSky->update();
+	return RLV_RET_SUCCESS;
+}
+
+template<>
+std::string RlvEnvironment::handleLegacyGetFn<LLVector2>(const std::function<const LLVector2& (LLSettingsSkyPtr_t)>& getFn, U32 idxComponent)
+{
+	if (idxComponent > 2)
+		return LLStringUtil::null;
+	return std::to_string(getFn(LLEnvironment::instance().getCurrentSky()).mV[idxComponent]);
+}
+
+template<>
+std::string RlvEnvironment::handleLegacyGetFn<LLColor3>(const std::function<const LLColor3& (LLSettingsSkyPtr_t)>& getFn, U32 idxComponent)
+{
+	if ( (idxComponent >= VRED) && (idxComponent <= VBLUE) )
+	{
+		return std::to_string(getFn(LLEnvironment::instance().getCurrentSky()).mV[idxComponent]);
+	}
+	else if (idxComponent == VALPHA)
+	{
+		const LLColor3& clr = getFn(LLEnvironment::instance().getCurrentSky());
+		return std::to_string(llmax(clr.mV[VRED], clr.mV[VGREEN], clr.mV[VBLUE]));
+	}
+	return LLStringUtil::null;
+}
+
+template<>
+ERlvCmdRet RlvEnvironment::handleLegacySetFn<LLVector2>(float optionValue, LLVector2 curValue, const std::function<void(LLSettingsSkyPtr_t, const LLVector2&)>& setFn, U32 idxComponent)
+{
+	if (idxComponent > 2)
+		return RLV_RET_FAILED_UNKNOWN;
+
+	LLSettingsSky::ptr_t pSky = LLEnvironment::instance().getCurrentSky();
+	curValue.mV[idxComponent] = optionValue;
+	setFn(pSky, curValue);
+	pSky->update();
+
+	return RLV_RET_SUCCESS;
+}
+
+template<>
+ERlvCmdRet RlvEnvironment::handleLegacySetFn<LLColor3>(float optionValue, LLColor3 curValue, const std::function<void(LLSettingsSkyPtr_t, const LLColor3&)>& setFn, U32 idxComponent)
+{
+	LLSettingsSky::ptr_t pSky = LLEnvironment::instance().getCurrentSky();
+	if ( (idxComponent >= VRED) && (idxComponent <= VBLUE) )
+	{
+		curValue.mV[idxComponent] = optionValue;
+	}
+	else if (idxComponent == VALPHA)
+	{
+		const F32 curMax = llmax(curValue.mV[VRED], curValue.mV[VGREEN], curValue.mV[VBLUE]);
+		if ( (0.0f == optionValue) || (0.0f == curMax) )
+		{
+			curValue.mV[VRED] = curValue.mV[VGREEN] = curValue.mV[VBLUE] = optionValue;
+		}
+		else
+		{
+			const F32 nDelta = (optionValue - curMax) / curMax;
+			curValue.mV[VRED] *= (1.0f + nDelta);
+			curValue.mV[VGREEN] *= (1.0f + nDelta);
+			curValue.mV[VBLUE] *= (1.0f + nDelta);
+		}
+	}
+	else
+	{
+		return RLV_RET_FAILED_UNKNOWN;
+	}
+
+	setFn(pSky, curValue);
+	pSky->update();
+
+	return RLV_RET_SUCCESS;
+}
+
+
+template<typename T>
+void RlvEnvironment::registerSkyFn(const std::string& strFnName, const std::function<T(LLSettingsSkyPtr_t)>& getFn, const std::function<void(LLSettingsSkyPtr_t, const T&)>& setFn)
+{
+	RLV_ASSERT(m_GetFnLookup.end() == m_GetFnLookup.find(strFnName));
+	m_GetFnLookup.insert(std::make_pair(strFnName, [this, getFn](const std::string& strRlvParam)
+		{
+			if (RlvUtil::sendChatReply(strRlvParam, handleGetFn<T>(getFn)))
+				return RLV_RET_SUCCESS;
+			return RLV_RET_FAILED_PARAM;
+		}));
+
+	RLV_ASSERT(m_SetFnLookup.end() == m_SetFnLookup.find(strFnName));
+	m_SetFnLookup.insert(std::make_pair(strFnName, [this, setFn](const std::string& strRlvOption)
+		{
+			return handleSetFn<T>(strRlvOption, setFn);
+		}));
+}
+
+void RlvEnvironment::registerGetEnvFn(const std::string& strFnName, const std::function<std::string(LLEnvironment::EnvSelection_t env)>& getFn)
+{
+	RLV_ASSERT(m_GetFnLookup.end() == m_GetFnLookup.find(strFnName));
+	m_GetFnLookup.insert(std::make_pair(strFnName, [getFn](const std::string& strRlvParam)
+		{
+			if (RlvUtil::sendChatReply(strRlvParam, getFn(getTargetEnvironment())))
+				return RLV_RET_SUCCESS;
+			return RLV_RET_FAILED_PARAM;
+		}));
+}
+
+template<typename T>
+void RlvEnvironment::registerSetEnvFn(const std::string& strFnName, const std::function<ERlvCmdRet(LLEnvironment::EnvSelection_t env, const T& strRlvOption)>& setFn)
+{
+	RLV_ASSERT(m_SetFnLookup.end() == m_SetFnLookup.find(strFnName));
+	m_SetFnLookup.insert(std::make_pair(strFnName, [setFn](const std::string& strRlvOption)
+		{
+			T optionValue;
+			if (!RlvCommandOptionHelper::parseOption<T>(strRlvOption, optionValue))
+				return RLV_RET_FAILED_PARAM;
+			return setFn(getTargetEnvironment(), optionValue);
+		}));
+}
+
+template<typename T>
+void RlvEnvironment::registerLegacySkyFn(const std::string& strFnName, const std::function<const T& (LLSettingsSkyPtr_t)>& getFn, const std::function<void(LLSettingsSkyPtr_t, const T&)>& setFn)
+{
+	RLV_ASSERT(m_LegacyGetFnLookup.end() == m_LegacyGetFnLookup.find(strFnName));
+	m_LegacyGetFnLookup.insert(std::make_pair(strFnName, [this, getFn](const std::string& strRlvParam, U32 idxComponent)
+		{
+			const std::string strReply = handleLegacyGetFn<T>(getFn, idxComponent);
+			if (strReply.empty())
+				return RLV_RET_FAILED_UNKNOWN;
+			else if (RlvUtil::sendChatReply(strRlvParam, strReply))
+				return RLV_RET_SUCCESS;
+			return RLV_RET_FAILED_PARAM;
+		}));
+
+	RLV_ASSERT(m_LegacySetFnLookup.end() == m_LegacySetFnLookup.find(strFnName));
+	m_LegacySetFnLookup.insert(std::make_pair(strFnName, [this, getFn, setFn](const std::string& strRlvOption, U32 idxComponent)
+		{
+			float optionValue;
+			if (!RlvCommandOptionHelper::parseOption(strRlvOption, optionValue))
+				return RLV_RET_FAILED_PARAM;
+			return handleLegacySetFn<T>(optionValue, getFn(LLEnvironment::instance().getCurrentSky()), setFn, idxComponent);;
+		}));
+}
+
+// ================================================================================================
diff --git a/indra/newview/rlvenvironment.h b/indra/newview/rlvenvironment.h
new file mode 100644
index 0000000000000000000000000000000000000000..0f70e24cae29896103440ce6e8a798a608bf0721
--- /dev/null
+++ b/indra/newview/rlvenvironment.h
@@ -0,0 +1,66 @@
+/**
+ *
+ * Copyright (c) 2009-2020, 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 "llenvironment.h"
+
+#include "rlvcommon.h"
+
+// ============================================================================
+// RlvEnvironment - viewer-side scripted environment changes
+//
+
+class RlvEnvironment : public RlvExtCommandHandler
+{
+public:
+	RlvEnvironment();
+	~RlvEnvironment() override;
+
+	bool onReplyCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) override;
+	bool onForceCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) override;
+protected:
+	static LLEnvironment::EnvSelection_t getTargetEnvironment();
+	typedef std::map<std::string, std::function<ERlvCmdRet(const std::string&)>> handler_map_t;
+	typedef std::map<std::string, std::function<ERlvCmdRet(const std::string&, U32)>> legacy_handler_map_t;
+	static bool onHandleCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet, const std::string& strCmdPrefix, const handler_map_t& fnLookup, const legacy_handler_map_t& legacyFnLookup);
+
+	/*
+	 * Command registration
+	 */
+protected:
+	                     void registerGetEnvFn(const std::string& strFnName, const std::function<std::string(LLEnvironment::EnvSelection_t env)>& getFn);
+	template<typename T> void registerSetEnvFn(const std::string& strFnName, const std::function<ERlvCmdRet(LLEnvironment::EnvSelection_t env, const T& strRlvOption)>& setFn);
+	template<typename T> void registerSkyFn(const std::string& strFnName, const std::function<T(LLSettingsSky::ptr_t)>& getFn, const std::function<void(LLSettingsSky::ptr_t, const T&)>& setFn);
+	template<typename T> void registerLegacySkyFn(const std::string& strFnName, const std::function<const T& (LLSettingsSky::ptr_t)>& getFn, const std::function<void(LLSettingsSky::ptr_t, const T&)>& setFn);
+
+	// Command handling helpers
+	template<typename T> std::string handleGetFn(const std::function<T(LLSettingsSky::ptr_t)>& fn);
+	template<typename T> ERlvCmdRet  handleSetFn(const std::string& strRlvOption, const std::function<void(LLSettingsSky::ptr_t, const T&)>& fn);
+	template<typename T> std::string handleLegacyGetFn(const std::function<const T& (LLSettingsSky::ptr_t)>& getFn, U32 idxComponent);
+	template<typename T> ERlvCmdRet  handleLegacySetFn(float optionValue, T value, const std::function<void(LLSettingsSky::ptr_t, const T&)>& setFn, U32 idxComponent);
+
+	/*
+	 * Member variables
+	 */
+protected:
+	handler_map_t m_GetFnLookup;
+	handler_map_t m_SetFnLookup;
+	legacy_handler_map_t m_LegacyGetFnLookup;
+	legacy_handler_map_t m_LegacySetFnLookup;
+};
+
+// ============================================================================
diff --git a/indra/newview/rlvextensions.cpp b/indra/newview/rlvextensions.cpp
index 00dcd5cf38a7bcdff42cae8f8f9b3ccaf2cc5a0a..4612331c96f5200b3914c613f96fb5efa2601f24 100644
--- a/indra/newview/rlvextensions.cpp
+++ b/indra/newview/rlvextensions.cpp
@@ -1,17 +1,17 @@
-/** 
+/**
  *
  * Copyright (c) 2009-2011, Kitty Barnett
- * 
- * The source code in this file is provided to you under the terms of the 
+ *
+ * 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 
+ * 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 
+ * you have read and understood your obligations described above, and agree to
  * abide by those obligations.
- * 
+ *
  */
 
 #include "llviewerprecompiledheaders.h"
@@ -23,352 +23,6 @@
 #include "rlvhandler.h"
 #include "rlvhelper.h"
 
-// ============================================================================
-/*
-class RlvWindLightControl
-{
-public:
-	enum EType			 { TYPE_COLOR, TYPE_COLOR_R, TYPE_FLOAT, TYPE_UNKNOWN };
-	enum EColorComponent { COMPONENT_R, COMPONENT_G, COMPONENT_B, COMPONENT_I, COMPONENT_NONE };
-public:
-	RlvWindLightControl(WLColorControl* pCtrl, bool fColorR) : m_eType((!fColorR) ? TYPE_COLOR: TYPE_COLOR_R), m_pColourCtrl(pCtrl), m_pFloatCtrl(NULL) {}
-	RlvWindLightControl(WLFloatControl* pCtrl) : m_eType(TYPE_FLOAT), m_pColourCtrl(NULL), m_pFloatCtrl(pCtrl) {}
-
-	EType		getControlType() const	{ return m_eType; }
-	bool		isColorType() const		{ return (TYPE_COLOR == m_eType) || (TYPE_COLOR_R == m_eType); }
-	bool		isFloatType() const		{ return (TYPE_FLOAT == m_eType); }
-	// TYPE_COLOR and TYPE_COLOR_R
-	F32			getColorComponent(EColorComponent eComponent, bool& fError) const;
-	LLVector4	getColorVector(bool& fError) const;
-	bool		setColorComponent(EColorComponent eComponent, F32 nValue);
-	// TYPE_FLOAT
-	F32			getFloat(bool& fError) const;
-	bool		setFloat(F32 nValue);
-
-	static EColorComponent getComponentFromCharacter(char ch);
-protected:
-	EType			m_eType;			// Type of the WindLight control
-	WLColorControl*	m_pColourCtrl;
-	WLFloatControl*	m_pFloatCtrl;
-};
-
-// Checked: 2011-08-29 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-static F32 get_intensity_from_color(const LLVector4& v)
-{
-	return llmax(v.mV[0], v.mV[1], v.mV[2]);
-}
-
-// Checked: 2011-08-29 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-F32 RlvWindLightControl::getColorComponent(EColorComponent eComponent, bool& fError) const
-{
-	switch (eComponent)
-	{
-		case COMPONENT_R: return getColorVector(fError).mV[0];
-		case COMPONENT_G: return getColorVector(fError).mV[1];
-		case COMPONENT_B: return getColorVector(fError).mV[2];
-		case COMPONENT_I: return get_intensity_from_color(getColorVector(fError));	// SL-2.8: Always seems to be 1.0 so get it manually
-		default         : RLV_ASSERT(false); fError = true; return 0.0;
-	}
-}
-
-// Checked: 2011-08-29 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-RlvWindLightControl::EColorComponent RlvWindLightControl::getComponentFromCharacter(char ch)
-{
-	if (('r' == ch) || ('x' == ch))
-		return COMPONENT_R;
-	else if (('g' == ch) || ('y' == ch))
-		return COMPONENT_G;
-	else if (('b' == ch) || ('d' == ch))
-		return COMPONENT_B;
-	else if ('i' == ch)
-		return COMPONENT_I;
-	return COMPONENT_NONE;
-}
-
-// Checked: 2011-08-29 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-LLVector4 RlvWindLightControl::getColorVector(bool& fError) const
-{
-	if ((fError = !isColorType()))
-		return LLVector4(0, 0, 0, 0);
-	F32 nMult = (m_pColourCtrl->isSunOrAmbientColor) ? 3.0f : ((m_pColourCtrl->isBlueHorizonOrDensity) ? 2.0f : 1.0f);
-	return LLWLParamManager::getInstance()->mCurParams.getVector(m_pColourCtrl->mName, fError) / nMult;
-}
-
-// Checked: 2011-08-28 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-bool RlvWindLightControl::setColorComponent(EColorComponent eComponent, F32 nValue)
-{
-	if (isColorType())
-	{
-		nValue *= (m_pColourCtrl->isSunOrAmbientColor) ? 3.0f : ((m_pColourCtrl->isBlueHorizonOrDensity) ? 2.0f : 1.0f);
-		if (COMPONENT_I == eComponent)								// (See: LLFloaterWindLight::onColorControlIMoved)
-		{
-			if (m_pColourCtrl->hasSliderName)
-			{
-				F32 curMax = llmax(m_pColourCtrl->r, m_pColourCtrl->g, m_pColourCtrl->b);
-				if ( (0.0f == nValue) || (0.0f == curMax) )
-				{
-					m_pColourCtrl->r = m_pColourCtrl->g = m_pColourCtrl->b = m_pColourCtrl->i = nValue;
-				}
-				else
-				{
-					F32 nDelta = (nValue - curMax) / curMax;
-					m_pColourCtrl->r *= (1.0f + nDelta);
-					m_pColourCtrl->g *= (1.0f + nDelta);
-					m_pColourCtrl->b *= (1.0f + nDelta);
-					m_pColourCtrl->i = nValue;
-				}
-			}
-		}
-		else														// (See: LLFloaterWindLight::onColorControlRMoved)
-		{
-			F32* pnValue = (COMPONENT_R == eComponent) ? &m_pColourCtrl->r : (COMPONENT_G == eComponent) ? &m_pColourCtrl->g : (COMPONENT_B == eComponent) ? &m_pColourCtrl->b : NULL;
-			if (pnValue)
-				*pnValue = nValue;
-			if (m_pColourCtrl->hasSliderName)
-				m_pColourCtrl->i = llmax(m_pColourCtrl->r, m_pColourCtrl->g, m_pColourCtrl->b);
-		}
-		m_pColourCtrl->update(LLWLParamManager::getInstance()->mCurParams);
-		LLWLParamManager::getInstance()->propagateParameters();
-	}
-	return isColorType();
-}
-
-// Checked: 2011-08-29 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-F32 RlvWindLightControl::getFloat(bool& fError) const
-{
-	return (!(fError = (TYPE_FLOAT != m_eType))) ? LLWLParamManager::getInstance()->mCurParams.getVector(m_pFloatCtrl->mName, fError).mV[0] * m_pFloatCtrl->mult : 0.0;
-}
-
-// Checked: 2011-08-28 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-bool RlvWindLightControl::setFloat(F32 nValue)
-{
-	if (TYPE_FLOAT == m_eType)
-	{
-		m_pFloatCtrl->x = nValue / m_pFloatCtrl->mult;
-		m_pFloatCtrl->update(LLWLParamManager::getInstance()->mCurParams);
-		LLWLParamManager::getInstance()->propagateParameters();
-	}
-	return (TYPE_FLOAT == m_eType);
-}
-
-// ============================================================================
-
-class RlvWindLight : public LLSingleton<RlvWindLight>
-{
-	LLSINGLETON(RlvWindLight);
-public:
-	std::string	getValue(const std::string& strSetting, bool& fError);
-	bool		setValue(const std::string& strRlvName, const std::string& strValue);
-
-protected:
-	std::map<std::string, RlvWindLightControl> m_ControlLookupMap;
-};
-
-// Checked: 2011-08-29 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-RlvWindLight::RlvWindLight()
-{
-	LLWLParamManager* pWLParamMgr = LLWLParamManager::getInstance();
-
-	// TYPE_FLOAT
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("cloudcoverage",	RlvWindLightControl(&pWLParamMgr->mCloudCoverage)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("cloudscale",		RlvWindLightControl(&pWLParamMgr->mCloudScale)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("densitymultiplier", RlvWindLightControl(&pWLParamMgr->mDensityMult)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("distancemultiplier", RlvWindLightControl(&pWLParamMgr->mDistanceMult)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("maxaltitude",	RlvWindLightControl(&pWLParamMgr->mMaxAlt)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("scenegamma",		RlvWindLightControl(&pWLParamMgr->mWLGamma)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("hazedensity",	RlvWindLightControl(&pWLParamMgr->mHazeDensity)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("hazehorizon",	RlvWindLightControl(&pWLParamMgr->mHazeHorizon)));
-	// TYPE_COLOR
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("ambient",		RlvWindLightControl(&pWLParamMgr->mAmbient, false)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("bluedensity",	RlvWindLightControl(&pWLParamMgr->mBlueDensity, false)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("bluehorizon",	RlvWindLightControl(&pWLParamMgr->mBlueHorizon, false)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("cloud",			RlvWindLightControl(&pWLParamMgr->mCloudMain, false)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("cloudcolor",		RlvWindLightControl(&pWLParamMgr->mCloudColor, false)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("clouddetail",	RlvWindLightControl(&pWLParamMgr->mCloudDetail, false)));
-	m_ControlLookupMap.insert(std::pair<std::string, RlvWindLightControl>("sunmooncolor",	RlvWindLightControl(&pWLParamMgr->mSunlight, false)));
-}
-
-// Checked: 2011-08-29 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-std::string RlvWindLight::getValue(const std::string& strSetting, bool& fError)
-{
-	LLWLParamManager* pWLParams = LLWLParamManager::getInstance(); 
-	LLEnvManagerNew* pEnvMgr = LLEnvManagerNew::getInstance();
-
-	fError = false;							// Assume we won't fail
-	if ("preset" == strSetting)
-		return (pEnvMgr->getUseFixedSky()) ? pEnvMgr->getSkyPresetName() : std::string();
-	else if ("daycycle" == strSetting)
-		return (pEnvMgr->getUseDayCycle()) ? pEnvMgr->getDayCycleName() : std::string();
-
-	F32 nValue = 0.0f;
-	if ("daytime" == strSetting)
-	{
-		nValue = (pEnvMgr->getUseFixedSky()) ? pWLParams->mCurParams.getFloat("sun_angle", fError) / F_TWO_PI : -1.0f;
-	}
-	else if (("sunglowfocus" == strSetting) || ("sunglowsize" == strSetting))
-	{
-		pWLParams->mGlow = pWLParams->mCurParams.getVector(pWLParams->mGlow.mName, fError);
-		RLV_ASSERT_DBG(!fError);
-
-		if ("sunglowfocus" == strSetting) 
-			nValue = -pWLParams->mGlow.b / 5.0f;
-		else
-			nValue = 2 - pWLParams->mGlow.r / 20.0f;
-	}
-	else if ("starbrightness" == strSetting)		nValue = pWLParams->mCurParams.getStarBrightness();
-	else if ("eastangle" == strSetting)				nValue = pWLParams->mCurParams.getEastAngle() / F_TWO_PI;
-	else if ("sunmoonposition" == strSetting)		nValue = pWLParams->mCurParams.getSunAngle() / F_TWO_PI;
-	else if ("cloudscrollx" == strSetting)			nValue = pWLParams->mCurParams.getCloudScrollX() - 10.0f;
-	else if ("cloudscrolly" == strSetting)			nValue = pWLParams->mCurParams.getCloudScrollY() - 10.0f;
-	else
-	{
-		std::map<std::string, RlvWindLightControl>::const_iterator itControl = m_ControlLookupMap.find(strSetting);
-		if (m_ControlLookupMap.end() != itControl)
-		{
-			switch (itControl->second.getControlType())
-			{
-				case RlvWindLightControl::TYPE_FLOAT:
-					nValue = itControl->second.getFloat(fError);
-					break;
-				case RlvWindLightControl::TYPE_COLOR_R:
-					nValue = itControl->second.getColorComponent(RlvWindLightControl::COMPONENT_R, fError);
-					break;
-				default:
-					fError = true;
-					break;
-			}
-		}
-		else
-		{
-			// Couldn't find the exact name, check for a color control name
-			RlvWindLightControl::EColorComponent eComponent = RlvWindLightControl::getComponentFromCharacter(strSetting[strSetting.length() - 1]);
-			if (RlvWindLightControl::COMPONENT_NONE != eComponent)
-				itControl = m_ControlLookupMap.find(strSetting.substr(0, strSetting.length() - 1));
-			if ( (m_ControlLookupMap.end() != itControl) && (itControl->second.isColorType()) )
-				nValue = itControl->second.getColorComponent(eComponent, fError);
-			else
-				fError = true;
-		}
-	}
-	return llformat("%f", nValue);
-}
-
-// Checked: 2011-08-29 (RLVa-1.4.1a) | Added: RLVa-1.4.1a
-bool RlvWindLight::setValue(const std::string& strRlvName, const std::string& strValue)
-{
-	F32 nValue = 0.0f;
-	// Sanity check - make sure strValue specifies a number for all settings except "preset" and "daycycle"
-	if ( (RlvSettings::getNoSetEnv()) || 
-		 ( (!LLStringUtil::convertToF32(strValue, nValue)) && (("preset" != strRlvName) && ("daycycle" != strRlvName)) ) )
- 	{
-		return false;
-	}
-
-	LLWLParamManager* pWLParams = LLWLParamManager::getInstance(); 
-	LLEnvManagerNew* pEnvMgr = LLEnvManagerNew::getInstance();
-
-	if ("daytime" == strRlvName)
-	{
-		if (0.0f <= nValue)
-		{
-			pWLParams->mAnimator.deactivate();
-			pWLParams->mAnimator.setDayTime(nValue);
-			pWLParams->mAnimator.update(pWLParams->mCurParams);
-		}
-		else
-		{
-			pEnvMgr->setUserPrefs(pEnvMgr->getWaterPresetName(), pEnvMgr->getSkyPresetName(), pEnvMgr->getDayCycleName(), false, true);
-		}
-		return true;
-	}
-	else if ("preset" == strRlvName)
-	{
-		std::string strPresetName = pWLParams->findPreset(strValue, LLEnvKey::SCOPE_LOCAL);
-		if (!strPresetName.empty())
-			pEnvMgr->useSkyPreset(strPresetName);
-		return !strPresetName.empty();
-	}
-	else if ("daycycle" == strRlvName)
-	{
-		std::string strPresetName = LLDayCycleManager::instance().findPreset(strValue);
-		if (!strPresetName.empty())
-			pEnvMgr->useDayCycle(strValue, LLEnvKey::SCOPE_LOCAL);
-		return !strPresetName.empty();
-	}
-
-	bool fError = false;
-	pWLParams->mAnimator.deactivate();
-	if (("sunglowfocus" == strRlvName) || ("sunglowsize" == strRlvName))
-	{
-		pWLParams->mGlow = pWLParams->mCurParams.getVector(pWLParams->mGlow.mName, fError);
-		RLV_ASSERT_DBG(!fError);
-
-		if ("sunglowfocus" == strRlvName)
-			pWLParams->mGlow.b = -nValue * 5;
-		else
-			pWLParams->mGlow.r = (2 - nValue) * 20;
-
-		pWLParams->mGlow.update(pWLParams->mCurParams);
-		pWLParams->propagateParameters();
-		return true;
-	}
-	else if ("starbrightness" == strRlvName)
-	{
-		pWLParams->mCurParams.setStarBrightness(nValue);
-		return true;
-	}
-	else if (("eastangle" == strRlvName) || ("sunmoonposition" == strRlvName))
-	{
-		if ("eastangle" == strRlvName)
-			pWLParams->mCurParams.setEastAngle(F_TWO_PI * nValue);
-		else
-			pWLParams->mCurParams.setSunAngle(F_TWO_PI * nValue);
-
-		// Set the sun vector
-		pWLParams->mLightnorm.r = -sin(pWLParams->mCurParams.getEastAngle()) * cos(pWLParams->mCurParams.getSunAngle());
-		pWLParams->mLightnorm.g = sin(pWLParams->mCurParams.getSunAngle());
-		pWLParams->mLightnorm.b = cos(pWLParams->mCurParams.getEastAngle()) * cos(pWLParams->mCurParams.getSunAngle());
-		pWLParams->mLightnorm.i = 1.f;
-
-		pWLParams->propagateParameters();
-		return true;
-	}
-	else if ("cloudscrollx" == strRlvName)
-	{
-		pWLParams->mCurParams.setCloudScrollX(nValue + 10.0f);
-		return true;
-	}
-	else if ("cloudscrolly" == strRlvName)
-	{
-		pWLParams->mCurParams.setCloudScrollY(nValue + 10.0f);
-		return true;
-	}
-
-	std::map<std::string, RlvWindLightControl>::iterator itControl = m_ControlLookupMap.find(strRlvName);
-	if (m_ControlLookupMap.end() != itControl)
-	{
-		switch (itControl->second.getControlType())
-		{
-			case RlvWindLightControl::TYPE_FLOAT:
-				return itControl->second.setFloat(nValue);
-			case RlvWindLightControl::TYPE_COLOR_R:
-				return itControl->second.setColorComponent(RlvWindLightControl::COMPONENT_R, nValue);
-			default:
-				RLV_ASSERT(false);
-		}
-	}
-	else
-	{
-		// Couldn't find the exact name, check for a color control name
-		RlvWindLightControl::EColorComponent eComponent = RlvWindLightControl::getComponentFromCharacter(strRlvName[strRlvName.length() - 1]);
-		if (RlvWindLightControl::COMPONENT_NONE != eComponent)
-			itControl = m_ControlLookupMap.find(strRlvName.substr(0, strRlvName.length() - 1));
-		if ( (m_ControlLookupMap.end() != itControl) && (itControl->second.isColorType()) )
-			return itControl->second.setColorComponent(eComponent, nValue);
-	}
-	return false;
-}
-*/
 // ============================================================================
 
 std::map<std::string, S16> RlvExtGetSet::m_DbgAllowed;
@@ -437,24 +91,6 @@ bool RlvExtGetSet::processCommand(const RlvCommand& rlvCmd, ERlvCmdRet& eRet)
 				return true;
 			}
 		}
-		else if ("env" == strBehaviour)
-		{
-			//bool fError = false;
-			//if ( ("get" == strGetSet) && (RLV_TYPE_REPLY == rlvCmd.getParamType()) )
-			//{
-			//	RlvUtil::sendChatReply(rlvCmd.getParam(), RlvWindLight::instance().getValue(strSetting, fError));
-			//	eRet = (!fError) ? RLV_RET_SUCCESS : RLV_RET_FAILED_UNKNOWN;
-			//	return true;
-			//}
-			//else if ( ("set" == strGetSet) && (RLV_TYPE_FORCE == rlvCmd.getParamType()) )
-			//{
-			//	if (!gRlvHandler.hasBehaviourExcept(RLV_BHVR_SETENV, rlvCmd.getObjectID()))
-			//		eRet = (RlvWindLight::instance().setValue(strSetting, rlvCmd.getOption())) ? RLV_RET_SUCCESS : RLV_RET_FAILED_UNKNOWN;
-			//	else
-			//		eRet = RLV_RET_FAILED_LOCK;
-			//	return true;
-			//}
-		}
 	}
 	else if ("setrot" == rlvCmd.getBehaviour())
 	{
diff --git a/indra/newview/rlvhandler.cpp b/indra/newview/rlvhandler.cpp
index dc7f31e451bd16650b84ebcaca4c3d4ce452ebcf..9ef4cb5db5a8c5b34f8a6df2e8c5eddd4f0ea87e 100644
--- a/indra/newview/rlvhandler.cpp
+++ b/indra/newview/rlvhandler.cpp
@@ -37,6 +37,7 @@
 #include "llavataractions.h"            // @stopim IM query
 #include "llavatarnamecache.h"			// @shownames
 #include "llavatarlist.h"				// @shownames
+#include "llfloatercamera.h"			// @setcam family
 #include "llfloatersidepanelcontainer.h"// @shownames
 #include "llnotifications.h"			// @list IM query
 #include "llnotificationsutil.h"
@@ -55,6 +56,7 @@
 
 // RLVa includes
 #include "rlvactions.h"
+#include "rlvenvironment.h"
 #include "rlvfloaters.h"
 #include "rlvactions.h"
 #include "rlvhandler.h"
@@ -144,14 +146,58 @@ RlvHandler::RlvHandler() : m_fCanCancelTp(true), m_posSitSource(), m_pGCTimer(NU
 
 RlvHandler::~RlvHandler()
 {
+	cleanup();
+}
+
+void RlvHandler::cleanup()
+{
+	// Nothing to clean if we're not enabled (or already cleaned up)
+	if (!m_fEnabled)
+		return;
+
+	//
+	// Clean up any restrictions that are still active
+	//
+	RLV_ASSERT(LLApp::isQuitting());	// Several commands toggle debug settings but won't if they know the viewer is quitting
+
+	// Assume we have no way to predict how m_Objects will change so make a copy ahead of time
+	uuid_vec_t idRlvObjects;
+	idRlvObjects.reserve(m_Objects.size());
+	std::transform(m_Objects.begin(), m_Objects.end(), std::back_inserter(idRlvObjects), [](const rlv_object_map_t::value_type& kvPair) {return kvPair.first; });
+	for (const LLUUID & idRlvObj : idRlvObjects)
+	{
+		processCommand(idRlvObj, "clear", true);
+	}
+
+	// Sanity check
+	RLV_ASSERT(m_Objects.empty());
+	RLV_ASSERT(m_Exceptions.empty());
+	RLV_ASSERT(std::all_of(m_Behaviours, m_Behaviours + RLV_BHVR_COUNT, [](S16 cnt) { return !cnt; }));
+	RLV_ASSERT(m_CurCommandStack.empty());
+	RLV_ASSERT(m_CurObjectStack.empty());
+	RLV_ASSERT(m_pOverlayImage.isNull());
+
+	//
+	// Clean up what's left
+	//
 	gAgent.removeListener(this);
+	m_Retained.clear();
+	//delete m_pGCTimer;	// <- deletes itself
+
 	if (m_PendingGroupChange.first.notNull())
 	{
-		LLGroupMgr::instance().removeObserver(m_PendingGroupChange.first, this);
+		if (LLGroupMgr::instanceExists())
+			LLGroupMgr::instance().removeObserver(m_PendingGroupChange.first, this);
 		m_PendingGroupChange = std::make_pair(LLUUID::null, LLStringUtil::null);
 	}
 
-	//delete m_pGCTimer;	// <- deletes itself
+	for (RlvExtCommandHandler* pCmdHandler : m_CommandHandlers)
+	{
+		delete pCmdHandler;
+	}
+	m_CommandHandlers.clear();
+
+	m_fEnabled = false;
 }
 
 // ============================================================================
@@ -393,42 +439,46 @@ bool RlvHandler::notifyCommandHandlers(rlvExtCommandHandler f, const RlvCommand&
 }
 
 // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f
-ERlvCmdRet RlvHandler::processCommand(const RlvCommand& rlvCmd, bool fFromObj)
+ERlvCmdRet RlvHandler::processCommand(std::reference_wrapper<const RlvCommand> rlvCmd, bool fFromObj)
 {
-	RLV_DEBUGS << "[" << rlvCmd.getObjectID() << "]: " << rlvCmd.asString() << RLV_ENDL;
-
-	if ( (isBlockedObject(rlvCmd.getObjectID())) && (RLV_TYPE_REMOVE != rlvCmd.getParamType()) && (RLV_TYPE_CLEAR != rlvCmd.getParamType()) )
-	{
-		RLV_DEBUGS << "\t-> blocked object" << RLV_ENDL;
-		return RLV_RET_FAILED_BLOCKED;
-	}
-	if (!rlvCmd.isValid())
 	{
-		RLV_DEBUGS << "\t-> invalid syntax" << RLV_ENDL;
-		return RLV_RET_FAILED_SYNTAX;
-	}
-	if (rlvCmd.isBlocked())
-	{
-		RLV_DEBUGS << "\t-> blocked command" << RLV_ENDL;
-		return RLV_RET_FAILED_DISABLED;
+		const RlvCommand& rlvCmdTmp = rlvCmd; // Reference to the temporary with limited variable scope since we don't want it to leak below
+
+		RLV_DEBUGS << "[" << rlvCmdTmp.getObjectID() << "]: " << rlvCmdTmp.asString() << RLV_ENDL;
+
+		if ( (isBlockedObject(rlvCmdTmp.getObjectID())) && (RLV_TYPE_REMOVE != rlvCmdTmp.getParamType()) && (RLV_TYPE_CLEAR != rlvCmdTmp.getParamType()) )
+		{
+			RLV_DEBUGS << "\t-> blocked object" << RLV_ENDL;
+			return RLV_RET_FAILED_BLOCKED;
+		}
+		if (!rlvCmdTmp.isValid())
+		{
+			RLV_DEBUGS << "\t-> invalid syntax" << RLV_ENDL;
+			return RLV_RET_FAILED_SYNTAX;
+		}
+		if (rlvCmdTmp.isBlocked())
+		{
+			RLV_DEBUGS << "\t-> blocked command" << RLV_ENDL;
+			return RLV_RET_FAILED_DISABLED;
+		}
 	}
 
 	// Using a stack for executing commands solves a few problems:
 	//   - if we passed RlvObject::m_idObj for idObj somewhere and process a @clear then idObj points to invalid/cleared memory at the end
 	//   - if command X triggers command Y along the way then getCurrentCommand()/getCurrentObject() still return Y even when finished
-	m_CurCommandStack.push(&rlvCmd); m_CurObjectStack.push(rlvCmd.getObjectID());
+	m_CurCommandStack.push(rlvCmd); m_CurObjectStack.push(rlvCmd.get().getObjectID());
 	const LLUUID& idCurObj = m_CurObjectStack.top();
 
 	ERlvCmdRet eRet = RLV_RET_UNKNOWN;
-	switch (rlvCmd.getParamType())
+	switch (rlvCmd.get().getParamType())
 	{
 		case RLV_TYPE_ADD:		// Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f
 			{
-				if ( (m_Behaviours[rlvCmd.getBehaviourType()]) && 
-					 ( (RLV_BHVR_SETCAM == rlvCmd.getBehaviourType()) || (RLV_BHVR_SETDEBUG == rlvCmd.getBehaviourType()) || (RLV_BHVR_SETENV == rlvCmd.getBehaviourType()) ) )
+				ERlvBehaviour eBhvr = rlvCmd.get().getBehaviourType();
+				if ( (m_Behaviours[eBhvr]) && ( (RLV_BHVR_SETCAM == eBhvr) || (RLV_BHVR_SETDEBUG == eBhvr) || (RLV_BHVR_SETENV == eBhvr) ) )
 				{
 					// Some restrictions can only be held by one single object to avoid deadlocks
-					RLV_DEBUGS << "\t- " << rlvCmd.getBehaviour() << " is already set by another object => discarding" << RLV_ENDL;
+					RLV_DEBUGS << "\t- " << rlvCmd.get().getBehaviour() << " is already set by another object => discarding" << RLV_ENDL;
 					eRet = RLV_RET_FAILED_LOCK;
 					break;
 				}
@@ -436,14 +486,14 @@ ERlvCmdRet RlvHandler::processCommand(const RlvCommand& rlvCmd, bool fFromObj)
 				rlv_object_map_t::iterator itObj = m_Objects.find(idCurObj); bool fAdded = false;
 				if (itObj != m_Objects.end())
 				{
-					RlvObject& rlvObj = itObj->second;
-					fAdded = rlvObj.addCommand(rlvCmd);
+					// Add the command to an existing object
+					rlvCmd = itObj->second.addCommand(rlvCmd, fAdded);
 				}
 				else
 				{
-					RlvObject rlvObj(idCurObj);
-					fAdded = rlvObj.addCommand(rlvCmd);
-					itObj = m_Objects.insert(std::pair<LLUUID, RlvObject>(idCurObj, rlvObj)).first;
+					// Create a new RLV object and then add the command to it (and grab its reference)
+					itObj = m_Objects.insert(std::pair<LLUUID, RlvObject>(idCurObj, RlvObject(idCurObj))).first;
+					rlvCmd = itObj->second.addCommand(rlvCmd, fAdded);
 				}
 
 				RLV_DEBUGS << "\t- " << ( (fAdded) ? "adding behaviour" : "skipping duplicate" ) << RLV_ENDL;
@@ -518,12 +568,13 @@ ERlvCmdRet RlvHandler::processCommand(const RlvCommand& rlvCmd, bool fFromObj)
 // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f
 ERlvCmdRet RlvHandler::processCommand(const LLUUID& idObj, const std::string& strCommand, bool fFromObj)
 {
+	const RlvCommand rlvCmd(idObj, strCommand);
 	if (STATE_STARTED != LLStartUp::getStartupState())
 	{
-		m_Retained.push_back(RlvCommand(idObj, strCommand));
+		m_Retained.push_back(rlvCmd);
 		return RLV_RET_RETAINED;
 	}
-	return processCommand(RlvCommand(idObj, strCommand), fFromObj);
+	return processCommand(std::ref(rlvCmd), fFromObj);
 }
 
 // Checked: 2010-02-27 (RLVa-1.2.0a) | Modified: RLVa-1.1.0f
@@ -538,7 +589,7 @@ void RlvHandler::processRetainedCommands(ERlvBehaviour eBhvrFilter /*=RLV_BHVR_U
 		if ( ((RLV_BHVR_UNKNOWN == eBhvrFilter) || (rlvCmd.getBehaviourType() == eBhvrFilter)) && 
 			 ((RLV_TYPE_UNKNOWN == eTypeFilter) || (rlvCmd.getParamType() == eTypeFilter)) )
 		{
-			processCommand(rlvCmd, true);
+			processCommand(std::ref(rlvCmd), true);
 			m_Retained.erase(itCurCmd);
 		}
 	}
@@ -807,6 +858,23 @@ void RlvHandler::setActiveGroupRole(const LLUUID& idGroup, const std::string& st
 	m_PendingGroupChange = std::make_pair(LLUUID::null, LLStringUtil::null);
 }
 
+// @setcam family
+void RlvHandler::setCameraOverride(bool fOverride)
+{
+	if ( (fOverride) && (CAMERA_RLV_SETCAM_VIEW != gAgentCamera.getCameraPreset()) )
+	{
+		m_strCameraPresetRestore = gSavedSettings.getString("PresetCameraActive");
+		gAgentCamera.switchCameraPreset(CAMERA_RLV_SETCAM_VIEW);
+	}
+	else if ( (!fOverride) && (CAMERA_RLV_SETCAM_VIEW == gAgentCamera.getCameraPreset() && (!RlvActions::isCameraPresetLocked())) )
+	{
+		// We need to clear it or it won't reset properly
+		gSavedSettings.setString("PresetCameraActive", LLStringUtil::null);
+		LLFloaterCamera::switchToPreset(m_strCameraPresetRestore);
+		m_strCameraPresetRestore.clear();
+	}
+}
+
 // ============================================================================
 // Externally invoked event handlers
 //
@@ -1031,6 +1099,12 @@ bool RlvHandler::onGC()
 	return (0 != m_Objects.size());	// GC will kill itself if it has nothing to do
 }
 
+// static
+void RlvHandler::cleanupClass()
+{
+	gRlvHandler.cleanup();
+}
+
 // Checked: 2009-11-26 (RLVa-1.1.0f) | Added: RLVa-1.1.0f
 void RlvHandler::onIdleStartup(void* pParam)
 {
@@ -1432,6 +1506,7 @@ bool RlvHandler::setEnabled(bool fEnable)
 		RlvSettings::initClass();
 		RlvStrings::initClass();
 
+		RlvHandler::instance().addCommandHandler(new RlvEnvironment());
 		RlvHandler::instance().addCommandHandler(new RlvExtGetSet());
 
 		// Make sure we get notified when login is successful
@@ -2050,20 +2125,23 @@ void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_AVDISTMIN>::onValueChange()
 		gAgentCamera.changeCameraToThirdPerson();
 }
 
-// Handles: @setcam_eyeoffset:<vector3>=n|y and @setcam_focusoffset:<vector3>=n|y toggles
+// Handles: @setcam_eyeoffset:<vector3>=n|y, @setcam_eyeoffsetscale:<float>=n|y and @setcam_focusoffset:<vector3>=n|y toggles
 template<> template<>
 void RlvBehaviourCamEyeFocusOffsetHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr)
 {
 	if (fHasBhvr)
 	{
-		gAgentCamera.switchCameraPreset(CAMERA_RLV_SETCAM_VIEW);
+		gRlvHandler.setCameraOverride(true);
 	}
 	else
 	{
-		const RlvBehaviourModifier* pBhvrEyeModifier = RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_EYEOFFSET);
-		const RlvBehaviourModifier* pBhvrOffsetModifier = RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_FOCUSOFFSET);
-		if ( (!pBhvrEyeModifier->hasValue()) && (!pBhvrOffsetModifier->hasValue()) )
-			gAgentCamera.switchCameraPreset(CAMERA_PRESET_REAR_VIEW);
+		const RlvBehaviourModifier* pBhvrEyeOffsetModifier = RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_EYEOFFSET);
+		const RlvBehaviourModifier* pBhvrEyeOffsetScaleModifier = RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_EYEOFFSETSCALE);
+		const RlvBehaviourModifier* pBhvrFocusOffsetModifier = RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_FOCUSOFFSET);
+		if ( (!pBhvrEyeOffsetModifier->hasValue()) && (!pBhvrEyeOffsetScaleModifier->hasValue()) && (!pBhvrFocusOffsetModifier->hasValue()) )
+		{
+			gRlvHandler.setCameraOverride(false);
+		}
 	}
 }
 
@@ -2081,7 +2159,21 @@ void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_EYEOFFSET>::onValueChange()
 	}
 }
 
-// Handles: @setcam_focusoffset:<vector3>=n|y changes
+// Handles: @setcam_eyeoffsetscale:<float>=n|y changes
+template<>
+void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_EYEOFFSETSCALE>::onValueChange() const
+{
+	if (RlvBehaviourModifier* pBhvrModifier = RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_EYEOFFSETSCALE))
+	{
+		LLControlVariable* pControl = gSavedSettings.getControl("CameraOffsetScaleRLVa");
+		if (pBhvrModifier->hasValue())
+			pControl->setValue(pBhvrModifier->getValue<float>());
+		else
+			pControl->resetToDefault();
+	}
+}
+
+// Handles: @setcam_focusoffset:<vector3d>=n|y changes
 template<>
 void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOCUSOFFSET>::onValueChange() const
 {
@@ -2089,7 +2181,7 @@ void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOCUSOFFSET>::onValueChange
 	{
 		LLControlVariable* pControl = gSavedSettings.getControl("FocusOffsetRLVaView");
 		if (pBhvrModifier->hasValue())
-			pControl->setValue(pBhvrModifier->getValue<LLVector3>().getValue());
+			pControl->setValue(pBhvrModifier->getValue<LLVector3d>().getValue());
 		else
 			pControl->resetToDefault();
 	}
@@ -2208,12 +2300,13 @@ void RlvBehaviourToggleHandler<RLV_BHVR_SETCAM>::onCommandToggle(ERlvBehaviour e
 	if (fHasCamUnlock != gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_UNLOCK))
 		RlvBehaviourToggleHandler<RLV_BHVR_SETCAM_UNLOCK>::onCommandToggle(RLV_BHVR_SETCAM_UNLOCK, !fHasCamUnlock);
 
-	gAgentCamera.switchCameraPreset( (fHasBhvr) ? CAMERA_RLV_SETCAM_VIEW : CAMERA_PRESET_REAR_VIEW );
+	gRlvHandler.setCameraOverride(fHasBhvr);
 	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_AVDISTMIN)->setPrimaryObject(idRlvObject);
 	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_AVDISTMAX)->setPrimaryObject(idRlvObject);
 	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_ORIGINDISTMIN)->setPrimaryObject(idRlvObject);
 	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_ORIGINDISTMAX)->setPrimaryObject(idRlvObject);
 	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_EYEOFFSET)->setPrimaryObject(idRlvObject);
+	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_EYEOFFSETSCALE)->setPrimaryObject(idRlvObject);
 	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_FOCUSOFFSET)->setPrimaryObject(idRlvObject);
 	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_FOVMIN)->setPrimaryObject(idRlvObject);
 	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_FOVMAX)->setPrimaryObject(idRlvObject);
@@ -2235,30 +2328,43 @@ void RlvBehaviourToggleHandler<RLV_BHVR_SETDEBUG>::onCommandToggle(ERlvBehaviour
 template<> template<>
 void RlvBehaviourToggleHandler<RLV_BHVR_SETENV>::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr)
 {
-	//const std::string strEnvFloaters[] = { "env_post_process", "env_settings", "env_delete_preset", "env_edit_sky", "env_edit_water", "env_edit_day_cycle" };
-	//for (int idxFloater = 0, cntFloater = sizeof(strEnvFloaters) / sizeof(std::string); idxFloater < cntFloater; idxFloater++)
-	//{
-	//	if (fHasBhvr)
-	//	{
-	//		// Hide the floater if it's currently visible
-	//		LLFloaterReg::const_instance_list_t envFloaters = LLFloaterReg::getFloaterList(strEnvFloaters[idxFloater]);
-	//		for (LLFloater* pFloater : envFloaters)
-	//			pFloater->closeFloater();
-	//		RlvUIEnabler::instance().addGenericFloaterFilter(strEnvFloaters[idxFloater]);
-	//	}
-	//	else
-	//	{
-	//		RlvUIEnabler::instance().removeGenericFloaterFilter(strEnvFloaters[idxFloater]);
-	//	}
-	//}
-
-	//// Don't allow toggling "Basic Shaders" and/or "Atmopsheric Shaders" through the debug settings under @setenv=n
-	//gSavedSettings.getControl("VertexShaderEnable")->setHiddenFromSettingsEditor(fHasBhvr);
-	//gSavedSettings.getControl("WindLightUseAtmosShaders")->setHiddenFromSettingsEditor(fHasBhvr);
-
-	//// Restore the user's WindLight preferences when releasing
-	//if (!fHasBhvr)
-	//	LLEnvManagerNew::instance().usePrefs();
+	const std::string strEnvFloaters[] = { "env_adjust_snapshot", "env_edit_extdaycycle", "env_fixed_environmentent_sky", "env_fixed_environmentent_water", "my_environments" };
+	for (int idxFloater = 0, cntFloater = sizeof(strEnvFloaters) / sizeof(std::string); idxFloater < cntFloater; idxFloater++)
+	{
+		if (fHasBhvr)
+		{
+			// Hide the floater if it's currently visible
+			LLFloaterReg::const_instance_list_t envFloaters = LLFloaterReg::getFloaterList(strEnvFloaters[idxFloater]);
+			for (LLFloater* pFloater : envFloaters)
+				pFloater->closeFloater();
+			RlvUIEnabler::instance().addGenericFloaterFilter(strEnvFloaters[idxFloater]);
+		}
+		else
+		{
+			RlvUIEnabler::instance().removeGenericFloaterFilter(strEnvFloaters[idxFloater]);
+		}
+	}
+
+	// Don't allow toggling "Atmopsheric Shaders" through the debug settings under @setenv=n
+	gSavedSettings.getControl("WindLightUseAtmosShaders")->setHiddenFromSettingsEditor(fHasBhvr);
+
+	if (fHasBhvr)
+	{
+		// Usurp the 'edit' environment for RLVa locking so TPV tools like quick prefs and phototools are automatically locked out as well
+		// (these needed per-feature awareness of RLV in the previous implementation which often wasn't implemented)
+		LLEnvironment* pEnv = LLEnvironment::getInstance();
+		LLSettingsSky::ptr_t pRlvSky = pEnv->getEnvironmentFixedSky(LLEnvironment::ENV_LOCAL, true)->buildClone();
+		pEnv->setEnvironment(LLEnvironment::ENV_EDIT, pRlvSky);
+		pEnv->setSelectedEnvironment(LLEnvironment::ENV_EDIT, LLEnvironment::TRANSITION_INSTANT);
+		pEnv->updateEnvironment(LLEnvironment::TRANSITION_INSTANT);
+	}
+	else
+	{
+		// Restore the user's WindLight preferences when releasing
+		LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_EDIT);
+		LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL);
+		LLEnvironment::instance().updateEnvironment();
+	}
 }
 
 // Handles: @showhovertext:<uuid>=n|y
@@ -2657,7 +2763,7 @@ ERlvCmdRet RlvForceHandler<RLV_BHVR_REMOUTFIT>::onCommand(const RlvCommand& rlvC
 	return RLV_RET_SUCCESS;
 }
 
-// Handles: @setcam_eyeoffset[:<vector3>]=force and @setcam_focusoffset[:<vector3>]=force
+// Handles: @setcam_eyeoffset[:<vector3>]=force, @setcam_eyeoffsetscale[:<float>]=force and @setcam_focusoffset[:<vector3>]=force
 template<> template<>
 ERlvCmdRet RlvForceCamEyeFocusOffsetHandler::onCommand(const RlvCommand& rlvCmd)
 {
@@ -2665,22 +2771,54 @@ ERlvCmdRet RlvForceCamEyeFocusOffsetHandler::onCommand(const RlvCommand& rlvCmd)
 	if (!RlvActions::canChangeCameraPreset(rlvCmd.getObjectID()))
 		return RLV_RET_FAILED_LOCK;
 
-	LLControlVariable* pOffsetControl = gSavedSettings.getControl("CameraOffsetRLVaView");
-	LLControlVariable* pFocusControl = gSavedSettings.getControl("FocusOffsetRLVaView");
-	LLControlVariable* pControl = (rlvCmd.getBehaviourType() == RLV_BHVR_SETCAM_EYEOFFSET) ? pOffsetControl : pFocusControl;
-	if (rlvCmd.hasOption())
+	LLControlVariable* pEyeOffsetControl = gSavedSettings.getControl("CameraOffsetRLVaView");
+	LLControlVariable* pEyeOffsetScaleControl = gSavedSettings.getControl("CameraOffsetScaleRLVa");
+	LLControlVariable* pFocusOffsetControl = gSavedSettings.getControl("FocusOffsetRLVaView");
+
+	LLControlVariable* pControl; LLSD sdControlValue;
+	switch (rlvCmd.getBehaviourType())
 	{
-		LLVector3 vecOffset;
-		if (!RlvCommandOptionHelper::parseOption(rlvCmd.getOption(), vecOffset))
-			return RLV_RET_FAILED_OPTION;
-		pControl->setValue(vecOffset.getValue());
+		case RLV_BHVR_SETCAM_EYEOFFSET:
+			if (rlvCmd.hasOption())
+			{
+				LLVector3 vecOffset;
+				if (!RlvCommandOptionHelper::parseOption(rlvCmd.getOption(), vecOffset))
+					return RLV_RET_FAILED_OPTION;
+				sdControlValue = vecOffset.getValue();
+			}
+			pControl = pEyeOffsetControl;
+			break;
+		case RLV_BHVR_SETCAM_EYEOFFSETSCALE:
+			if (rlvCmd.hasOption())
+			{
+				float nScale;
+				if (!RlvCommandOptionHelper::parseOption(rlvCmd.getOption(), nScale))
+					return RLV_RET_FAILED_OPTION;
+				sdControlValue = nScale;
+			}
+			pControl = pEyeOffsetScaleControl;
+			break;
+		case RLV_BHVR_SETCAM_FOCUSOFFSET:
+			if (rlvCmd.hasOption())
+			{
+				LLVector3d vecOffset;
+				if (!RlvCommandOptionHelper::parseOption(rlvCmd.getOption(), vecOffset))
+					return RLV_RET_FAILED_OPTION;
+				sdControlValue = vecOffset.getValue();
+			}
+			pControl = pFocusOffsetControl;
+			break;
+		default:
+			return RLV_RET_FAILED;
 	}
+
+	if (!sdControlValue.isUndefined())
+		pControl->setValue(sdControlValue);
 	else
-	{
 		pControl->resetToDefault();
-	}
 
-	gAgentCamera.switchCameraPreset( ((pOffsetControl->isDefault()) && (pFocusControl->isDefault())) ? CAMERA_PRESET_REAR_VIEW : CAMERA_RLV_SETCAM_VIEW);
+	// NOTE: this doesn't necessarily release the camera preset even if all 3 are at their default now (e.g. @setcam is currently set)
+	gRlvHandler.setCameraOverride( (!pEyeOffsetControl->isDefault()) || (!pEyeOffsetScaleControl->isDefault()) || (!pFocusOffsetControl->isDefault()) );
 	return RLV_RET_SUCCESS;
 }
 
@@ -3053,7 +3191,10 @@ ERlvCmdRet RlvHandler::processReplyCommand(const RlvCommand& rlvCmd) const
 			break;
 		case RLV_BHVR_VERSIONNUM:		// @versionnum=<channel>				- Checked: 2010-03-27 (RLVa-1.4.0a) | Added: RLVa-1.0.4b
 			// NOTE: RLV will respond even if there's an option
-			strReply = RlvStrings::getVersionNum(rlvCmd.getObjectID());
+			if (!rlvCmd.hasOption())
+				strReply = RlvStrings::getVersionNum(rlvCmd.getObjectID());
+			else if ("impl" == rlvCmd.getOption())
+				strReply = RlvStrings::getVersionImplNum();
 			break;
 		case RLV_BHVR_GETATTACH:		// @getattach[:<layer>]=<channel>
 			eRet = onGetAttach(rlvCmd, strReply);
diff --git a/indra/newview/rlvhandler.h b/indra/newview/rlvhandler.h
index eba2c328830a131c34af0d273ff89e4a7c04cbbf..c66414f63e4adb92e209d15c898cb731ffd75eb1 100644
--- a/indra/newview/rlvhandler.h
+++ b/indra/newview/rlvhandler.h
@@ -133,7 +133,7 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou
 	bool       processIMQuery(const LLUUID& idSender, const std::string& strCommand);
 
 	// Returns a pointer to the currently executing command (do *not* save this pointer)
-	const RlvCommand* getCurrentCommand() const { return (!m_CurCommandStack.empty()) ? m_CurCommandStack.top() : NULL; }
+	const RlvCommand* getCurrentCommand() const { return (!m_CurCommandStack.empty()) ? &m_CurCommandStack.top().get() : nullptr; }
 	// Returns the UUID of the object we're currently executing a command for
 	const LLUUID&     getCurrentObject() const	{ return (!m_CurObjectStack.empty()) ? m_CurObjectStack.top() : LLUUID::null; }
 
@@ -147,6 +147,7 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou
 	void clearOverlayImage();                                                                   // @setoverlay=n
 	void setActiveGroup(const LLUUID& idGroup);                                                 // @setgroup=force
 	void setActiveGroupRole(const LLUUID& idGroup, const std::string& strRole);                 // @setgroup=force
+	void setCameraOverride(bool fOverride);                                                     // @setcam family
 	void setOverlayImage(const LLUUID& idTexture);                                              // @setoverlay=n
 
 	void onIMQueryListResponse(const LLSD& sdNotification, const LLSD sdResponse);
@@ -173,6 +174,7 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou
 
 	// Externally invoked event handlers
 public:
+	void cleanup();
 	void onActiveGroupChanged();
 	void onAttach(const LLViewerObject* pAttachObj, const LLViewerJointAttachment* pAttachPt);
 	void onDetach(const LLViewerObject* pAttachObj, const LLViewerJointAttachment* pAttachPt);
@@ -183,6 +185,7 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou
 	void onSitOrStand(bool fSitting);
 	void onTeleportFailed();
 	void onTeleportFinished(const LLVector3d& posArrival);
+	static void cleanupClass();
 	static void onIdleStartup(void* pParam);
 protected:
 	void getAttachmentResourcesCoro(const std::string strUrl);
@@ -201,7 +204,7 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou
 	 * Command processing
 	 */
 protected:
-	ERlvCmdRet processCommand(const RlvCommand& rlvCmd, bool fFromObj);
+	ERlvCmdRet processCommand(std::reference_wrapper<const RlvCommand> rlvCmdRef, bool fFromObj);
 	ERlvCmdRet processClearCommand(const RlvCommand& rlvCmd);
 
 	// Command handlers (RLV_TYPE_ADD and RLV_TYPE_CLEAR)
@@ -242,7 +245,7 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou
 	rlv_command_list_t    m_Retained;
 	RlvGCTimer*           m_pGCTimer;
 
-	std::stack<const RlvCommand*> m_CurCommandStack;// Convenience (see @tpto)
+	std::stack<std::reference_wrapper<const RlvCommand>> m_CurCommandStack; // Convenience (see @tpto)
 	std::stack<LLUUID>    m_CurObjectStack;			// Convenience (see @tpto)
 
 	rlv_behaviour_signal_t m_OnBehaviour;
@@ -263,6 +266,8 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou
 	LLPointer<LLViewerFetchedTexture>       m_pOverlayImage = nullptr;		// @setoverlay=n
 	int                                     m_nOverlayOrigBoost = 0;		// @setoverlay=n
 
+	std::string                             m_strCameraPresetRestore;       // @setcam_eyeoffset, @setcam_eyeoffsetscale and @setcam_focusoffset
+
 	friend class RlvSharedRootFetcher;				// Fetcher needs access to m_fFetchComplete
 	friend class RlvGCTimer;						// Timer clear its own point at destruction
 	template<ERlvBehaviourOptionType> friend struct RlvBehaviourGenericHandler;
diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp
index 4a4ab3c7c0b43787a1b994b4a855b21b24efb538..3f4c2f3e505a947f7b1ea1d0d78d010e1e70c08e 100644
--- a/indra/newview/rlvhelper.cpp
+++ b/indra/newview/rlvhelper.cpp
@@ -189,10 +189,12 @@ RlvBehaviourDictionary::RlvBehaviourDictionary()
 	addModifier(RLV_BHVR_SETCAM_ORIGINDISTMIN, RLV_MODIFIER_SETCAM_ORIGINDISTMIN, new RlvBehaviourModifier("Camera - Focus Distance (Min)", 0.0f, true, new RlvBehaviourModifierCompMax));
 	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_MODIFIER>("setcam_origindistmax", RLV_BHVR_SETCAM_ORIGINDISTMAX, RlvBehaviourInfo::BHVR_EXPERIMENTAL));
 	addModifier(RLV_BHVR_SETCAM_ORIGINDISTMAX, RLV_MODIFIER_SETCAM_ORIGINDISTMAX, new RlvBehaviourModifier("Camera - Focus Distance (Max)", F32_MAX, true, new RlvBehaviourModifierCompMin));
-	addEntry(new RlvBehaviourGenericToggleProcessor<RLV_BHVR_SETCAM_EYEOFFSET, RLV_OPTION_MODIFIER, RlvBehaviourCamEyeFocusOffsetHandler>("setcam_eyeoffset", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
+	addEntry(new RlvBehaviourGenericToggleProcessor<RLV_BHVR_SETCAM_EYEOFFSET, RLV_OPTION_MODIFIER, RlvBehaviourCamEyeFocusOffsetHandler>("setcam_eyeoffset"));
 	addModifier(RLV_BHVR_SETCAM_EYEOFFSET, RLV_MODIFIER_SETCAM_EYEOFFSET, new RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_EYEOFFSET>("Camera - Eye Offset", LLVector3::zero, true, nullptr));
-	addEntry(new RlvBehaviourGenericToggleProcessor<RLV_BHVR_SETCAM_FOCUSOFFSET, RLV_OPTION_MODIFIER, RlvBehaviourCamEyeFocusOffsetHandler>("setcam_focusoffset", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
-	addModifier(RLV_BHVR_SETCAM_FOCUSOFFSET, RLV_MODIFIER_SETCAM_FOCUSOFFSET, new RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOCUSOFFSET>("Camera - Focus Offset", LLVector3::zero, true, nullptr));
+	addEntry(new RlvBehaviourGenericToggleProcessor<RLV_BHVR_SETCAM_EYEOFFSETSCALE, RLV_OPTION_MODIFIER, RlvBehaviourCamEyeFocusOffsetHandler>("setcam_eyeoffsetscale"));
+	addModifier(RLV_BHVR_SETCAM_EYEOFFSETSCALE, RLV_MODIFIER_SETCAM_EYEOFFSETSCALE, new RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_EYEOFFSETSCALE>("Camera - Eye Offset Scale", 0, true, nullptr));
+	addEntry(new RlvBehaviourGenericToggleProcessor<RLV_BHVR_SETCAM_FOCUSOFFSET, RLV_OPTION_MODIFIER, RlvBehaviourCamEyeFocusOffsetHandler>("setcam_focusoffset"));
+	addModifier(RLV_BHVR_SETCAM_FOCUSOFFSET, RLV_MODIFIER_SETCAM_FOCUSOFFSET, new RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOCUSOFFSET>("Camera - Focus Offset", LLVector3d::zero, true, nullptr));
 	addEntry(new RlvBehaviourProcessor<RLV_BHVR_SETCAM_FOVMIN, RlvBehaviourSetCamFovHandler>("setcam_fovmin"));
 	addModifier(RLV_BHVR_SETCAM_FOVMIN, RLV_MODIFIER_SETCAM_FOVMIN, new RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOVMIN>("Camera - FOV (Min)", DEFAULT_FIELD_OF_VIEW, true, new RlvBehaviourModifierCompMax));
 	addEntry(new RlvBehaviourProcessor<RLV_BHVR_SETCAM_FOVMAX, RlvBehaviourSetCamFovHandler>("setcam_fovmax"));
@@ -261,8 +263,9 @@ RlvBehaviourDictionary::RlvBehaviourDictionary()
 	addEntry(new RlvForceProcessor<RLV_BHVR_DETACHME>("detachme"));
 	addEntry(new RlvForceProcessor<RLV_BHVR_FLY>("fly"));
 	addEntry(new RlvForceProcessor<RLV_BHVR_SETCAM_FOCUS>("setcam_focus", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
-	addEntry(new RlvForceProcessor<RLV_BHVR_SETCAM_EYEOFFSET, RlvForceCamEyeFocusOffsetHandler>("setcam_eyeoffset", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
-	addEntry(new RlvForceProcessor<RLV_BHVR_SETCAM_FOCUSOFFSET, RlvForceCamEyeFocusOffsetHandler>("setcam_focusoffset", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
+	addEntry(new RlvForceProcessor<RLV_BHVR_SETCAM_EYEOFFSET, RlvForceCamEyeFocusOffsetHandler>("setcam_eyeoffset"));
+	addEntry(new RlvForceProcessor<RLV_BHVR_SETCAM_EYEOFFSETSCALE, RlvForceCamEyeFocusOffsetHandler>("setcam_eyeoffsetscale"));
+	addEntry(new RlvForceProcessor<RLV_BHVR_SETCAM_FOCUSOFFSET, RlvForceCamEyeFocusOffsetHandler>("setcam_focusoffset"));
 	addEntry(new RlvForceProcessor<RLV_BHVR_SETCAM_FOV>("setcam_fov", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
 	addEntry(new RlvForceProcessor<RLV_BHVR_SETCAM_MODE>("setcam_mode", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
 	addEntry(new RlvForceProcessor<RLV_BHVR_SETGROUP>("setgroup"));
@@ -642,8 +645,9 @@ RlvCommand::RlvCommand(const LLUUID& idObj, const std::string& strCommand)
 }
 
 RlvCommand::RlvCommand(const RlvCommand& rlvCmd, ERlvParamType eParamType)
-	: m_fValid(rlvCmd.m_fValid), m_idObj(rlvCmd.m_idObj), m_strBehaviour(rlvCmd.m_strBehaviour), m_pBhvrInfo(rlvCmd.m_pBhvrInfo),
-	  m_eParamType( (RLV_TYPE_UNKNOWN == eParamType) ? rlvCmd.m_eParamType : eParamType),m_fStrict(rlvCmd.m_fStrict), m_strOption(rlvCmd.m_strOption), m_strParam(rlvCmd.m_strParam), m_fRefCounted(false)
+	: m_fValid(rlvCmd.m_fValid), m_idObj(rlvCmd.m_idObj), m_strBehaviour(rlvCmd.m_strBehaviour), m_pBhvrInfo(rlvCmd.m_pBhvrInfo)
+	, m_eParamType( (RLV_TYPE_UNKNOWN == eParamType) ? rlvCmd.m_eParamType : eParamType),m_fStrict(rlvCmd.m_fStrict), m_strOption(rlvCmd.m_strOption)
+	, m_strParam(rlvCmd.m_strParam), m_fRefCounted(rlvCmd.m_fRefCounted)
 {
 }
 
@@ -685,6 +689,13 @@ bool RlvCommand::parseCommand(const std::string& strCommand, std::string& strBeh
 // Command option parsing utility classes
 //
 
+template<>
+bool RlvCommandOptionHelper::parseOption<std::string>(const std::string& strOption, std::string& valueOption)
+{
+	valueOption = strOption;
+	return true;
+}
+
 template<>
 bool RlvCommandOptionHelper::parseOption<LLUUID>(const std::string& strOption, LLUUID& idOption)
 {
@@ -766,6 +777,17 @@ bool RlvCommandOptionHelper::parseOption<LLViewerInventoryCategory*>(const std::
 	return pFolder != NULL;
 }
 
+template<>
+bool RlvCommandOptionHelper::parseOption<LLVector2>(const std::string& strOption, LLVector2& vecOption)
+{
+	if (!strOption.empty())
+	{
+		S32 cntToken = sscanf(strOption.c_str(), "%f/%f", vecOption.mV + 0, vecOption.mV + 1);
+		return (2 == cntToken);
+	}
+	return false;
+}
+
 template<>
 bool RlvCommandOptionHelper::parseOption<LLVector3>(const std::string& strOption, LLVector3& vecOption)
  {
@@ -788,6 +810,17 @@ bool RlvCommandOptionHelper::parseOption<LLVector3d>(const std::string& strOptio
 	return false;
 }
 
+template<>
+bool RlvCommandOptionHelper::parseOption<LLColor3>(const std::string& strOption, LLColor3& clrOption)
+{
+	if (!strOption.empty())
+	{
+		S32 cntToken = sscanf(strOption.c_str(), "%f/%f/%f", clrOption.mV + 0, clrOption.mV + 1, clrOption.mV + 2);
+		return (3 == cntToken);
+	}
+	return false;
+}
+
 template<>
 bool RlvCommandOptionHelper::parseOption<RlvCommandOptionGeneric>(const std::string& strOption, RlvCommandOptionGeneric& genericOption)
 {
@@ -989,7 +1022,7 @@ RlvObject::RlvObject(const LLUUID& idObj) : m_idObj(idObj), m_nLookupMisses(0)
 	m_idRoot = (pObj) ? pObj->getRootEdit()->getID() : LLUUID::null;
 }
 
-bool RlvObject::addCommand(const RlvCommand& rlvCmd)
+const RlvCommand& RlvObject::addCommand(const RlvCommand& rlvCmd, bool& fAdded)
 {
 	RLV_ASSERT(RLV_TYPE_ADD == rlvCmd.getParamType());
 
@@ -999,14 +1032,15 @@ bool RlvObject::addCommand(const RlvCommand& rlvCmd)
 		if ( (itCmd->getBehaviour() == rlvCmd.getBehaviour()) && (itCmd->getOption() == rlvCmd.getOption()) && 
 			 (itCmd->isStrict() == rlvCmd.isStrict() ) )
 		{
-			return false;
+			fAdded = false;
+			return *itCmd;
 		}
 	}
 
 	// Now that we know it's not a duplicate, add it to the end of the list
 	m_Commands.push_back(rlvCmd);
-
-	return true;
+	fAdded = true;
+	return m_Commands.back();
 }
 
 bool RlvObject::removeCommand(const RlvCommand& rlvCmd)
diff --git a/indra/newview/rlvhelper.h b/indra/newview/rlvhelper.h
index 6cf2c5c62ebc886f22ba952ffb039e75dc212fe3..cbe79ec57a0347790b4227ab014eee427933df1e 100644
--- a/indra/newview/rlvhelper.h
+++ b/indra/newview/rlvhelper.h
@@ -172,7 +172,7 @@ template<ERlvBehaviour templBhvr> using RlvForceHandler = RlvCommandHandler<RLV_
 template<ERlvBehaviour templBhvr> using RlvReplyHandler = RlvCommandHandler<RLV_TYPE_REPLY, templBhvr>;
 
 // List of shared handlers
-typedef RlvBehaviourToggleHandler<RLV_BHVR_SETCAM_EYEOFFSET> RlvBehaviourCamEyeFocusOffsetHandler;	// Shared between @setcam_eyeoffset and @setcam_focusoffset
+typedef RlvBehaviourToggleHandler<RLV_BHVR_SETCAM_EYEOFFSET> RlvBehaviourCamEyeFocusOffsetHandler;	// Shared between @setcam_eyeoffset, @setcam_eyeoffsetscale and @setcam_focusoffset
 typedef RlvBehaviourHandler<RLV_BHVR_REMATTACH> RlvBehaviourAddRemAttachHandler;					// Shared between @addattach and @remattach
 typedef RlvBehaviourHandler<RLV_BHVR_SENDCHANNEL> RlvBehaviourSendChannelHandler;					// Shared between @sendchannel and @sendchannel_except
 typedef RlvBehaviourHandler<RLV_BHVR_SENDIM> RlvBehaviourRecvSendStartIMHandler;					// Shared between @recvim, @sendim and @startim
@@ -181,7 +181,7 @@ typedef RlvBehaviourToggleHandler<RLV_BHVR_SHOWSELF> RlvBehaviourShowSelfToggleH
 typedef RlvBehaviourHandler<RLV_BHVR_CAMZOOMMIN> RlvBehaviourCamZoomMinMaxHandler;					// Shared between @camzoommin and @camzoommax (deprecated)
 typedef RlvReplyHandler<RLV_BHVR_GETCAM_AVDISTMIN> RlvReplyCamMinMaxModifierHandler;				// Shared between @getcam_avdistmin and @getcam_avdistmax
 typedef RlvForceHandler<RLV_BHVR_REMATTACH> RlvForceRemAttachHandler;								// Shared between @remattach and @detach
-typedef RlvForceHandler<RLV_BHVR_SETCAM_EYEOFFSET> RlvForceCamEyeFocusOffsetHandler;				// Shared between @setcam_eyeoffset and @setcam_focusoffset
+typedef RlvForceHandler<RLV_BHVR_SETCAM_EYEOFFSET> RlvForceCamEyeFocusOffsetHandler;				// Shared between @setcam_eyeoffset, @setcam_eyeoffsetscale and @setcam_focusoffset
 
 //
 // RlvCommandProcessor - Templated glue class that brings RlvBehaviourInfo, RlvCommandHandlerBaseImpl and RlvCommandHandler together
@@ -434,8 +434,8 @@ class RlvObject
 	 * Member functions
 	 */
 public:
-	bool addCommand(const RlvCommand& rlvCmd);
-	bool removeCommand(const RlvCommand& rlvCmd);
+	const RlvCommand& addCommand(const RlvCommand& rlvCmd, bool& fAdded);
+	bool              removeCommand(const RlvCommand& rlvCmd);
 
 	std::string getStatusString(const std::string& strFilter, const std::string& strSeparator) const;
 	bool        hasBehaviour(ERlvBehaviour eBehaviour, bool fStrictOnly) const;
diff --git a/indra/newview/rlvinventory.cpp b/indra/newview/rlvinventory.cpp
index 082cc6065b5d1eeeb2ed9b7a260bb0192915e838..e3d06e2bdc67686bc13d3570751a6e76cf2a8e6b 100644
--- a/indra/newview/rlvinventory.cpp
+++ b/indra/newview/rlvinventory.cpp
@@ -578,36 +578,56 @@ void RlvGiveToRLVOffer::onCategoryCreateCallback(LLUUID idFolder, RlvGiveToRLVOf
 	pInstance->onDestinationCreated(idFolder, pInstance->m_DestPath.front());
 }
 
-// Checked: 2014-01-07 (RLVa-1.4.10)
-void RlvGiveToRLVOffer::moveAndRename(const LLUUID& idFolder, const LLUUID& idDestination, const std::string& strName)
+// static
+void RlvGiveToRLVOffer::moveAndRename(const LLUUID& idFolder, const LLUUID& idDestination, const std::string& strName, const LLPointer<LLInventoryCallback> cbFinal)
 {
-	const LLViewerInventoryCategory* pDest = gInventory.getCategory(idDestination);
 	const LLViewerInventoryCategory* pFolder = gInventory.getCategory(idFolder);
-	if ( (pDest) && (pFolder) )
+	if ( (idDestination.notNull()) && (pFolder) )
 	{
-		LLPointer<LLViewerInventoryCategory> pNewFolder = new LLViewerInventoryCategory(pFolder);
-		if (pDest->getUUID() != pFolder->getParentUUID())
+		bool needsRename = (pFolder->getName() != strName);
+
+		LLPointer<LLInventoryCallback> cbMove;
+		if (idDestination != pFolder->getParentUUID())
 		{
-			LLInventoryModel::update_list_t update;
-			LLInventoryModel::LLCategoryUpdate updOldParent(pFolder->getParentUUID(), -1);
-			update.push_back(updOldParent);
-			LLInventoryModel::LLCategoryUpdate updNewParent(pDest->getUUID(), 1);
-			update.push_back(updNewParent);
-			gInventory.accountForUpdate(update);
-
-			pNewFolder->setParent(pDest->getUUID());
-			pNewFolder->updateParentOnServer(FALSE);
-		}
+			// We have to move *after* the rename operation completes or AIS will drop it
+			if (!needsRename)
+			{
+				LLInventoryModel::update_list_t update;
+				LLInventoryModel::LLCategoryUpdate updOldParent(pFolder->getParentUUID(), -1);
+				update.push_back(updOldParent);
+				LLInventoryModel::LLCategoryUpdate updNewParent(idDestination, 1);
+				update.push_back(updNewParent);
+				gInventory.accountForUpdate(update);
 
-		pNewFolder->rename(strName);
-		pNewFolder->updateServer(FALSE);
-		gInventory.updateCategory(pNewFolder);
+				LLPointer<LLViewerInventoryCategory> pNewFolder = new LLViewerInventoryCategory(pFolder);
+				pNewFolder->setParent(idDestination);
+				pNewFolder->updateParentOnServer(FALSE);
 
-		gInventory.notifyObservers();
+				gInventory.updateCategory(pNewFolder);
+				gInventory.notifyObservers();
+
+				if (cbFinal)
+				{
+					cbFinal.get()->fire(idFolder);
+				}
+			}
+			else
+			{
+				cbMove = new LLBoostFuncInventoryCallback(boost::bind(RlvGiveToRLVOffer::moveAndRename, _1, idDestination, strName, cbFinal));
+			}
+		}
+
+		if (needsRename)
+		{
+			rename_category(&gInventory, idFolder, strName, (cbMove) ? cbMove : cbFinal);
+		}
+	}
+	else if (cbFinal)
+	{
+		cbFinal.get()->fire(LLUUID::null);
 	}
 }
 
-// Checked: 2010-04-18 (RLVa-1.2.0)
 void RlvGiveToRLVTaskOffer::changed(U32 mask)
 {
 	if (mask & LLInventoryObserver::ADD)
@@ -633,7 +653,6 @@ void RlvGiveToRLVTaskOffer::changed(U32 mask)
 	}
 }
 
-// Checked: 2010-04-18 (RLVa-1.2.0)
 void RlvGiveToRLVTaskOffer::done()
 {
 	gInventory.removeObserver(this);
@@ -642,22 +661,29 @@ void RlvGiveToRLVTaskOffer::done()
 	doOnIdleOneTime(boost::bind(&RlvGiveToRLVTaskOffer::doneIdle, this));
 }
 
-// Checked: 2014-01-07 (RLVa-1.4.10)
 void RlvGiveToRLVTaskOffer::doneIdle()
 {
-	const LLViewerInventoryCategory* pFolder = (m_Folders.size()) ? gInventory.getCategory(m_Folders.front()) : NULL;
+	const LLViewerInventoryCategory* pFolder = (m_Folders.size()) ? gInventory.getCategory(m_Folders.front()) : nullptr;
 	if ( (!pFolder) || (!createDestinationFolder(pFolder->getName())) )
 		delete this;
 }
 
-// Checked: 2010-04-18 (RLVa-1.2.0)
-void RlvGiveToRLVTaskOffer::onDestinationCreated(const LLUUID& idFolder, const std::string& strName)
+void RlvGiveToRLVTaskOffer::onDestinationCreated(const LLUUID& idDestFolder, const std::string& strName)
+{
+	if (const LLViewerInventoryCategory* pTarget = (idDestFolder.notNull()) ? gInventory.getCategory(idDestFolder) : nullptr)
+	{
+		moveAndRename(m_Folders.front(), idDestFolder, strName, new LLBoostFuncInventoryCallback(boost::bind(&RlvGiveToRLVTaskOffer::onOfferCompleted, this, _1)));
+	}
+	else
+	{
+		onOfferCompleted(LLUUID::null);
+	}
+}
+
+void RlvGiveToRLVTaskOffer::onOfferCompleted(const LLUUID& idOfferedFolder)
 {
-	const LLViewerInventoryCategory* pTarget = (idFolder.notNull()) ? gInventory.getCategory(idFolder) : NULL;
-	if (pTarget)
+	if (idOfferedFolder.notNull())
 	{
-		const LLUUID& idOfferedFolder = m_Folders.front();
-		moveAndRename(idOfferedFolder, idFolder, strName);
 		RlvBehaviourNotifyHandler::sendNotification("accepted_in_rlv inv_offer " + RlvInventory::instance().getSharedPath(idOfferedFolder));
 	}
 	delete this;
@@ -684,7 +710,7 @@ void RlvGiveToRLVAgentOffer::doneIdle()
 void RlvGiveToRLVAgentOffer::onDestinationCreated(const LLUUID& idFolder, const std::string& strName)
 {
 	if ( (idFolder.notNull()) && (mComplete.size()) )
-		moveAndRename(mComplete[0], idFolder, strName);
+		moveAndRename(mComplete[0], idFolder, strName, nullptr);
 	delete this;
 }
 
diff --git a/indra/newview/rlvinventory.h b/indra/newview/rlvinventory.h
index 1279dff2f85bdbb670050ac848df3066ee773cec..2cb7f8bc8edac693c8a7cc9a22831631597f685d 100644
--- a/indra/newview/rlvinventory.h
+++ b/indra/newview/rlvinventory.h
@@ -131,8 +131,8 @@ class RlvGiveToRLVOffer
 	virtual ~RlvGiveToRLVOffer() {}
 protected:
 	bool         createDestinationFolder(const std::string& strPath);
-	virtual void onDestinationCreated(const LLUUID& idFolder, const std::string& strName) = 0;
-	void         moveAndRename(const LLUUID& idFolder, const LLUUID& idDestination, const std::string& strName);
+	virtual void onDestinationCreated(const LLUUID& idDestFolder, const std::string& strName) = 0;
+	static void  moveAndRename(const LLUUID& idFolder, const LLUUID& idDestination, const std::string& strName, LLPointer<LLInventoryCallback> cb);
 private:
 	static void  onCategoryCreateCallback(LLUUID idFolder, RlvGiveToRLVOffer* pInstance);
 
@@ -147,11 +147,12 @@ class RlvGiveToRLVTaskOffer : public LLInventoryObserver, RlvGiveToRLVOffer
 {
 public:
 	RlvGiveToRLVTaskOffer(const LLUUID& idTransaction) : RlvGiveToRLVOffer(), m_idTransaction(idTransaction) {}
-	/*virtual*/ void changed(U32 mask);
+	void changed(U32 mask) override;
 protected:
-	/*virtual*/ void done();
-	            void doneIdle();
-	/*virtual*/ void onDestinationCreated(const LLUUID& idFolder, const std::string& strName);
+	void done();
+	void doneIdle();
+	void onDestinationCreated(const LLUUID& idDestFolder, const std::string& strName) override;
+	void onOfferCompleted(const LLUUID& idOfferedFolder);
 
 protected:
 	typedef std::vector<LLUUID> folder_ref_t;
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index bf1ec6616bc4c4f875fc5ecfc39dda2dd2e62743..f5779ccf47f468f4a9f9c73baf444c81a4435826 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -764,9 +764,9 @@
 	     	 	<menu_item_call.on_click
 	     	 	 function="World.EnvSettings"
                  parameter="my_environs" />
-          <menu_item_call.on_enable
-           function="RLV.EnableIfNot"
-           parameter="setenv" />
+				<menu_item_call.on_enable
+				 function="RLV.EnableIfNot"
+				 parameter="setenv" />
 	     	</menu_item_call>
 	     	
 <menu_item_call
@@ -775,9 +775,9 @@
 	     	 	 	<menu_item_call.on_click
 	     	 	 	function="World.EnvSettings"
 	     	 	 	parameter="adjust_tool" />
-            <menu_item_call.on_enable
-            function="RLV.EnableIfNot"
-            parameter="setenv" />
+					<menu_item_call.on_enable
+					 function="RLV.EnableIfNot"
+					 parameter="setenv" />
 	     	 	</menu_item_call>
 	     	 	<menu_item_separator/>
 	     	 	 <menu_item_check
@@ -825,7 +825,6 @@ label="Edit preset..."
 <menu_item_call.on_click
 function="World.EnvPreset"
 	     	 	 	parameter="edit_water"/>
-</menu_item_call>
 </menu>
 	     	 	 <menu
 name="Sky Presets"