summaryrefslogtreecommitdiff
path: root/indra/newview
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview')
-rw-r--r--indra/newview/CMakeLists.txt11
-rw-r--r--indra/newview/app_settings/settings.xml96
-rw-r--r--indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl2
-rw-r--r--indra/newview/app_settings/shaders/class1/interface/terrainStampF.glsl44
-rw-r--r--indra/newview/app_settings/shaders/class1/interface/terrainStampV.glsl39
-rw-r--r--indra/newview/llagent.cpp14
-rw-r--r--indra/newview/llagent.h7
-rw-r--r--indra/newview/llappviewer.cpp8
-rw-r--r--indra/newview/llchathistory.cpp1
-rw-r--r--indra/newview/llconversationlog.cpp20
-rw-r--r--indra/newview/lldirpicker.cpp36
-rw-r--r--indra/newview/lldirpicker.h2
-rw-r--r--indra/newview/lldrawpoolterrain.cpp8
-rw-r--r--indra/newview/llfilepicker.h8
-rw-r--r--indra/newview/llfloaterimsessiontab.cpp15
-rw-r--r--indra/newview/llfloaterinspect.cpp3
-rw-r--r--indra/newview/llfloaterland.cpp3
-rw-r--r--indra/newview/llfloaterpreference.cpp11
-rw-r--r--indra/newview/llfloaterpreference.h2
-rw-r--r--indra/newview/llfloaterregioninfo.cpp177
-rw-r--r--indra/newview/llfloaterregioninfo.h32
-rwxr-xr-xindra/newview/llfloaterworldmap.cpp7
-rw-r--r--indra/newview/lllogchat.cpp48
-rw-r--r--indra/newview/llmodelpreview.cpp14
-rw-r--r--indra/newview/llnotificationlistitem.cpp21
-rw-r--r--indra/newview/llpanelenvironment.cpp23
-rw-r--r--indra/newview/llpanelgrouproles.cpp3
-rw-r--r--indra/newview/llpanellandmarkinfo.cpp4
-rw-r--r--indra/newview/llpanelteleporthistory.cpp15
-rw-r--r--indra/newview/llsettingsvo.cpp3
-rw-r--r--indra/newview/llsidepaneliteminfo.cpp3
-rw-r--r--indra/newview/llstartup.cpp3
-rw-r--r--indra/newview/llterrainpaintmap.cpp712
-rw-r--r--indra/newview/llterrainpaintmap.h128
-rw-r--r--indra/newview/lltexturefetch.cpp4
-rw-r--r--indra/newview/lltoastgroupnotifypanel.cpp19
-rw-r--r--indra/newview/llviewercontrol.cpp4
-rw-r--r--indra/newview/llviewerfloaterreg.cpp2
-rw-r--r--indra/newview/llviewermenu.cpp102
-rw-r--r--indra/newview/llviewermessage.cpp42
-rwxr-xr-xindra/newview/llviewerregion.cpp4
-rw-r--r--indra/newview/llviewershadermgr.cpp29
-rw-r--r--indra/newview/llviewershadermgr.h1
-rw-r--r--indra/newview/llvlcomposition.cpp4
-rw-r--r--indra/newview/llvlcomposition.h16
-rw-r--r--indra/newview/llvoicevisualizer.cpp2
-rw-r--r--indra/newview/llworldmap.cpp18
-rw-r--r--indra/newview/rlvactions.cpp42
-rw-r--r--indra/newview/rlvactions.h46
-rw-r--r--indra/newview/rlvcommon.cpp134
-rw-r--r--indra/newview/rlvcommon.h72
-rw-r--r--indra/newview/rlvdefines.h194
-rw-r--r--indra/newview/rlvfloaters.cpp122
-rw-r--r--indra/newview/rlvfloaters.h68
-rw-r--r--indra/newview/rlvhandler.cpp225
-rw-r--r--indra/newview/rlvhandler.h80
-rw-r--r--indra/newview/rlvhelper.cpp389
-rw-r--r--indra/newview/rlvhelper.h299
-rw-r--r--indra/newview/skins/default/xui/da/sidepanel_item_info.xml3
-rw-r--r--indra/newview/skins/default/xui/de/floater_about_land.xml3
-rw-r--r--indra/newview/skins/default/xui/de/floater_inspect.xml5
-rw-r--r--indra/newview/skins/default/xui/de/panel_classified_info.xml2
-rw-r--r--indra/newview/skins/default/xui/de/panel_landmark_info.xml5
-rw-r--r--indra/newview/skins/default/xui/de/panel_place_profile.xml5
-rw-r--r--indra/newview/skins/default/xui/de/panel_profile_classified.xml2
-rw-r--r--indra/newview/skins/default/xui/de/panel_profile_secondlife.xml6
-rw-r--r--indra/newview/skins/default/xui/de/panel_status_bar.xml2
-rw-r--r--indra/newview/skins/default/xui/de/sidepanel_item_info.xml5
-rw-r--r--indra/newview/skins/default/xui/de/strings.xml8
-rw-r--r--indra/newview/skins/default/xui/en/floater_about_land.xml3
-rw-r--r--indra/newview/skins/default/xui/en/floater_inspect.xml4
-rw-r--r--indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml4
-rw-r--r--indra/newview/skins/default/xui/en/floater_rlv_console.xml74
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml98
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml2
-rw-r--r--indra/newview/skins/default/xui/en/panel_landmark_info.xml4
-rw-r--r--indra/newview/skins/default/xui/en/panel_place_profile.xml4
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_general.xml31
-rw-r--r--indra/newview/skins/default/xui/en/sidepanel_item_info.xml4
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml28
-rw-r--r--indra/newview/tests/llworldmap_test.cpp4
81 files changed, 3442 insertions, 305 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 6c8d7b910e..3bf01d252d 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -734,6 +734,11 @@ set(viewer_SOURCE_FILES
llxmlrpctransaction.cpp
noise.cpp
pipeline.cpp
+ rlvactions.cpp
+ rlvcommon.cpp
+ rlvfloaters.cpp
+ rlvhelper.cpp
+ rlvhandler.cpp
)
set(VIEWER_BINARY_NAME "secondlife-bin" CACHE STRING
@@ -1399,6 +1404,12 @@ set(viewer_HEADER_FILES
llxmlrpctransaction.h
noise.h
pipeline.h
+ rlvdefines.h
+ rlvactions.h
+ rlvcommon.h
+ rlvfloaters.h
+ rlvhelper.h
+ rlvhandler.h
roles_constants.h
VertexCache.h
VorbisFramework.h
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index b11ba8955a..35d1542c6c 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -2469,6 +2469,17 @@
<key>Value</key>
<integer>1</integer>
</map>
+ <key>UseGroupMemberPagination</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable pagination of group memeber list 50 members at a time.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>DisplayTimecode</key>
<map>
<key>Comment</key>
@@ -10113,6 +10124,83 @@
<key>Value</key>
<string>https://feedback.secondlife.com/</string>
</map>
+ <key>RestrainedLove</key>
+ <map>
+ <key>Comment</key>
+ <string>Toggles RLVa features (requires restart)</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>RestrainedLoveDebug</key>
+ <map>
+ <key>Comment</key>
+ <string>Toggles RLVa debug mode (displays the commands when in debug mode)</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>RLVaBlockedExperiences</key>
+ <map>
+ <key>Comment</key>
+ <string>List of experiences blocked from interacting with RLVa</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>bfe25fb4-222c-11e5-85a2-fa4c4ccaa202</string>
+ </map>
+ <key>RLVaDebugHideUnsetDuplicate</key>
+ <map>
+ <key>Comment</key>
+ <string>Suppresses reporting "unset" or "duplicate" command restrictions when RestrainedLoveDebug is TRUE</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>0</boolean>
+ </map>
+ <key>RLVaEnableTemporaryAttachments</key>
+ <map>
+ <key>Comment</key>
+ <string>Allows temporary attachments (regardless of origin) to issue RLV commands</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>RLVaExperimentalCommands</key>
+ <map>
+ <key>Comment</key>
+ <string>Enables the experimental command set</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
+ <key>RLVaTopLevelMenu</key>
+ <map>
+ <key>Comment</key>
+ <string>Show the RLVa specific menu as a top level menu</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <boolean>1</boolean>
+ </map>
<key>RevokePermsOnStopAnimation</key>
<map>
<key>Comment</key>
@@ -13000,9 +13088,9 @@
<key>Use24HourClock</key>
<map>
<key>Comment</key>
- <string>12 vs 24. At the moment only for region restart schedule floater</string>
+ <string>12 vs 24. At the moment coverage is partial</string>
<key>Persist</key>
- <integer>0</integer>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -13657,7 +13745,7 @@
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
- <integer>0</integer>
+ <integer>1</integer>
</map>
<key>WarningsAsChat</key>
<map>
@@ -15301,7 +15389,7 @@
<key>Type</key>
<string>U32</string>
<key>Value</key>
- <integer>5</integer>
+ <integer>8</integer>
</map>
<key>TerrainPaintResolution</key>
<map>
diff --git a/indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl b/indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl
index cf20653a0f..a79a56d725 100644
--- a/indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl
+++ b/indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl
@@ -1,5 +1,5 @@
/**
- * @file terrainBakeF.glsl
+ * @file pbrTerrainBakeF.glsl
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
diff --git a/indra/newview/app_settings/shaders/class1/interface/terrainStampF.glsl b/indra/newview/app_settings/shaders/class1/interface/terrainStampF.glsl
new file mode 100644
index 0000000000..e79e9010e6
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/interface/terrainStampF.glsl
@@ -0,0 +1,44 @@
+/**
+ * @file terrainStampF.glsl
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+/*[EXTRA_CODE_HERE]*/
+
+out vec4 frag_color;
+
+// Paint texture to stamp into the "paintmap"
+uniform sampler2D diffuseMap;
+
+in vec2 vary_texcoord0;
+
+void main()
+{
+ vec4 col = texture(diffuseMap, vary_texcoord0);
+ if (col.a <= 0.0f)
+ {
+ discard;
+ }
+
+ frag_color = max(col, vec4(0));
+}
diff --git a/indra/newview/app_settings/shaders/class1/interface/terrainStampV.glsl b/indra/newview/app_settings/shaders/class1/interface/terrainStampV.glsl
new file mode 100644
index 0000000000..294fa6be26
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/interface/terrainStampV.glsl
@@ -0,0 +1,39 @@
+/**
+ * @file terrainStampV.glsl
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+uniform mat4 modelview_projection_matrix;
+uniform vec2 terrain_stamp_scale;
+
+in vec3 position;
+
+out vec2 vary_texcoord0;
+
+void main()
+{
+ gl_Position = modelview_projection_matrix * vec4(position, 1.0);
+ // Positions without transforms are treated as UVs for the purpose of this shader.
+ vary_texcoord0.xy = terrain_stamp_scale * position.xy;
+}
+
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index ca35608175..3d4f5e1054 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -355,20 +355,6 @@ bool LLAgent::isMicrophoneOn(const LLSD& sdname)
return LLVoiceClient::getInstance()->getUserPTTState();
}
-//static
-void LLAgent::toggleHearMediaSoundFromAvatar()
-{
- const S32 mediaSoundsEarLocation = gSavedSettings.getS32("MediaSoundsEarLocation");
- gSavedSettings.setS32("MediaSoundsEarLocation", !mediaSoundsEarLocation);
-}
-
-//static
-void LLAgent::toggleHearVoiceFromAvatar()
-{
- const S32 voiceEarLocation = gSavedSettings.getS32("VoiceEarLocation");
- gSavedSettings.setS32("VoiceEarLocation", !voiceEarLocation);
-}
-
// ************************************************************
// Enabled this definition to compile a 'hacked' viewer that
// locally believes the end user has godlike powers.
diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h
index 448ee575f5..489131d974 100644
--- a/indra/newview/llagent.h
+++ b/indra/newview/llagent.h
@@ -377,13 +377,6 @@ private:
bool mVoiceConnected;
//--------------------------------------------------------------------
- // Sound
- //--------------------------------------------------------------------
-public:
- static void toggleHearMediaSoundFromAvatar();
- static void toggleHearVoiceFromAvatar();
-
- //--------------------------------------------------------------------
// Chat
//--------------------------------------------------------------------
public:
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index acc79ed994..46681af808 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -255,6 +255,10 @@ using namespace LL;
#include "llcoproceduremanager.h"
#include "llviewereventrecorder.h"
+#include "rlvactions.h"
+#include "rlvcommon.h"
+#include "rlvhandler.h"
+
// *FIX: These extern globals should be cleaned up.
// The globals either represent state/config/resource-storage of either
// this app, or another 'component' of the viewer. App globals should be
@@ -1818,6 +1822,9 @@ bool LLAppViewer::cleanup()
//ditch LLVOAvatarSelf instance
gAgentAvatarp = NULL;
+ // Sanity check to catch cases where someone forgot to do an RlvActions::isRlvEnabled() check
+ LL_ERRS_IF(!RlvHandler::isEnabled() && RlvHandler::instanceExists()) << "RLV handler instance exists even though RLVa is disabled" << LL_ENDL;
+
LLNotifications::instance().clear();
// workaround for DEV-35406 crash on shutdown
@@ -3456,6 +3463,7 @@ LLSD LLAppViewer::getViewerInfo() const
}
#endif
+ info["RLV_VERSION"] = RlvActions::isRlvEnabled() ? Rlv::Strings::getVersionAbout() : "(disabled)";
info["OPENGL_VERSION"] = ll_safe_string((const char*)(glGetString(GL_VERSION)));
// Settings
diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp
index 3e02820feb..bb5ee558ca 100644
--- a/indra/newview/llchathistory.cpp
+++ b/indra/newview/llchathistory.cpp
@@ -426,6 +426,7 @@ public:
time_t current_time = time_corrected();
time_t message_time = (time_t)(current_time - LLFrameTimer::getElapsedSeconds() + mTime);
+ // Report abuse shouldn't use AM/PM, use 24-hour time
time_string = "[" + LLTrans::getString("TimeMonth") + "]/["
+ LLTrans::getString("TimeDay") + "]/["
+ LLTrans::getString("TimeYear") + "] ["
diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp
index ed563cbec9..ed16a4b135 100644
--- a/indra/newview/llconversationlog.cpp
+++ b/indra/newview/llconversationlog.cpp
@@ -118,11 +118,21 @@ const std::string LLConversation::createTimestamp(const U64Seconds& utc_time)
LLSD substitution;
substitution["datetime"] = (S32)utc_time.value();
- timeStr = "["+LLTrans::getString ("TimeMonth")+"]/["
- +LLTrans::getString ("TimeDay")+"]/["
- +LLTrans::getString ("TimeYear")+"] ["
- +LLTrans::getString ("TimeHour")+"]:["
- +LLTrans::getString ("TimeMin")+"]";
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ timeStr = "[" + LLTrans::getString("TimeMonth") + "]/["
+ + LLTrans::getString("TimeDay") + "]/["
+ + LLTrans::getString("TimeYear") + "] [";
+ if (use_24h)
+ {
+ timeStr += LLTrans::getString("TimeHour") + "]:["
+ + LLTrans::getString("TimeMin") + "]";
+ }
+ else
+ {
+ timeStr += LLTrans::getString("TimeHour12") + "]:["
+ + LLTrans::getString("TimeMin") + "] ["
+ + LLTrans::getString("TimeAMPM") + "]";
+ }
LLStringUtil::format (timeStr, substitution);
diff --git a/indra/newview/lldirpicker.cpp b/indra/newview/lldirpicker.cpp
index 51157fa430..17edca7ccb 100644
--- a/indra/newview/lldirpicker.cpp
+++ b/indra/newview/lldirpicker.cpp
@@ -41,11 +41,6 @@
# include "llfilepicker.h"
#endif
-#ifdef LL_FLTK
- #include "FL/Fl.H"
- #include "FL/Fl_Native_File_Chooser.H"
-#endif
-
#if LL_WINDOWS
#include <shlobj.h>
#endif
@@ -219,28 +214,20 @@ LLDirPicker::LLDirPicker() :
mFileName(NULL),
mLocked(false)
{
-#ifndef LL_FLTK
mFilePicker = new LLFilePicker();
-#endif
reset();
}
LLDirPicker::~LLDirPicker()
{
-#ifndef LL_FLTK
delete mFilePicker;
-#endif
}
void LLDirPicker::reset()
{
-#ifndef LL_FLTK
if (mFilePicker)
mFilePicker->reset();
-#else
- mDir = "";
-#endif
}
bool LLDirPicker::getDir(std::string* filename, bool blocking)
@@ -253,39 +240,16 @@ bool LLDirPicker::getDir(std::string* filename, bool blocking)
return false;
}
-#ifdef LL_FLTK
- gViewerWindow->getWindow()->beforeDialog();
- Fl_Native_File_Chooser flDlg;
- flDlg.title(LLTrans::getString("choose_the_directory").c_str());
- flDlg.type(Fl_Native_File_Chooser::BROWSE_DIRECTORY );
- int res = flDlg.show();
- gViewerWindow->getWindow()->afterDialog();
- if( res == 0 )
- {
- char const *pDir = flDlg.filename(0);
- if( pDir )
- mDir = pDir;
- }
- else if( res == -1 )
- {
- LL_WARNS() << "FLTK failed: " << flDlg.errmsg() << LL_ENDL;
- }
- return !mDir.empty();
-#endif
return false;
}
std::string LLDirPicker::getDirName()
{
-#ifndef LL_FLTK
if (mFilePicker)
{
return mFilePicker->getFirstFile();
}
return "";
-#else
- return mDir;
-#endif
}
#else // not implemented
diff --git a/indra/newview/lldirpicker.h b/indra/newview/lldirpicker.h
index 2ac3db7c2e..dc740caab2 100644
--- a/indra/newview/lldirpicker.h
+++ b/indra/newview/lldirpicker.h
@@ -77,10 +77,8 @@ private:
#if LL_LINUX || LL_DARWIN
// On Linux we just implement LLDirPicker on top of LLFilePicker
-#ifndef LL_FLTK
LLFilePicker *mFilePicker;
#endif
-#endif
std::string* mFileName;
diff --git a/indra/newview/lldrawpoolterrain.cpp b/indra/newview/lldrawpoolterrain.cpp
index 5e676bc5b3..57def49539 100644
--- a/indra/newview/lldrawpoolterrain.cpp
+++ b/indra/newview/lldrawpoolterrain.cpp
@@ -296,7 +296,7 @@ void LLDrawPoolTerrain::renderFullShaderTextures()
// GL_BLEND disabled by default
drawLoop();
- // Disable multitexture
+ // Disable textures
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_DETAIL0);
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_DETAIL1);
@@ -557,7 +557,7 @@ void LLDrawPoolTerrain::renderFullShaderPBR(bool use_local_materials)
// GL_BLEND disabled by default
drawLoop();
- // Disable multitexture
+ // Disable textures
if (paint_type == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE)
{
@@ -769,7 +769,7 @@ void LLDrawPoolTerrain::renderFull4TU()
}
LLVertexBuffer::unbind();
- // Disable multitexture
+ // Disable textures
gGL.getTexUnit(3)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(3)->disable();
gGL.getTexUnit(3)->activate();
@@ -949,7 +949,7 @@ void LLDrawPoolTerrain::renderFull2TU()
// Restore blend state
gGL.setSceneBlendType(LLRender::BT_ALPHA);
- // Disable multitexture
+ // Disable textures
gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(1)->disable();
diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h
index e0bd32fe70..4d71a3b392 100644
--- a/indra/newview/llfilepicker.h
+++ b/indra/newview/llfilepicker.h
@@ -174,14 +174,6 @@ private:
void *userdata);
#endif
-#if LL_FLTK
- enum EType
- {
- eSaveFile, eOpenFile, eOpenMultiple
- };
- bool openFileDialog( int32_t filter, bool blocking, EType aType );
-#endif
-
std::vector<std::string> mFiles;
S32 mCurrentFile;
bool mLocked;
diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp
index 6a96dc0c69..0855a628fb 100644
--- a/indra/newview/llfloaterimsessiontab.cpp
+++ b/indra/newview/llfloaterimsessiontab.cpp
@@ -629,8 +629,19 @@ void LLFloaterIMSessionTab::deleteAllChildren()
std::string LLFloaterIMSessionTab::appendTime()
{
- std::string timeStr = "[" + LLTrans::getString("TimeHour") + "]:"
- "[" + LLTrans::getString("TimeMin") + "]";
+ std::string timeStr;
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ if (use_24h)
+ {
+ timeStr = "[" + LLTrans::getString("TimeHour") + "]:"
+ "[" + LLTrans::getString("TimeMin") + "]";
+ }
+ else
+ {
+ timeStr = "[" + LLTrans::getString("TimeHour12") + "]:"
+ "[" + LLTrans::getString("TimeMin") + "] ["
+ + LLTrans::getString("TimeAMPM") + "]";
+ }
LLSD substitution;
substitution["datetime"] = (S32)time_corrected();
diff --git a/indra/newview/llfloaterinspect.cpp b/indra/newview/llfloaterinspect.cpp
index 0f1eb0cef0..5dea46843c 100644
--- a/indra/newview/llfloaterinspect.cpp
+++ b/indra/newview/llfloaterinspect.cpp
@@ -220,7 +220,8 @@ void LLFloaterInspect::refresh()
}
time_t timestamp = (time_t) (obj->mCreationDate/1000000);
- std::string timeStr = getString("timeStamp");
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ std::string timeStr = use_24h ? getString("timeStamp") : getString("timeStampAMPM");
LLSD substitution;
substitution["datetime"] = (S32) timestamp;
LLStringUtil::format (timeStr, substitution);
diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp
index 52a3e78d04..5c5219bcdd 100644
--- a/indra/newview/llfloaterland.cpp
+++ b/indra/newview/llfloaterland.cpp
@@ -733,7 +733,8 @@ void LLPanelLandGeneral::refresh()
// Display claim date
time_t claim_date = parcel->getClaimDate();
- std::string claim_date_str = getString("time_stamp_template");
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ std::string claim_date_str = use_24h ? getString("time_stamp_template") : getString("time_stamp_template_ampm");
LLSD substitution;
substitution["datetime"] = (S32) claim_date;
LLStringUtil::format (claim_date_str, substitution);
diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp
index 0c96c93d31..d7ac8f0552 100644
--- a/indra/newview/llfloaterpreference.cpp
+++ b/indra/newview/llfloaterpreference.cpp
@@ -473,6 +473,8 @@ bool LLFloaterPreference::postBuild()
getChild<LLUICtrl>("log_path_string")->setEnabled(false); // make it read-only but selectable
getChild<LLComboBox>("language_combobox")->setCommitCallback(boost::bind(&LLFloaterPreference::onLanguageChange, this));
+ mTimeFormatCombobox = getChild<LLComboBox>("time_format_combobox");
+ mTimeFormatCombobox->setCommitCallback(boost::bind(&LLFloaterPreference::onTimeFormatChange, this));
getChild<LLComboBox>("FriendIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"FriendIMOptions"));
getChild<LLComboBox>("NonFriendIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"NonFriendIMOptions"));
@@ -1108,6 +1110,13 @@ void LLFloaterPreference::onLanguageChange()
}
}
+void LLFloaterPreference::onTimeFormatChange()
+{
+ std::string val = mTimeFormatCombobox->getValue();
+ gSavedSettings.setBOOL("Use24HourClock", val == "1");
+ onLanguageChange();
+}
+
void LLFloaterPreference::onNotificationsChange(const std::string& OptionName)
{
mNotificationOptions[OptionName] = getChild<LLComboBox>(OptionName)->getSelectedItemLabel();
@@ -1331,6 +1340,8 @@ void LLFloaterPreference::refresh()
advanced->refresh();
}
updateClickActionViews();
+
+ mTimeFormatCombobox->selectByValue(gSavedSettings.getBOOL("Use24HourClock") ? "1" : "0");
}
void LLFloaterPreference::onCommitWindowedMode()
diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h
index e06e758e3a..d5bd43e0ab 100644
--- a/indra/newview/llfloaterpreference.h
+++ b/indra/newview/llfloaterpreference.h
@@ -124,6 +124,7 @@ protected:
void onClickClearCache(); // Clear viewer texture cache, file cache on next startup
void onClickBrowserClearCache(); // Clear web history and caches as well as viewer caches above
void onLanguageChange();
+ void onTimeFormatChange();
void onNotificationsChange(const std::string& OptionName);
void onNameTagOpacityChange(const LLSD& newvalue);
@@ -242,6 +243,7 @@ private:
LLButton* mDeleteTranscriptsBtn = nullptr;
LLButton* mEnablePopupBtn = nullptr;
LLButton* mDisablePopupBtn = nullptr;
+ LLComboBox* mTimeFormatCombobox = nullptr;
std::unique_ptr< ll::prefs::SearchData > mSearchData;
bool mSearchDataDirty;
diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp
index 165fb5ef88..df4acfac5e 100644
--- a/indra/newview/llfloaterregioninfo.cpp
+++ b/indra/newview/llfloaterregioninfo.cpp
@@ -148,40 +148,6 @@ public:
static LLSD getIDs( sparam_t::const_iterator it, sparam_t::const_iterator end, S32 count );
};
-
-/*
-void unpack_request_params(
- LLMessageSystem* msg,
- LLDispatcher::sparam_t& strings,
- LLDispatcher::iparam_t& integers)
-{
- char str_buf[MAX_STRING];
- S32 str_count = msg->getNumberOfBlocksFast(_PREHASH_StringData);
- S32 i;
- for (i = 0; i < str_count; ++i)
- {
- // we treat the SParam as binary data (since it might be an
- // LLUUID in compressed form which may have embedded \0's,)
- str_buf[0] = '\0';
- S32 data_size = msg->getSizeFast(_PREHASH_StringData, i, _PREHASH_SParam);
- if (data_size >= 0)
- {
- msg->getBinaryDataFast(_PREHASH_StringData, _PREHASH_SParam,
- str_buf, data_size, i, MAX_STRING - 1);
- strings.push_back(std::string(str_buf, data_size));
- }
- }
-
- U32 int_buf;
- S32 int_count = msg->getNumberOfBlocksFast(_PREHASH_IntegerData);
- for (i = 0; i < int_count; ++i)
- {
- msg->getU32("IntegerData", "IParam", int_buf, i);
- integers.push_back(int_buf);
- }
-}
-*/
-
class LLPanelRegionEnvironment : public LLPanelEnvironmentInfo
{
public:
@@ -326,8 +292,8 @@ void LLFloaterRegionInfo::onOpen(const LLSD& key)
disableTabCtrls();
return;
}
- refreshFromRegion(gAgent.getRegion());
- requestRegionInfo();
+ refreshFromRegion(gAgent.getRegion(), ERefreshFromRegionPhase::BeforeRequestRegionInfo);
+ requestRegionInfo(true);
if (!mGodLevelChangeSlot.connected())
{
@@ -347,12 +313,14 @@ void LLFloaterRegionInfo::onRegionChanged()
{
if (getVisible()) //otherwise onOpen will do request
{
- requestRegionInfo();
+ requestRegionInfo(false);
}
}
-void LLFloaterRegionInfo::requestRegionInfo()
+void LLFloaterRegionInfo::requestRegionInfo(bool is_opening)
{
+ mIsRegionInfoRequestedFromOpening = is_opening;
+
LLTabContainer* tab = findChild<LLTabContainer>("region_panels");
if (tab)
{
@@ -523,8 +491,7 @@ void LLFloaterRegionInfo::processRegionInfo(LLMessageSystem* msg)
panel->getChild<LLSpinCtrl>("agent_limit_spin")->setMaxValue((F32)hard_agent_limit);
- LLPanelRegionGeneralInfo* panel_general = LLFloaterRegionInfo::getPanelGeneral();
- if (panel)
+ if (LLPanelRegionGeneralInfo* panel_general = LLFloaterRegionInfo::getPanelGeneral())
{
panel_general->setObjBonusFactor(object_bonus_factor);
}
@@ -561,21 +528,25 @@ void LLFloaterRegionInfo::processRegionInfo(LLMessageSystem* msg)
{
// Note: region info also causes LLRegionInfoModel::instance().update(msg); -> requestRegion(); -> changed message
// we need to know env version here and in update(msg) to know when to request and when not to, when to filter 'changed'
- floater->refreshFromRegion(gAgent.getRegion());
+ ERefreshFromRegionPhase phase = floater->mIsRegionInfoRequestedFromOpening ?
+ ERefreshFromRegionPhase::AfterRequestRegionInfo :
+ ERefreshFromRegionPhase::NotFromFloaterOpening;
+ floater->refreshFromRegion(gAgent.getRegion(), phase);
} // else will rerequest on onOpen either way
}
// static
-void LLFloaterRegionInfo::sRefreshFromRegion(LLViewerRegion* region)
+void LLFloaterRegionInfo::refreshFromRegion(LLViewerRegion* region)
{
- if (region != gAgent.getRegion()) { return; }
-
- LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance<LLFloaterRegionInfo>("region_info");
- if (!floater) { return; }
+ if (region != gAgent.getRegion())
+ return;
- if (floater->getVisible() && region == gAgent.getRegion())
+ if (LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance<LLFloaterRegionInfo>("region_info"))
{
- floater->refreshFromRegion(region);
+ if (floater->getVisible() && region == gAgent.getRegion())
+ {
+ floater->refreshFromRegion(region, ERefreshFromRegionPhase::NotFromFloaterOpening);
+ }
}
}
@@ -682,7 +653,7 @@ void LLFloaterRegionInfo::onTabSelected(const LLSD& param)
active_panel->onOpen(LLSD());
}
-void LLFloaterRegionInfo::refreshFromRegion(LLViewerRegion* region)
+void LLFloaterRegionInfo::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
if (!region)
{
@@ -692,7 +663,7 @@ void LLFloaterRegionInfo::refreshFromRegion(LLViewerRegion* region)
// call refresh from region on all panels
for (const auto& infoPanel : mInfoPanels)
{
- infoPanel->refreshFromRegion(region);
+ infoPanel->refreshFromRegion(region, phase);
}
mEnvironmentPanel->refreshFromRegion(region);
}
@@ -725,7 +696,7 @@ void LLFloaterRegionInfo::onGodLevelChange(U8 god_level)
LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance<LLFloaterRegionInfo>("region_info");
if (floater && floater->getVisible())
{
- refreshFromRegion(gAgent.getRegion());
+ refreshFromRegion(gAgent.getRegion(), ERefreshFromRegionPhase::NotFromFloaterOpening);
}
}
@@ -795,7 +766,7 @@ void LLPanelRegionInfo::updateChild(LLUICtrl* child_ctr)
}
// virtual
-bool LLPanelRegionInfo::refreshFromRegion(LLViewerRegion* region)
+bool LLPanelRegionInfo::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
if (region) mHost = region->getHost();
return true;
@@ -823,12 +794,10 @@ void LLPanelRegionInfo::sendEstateOwnerMessage(
}
else
{
- strings_t::const_iterator it = strings.begin();
- strings_t::const_iterator end = strings.end();
- for(; it != end; ++it)
+ for (const std::string& string : strings)
{
msg->nextBlock("ParamList");
- msg->addString("Parameter", *it);
+ msg->addString("Parameter", string);
}
}
msg->sendReliable(mHost);
@@ -887,7 +856,7 @@ void LLPanelRegionInfo::onClickManageRestartSchedule()
/////////////////////////////////////////////////////////////////////////////
// LLPanelRegionGeneralInfo
//
-bool LLPanelRegionGeneralInfo::refreshFromRegion(LLViewerRegion* region)
+bool LLPanelRegionGeneralInfo::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate());
setCtrlsEnabled(allow_modify);
@@ -904,7 +873,7 @@ bool LLPanelRegionGeneralInfo::refreshFromRegion(LLViewerRegion* region)
// Data gets filled in by processRegionInfo
- return LLPanelRegionInfo::refreshFromRegion(region);
+ return LLPanelRegionInfo::refreshFromRegion(region, phase);
}
bool LLPanelRegionGeneralInfo::postBuild()
@@ -1173,7 +1142,7 @@ bool LLPanelRegionDebugInfo::postBuild()
}
// virtual
-bool LLPanelRegionDebugInfo::refreshFromRegion(LLViewerRegion* region)
+bool LLPanelRegionDebugInfo::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate());
setCtrlsEnabled(allow_modify);
@@ -1191,7 +1160,7 @@ bool LLPanelRegionDebugInfo::refreshFromRegion(LLViewerRegion* region)
getChildView("cancel_restart_btn")->setEnabled(allow_modify);
getChildView("region_debug_console_btn")->setEnabled(allow_modify);
- return LLPanelRegionInfo::refreshFromRegion(region);
+ return LLPanelRegionInfo::refreshFromRegion(region, phase);
}
// virtual
@@ -1233,7 +1202,7 @@ void LLPanelRegionDebugInfo::callbackAvatarID(const uuid_vec_t& ids, const std::
if (ids.empty() || names.empty()) return;
mTargetAvatar = ids[0];
getChild<LLUICtrl>("target_avatar_name")->setValue(LLSD(names[0].getCompleteName()));
- refreshFromRegion( gAgent.getRegion() );
+ refreshFromRegion(gAgent.getRegion(), ERefreshFromRegionPhase::NotFromFloaterOpening);
}
// static
@@ -1675,7 +1644,7 @@ void LLPanelRegionTerrainInfo::updateForMaterialType()
}
// virtual
-bool LLPanelRegionTerrainInfo::refreshFromRegion(LLViewerRegion* region)
+bool LLPanelRegionTerrainInfo::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
bool owner_or_god = gAgent.isGodlike()
|| (region && (region->getOwner() == gAgent.getID()));
@@ -1819,7 +1788,7 @@ bool LLPanelRegionTerrainInfo::refreshFromRegion(LLViewerRegion* region)
getChildView("upload_raw_btn")->setEnabled(owner_or_god);
getChildView("bake_terrain_btn")->setEnabled(owner_or_god);
- return LLPanelRegionInfo::refreshFromRegion(region);
+ return LLPanelRegionInfo::refreshFromRegion(region, phase);
}
@@ -2289,25 +2258,27 @@ void LLPanelEstateInfo::updateControls(LLViewerRegion* region)
refresh();
}
-bool LLPanelEstateInfo::refreshFromRegion(LLViewerRegion* region)
+bool LLPanelEstateInfo::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
updateControls(region);
// let the parent class handle the general data collection.
- bool rv = LLPanelRegionInfo::refreshFromRegion(region);
-
- // We want estate info. To make sure it works across region
- // boundaries and multiple packets, we add a serial number to the
- // integers and track against that on update.
- strings_t strings;
- //integers_t integers;
- //LLFloaterRegionInfo::incrementSerial();
- LLFloaterRegionInfo::nextInvoice();
- LLUUID invoice(LLFloaterRegionInfo::getLastInvoice());
- //integers.push_back(LLFloaterRegionInfo::());::getPanelEstate();
+ bool rv = LLPanelRegionInfo::refreshFromRegion(region, phase);
+ if (phase != ERefreshFromRegionPhase::BeforeRequestRegionInfo)
+ {
+ // We want estate info. To make sure it works across region
+ // boundaries and multiple packets, we add a serial number to the
+ // integers and track against that on update.
+ strings_t strings;
+ //integers_t integers;
+ //LLFloaterRegionInfo::incrementSerial();
+ LLFloaterRegionInfo::nextInvoice();
+ LLUUID invoice(LLFloaterRegionInfo::getLastInvoice());
+ //integers.push_back(LLFloaterRegionInfo::());::getPanelEstate();
- sendEstateOwnerMessage(gMessageSystem, "getinfo", invoice, strings);
+ sendEstateOwnerMessage(gMessageSystem, "getinfo", invoice, strings);
+ }
refresh();
@@ -2528,7 +2499,7 @@ LLPanelEstateCovenant::LLPanelEstateCovenant()
}
// virtual
-bool LLPanelEstateCovenant::refreshFromRegion(LLViewerRegion* region)
+bool LLPanelEstateCovenant::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
LLTextBox* region_name = getChild<LLTextBox>("region_name_text");
if (region_name)
@@ -2574,13 +2545,17 @@ bool LLPanelEstateCovenant::refreshFromRegion(LLViewerRegion* region)
getChild<LLButton>("reset_covenant")->setEnabled(gAgent.isGodlike() || (region && region->canManageEstate()));
// let the parent class handle the general data collection.
- bool rv = LLPanelRegionInfo::refreshFromRegion(region);
- LLMessageSystem *msg = gMessageSystem;
- msg->newMessage("EstateCovenantRequest");
- msg->nextBlockFast(_PREHASH_AgentData);
- msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
- msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID());
- msg->sendReliable(region->getHost());
+ bool rv = LLPanelRegionInfo::refreshFromRegion(region, phase);
+
+ if (phase != ERefreshFromRegionPhase::AfterRequestRegionInfo)
+ {
+ gMessageSystem->newMessage("EstateCovenantRequest");
+ gMessageSystem->nextBlockFast(_PREHASH_AgentData);
+ gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+ gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+ gMessageSystem->sendReliable(region->getHost());
+ }
+
return rv;
}
@@ -3128,7 +3103,7 @@ std::string LLPanelRegionExperiences::regionCapabilityQuery(LLViewerRegion* regi
return region->getCapability(cap);
}
-bool LLPanelRegionExperiences::refreshFromRegion(LLViewerRegion* region)
+bool LLPanelRegionExperiences::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate());
@@ -3151,10 +3126,13 @@ bool LLPanelRegionExperiences::refreshFromRegion(LLViewerRegion* region)
mTrusted->loading();
mTrusted->setReadonly(!allow_modify);
- LLExperienceCache::instance().getRegionExperiences(boost::bind(&LLPanelRegionExperiences::regionCapabilityQuery, region, _1),
- boost::bind(&LLPanelRegionExperiences::infoCallback, getDerivedHandle<LLPanelRegionExperiences>(), _1));
+ if (phase != ERefreshFromRegionPhase::AfterRequestRegionInfo)
+ {
+ LLExperienceCache::instance().getRegionExperiences(boost::bind(&LLPanelRegionExperiences::regionCapabilityQuery, region, _1),
+ boost::bind(&LLPanelRegionExperiences::infoCallback, getDerivedHandle<LLPanelRegionExperiences>(), _1));
+ }
- return LLPanelRegionInfo::refreshFromRegion(region);
+ return LLPanelRegionInfo::refreshFromRegion(region, phase);
}
LLSD LLPanelRegionExperiences::addIds(LLPanelExperienceListEditor* panel)
@@ -4181,35 +4159,32 @@ void LLPanelEstateAccess::copyListToClipboard(std::string list_name)
{
LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess();
if (!panel) return;
- LLNameListCtrl* name_list = panel->getChild<LLNameListCtrl>(list_name);
- if (!name_list) return;
+ LLNameListCtrl* name_list = panel->getChild<LLNameListCtrl>(list_name);
std::vector<LLScrollListItem*> list_vector = name_list->getAllData();
- if (list_vector.size() == 0) return;
+ if (list_vector.empty())
+ return;
LLSD::String list_to_copy;
- for (std::vector<LLScrollListItem*>::const_iterator iter = list_vector.begin();
- iter != list_vector.end();
- iter++)
+ for (LLScrollListItem* item : list_vector)
{
- LLScrollListItem *item = (*iter);
if (item)
{
+ if (!list_to_copy.empty())
+ {
+ list_to_copy += "\n";
+ }
list_to_copy += item->getColumn(0)->getValue().asString();
}
- if (std::next(iter) != list_vector.end())
- {
- list_to_copy += "\n";
- }
}
LLClipboard::instance().copyToClipboard(utf8str_to_wstring(list_to_copy), 0, static_cast<S32>(list_to_copy.length()));
}
-bool LLPanelEstateAccess::refreshFromRegion(LLViewerRegion* region)
+bool LLPanelEstateAccess::refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase)
{
updateLists();
- return LLPanelRegionInfo::refreshFromRegion(region);
+ return LLPanelRegionInfo::refreshFromRegion(region, phase);
}
//=========================================================================
diff --git a/indra/newview/llfloaterregioninfo.h b/indra/newview/llfloaterregioninfo.h
index 65c1291728..119f7c98f3 100644
--- a/indra/newview/llfloaterregioninfo.h
+++ b/indra/newview/llfloaterregioninfo.h
@@ -71,6 +71,13 @@ class LLPanelRegionEnvironment;
class LLEventTimer;
+enum class ERefreshFromRegionPhase
+{
+ NotFromFloaterOpening,
+ BeforeRequestRegionInfo,
+ AfterRequestRegionInfo
+};
+
class LLFloaterRegionInfo : public LLFloater
{
friend class LLFloaterReg;
@@ -85,7 +92,7 @@ public:
// get and process region info if necessary.
static void processRegionInfo(LLMessageSystem* msg);
- static void sRefreshFromRegion(LLViewerRegion* region);
+ static void refreshFromRegion(LLViewerRegion* region);
static const LLUUID& getLastInvoice() { return sRequestInvoice; }
static void nextInvoice() { sRequestInvoice.generate(); }
@@ -104,7 +111,7 @@ public:
void refresh() override;
void onRegionChanged();
- void requestRegionInfo();
+ void requestRegionInfo(bool is_opening);
void enableTopButtons();
void disableTopButtons();
@@ -116,7 +123,7 @@ private:
protected:
void onTabSelected(const LLSD& param);
void disableTabCtrls();
- void refreshFromRegion(LLViewerRegion* region);
+ void refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase);
void onGodLevelChange(U8 god_level);
// member data
@@ -124,6 +131,7 @@ protected:
typedef std::vector<LLPanelRegionInfo*> info_panels_t;
info_panels_t mInfoPanels;
LLPanelRegionEnvironment *mEnvironmentPanel;
+ bool mIsRegionInfoRequestedFromOpening { false };
//static S32 sRequestSerial; // serial # of last EstateOwnerRequest
static LLUUID sRequestInvoice;
@@ -144,7 +152,7 @@ public:
void onChangeAnything();
static void onChangeText(LLLineEditor* caller, void* user_data);
- virtual bool refreshFromRegion(LLViewerRegion* region);
+ virtual bool refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase);
virtual bool estateUpdate(LLMessageSystem* msg) { return true; }
bool postBuild() override;
@@ -190,7 +198,7 @@ public:
: LLPanelRegionInfo() {}
~LLPanelRegionGeneralInfo() {}
- bool refreshFromRegion(LLViewerRegion* region) override;
+ bool refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase) override;
bool postBuild() override;
@@ -222,7 +230,7 @@ public:
bool postBuild() override;
- bool refreshFromRegion(LLViewerRegion* region) override;
+ bool refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase) override;
protected:
bool sendUpdate() override;
@@ -254,8 +262,8 @@ public:
bool postBuild() override;
- bool refreshFromRegion(LLViewerRegion* region) override; // refresh local settings from region update from simulator
- void setEnvControls(bool available); // Whether environment settings are available for this region
+ bool refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase) override; // refresh local settings from region update from simulator
+ void setEnvControls(bool available); // Whether environment settings are available for this region
bool validateTextureSizes();
bool validateMaterials();
@@ -327,7 +335,7 @@ public:
static void updateEstateName(const std::string& name);
static void updateEstateOwnerName(const std::string& name);
- bool refreshFromRegion(LLViewerRegion* region) override;
+ bool refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase) override;
bool estateUpdate(LLMessageSystem* msg) override;
bool postBuild() override;
@@ -364,7 +372,7 @@ public:
bool postBuild() override;
void updateChild(LLUICtrl* child_ctrl) override;
- bool refreshFromRegion(LLViewerRegion* region) override;
+ bool refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase) override;
bool estateUpdate(LLMessageSystem* msg) override;
// LLView overrides
@@ -430,7 +438,7 @@ public:
static void sendEstateExperienceDelta(U32 flags, const LLUUID& agent_id);
static void infoCallback(LLHandle<LLPanelRegionExperiences> handle, const LLSD& content);
- bool refreshFromRegion(LLViewerRegion* region) override;
+ bool refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase) override;
void sendPurchaseRequest()const;
void processResponse( const LLSD& content );
@@ -470,7 +478,7 @@ public:
void setPendingUpdate(bool pending) { mPendingUpdate = pending; }
bool getPendingUpdate() { return mPendingUpdate; }
- bool refreshFromRegion(LLViewerRegion* region) override;
+ bool refreshFromRegion(LLViewerRegion* region, ERefreshFromRegionPhase phase) override;
private:
void onClickAddAllowedAgent();
diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp
index dda7266220..4674cd68b6 100755
--- a/indra/newview/llfloaterworldmap.cpp
+++ b/indra/newview/llfloaterworldmap.cpp
@@ -486,8 +486,11 @@ void LLFloaterWorldMap::onOpen(const LLSD& key)
const LLUUID landmark_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK);
LLInventoryModelBackgroundFetch::instance().start(landmark_folder_id);
- mLocationEditor->setFocus( true);
- gFocusMgr.triggerFocusFlash();
+ if (hasFocus())
+ {
+ mLocationEditor->setFocus( true);
+ gFocusMgr.triggerFocusFlash();
+ }
buildAvatarIDList();
buildLandmarkIDLists();
diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp
index bf49f33049..51f30dd86b 100644
--- a/indra/newview/lllogchat.cpp
+++ b/indra/newview/lllogchat.cpp
@@ -78,8 +78,8 @@ const static std::string MULTI_LINE_PREFIX(" ");
*
* Note: "You" was used as an avatar names in viewers of previous versions
*/
-const static boost::regex TIMESTAMP_AND_STUFF("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]\\s+|\\[\\d{1,2}:\\d{2}\\]\\s+)?(.*)$");
-const static boost::regex TIMESTAMP("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]|\\[\\d{1,2}:\\d{2}\\]).*");
+const static boost::regex TIMESTAMP_AND_STUFF("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\s[AaPp][Mm]\\]\\s+|\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]\\s+|\\[\\d{1,2}:\\d{2}\\s[AaPp][Mm]\\]\\s+|\\[\\d{1,2}:\\d{2}\\]\\s+)?(.*)$");
+const static boost::regex TIMESTAMP("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}(\\s[AaPp][Mm])?\\]|\\[\\d{1,2}:\\d{2}(\\s[AaPp][Mm])?\\]).*");
/**
* Regular expression suitable to match names like
@@ -150,6 +150,10 @@ public:
void checkAndCutOffDate(std::string& time_str)
{
+ if (time_str.size() < 10) // not enough space for a date
+ {
+ return;
+ }
// Cuts off the "%Y/%m/%d" from string for todays timestamps.
// Assume that passed string has at least "%H:%M" time format.
date log_date(not_a_date_time);
@@ -166,20 +170,12 @@ public:
if ( days_alive == zero_days )
{
- // Yep, today's so strip "%Y/%m/%d" info
- ptime stripped_time(not_a_date_time);
-
- mTimeStream.str(LLStringUtil::null);
- mTimeStream << time_str;
- mTimeStream >> stripped_time;
- mTimeStream.clear();
-
- time_str.clear();
-
- mTimeStream.str(LLStringUtil::null);
- mTimeStream << stripped_time;
- mTimeStream >> time_str;
- mTimeStream.clear();
+ size_t pos = time_str.find_first_of(' ');
+ if (pos != std::string::npos)
+ {
+ time_str.erase(0, pos + 1);
+ LLStringUtil::trim(time_str);
+ }
}
LL_DEBUGS("LLChatLogParser")
@@ -308,16 +304,22 @@ std::string LLLogChat::timestamp2LogString(U32 timestamp, bool withdate)
std::string timeStr;
if (withdate)
{
- timeStr = "[" + LLTrans::getString ("TimeYear") + "]/["
- + LLTrans::getString ("TimeMonth") + "]/["
- + LLTrans::getString ("TimeDay") + "] ["
- + LLTrans::getString ("TimeHour") + "]:["
- + LLTrans::getString ("TimeMin") + "]";
+ timeStr = "[" + LLTrans::getString("TimeYear") + "]/["
+ + LLTrans::getString("TimeMonth") + "]/["
+ + LLTrans::getString("TimeDay") + "] ";
+ }
+
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ if (use_24h)
+ {
+ timeStr += "[" + LLTrans::getString("TimeHour") + "]:["
+ + LLTrans::getString("TimeMin") + "]";
}
else
{
- timeStr = "[" + LLTrans::getString("TimeHour") + "]:["
- + LLTrans::getString ("TimeMin")+"]";
+ timeStr += "[" + LLTrans::getString("TimeHour12") + "]:["
+ + LLTrans::getString("TimeMin") + "] ["
+ + LLTrans::getString("TimeAMPM") + "]";
}
LLSD substitution;
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index 1e8b7d39cc..1e7da126b0 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -3365,7 +3365,7 @@ bool LLModelPreview::render()
mFMP->childSetEnabled("upload_joints", upload_skin);
}
- F32 explode = (F32)mFMP->childGetValue("physics_explode").asReal();
+ F32 physics_explode = (F32)mFMP->childGetValue("physics_explode").asReal();
LLGLDepthTest gls_depth(GL_TRUE); // SL-12781 re-enable z-buffer for 3D model preview
@@ -3590,12 +3590,12 @@ bool LLModelPreview::render()
for (U32 i = 0; i < physics.mMesh.size(); ++i)
{
- if (explode > 0.f)
+ if (physics_explode > 0.f)
{
gGL.pushMatrix();
LLVector3 offset = model->mHullCenter[i] - model->mCenterOfHullCenters;
- offset *= explode;
+ offset *= physics_explode;
gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]);
}
@@ -3610,7 +3610,7 @@ bool LLModelPreview::render()
gGL.diffuseColor4ubv(hull_colors[i].mV);
LLVertexBuffer::drawArrays(LLRender::TRIANGLES, physics.mMesh[i].mPositions);
- if (explode > 0.f)
+ if (physics_explode > 0.f)
{
gGL.popMatrix();
}
@@ -3625,7 +3625,8 @@ bool LLModelPreview::render()
if (render_mesh)
{
auto num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size();
- if (pass > 0){
+ if (pass > 0)
+ {
for (size_t i = 0; i < num_models; ++i)
{
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
@@ -3805,7 +3806,8 @@ bool LLModelPreview::render()
}
}
- for (U32 i = 0, e = static_cast<U32>(mVertexBuffer[mPreviewLOD][model].size()); i < e; ++i)
+ std::size_t size = mVertexBuffer[mPreviewLOD][model].size();
+ for (U32 i = 0; i < size; ++i)
{
model->mSkinInfo.updateHash();
LLRenderPass::uploadMatrixPalette(mPreviewAvatar, &model->mSkinInfo);
diff --git a/indra/newview/llnotificationlistitem.cpp b/indra/newview/llnotificationlistitem.cpp
index 5b8b28ebe6..9a33bcb1b9 100644
--- a/indra/newview/llnotificationlistitem.cpp
+++ b/indra/newview/llnotificationlistitem.cpp
@@ -38,6 +38,7 @@
#include "lluicolortable.h"
#include "message.h"
#include "llnotificationsutil.h"
+#include "llviewercontrol.h"
#include <boost/regex.hpp>
LLNotificationListItem::LLNotificationListItem(const Params& p) : LLPanel(p),
@@ -133,10 +134,22 @@ std::string LLNotificationListItem::buildNotificationDate(const LLDate& time_sta
default:
timeStr = "[" + LLTrans::getString("TimeMonth") + "]/["
+LLTrans::getString("TimeDay")+"]/["
- +LLTrans::getString("TimeYear")+"] ["
- +LLTrans::getString("TimeHour")+"]:["
- +LLTrans::getString("TimeMin")+"] ["
- +LLTrans::getString("TimeTimezone")+"]";
+ +LLTrans::getString("TimeYear")+"] [";
+
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ if (use_24h)
+ {
+ timeStr += LLTrans::getString("TimeHour") + "]:["
+ + LLTrans::getString("TimeMin") + "] ["
+ + LLTrans::getString("TimeTimezone") + "]";
+ }
+ else
+ {
+ timeStr += LLTrans::getString("TimeHour12") + "]:["
+ + LLTrans::getString("TimeMin") + "] ["
+ + LLTrans::getString("TimeAMPM") + "] ["
+ + LLTrans::getString("TimeTimezone") + "]";
+ }
break;
}
LLSD substitution;
diff --git a/indra/newview/llpanelenvironment.cpp b/indra/newview/llpanelenvironment.cpp
index be61c44b7c..d3df88b65e 100644
--- a/indra/newview/llpanelenvironment.cpp
+++ b/indra/newview/llpanelenvironment.cpp
@@ -48,6 +48,7 @@
#include "llappviewer.h"
#include "llcallbacklist.h"
+#include "llviewercontrol.h"
#include "llviewerparcelmgr.h"
#include "llinventorymodel.h"
@@ -939,19 +940,29 @@ void LLPanelEnvironmentInfo::udpateApparentTimeOfDay()
S32Hours hourofday(secondofday);
S32Seconds secondofhour(secondofday - hourofday);
S32Minutes minutesofhour(secondofhour);
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
bool am_pm(hourofday.value() >= 12);
- if (hourofday.value() < 1)
- hourofday = S32Hours(12);
- if (hourofday.value() > 12)
- hourofday -= S32Hours(12);
+ if (!use_24h)
+ {
+ if (hourofday.value() < 1)
+ hourofday = S32Hours(12);
+ if (hourofday.value() > 12)
+ hourofday -= S32Hours(12);
+ }
std::string lblminute(((minutesofhour.value() < 10) ? "0" : "") + LLSD(minutesofhour.value()).asString());
-
mLabelApparentTime->setTextArg("[HH]", LLSD(hourofday.value()).asString());
mLabelApparentTime->setTextArg("[MM]", lblminute);
- mLabelApparentTime->setTextArg("[AP]", std::string(am_pm ? "PM" : "AM"));
+ if (use_24h)
+ {
+ mLabelApparentTime->setTextArg("[AP]", std::string());
+ }
+ else
+ {
+ mLabelApparentTime->setTextArg("[AP]", std::string(am_pm ? "PM" : "AM"));
+ }
mLabelApparentTime->setTextArg("[PRC]", LLSD((S32)(100 * perc)).asString());
}
diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp
index 4404efff98..e1f2d7588c 100644
--- a/indra/newview/llpanelgrouproles.cpp
+++ b/indra/newview/llpanelgrouproles.cpp
@@ -1360,7 +1360,8 @@ void LLPanelGroupMembersSubTab::activate()
{
if (!gdatap || !gdatap->isMemberDataComplete())
{
- const U32 page_size = 50;
+ static LLCachedControl<bool> enable_pagination(gSavedSettings, "UseGroupMemberPagination", false);
+ const U32 page_size = enable_pagination() ? 50 : 0;
std::string sort_column_name = mMembersList->getSortColumnName();
bool sort_descending = !mMembersList->getSortAscending();
LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mGroupID, page_size, 0, sort_column_name, sort_descending);
diff --git a/indra/newview/llpanellandmarkinfo.cpp b/indra/newview/llpanellandmarkinfo.cpp
index 41373cd7f5..7596c0eba8 100644
--- a/indra/newview/llpanellandmarkinfo.cpp
+++ b/indra/newview/llpanellandmarkinfo.cpp
@@ -41,6 +41,7 @@
#include "lllandmarkactions.h"
#include "llparcel.h"
#include "llslurl.h"
+#include "llviewercontrol.h"
#include "llviewerinventory.h"
#include "llviewerparcelmgr.h"
#include "llviewerregion.h"
@@ -326,7 +327,8 @@ void LLPanelLandmarkInfo::displayItemInfo(const LLInventoryItem* pItem)
}
else
{
- std::string timeStr = getString("acquired_date");
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ std::string timeStr = use_24h ? getString("acquired_date") : getString("acquired_date_ampm");
LLSD substitution;
substitution["datetime"] = (S32) time_utc;
LLStringUtil::format (timeStr, substitution);
diff --git a/indra/newview/llpanelteleporthistory.cpp b/indra/newview/llpanelteleporthistory.cpp
index ebfdafdfe2..8097e0ac0b 100644
--- a/indra/newview/llpanelteleporthistory.cpp
+++ b/indra/newview/llpanelteleporthistory.cpp
@@ -42,6 +42,7 @@
#include "llnotificationsutil.h"
#include "lltextbox.h"
#include "lltoggleablemenu.h"
+#include "llviewercontrol.h"
#include "llviewermenu.h"
#include "lllandmarkactions.h"
#include "llclipboard.h"
@@ -215,8 +216,18 @@ std::string LLTeleportHistoryFlatItem::getTimestamp()
// Only show timestamp for today and yesterday
if(time_diff < seconds_today + seconds_in_day)
{
- timestamp = "[" + LLTrans::getString("TimeHour12")+"]:["
- + LLTrans::getString("TimeMin")+"] ["+ LLTrans::getString("TimeAMPM")+"]";
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ if (use_24h)
+ {
+ timestamp = "[" + LLTrans::getString("TimeHour") + "]:["
+ + LLTrans::getString("TimeMin") + "]";
+ }
+ else
+ {
+ timestamp = "[" + LLTrans::getString("TimeHour12") + "]:["
+ + LLTrans::getString("TimeMin") + "] [" + LLTrans::getString("TimeAMPM") + "]";
+ }
+
LLSD substitution;
substitution["datetime"] = (S32) date.secondsSinceEpoch();
LLStringUtil::format(timestamp, substitution);
diff --git a/indra/newview/llsettingsvo.cpp b/indra/newview/llsettingsvo.cpp
index 34b8535c84..fee0bbcda4 100644
--- a/indra/newview/llsettingsvo.cpp
+++ b/indra/newview/llsettingsvo.cpp
@@ -671,7 +671,8 @@ void LLSettingsVOSky::updateSettings()
// After some A/B comparison of relesae vs EEP, tweak to allow strength to fall below 2
// at night, for better match. (mSceneLightStrength is a divisor, so lower value means brighter
// local lights)
- F32 sun_dynamic_range = llmax(gSavedSettings.getF32("RenderSunDynamicRange"), 0.0001f);
+ LLCachedControl<F32> sdr(gSavedSettings, "RenderSunDynamicRange", 1.f);
+ F32 sun_dynamic_range = llmax(sdr(), 0.0001f);
mSceneLightStrength = 2.0f * (0.75f + sun_dynamic_range * dp);
gSky.setSunAndMoonDirectionsCFR(sun_direction, moon_direction);
diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp
index fccf745a74..385e4314a9 100644
--- a/indra/newview/llsidepaneliteminfo.cpp
+++ b/indra/newview/llsidepaneliteminfo.cpp
@@ -483,7 +483,8 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item)
}
else
{
- std::string timeStr = getString("acquiredDate");
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ std::string timeStr = use_24h ? getString("acquiredDate") : getString("acquiredDateAMPM");
LLSD substitution;
substitution["datetime"] = (S32) time_utc;
LLStringUtil::format (timeStr, substitution);
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 83d6b57fa4..dd8c6d989e 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -204,6 +204,7 @@
#include "threadpool.h"
#include "llperfstats.h"
+#include "rlvhandler.h"
#if LL_WINDOWS
#include "lldxhardware.h"
@@ -875,6 +876,8 @@ bool idle_startup()
return false;
}
+ RlvHandler::setEnabled(gSavedSettings.get<bool>(Rlv::Settings::Main));
+
// reset the values that could have come in from a slurl
// DEV-42215: Make sure they're not empty -- gUserCredential
// might already have been set from gSavedSettings, and it's too bad
diff --git a/indra/newview/llterrainpaintmap.cpp b/indra/newview/llterrainpaintmap.cpp
index 8ccde74c93..a858f92d8e 100644
--- a/indra/newview/llterrainpaintmap.cpp
+++ b/indra/newview/llterrainpaintmap.cpp
@@ -31,6 +31,8 @@
// library includes
#include "llglslshader.h"
#include "llrendertarget.h"
+#include "llrender2dutils.h"
+#include "llshadermgr.h"
#include "llvertexbuffer.h"
// newview includes
@@ -38,18 +40,31 @@
#include "llsurface.h"
#include "llsurfacepatch.h"
#include "llviewercamera.h"
+#include "llviewercontrol.h"
#include "llviewerregion.h"
#include "llviewershadermgr.h"
#include "llviewertexture.h"
-// static
-bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, LLViewerTexture& tex)
+namespace
+{
+#ifdef SHOW_ASSERT
+void check_tex(const LLViewerTexture& tex)
{
llassert(tex.getComponents() == 3);
llassert(tex.getWidth() > 0 && tex.getHeight() > 0);
llassert(tex.getWidth() == tex.getHeight());
llassert(tex.getPrimaryFormat() == GL_RGB);
llassert(tex.getGLTexture());
+}
+#else
+#define check_tex(tex)
+#endif
+} // namespace
+
+// static
+bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, LLViewerTexture& tex)
+{
+ check_tex(tex);
const LLSurface& surface = region.getLand();
const U32 patch_count = surface.getPatchesPerEdge();
@@ -76,8 +91,8 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
// Bind the debug shader and render terrain to tex
// Use a scratch render target because its dimensions may exceed the standard bake target, and this is a one-off bake
LLRenderTarget scratch_target;
- const S32 dim = llmin(tex.getWidth(), tex.getHeight());
- scratch_target.allocate(dim, dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
+ const S32 max_dim = llmax(tex.getWidth(), tex.getHeight());
+ scratch_target.allocate(max_dim, max_dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
LLTexUnit::eTextureMipGeneration::TMG_NONE);
if (!scratch_target.isComplete())
{
@@ -104,6 +119,7 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
const F32 region_half_width = region_width / 2.0f;
const F32 region_camera_height = surface.getMaxZ() + DEFAULT_NEAR_PLANE;
LLViewerCamera camera;
+ // TODO: Huh... I just realized this view vector is not completely vertical
const LLVector3 region_center = LLVector3(region_half_width, region_half_width, 0.0) + region.getOriginAgent();
const LLVector3 camera_origin = LLVector3(0.0f, 0.0f, region_camera_height) + region_center;
camera.lookAt(camera_origin, region_center, LLVector3::y_axis);
@@ -237,6 +253,7 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
S32 alpha_ramp = shader.enableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
LLPointer<LLViewerTexture> alpha_ramp_texture = LLViewerTextureManager::getFetchedTexture(IMG_ALPHA_GRAD_2D);
+ // TODO: Consider using LLGLSLShader::bindTexture
gGL.getTexUnit(alpha_ramp)->bind(alpha_ramp_texture);
gGL.getTexUnit(alpha_ramp)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
@@ -250,6 +267,7 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
const U32 vertex_offset = n * patch_index;
llassert(index_offset + ni <= region_indices);
llassert(vertex_offset + n <= region_vertices);
+ // TODO: Try a single big drawRange and see if that still works
buf->drawRange(LLRender::TRIANGLES, vertex_offset, vertex_offset + n - 1, ni, index_offset);
}
}
@@ -269,7 +287,7 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
gGL.flush();
LLVertexBuffer::unbind();
// Final step: Copy the output to the terrain paintmap
- const bool success = tex.getGLTexture()->setSubImageFromFrameBuffer(0, 0, 0, 0, dim, dim);
+ const bool success = tex.getGLTexture()->setSubImageFromFrameBuffer(0, 0, 0, 0, tex.getWidth(), tex.getHeight());
if (!success)
{
LL_WARNS() << "Failed to copy framebuffer to paintmap" << LL_ENDL;
@@ -283,3 +301,687 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
return success;
}
+
+// *TODO: Decide when to apply the paint queue - ideally once per frame per region
+// Applies paints and then clears the paint queue
+// *NOTE The paint queue is also cleared when setting the paintmap texture
+void LLTerrainPaintMap::applyPaintQueueRGB(LLViewerTexture& tex, LLTerrainPaintQueue& queue)
+{
+ if (queue.empty()) { return; }
+
+ check_tex(tex);
+
+ gGL.getTexUnit(0)->bind(tex.getGLTexture(), false, true);
+
+ // glTexSubImage2D replaces all pixels in the rectangular region. That
+ // makes it unsuitable for alpha.
+ llassert(queue.getComponents() == LLTerrainPaint::RGB);
+ constexpr GLenum pixformat = GL_RGB;
+
+ const std::vector<LLTerrainPaint::ptr_t>& queue_list = queue.get();
+ for (size_t i = 0; i < queue_list.size(); ++i)
+ {
+ // It is currently the responsibility of the paint queue to convert
+ // incoming bits to the right bit depth for the paintmap (this could
+ // change in the future).
+ queue.convertBitDepths(i, 8);
+ const LLTerrainPaint::ptr_t& paint = queue_list[i];
+
+ if (paint->mData.empty()) { continue; }
+ constexpr GLint level = 0;
+ if ((paint->mStartX >= tex.getWidth() - 1) || (paint->mStartY >= tex.getHeight() - 1)) { continue; }
+ constexpr GLint miplevel = 0;
+ const S32 x_offset = paint->mStartX;
+ const S32 y_offset = paint->mStartY;
+ const S32 width = llmin(paint->mWidthX, tex.getWidth() - x_offset);
+ const S32 height = llmin(paint->mWidthY, tex.getHeight() - y_offset);
+ const U8* pixels = paint->mData.data();
+ constexpr GLenum pixtype = GL_UNSIGNED_BYTE;
+ // *TODO: Performance suggestion: Use the sub-image utility function
+ // that LLImageGL::setSubImage uses to split texture updates into
+ // lines, if that's faster.
+ glTexSubImage2D(GL_TEXTURE_2D, miplevel, x_offset, y_offset, width, height, pixformat, pixtype, pixels);
+ stop_glerror();
+ }
+
+ // Generating mipmaps at the end...
+ glGenerateMipmap(GL_TEXTURE_2D);
+ stop_glerror();
+
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+
+ queue.clear();
+}
+
+namespace
+{
+
+// A general-purpose vertex buffer of a quad for stamping textures on the z=0
+// plane.
+// *NOTE: Because we know the vertex XY coordinates go from 0 to 1
+// pre-transform, UVs can be calculated from the vertices
+LLVertexBuffer* get_paint_triangle_buffer()
+{
+ static LLPointer<LLVertexBuffer> buf = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX);
+ static bool initialized = false;
+ if (!initialized)
+ {
+ // Two triangles forming a square from (0,0) to (1,1)
+ buf->allocateBuffer(/*vertices =*/ 4, /*indices =*/ 6);
+ LLStrider<U16> indices;
+ LLStrider<LLVector3> vertices;
+ buf->getVertexStrider(vertices);
+ buf->getIndexStrider(indices);
+ // y
+ // 2....3
+ // ^ . .
+ // | 0....1
+ // |
+ // -------> x
+ //
+ // triangle 1: 0,1,2
+ // triangle 2: 1,3,2
+ (*(vertices++)).set(0.0f, 0.0f, 0.0f);
+ (*(vertices++)).set(1.0f, 0.0f, 0.0f);
+ (*(vertices++)).set(0.0f, 1.0f, 0.0f);
+ (*(vertices++)).set(1.0f, 1.0f, 0.0f);
+ *(indices++) = 0;
+ *(indices++) = 1;
+ *(indices++) = 2;
+ *(indices++) = 1;
+ *(indices++) = 3;
+ *(indices++) = 2;
+ buf->unmapBuffer();
+ }
+ return buf.get();
+}
+
+};
+
+// static
+LLTerrainPaintQueue LLTerrainPaintMap::convertPaintQueueRGBAToRGB(LLViewerTexture& tex, LLTerrainPaintQueue& queue_in)
+{
+ check_tex(tex);
+ llassert(queue_in.getComponents() == LLTerrainPaint::RGBA);
+
+ // TODO: Avoid allocating a scratch render buffer and use mAuxillaryRT instead
+ // TODO: even if it means performing extra render operations to apply the paints, in rare cases where the paints can't all fit within an area that can be represented by the buffer
+ LLRenderTarget scratch_target;
+ const S32 max_dim = llmax(tex.getWidth(), tex.getHeight());
+ scratch_target.allocate(max_dim, max_dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
+ LLTexUnit::eTextureMipGeneration::TMG_NONE);
+ if (!scratch_target.isComplete())
+ {
+ llassert(false);
+ LL_WARNS() << "Failed to allocate render target" << LL_ENDL;
+ return false;
+ }
+ gGL.getTexUnit(0)->disable();
+ stop_glerror();
+
+ scratch_target.bindTarget();
+ glClearColor(0, 0, 0, 0);
+ scratch_target.clear();
+ const F32 target_half_width = (F32)scratch_target.getWidth() / 2.0f;
+ const F32 target_half_height = (F32)scratch_target.getHeight() / 2.0f;
+
+ LLVertexBuffer* buf = get_paint_triangle_buffer();
+
+ // Update projection matrix and viewport
+ // *NOTE: gl_state_for_2d also sets the modelview matrix. This will be overridden later.
+ {
+ stop_glerror();
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.pushMatrix();
+ gGL.loadIdentity();
+ gGL.ortho(-target_half_width, target_half_width, -target_half_height, target_half_height, 0.25f, 1.0f);
+ stop_glerror();
+ const LLRect texture_rect(0, scratch_target.getHeight(), scratch_target.getWidth(), 0);
+ glViewport(texture_rect.mLeft, texture_rect.mBottom, texture_rect.getWidth(), texture_rect.getHeight());
+ }
+
+ // View matrix
+ // Coordinates should be in pixels. 1.0f = 1 pixel on the framebuffer.
+ // Camera is centered in the middle of the framebuffer.
+ glm::mat4 view(glm::make_mat4((GLfloat*) OGL_TO_CFR_ROTATION));
+ {
+ LLViewerCamera camera;
+ const LLVector3 camera_origin(target_half_width, target_half_height, 0.5f);
+ const LLVector3 camera_look_down(target_half_width, target_half_height, 0.0f);
+ camera.lookAt(camera_origin, camera_look_down, LLVector3::y_axis);
+ camera.setAspect(F32(scratch_target.getHeight()) / F32(scratch_target.getWidth()));
+ GLfloat ogl_matrix[16];
+ camera.getOpenGLTransform(ogl_matrix);
+ view *= glm::make_mat4(ogl_matrix);
+ }
+
+ LLGLDisable stencil(GL_STENCIL_TEST);
+ LLGLDisable scissor(GL_SCISSOR_TEST);
+ LLGLEnable cull_face(GL_CULL_FACE);
+ LLGLDepthTest depth_test(GL_FALSE, GL_FALSE, GL_ALWAYS);
+ LLGLEnable blend(GL_BLEND);
+ gGL.setSceneBlendType(LLRender::BT_ALPHA);
+
+ LLGLSLShader& shader = gTerrainStampProgram;
+ shader.bind();
+
+ // First, apply the paint map as the background
+ {
+ glm::mat4 model;
+ {
+ model = glm::scale(model, glm::vec3((F32)tex.getWidth(), (F32)tex.getHeight(), 1.0f));
+ model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
+ }
+ glm::mat4 modelview = view * model;
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.loadMatrix(glm::value_ptr(modelview));
+
+ shader.bindTexture(LLShaderMgr::DIFFUSE_MAP, &tex);
+ // We care about the whole paintmap, which is already a power of two.
+ // Hence, TERRAIN_STAMP_SCALE = (1.0,1.0)
+ shader.uniform2f(LLShaderMgr::TERRAIN_STAMP_SCALE, 1.0f, 1.0f);
+ buf->setBuffer();
+ buf->draw(LLRender::TRIANGLES, buf->getIndicesSize(), 0);
+ }
+
+ LLTerrainPaintQueue queue_out(LLTerrainPaint::RGB);
+
+ // Incrementally apply each RGBA paint to the render target, then extract
+ // the result back into memory as an RGB paint.
+ // Put each result in queue_out.
+ const std::vector<LLTerrainPaint::ptr_t>& queue_in_list = queue_in.get();
+ for (size_t i = 0; i < queue_in_list.size(); ++i)
+ {
+ // It is currently the responsibility of the paint queue to convert
+ // incoming bits to the right bit depth for paint operations (this
+ // could change in the future).
+ queue_in.convertBitDepths(i, 8);
+ const LLTerrainPaint::ptr_t& paint_in = queue_in_list[i];
+
+ // Modelview matrix for the current paint
+ // View matrix is already computed. Just need the model matrix.
+ // Orthographic projection matrix is already updated
+ glm::mat4 model;
+ {
+ model = glm::scale(model, glm::vec3(paint_in->mWidthX, paint_in->mWidthY, 1.0f));
+ model = glm::translate(model, glm::vec3(paint_in->mStartX, paint_in->mStartY, 0.0f));
+ }
+ glm::mat4 modelview = view * model;
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.loadMatrix(glm::value_ptr(modelview));
+
+ // Generate temporary stamp texture from paint contents.
+ // Our stamp image needs to be a power of two.
+ // Because the paint data may not cover a whole power-of-two region,
+ // allocate a bigger 2x2 image if needed, but set the image data later
+ // for a subset of the image.
+ // Pixel data outside this subset is left undefined. We will use
+ // TERRAIN_STAMP_SCALE in the stamp shader to define the subset of the
+ // image we care about.
+ const U32 width_rounded = 1 << U32(ceil(log2(F32(paint_in->mWidthX))));
+ const U32 height_rounded = 1 << U32(ceil(log2(F32(paint_in->mWidthY))));
+ LLPointer<LLImageGL> stamp_image;
+ {
+ // Create image object (dimensions not yet initialized in GL)
+ U32 stamp_tex_name;
+ LLImageGL::generateTextures(1, &stamp_tex_name);
+ const U32 components = paint_in->mComponents;
+ constexpr LLGLenum target = GL_TEXTURE_2D;
+ const LLGLenum internal_format = paint_in->mComponents == 4 ? GL_RGBA8 : GL_RGB8;
+ const LLGLenum format = paint_in->mComponents == 4 ? GL_RGBA : GL_RGB;
+ constexpr LLGLenum type = GL_UNSIGNED_BYTE;
+ stamp_image = new LLImageGL(stamp_tex_name, components, target, internal_format, format, type, LLTexUnit::TAM_WRAP);
+ // Nearest-neighbor filtering to reduce surprises
+ stamp_image->setFilteringOption(LLTexUnit::TFO_POINT);
+
+ // Initialize the image dimensions in GL
+ constexpr U8* undefined_data_for_now = nullptr;
+ gGL.getTexUnit(0)->bind(stamp_image, false, true);
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width_rounded, height_rounded, 0, format, type, undefined_data_for_now);
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ stamp_image->setSize(width_rounded, height_rounded, components);
+ stamp_image->setDiscardLevel(0);
+
+ // Manually set a subset of the image in GL
+ const U8* data = paint_in->mData.data();
+ const S32 data_width = paint_in->mWidthX;
+ const S32 data_height = paint_in->mWidthY;
+ constexpr S32 origin = 0;
+ // width = data_width; height = data_height. i.e.: Copy the full
+ // contents of data into the image.
+ stamp_image->setSubImage(data, data_width, data_height, origin, origin, /*width=*/data_width, /*height=*/data_height);
+ }
+
+ // Apply ("stamp") the paint to the render target
+ {
+ shader.bindTextureImageGL(LLShaderMgr::DIFFUSE_MAP, stamp_image);
+ const F32 width_fraction = F32(paint_in->mWidthX) / F32(width_rounded);
+ const F32 height_fraction = F32(paint_in->mWidthY) / F32(height_rounded);
+ shader.uniform2f(LLShaderMgr::TERRAIN_STAMP_SCALE, width_fraction, height_fraction);
+ buf->setBuffer();
+ buf->draw(LLRender::TRIANGLES, buf->getIndicesSize(), 0);
+ }
+
+ // Extract the result back into memory as an RGB paint
+ LLTerrainPaint::ptr_t paint_out = std::make_shared<LLTerrainPaint>();
+ {
+ paint_out->mStartX = paint_in->mStartX;
+ paint_out->mStartY = paint_in->mStartY;
+ paint_out->mWidthX = paint_in->mWidthX;
+ paint_out->mWidthY = paint_in->mWidthY;
+ paint_out->mBitDepth = 8; // Will be reduced to TerrainPaintBitDepth bits later
+ paint_out->mComponents = LLTerrainPaint::RGB;
+#ifdef SHOW_ASSERT
+ paint_out->assert_confined_to(tex);
+#endif
+ paint_out->confine_to(tex);
+ paint_out->mData.resize(paint_out->mComponents * paint_out->mWidthX * paint_out->mWidthY);
+ constexpr GLint miplevel = 0;
+ const S32 x_offset = paint_out->mStartX;
+ const S32 y_offset = paint_out->mStartY;
+ const S32 width = paint_out->mWidthX;
+ const S32 height = paint_out->mWidthY;
+ constexpr GLenum pixformat = GL_RGB;
+ constexpr GLenum pixtype = GL_UNSIGNED_BYTE;
+ llassert(paint_out->mData.size() <= std::numeric_limits<GLsizei>::max());
+ const GLsizei buf_size = (GLsizei)paint_out->mData.size();
+ U8* pixels = paint_out->mData.data();
+ glReadPixels(x_offset, y_offset, width, height, pixformat, pixtype, pixels);
+ }
+
+ // Enqueue the result to the new paint queue, with bit depths per color
+ // channel reduced from 8 to 5, and reduced from RGBA (paintmap
+ // sub-rectangle update with alpha mask) to RGB (paintmap sub-rectangle
+ // update without alpha mask). This format is suitable for sending
+ // over the network.
+ // *TODO: At some point, queue_out will pass through a network
+ // round-trip which will reduce the bit depth, making the
+ // pre-conversion step not necessary.
+ queue_out.enqueue(paint_out);
+ LLCachedControl<U32> bit_depth(gSavedSettings, "TerrainPaintBitDepth");
+ queue_out.convertBitDepths(queue_out.size()-1, bit_depth);
+ }
+
+ queue_in.clear();
+
+ scratch_target.flush();
+
+ LLGLSLShader::unbind();
+
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.popMatrix();
+
+ return queue_out;
+}
+
+// static
+LLTerrainPaintQueue LLTerrainPaintMap::convertBrushQueueToPaintRGB(const LLViewerRegion& region, LLViewerTexture& tex, LLTerrainBrushQueue& queue_in)
+{
+ check_tex(tex);
+
+ // TODO: Avoid allocating a scratch render buffer and use mAuxillaryRT instead
+ // TODO: even if it means performing extra render operations to apply the brushes, in rare cases where the paints can't all fit within an area that can be represented by the buffer
+ LLRenderTarget scratch_target;
+ const S32 max_dim = llmax(tex.getWidth(), tex.getHeight());
+ scratch_target.allocate(max_dim, max_dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
+ LLTexUnit::eTextureMipGeneration::TMG_NONE);
+ if (!scratch_target.isComplete())
+ {
+ llassert(false);
+ LL_WARNS() << "Failed to allocate render target" << LL_ENDL;
+ return false;
+ }
+ gGL.getTexUnit(0)->disable();
+ stop_glerror();
+
+ scratch_target.bindTarget();
+ glClearColor(0, 0, 0, 0);
+ scratch_target.clear();
+ const F32 target_half_width = (F32)scratch_target.getWidth() / 2.0f;
+ const F32 target_half_height = (F32)scratch_target.getHeight() / 2.0f;
+
+ LLVertexBuffer* buf = get_paint_triangle_buffer();
+
+ // Update projection matrix and viewport
+ // *NOTE: gl_state_for_2d also sets the modelview matrix. This will be overridden later.
+ {
+ stop_glerror();
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.pushMatrix();
+ gGL.loadIdentity();
+ gGL.ortho(-target_half_width, target_half_width, -target_half_height, target_half_height, 0.25f, 1.0f);
+ stop_glerror();
+ const LLRect texture_rect(0, scratch_target.getHeight(), scratch_target.getWidth(), 0);
+ glViewport(texture_rect.mLeft, texture_rect.mBottom, texture_rect.getWidth(), texture_rect.getHeight());
+ }
+
+ // View matrix
+ // Coordinates should be in pixels. 1.0f = 1 pixel on the framebuffer.
+ // Camera is centered in the middle of the framebuffer.
+ glm::mat4 view(glm::make_mat4((GLfloat*)OGL_TO_CFR_ROTATION));
+ {
+ LLViewerCamera camera;
+ const LLVector3 camera_origin(target_half_width, target_half_height, 0.5f);
+ const LLVector3 camera_look_down(target_half_width, target_half_height, 0.0f);
+ camera.lookAt(camera_origin, camera_look_down, LLVector3::y_axis);
+ camera.setAspect(F32(scratch_target.getHeight()) / F32(scratch_target.getWidth()));
+ GLfloat ogl_matrix[16];
+ camera.getOpenGLTransform(ogl_matrix);
+ view *= glm::make_mat4(ogl_matrix);
+ }
+
+ LLGLDisable stencil(GL_STENCIL_TEST);
+ LLGLDisable scissor(GL_SCISSOR_TEST);
+ LLGLEnable cull_face(GL_CULL_FACE);
+ LLGLDepthTest depth_test(GL_FALSE, GL_FALSE, GL_ALWAYS);
+ LLGLEnable blend(GL_BLEND);
+ gGL.setSceneBlendType(LLRender::BT_ALPHA);
+
+ LLGLSLShader& shader = gTerrainStampProgram;
+ shader.bind();
+
+ // First, apply the paint map as the background
+ {
+ glm::mat4 model;
+ {
+ model = glm::scale(model, glm::vec3((F32)tex.getWidth(), (F32)tex.getHeight(), 1.0f));
+ model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
+ }
+ glm::mat4 modelview = view * model;
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.loadMatrix(glm::value_ptr(modelview));
+
+ shader.bindTexture(LLShaderMgr::DIFFUSE_MAP, &tex);
+ // We care about the whole paintmap, which is already a power of two.
+ // Hence, TERRAIN_STAMP_SCALE = (1.0,1.0)
+ shader.uniform2f(LLShaderMgr::TERRAIN_STAMP_SCALE, 1.0f, 1.0f);
+ buf->setBuffer();
+ buf->draw(LLRender::TRIANGLES, buf->getIndicesSize(), 0);
+ }
+
+ LLTerrainPaintQueue queue_out(LLTerrainPaint::RGB);
+
+ // Incrementally apply each brush stroke to the render target, then extract
+ // the result back into memory as an RGB paint.
+ // Put each result in queue_out.
+ const std::vector<LLTerrainBrush::ptr_t>& brush_list = queue_in.get();
+ for (size_t i = 0; i < brush_list.size(); ++i)
+ {
+ const LLTerrainBrush::ptr_t& brush_in = brush_list[i];
+
+ // Modelview matrix for the current brush
+ // View matrix is already computed. Just need the model matrix.
+ // Orthographic projection matrix is already updated
+ // *NOTE: Brush path information is in region space. It will need to be
+ // converted to paintmap pixel space before it makes sense.
+ F32 brush_width_x;
+ F32 brush_width_y;
+ F32 brush_start_x;
+ F32 brush_start_y;
+ {
+ F32 min_x = brush_in->mPath[0].mV[VX];
+ F32 max_x = min_x;
+ F32 min_y = brush_in->mPath[0].mV[VY];
+ F32 max_y = min_y;
+ for (size_t i = 1; i < brush_in->mPath.size(); ++i)
+ {
+ const F32 x = brush_in->mPath[i].mV[VX];
+ const F32 y = brush_in->mPath[i].mV[VY];
+ min_x = llmin(min_x, x);
+ max_x = llmax(max_x, x);
+ min_y = llmin(min_y, y);
+ max_y = llmax(max_y, y);
+ }
+ brush_width_x = brush_in->mBrushSize + (max_x - min_x);
+ brush_width_y = brush_in->mBrushSize + (max_y - min_y);
+ brush_start_x = min_x - (brush_in->mBrushSize / 2.0f);
+ brush_start_y = min_y - (brush_in->mBrushSize / 2.0f);
+ // Convert brush path information to paintmap pixel space from region
+ // space.
+ brush_width_x *= tex.getWidth() / region.getWidth();
+ brush_width_y *= tex.getHeight() / region.getWidth();
+ brush_start_x *= tex.getWidth() / region.getWidth();
+ brush_start_y *= tex.getHeight() / region.getWidth();
+ }
+ glm::mat4 model;
+ {
+ model = glm::scale(model, glm::vec3(brush_width_x, brush_width_y, 1.0f));
+ model = glm::translate(model, glm::vec3(brush_start_x, brush_start_y, 0.0f));
+ }
+ glm::mat4 modelview = view * model;
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.loadMatrix(glm::value_ptr(modelview));
+
+ // Apply the "brush" to the render target
+ {
+ // TODO: Use different shader for this - currently this is using the stamp shader. The white image is just a placeholder for now
+ shader.bindTexture(LLShaderMgr::DIFFUSE_MAP, LLViewerFetchedTexture::sWhiteImagep);
+ shader.uniform2f(LLShaderMgr::TERRAIN_STAMP_SCALE, 1.0f, 1.0f);
+ buf->setBuffer();
+ buf->draw(LLRender::TRIANGLES, buf->getIndicesSize(), 0);
+ }
+
+ // Extract the result back into memory as an RGB paint
+ LLTerrainPaint::ptr_t paint_out = std::make_shared<LLTerrainPaint>();
+ {
+ paint_out->mStartX = U16(floor(brush_start_x));
+ paint_out->mStartY = U16(floor(brush_start_y));
+ const F32 dX = brush_start_x - F32(paint_out->mStartX);
+ const F32 dY = brush_start_y - F32(paint_out->mStartY);
+ paint_out->mWidthX = U16(ceil(brush_width_x + dX));
+ paint_out->mWidthY = U16(ceil(brush_width_y + dY));
+ paint_out->mBitDepth = 8; // Will be reduced to TerrainPaintBitDepth bits later
+ paint_out->mComponents = LLTerrainPaint::RGB;
+ // The brush strokes are expected to sometimes partially venture
+ // outside of the paintmap bounds.
+ paint_out->confine_to(tex);
+ paint_out->mData.resize(paint_out->mComponents * paint_out->mWidthX * paint_out->mWidthY);
+ constexpr GLint miplevel = 0;
+ const S32 x_offset = paint_out->mStartX;
+ const S32 y_offset = paint_out->mStartY;
+ const S32 width = paint_out->mWidthX;
+ const S32 height = paint_out->mWidthY;
+ constexpr GLenum pixformat = GL_RGB;
+ constexpr GLenum pixtype = GL_UNSIGNED_BYTE;
+ llassert(paint_out->mData.size() <= std::numeric_limits<GLsizei>::max());
+ const GLsizei buf_size = (GLsizei)paint_out->mData.size();
+ U8* pixels = paint_out->mData.data();
+ glReadPixels(x_offset, y_offset, width, height, pixformat, pixtype, pixels);
+ }
+
+ // Enqueue the result to the new paint queue, with bit depths per color
+ // channel reduced from 8 to 5, and reduced from RGBA (paintmap
+ // sub-rectangle update with alpha mask) to RGB (paintmap sub-rectangle
+ // update without alpha mask). This format is suitable for sending
+ // over the network.
+ // *TODO: At some point, queue_out will pass through a network
+ // round-trip which will reduce the bit depth, making the
+ // pre-conversion step not necessary.
+ queue_out.enqueue(paint_out);
+ LLCachedControl<U32> bit_depth(gSavedSettings, "TerrainPaintBitDepth");
+ queue_out.convertBitDepths(queue_out.size()-1, bit_depth);
+ }
+
+ queue_in.clear();
+
+ scratch_target.flush();
+
+ LLGLSLShader::unbind();
+
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.popMatrix();
+
+ return queue_out;
+}
+
+template<typename T>
+LLTerrainQueue<T>::LLTerrainQueue(LLTerrainQueue<T>& other)
+{
+ *this = other;
+}
+
+template<typename T>
+LLTerrainQueue<T>& LLTerrainQueue<T>::operator=(LLTerrainQueue<T>& other)
+{
+ mList = other.mList;
+ return *this;
+}
+
+template<typename T>
+bool LLTerrainQueue<T>::enqueue(std::shared_ptr<T>& t, bool dry_run)
+{
+ if (!dry_run) { mList.push_back(t); }
+ return true;
+}
+
+template<typename T>
+bool LLTerrainQueue<T>::enqueue(std::vector<std::shared_ptr<T>>& list)
+{
+ constexpr bool dry_run = true;
+ for (auto& t : list)
+ {
+ if (!enqueue(t, dry_run)) { return false; }
+ }
+ for (auto& t : list)
+ {
+ enqueue(t);
+ }
+ return true;
+}
+
+template<typename T>
+size_t LLTerrainQueue<T>::size() const
+{
+ return mList.size();
+}
+
+template<typename T>
+bool LLTerrainQueue<T>::empty() const
+{
+ return mList.empty();
+}
+
+template<typename T>
+void LLTerrainQueue<T>::clear()
+{
+ mList.clear();
+}
+
+void LLTerrainPaint::assert_confined_to(const LLTexture& tex) const
+{
+ llassert(mStartX >= 0 && mStartX < tex.getWidth());
+ llassert(mStartY >= 0 && mStartY < tex.getHeight());
+ llassert(mWidthX <= tex.getWidth() - mStartX);
+ llassert(mWidthY <= tex.getHeight() - mStartY);
+}
+
+void LLTerrainPaint::confine_to(const LLTexture& tex)
+{
+ mStartX = llmax(mStartX, 0);
+ mStartY = llmax(mStartY, 0);
+ mWidthX = llmin(mWidthX, tex.getWidth() - mStartX);
+ mWidthY = llmin(mWidthY, tex.getHeight() - mStartY);
+ assert_confined_to(tex);
+}
+
+LLTerrainPaintQueue::LLTerrainPaintQueue(U8 components)
+: mComponents(components)
+{
+ llassert(mComponents == LLTerrainPaint::RGB || mComponents == LLTerrainPaint::RGBA);
+}
+
+LLTerrainPaintQueue::LLTerrainPaintQueue(LLTerrainPaintQueue& other)
+: LLTerrainQueue<LLTerrainPaint>(other)
+, mComponents(other.mComponents)
+{
+ llassert(mComponents == LLTerrainPaint::RGB || mComponents == LLTerrainPaint::RGBA);
+}
+
+LLTerrainPaintQueue& LLTerrainPaintQueue::operator=(LLTerrainPaintQueue& other)
+{
+ LLTerrainQueue<LLTerrainPaint>::operator=(other);
+ mComponents = other.mComponents;
+ return *this;
+}
+
+bool LLTerrainPaintQueue::enqueue(LLTerrainPaint::ptr_t& paint, bool dry_run)
+{
+ llassert(paint);
+ if (!paint) { return false; }
+
+ // The paint struct should be pre-validated before this code is reached.
+ llassert(!paint->mData.empty());
+ // The internal paint map image is currently 8 bits, so that's the maximum
+ // allowed bit depth.
+ llassert(paint->mBitDepth > 0 && paint->mBitDepth <= 8);
+ llassert(paint->mData.size() == (mComponents * paint->mWidthX * paint->mWidthY));
+ llassert(paint->mWidthX > 0);
+ llassert(paint->mWidthY > 0);
+#ifdef SHOW_ASSERT
+ static LLCachedControl<U32> max_texture_width(gSavedSettings, "RenderMaxTextureResolution", 2048);
+#endif
+ llassert(paint->mWidthX <= max_texture_width);
+ llassert(paint->mWidthY <= max_texture_width);
+ llassert(paint->mStartX < max_texture_width);
+ llassert(paint->mStartY < max_texture_width);
+
+ return LLTerrainQueue<LLTerrainPaint>::enqueue(paint, dry_run);
+}
+
+bool LLTerrainPaintQueue::enqueue(LLTerrainPaintQueue& queue)
+{
+ return LLTerrainQueue<LLTerrainPaint>::enqueue(queue.mList);
+}
+
+void LLTerrainPaintQueue::convertBitDepths(size_t index, U8 target_bit_depth)
+{
+ llassert(target_bit_depth > 0 && target_bit_depth <= 8);
+ llassert(index < mList.size());
+
+ LLTerrainPaint::ptr_t& paint = mList[index];
+ if (paint->mBitDepth == target_bit_depth) { return; }
+
+ const F32 old_bit_max = F32((1 << paint->mBitDepth) - 1);
+ const F32 new_bit_max = F32((1 << target_bit_depth) - 1);
+ const F32 bit_conversion_factor = new_bit_max / old_bit_max;
+
+ for (U8& color : paint->mData)
+ {
+ color = (U8)llround(F32(color) * bit_conversion_factor);
+ }
+
+ paint->mBitDepth = target_bit_depth;
+}
+
+LLTerrainBrushQueue::LLTerrainBrushQueue()
+: LLTerrainQueue<LLTerrainBrush>()
+{
+}
+
+LLTerrainBrushQueue::LLTerrainBrushQueue(LLTerrainBrushQueue& other)
+: LLTerrainQueue<LLTerrainBrush>(other)
+{
+}
+
+LLTerrainBrushQueue& LLTerrainBrushQueue::operator=(LLTerrainBrushQueue& other)
+{
+ LLTerrainQueue<LLTerrainBrush>::operator=(other);
+ return *this;
+}
+
+bool LLTerrainBrushQueue::enqueue(LLTerrainBrush::ptr_t& brush, bool dry_run)
+{
+ llassert(brush->mBrushSize > 0);
+ llassert(!brush->mPath.empty());
+ llassert(brush->mPathOffset < brush->mPath.size());
+ llassert(brush->mPathOffset < 2); // Harmless, but doesn't do anything useful, so might be a sign of implementation error
+ return LLTerrainQueue<LLTerrainBrush>::enqueue(brush, dry_run);
+}
+
+bool LLTerrainBrushQueue::enqueue(LLTerrainBrushQueue& queue)
+{
+ return LLTerrainQueue<LLTerrainBrush>::enqueue(queue.mList);
+}
+
+template class LLTerrainQueue<LLTerrainPaint>;
+template class LLTerrainQueue<LLTerrainBrush>;
diff --git a/indra/newview/llterrainpaintmap.h b/indra/newview/llterrainpaintmap.h
index 66827862c5..d4859b747b 100644
--- a/indra/newview/llterrainpaintmap.h
+++ b/indra/newview/llterrainpaintmap.h
@@ -26,9 +26,20 @@
#pragma once
+#include <memory>
+#include <vector>
+
+#include "stdtypes.h"
+#include "v2math.h"
+
+class LLTexture;
class LLViewerRegion;
class LLViewerTexture;
+class LLTerrainPaintQueue;
+class LLTerrainBrushQueue;
+// TODO: Terrain painting across regions. Assuming painting is confined to one
+// region for now.
class LLTerrainPaintMap
{
public:
@@ -39,4 +50,121 @@ public:
// to type TERRAIN_PAINT_TYPE_PBR_PAINTMAP.
// Returns true if successful
static bool bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, LLViewerTexture& tex);
+
+ // This operation clears the queue
+ // TODO: Decide if clearing the queue is needed - seems inconsistent
+ static void applyPaintQueueRGB(LLViewerTexture& tex, LLTerrainPaintQueue& queue);
+
+ static LLTerrainPaintQueue convertPaintQueueRGBAToRGB(LLViewerTexture& tex, LLTerrainPaintQueue& queue_in);
+ // TODO: Implement (it's similar to convertPaintQueueRGBAToRGB but different shader + need to calculate the dimensions + need a different vertex buffer for each brush stroke)
+ static LLTerrainPaintQueue convertBrushQueueToPaintRGB(const LLViewerRegion& region, LLViewerTexture& tex, LLTerrainBrushQueue& queue_in);
+};
+
+template<typename T>
+class LLTerrainQueue
+{
+public:
+ LLTerrainQueue() = default;
+ LLTerrainQueue(LLTerrainQueue<T>& other);
+ LLTerrainQueue& operator=(LLTerrainQueue<T>& other);
+
+ bool enqueue(std::shared_ptr<T>& t, bool dry_run = false);
+ size_t size() const;
+ bool empty() const;
+ void clear();
+
+ const std::vector<std::shared_ptr<T>>& get() const { return mList; }
+
+protected:
+ bool enqueue(std::vector<std::shared_ptr<T>>& list);
+
+ std::vector<std::shared_ptr<T>> mList;
+};
+
+// Enqueued paint operations, in texture coordinates.
+// mData is always RGB or RGBA (determined by mComponents), with each U8
+// storing one color with a max value of (1 >> mBitDepth) - 1
+struct LLTerrainPaint
+{
+ using ptr_t = std::shared_ptr<LLTerrainPaint>;
+
+ U16 mStartX;
+ U16 mStartY;
+ U16 mWidthX;
+ U16 mWidthY;
+ U8 mBitDepth;
+ U8 mComponents;
+ static constexpr U8 RGB = 3;
+ static constexpr U8 RGBA = 4;
+ std::vector<U8> mData;
+
+ // Asserts that this paint's start/width fit within the bounds of the
+ // provided texture dimensions.
+ void assert_confined_to(const LLTexture& tex) const;
+ // Confines this paint's start/width so it fits within the bounds of the
+ // provided texture dimensions.
+ // Does not allocate mData.
+ void confine_to(const LLTexture& tex);
+};
+
+class LLTerrainPaintQueue : public LLTerrainQueue<LLTerrainPaint>
+{
+public:
+ LLTerrainPaintQueue() = delete;
+ // components determines what type of LLTerrainPaint is allowed. Must be 3 (RGB) or 4 (RGBA)
+ LLTerrainPaintQueue(U8 components);
+ LLTerrainPaintQueue(LLTerrainPaintQueue& other);
+ LLTerrainPaintQueue& operator=(LLTerrainPaintQueue& other);
+
+ bool enqueue(LLTerrainPaint::ptr_t& paint, bool dry_run = false);
+ bool enqueue(LLTerrainPaintQueue& queue);
+
+ U8 getComponents() const { return mComponents; }
+ // Convert mBitDepth for the LLTerrainPaint in the queue at index
+ // If mBitDepth is already equal to target_bit_depth, no conversion takes
+ // place.
+ // It is currently the responsibility of the paint queue to convert
+ // incoming bits to the right bit depth for the paintmap (this could
+ // change in the future).
+ void convertBitDepths(size_t index, U8 target_bit_depth);
+
+private:
+ U8 mComponents;
+};
+
+struct LLTerrainBrush
+{
+ using ptr_t = std::shared_ptr<LLTerrainBrush>;
+
+ // Width of the brush in region space. The brush is a square texture with
+ // alpha.
+ F32 mBrushSize;
+ // Brush path points in region space, excluding the vertical axis, which
+ // does not contribute to the paint map.
+ std::vector<LLVector2> mPath;
+ // Offset of the brush path to actually start drawing at. An offset of 0
+ // indicates that a brush stroke has just started (i.e. the user just
+ // pressed down the mouse button). An offset greater than 0 indicates the
+ // continuation of a brush stroke. Skipped entries in mPath are not drawn
+ // directly, but are used for stroke orientation and path interpolation.
+ // TODO: For the initial implementation, mPathOffset will be 0 and mPath
+ // will be of length of at most 1, leading to discontinuous paint paths.
+ // Then, mPathOffset may be 1 or 0, 1 indicating the continuation of a
+ // stroke with linear interpolation. It is unlikely that we will implement
+ // anything more sophisticated than that for now.
+ U8 mPathOffset;
+ // Indicates if this is the end of the brush stroke. Can occur if the mouse
+ // button is lifted, or if the mouse temporarily stops while held down.
+ bool mPathEnd;
+};
+
+class LLTerrainBrushQueue : public LLTerrainQueue<LLTerrainBrush>
+{
+public:
+ LLTerrainBrushQueue();
+ LLTerrainBrushQueue(LLTerrainBrushQueue& other);
+ LLTerrainBrushQueue& operator=(LLTerrainBrushQueue& other);
+
+ bool enqueue(LLTerrainBrush::ptr_t& brush, bool dry_run = false);
+ bool enqueue(LLTerrainBrushQueue& queue);
};
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 69c40066b4..ed91214bd0 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -2090,7 +2090,7 @@ bool LLTextureFetchWorker::deleteOK()
// Allow any pending reads or writes to complete
if (mCacheReadHandle != LLTextureCache::nullHandle())
{
- if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, true))
+ if (!mFetcher->mTextureCache || mFetcher->mTextureCache->readComplete(mCacheReadHandle, true))
{
mCacheReadHandle = LLTextureCache::nullHandle();
}
@@ -2101,7 +2101,7 @@ bool LLTextureFetchWorker::deleteOK()
}
if (mCacheWriteHandle != LLTextureCache::nullHandle())
{
- if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
+ if (!mFetcher->mTextureCache || mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
{
mCacheWriteHandle = LLTextureCache::nullHandle();
}
diff --git a/indra/newview/lltoastgroupnotifypanel.cpp b/indra/newview/lltoastgroupnotifypanel.cpp
index 3c3440d41a..95653dc19b 100644
--- a/indra/newview/lltoastgroupnotifypanel.cpp
+++ b/indra/newview/lltoastgroupnotifypanel.cpp
@@ -87,10 +87,21 @@ LLToastGroupNotifyPanel::LLToastGroupNotifyPanel(const LLNotificationPtr& notifi
std::string timeStr = "[" + LLTrans::getString("TimeWeek") + "], ["
+ LLTrans::getString("TimeMonth") + "]/["
+ LLTrans::getString("TimeDay") + "]/["
- + LLTrans::getString("TimeYear") + "] ["
- + LLTrans::getString("TimeHour") + "]:["
- + LLTrans::getString("TimeMin") + "] ["
- + LLTrans::getString("TimeTimezone") + "]";
+ + LLTrans::getString("TimeYear") + "] [";
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ if (use_24h)
+ {
+ timeStr += LLTrans::getString("TimeHour") + "]:["
+ + LLTrans::getString("TimeMin") + "] ["
+ + LLTrans::getString("TimeTimezone") + "]";
+ }
+ else
+ {
+ timeStr += LLTrans::getString("TimeHour12") + "]:["
+ + LLTrans::getString("TimeMin") + "] ["
+ + LLTrans::getString("TimeAMPM") + "] ["
+ + LLTrans::getString("TimeTimezone") + "]";
+ }
const LLDate timeStamp = notification->getDate();
LLDate notice_date = timeStamp.notNull() ? timeStamp : payload["received_time"].asDate();
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index 865d7fd442..184c0e7d8b 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -76,6 +76,7 @@
#include "llslurl.h"
#include "llstartup.h"
#include "llperfstats.h"
+#include "rlvcommon.h"
// Third party library includes
#include <boost/algorithm/string.hpp>
@@ -910,6 +911,7 @@ void settings_setup_listeners()
setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorByDistEnabled", handleUserImpostorByDistEnabledChanged);
setting_setup_signal_listener(gSavedSettings, "TuningFPSStrategy", handleFPSTuningStrategyChanged);
{
+ setting_setup_signal_listener(gSavedSettings, "LocalTerrainPaintEnabled", handleSetShaderChanged);
setting_setup_signal_listener(gSavedSettings, "LocalTerrainPaintEnabled", handleLocalTerrainChanged);
const char* transform_suffixes[] = {
"ScaleU",
@@ -932,6 +934,8 @@ void settings_setup_listeners()
setting_setup_signal_listener(gSavedSettings, "TerrainPaintBitDepth", handleSetShaderChanged);
setting_setup_signal_listener(gSavedPerAccountSettings, "AvatarHoverOffsetZ", handleAvatarHoverOffsetChanged);
+
+ setting_setup_signal_listener(gSavedSettings, Rlv::Settings::TopLevelMenu, Rlv::Util::menuToggleVisible);
}
#if TEST_CACHED_CONTROL
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 9d9961d51c..b2a7d875ab 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -176,6 +176,7 @@
#include "llpreviewtexture.h"
#include "llscriptfloater.h"
#include "llsyswellwindow.h"
+#include "rlvfloaters.h"
// *NOTE: Please add files in alphabetical order to keep merges easy.
@@ -483,6 +484,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("region_debug_console", "floater_region_debug_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionDebugConsole>);
LLFloaterReg::add("region_info", "floater_region_info.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionInfo>);
LLFloaterReg::add("region_restarting", "floater_region_restarting.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionRestarting>);
+ LLFloaterReg::add("rlv_console", "floater_rlv_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<Rlv::FloaterConsole>);
LLFloaterReg::add("script_debug", "floater_script_debug.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptDebug>);
LLFloaterReg::add("script_debug_output", "floater_script_debug_panel.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptDebugOutput>);
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 39aa85beea..39213569a5 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -117,6 +117,7 @@
#include "lltoolmgr.h"
#include "lltoolpie.h"
#include "lltoolselectland.h"
+#include "llterrainpaintmap.h"
#include "lltrans.h"
#include "llviewerdisplay.h" //for gWindowResized
#include "llviewergenericmessage.h"
@@ -151,6 +152,7 @@
#include "llviewershadermgr.h"
#include "gltfscenemanager.h"
#include "gltf/asset.h"
+#include "rlvcommon.h"
using namespace LLAvatarAppearanceDefines;
@@ -1428,16 +1430,21 @@ class LLAdvancedTerrainCreateLocalPaintMap : public view_listener_t
return false;
}
+ // This calls gLocalTerrainMaterials.setPaintType
+ // It also ensures the terrain bake shader is compiled (handleSetShaderChanged), so call this first
+ // *TODO: Fix compile errors in shader so it can be used for all platforms. Then we can unhide the shader from behind this setting and remove the hook to handleSetShaderChanged. This advanced setting is intended to be used as a local setting for testing terrain, not a feature flag, but it is currently used like a feature flag as a temporary hack.
+ // *TODO: Ideally we would call setPaintType *after* the paint map is well-defined. The terrain draw pool should be able to handle an undefined paint map in the meantime.
+ gSavedSettings.setBOOL("LocalTerrainPaintEnabled", true);
+
U16 dim = (U16)gSavedSettings.getU32("TerrainPaintResolution");
// Ensure a reasonable image size of power two
const U32 max_resolution = gSavedSettings.getU32("RenderMaxTextureResolution");
dim = llclamp(dim, 16, max_resolution);
dim = 1 << U32(std::ceil(std::log2(dim)));
+ // TODO: Could probably get away with not using image raw here, now that we aren't writing bits via the CPU (see load_exr for example)
LLPointer<LLImageRaw> image_raw = new LLImageRaw(dim,dim,3);
LLPointer<LLViewerTexture> tex = LLViewerTextureManager::getLocalTexture(image_raw.get(), true);
const bool success = LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(*region, *tex);
- // This calls gLocalTerrainMaterials.setPaintType
- gSavedSettings.setBOOL("LocalTerrainPaintEnabled", true);
// If baking the paintmap failed, set the paintmap to nullptr. This
// causes LLDrawPoolTerrain to use a blank paintmap instead.
if (!success) { tex = nullptr; }
@@ -1447,6 +1454,92 @@ class LLAdvancedTerrainCreateLocalPaintMap : public view_listener_t
}
};
+class LLAdvancedTerrainEditLocalPaintMap : public view_listener_t
+{
+ bool handleEvent(const LLSD& userdata)
+ {
+ LLViewerTexture* tex = gLocalTerrainMaterials.getPaintMap();
+ if (!tex)
+ {
+ LL_WARNS() << "No local paint map available to edit" << LL_ENDL;
+ return false;
+ }
+
+ LLTerrainBrushQueue& brush_queue = gLocalTerrainMaterials.getBrushQueue();
+ LLTerrainPaintQueue& paint_request_queue = gLocalTerrainMaterials.getPaintRequestQueue();
+
+ const LLViewerRegion* region = gAgent.getRegion();
+ if (!region)
+ {
+ LL_WARNS() << "No current region for calculating paint operations" << LL_ENDL;
+ return false;
+ }
+ // TODO: Create the brush
+ // Just a dab for now
+ LLTerrainBrush::ptr_t brush = std::make_shared<LLTerrainBrush>();
+ brush->mBrushSize = 16.0f;
+ brush->mPath.emplace_back(17.0f, 17.0f);
+ brush->mPathOffset = 0;
+ brush->mPathEnd = true;
+ brush_queue.enqueue(brush);
+ LLTerrainPaintQueue brush_as_paint_queue = LLTerrainPaintMap::convertBrushQueueToPaintRGB(*region, *tex, brush_queue);
+ //paint_send_queue.enqueue(brush_as_paint_queue); // TODO: What was this line for? (it might also be a leftover line from an unfinished edit)
+
+ // TODO: Keep this around for later testing (i.e. when reducing framebuffer size and the offsets that requires)
+#if 0
+ // Enqueue a paint
+ // Modifies a subsection of the region paintmap with the material in
+ // the last slot.
+ // It is currently the responsibility of the paint queue to convert
+ // incoming bits to the right bit depth for the paintmap (this could
+ // change in the future).
+ LLTerrainPaint::ptr_t paint = std::make_shared<LLTerrainPaint>();
+ const U16 width = 33;
+ paint->mStartX = 1;
+ paint->mStartY = 1;
+ paint->mWidthX = width;
+ paint->mWidthY = width;
+ constexpr U8 bit_depth = 5;
+ paint->mBitDepth = bit_depth;
+ constexpr U8 max_value = (1 << bit_depth) - 1;
+ const size_t pixel_count = width * width;
+ const U8 components = LLTerrainPaint::RGBA;
+ paint->mComponents = components;
+ paint->mData.resize(components * pixel_count);
+ for (size_t h = 0; h < paint->mWidthY; ++h)
+ {
+ for (size_t w = 0; w < paint->mWidthX; ++w)
+ {
+ const size_t pixel = (h * paint->mWidthX) + w;
+ // Solid blue color
+ paint->mData[(components*pixel) + components - 2] = max_value; // blue
+ //// Alpha grid: 1.0 if odd for either dimension, 0.0 otherwise
+ //const U8 alpha = U8(F32(max_value) * F32(bool(w % 2) || bool(h % 2)));
+ //paint->mData[(components*pixel) + components - 1] = alpha; // alpha
+ // Alpha "frame"
+ const bool edge = w == 0 || h == 0 || w == (paint->mWidthX - 1) || h == (paint->mWidthY - 1);
+ const bool near_edge_frill = ((w == 1 || w == (paint->mWidthX - 2)) && (h % 2 == 0)) ||
+ ((h == 1 || h == (paint->mWidthY - 2)) && (w % 2 == 0));
+ const U8 alpha = U8(F32(max_value) * F32(edge || near_edge_frill));
+ paint->mData[(components*pixel) + components - 1] = alpha; // alpha
+ }
+ }
+ paint_request_queue.enqueue(paint);
+#endif
+
+ // Apply the paint queues ad-hoc right here for now.
+ // *TODO: Eventually the paint queue(s) should be applied at a
+ // predictable time in the viewer frame loop.
+ // TODO: In hindsight... maybe we *should* bind the paintmap to the render buffer. That makes a lot more sense, and we wouldn't have to reduce its resolution by settling for the bake buffer. If we do that, make a comment above convertPaintQueueRGBAToRGB that the texture is modified!
+ LLTerrainPaintQueue paint_send_queue = LLTerrainPaintMap::convertPaintQueueRGBAToRGB(*tex, paint_request_queue);
+ LLTerrainPaintQueue& paint_map_queue = gLocalTerrainMaterials.getPaintMapQueue();
+ paint_map_queue.enqueue(paint_send_queue);
+ LLTerrainPaintMap::applyPaintQueueRGB(*tex, paint_map_queue);
+
+ return true;
+ }
+};
+
class LLAdvancedTerrainDeleteLocalPaintMap : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
@@ -6280,6 +6373,8 @@ void show_debug_menus()
gMenuBarView->setItemVisible("Advanced", debug);
// gMenuBarView->setItemEnabled("Advanced", debug); // Don't disable Advanced keyboard shortcuts when hidden
+ Rlv::Util::menuToggleVisible();
+
gMenuBarView->setItemVisible("Debug", qamode);
gMenuBarView->setItemEnabled("Debug", qamode);
@@ -9624,8 +9719,6 @@ void initialize_menus()
registrar.add("Agent.ToggleMicrophone", boost::bind(&LLAgent::toggleMicrophone, _2), cb_info::UNTRUSTED_BLOCK);
enable.add("Agent.IsMicrophoneOn", boost::bind(&LLAgent::isMicrophoneOn, _2));
enable.add("Agent.IsActionAllowed", boost::bind(&LLAgent::isActionAllowed, _2));
- registrar.add("Agent.ToggleHearMediaSoundFromAvatar", boost::bind(&LLAgent::toggleHearMediaSoundFromAvatar), cb_info::UNTRUSTED_BLOCK);
- registrar.add("Agent.ToggleHearVoiceFromAvatar", boost::bind(&LLAgent::toggleHearVoiceFromAvatar), cb_info::UNTRUSTED_BLOCK);
// File menu
init_menu_file();
@@ -9810,6 +9903,7 @@ void initialize_menus()
// Develop > Terrain
view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain");
view_listener_t::addMenu(new LLAdvancedTerrainCreateLocalPaintMap(), "Advanced.TerrainCreateLocalPaintMap");
+ view_listener_t::addMenu(new LLAdvancedTerrainEditLocalPaintMap(), "Advanced.TerrainEditLocalPaintMap");
view_listener_t::addMenu(new LLAdvancedTerrainDeleteLocalPaintMap(), "Advanced.TerrainDeleteLocalPaintMap");
// Advanced > UI
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index da019d60d8..33d9b5eb67 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -118,6 +118,8 @@
#include "llpanelplaceprofile.h"
#include "llviewerregion.h"
#include "llfloaterregionrestarting.h"
+#include "rlvactions.h"
+#include "rlvhandler.h"
#include "llnotificationmanager.h" //
#include "llexperiencecache.h"
@@ -2382,15 +2384,16 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
}
bool is_audible = (CHAT_AUDIBLE_FULLY == chat.mAudible);
+ bool show_script_chat_particles = chat.mSourceType == CHAT_SOURCE_OBJECT
+ && chat.mChatType != CHAT_TYPE_DEBUG_MSG
+ && gSavedSettings.getBOOL("EffectScriptChatParticles");
chatter = gObjectList.findObject(from_id);
if (chatter)
{
chat.mPosAgent = chatter->getPositionAgent();
// Make swirly things only for talking objects. (not script debug messages, though)
- if (chat.mSourceType == CHAT_SOURCE_OBJECT
- && chat.mChatType != CHAT_TYPE_DEBUG_MSG
- && gSavedSettings.getBOOL("EffectScriptChatParticles") )
+ if (show_script_chat_particles && (!RlvActions::isRlvEnabled() || CHAT_TYPE_OWNER != chat.mChatType) )
{
LLPointer<LLViewerPartSourceChat> psc = new LLViewerPartSourceChat(chatter->getPositionAgent());
psc->setSourceObject(chatter);
@@ -2482,8 +2485,25 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
case CHAT_TYPE_SHOUT:
prefix += LLTrans::getString("shout") + " ";
break;
- case CHAT_TYPE_DEBUG_MSG:
case CHAT_TYPE_OWNER:
+ if (RlvActions::isRlvEnabled())
+ {
+ if (RlvHandler::instance().handleSimulatorChat(mesg, chat, chatter))
+ {
+ break;
+ }
+ else if (show_script_chat_particles)
+ {
+ LLPointer<LLViewerPartSourceChat> psc = new LLViewerPartSourceChat(chatter->getPositionAgent());
+ psc->setSourceObject(chatter);
+ psc->setColor(color);
+ //We set the particles to be owned by the object's owner,
+ //just in case they should be muted by the mute list
+ psc->setOwnerUUID(owner_id);
+ LLViewerPartSim::getInstance()->addPartSource(psc);
+ }
+ }
+ case CHAT_TYPE_DEBUG_MSG:
case CHAT_TYPE_NORMAL:
case CHAT_TYPE_DIRECT:
break;
@@ -6623,7 +6643,6 @@ void process_initiate_download(LLMessageSystem* msg, void**)
(void**)new std::string(viewer_filename));
}
-
void process_script_teleport_request(LLMessageSystem* msg, void**)
{
if (!gSavedSettings.getBOOL("ScriptsCanShowUI")) return;
@@ -6637,6 +6656,11 @@ void process_script_teleport_request(LLMessageSystem* msg, void**)
msg->getString("Data", "SimName", sim_name);
msg->getVector3("Data", "SimPosition", pos);
msg->getVector3("Data", "LookAt", look_at);
+ U32 flags = (BEACON_SHOW_MAP | BEACON_FOCUS_MAP);
+ if (msg->has("Options"))
+ {
+ msg->getU32("Options", "Flags", flags);
+ }
LLFloaterWorldMap* instance = LLFloaterWorldMap::getInstance();
if(instance)
@@ -6647,7 +6671,13 @@ void process_script_teleport_request(LLMessageSystem* msg, void**)
<< LL_ENDL;
instance->trackURL(sim_name, (S32)pos.mV[VX], (S32)pos.mV[VY], (S32)pos.mV[VZ]);
- LLFloaterReg::showInstance("world_map", "center");
+ if (flags & BEACON_SHOW_MAP)
+ {
+ bool old_auto_focus = instance->getAutoFocus();
+ instance->setAutoFocus(flags & BEACON_FOCUS_MAP);
+ instance->openFloater("center");
+ instance->setAutoFocus(old_auto_focus);
+ }
}
// remove above two lines and replace with below line
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 88e5d900cb..739821d2f5 100755
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -3155,7 +3155,7 @@ void LLViewerRegion::unpackRegionHandshake()
std::string cap = getCapability("ModifyRegion"); // needed for queueQuery
if (cap.empty())
{
- LLFloaterRegionInfo::sRefreshFromRegion(this);
+ LLFloaterRegionInfo::refreshFromRegion(this);
}
else
{
@@ -3167,7 +3167,7 @@ void LLViewerRegion::unpackRegionHandshake()
LLVLComposition* compp = region->getComposition();
if (!compp) { return; }
compp->apply(composition_changes);
- LLFloaterRegionInfo::sRefreshFromRegion(region);
+ LLFloaterRegionInfo::refreshFromRegion(region);
});
}
}
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index a8fe221d98..a6d397c039 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -100,6 +100,7 @@ LLGLSLShader gReflectionProbeDisplayProgram;
LLGLSLShader gCopyProgram;
LLGLSLShader gCopyDepthProgram;
LLGLSLShader gPBRTerrainBakeProgram;
+LLGLSLShader gTerrainStampProgram;
//object shaders
LLGLSLShader gObjectPreviewProgram;
@@ -3170,6 +3171,34 @@ bool LLViewerShaderMgr::loadShadersInterface()
}
}
+ if (gSavedSettings.getBOOL("LocalTerrainPaintEnabled"))
+ {
+ if (success)
+ {
+ LLGLSLShader* shader = &gTerrainStampProgram;
+ U32 bit_depth = gSavedSettings.getU32("TerrainPaintBitDepth");
+ // LLTerrainPaintMap currently uses an RGB8 texture internally
+ bit_depth = llclamp(bit_depth, 1, 8);
+ shader->mName = llformat("Terrain Stamp Shader RGB%o", bit_depth);
+
+ shader->mShaderFiles.clear();
+ shader->mShaderFiles.push_back(make_pair("interface/terrainStampV.glsl", GL_VERTEX_SHADER));
+ shader->mShaderFiles.push_back(make_pair("interface/terrainStampF.glsl", GL_FRAGMENT_SHADER));
+ shader->mShaderLevel = mShaderLevel[SHADER_INTERFACE];
+ const U32 value_range = (1 << bit_depth) - 1;
+ shader->addPermutation("TERRAIN_PAINT_PRECISION", llformat("%d", value_range));
+ success = success && shader->createShader();
+ //llassert(success);
+ if (!success)
+ {
+ LL_WARNS() << "Failed to create shader '" << shader->mName << "', disabling!" << LL_ENDL;
+ gSavedSettings.setBOOL("RenderCanUseTerrainBakeShaders", false);
+ // continue as if this shader never happened
+ success = true;
+ }
+ }
+ }
+
if (success)
{
gAlphaMaskProgram.mName = "Alpha Mask Shader";
diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h
index b08796025a..e654967c46 100644
--- a/indra/newview/llviewershadermgr.h
+++ b/indra/newview/llviewershadermgr.h
@@ -175,6 +175,7 @@ extern LLGLSLShader gReflectionProbeDisplayProgram;
extern LLGLSLShader gCopyProgram;
extern LLGLSLShader gCopyDepthProgram;
extern LLGLSLShader gPBRTerrainBakeProgram;
+extern LLGLSLShader gTerrainStampProgram;
//output tex0[tc0] - tex1[tc1]
extern LLGLSLShader gTwoTextureCompareProgram;
diff --git a/indra/newview/llvlcomposition.cpp b/indra/newview/llvlcomposition.cpp
index 077e6e6cb1..ca76d93cd7 100644
--- a/indra/newview/llvlcomposition.cpp
+++ b/indra/newview/llvlcomposition.cpp
@@ -320,7 +320,11 @@ LLViewerTexture* LLTerrainMaterials::getPaintMap()
void LLTerrainMaterials::setPaintMap(LLViewerTexture* paint_map)
{
llassert(!paint_map || mPaintType == TERRAIN_PAINT_TYPE_PBR_PAINTMAP);
+ const bool changed = paint_map != mPaintMap;
mPaintMap = paint_map;
+ // The paint map has changed, so edits are no longer valid
+ mPaintRequestQueue.clear();
+ mPaintMapQueue.clear();
}
// Boost the texture loading priority
diff --git a/indra/newview/llvlcomposition.h b/indra/newview/llvlcomposition.h
index f15f9bff6a..21fd484375 100644
--- a/indra/newview/llvlcomposition.h
+++ b/indra/newview/llvlcomposition.h
@@ -31,6 +31,7 @@
#include "llviewershadermgr.h"
#include "llviewertexture.h"
#include "llpointer.h"
+#include "llterrainpaintmap.h"
#include "llimage.h"
@@ -87,6 +88,18 @@ public:
void setPaintType(U32 paint_type) { mPaintType = paint_type; }
LLViewerTexture* getPaintMap();
void setPaintMap(LLViewerTexture* paint_map);
+ // Queue of client-triggered brush operations that need to be converted
+ // into a form that can be sent to the server.
+ // TODO: Consider getting rid of mPaintRequestQueue, as it's not really needed (brushes go directly to RGB queue)
+ LLTerrainBrushQueue& getBrushQueue() { return mBrushQueue; }
+ // Queue of client-triggered paint operations that need to be converted
+ // into a form that can be sent to the server.
+ // Paints in this queue are in RGBA format.
+ LLTerrainPaintQueue& getPaintRequestQueue() { return mPaintRequestQueue; }
+ // Paint queue for current paint map - this queue gets applied directly to
+ // the paint map. Paints within are assumed to have already been sent to
+ // the server. Paints in this queue are in RGB format.
+ LLTerrainPaintQueue& getPaintMapQueue() { return mPaintMapQueue; }
protected:
void unboost();
@@ -105,6 +118,9 @@ protected:
U32 mPaintType = TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE;
LLPointer<LLViewerTexture> mPaintMap;
+ LLTerrainBrushQueue mBrushQueue;
+ LLTerrainPaintQueue mPaintRequestQueue{U8(4)};
+ LLTerrainPaintQueue mPaintMapQueue{U8(3)};
};
// Local materials to override all regions
diff --git a/indra/newview/llvoicevisualizer.cpp b/indra/newview/llvoicevisualizer.cpp
index 7691ac54f3..c5704982b8 100644
--- a/indra/newview/llvoicevisualizer.cpp
+++ b/indra/newview/llvoicevisualizer.cpp
@@ -337,7 +337,7 @@ void LLVoiceVisualizer::lipSyncOohAah( F32& ooh, F32& aah )
//---------------------------------------------------
void LLVoiceVisualizer::render()
{
- static LLCachedControl<bool> show_visualizer(gSavedSettings, "VoiceVisualizerEnabled", false);
+ static LLCachedControl<bool> show_visualizer(gSavedSettings, "VoiceVisualizerEnabled", true);
if (!mVoiceEnabled || !show_visualizer)
{
return;
diff --git a/indra/newview/llworldmap.cpp b/indra/newview/llworldmap.cpp
index 7962c28e6d..5951d6a93a 100644
--- a/indra/newview/llworldmap.cpp
+++ b/indra/newview/llworldmap.cpp
@@ -32,6 +32,7 @@
#include "message.h"
#include "lltracker.h"
#include "lluistring.h"
+#include "llviewercontrol.h"
#include "llviewertexturelist.h"
#include "lltrans.h"
#include "llgltexture.h"
@@ -492,9 +493,20 @@ bool LLWorldMap::insertItem(U32 x_world, U32 y_world, std::string& name, LLUUID&
case MAP_ITEM_MATURE_EVENT:
case MAP_ITEM_ADULT_EVENT:
{
- std::string timeStr = "["+ LLTrans::getString ("TimeHour")+"]:["
- +LLTrans::getString ("TimeMin")+"] ["
- +LLTrans::getString ("TimeAMPM")+"]";
+ std::string timeStr;
+
+ static bool use_24h = gSavedSettings.getBOOL("Use24HourClock");
+ if (use_24h)
+ {
+ std::string timeStr = "[" + LLTrans::getString("TimeHour") + "]:["
+ + LLTrans::getString("TimeMin") + "]";
+ }
+ else
+ {
+ std::string timeStr = "[" + LLTrans::getString("TimeHour12") + "]:["
+ + LLTrans::getString("TimeMin") + "] ["
+ + LLTrans::getString("TimeAMPM") + "]";
+ }
LLSD substitution;
substitution["datetime"] = (S32) extra;
LLStringUtil::format (timeStr, substitution);
diff --git a/indra/newview/rlvactions.cpp b/indra/newview/rlvactions.cpp
new file mode 100644
index 0000000000..110beeafc0
--- /dev/null
+++ b/indra/newview/rlvactions.cpp
@@ -0,0 +1,42 @@
+/**
+ * @file rlvactions.cpp
+ * @author Kitty Barnett
+ * @brief RLVa public facing helper class to easily make RLV checks
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "rlvactions.h"
+#include "rlvhandler.h"
+
+// ============================================================================
+// Helper functions
+//
+
+bool RlvActions::isRlvEnabled()
+{
+ return RlvHandler::isEnabled();
+}
+
+// ============================================================================
diff --git a/indra/newview/rlvactions.h b/indra/newview/rlvactions.h
new file mode 100644
index 0000000000..cb0df95e37
--- /dev/null
+++ b/indra/newview/rlvactions.h
@@ -0,0 +1,46 @@
+/**
+ * @file rlvactions.h
+ * @author Kitty Barnett
+ * @brief RLVa public facing helper class to easily make RLV checks
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+// ============================================================================
+// RlvActions - developer-friendly non-RLVa code facing class, use in lieu of RlvHandler whenever possible
+//
+
+class RlvActions
+{
+ // ================
+ // Helper functions
+ // ================
+public:
+ /*
+ * Convenience function to check if RLVa is enabled without having to include rlvhandler.h
+ */
+ static bool isRlvEnabled();
+};
+
+// ============================================================================
diff --git a/indra/newview/rlvcommon.cpp b/indra/newview/rlvcommon.cpp
new file mode 100644
index 0000000000..4140659715
--- /dev/null
+++ b/indra/newview/rlvcommon.cpp
@@ -0,0 +1,134 @@
+/**
+ * @file rlvcommon.h
+ * @author Kitty Barnett
+ * @brief RLVa helper functions and constants used throughout the viewer
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llagent.h"
+#include "llchat.h"
+#include "lldbstrings.h"
+#include "llversioninfo.h"
+#include "llviewermenu.h"
+#include "llviewerstats.h"
+#include "message.h"
+#include <boost/algorithm/string.hpp>
+
+#include "rlvcommon.h"
+
+#include "llviewercontrol.h"
+#include "rlvhandler.h"
+
+using namespace Rlv;
+
+// ============================================================================
+// RlvStrings
+//
+
+std::string Strings::getVersion(bool wants_legacy)
+{
+ return llformat("%s viewer v%d.%d.%d (RLVa %d.%d.%d)",
+ !wants_legacy ? "RestrainedLove" : "RestrainedLife",
+ SpecVersion::Major, SpecVersion::Minor, SpecVersion::Patch,
+ ImplVersion::Major, ImplVersion::Minor, ImplVersion::Patch);
+}
+
+std::string Strings::getVersionAbout()
+{
+ return llformat("RLV v%d.%d.%d / RLVa v%d.%d.%d.%d",
+ SpecVersion::Major, SpecVersion::Minor, SpecVersion::Patch,
+ ImplVersion::Major, ImplVersion::Minor, ImplVersion::Patch, LLVersionInfo::instance().getBuild());
+}
+
+std::string Strings::getVersionNum()
+{
+ return llformat("%d%02d%02d%02d",
+ SpecVersion::Major, SpecVersion::Minor, SpecVersion::Patch, SpecVersion::Build);
+}
+
+std::string Strings::getVersionImplNum()
+{
+ return llformat("%d%02d%02d%02d",
+ ImplVersion::Major, ImplVersion::Minor, ImplVersion::Patch, ImplVersion::ImplId);
+}
+
+// ============================================================================
+// RlvUtil
+//
+
+void Util::menuToggleVisible()
+{
+ bool isTopLevel = gSavedSettings.getBOOL(Settings::TopLevelMenu);
+ bool isRlvEnabled = RlvHandler::isEnabled();
+
+ LLMenuGL* menuRLVaMain = gMenuBarView->findChildMenuByName("RLVa Main", false);
+ LLMenuGL* menuAdvanced = gMenuBarView->findChildMenuByName("Advanced", false);
+ LLMenuGL* menuRLVaEmbed= menuAdvanced->findChildMenuByName("RLVa Embedded", false);
+
+ gMenuBarView->setItemVisible("RLVa Main", isRlvEnabled && isTopLevel);
+ menuAdvanced->setItemVisible("RLVa Embedded", isRlvEnabled && !isTopLevel);
+
+ if ( isRlvEnabled && menuRLVaMain && menuRLVaEmbed &&
+ ( (isTopLevel && 1 == menuRLVaMain->getItemCount()) || (!isTopLevel && 1 == menuRLVaEmbed->getItemCount())) )
+ {
+ LLMenuGL* menuFrom = isTopLevel ? menuRLVaEmbed : menuRLVaMain;
+ LLMenuGL* menuTo = isTopLevel ? menuRLVaMain : menuRLVaEmbed;
+ while (LLMenuItemGL* pItem = menuFrom->getItem(1))
+ {
+ menuFrom->removeChild(pItem);
+ menuTo->addChild(pItem);
+ pItem->updateBranchParent(menuTo);
+ }
+ }
+}
+
+bool Util::parseStringList(const std::string& strInput, std::vector<std::string>& optionList, std::string_view strSeparator)
+{
+ if (!strInput.empty())
+ boost::split(optionList, strInput, boost::is_any_of(strSeparator));
+ return !optionList.empty();
+}
+
+bool Util::sendChatReply(S32 nChannel, const std::string& strUTF8Text)
+{
+ if (!isValidReplyChannel(nChannel))
+ return false;
+
+ // Copy/paste from send_chat_from_viewer()
+ gMessageSystem->newMessageFast(_PREHASH_ChatFromViewer);
+ gMessageSystem->nextBlockFast(_PREHASH_AgentData);
+ gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+ gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+ gMessageSystem->nextBlockFast(_PREHASH_ChatData);
+ gMessageSystem->addStringFast(_PREHASH_Message, utf8str_truncate(strUTF8Text, MAX_MSG_STR_LEN));
+ gMessageSystem->addU8Fast(_PREHASH_Type, CHAT_TYPE_SHOUT);
+ gMessageSystem->addS32("Channel", nChannel);
+ gAgent.sendReliableMessage();
+ add(LLStatViewer::CHAT_COUNT, 1);
+
+ return true;
+}
+
+// ============================================================================
diff --git a/indra/newview/rlvcommon.h b/indra/newview/rlvcommon.h
new file mode 100644
index 0000000000..6f1bbbbdc6
--- /dev/null
+++ b/indra/newview/rlvcommon.h
@@ -0,0 +1,72 @@
+/**
+ * @file rlvcommon.h
+ * @author Kitty Barnett
+ * @brief RLVa helper functions and constants used throughout the viewer
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+#include "rlvdefines.h"
+
+namespace Rlv
+{
+ // ============================================================================
+ // RlvStrings
+ //
+
+ class Strings
+ {
+ public:
+ static std::string getVersion(bool wants_legacy);
+ static std::string getVersionAbout();
+ static std::string getVersionImplNum();
+ static std::string getVersionNum();
+ };
+
+ // ============================================================================
+ // RlvUtil
+ //
+
+ namespace Util
+ {
+ bool isValidReplyChannel(S32 nChannel, bool isLoopback = false);
+ void menuToggleVisible();
+ bool parseStringList(const std::string& strInput, std::vector<std::string>& optionList, std::string_view strSeparator = Constants::OptionSeparator);
+ bool sendChatReply(S32 nChannel, const std::string& strUTF8Text);
+ bool sendChatReply(const std::string& strChannel, const std::string& strUTF8Text);
+ };
+
+ inline bool Util::isValidReplyChannel(S32 nChannel, bool isLoopback)
+ {
+ return (nChannel > (!isLoopback ? 0 : -1)) && (CHAT_CHANNEL_DEBUG != nChannel);
+ }
+
+ inline bool Util::sendChatReply(const std::string& strChannel, const std::string& strUTF8Text)
+ {
+ S32 nChannel;
+ return LLStringUtil::convertToS32(strChannel, nChannel) ? sendChatReply(nChannel, strUTF8Text) : false;
+ }
+
+ // ============================================================================
+}
diff --git a/indra/newview/rlvdefines.h b/indra/newview/rlvdefines.h
new file mode 100644
index 0000000000..e39328fdd6
--- /dev/null
+++ b/indra/newview/rlvdefines.h
@@ -0,0 +1,194 @@
+/**
+ * @file rlvdefines.h
+ * @author Kitty Barnett
+ * @brief RLVa common defines, constants and enums
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+// ============================================================================
+// Defines
+//
+
+// Defining these makes it easier if we ever need to change our tag
+#define RLV_WARNS LL_WARNS("RLV")
+#define RLV_INFOS LL_INFOS("RLV")
+#define RLV_DEBUGS LL_DEBUGS("RLV")
+#define RLV_ENDL LL_ENDL
+#define RLV_VERIFY(f) (f)
+
+#if LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
+ // Make sure we halt execution on errors
+ #define RLV_ERRS LL_ERRS("RLV")
+ // Keep our asserts separate from LL's
+ #define RLV_ASSERT(f) if (!(f)) { RLV_ERRS << "ASSERT (" << #f << ")" << RLV_ENDL; }
+ #define RLV_ASSERT_DBG(f) RLV_ASSERT(f)
+#else
+ // Don't halt execution on errors in release
+ #define RLV_ERRS LL_WARNS("RLV")
+ // We don't want to check assertions in release builds
+ #ifdef RLV_DEBUG
+ #define RLV_ASSERT(f) RLV_VERIFY(f)
+ #define RLV_ASSERT_DBG(f)
+ #else
+ #define RLV_ASSERT(f)
+ #define RLV_ASSERT_DBG(f)
+ #endif // RLV_DEBUG
+#endif // LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
+
+namespace Rlv
+{
+ // Version of the specification we report
+ namespace SpecVersion {
+ constexpr S32 Major = 4;
+ constexpr S32 Minor = 0;
+ constexpr S32 Patch = 0;
+ constexpr S32 Build = 0;
+ };
+
+ // RLVa implementation version
+ namespace ImplVersion {
+ constexpr S32 Major = 3;
+ constexpr S32 Minor = 0;
+ constexpr S32 Patch = 0;
+ constexpr S32 ImplId = 13;
+ };
+
+ namespace Constants
+ {
+ constexpr char CmdPrefix = '@';
+ constexpr char ConsolePrompt[] = "> ";
+ constexpr std::string_view OptionSeparator = ";";
+ }
+}
+
+// ============================================================================
+// Enumeration declarations
+//
+
+namespace Rlv
+{
+ enum class EBehaviour {
+ Version = 0,
+ VersionNew,
+ VersionNum,
+ GetCommand,
+
+ Count,
+ Unknown,
+ };
+
+ enum class EBehaviourOptionType
+ {
+ EmptyOrException, // Behaviour takes no parameters
+ Exception, // Behaviour requires an exception as a parameter
+ NoneOrException, // Behaviour takes either no parameters or an exception
+ };
+
+ enum class EParamType {
+ Unknown = 0x00,
+ Add = 0x01, // <param> == "n"|"add"
+ Remove = 0x02, // <param> == "y"|"rem"
+ Force = 0x04, // <param> == "force"
+ Reply = 0x08, // <param> == <number>
+ Clear = 0x10,
+ AddRem = Add | Remove
+ };
+
+ enum class ECmdRet {
+ Unknown = 0x0000, // Unknown error (should only be used internally)
+ Retained, // Command was retained
+ Succeeded = 0x0100, // Command executed successfully
+ SuccessUnset, // Command executed successfully (RLV_TYPE_REMOVE for an unrestricted behaviour)
+ SuccessDuplicate, // Command executed successfully (RLV_TYPE_ADD for an already restricted behaviour)
+ SuccessDeprecated, // Command executed successfully but has been marked as deprecated
+ SuccessDelayed, // Command parsed valid but will execute at a later time
+ Failed = 0x0200, // Command failed (general failure)
+ FailedSyntax, // Command failed (syntax error)
+ FailedOption, // Command failed (invalid option)
+ FailedParam, // Command failed (invalid param)
+ FailedLock, // Command failed (command is locked by another object)
+ FailedDisabled, // Command failed (command disabled by user)
+ FailedUnknown, // Command failed (unknown command)
+ FailedNoSharedRoot, // Command failed (missing #RLV)
+ FailedDeprecated, // Command failed (deprecated and no longer supported)
+ FailedNoBehaviour, // Command failed (force modifier on an object with no active restrictions)
+ FailedUnheldBehaviour, // Command failed (local modifier on an object that doesn't hold the base behaviour)
+ FailedBlocked, // Command failed (object is blocked)
+ FailedThrottled, // Command failed (throttled)
+ FailedNoProcessor // Command doesn't have a template processor define (legacy code)
+ };
+
+ enum class EExceptionCheck
+ {
+ Permissive, // Exception can be set by any object
+ Strict, // Exception must be set by all objects holding the restriction
+ Default, // Permissive or strict will be determined by currently enforced restrictions
+ };
+
+ // Replace&remove in c++23
+ template <typename E>
+ constexpr std::enable_if_t<std::is_enum_v<E> && !std::is_convertible_v<E, int>, std::underlying_type_t<E>> to_underlying(E e) noexcept
+ {
+ return static_cast<std::underlying_type_t<E>>(e);
+ }
+
+ template <typename E>
+ constexpr std::enable_if_t<std::is_enum_v<E> && !std::is_convertible_v<E, int>, bool> has_flag(E value, E flag) noexcept
+ {
+ return (to_underlying(value) & to_underlying(flag)) != 0;
+ }
+
+ constexpr bool isReturnCodeSuccess(ECmdRet eRet)
+ {
+ return (to_underlying(eRet) & to_underlying(ECmdRet::Succeeded)) == to_underlying(ECmdRet::Succeeded);
+ }
+
+ constexpr bool isReturnCodeFailed(ECmdRet eRet)
+ {
+ return (to_underlying(eRet) & to_underlying(ECmdRet::Failed)) == to_underlying(ECmdRet::Failed);
+ }
+}
+
+// ============================================================================
+// Settings
+//
+
+namespace Rlv
+{
+ namespace Settings
+ {
+ constexpr char Main[] = "RestrainedLove";
+ constexpr char Debug[] = "RestrainedLoveDebug";
+
+ constexpr char DebugHideUnsetDup[] = "RLVaDebugHideUnsetDuplicate";
+ constexpr char EnableExperimentalCommands[] = "RLVaExperimentalCommands";
+ constexpr char EnableIMQuery[] = "RLVaEnableIMQuery";
+ constexpr char EnableTempAttach[] = "RLVaEnableTemporaryAttachments";
+ constexpr char TopLevelMenu[] = "RLVaTopLevelMenu";
+ };
+
+};
+
+// ============================================================================
diff --git a/indra/newview/rlvfloaters.cpp b/indra/newview/rlvfloaters.cpp
new file mode 100644
index 0000000000..8a074fd14d
--- /dev/null
+++ b/indra/newview/rlvfloaters.cpp
@@ -0,0 +1,122 @@
+/**
+ * @file rlvfloaters.cpp
+ * @author Kitty Barnett
+ * @brief RLVa floaters class implementations
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llagentdata.h"
+#include "llchatentry.h"
+#include "lltexteditor.h"
+#include "lltrans.h"
+#include "llvoavatarself.h"
+
+#include "rlvfloaters.h"
+#include "rlvhandler.h"
+
+using namespace Rlv;
+
+// ============================================================================
+// FloaterConsole
+//
+
+bool FloaterConsole::postBuild()
+{
+ mInputEdit = getChild<LLChatEntry>("console_input");
+ mInputEdit->setCommitCallback(std::bind(&FloaterConsole::onInput, this));
+ mInputEdit->setTextExpandedCallback(std::bind(&FloaterConsole::reshapeLayoutPanel, this));
+ mInputEdit->setFocus(true);
+ mInputEdit->setCommitOnFocusLost(false);
+
+ mInputPanel = getChild<LLLayoutPanel>("input_panel");
+ mInputEditPad = mInputPanel->getRect().getHeight() - mInputEdit->getRect().getHeight();
+
+ mOutputText = getChild<LLTextEditor>("console_output");
+ mOutputText->appendText(Constants::ConsolePrompt, false);
+
+ if (RlvHandler::isEnabled())
+ {
+ mCommandOutputConn = RlvHandler::instance().setCommandOutputCallback([this](const RlvCommand& rlvCmd, S32, const std::string strText)
+ {
+ if (rlvCmd.getObjectID() == gAgentID)
+ {
+ mOutputText->appendText(rlvCmd.getBehaviour() + ": ", true);
+ mOutputText->appendText(strText, false);
+ }
+ });
+ }
+
+ return true;
+}
+
+void FloaterConsole::onClose(bool fQuitting)
+{
+ if (RlvHandler::isEnabled())
+ {
+ RlvHandler::instance().processCommand(gAgentID, "clear", true);
+ }
+}
+
+void FloaterConsole::onInput()
+{
+ if (!isAgentAvatarValid())
+ {
+ return;
+ }
+
+ std::string strText = mInputEdit->getText();
+ LLStringUtil::trim(strText);
+
+ mOutputText->appendText(strText, false);
+ mInputEdit->setText(LLStringUtil::null);
+
+ if (!RlvHandler::isEnabled())
+ {
+ mOutputText->appendText(LLTrans::getString("RlvConsoleDisable"), true);
+ }
+ else if (strText.length() <= 3 || Constants::CmdPrefix != strText[0])
+ {
+ mOutputText->appendText(LLTrans::getString("RlvConsoleInvalidCmd"), true);
+ }
+ else
+ {
+ LLChat chat;
+ chat.mFromID = gAgentID;
+ chat.mChatType = CHAT_TYPE_OWNER;
+
+ RlvHandler::instance().handleSimulatorChat(strText, chat, gAgentAvatarp);
+
+ mOutputText->appendText(strText, true);
+ }
+
+ mOutputText->appendText(Constants::ConsolePrompt, true);
+}
+
+void FloaterConsole::reshapeLayoutPanel()
+{
+ mInputPanel->reshape(mInputPanel->getRect().getWidth(), mInputEdit->getRect().getHeight() + mInputEditPad, false);
+}
+
+// ============================================================================
diff --git a/indra/newview/rlvfloaters.h b/indra/newview/rlvfloaters.h
new file mode 100644
index 0000000000..8acfa43f28
--- /dev/null
+++ b/indra/newview/rlvfloaters.h
@@ -0,0 +1,68 @@
+/**
+ * @file rlvfloaters.h
+ * @author Kitty Barnett
+ * @brief RLVa floaters class implementations
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+#include "llfloater.h"
+
+#include "rlvdefines.h"
+
+class LLChatEntry;
+class LLFloaterReg;
+class LLLayoutPanel;
+class LLTextEditor;
+class RlvCommand;
+class RlvHandler;
+
+namespace Rlv
+{
+ // ============================================================================
+ // FloaterConsole - debug console to allow command execution without the need for a script
+ //
+
+ class FloaterConsole : public LLFloater
+ {
+ friend class ::LLFloaterReg;
+ FloaterConsole(const LLSD& sdKey) : LLFloater(sdKey) {}
+
+ public:
+ bool postBuild() override;
+ void onClose(bool fQuitting) override;
+ protected:
+ void onInput();
+ void reshapeLayoutPanel();
+
+ private:
+ boost::signals2::scoped_connection mCommandOutputConn;
+ int mInputEditPad = 0;
+ LLLayoutPanel* mInputPanel = nullptr;
+ LLChatEntry* mInputEdit = nullptr;
+ LLTextEditor* mOutputText = nullptr;
+ };
+
+ // ============================================================================
+};
diff --git a/indra/newview/rlvhandler.cpp b/indra/newview/rlvhandler.cpp
new file mode 100644
index 0000000000..6c4b439105
--- /dev/null
+++ b/indra/newview/rlvhandler.cpp
@@ -0,0 +1,225 @@
+/**
+ * @file rlvhandler.cpp
+ * @author Kitty Barnett
+ * @brief RLVa helper classes for internal use only
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "llagent.h"
+#include "llstartup.h"
+#include "llviewercontrol.h"
+#include "llviewerobject.h"
+
+#include "rlvcommon.h"
+#include "rlvhandler.h"
+#include "rlvhelper.h"
+
+#include <boost/algorithm/string.hpp>
+
+using namespace Rlv;
+
+// ============================================================================
+// Static variable initialization
+//
+
+bool RlvHandler::mIsEnabled = false;
+
+// ============================================================================
+// Command processing functions
+//
+
+bool RlvHandler::handleSimulatorChat(std::string& message, const LLChat& chat, const LLViewerObject* chatObj)
+{
+ // *TODO: There's an edge case for temporary attachments when going from enabled -> disabled with restrictions already in place
+ static LLCachedControl<bool> enable_temp_attach(gSavedSettings, Settings::EnableTempAttach);
+ static LLCachedControl<bool> show_debug_output(gSavedSettings, Settings::Debug);
+ static LLCachedControl<bool> hide_unset_dupes(gSavedSettings, Settings::DebugHideUnsetDup);
+
+ if ( message.length() <= 3 || Constants::CmdPrefix != message[0] || CHAT_TYPE_OWNER != chat.mChatType ||
+ (chatObj && chatObj->isTempAttachment() && !enable_temp_attach()) )
+ {
+ return false;
+ }
+
+ message.erase(0, 1);
+ LLStringUtil::toLower(message);
+ CommandDbgOut cmdDbgOut(message, chatObj->getID() == gAgentID);
+
+ boost_tokenizer tokens(message, boost::char_separator<char>(",", "", boost::drop_empty_tokens));
+ for (const std::string& strCmd : tokens)
+ {
+ ECmdRet eRet = processCommand(chat.mFromID, strCmd, true);
+ if ( show_debug_output() &&
+ (!hide_unset_dupes() || (ECmdRet::SuccessUnset != eRet && ECmdRet::SuccessDuplicate != eRet)) )
+ {
+ cmdDbgOut.add(strCmd, eRet);
+ }
+ }
+
+ message = cmdDbgOut.get();
+ return true;
+}
+
+ECmdRet RlvHandler::processCommand(const LLUUID& idObj, const std::string& strCmd, bool fromObj)
+{
+ const RlvCommand rlvCmd(idObj, strCmd);
+ return processCommand(std::ref(rlvCmd), fromObj);
+}
+
+ECmdRet RlvHandler::processCommand(std::reference_wrapper<const RlvCommand> rlvCmd, bool fromObj)
+{
+ {
+ const RlvCommand& rlvCmdTmp = rlvCmd; // Reference to the temporary with limited variable scope since we don't want it to leak below
+
+ RLV_DEBUGS << "[" << rlvCmdTmp.getObjectID() << "]: " << rlvCmdTmp.asString() << RLV_ENDL;
+ if (!rlvCmdTmp.isValid())
+ {
+ RLV_DEBUGS << "\t-> invalid syntax" << RLV_ENDL;
+ return ECmdRet::FailedSyntax;
+ }
+ if (rlvCmdTmp.isBlocked())
+ {
+ RLV_DEBUGS << "\t-> blocked command" << RLV_ENDL;
+ return ECmdRet::FailedDisabled;
+ }
+ }
+
+ ECmdRet eRet = ECmdRet::Unknown;
+ switch (rlvCmd.get().getParamType())
+ {
+ case EParamType::Reply:
+ eRet = rlvCmd.get().processCommand();
+ break;
+ case EParamType::Unknown:
+ default:
+ eRet = ECmdRet::FailedParam;
+ break;
+ }
+ RLV_ASSERT(ECmdRet::Unknown != eRet);
+
+ RLV_DEBUGS << "\t--> command " << (isReturnCodeSuccess(eRet) ? "succeeded" : "failed") << RLV_ENDL;
+
+ return eRet;
+}
+
+// ============================================================================
+// Initialization helper functions
+//
+
+bool RlvHandler::canEnable()
+{
+ return LLStartUp::getStartupState() <= STATE_LOGIN_CLEANUP;
+}
+
+bool RlvHandler::setEnabled(bool enable)
+{
+ if (mIsEnabled == enable)
+ return enable;
+
+ if (enable && canEnable())
+ {
+ RLV_INFOS << "Enabling Restrained Love API support - " << Strings::getVersionAbout() << RLV_ENDL;
+ mIsEnabled = true;
+ }
+
+ return mIsEnabled;
+}
+
+// ============================================================================
+// Command handlers (RLV_TYPE_REPLY)
+//
+
+ECmdRet CommandHandlerBaseImpl<EParamType::Reply>::processCommand(const RlvCommand& rlvCmd, ReplyHandlerFunc* pHandler)
+{
+ // Sanity check - <param> should specify a - valid - reply channel
+ S32 nChannel;
+ if (!LLStringUtil::convertToS32(rlvCmd.getParam(), nChannel) || !Util::isValidReplyChannel(nChannel, rlvCmd.getObjectID() == gAgentID))
+ return ECmdRet::FailedParam;
+
+ std::string strReply;
+ ECmdRet eRet = (*pHandler)(rlvCmd, strReply);
+
+ // If we made it this far then:
+ // - the command was handled successfully so we send off the response
+ // - the command failed but we still send off an - empty - response to keep the issuing script from blocking
+ if (nChannel != 0)
+ {
+ Util::sendChatReply(nChannel, strReply);
+ }
+ RlvHandler::instance().mOnCommandOutput(rlvCmd, nChannel, strReply);
+
+ return eRet;
+}
+
+// Handles: @getcommand[:<behaviour>[;<type>[;<separator>]]]=<channel>
+template<> template<>
+ECmdRet ReplyHandler<EBehaviour::GetCommand>::onCommand(const RlvCommand& rlvCmd, std::string& strReply)
+{
+ std::vector<std::string> optionList;
+ Util::parseStringList(rlvCmd.getOption(), optionList);
+
+ // If a second parameter is present it'll specify the command type
+ EParamType eType = EParamType::Unknown;
+ if (optionList.size() >= 2)
+ {
+ if (optionList[1] == "any" || optionList[1].empty())
+ eType = EParamType::Unknown;
+ else if (optionList[1] == "add")
+ eType = EParamType::AddRem;
+ else if (optionList[1] == "force")
+ eType = EParamType::Force;
+ else if (optionList[1] == "reply")
+ eType = EParamType::Reply;
+ else
+ return ECmdRet::FailedOption;
+ }
+
+ std::list<std::string> cmdList;
+ if (BehaviourDictionary::instance().getCommands(!optionList.empty() ? optionList[0] : LLStringUtil::null, eType, cmdList))
+ strReply = boost::algorithm::join(cmdList, optionList.size() >= 3 ? optionList[2] : Constants::OptionSeparator);
+ return ECmdRet::Succeeded;
+}
+
+// Handles: @version=<chnannel> and @versionnew=<channel>
+template<> template<>
+ECmdRet VersionReplyHandler::onCommand(const RlvCommand& rlvCmd, std::string& strReply)
+{
+ strReply = Strings::getVersion(EBehaviour::Version == rlvCmd.getBehaviourType());
+ return ECmdRet::Succeeded;
+}
+
+// Handles: @versionnum[:impl]=<channel>
+template<> template<>
+ECmdRet ReplyHandler<EBehaviour::VersionNum>::onCommand(const RlvCommand& rlvCmd, std::string& strReply)
+{
+ if (!rlvCmd.hasOption())
+ strReply = Strings::getVersionNum();
+ else if ("impl" == rlvCmd.getOption())
+ strReply = Strings::getVersionImplNum();
+ else
+ return ECmdRet::FailedOption;
+ return ECmdRet::Succeeded;
+}
+
+// ============================================================================
diff --git a/indra/newview/rlvhandler.h b/indra/newview/rlvhandler.h
new file mode 100644
index 0000000000..38612485b1
--- /dev/null
+++ b/indra/newview/rlvhandler.h
@@ -0,0 +1,80 @@
+/**
+ * @file rlvhandler.h
+ * @author Kitty Barnett
+ * @brief Primary command process and orchestration class
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+#include "llchat.h"
+#include "llsingleton.h"
+
+#include "rlvhelper.h"
+
+class LLViewerObject;
+
+// ============================================================================
+// RlvHandler class
+//
+
+class RlvHandler : public LLSingleton<RlvHandler>
+{
+ template<Rlv::EParamType> friend struct Rlv::CommandHandlerBaseImpl;
+
+ LLSINGLETON_EMPTY_CTOR(RlvHandler);
+
+ /*
+ * Command processing
+ */
+public:
+ // Command processing helper functions
+ bool handleSimulatorChat(std::string& message, const LLChat& chat, const LLViewerObject* chatObj);
+ Rlv::ECmdRet processCommand(const LLUUID& idObj, const std::string& stCmd, bool fromObj);
+protected:
+ Rlv::ECmdRet processCommand(std::reference_wrapper<const RlvCommand> rlvCmdRef, bool fromObj);
+
+ /*
+ * Helper functions
+ */
+public:
+ // Initialization (deliberately static so they can safely be called in tight loops)
+ static bool canEnable();
+ static bool isEnabled() { return mIsEnabled; }
+ static bool setEnabled(bool enable);
+
+ /*
+ * Event handling
+ */
+public:
+ // The command output signal is triggered whenever a command produces channel or debug output
+ using command_output_signal_t = boost::signals2::signal<void (const RlvCommand&, S32, const std::string&)>;
+ boost::signals2::connection setCommandOutputCallback(const command_output_signal_t::slot_type& cb) { return mOnCommandOutput.connect(cb); }
+
+protected:
+ command_output_signal_t mOnCommandOutput;
+private:
+ static bool mIsEnabled;
+};
+
+// ============================================================================
diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp
new file mode 100644
index 0000000000..1dac297bf1
--- /dev/null
+++ b/indra/newview/rlvhelper.cpp
@@ -0,0 +1,389 @@
+/**
+ * @file rlvhelper.cpp
+ * @author Kitty Barnett
+ * @brief RLVa helper classes for internal use only
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "lltrans.h"
+#include "llviewercontrol.h"
+
+#include "rlvhelper.h"
+
+#include <boost/algorithm/string.hpp>
+
+using namespace Rlv;
+
+// ============================================================================
+// BehaviourDictionary
+//
+
+BehaviourDictionary::BehaviourDictionary()
+{
+ //
+ // Restrictions
+ //
+
+ //
+ // Reply-only
+ //
+ addEntry(new ReplyProcessor<EBehaviour::GetCommand>("getcommand"));
+ addEntry(new ReplyProcessor<EBehaviour::Version, VersionReplyHandler>("version"));
+ addEntry(new ReplyProcessor<EBehaviour::VersionNew, VersionReplyHandler>("versionnew"));
+ addEntry(new ReplyProcessor<EBehaviour::VersionNum>("versionnum"));
+
+ // Populate mString2InfoMap (the tuple <behaviour, type> should be unique)
+ for (const BehaviourInfo* bhvr_info_p : mBhvrInfoList)
+ {
+ mString2InfoMap.insert(std::make_pair(std::make_pair(bhvr_info_p->getBehaviour(), static_cast<EParamType>(bhvr_info_p->getParamTypeMask())), bhvr_info_p));
+ }
+
+ // Populate m_Bhvr2InfoMap (there can be multiple entries per ERlvBehaviour)
+ for (const BehaviourInfo* bhvr_info_p : mBhvrInfoList)
+ {
+ if ((bhvr_info_p->getParamTypeMask() & to_underlying(EParamType::AddRem)) && !bhvr_info_p->isSynonym())
+ {
+#ifdef RLV_DEBUG
+ for (const auto& itBhvr : boost::make_iterator_range(mBhvr2InfoMap.lower_bound(bhvr_info_p->getBehaviourType()), mBhvr2InfoMap.upper_bound(bhvr_info_p->getBehaviourType())))
+ {
+ RLV_ASSERT((itBhvr.first != bhvr_info_p->getBehaviourType()) || (itBhvr.second->getBehaviourFlags() != bhvr_info_p->getBehaviourFlags()));
+ }
+#endif // RLV_DEBUG
+ mBhvr2InfoMap.insert(std::pair(bhvr_info_p->getBehaviourType(), bhvr_info_p));
+ }
+ }
+}
+
+BehaviourDictionary::~BehaviourDictionary()
+{
+ for (const BehaviourInfo* bhvr_info_p : mBhvrInfoList)
+ {
+ delete bhvr_info_p;
+ }
+ mBhvrInfoList.clear();
+}
+
+void BehaviourDictionary::addEntry(const BehaviourInfo* entry_p)
+{
+ // Filter experimental commands (if disabled)
+ static LLCachedControl<bool> sEnableExperimental(gSavedSettings, Settings::EnableExperimentalCommands);
+ if (!entry_p || (!sEnableExperimental && entry_p->isExperimental()))
+ {
+ return;
+ }
+
+ // Sanity check for duplicate entries
+#if LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
+ std::for_each(mBhvrInfoList.begin(), mBhvrInfoList.end(),
+ [&entry_p](const BehaviourInfo* bhvr_info_p) {
+ RLV_ASSERT_DBG((bhvr_info_p->getBehaviour() != entry_p->getBehaviour()) || ((bhvr_info_p->getParamTypeMask() & entry_p->getParamTypeMask()) == 0));
+ });
+#endif // LL_RELEASE_WITH_DEBUG_INFO || LL_DEBUG
+
+ mBhvrInfoList.push_back(entry_p);
+}
+
+const BehaviourInfo* BehaviourDictionary::getBehaviourInfo(EBehaviour eBhvr, EParamType eParamType) const
+{
+ const BehaviourInfo* bhvr_info_p = nullptr;
+ for (auto itBhvrLower = mBhvr2InfoMap.lower_bound(eBhvr), itBhvrUpper = mBhvr2InfoMap.upper_bound(eBhvr);
+ std::find_if(itBhvrLower, itBhvrUpper, [eParamType](const auto& bhvrEntry) { return bhvrEntry.second->getParamTypeMask() == to_underlying(eParamType); }) != itBhvrUpper;
+ ++itBhvrLower)
+ {
+ if (bhvr_info_p)
+ return nullptr;
+ bhvr_info_p = itBhvrLower->second;
+ }
+ return bhvr_info_p;
+}
+
+const BehaviourInfo* BehaviourDictionary::getBehaviourInfo(const std::string& strBhvr, EParamType eParamType, bool* is_strict_p) const
+{
+ size_t idxBhvrLastPart = strBhvr.find_last_of('_');
+ std::string strBhvrLastPart((std::string::npos != idxBhvrLastPart) && (idxBhvrLastPart < strBhvr.size()) ? strBhvr.substr(idxBhvrLastPart + 1) : LLStringUtil::null);
+
+ bool isStrict = (strBhvrLastPart.compare("sec") == 0);
+ if (is_strict_p)
+ *is_strict_p = isStrict;
+
+ auto itBhvr = mString2InfoMap.find(std::make_pair((!isStrict) ? strBhvr : strBhvr.substr(0, strBhvr.size() - 4), (has_flag(eParamType, EParamType::AddRem)) ? EParamType::AddRem : eParamType));
+ if ((mString2InfoMap.end() == itBhvr) && (!isStrict) && (!strBhvrLastPart.empty()) && (EParamType::Force == eParamType))
+ {
+ // No match found but it could still be a local scope modifier
+ auto itBhvrMod = mString2InfoMap.find(std::make_pair(strBhvr.substr(0, idxBhvrLastPart), EParamType::AddRem));
+ }
+
+ return ((itBhvr != mString2InfoMap.end()) && ((!isStrict) || (itBhvr->second->hasStrict()))) ? itBhvr->second : nullptr;
+}
+
+EBehaviour BehaviourDictionary::getBehaviourFromString(const std::string& strBhvr, EParamType eParamType, bool* pisStrict) const
+{
+ const BehaviourInfo* bhvr_info_p = getBehaviourInfo(strBhvr, eParamType, pisStrict);
+ // Filter out locally scoped modifier commands since they don't actually have a unique behaviour value of their own
+ return bhvr_info_p->getBehaviourType();
+}
+
+bool BehaviourDictionary::getCommands(const std::string& strMatch, EParamType eParamType, std::list<std::string>& cmdList) const
+{
+ cmdList.clear();
+ for (const BehaviourInfo* bhvr_info_p : mBhvrInfoList)
+ {
+ if ((bhvr_info_p->getParamTypeMask() & to_underlying(eParamType)) || (EParamType::Unknown == eParamType))
+ {
+ std::string strCmd = bhvr_info_p->getBehaviour();
+ if ((std::string::npos != strCmd.find(strMatch)) || (strMatch.empty()))
+ cmdList.push_back(strCmd);
+ if ((bhvr_info_p->hasStrict()) && ((std::string::npos != strCmd.append("_sec").find(strMatch)) || (strMatch.empty())))
+ cmdList.push_back(strCmd);
+ }
+ }
+ return !cmdList.empty();
+}
+
+bool BehaviourDictionary::getHasStrict(EBehaviour eBhvr) const
+{
+ for (const auto& itBhvr : boost::make_iterator_range(mBhvr2InfoMap.lower_bound(eBhvr), mBhvr2InfoMap.upper_bound(eBhvr)))
+ {
+ // Only restrictions can be strict
+ if (to_underlying(EParamType::AddRem) != itBhvr.second->getParamTypeMask())
+ continue;
+ return itBhvr.second->hasStrict();
+ }
+ RLV_ASSERT(false);
+ return false;
+}
+
+void BehaviourDictionary::toggleBehaviourFlag(const std::string& strBhvr, EParamType eParamType, BehaviourInfo::EBehaviourFlags eBhvrFlag, bool fEnable)
+{
+ auto itBhvr = mString2InfoMap.find(std::make_pair(strBhvr, (has_flag(eParamType, EParamType::AddRem)) ? EParamType::AddRem : eParamType));
+ if (mString2InfoMap.end() != itBhvr)
+ {
+ const_cast<BehaviourInfo*>(itBhvr->second)->toggleBehaviourFlag(eBhvrFlag, fEnable);
+ }
+}
+
+// ============================================================================
+// RlvCommmand
+//
+
+RlvCommand::RlvCommand(const LLUUID& idObj, const std::string& strCmd)
+ : mObjId(idObj)
+{
+ if (parseCommand(strCmd, mBehaviour, mOption, mParam))
+ {
+ if ("n" == mParam || "add" == mParam)
+ mParamType = EParamType::Add;
+ else if ("y" == mParam || "rem" == mParam)
+ mParamType = EParamType::Remove;
+ else if ("clear" == mBehaviour) // clear is the odd one out so just make it its own type
+ mParamType = EParamType::Clear;
+ else if ("force" == mParam)
+ mParamType = EParamType::Force;
+ else if (S32 nTemp; LLStringUtil::convertToS32(mParam, nTemp)) // Assume it's a reply command if we can convert <param> to an S32
+ mParamType = EParamType::Reply;
+ }
+
+ mIsValid = mParamType != EParamType::Unknown;
+ if (!mIsValid)
+ {
+ mOption.clear();
+ mParam.clear();
+ return;
+ }
+
+ mBhvrInfo = BehaviourDictionary::instance().getBehaviourInfo(mBehaviour, mParamType, &mIsStrict);
+}
+
+RlvCommand::RlvCommand(const RlvCommand& rlvCmd, EParamType eParamType)
+ : mIsValid(rlvCmd.mIsValid), mObjId(rlvCmd.mObjId), mBehaviour(rlvCmd.mBehaviour), mBhvrInfo(rlvCmd.mBhvrInfo)
+ , mParamType( (EParamType::Unknown == eParamType) ? rlvCmd.mParamType : eParamType)
+ , mIsStrict(rlvCmd.mIsStrict), mOption(rlvCmd.mOption), mParam(rlvCmd.mParam), mIsRefCounted(rlvCmd.mIsRefCounted)
+{
+}
+
+bool RlvCommand::parseCommand(const std::string& strCmd, std::string& strBhvr, std::string& strOption, std::string& strParam)
+{
+ // Format: <behaviour>[:<option>]=<param>
+ const size_t idxOption = strCmd.find(':');
+ const size_t idxParam = strCmd.find('=');
+
+ // If <behaviour> is missing it's always an improperly formatted command
+ // If there's an option, but it comes after <param> it's also invalid
+ if ( (idxOption == 0 || idxParam == 0) ||
+ (idxOption != std::string::npos && idxOption >= idxParam) )
+ {
+ return false;
+ }
+
+ strBhvr = strCmd.substr(0, std::string::npos != idxOption ? idxOption : idxParam);
+ strOption = strParam = "";
+
+ // If <param> is missing it's an improperly formatted command
+ if (idxParam == std::string::npos || idxParam + 1 == strCmd.length())
+ {
+ // Unless "<behaviour> == "clear" AND (idxOption == 0)"
+ // OR <behaviour> == "clear" AND (idxParam != 0)
+ if (strBhvr == "clear" && (!idxOption || idxParam))
+ return true;
+ return false;
+ }
+
+ if (idxOption != std::string::npos && idxOption + 1 != idxParam)
+ strOption = strCmd.substr(idxOption + 1, idxParam - idxOption - 1);
+ strParam = strCmd.substr(idxParam + 1);
+
+ return true;
+}
+
+std::string RlvCommand::asString() const
+{
+ // NOTE: @clear=<param> should be represented as clear:<param>
+ return mParamType != EParamType::Clear
+ ? getBehaviour() + (!mOption.empty() ? ":" + mOption : "")
+ : getBehaviour() + (!mParam.empty() ? ":" + mParam : "");
+}
+
+// =========================================================================
+// Various helper classes/timers/functors
+//
+
+namespace Rlv
+{
+ // ===========================================================================
+ // CommandDbgOut
+ //
+
+ void CommandDbgOut::add(std::string strCmd, ECmdRet eRet)
+ {
+ const std::string strSuffix = getReturnCodeString(eRet);
+ if (!strSuffix.empty())
+ strCmd.append(llformat(" (%s)", strSuffix.c_str()));
+ else if (mForConsole)
+ return; // Only show console feedback on successful commands when there's an informational notice
+
+ std::string& strResult = mCommandResults[isReturnCodeSuccess(eRet) ? ECmdRet::Succeeded : (ECmdRet::Retained == eRet ? ECmdRet::Retained : ECmdRet::Failed)];
+ if (!strResult.empty())
+ strResult.append(", ");
+ strResult.append(strCmd);
+ }
+
+ std::string CommandDbgOut::get() const {
+ std::ostringstream result;
+
+ if (1 == mCommandResults.size() && !mForConsole)
+ {
+ auto it = mCommandResults.begin();
+ result << " " << getDebugVerbFromReturnCode(it->first) << ": @" << it->second;
+ }
+ else if (!mCommandResults.empty())
+ {
+ auto appendResult = [&](ECmdRet eRet, const std::string& name)
+ {
+ auto it = mCommandResults.find(eRet);
+ if (it == mCommandResults.end()) return;
+ if (!mForConsole) result << "\n - ";
+ result << LLTrans::getString(name) << ": @" << it->second;
+ };
+ if (!mForConsole)
+ result << ": @" << mOrigCmd;
+ appendResult(ECmdRet::Succeeded, !mForConsole ? "RlvDebugExecuted" : "RlvConsoleExecuted");
+ appendResult(ECmdRet::Failed, !mForConsole ? "RlvDebugFailed" : "RlvConsoleFailed");
+ appendResult(ECmdRet::Retained, !mForConsole ? "RlvDebugRetained" : "RlvConsoleRetained");
+ }
+
+ return result.str();
+ }
+
+ std::string CommandDbgOut::getDebugVerbFromReturnCode(ECmdRet eRet)
+ {
+ switch (eRet)
+ {
+ case ECmdRet::Succeeded:
+ return LLTrans::getString("RlvDebugExecuted");
+ case ECmdRet::Failed:
+ return LLTrans::getString("RlvDebugFailed");
+ case ECmdRet::Retained:
+ return LLTrans::getString("RlvDebugRetained");
+ default:
+ RLV_ASSERT(false);
+ return LLStringUtil::null;
+ }
+ }
+
+ std::string CommandDbgOut::getReturnCodeString(ECmdRet eRet)
+ {
+ switch (eRet)
+ {
+ case ECmdRet::SuccessUnset:
+ return LLTrans::getString("RlvReturnCodeUnset");
+ case ECmdRet::SuccessDuplicate:
+ return LLTrans::getString("RlvReturnCodeDuplicate");
+ case ECmdRet::SuccessDelayed:
+ return LLTrans::getString("RlvReturnCodeDelayed");
+ case ECmdRet::SuccessDeprecated:
+ return LLTrans::getString("RlvReturnCodeDeprecated");
+ case ECmdRet::FailedSyntax:
+ return LLTrans::getString("RlvReturnCodeSyntax");
+ case ECmdRet::FailedOption:
+ return LLTrans::getString("RlvReturnCodeOption");
+ case ECmdRet::FailedParam:
+ return LLTrans::getString("RlvReturnCodeParam");
+ case ECmdRet::FailedLock:
+ return LLTrans::getString("RlvReturnCodeLock");
+ case ECmdRet::FailedDisabled:
+ return LLTrans::getString("RlvReturnCodeDisabled");
+ case ECmdRet::FailedUnknown:
+ return LLTrans::getString("RlvReturnCodeUnknown");
+ case ECmdRet::FailedNoSharedRoot:
+ return LLTrans::getString("RlvReturnCodeNoSharedRoot");
+ case ECmdRet::FailedDeprecated:
+ return LLTrans::getString("RlvReturnCodeDeprecatedAndDisabled");
+ case ECmdRet::FailedNoBehaviour:
+ return LLTrans::getString("RlvReturnCodeNoBehaviour");
+ case ECmdRet::FailedUnheldBehaviour:
+ return LLTrans::getString("RlvReturnCodeUnheldBehaviour");
+ case ECmdRet::FailedBlocked:
+ return LLTrans::getString("RlvReturnCodeBlocked");
+ case ECmdRet::FailedThrottled:
+ return LLTrans::getString("RlvReturnCodeThrottled");
+ case ECmdRet::FailedNoProcessor:
+ return LLTrans::getString("RlvReturnCodeNoProcessor");
+ // The following are identified by the chat verb
+ case ECmdRet::Retained:
+ case ECmdRet::Succeeded:
+ case ECmdRet::Failed:
+ return LLStringUtil::null;
+ // The following shouldn't occur
+ case ECmdRet::Unknown:
+ default:
+ RLV_ASSERT(false);
+ return LLStringUtil::null;
+ }
+ }
+
+ // ===========================================================================
+}
+
+// ============================================================================
diff --git a/indra/newview/rlvhelper.h b/indra/newview/rlvhelper.h
new file mode 100644
index 0000000000..f241332594
--- /dev/null
+++ b/indra/newview/rlvhelper.h
@@ -0,0 +1,299 @@
+/**
+ * @file rlvhelper.h
+ * @author Kitty Barnett
+ * @brief RLVa helper classes for internal use only
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+#include "rlvdefines.h"
+
+// ============================================================================
+// Forward declarations
+//
+
+class RlvCommand;
+
+// ============================================================================
+
+namespace Rlv
+{
+ // ============================================================================
+ // BehaviourInfo class - Generic behaviour descriptor (used by restrictions, reply and force commands)
+ //
+
+ class BehaviourInfo
+ {
+ public:
+ enum EBehaviourFlags : uint32_t
+ {
+ // General behaviour flags
+ Strict = 0x0001, // Behaviour has a "_sec" version
+ Synonym = 0x0002, // Behaviour is a synonym of another
+ Extended = 0x0004, // Behaviour is part of the RLVa extended command set
+ Experimental = 0x0008, // Behaviour is part of the RLVa experimental command set
+ Blocked = 0x0010, // Behaviour is blocked
+ Deprecated = 0x0020, // Behaviour is deprecated
+ MaskGeneral = 0x0FFF,
+
+ // Force-wear specific flags
+ ForceWear_WearReplace = 0x0001 << 16,
+ ForceWear_WearAdd = 0x0002 << 16,
+ ForceWear_WearRemove = 0x0004 << 16,
+ ForceWear_Node = 0x0010 << 16,
+ ForceWear_Subtree = 0x0020 << 16,
+ ForceWear_ContextNone = 0x0100 << 16,
+ ForceWear_ContextObject = 0x0200 << 16,
+ MaskForceWear = 0xFFFFu << 16
+ };
+
+ BehaviourInfo(const std::string& strBhvr, EBehaviour eBhvr, EParamType maskParamType, std::underlying_type_t<EBehaviourFlags> nBhvrFlags = 0)
+ : mBhvr(strBhvr), mBhvrType(eBhvr), mBhvrFlags(nBhvrFlags), mMaskParamType(to_underlying(maskParamType)) {}
+ virtual ~BehaviourInfo() {}
+
+ const std::string& getBehaviour() const { return mBhvr; }
+ EBehaviour getBehaviourType() const { return mBhvrType; }
+ std::underlying_type_t<EBehaviourFlags> getBehaviourFlags() const { return mBhvrFlags; }
+ std::underlying_type_t<EParamType> getParamTypeMask() const { return mMaskParamType; }
+ bool hasStrict() const { return mBhvrFlags & Strict; }
+ bool isBlocked() const { return mBhvrFlags & Blocked; }
+ bool isExperimental() const { return mBhvrFlags & Experimental; }
+ bool isExtended() const { return mBhvrFlags & Extended; }
+ bool isSynonym() const { return mBhvrFlags & Synonym; }
+ void toggleBehaviourFlag(EBehaviourFlags eBhvrFlag, bool fEnable);
+
+ virtual ECmdRet processCommand(const RlvCommand& rlvCmd) const { return ECmdRet::FailedNoProcessor; }
+
+ protected:
+ std::string mBhvr;
+ EBehaviour mBhvrType;
+ std::underlying_type_t<EBehaviourFlags> mBhvrFlags;
+ std::underlying_type_t<EParamType> mMaskParamType;
+ };
+
+ inline void BehaviourInfo::toggleBehaviourFlag(EBehaviourFlags eBhvrFlag, bool fEnable)
+ {
+ if (fEnable)
+ mBhvrFlags |= eBhvrFlag;
+ else
+ mBhvrFlags &= ~eBhvrFlag;
+ }
+
+ // ============================================================================
+ // BehaviourDictionary and related classes
+ //
+
+ class BehaviourDictionary : public LLSingleton<BehaviourDictionary>
+ {
+ LLSINGLETON(BehaviourDictionary);
+ protected:
+ ~BehaviourDictionary() override;
+ public:
+ void addEntry(const BehaviourInfo* entry_p);
+
+ /*
+ * General helper functions
+ */
+ public:
+ EBehaviour getBehaviourFromString(const std::string& strBhvr, EParamType eParamType, bool* is_strict_p = nullptr) const;
+ const BehaviourInfo* getBehaviourInfo(EBehaviour eBhvr, EParamType eParamType) const;
+ const BehaviourInfo* getBehaviourInfo(const std::string& strBhvr, EParamType eParamType, bool* is_strict_p = nullptr) const;
+ bool getCommands(const std::string& strMatch, EParamType eParamType, std::list<std::string>& cmdList) const;
+ bool getHasStrict(EBehaviour eBhvr) const;
+ void toggleBehaviourFlag(const std::string& strBhvr, EParamType eParamType, BehaviourInfo::EBehaviourFlags eBvhrFlag, bool fEnable);
+
+ /*
+ * Member variables
+ */
+ protected:
+ std::list<const BehaviourInfo*> mBhvrInfoList;
+ std::map<std::pair<std::string, EParamType>, const BehaviourInfo*> mString2InfoMap;
+ std::multimap<EBehaviour, const BehaviourInfo*> mBhvr2InfoMap;
+ };
+
+ // ============================================================================
+ // CommandHandler and related classes
+ //
+
+ typedef ECmdRet(BhvrHandlerFunc)(const RlvCommand&, bool&);
+ typedef void(BhvrToggleHandlerFunc)(EBehaviour, bool);
+ typedef ECmdRet(ForceHandlerFunc)(const RlvCommand&);
+ typedef ECmdRet(ReplyHandlerFunc)(const RlvCommand&, std::string&);
+
+ //
+ // CommandHandlerBaseImpl - Base implementation for each command type (the old process(AddRem|Force|Reply)Command functions)
+ //
+ template<EParamType paramType> struct CommandHandlerBaseImpl;
+ template<> struct CommandHandlerBaseImpl<EParamType::AddRem> { static ECmdRet processCommand(const RlvCommand&, BhvrHandlerFunc*, BhvrToggleHandlerFunc* = nullptr); };
+ template<> struct CommandHandlerBaseImpl<EParamType::Force> { static ECmdRet processCommand(const RlvCommand&, ForceHandlerFunc*); };
+ template<> struct CommandHandlerBaseImpl<EParamType::Reply> { static ECmdRet processCommand(const RlvCommand&, ReplyHandlerFunc*); };
+
+ //
+ // CommandHandler - The actual command handler (Note that a handler is more general than a processor; a handler can - for instance - be used by multiple processors)
+ //
+ #if LL_WINDOWS
+ #define RLV_TEMPL_FIX(x) template<x>
+ #else
+ #define RLV_TEMPL_FIX(x) template<typename Placeholder = int>
+ #endif // LL_WINDOWS
+
+
+ template <EParamType templParamType, EBehaviour templBhvr>
+ struct CommandHandler
+ {
+ RLV_TEMPL_FIX(typename = typename std::enable_if<templParamType == EParamType::AddRem>::type) static ECmdRet onCommand(const RlvCommand&, bool&);
+ RLV_TEMPL_FIX(typename = typename std::enable_if<templParamType == EParamType::AddRem>::type) static void onCommandToggle(EBehaviour, bool);
+ RLV_TEMPL_FIX(typename = typename std::enable_if<templParamType == EParamType::Force>::type) static ECmdRet onCommand(const RlvCommand&);
+ RLV_TEMPL_FIX(typename = typename std::enable_if<templParamType == EParamType::Reply>::type) static ECmdRet onCommand(const RlvCommand&, std::string&);
+ };
+
+ // Aliases to improve readability in definitions
+ template<EBehaviour templBhvr> using BehaviourHandler = CommandHandler<EParamType::AddRem, templBhvr>;
+ template<EBehaviour templBhvr> using BehaviourToggleHandler = BehaviourHandler<templBhvr>;
+ template<EBehaviour templBhvr> using ForceHandler = CommandHandler<EParamType::Force, templBhvr>;
+ template<EBehaviour templBhvr> using ReplyHandler = CommandHandler<EParamType::Reply, templBhvr>;
+
+ // List of shared handlers
+ using VersionReplyHandler = ReplyHandler<EBehaviour::Version>; // Shared between @version and @versionnew
+
+ //
+ // CommandProcessor - Templated glue class that brings BehaviourInfo, CommandHandlerBaseImpl and CommandHandler together
+ //
+ template <EParamType templParamType, EBehaviour templBhvr, typename handlerImpl = CommandHandler<templParamType, templBhvr>, typename baseImpl = CommandHandlerBaseImpl<templParamType>>
+ class CommandProcessor : public BehaviourInfo
+ {
+ public:
+ // Default constructor used by behaviour specializations
+ RLV_TEMPL_FIX(typename = typename std::enable_if<templBhvr != EBehaviour::Unknown>::type)
+ CommandProcessor(const std::string& strBhvr, U32 nBhvrFlags = 0) : BehaviourInfo(strBhvr, templBhvr, templParamType, nBhvrFlags) {}
+
+ // Constructor used when we don't want to specialize on behaviour (see BehaviourGenericProcessor)
+ RLV_TEMPL_FIX(typename = typename std::enable_if<templBhvr == EBehaviour::Unknown>::type)
+ CommandProcessor(const std::string& strBhvr, EBehaviour eBhvr, U32 nBhvrFlags = 0) : BehaviourInfo(strBhvr, eBhvr, templParamType, nBhvrFlags) {}
+
+ ECmdRet processCommand(const RlvCommand& rlvCmd) const override { return baseImpl::processCommand(rlvCmd, &handlerImpl::onCommand); }
+ };
+
+ // Aliases to improve readability in definitions
+ template<EBehaviour templBhvr, typename handlerImpl = CommandHandler<EParamType::AddRem, templBhvr>> using BehaviourProcessor = CommandProcessor<EParamType::AddRem, templBhvr, handlerImpl>;
+ template<EBehaviour templBhvr, typename handlerImpl = CommandHandler<EParamType::Force, templBhvr>> using ForceProcessor = CommandProcessor<EParamType::Force, templBhvr, handlerImpl>;
+ template<EBehaviour templBhvr, typename handlerImpl = CommandHandler<EParamType::Reply, templBhvr>> using ReplyProcessor = CommandProcessor<EParamType::Reply, templBhvr, handlerImpl>;
+
+ // Provides pre-defined generic implementations of basic behaviours (template voodoo - see original commit for something that still made sense)
+ template<EBehaviourOptionType templOptionType> struct BehaviourGenericHandler { static ECmdRet onCommand(const RlvCommand& rlvCmd, bool& fRefCount); };
+ template<EBehaviourOptionType templOptionType> using BehaviourGenericProcessor = BehaviourProcessor<EBehaviour::Unknown, BehaviourGenericHandler<templOptionType>>;
+ template<EBehaviourOptionType templOptionType> struct ForceGenericHandler { static ECmdRet onCommand(const RlvCommand& rlvCmd); };
+ template<EBehaviourOptionType templOptionType> using ForceGenericProcessor = ForceProcessor<EBehaviour::Unknown, ForceGenericHandler<templOptionType>>;
+
+ // ============================================================================
+ // BehaviourProcessor and related classes - Handles add/rem comamnds aka "restrictions)
+ //
+
+ template <EBehaviour eBhvr, typename handlerImpl = BehaviourHandler<eBhvr>, typename toggleHandlerImpl = BehaviourToggleHandler<eBhvr>>
+ class BehaviourToggleProcessor : public BehaviourInfo
+ {
+ public:
+ BehaviourToggleProcessor(const std::string& strBhvr, U32 nBhvrFlags = 0) : BehaviourInfo(strBhvr, eBhvr, EParamType::AddRem, nBhvrFlags) {}
+ ECmdRet processCommand(const RlvCommand& rlvCmd) const override { return CommandHandlerBaseImpl<EParamType::AddRem>::processCommand(rlvCmd, &handlerImpl::onCommand, &toggleHandlerImpl::onCommandToggle); }
+ };
+ template <EBehaviour eBhvr, EBehaviourOptionType optionType, typename toggleHandlerImpl = BehaviourToggleHandler<eBhvr>> using RlvBehaviourGenericToggleProcessor = BehaviourToggleProcessor<eBhvr, BehaviourGenericHandler<optionType>, toggleHandlerImpl>;
+
+ // ============================================================================
+ // Various helper classes/timers/functors
+ //
+
+ struct CommandDbgOut
+ {
+ CommandDbgOut(const std::string& orig_cmd, bool for_console) : mOrigCmd(orig_cmd), mForConsole(for_console) {}
+ void add(std::string strCmd, ECmdRet eRet);
+ std::string get() const;
+ static std::string getDebugVerbFromReturnCode(ECmdRet eRet);
+ static std::string getReturnCodeString(ECmdRet eRet);
+ private:
+ std::string mOrigCmd;
+ std::map<ECmdRet, std::string> mCommandResults;
+ bool mForConsole = false;
+ };
+}
+
+// ============================================================================
+// RlvCommand
+//
+
+class RlvCommand
+{
+public:
+ explicit RlvCommand(const LLUUID& idObj, const std::string& strCmd);
+ RlvCommand(const RlvCommand& rlvCmd, Rlv::EParamType eParamType = Rlv::EParamType::Unknown);
+
+ /*
+ * Member functions
+ */
+public:
+ std::string asString() const;
+ const std::string& getBehaviour() const { return mBehaviour; }
+ const Rlv::BehaviourInfo* getBehaviourInfo() const { return mBhvrInfo; }
+ Rlv::EBehaviour getBehaviourType() const { return (mBhvrInfo) ? mBhvrInfo->getBehaviourType() : Rlv::EBehaviour::Unknown; }
+ U32 getBehaviourFlags() const { return (mBhvrInfo) ? mBhvrInfo->getBehaviourFlags() : 0; }
+ const LLUUID& getObjectID() const { return mObjId; }
+ const std::string& getOption() const { return mOption; }
+ const std::string& getParam() const { return mParam; }
+ Rlv::EParamType getParamType() const { return mParamType; }
+ bool hasOption() const { return !mOption.empty(); }
+ bool isBlocked() const { return (mBhvrInfo) ? mBhvrInfo->isBlocked() : false; }
+ bool isRefCounted() const { return mIsRefCounted; }
+ bool isStrict() const { return mIsStrict; }
+ bool isValid() const { return mIsValid; }
+ Rlv::ECmdRet processCommand() const { return (mBhvrInfo) ? mBhvrInfo->processCommand(*this) : Rlv::ECmdRet::FailedNoProcessor; }
+
+protected:
+ static bool parseCommand(const std::string& strCommand, std::string& strBehaviour, std::string& strOption, std::string& strParam);
+ bool markRefCounted() const { return mIsRefCounted = true; }
+
+ /*
+ * Operators
+ */
+public:
+ bool operator ==(const RlvCommand&) const;
+
+ /*
+ * Member variables
+ */
+protected:
+ bool mIsValid = false;
+ LLUUID mObjId;
+ std::string mBehaviour;
+ const Rlv::BehaviourInfo* mBhvrInfo = nullptr;
+ Rlv::EParamType mParamType = Rlv::EParamType::Unknown;
+ bool mIsStrict = false;
+ std::string mOption;
+ std::string mParam;
+ mutable bool mIsRefCounted = false;
+
+ friend class RlvHandler;
+ friend class RlvObject;
+ template<Rlv::EParamType> friend struct Rlv::CommandHandlerBaseImpl;
+};
+
+// ============================================================================
diff --git a/indra/newview/skins/default/xui/da/sidepanel_item_info.xml b/indra/newview/skins/default/xui/da/sidepanel_item_info.xml
index d52845160b..6a2acfedf7 100644
--- a/indra/newview/skins/default/xui/da/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/da/sidepanel_item_info.xml
@@ -15,6 +15,9 @@
<panel.string name="acquiredDate">
[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]
</panel.string>
+ <panel.string name="acquiredDateAMPM">
+ [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [ampm,datetime,local] [year,datetime,local]
+ </panel.string>
<panel.string name="origin_inventory">
(Beholdning)
</panel.string>
diff --git a/indra/newview/skins/default/xui/de/floater_about_land.xml b/indra/newview/skins/default/xui/de/floater_about_land.xml
index bb9ab26ef5..9c93ff38ad 100644
--- a/indra/newview/skins/default/xui/de/floater_about_land.xml
+++ b/indra/newview/skins/default/xui/de/floater_about_land.xml
@@ -25,7 +25,8 @@
<panel.string name="none_text">(keiner)</panel.string>
<panel.string name="sale_pending_text">(Wird verkauft)</panel.string>
<panel.string name="no_selection_text">Keine Parzelle ausgewählt.</panel.string>
- <panel.string name="time_stamp_template">[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]</panel.string>
+ <panel.string name="time_stamp_template_ampm">[wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour12,datetime,local]:[min,datetime,local]:[second,datetime,local] [ampm,datetime,slt]</panel.string>
+ <panel.string name="time_stamp_template">[wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local]</panel.string>
<text name="Name:">Name:</text>
<text name="Description:">Beschreibung:</text>
<text name="LandType">Typ:</text>
diff --git a/indra/newview/skins/default/xui/de/floater_inspect.xml b/indra/newview/skins/default/xui/de/floater_inspect.xml
index da97ceb2d8..a193e1123e 100644
--- a/indra/newview/skins/default/xui/de/floater_inspect.xml
+++ b/indra/newview/skins/default/xui/de/floater_inspect.xml
@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater min_width="460" name="inspect" title="OBJEKTE UNTERSUCHEN" width="460">
+ <floater.string name="timeStampAMPM">
+ [wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour12,datetime,local]:[min,datetime,local]:[second,datetime,local] [ampm,datetime,slt]
+ </floater.string>
<floater.string name="timeStamp">
- [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]
+ [wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local]
</floater.string>
<scroll_list name="object_list" tool_tip="Wählen Sie ein Objekt aus dieser Liste, um es in der Second Life-Welt zu markieren">
<scroll_list.columns label="Objektname" name="object_name"/>
diff --git a/indra/newview/skins/default/xui/de/panel_classified_info.xml b/indra/newview/skins/default/xui/de/panel_classified_info.xml
index 007e9d69f0..1b8caf5f78 100644
--- a/indra/newview/skins/default/xui/de/panel_classified_info.xml
+++ b/indra/newview/skins/default/xui/de/panel_classified_info.xml
@@ -13,7 +13,7 @@
[TELEPORT] teleportieren, [MAP] Karte, [PROFILE] Profil
</panel.string>
<panel.string name="date_fmt">
- [mthnum,datetime,slt]/[day,datetime,slt]/[year,datetime,slt]
+ [day,datetime,slt].[mthnum,datetime,slt].[year,datetime,slt]
</panel.string>
<panel.string name="auto_renew_on">
Aktiviert
diff --git a/indra/newview/skins/default/xui/de/panel_landmark_info.xml b/indra/newview/skins/default/xui/de/panel_landmark_info.xml
index 10cf34c170..8726d5e645 100644
--- a/indra/newview/skins/default/xui/de/panel_landmark_info.xml
+++ b/indra/newview/skins/default/xui/de/panel_landmark_info.xml
@@ -15,8 +15,11 @@
<string name="server_forbidden_text">
Die Informationen über diesen Standort sind zugriffsbeschränkt. Bitte wenden Sie sich bezüglich Ihrer Berechtigungen an den Eigentümer der Parzelle.
</string>
+ <string name="acquired_date_ampm">
+ [wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour12,datetime,local]:[min,datetime,local]:[second,datetime,local] [ampm,datetime,local]
+ </string>
<string name="acquired_date">
- [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]
+ [wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local]
</string>
<button name="back_btn" tool_tip="Hinten"/>
<text name="title" value="Ortsprofil"/>
diff --git a/indra/newview/skins/default/xui/de/panel_place_profile.xml b/indra/newview/skins/default/xui/de/panel_place_profile.xml
index 4077fdab36..a0b5a1e9dc 100644
--- a/indra/newview/skins/default/xui/de/panel_place_profile.xml
+++ b/indra/newview/skins/default/xui/de/panel_place_profile.xml
@@ -38,8 +38,11 @@
<string name="server_forbidden_text">
Die Informationen über diesen Standort sind zugriffsbeschränkt. Bitte wenden Sie sich bezüglich Ihrer Berechtigungen an den Eigentümer der Parzelle.
</string>
+ <string name="acquired_date_ampm">
+ [wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour12,datetime,local]:[min,datetime,local]:[second,datetime,local] [ampm,datetime,local]
+ </string>
<string name="acquired_date">
- [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]
+ [wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local]
</string>
<button name="back_btn" tool_tip="Hinten"/>
<text name="title" value="Ortsprofil"/>
diff --git a/indra/newview/skins/default/xui/de/panel_profile_classified.xml b/indra/newview/skins/default/xui/de/panel_profile_classified.xml
index 5c11a01977..5a4b42870c 100644
--- a/indra/newview/skins/default/xui/de/panel_profile_classified.xml
+++ b/indra/newview/skins/default/xui/de/panel_profile_classified.xml
@@ -13,7 +13,7 @@
[TELEPORT] Teleportieren, [MAP] Karten, [PROFILE] Profil
</panel.string>
<panel.string name="date_fmt">
- [mthnum,datetime,slt]/[day,datetime,slt]/[year,datetime,slt]
+ [day,datetime,slt].[mthnum,datetime,slt].[year,datetime,slt]
</panel.string>
<panel.string name="auto_renew_on">
Aktiviert
diff --git a/indra/newview/skins/default/xui/de/panel_profile_secondlife.xml b/indra/newview/skins/default/xui/de/panel_profile_secondlife.xml
index baaa58e1d7..437cde99fe 100644
--- a/indra/newview/skins/default/xui/de/panel_profile_secondlife.xml
+++ b/indra/newview/skins/default/xui/de/panel_profile_secondlife.xml
@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<panel label="Profil" name="panel_profile">
+ <string
+ name="date_format_full"
+ value="SL-Geburtstag: [day,datetime,utc]. [mth,datetime,utc]. [year,datetime,utc]" />
+ <string
+ name="date_format_short"
+ value="SL-Geburtstag: [day,datetime,utc]. [mth,datetime,utc]." />
<string name="status_online">
Zurzeit online
</string>
diff --git a/indra/newview/skins/default/xui/de/panel_status_bar.xml b/indra/newview/skins/default/xui/de/panel_status_bar.xml
index 0829814220..e017c6dd82 100644
--- a/indra/newview/skins/default/xui/de/panel_status_bar.xml
+++ b/indra/newview/skins/default/xui/de/panel_status_bar.xml
@@ -10,7 +10,7 @@
[hour12, datetime, slt]:[min, datetime, slt] [ampm, datetime, slt] [timezone,datetime, slt]
</panel.string>
<panel.string name="timeTooltip">
- [weekday, datetime, slt], [day, datetime, slt] [month, datetime, slt] [year, datetime, slt]
+ [weekday, datetime, slt], [day, datetime, slt]. [month, datetime, slt] [year, datetime, slt]
</panel.string>
<panel.string name="buycurrencylabel">
[AMT] L$
diff --git a/indra/newview/skins/default/xui/de/sidepanel_item_info.xml b/indra/newview/skins/default/xui/de/sidepanel_item_info.xml
index 168bb14248..30886d1433 100644
--- a/indra/newview/skins/default/xui/de/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/de/sidepanel_item_info.xml
@@ -19,7 +19,10 @@
Eigentümer kann:
</panel.string>
<panel.string name="acquiredDate">
- [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]
+ [wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local]
+ </panel.string>
+ <panel.string name="acquiredDateAMPM">
+ [wkday,datetime,local]. [day,datetime,local]. [mth,datetime,local]. [year,datetime,local] [hour12,datetime,local]:[min,datetime,local]:[second,datetime,local] [ampm,datetime,local]
</panel.string>
<panel.string name="origin_inventory">
(Inventar)
diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml
index 486d604e9f..8464bd9b0c 100644
--- a/indra/newview/skins/default/xui/de/strings.xml
+++ b/indra/newview/skins/default/xui/de/strings.xml
@@ -39,7 +39,7 @@ Audiotreiberversion: [AUDIO_DRIVER_VERSION]
LibVLC-Version: [LIBVLC_VERSION]
Voice-Server-Version: [VOICE_VERSION]</string>
<string name="AboutTraffic">Paketverlust: [PACKETS_LOST,number,0]/[PACKETS_IN,number,0] ([PACKETS_PCT,number,1] %)</string>
- <string name="AboutTime">[month, datetime, slt] [day, datetime, slt] [year, datetime, slt] [hour, datetime, slt]:[min, datetime, slt]:[second,datetime,slt]</string>
+ <string name="AboutTime">[day, datetime, slt]. [month, datetime, slt] [year, datetime, slt] [hour, datetime, slt]:[min, datetime, slt]:[second,datetime,slt]</string>
<string name="ErrorFetchingServerReleaseNotesURL">Fehler beim Abrufen der URL für die Server-Versionshinweise.</string>
<string name="BuildConfiguration">Build-Konfiguration</string>
<string name="ProgressRestoring">Wird wiederhergestellt...</string>
@@ -812,7 +812,7 @@ Falls diese Meldung weiterhin angezeigt wird, wenden Sie sich unter http://suppo
<string name="Unknown">(unbekannt)</string>
<string name="SummaryForTheWeek" value="Zusammenfassung für diese Woche, beginnend am "/>
<string name="NextStipendDay" value=". Der nächste Stipendium-Tag ist "/>
- <string name="GroupPlanningDate">[mthnum,datetime,utc]/[day,datetime,utc]/[year,datetime,utc]</string>
+ <string name="GroupPlanningDate">[day,datetime,utc].[mthnum,datetime,utc].[year,datetime,utc]</string>
<string name="GroupIndividualShare" value=" Gruppenanteil Einzelanteil"/>
<string name="GroupColumn" value="Gruppe"/>
<string name="Balance">Kontostand</string>
@@ -961,7 +961,7 @@ Falls diese Meldung weiterhin angezeigt wird, wenden Sie sich unter http://suppo
<string name="GroupMoneyBalance">Kontostand</string>
<string name="GroupMoneyCredits">Danksagung</string>
<string name="GroupMoneyDebits">Soll</string>
- <string name="GroupMoneyDate">[weekday,datetime,utc] [mth,datetime,utc] [day,datetime,utc], [year,datetime,utc]</string>
+ <string name="GroupMoneyDate">[weekday,datetime,utc] [day,datetime,utc]. [mth,datetime,utc]. [year,datetime,utc]</string>
<string name="AcquiredItems">Erworbene Artikel</string>
<string name="Cancel">Abbrechen</string>
<string name="UploadingCosts">Das Hochladen von [NAME] kostet [AMOUNT] L$</string>
@@ -1765,7 +1765,7 @@ Missbrauchsbericht</string>
<string name="dance6">Tanzen6</string>
<string name="dance7">Tanzen7</string>
<string name="dance8">Tanzen8</string>
- <string name="AvatarBirthDateFormat">[mthnum,datetime,slt]/[day,datetime,slt]/[year,datetime,slt]</string>
+ <string name="AvatarBirthDateFormat">[day,datetime,slt].[mthnum,datetime,slt].[year,datetime,slt]</string>
<string name="DefaultMimeType">Keine/Keiner</string>
<string name="texture_load_dimensions_error">Bilder, die größer sind als [WIDTH]*[HEIGHT] können nicht geladen werden</string>
<string name="outfit_photo_load_dimensions_error">Max. Fotogröße für Outfit ist [WIDTH]*[HEIGHT]. Bitte verkleinern Sie das Bild oder verwenden Sie ein anderes.</string>
diff --git a/indra/newview/skins/default/xui/en/floater_about_land.xml b/indra/newview/skins/default/xui/en/floater_about_land.xml
index 508aba6ae1..c5b42b6dae 100644
--- a/indra/newview/skins/default/xui/en/floater_about_land.xml
+++ b/indra/newview/skins/default/xui/en/floater_about_land.xml
@@ -125,6 +125,9 @@
name="no_selection_text">
No parcel selected.
</panel.string>
+ <panel.string name="time_stamp_template_ampm">
+ [wkday,datetime,slt] [mth,datetime,slt] [day,datetime,slt] [hour12,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [ampm,datetime,slt] [year,datetime,slt]
+ </panel.string>
<panel.string name="time_stamp_template">
[wkday,datetime,slt] [mth,datetime,slt] [day,datetime,slt] [hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [year,datetime,slt]
</panel.string>
diff --git a/indra/newview/skins/default/xui/en/floater_inspect.xml b/indra/newview/skins/default/xui/en/floater_inspect.xml
index 9403d58441..a083683c23 100644
--- a/indra/newview/skins/default/xui/en/floater_inspect.xml
+++ b/indra/newview/skins/default/xui/en/floater_inspect.xml
@@ -12,6 +12,10 @@
title="INSPECT OBJECTS"
width="400">
<floater.string
+ name="timeStampAMPM">
+ [wkday,datetime,slt] [mth,datetime,slt] [day,datetime,slt] [hour12,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [ampm,datetime,slt] [year,datetime,slt]
+ </floater.string>
+ <floater.string
name="timeStamp">
[wkday,datetime,slt] [mth,datetime,slt] [day,datetime,slt] [hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [year,datetime,slt]
</floater.string>
diff --git a/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml
index 6c3214a76d..1aa216d6b4 100644
--- a/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml
+++ b/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml
@@ -28,6 +28,10 @@
name="acquiredDate">
[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]
</floater.string>
+ <floater.string
+ name="acquiredDateAMPM">
+ [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour12,datetime,local]:[min,datetime,local]:[second,datetime,local] [ampm,datetime,local] [year,datetime,local]
+ </floater.string>
<icon
follows="top|right"
height="18"
diff --git a/indra/newview/skins/default/xui/en/floater_rlv_console.xml b/indra/newview/skins/default/xui/en/floater_rlv_console.xml
new file mode 100644
index 0000000000..708055d1b6
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_rlv_console.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ can_resize="true"
+ height="400"
+ layout="topleft"
+ min_height="300"
+ min_width="300"
+ name="rlv_console"
+ title="RLVa console"
+ width="600"
+ >
+ <layout_stack
+ animate="false"
+ bottom="-1"
+ default_tab_group="2"
+ follows="all"
+ left="5"
+ layout="topleft"
+ mouse_opaque="false"
+ name="main_stack"
+ right="-5"
+ orientation="vertical"
+ tab_group="1"
+ top="1"
+ >
+ <layout_panel
+ name="body_panel"
+ height="235">
+ <text_editor
+ follows="all"
+ left="1"
+ right="-1"
+ top="0"
+ length="1"
+ font="Monospace"
+ bottom="-1"
+ ignore_tab="false"
+ layout="topleft"
+ max_length="65536"
+ name="console_output"
+ read_only="true"
+ track_end="true"
+ type="string"
+ word_wrap="true"
+ >
+ </text_editor>
+ </layout_panel>
+
+ <layout_panel
+ height="26"
+ auto_resize="false"
+ name="input_panel">
+ <chat_editor
+ layout="topleft"
+ expand_lines_count="5"
+ follows="left|right|bottom"
+ font="SansSerifSmall"
+ height="20"
+ is_expandable="true"
+ text_tentative_color="TextFgTentativeColor"
+ name="console_input"
+ max_length="1023"
+ spellcheck="true"
+ tab_group="3"
+ bottom_delta="20"
+ left="1"
+ top="1"
+ right="-1"
+ show_emoji_helper="false"
+ wrap="true"
+ />
+ </layout_panel>
+ </layout_stack>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index da9cc0c98d..53615968e0 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -562,25 +562,6 @@
</menu_item_check>
<menu_item_separator/>
<menu_item_check
- label="Hear Media and Sound from Avatar"
- name="Hear Media and Sound from Avatar">
- <menu_item_check.on_check
- control="MediaSoundsEarLocation" />
- <menu_item_check.on_click
- function="Agent.ToggleHearMediaSoundFromAvatar" />
- </menu_item_check>
- <menu_item_check
- label="Hear Voice from Avatar"
- name="Hear Voice from Avatar">
- <menu_item_check.on_check
- control="VoiceEarLocation" />
- <menu_item_check.on_click
- function="Agent.ToggleHearVoiceFromAvatar" />
- <menu_item_call.on_enable
- control="EnableVoiceChat" />
- </menu_item_check>
- <menu_item_separator/>
- <menu_item_check
label="Gestures..."
name="Gestures"
shortcut="control|G">
@@ -1838,6 +1819,71 @@ function="World.EnvPreset"
</menu>
<menu
create_jump_keys="true"
+ label="RLVa"
+ name="RLVa Main"
+ tear_off="true"
+ visible="true">
+ <menu
+ label="Debug"
+ name="Debug"
+ tear_off="true">
+ <menu_item_check
+ label="Show Top-level RLVa Menu"
+ name="Show Top-level RLVa Menu">
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="RLVaTopLevelMenu" />
+ <menu_item_check.on_click
+ function="ToggleControl"
+ parameter="RLVaTopLevelMenu" />
+ </menu_item_check>
+ <menu_item_separator/>
+ <menu_item_check
+ label="Show Debug Messages"
+ name="Show Debug Messages">
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="RestrainedLoveDebug" />
+ <menu_item_check.on_click
+ function="ToggleControl"
+ parameter="RestrainedLoveDebug" />
+ </menu_item_check>
+ <menu_item_check
+ label="Hide Unset or Duplicate Messages"
+ name="Hide Unset or Duplicate Messages">
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="RLVaDebugHideUnsetDuplicate" />
+ <menu_item_check.on_click
+ function="ToggleControl"
+ parameter="RLVaDebugHideUnsetDuplicate" />
+ </menu_item_check>
+ </menu>
+ <menu_item_separator/>
+ <menu_item_check
+ label="Allow Temporary Attachments"
+ name="Allow Temporary Attachments">
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="RLVaEnableTemporaryAttachments" />
+ <menu_item_check.on_click
+ function="ToggleControl"
+ parameter="RLVaEnableTemporaryAttachments" />
+ </menu_item_check>
+ <menu_item_separator />
+ <menu_item_check
+ label="Console..."
+ name="Console">
+ <menu_item_check.on_check
+ function="Floater.Visible"
+ parameter="rlv_console" />
+ <menu_item_check.on_click
+ function="Floater.Toggle"
+ parameter="rlv_console" />
+ </menu_item_check>
+ </menu>
+ <menu
+ create_jump_keys="true"
label="Advanced"
name="Advanced"
tear_off="true"
@@ -2343,7 +2389,12 @@ function="World.EnvPreset"
function="Advanced.ToggleFeature"
parameter="flexible" />
</menu_item_check>
- </menu>
+ </menu>
+ <menu
+ label="RLVa"
+ name="RLVa Embedded"
+ tear_off="true"
+ visible="true" />
<menu_item_check
label="Use Plugin Read Thread"
name="Use Plugin Read Thread">
@@ -3756,6 +3807,13 @@ function="World.EnvPreset"
</menu_item_call>
<menu_item_call
enabled="true"
+ label="Edit Local Paintmap"
+ name="Edit Local Paintmap">
+ <menu_item_call.on_click
+ function="Advanced.TerrainEditLocalPaintMap" />
+ </menu_item_call>
+ <menu_item_call
+ enabled="true"
label="Delete Local Paintmap"
name="Delete Local Paintmap">
<menu_item_call.on_click
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index bc817765cf..4d74261a9a 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -1662,7 +1662,7 @@ The new skin will appear after you restart [APP_NAME].
icon="alertmodal.tga"
name="ChangeLanguage"
type="alertmodal">
-Changing language will take effect after you restart [APP_NAME].
+Changing language or time format will take effect after you restart [APP_NAME].
</notification>
<notification
diff --git a/indra/newview/skins/default/xui/en/panel_landmark_info.xml b/indra/newview/skins/default/xui/en/panel_landmark_info.xml
index af68bd7fee..19599efdc2 100644
--- a/indra/newview/skins/default/xui/en/panel_landmark_info.xml
+++ b/indra/newview/skins/default/xui/en/panel_landmark_info.xml
@@ -40,6 +40,10 @@
Information about this location is unavailable due to access restrictions. Please check your permissions with the parcel owner.
</string>
<string
+ name="acquired_date_ampm">
+ [wkday,datetime,slt] [mth,datetime,slt] [day,datetime,slt] [hour12,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [ampm,datetime,slt] [year,datetime,slt]
+ </string>
+ <string
name="acquired_date">
[wkday,datetime,slt] [mth,datetime,slt] [day,datetime,slt] [hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [year,datetime,slt]
</string>
diff --git a/indra/newview/skins/default/xui/en/panel_place_profile.xml b/indra/newview/skins/default/xui/en/panel_place_profile.xml
index 8f5292c531..bb877080b1 100644
--- a/indra/newview/skins/default/xui/en/panel_place_profile.xml
+++ b/indra/newview/skins/default/xui/en/panel_place_profile.xml
@@ -89,6 +89,10 @@
Information about this location is unavailable due to access restrictions. Please check your permissions with the parcel owner.
</string>
<string
+ name="acquired_date_ampm">
+ [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour12,datetime,local]:[min,datetime,local]:[second,datetime,local] [ampm,datetime,local] [year,datetime,local]
+ </string>
+ <string
name="acquired_date">
[wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local]
</string>
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_general.xml b/indra/newview/skins/default/xui/en/panel_preferences_general.xml
index 101c506309..ddddb4855f 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_general.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_general.xml
@@ -91,6 +91,37 @@
name="Traditional Chinese"
value="zh" />
</combo_box>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="15"
+ layout="topleft"
+ left="255"
+ name="time_format_textbox"
+ top="10"
+ width="200">
+ Time Format:
+ </text>
+ <combo_box
+ follows="left|top"
+ height="23"
+ layout="topleft"
+ left="255"
+ max_chars="135"
+ name="time_format_combobox"
+ width="70">
+ <combo_box.item
+ enabled="true"
+ label="1:00 AM"
+ name="12H"
+ value="0" />
+ <combo_box.item
+ enabled="true"
+ label="13:00"
+ name="24H"
+ value="1" />
+ </combo_box>
<text
font="SansSerifSmall"
type="string"
diff --git a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
index 1cb3eca2eb..40a88d4121 100644
--- a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
+++ b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml
@@ -32,6 +32,10 @@
Owner can:
</panel.string>
<panel.string
+ name="acquiredDateAMPM">
+ [wkday,datetime,slt] [mth,datetime,slt] [day,datetime,slt] [hour12,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [ampm,datetime,slt] [year,datetime,slt]
+ </panel.string>
+ <panel.string
name="acquiredDate">
[wkday,datetime,slt] [mth,datetime,slt] [day,datetime,slt] [hour,datetime,slt]:[min,datetime,slt]:[second,datetime,slt] [year,datetime,slt]
</panel.string>
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index effd19b708..3ec4b7205b 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -60,6 +60,7 @@ Disk cache: [DISK_CACHE_INFO]
HiDPI display mode: [HIDPI]
</string>
<string name="AboutLibs">
+RestrainedLove API: [RLV_VERSION]
J2C Decoder Version: [J2C_VERSION]
Audio Driver Version: [AUDIO_DRIVER_VERSION]
[LIBCEF_VERSION]
@@ -4381,4 +4382,31 @@ and report the problem.
[https://community.secondlife.com/knowledgebase/english/error-messages-r520/#Section__3 Knowledge Base]
</string>
+ <!-- RLVa -->
+ <string name="RlvConsoleDisable">RLVa is disabled</string>
+ <string name="RlvConsoleInvalidCmd">Invalid command</string>
+ <string name="RlvConsoleExecuted">INFO</string>
+ <string name="RlvConsoleFailed">ERR</string>
+ <string name="RlvConsoleRetained">RET</string>
+ <string name="RlvDebugExecuted">executed</string>
+ <string name="RlvDebugFailed">failed</string>
+ <string name="RlvDebugRetained">retained</string>
+ <string name="RlvReturnCodeUnset">unset</string>
+ <string name="RlvReturnCodeDuplicate">duplicate</string>
+ <string name="RlvReturnCodeDelayed">delayed</string>
+ <string name="RlvReturnCodeDeprecated">deprecated</string>
+ <string name="RlvReturnCodeSyntax">thingy error</string>
+ <string name="RlvReturnCodeOption">invalid option</string>
+ <string name="RlvReturnCodeParam">invalid param</string>
+ <string name="RlvReturnCodeLock">locked command</string>
+ <string name="RlvReturnCodeDisabled">disabled command</string>
+ <string name="RlvReturnCodeUnknown">unknown command</string>
+ <string name="RlvReturnCodeNoSharedRoot">missing #RLV</string>
+ <string name="RlvReturnCodeDeprecatedAndDisabled">deprecated and disabled</string>
+ <string name="RlvReturnCodeNoBehaviour">no active behaviours</string>
+ <string name="RlvReturnCodeUnheldBehaviour">base behaviour not held</string>
+ <string name="RlvReturnCodeBlocked">blocked object</string>
+ <string name="RlvReturnCodeThrottled">throttled</string>
+ <string name="RlvReturnCodeNoProcessor">no command processor found</string>
+
</strings>
diff --git a/indra/newview/tests/llworldmap_test.cpp b/indra/newview/tests/llworldmap_test.cpp
index d5bf189d82..99c98f63ed 100644
--- a/indra/newview/tests/llworldmap_test.cpp
+++ b/indra/newview/tests/llworldmap_test.cpp
@@ -28,6 +28,7 @@
// Dependencies
#include "linden_common.h"
#include "llapr.h"
+#include "llcontrol.h" // LLControlGroup
#include "llsingleton.h"
#include "lltrans.h"
#include "lluistring.h"
@@ -71,6 +72,8 @@ void LLUIString::updateResult() const { }
void LLUIString::setArg(const std::string& , const std::string& ) { }
void LLUIString::assign(const std::string& ) { }
+LLControlGroup gSavedSettings("Global"); // saved at end of session
+
// End Stubbing
// -------------------------------------------------------------------------------------------
@@ -131,6 +134,7 @@ namespace tut
// Constructor and destructor of the test wrapper
worldmap_test()
{
+ gSavedSettings.declareBOOL("Use24HourClock", true, "", LLControlVariable::PERSIST_NO);
mWorld = LLWorldMap::getInstance();
}
~worldmap_test()