From fbd23878fc718dc443a0861ec6dd46d17d6c2908 Mon Sep 17 00:00:00 2001
From: Kitty Barnett <develop@catznip.com>
Date: Sun, 19 Apr 2020 14:12:00 +0200
Subject: [PATCH] Rewrite all @setenv related commands against the new
 atmospherics/environment class

---
 indra/llinventory/llsettingssky.cpp |   4 +-
 indra/newview/CMakeLists.txt        |   2 +
 indra/newview/rlvenvironment.cpp    | 657 ++++++++++++++++++++++++++++
 indra/newview/rlvenvironment.h      |  65 +++
 indra/newview/rlvextensions.cpp     | 382 +---------------
 indra/newview/rlvhandler.cpp        |   2 +
 indra/newview/rlvhelper.cpp         |  29 ++
 7 files changed, 765 insertions(+), 376 deletions(-)
 create mode 100644 indra/newview/rlvenvironment.cpp
 create mode 100644 indra/newview/rlvenvironment.h

diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp
index 306c7329205..02dcd47a47c 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/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index a6b98bb457a..b04ff8f614e 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -726,6 +726,7 @@ set(viewer_SOURCE_FILES
     noise.cpp
     pipeline.cpp
     rlvactions.cpp
+    rlvenvironment.cpp
     rlvhandler.cpp
     rlvhelper.cpp
     rlvcommon.cpp
@@ -1355,6 +1356,7 @@ set(viewer_HEADER_FILES
     noise.h
     pipeline.h
     rlvactions.h
+    rlvenvironment.h
     rlvdefines.h
     rlvhandler.h
     rlvhelper.h
diff --git a/indra/newview/rlvenvironment.cpp b/indra/newview/rlvenvironment.cpp
new file mode 100644
index 00000000000..83423994856
--- /dev/null
+++ b/indra/newview/rlvenvironment.cpp
@@ -0,0 +1,657 @@
+/**
+ *
+ * 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 "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;
+													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]);
+
+													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(LLEnvironment::ENV_LOCAL)) {
+													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
+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 <= VBLUE)
+		{
+			strEnvCommand.pop_back();
+
+			legacy_handler_map_t::const_iterator itLegacyFnEntry = legacyFnLookup.find(strEnvCommand);
+			if (legacyFnLookup.end() != itLegacyFnEntry)
+			{
+				cmdRet = itLegacyFnEntry->second(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 > 3)
+		return LLStringUtil::null;
+	return std::to_string(getFn(LLEnvironment::instance().getCurrentSky()).mV[idxComponent]);
+}
+
+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)
+{
+	if (idxComponent > 3)
+		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<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, [this, getFn](const std::string& strRlvParam)
+		{
+			if (RlvUtil::sendChatReply(strRlvParam, getFn(LLEnvironment::ENV_LOCAL)))
+				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, [this, setFn](const std::string& strRlvOption)
+		{
+			T optionValue;
+			if (!RlvCommandOptionHelper::parseOption<T>(strRlvOption, optionValue))
+				return RLV_RET_FAILED_PARAM;
+			return setFn(LLEnvironment::ENV_LOCAL, 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 00000000000..9ad60f72645
--- /dev/null
+++ b/indra/newview/rlvenvironment.h
@@ -0,0 +1,65 @@
+/**
+ *
+ * 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:
+	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 cabf1891eb2..4612331c96f 100644
--- a/indra/newview/rlvextensions.cpp
+++ b/indra/newview/rlvextensions.cpp
@@ -1,25 +1,23 @@
-/** 
+/**
  *
  * 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"
 #include "llagent.h"
 #include "llagentcamera.h"
-//#include "lldaycyclemanager.h"
 #include "llvoavatarself.h"
-//#include "llwlparammanager.h"
 
 #include "rlvextensions.h"
 #include "rlvhandler.h"
@@ -27,352 +25,6 @@
 
 // ============================================================================
 
-//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;
 std::map<std::string, std::string> RlvExtGetSet::m_PseudoDebug;
 
@@ -439,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 3f4bfaf2ccd..71a6b51b153 100644
--- a/indra/newview/rlvhandler.cpp
+++ b/indra/newview/rlvhandler.cpp
@@ -56,6 +56,7 @@
 
 // RLVa includes
 #include "rlvactions.h"
+#include "rlvenvironment.h"
 #include "rlvfloaters.h"
 #include "rlvactions.h"
 #include "rlvhandler.h"
@@ -1433,6 +1434,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
diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp
index 6a4375d6cfb..a58c5b7abd4 100644
--- a/indra/newview/rlvhelper.cpp
+++ b/indra/newview/rlvhelper.cpp
@@ -685,6 +685,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 +773,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 +806,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)
 {
-- 
GitLab