From 056d8cff552f80550ec277e8f0afcc019e1dec42 Mon Sep 17 00:00:00 2001
From: Kitty Barnett <develop@catznip.com>
Date: Mon, 16 May 2016 01:32:12 +0200
Subject: [PATCH] Added @setcam=n|y, @setcam_eyeoffset[:<vector3>]=n|y,
 @setcam_focusoffset[:<vector3>]=n|y, @setcam_fov[:<angle>]=n|y,
 @setcam_fovmin[:<angle>]=n|y and @setcam_fovmax[:<angle>]=n|y

--HG--
branch : RLVa
---
 indra/newview/llagentcamera.cpp  |  30 ++-
 indra/newview/llagentcamera.h    |   7 +-
 indra/newview/llviewercamera.cpp |   7 +
 indra/newview/rlvactions.cpp     |  61 +++++-
 indra/newview/rlvactions.h       |  33 ++-
 indra/newview/rlvcommon.h        |   2 +-
 indra/newview/rlvdefines.h       |  17 +-
 indra/newview/rlvhandler.cpp     | 334 +++++++++++++++++++++++--------
 indra/newview/rlvhandler.h       |   5 +
 indra/newview/rlvhelper.cpp      |  51 ++++-
 indra/newview/rlvhelper.h        |  23 ++-
 11 files changed, 459 insertions(+), 111 deletions(-)

diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp
index a02ddccb6b..637326acd0 100755
--- a/indra/newview/llagentcamera.cpp
+++ b/indra/newview/llagentcamera.cpp
@@ -212,10 +212,18 @@ void LLAgentCamera::init()
 	mCameraOffsetInitial[CAMERA_PRESET_REAR_VIEW] = gSavedSettings.getControl("CameraOffsetRearView");
 	mCameraOffsetInitial[CAMERA_PRESET_FRONT_VIEW] = gSavedSettings.getControl("CameraOffsetFrontView");
 	mCameraOffsetInitial[CAMERA_PRESET_GROUP_VIEW] = gSavedSettings.getControl("CameraOffsetGroupView");
+// [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]
 
 	mFocusOffsetInitial[CAMERA_PRESET_REAR_VIEW] = gSavedSettings.getControl("FocusOffsetRearView");
 	mFocusOffsetInitial[CAMERA_PRESET_FRONT_VIEW] = gSavedSettings.getControl("FocusOffsetFrontView");
 	mFocusOffsetInitial[CAMERA_PRESET_GROUP_VIEW] = gSavedSettings.getControl("FocusOffsetGroupView");
+// [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);
+// [/RLVa:KB]
 
 	mCameraCollidePlane.clearVec();
 	mCurrentCameraDistance = getCameraOffsetInitial().magVec() * gSavedSettings.getF32("CameraOffsetScale");
@@ -1147,7 +1155,7 @@ void LLAgentCamera::updateCamera()
 
 // [RLVa:KB] - Checked: RLVa-2.0.0
 	// Set focus back on our avie if something changed it
-	if ( (gRlvHandler.hasBehaviour(RLV_BHVR_CAMUNLOCK)) && (cameraThirdPerson()) && (!getFocusOnAvatar()) )
+	if ( (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_UNLOCK)) && (cameraThirdPerson()) && (!getFocusOnAvatar()) )
 	{
 		setFocusOnAvatar(TRUE, FALSE);
 	}
@@ -2333,6 +2341,26 @@ void LLAgentCamera::changeCameraToCustomizeAvatar()
 
 void LLAgentCamera::switchCameraPreset(ECameraPreset preset)
 {
+// [RLVa:KB] - Checked: RLVa-2.0.0
+	if (RlvActions::isRlvEnabled())
+	{
+		// Don't allow changing away from the 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)
+		{
+			mCameraOffsetInitial[CAMERA_RLV_SETCAM_VIEW]->resetToDefault();
+			mFocusOffsetInitial[CAMERA_RLV_SETCAM_VIEW]->resetToDefault();
+		}
+	}
+// [/RLVa:KB]
+
 	//zoom is supposed to be reset for the front and group views
 	mCameraZoomFraction = 1.f;
 
diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h
index ab793ff316..3a448de783 100755
--- a/indra/newview/llagentcamera.h
+++ b/indra/newview/llagentcamera.h
@@ -56,7 +56,12 @@ enum ECameraPreset
 	CAMERA_PRESET_FRONT_VIEW, 
 
 	/** "Above and to the left, over the shoulder, pulled back a little on the zoom" */
-	CAMERA_PRESET_GROUP_VIEW
+	CAMERA_PRESET_GROUP_VIEW,
+
+// [RLVa:KB] - Checked: RLVa-2.0.0
+	/* Used by RLVa */
+	CAMERA_RLV_SETCAM_VIEW
+// [/RLVa:KB]
 };
 
 //------------------------------------------------------------------------
diff --git a/indra/newview/llviewercamera.cpp b/indra/newview/llviewercamera.cpp
index ac09961402..314f6417de 100755
--- a/indra/newview/llviewercamera.cpp
+++ b/indra/newview/llviewercamera.cpp
@@ -42,6 +42,7 @@
 #include "lltoolmgr.h"
 #include "llviewerjoystick.h"
 // [RLVa:KB] - Checked: 2010-04-11 (RLVa-1.2.0e)
+#include "rlvactions.h"
 #include "rlvhandler.h"
 // [/RLVa:KB]
 
@@ -882,6 +883,12 @@ BOOL LLViewerCamera::areVertsVisible(LLViewerObject* volumep, BOOL all_verts)
 
 void LLViewerCamera::setDefaultFOV(F32 vertical_fov_rads) 
 {
+// [RLVa:KB] - Checked: RLVa-2.0.0
+	F32 nCamFOVMin, nCamFOVMax;
+	if ( (RlvActions::isRlvEnabled()) && (RlvActions::getCameraFOVLimits(nCamFOVMin, nCamFOVMax)) )
+		vertical_fov_rads = llclamp(vertical_fov_rads, nCamFOVMin, nCamFOVMax);
+// [/RLVa:KB]
+
 	vertical_fov_rads = llclamp(vertical_fov_rads, getMinView(), getMaxView());
 	setView(vertical_fov_rads);
 	mCameraFOVDefault = vertical_fov_rads; 
diff --git a/indra/newview/rlvactions.cpp b/indra/newview/rlvactions.cpp
index 102729a68b..89bba202fd 100644
--- a/indra/newview/rlvactions.cpp
+++ b/indra/newview/rlvactions.cpp
@@ -1,27 +1,70 @@
-/** 
+/**
  *
- * Copyright (c) 2009-2013, Kitty Barnett
- * 
- * The source code in this file is provided to you under the terms of the 
+ * Copyright (c) 2009-2016, 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 
+ * 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 "llimview.h"
+#include "llviewercamera.h"
 #include "llvoavatarself.h"
 #include "rlvactions.h"
 #include "rlvhelper.h"
 #include "rlvhandler.h"
 
+// ============================================================================
+// Camera
+//
+
+bool RlvActions::canChangeCameraPreset(const LLUUID& idRlvObject)
+{
+	// NOTE: if an object has exclusive camera controls 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));
+}
+
+bool RlvActions::canChangeCameraFOV(const LLUUID& idRlvObject)
+{
+	// NOTE: if an object has exclusive camera controls then all other objects are locked out
+	return (!gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM)) || (gRlvHandler.hasBehaviour(idRlvObject, RLV_BHVR_SETCAM));
+}
+
+bool RlvActions::isCameraPresetLocked()
+{
+	return (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM)) || (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_EYEOFFSET)) || (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOCUSOFFSET));
+}
+
+bool RlvActions::isCameraFOVClamped()
+{
+	return (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOVMIN)) || (gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOVMAX));
+}
+
+bool RlvActions::getCameraFOVLimits(F32& nFOVMin, F32& nFOVMax)
+{
+	static RlvCachedBehaviourModifier<float> sCamFovMin(RLV_MODIFIER_SETCAM_FOVMIN);
+	static RlvCachedBehaviourModifier<float> sCamFovMax(RLV_MODIFIER_SETCAM_FOVMAX);
+
+	bool fClampMax = gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOVMAX);
+	nFOVMax = (fClampMax) ? sCamFovMax : LLViewerCamera::getInstance()->getMaxView();
+
+	bool fClampMin = gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_FOVMIN);
+	nFOVMin = (fClampMin) ? sCamFovMin : LLViewerCamera::getInstance()->getMinView();
+
+	return (fClampMin) || (fClampMax);
+}
+
 // ============================================================================
 // Communication/Avatar interaction
 // 
diff --git a/indra/newview/rlvactions.h b/indra/newview/rlvactions.h
index 9d616f0925..e93060f384 100644
--- a/indra/newview/rlvactions.h
+++ b/indra/newview/rlvactions.h
@@ -25,6 +25,35 @@
 
 class RlvActions
 {
+	// ======
+	// Camera
+	// ======
+public:
+	/*
+	 * Returns true if the specified object can manipulate the camera offset and/or focus offset values
+	 */
+	static bool canChangeCameraPreset(const LLUUID& idRlvObject);
+
+	/*
+	 * Returns true if the specified object cannot manipulate the camera FOV
+	 */
+	static bool canChangeCameraFOV(const LLUUID& idRlvObject);
+
+	/*
+	 * Returns true if the camera offset and focus offset are locked (prevents changing the current camera preset)
+	 */
+	static bool isCameraPresetLocked();
+
+	/*
+	 * Returns true if the camera's FOV is currently restricted/clamped
+	 */
+	static bool isCameraFOVClamped();
+
+	/*
+	 * Retrieves the current camera FOV limits - returns isCameraFOVClamped()
+	 */
+	static bool getCameraFOVLimits(F32& nFOVMin, F32& nFOVMax);
+
 	// ================================
 	// Communication/Avatar interaction
 	// ================================
@@ -122,13 +151,13 @@ public:
 	// ================
 public:
 	/*
-	 * Convenience function to check for a behaviour without having to include rlvhandler.h. 
+	 * Convenience function to check for a behaviour without having to include rlvhandler.h
 	 * Do NOT call this function if speed is important (i.e. per-frame)
 	 */
 	static bool hasBehaviour(ERlvBehaviour eBhvr);
 
 	/*
-	 * Returns true if a - P2P or group - IM session is open with the specified UUID.
+	 * Returns true if a - P2P or group - IM session is open with the specified UUID
 	 */
 	static bool hasOpenP2PSession(const LLUUID& idAgent);
 	static bool hasOpenGroupSession(const LLUUID& idGroup);
diff --git a/indra/newview/rlvcommon.h b/indra/newview/rlvcommon.h
index 89da91c9d0..4dde21887d 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> RlvBehaviourModifierValue;
+typedef boost::variant<int, float, LLVector3> RlvBehaviourModifierValue;
 
 class RlvGCTimer;
 
diff --git a/indra/newview/rlvdefines.h b/indra/newview/rlvdefines.h
index c136c9921d..7814545bd0 100644
--- a/indra/newview/rlvdefines.h
+++ b/indra/newview/rlvdefines.h
@@ -157,7 +157,6 @@ enum ERlvBehaviour {
 	RLV_BHVR_SETENV,				// "setenv"
 	RLV_BHVR_ALWAYSRUN,				// "alwaysrun"
 	RLV_BHVR_TEMPRUN,				// "temprun"
-	RLV_BHVR_CAMUNLOCK,
 	RLV_BHVR_DETACHME,				// "detachme"
 	RLV_BHVR_ATTACHTHIS,			// "attachthis"
 	RLV_BHVR_ATTACHTHISEXCEPT,		// "attachthis_except"
@@ -165,7 +164,6 @@ enum ERlvBehaviour {
 	RLV_BHVR_DETACHTHISEXCEPT,		// "detachthis_except"
 	RLV_BHVR_ADJUSTHEIGHT,			// "adjustheight"
 	RLV_BHVR_TPTO,					// "tpto"
-	RLV_BHVR_CAMFOCUS,
 	RLV_BHVR_VERSION,				// "version"
 	RLV_BHVR_VERSIONNEW,			// "versionnew"
 	RLV_BHVR_VERSIONNUM,			// "versionnum"
@@ -190,12 +188,27 @@ enum ERlvBehaviour {
 	RLV_BHVR_GETSTATUSALL,			// "getstatusall"
 	RLV_CMD_FORCEWEAR,				// Internal representation of all force wear commands
 
+	// Camera
+	RLV_BHVR_SETCAM,                // Gives an object exclusive control of the user's camera
+	RLV_BHVR_SETCAM_EYEOFFSET,      // Changes the default camera offset
+	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
+	RLV_BHVR_SETCAM_FOVMIN,			// Enforces a minimum (vertical) FOV
+	RLV_BHVR_SETCAM_FOVMAX,			// Enforces a maximum (vertical) FOV
+	RLV_BHVR_SETCAM_UNLOCK,			// Forces the camera focus to the user's avatar
+
 	RLV_BHVR_COUNT,
 	RLV_BHVR_UNKNOWN
 };
 
 enum ERlvBehaviourModifier
 {
+	RLV_MODIFIER_SETCAM_EYEOFFSET,
+	RLV_MODIFIER_SETCAM_FOCUSOFFSET,
+	RLV_MODIFIER_SETCAM_FOVMIN,
+	RLV_MODIFIER_SETCAM_FOVMAX,
+
 	RLV_MODIFIER_COUNT,
 	RLV_MODIFIER_UNKNOWN
 };
diff --git a/indra/newview/rlvhandler.cpp b/indra/newview/rlvhandler.cpp
index be4bdb39c2..5309c8d79b 100644
--- a/indra/newview/rlvhandler.cpp
+++ b/indra/newview/rlvhandler.cpp
@@ -30,7 +30,7 @@
 #include "llviewerregion.h"
 
 // Command specific includes
-#include "llagentcamera.h"				// @camfocus
+#include "llagentcamera.h"				// @setcam and related
 #include "llenvmanager.h"				// @setenv
 #include "lloutfitslist.h"				// @showinv - "Appearance / My Outfits" panel
 #include "llpaneloutfitsinventory.h"	// @showinv - "Appearance" floater
@@ -38,9 +38,10 @@
 #include "llsidepanelappearance.h"		// @showinv - "Appearance / Edit appearance" panel
 #include "lltabcontainer.h"				// @showinv - Tab container control for inventory tabs
 #include "lltoolmgr.h"					// @edit
-#include "llviewercamera.h"				// @camfocus
+#include "llviewercamera.h"				// @setcam and related
 
 // RLVa includes
+#include "rlvactions.h"
 #include "rlvfloaters.h"
 #include "rlvhandler.h"
 #include "rlvhelper.h"
@@ -138,6 +139,23 @@ RlvHandler::~RlvHandler()
 // Behaviour related functions
 //
 
+bool RlvHandler::findBehaviour(ERlvBehaviour eBhvr, std::list<const RlvObject*>& lObjects) const
+{
+	lObjects.clear();
+	for (const auto& objEntry : m_Objects)
+		if (objEntry.second.hasBehaviour(eBhvr, false))
+			lObjects.push_back(&objEntry.second);
+	return !lObjects.empty();
+}
+
+bool RlvHandler::hasBehaviour(const LLUUID& idRlvObj, ERlvBehaviour eBhvr, const std::string& strOption) const
+{
+	rlv_object_map_t::const_iterator itObj = m_Objects.find(idRlvObj);
+	if (m_Objects.end() != itObj)
+		return itObj->second.hasBehaviour(eBhvr, strOption, false);
+	return false;
+}
+
 bool RlvHandler::hasBehaviourExcept(ERlvBehaviour eBhvr, const std::string& strOption, const LLUUID& idObj) const
 {
 	for (rlv_object_map_t::const_iterator itObj = m_Objects.begin(); itObj != m_Objects.end(); ++itObj)
@@ -296,7 +314,7 @@ ERlvCmdRet RlvHandler::processCommand(const RlvCommand& rlvCmd, bool fFromObj)
 		case RLV_TYPE_ADD:		// Checked: 2009-11-26 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f
 			{
 				if ( (m_Behaviours[rlvCmd.getBehaviourType()]) && 
-					 ( (RLV_BHVR_SETDEBUG == rlvCmd.getBehaviourType()) || (RLV_BHVR_SETENV == rlvCmd.getBehaviourType()) ) )
+					 ( (RLV_BHVR_SETCAM == rlvCmd.getBehaviourType()) || (RLV_BHVR_SETDEBUG == rlvCmd.getBehaviourType()) || (RLV_BHVR_SETENV == rlvCmd.getBehaviourType()) ) )
 				{
 					// 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;
@@ -466,70 +484,6 @@ bool RlvHandler::handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD&
 	return false;
 }
 
-// Handles: @camfocus:<uuid>[;<dist>[;<direction>]]=force
-template<> template<>
-ERlvCmdRet RlvForceHandler<RLV_BHVR_CAMFOCUS>::onCommand(const RlvCommand& rlvCmd)
-{
-	std::vector<std::string> optionList;
-	if (!RlvCommandOptionHelper::parseStringList(rlvCmd.getOption(), optionList))
-		return RLV_RET_FAILED_OPTION;
-
-	LLVector3 posAgent;
-	LLVector3d posGlobal;
-	F32 camDistance;
-
-	// Get the focus position/object (and verify it is known)
-	LLUUID idObject; LLVector3 posRegion;
-	if (RlvCommandOptionHelper::parseOption(optionList[0], idObject))
-	{
-		const LLViewerObject* pObj = gObjectList.findObject(idObject);
-		if (!pObj)
-			return RLV_RET_FAILED_OPTION;
-		posAgent = pObj->getPositionAgent();
-		posGlobal = pObj->getPositionGlobal();
-		camDistance = pObj->getScale().magVec();
-	}
-	else if (RlvCommandOptionHelper::parseOption(optionList[0], posRegion))
-	{
-		const LLViewerRegion* pRegion = gAgent.getRegion();
-		if (!pRegion)
-			return RLV_RET_FAILED_UNKNOWN;
-		posAgent = pRegion->getPosAgentFromRegion(posRegion);
-		posGlobal = pRegion->getPosGlobalFromRegion(posRegion);
-		camDistance = 0.0f;
-	}
-	else
-	{
-		return RLV_RET_FAILED_OPTION;
-	}
-
-	// Get the camera distance
-	if ( (optionList.size() > 1) && (!optionList[1].empty()) )
-	{
-		if (!RlvCommandOptionHelper::parseOption(optionList[1], camDistance))
-			return RLV_RET_FAILED_OPTION;
-	}
-
-	// Get the directional vector (or calculate it from the current camera position)
-	LLVector3 camDirection;
-	if ( (optionList.size() > 2) && (!optionList[2].empty()) )
-	{
-		if (!RlvCommandOptionHelper::parseOption(optionList[2], camDirection))
-			return RLV_RET_FAILED_OPTION;
-	}
-	else
-	{
-		camDirection = LLViewerCamera::getInstance()->getOrigin() - posAgent;
-	}
-	camDirection.normVec();
-
-	// Move the camera in place
-	gAgentCamera.setFocusOnAvatar(FALSE, ANIMATE);
-	gAgentCamera.setCameraPosAndFocusGlobal(posGlobal + LLVector3d(camDirection * llmax(F_APPROXIMATELY_ZERO, camDistance)), posGlobal, idObject);
-
-	return RLV_RET_SUCCESS;
-}
-
 // Checked: 2010-08-29 (RLVa-1.2.1c) | Modified: RLVa-1.2.1c
 void RlvHandler::onSitOrStand(bool fSitting)
 {
@@ -1436,10 +1390,19 @@ ERlvCmdRet RlvBehaviourGenericHandler<RLV_OPTION_MODIFIER>::onCommand(const RlvC
 	if ( (!rlvCmd.hasOption()) || (!pBhvrModifier) || (!pBhvrModifier->convertOptionValue(rlvCmd.getOption(), modValue)) )
 		return RLV_RET_FAILED_OPTION;
 
+	// HACK-RLVa: reference counting doesn't happen until control returns to our caller but the modifier callbacks will happen now so we need to adjust the reference counts here
 	if (RLV_TYPE_ADD == rlvCmd.getParamType())
+	{
+		gRlvHandler.m_Behaviours[rlvCmd.getBehaviourType()]++;
 		pBhvrModifier->addValue(modValue, rlvCmd.getObjectID());
+		gRlvHandler.m_Behaviours[rlvCmd.getBehaviourType()]--;
+	}
 	else
+	{
+		gRlvHandler.m_Behaviours[rlvCmd.getBehaviourType()]--;
 		pBhvrModifier->removeValue(modValue, rlvCmd.getObjectID());
+		gRlvHandler.m_Behaviours[rlvCmd.getBehaviourType()]++;
+	}
 
 	fRefCount = true;
 	return RLV_RET_SUCCESS;
@@ -1499,16 +1462,6 @@ ERlvCmdRet RlvBehaviourAddRemAttachHandler::onCommand(const RlvCommand& rlvCmd,
 	return RLV_RET_SUCCESS;
 }
 
-// Handles: @sendim=n|y toggles
-template<> template<>
-void RlvBehaviourHandler<RLV_BHVR_CAMUNLOCK>::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr)
-{
-	if (fHasBhvr)
-	{
-		handle_reset_view();
-	}
-}
-
 // Handles: @detach[:<attachpt>]=n|y
 template<> template<>
 ERlvCmdRet RlvBehaviourHandler<RLV_BHVR_DETACH>::onCommand(const RlvCommand& rlvCmd, bool& fRefCount)
@@ -1676,7 +1629,114 @@ void RlvBehaviourToggleHandler<RLV_BHVR_SENDIM>::onCommandToggle(ERlvBehaviour e
 	gSavedPerAccountSettings.getControl("DoNotDisturbModeResponse")->setHiddenFromSettingsEditor(fHasBhvr);
 }
 
-// Handles: @edit=n|y toggles
+// Handles: @setcam_unlock=n|y toggles
+template<> template<>
+void RlvBehaviourToggleHandler<RLV_BHVR_SETCAM_UNLOCK>::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr)
+{
+	if (fHasBhvr)
+		handle_reset_view();
+}
+
+// Handles: @setcam_eyeoffset:<vector3>=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);
+	}
+	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);
+	}
+}
+
+// Handles: @setcam_eyeoffset:<vector3>=n|y changes
+template<>
+void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_EYEOFFSET>::onValueChange() const
+{
+	if (RlvBehaviourModifier* pBhvrModifier = RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_EYEOFFSET))
+	{
+		LLControlVariable* pControl = gSavedSettings.getControl("CameraOffsetRLVaView");
+		if (pBhvrModifier->hasValue())
+			pControl->setValue(pBhvrModifier->getValue<LLVector3>().getValue());
+		else
+			pControl->resetToDefault();
+	}
+}
+
+// Handles: @setcam_focusoffset:<vector3>=n|y changes
+template<>
+void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOCUSOFFSET>::onValueChange() const
+{
+	if (RlvBehaviourModifier* pBhvrModifier = RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_FOCUSOFFSET))
+	{
+		LLControlVariable* pControl = gSavedSettings.getControl("FocusOffsetRLVaView");
+		if (pBhvrModifier->hasValue())
+			pControl->setValue(pBhvrModifier->getValue<LLVector3>().getValue());
+		else
+			pControl->resetToDefault();
+	}
+}
+
+// Handles: @setcam_fovmin:<angle>=n|y changes
+template<>
+void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOVMIN>::onValueChange() const
+{
+	LLViewerCamera::instance().setDefaultFOV(LLViewerCamera::instance().getDefaultFOV());
+}
+
+// Handles: @setcam_fovmax:<angle>=n|y changes
+template<>
+void RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOVMAX>::onValueChange() const
+{
+	LLViewerCamera::instance().setDefaultFOV(LLViewerCamera::instance().getDefaultFOV());
+}
+
+// Handles: @setcam=n|y toggles
+template<> template<>
+void RlvBehaviourToggleHandler<RLV_BHVR_SETCAM>::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr)
+{
+	// Once an object has exclusive control over the camera only its behaviours should be active. This affects:
+	//   - RLV_BHVR_SETCAM_EYEOFFSET   => behaviour modifiers handle this for us
+	//   - RLV_BHVR_SETCAM_FOCUSOFFSET => behaviour modifiers handle this for us
+	//   - RLV_BHVR_SETCAM_FOVMIN      => behaviour modifiers handle this for us
+	//   - RLV_BHVR_SETCAM_FOVMAX      => behaviour modifiers handle this for us
+	//   - RLV_BHVR_SETCAM_UNLOCK      => manually (re)set the reference count (and possibly invoke the toggle handler)
+
+	LLUUID idRlvObject; bool fHasCamUnlock = gRlvHandler.hasBehaviour(RLV_BHVR_SETCAM_UNLOCK);
+	if (fHasBhvr)
+	{
+		// Get the UUID of the primary object
+		std::list<const RlvObject*> lObjects;
+		gRlvHandler.findBehaviour(RLV_BHVR_SETCAM, lObjects);
+		idRlvObject = lObjects.front()->getObjectID();
+		// Reset the @setcam_unlock reference count
+		gRlvHandler.m_Behaviours[RLV_BHVR_SETCAM_UNLOCK] = (lObjects.front()->hasBehaviour(RLV_BHVR_SETCAM_UNLOCK, false)) ? 1 : 0;
+	}
+	else
+	{
+		std::list<const RlvObject*> lObjects;
+		// Restore the @setcam_unlock reference count
+		gRlvHandler.findBehaviour(RLV_BHVR_SETCAM_UNLOCK, lObjects);
+		gRlvHandler.m_Behaviours[RLV_BHVR_SETCAM_UNLOCK] = lObjects.size();
+	}
+
+	// Manually invoke the @setcam_unlock toggle handler if we toggled it on/off
+	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 );
+	RlvBehaviourDictionary::instance().getModifier(RLV_MODIFIER_SETCAM_EYEOFFSET)->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);
+}
+
+// Handles: @setdebug=n|y toggles
 template<> template<>
 void RlvBehaviourToggleHandler<RLV_BHVR_SETDEBUG>::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr)
 {
@@ -1954,6 +2014,124 @@ ERlvCmdRet RlvForceHandler<RLV_BHVR_REMOUTFIT>::onCommand(const RlvCommand& rlvC
 	return RLV_RET_SUCCESS;
 }
 
+// Handles: @setcam_eyeoffset[:<vector3>]=force and @setcam_focusoffset[:<vector3>]=force
+template<> template<>
+ERlvCmdRet RlvForceCamEyeFocusOffsetHandler::onCommand(const RlvCommand& rlvCmd)
+{
+	// Enforce exclusive camera locks
+	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())
+	{
+		LLVector3 vecOffset;
+		if (!RlvCommandOptionHelper::parseOption(rlvCmd.getOption(), vecOffset))
+			return RLV_RET_FAILED_OPTION;
+		pControl->setValue(vecOffset.getValue());
+	}
+	else
+	{
+		pControl->resetToDefault();
+	}
+
+	gAgentCamera.switchCameraPreset( ((pOffsetControl->isDefault()) && (pFocusControl->isDefault())) ? CAMERA_PRESET_REAR_VIEW : CAMERA_RLV_SETCAM_VIEW);
+	return RLV_RET_SUCCESS;
+}
+
+// Handles: @setcam_focus:<uuid>[;<dist>[;<direction>]]=force
+template<> template<>
+ERlvCmdRet RlvForceHandler<RLV_BHVR_SETCAM_FOCUS>::onCommand(const RlvCommand& rlvCmd)
+{
+	std::vector<std::string> optionList;
+	if (!RlvCommandOptionHelper::parseStringList(rlvCmd.getOption(), optionList))
+		return RLV_RET_FAILED_OPTION;
+
+	LLVector3 posAgent;
+	LLVector3d posGlobal;
+	F32 camDistance;
+
+	// Get the focus position/object (and verify it is known)
+	LLUUID idObject; LLVector3 posRegion;
+	if (RlvCommandOptionHelper::parseOption(optionList[0], idObject))
+	{
+		const LLViewerObject* pObj = gObjectList.findObject(idObject);
+		if (!pObj)
+			return RLV_RET_FAILED_OPTION;
+		if (!pObj->isAvatar())
+		{
+			posAgent = pObj->getPositionAgent();
+			posGlobal = pObj->getPositionGlobal();
+		}
+		else
+		{
+			/*const*/ LLVOAvatar* pAvatar = (/*const*/ LLVOAvatar*)pObj;
+			if (pAvatar->mHeadp)
+			{
+				posAgent = pAvatar->mHeadp->getWorldPosition();
+				posGlobal = pAvatar->getPosGlobalFromAgent(posAgent);
+			}
+		}
+		camDistance = pObj->getScale().magVec();
+	}
+	else if (RlvCommandOptionHelper::parseOption(optionList[0], posRegion))
+	{
+		const LLViewerRegion* pRegion = gAgent.getRegion();
+		if (!pRegion)
+			return RLV_RET_FAILED_UNKNOWN;
+		posAgent = pRegion->getPosAgentFromRegion(posRegion);
+		posGlobal = pRegion->getPosGlobalFromRegion(posRegion);
+		camDistance = 0.0f;
+	}
+	else
+	{
+		return RLV_RET_FAILED_OPTION;
+	}
+
+	// Get the camera distance
+	if ( (optionList.size() > 1) && (!optionList[1].empty()) )
+	{
+		if (!RlvCommandOptionHelper::parseOption(optionList[1], camDistance))
+			return RLV_RET_FAILED_OPTION;
+	}
+
+	// Get the directional vector (or calculate it from the current camera position)
+	LLVector3 camDirection;
+	if ( (optionList.size() > 2) && (!optionList[2].empty()) )
+	{
+		if (!RlvCommandOptionHelper::parseOption(optionList[2], camDirection))
+			return RLV_RET_FAILED_OPTION;
+	}
+	else
+	{
+		camDirection = LLViewerCamera::getInstance()->getOrigin() - posAgent;
+	}
+	camDirection.normVec();
+
+	// Move the camera in place
+	gAgentCamera.setFocusOnAvatar(FALSE, ANIMATE);
+	gAgentCamera.setCameraPosAndFocusGlobal(posGlobal + LLVector3d(camDirection * llmax(F_APPROXIMATELY_ZERO, camDistance)), posGlobal, idObject);
+
+	return RLV_RET_SUCCESS;
+}
+
+// Handles: @setcam_fov[:<angle>]=force
+template<> template<>
+ERlvCmdRet RlvForceHandler<RLV_BHVR_SETCAM_FOV>::onCommand(const RlvCommand& rlvCmd)
+{
+	if (!RlvActions::canChangeCameraFOV(rlvCmd.getObjectID()))
+		return RLV_RET_FAILED_LOCK;
+
+	F32 nFOV = DEFAULT_FIELD_OF_VIEW;
+	if ( (rlvCmd.hasOption()) && (!RlvCommandOptionHelper::parseOption(rlvCmd.getOption(), nFOV)) )
+		return RLV_RET_FAILED_OPTION;
+
+	LLViewerCamera::getInstance()->setDefaultFOV(nFOV);
+	return RLV_RET_SUCCESS;
+}
+
 // Checked: 2010-08-30 (RLVa-1.2.1c) | Modified: RLVa-1.2.1c
 ERlvCmdRet RlvHandler::onForceWear(const LLViewerInventoryCategory* pFolder, U32 nFlags) const
 {
diff --git a/indra/newview/rlvhandler.h b/indra/newview/rlvhandler.h
index c389d9ca3f..35d9160b5d 100644
--- a/indra/newview/rlvhandler.h
+++ b/indra/newview/rlvhandler.h
@@ -44,9 +44,13 @@ public:
 	//       - to check @remoutfit=n -> (see RlvWearableLocks)
 	//       - to check exceptions   -> isException()
 public:
+	// Returns a list of all objects containing the specified behaviour
+	bool findBehaviour(ERlvBehaviour eBhvr, std::list<const RlvObject*>& lObjects) const;
 	// Returns TRUE is at least one object contains the specified behaviour (and optional option)
 	bool hasBehaviour(ERlvBehaviour eBhvr) const { return (eBhvr < RLV_BHVR_COUNT) ? (0 != m_Behaviours[eBhvr]) : false; }
 	bool hasBehaviour(ERlvBehaviour eBhvr, const std::string& strOption) const;
+	// Returns TRUE if the specified object contains the specified behaviour (and optional option)
+	bool hasBehaviour(const LLUUID& idObj, ERlvBehaviour eBhvr, const std::string& strOption = LLStringUtil::null) const;
 	// Returns TRUE if at least one object (except the specified one) contains the specified behaviour (and optional option)
 	bool hasBehaviourExcept(ERlvBehaviour eBhvr, const LLUUID& idObj) const;
 	bool hasBehaviourExcept(ERlvBehaviour eBhvr, const std::string& strOption, const LLUUID& idObj) const;
@@ -207,6 +211,7 @@ protected:
 
 	friend class RlvSharedRootFetcher;				// Fetcher needs access to m_fFetchComplete
 	friend class RlvGCTimer;						// Timer clear its own point at destruction
+	template<ERlvBehaviourOptionType optionType> friend struct RlvBehaviourGenericHandler;
 	template<ERlvParamType> friend struct RlvCommandHandlerBaseImpl;
 	template<ERlvParamType, ERlvBehaviour> friend struct RlvCommandHandler;
 
diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp
index 3c9ee3408b..6237653cbe 100644
--- a/indra/newview/rlvhelper.cpp
+++ b/indra/newview/rlvhelper.cpp
@@ -32,9 +32,6 @@
 // RlvBehaviourDictionary
 //
 
-static RlvBehaviourModifier_CompMin s_RlvBehaviourModifier_CompMin;
-static RlvBehaviourModifier_CompMax s_RlvBehaviourModifier_CompMax;
-
 /*
  * Processing of RLVa commands used to be a big switch/case loop with one function for each command type(addrem, reply
  * and force). This is slowly being replaced with templated command handling which might be more confusing intially
@@ -91,7 +88,6 @@ RlvBehaviourDictionary::RlvBehaviourDictionary()
 	addEntry(new RlvBehaviourInfo("attachallthis",			RLV_BHVR_ATTACHTHIS,			RLV_TYPE_ADDREM, RlvBehaviourInfo::FORCEWEAR_SUBTREE));
 	addEntry(new RlvBehaviourInfo("attachthis_except",		RLV_BHVR_ATTACHTHISEXCEPT,		RLV_TYPE_ADDREM, RlvBehaviourInfo::FORCEWEAR_NODE));
 	addEntry(new RlvBehaviourInfo("attachallthis_except",	RLV_BHVR_ATTACHTHISEXCEPT,		RLV_TYPE_ADDREM, RlvBehaviourInfo::FORCEWEAR_SUBTREE));
-	addEntry(new RlvBehaviourToggleProcessor<RLV_BHVR_CAMUNLOCK, RLV_OPTION_NONE>("camunlock"));
 	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_NONE>("chatwhisper", RLV_BHVR_CHATWHISPER));
 	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_NONE>("chatnormal", RLV_BHVR_CHATNORMAL));
 	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_NONE>("chatshout", RLV_BHVR_CHATSHOUT));
@@ -163,6 +159,17 @@ RlvBehaviourDictionary::RlvBehaviourDictionary()
 	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_NONE>("viewnote", RLV_BHVR_VIEWNOTE));
 	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_NONE>("viewscript", RLV_BHVR_VIEWSCRIPT));
 	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_NONE>("viewtexture", RLV_BHVR_VIEWTEXTURE));
+	// Camera
+	addEntry(new RlvBehaviourToggleProcessor<RLV_BHVR_SETCAM, RLV_OPTION_NONE>("setcam"));
+	addEntry(new RlvBehaviourToggleProcessor<RLV_BHVR_SETCAM_EYEOFFSET, RLV_OPTION_MODIFIER, RlvBehaviourCamEyeFocusOffsetHandler>("setcam_eyeoffset", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
+	addModifier(RLV_BHVR_SETCAM_EYEOFFSET, RLV_MODIFIER_SETCAM_EYEOFFSET, new RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_EYEOFFSET>(LLVector3::zero, true, nullptr));
+	addEntry(new RlvBehaviourToggleProcessor<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>(LLVector3::zero, true, nullptr));
+	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_MODIFIER>("setcam_fovmin", RLV_BHVR_SETCAM_FOVMIN));
+	addModifier(RLV_BHVR_SETCAM_FOVMIN, RLV_MODIFIER_SETCAM_FOVMIN, new RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOVMIN>(DEFAULT_FIELD_OF_VIEW, true, new RlvBehaviourModifier_CompMax()));
+	addEntry(new RlvBehaviourGenericProcessor<RLV_OPTION_MODIFIER>("setcam_fovmax", RLV_BHVR_SETCAM_FOVMAX));
+	addModifier(RLV_BHVR_SETCAM_FOVMAX, RLV_MODIFIER_SETCAM_FOVMAX, new RlvBehaviourModifierHandler<RLV_MODIFIER_SETCAM_FOVMAX>(DEFAULT_FIELD_OF_VIEW, true, new RlvBehaviourModifier_CompMin()));
+	addEntry(new RlvBehaviourToggleProcessor<RLV_BHVR_SETCAM_UNLOCK, RLV_OPTION_NONE>("setcam_unlock"));
 
 	//
 	// Force-wear
@@ -200,8 +207,11 @@ RlvBehaviourDictionary::RlvBehaviourDictionary()
 	// Force-only
 	//
 	addEntry(new RlvBehaviourInfo("adjustheight",			RLV_BHVR_ADJUSTHEIGHT,			RLV_TYPE_FORCE));
-	addEntry(new RlvForceProcessor<RLV_BHVR_CAMFOCUS>("camfocus", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
 	addEntry(new RlvForceProcessor<RLV_BHVR_DETACHME>("detachme"));
+	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_FOV>("setcam_fov", RlvBehaviourInfo::BHVR_EXPERIMENTAL));
 	addEntry(new RlvForceProcessor<RLV_BHVR_SETGROUP>("setgroup"));
 	addEntry(new RlvForceProcessor<RLV_BHVR_SIT>("sit"));
 	addEntry(new RlvForceProcessor<RLV_BHVR_TPTO>("tpto"));
@@ -368,8 +378,18 @@ void RlvBehaviourDictionary::toggleBehaviourFlag(const std::string& strBhvr, ERl
 //
 
 RlvBehaviourModifier::RlvBehaviourModifier(const RlvBehaviourModifierValue& defaultValue, bool fAddDefaultOnEmpty, RlvBehaviourModifier_Comp* pValueComparator)
-	: m_DefaultValue(defaultValue), m_fAddDefaultOnEmpty(fAddDefaultOnEmpty), m_pValueComparator(pValueComparator)
+	: m_DefaultValue(defaultValue), m_fAddDefaultOnEmpty(fAddDefaultOnEmpty)
+{
+	m_pValueComparator = (pValueComparator) ? pValueComparator : new RlvBehaviourModifier_Comp();
+}
+
+RlvBehaviourModifier::~RlvBehaviourModifier()
 {
+	if (m_pValueComparator)
+	{
+		delete m_pValueComparator;
+		m_pValueComparator = NULL;
+	}
 }
 
 bool RlvBehaviourModifier::addValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject)
@@ -377,13 +397,21 @@ bool RlvBehaviourModifier::addValue(const RlvBehaviourModifierValue& modValue, c
 	if (modValue.which() == m_DefaultValue.which())
 	{
 		m_Values.insert((m_pValueComparator) ? std::lower_bound(m_Values.begin(), m_Values.end(), std::make_pair(modValue, idObject), boost::bind(&RlvBehaviourModifier_Comp::operator(), m_pValueComparator, _1, _2)) : m_Values.end(), std::make_pair(modValue, idObject));
-		onValueChange();
+		// NOTE: change signal needs to trigger before modifier handlers so cached values have a chance to update properly
 		m_ChangeSignal(getValue());
+		onValueChange();
 		return true;
 	}
 	return false;
 }
 
+bool RlvBehaviourModifier::hasValue() const {
+	// If no primary object is set this returns "any value set"; otherwise it returns "any value set by the primary object"
+	if ( (!m_pValueComparator) || (m_pValueComparator->m_idPrimaryObject.isNull()) )
+		return !m_Values.empty();
+	return (!m_Values.empty()) ? m_Values.front().second == m_pValueComparator->m_idPrimaryObject : false;
+}
+
 void RlvBehaviourModifier::removeValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject)
 {
 	if ( (modValue.which() == m_DefaultValue.which()) )
@@ -423,6 +451,15 @@ bool RlvBehaviourModifier::convertOptionValue(const std::string& optionValue, Rl
 			modValue = std::stoi(optionValue);
 			return true;
 		}
+		else if (typeid(LLVector3) == m_DefaultValue.type())
+		{
+			LLVector3 vecOption;
+			if (3 == sscanf(optionValue.c_str(), "%f/%f/%f", vecOption.mV + 0, vecOption.mV + 1, vecOption.mV + 2))
+			{
+				modValue = vecOption;
+				return true;
+			}
+		}
 		return false;
 	}
 	catch (const std::invalid_argument&)
diff --git a/indra/newview/rlvhelper.h b/indra/newview/rlvhelper.h
index 50b2850776..f8e94468dd 100644
--- a/indra/newview/rlvhelper.h
+++ b/indra/newview/rlvhelper.h
@@ -114,8 +114,10 @@ template<ERlvBehaviour eBhvr> using RlvForceHandler = RlvCommandHandler<RLV_TYPE
 template<ERlvBehaviour eBhvr> using RlvReplyHandler = RlvCommandHandler<RLV_TYPE_REPLY, eBhvr>;
 
 // List of shared handlers
-typedef RlvBehaviourHandler<RLV_BHVR_REMATTACH> RlvBehaviourAddRemAttachHandler;	// Shared between @addattach and @remattach
-typedef RlvForceHandler<RLV_BHVR_REMATTACH> RlvForceRemAttachHandler;				// Shared between @remattach and @detach
+typedef RlvBehaviourToggleHandler<RLV_BHVR_SETCAM_EYEOFFSET> RlvBehaviourCamEyeFocusOffsetHandler;	// Shared between @setcam_eyeoffset and @setcam_focusoffset
+typedef RlvBehaviourHandler<RLV_BHVR_REMATTACH> RlvBehaviourAddRemAttachHandler;					// Shared between @addattach and @remattach
+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
 
 //
 // RlvCommandProcessor - Templated glue class that brings RlvBehaviourInfo, RlvCommandHandlerBaseImpl and RlvCommandHandler together
@@ -197,8 +199,8 @@ struct RlvBehaviourModifier_CompMax : public RlvBehaviourModifier_Comp
 class RlvBehaviourModifier
 {
 public:
-	RlvBehaviourModifier(const RlvBehaviourModifierValue& defaultValue, bool fAddDefaultOnEmpty, RlvBehaviourModifier_Comp* pValueComparator);
-	virtual ~RlvBehaviourModifier() {}
+	RlvBehaviourModifier(const RlvBehaviourModifierValue& defaultValue, bool fAddDefaultOnEmpty, RlvBehaviourModifier_Comp* pValueComparator = nullptr);
+	virtual ~RlvBehaviourModifier();
 
 	/*
 	 * Member functions
@@ -206,14 +208,15 @@ public:
 protected:
 	virtual void onValueChange() const {}
 public:
-	bool addValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject);
-	bool convertOptionValue(const std::string& optionValue, RlvBehaviourModifierValue& modValue) const;
-	bool getAddDefault() const { return m_fAddDefaultOnEmpty; }
+	bool                             addValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject);
+	bool                             convertOptionValue(const std::string& optionValue, RlvBehaviourModifierValue& modValue) const;
+	bool                             getAddDefault() const { return m_fAddDefaultOnEmpty; }
 	const RlvBehaviourModifierValue& getDefaultValue() const { return m_DefaultValue; }
-	const RlvBehaviourModifierValue& getValue() const { return (!m_Values.empty()) ? m_Values.front().first : m_DefaultValue; }
+	const RlvBehaviourModifierValue& getValue() const { return (hasValue()) ? m_Values.front().first : m_DefaultValue; }
 	template<typename T> const T&    getValue() const { return boost::get<T>(getValue()); }
-	void removeValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject);
-	void setPrimaryObject(const LLUUID& idPrimaryObject);
+	bool                             hasValue() const;
+	void                             removeValue(const RlvBehaviourModifierValue& modValue, const LLUUID& idObject);
+	void                             setPrimaryObject(const LLUUID& idPrimaryObject);
 
 	typedef boost::signals2::signal<void(const RlvBehaviourModifierValue& newValue)> change_signal_t;
 	change_signal_t& getSignal() { return m_ChangeSignal; }
-- 
GitLab