Skip to content

Commit eecbb9d

Browse files
gwigzclaude
andcommitted
Add IRC-style chat colors
Optionally give every other local-chat speaker a stable, deterministic color (HSL from a hash of their UUID) and dim their displayed name relative to its text color, IRC-style. Ported from the slv fork, dropping the bundled LEAP/viewer-api theme endpoints. Adapted for Alchemy: the logic lives on ALAvatarGroups (the central avatar color controller) as getIRCChatColor/getIRCNameColor, and only overrides the "other agent" case so self, system, object and owner-say messages keep the colors already configured in Preferences > Colors > Chat. Muted speakers stay greyed. Exposed there as an enable toggle plus saturation / lightness / name-dimming sliders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 753e934 commit eecbb9d

7 files changed

Lines changed: 271 additions & 1 deletion

File tree

indra/newview/alavatargroups.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
// lib includes
2727
#include "llavatarname.h"
2828
#include "llavatarnamecache.h"
29+
#include "llchat.h"
2930
#include "lluicolor.h"
3031
#include "lluicolortable.h"
3132
#include "lluuid.h"
@@ -34,6 +35,7 @@
3435
// viewer includes
3536
#include "llagent.h"
3637
#include "llcallingcard.h"
38+
#include "llinstantmessage.h" // SYSTEM_FROM
3739
#include "llmutelist.h"
3840
#include "llviewercontrol.h"
3941
#include "rlvactions.h"
@@ -294,3 +296,98 @@ std::string ALAvatarGroups::getAvatarColorName(const LLUUID& id, std::string_vie
294296

295297
return out_color_name;
296298
}
299+
300+
bool ALAvatarGroups::getIRCChatColor(const LLChat& chat, LLUIColor& color)
301+
{
302+
static LLCachedControl<bool> enabled(gSavedSettings, "AlchemyChatIRCColorsEnabled", false);
303+
if (!enabled)
304+
{
305+
return false;
306+
}
307+
308+
// Only override "other" speakers; self, system, objects and owner-say keep
309+
// their user-configured chat colors from the existing switch.
310+
if (chat.mSourceType == CHAT_SOURCE_AGENT
311+
&& chat.mFromID.notNull()
312+
&& chat.mFromID != gAgentID
313+
&& SYSTEM_FROM != chat.mFromName
314+
// A stable per-avatar color would defeat @shownames, so skip it while
315+
// restricted from seeing this speaker's name.
316+
&& RlvActions::canShowName(RlvActions::SNC_DEFAULT, chat.mFromID))
317+
{
318+
color = deterministicAgentColor(chat.mFromID);
319+
return true;
320+
}
321+
322+
return false;
323+
}
324+
325+
bool ALAvatarGroups::getIRCNameColor(const LLChat& chat, const LLUIColor& chat_color, LLUIColor& color)
326+
{
327+
static LLCachedControl<bool> enabled(gSavedSettings, "AlchemyChatIRCColorsEnabled", false);
328+
if (!enabled)
329+
{
330+
return false;
331+
}
332+
333+
// Keep the default name styling while restricted from seeing this speaker's
334+
// name, matching getIRCChatColor.
335+
if (!RlvActions::canShowName(RlvActions::SNC_DEFAULT, chat.mFromID))
336+
{
337+
return false;
338+
}
339+
340+
color = dimNameColor(chat_color.get());
341+
return true;
342+
}
343+
344+
bool ALAvatarGroups::getIRCNameTagColor(const LLUUID& id, LLColor4& color)
345+
{
346+
static LLCachedControl<bool> enabled(gSavedSettings, "AlchemyChatIRCColorsEnabled", false);
347+
static LLCachedControl<bool> name_tags(gSavedSettings, "AlchemyChatIRCColorNameTags", false);
348+
if (!enabled || !name_tags)
349+
{
350+
return false;
351+
}
352+
353+
// Mirror the chat-color override: only other agents get a per-avatar color,
354+
// self keeps the user-configured name tag colors. Skip while restricted from
355+
// seeing this avatar's name so the color can't defeat @shownames. The name
356+
// dimming applied to chat names is applied here too so tags match.
357+
if (id.notNull() && id != gAgentID
358+
&& RlvActions::canShowName(RlvActions::SNC_DEFAULT, id))
359+
{
360+
color = dimNameColor(deterministicAgentColor(id));
361+
return true;
362+
}
363+
364+
return false;
365+
}
366+
367+
LLColor4 ALAvatarGroups::deterministicAgentColor(const LLUUID& id)
368+
{
369+
static LLCachedControl<F32> saturation(gSavedSettings, "AlchemyChatIRCAgentSaturation", 0.7f);
370+
static LLCachedControl<F32> lightness(gSavedSettings, "AlchemyChatIRCAgentLightness", 0.9f);
371+
372+
LLColor4 color;
373+
color.setHSL(static_cast<F32>(id.getCRC32() % 360) / 360.f, saturation(), lightness());
374+
color.mV[VALPHA] = 1.f;
375+
376+
return color;
377+
}
378+
379+
LLColor4 ALAvatarGroups::dimNameColor(const LLColor4& color)
380+
{
381+
static LLCachedControl<F32> scale(gSavedSettings, "AlchemyChatIRCNameLightnessScale", 0.8f);
382+
383+
F32 hue = 0.f;
384+
F32 saturation = 0.f;
385+
F32 lightness = 0.f;
386+
color.calcHSL(&hue, &saturation, &lightness);
387+
388+
LLColor4 result;
389+
result.setHSL(hue, saturation, lightness * scale());
390+
result.mV[VALPHA] = 1.f;
391+
392+
return result;
393+
}

indra/newview/alavatargroups.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
#include <boost/unordered_map.hpp>
2525
#include <boost/unordered_set.hpp>
2626

27+
class LLChat;
2728
class LLColor4;
29+
class LLUIColor;
2830
class LLUUID;
2931

3032
class ALAvatarGroups final : public LLSingleton < ALAvatarGroups >
@@ -54,4 +56,16 @@ class ALAvatarGroups final : public LLSingleton < ALAvatarGroups >
5456

5557
LLColor4 getAvatarColor(const LLUUID& id, LLColor4 default_color, EColorType color_type);
5658
std::string getAvatarColorName(const LLUUID& id, std::string_view color_name, EColorType color_type);
59+
60+
// IRC-style chat coloring: when enabled, gives every other speaker a stable
61+
// deterministic color and dims the displayed name relative to its text color.
62+
// Other message categories (self, system, objects, owner-say) keep their
63+
// user-configured chat colors.
64+
bool getIRCChatColor(const LLChat& chat, LLUIColor& color);
65+
bool getIRCNameColor(const LLChat& chat, const LLUIColor& chat_color, LLUIColor& color);
66+
bool getIRCNameTagColor(const LLUUID& id, LLColor4& color);
67+
68+
private:
69+
LLColor4 deterministicAgentColor(const LLUUID& id);
70+
LLColor4 dimNameColor(const LLColor4& color);
5771
};

indra/newview/app_settings/settings_alchemy.xml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,61 @@
387387
<key>Value</key>
388388
<string>/tp2cam</string>
389389
</map>
390+
<key>AlchemyChatIRCColorsEnabled</key>
391+
<map>
392+
<key>Comment</key>
393+
<string>Use IRC-style deterministic colors for local chat names and text.</string>
394+
<key>Persist</key>
395+
<integer>1</integer>
396+
<key>Type</key>
397+
<string>Boolean</string>
398+
<key>Value</key>
399+
<integer>0</integer>
400+
</map>
401+
<key>AlchemyChatIRCColorNameTags</key>
402+
<map>
403+
<key>Comment</key>
404+
<string>Overwrite each other agent's name tag with their IRC-style per-agent chat color.</string>
405+
<key>Persist</key>
406+
<integer>1</integer>
407+
<key>Type</key>
408+
<string>Boolean</string>
409+
<key>Value</key>
410+
<integer>0</integer>
411+
</map>
412+
<key>AlchemyChatIRCAgentSaturation</key>
413+
<map>
414+
<key>Comment</key>
415+
<string>Saturation of the per-agent IRC-style chat colors (0-1).</string>
416+
<key>Persist</key>
417+
<integer>1</integer>
418+
<key>Type</key>
419+
<string>F32</string>
420+
<key>Value</key>
421+
<real>0.7</real>
422+
</map>
423+
<key>AlchemyChatIRCAgentLightness</key>
424+
<map>
425+
<key>Comment</key>
426+
<string>Lightness of the per-agent IRC-style chat colors (0-1).</string>
427+
<key>Persist</key>
428+
<integer>1</integer>
429+
<key>Type</key>
430+
<string>F32</string>
431+
<key>Value</key>
432+
<real>0.9</real>
433+
</map>
434+
<key>AlchemyChatIRCNameLightnessScale</key>
435+
<map>
436+
<key>Comment</key>
437+
<string>Scales the lightness of the displayed name relative to its IRC-style chat color (0-1).</string>
438+
<key>Persist</key>
439+
<integer>1</integer>
440+
<key>Type</key>
441+
<string>F32</string>
442+
<key>Value</key>
443+
<real>0.8</real>
444+
</map>
390445
<key>AlchemyChatMarkUnnamedObjects</key>
391446
<map>
392447
<key>Comment</key>

indra/newview/llchathistory.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#include "llviewercontrol.h"
7070
#include "llviewermenu.h"
7171
#include "llviewerobjectlist.h"
72+
#include "alavatargroups.h"
7273
// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-07-10 (Catznip-3.3)
7374
#include "llaudioengine.h"
7475
#include "lltextparser.h"
@@ -1338,6 +1339,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
13381339
LLUIColor txt_color = LLUIColorTable::instance().getColor("White");
13391340
LLUIColor name_color = LLUIColorTable::instance().getColor("ChatHeaderDisplayNameColor"); // <alchemy/>
13401341
LLViewerChat::getChatColor(chat, txt_color, alpha);
1342+
const bool irc_name_color = ALAvatarGroups::instance().getIRCNameColor(chat, txt_color, name_color);
13411343

13421344
LLFontGL* fontp = LLViewerChat::getChatFont();
13431345
std::string font_name = LLFontGL::nameFromFont(fontp);
@@ -1463,6 +1465,11 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
14631465
static LLUIColor link_color = LLUIColorTable::instance().getColor("HTMLLinkColor");
14641466
link_params.color = link_color;
14651467
link_params.readonly_color = link_color;
1468+
if (irc_name_color)
1469+
{
1470+
link_params.color = name_color;
1471+
link_params.readonly_color = name_color;
1472+
}
14661473
link_params.is_link = true;
14671474
link_params.link_href = url;
14681475

@@ -1500,6 +1507,14 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
15001507
{
15011508
LLStyle::Params link_params(body_message_params);
15021509
link_params.overwriteFrom(LLStyleMap::instance().lookupAgent(chat.mFromID));
1510+
if (irc_name_color)
1511+
{
1512+
// Keep our dimmed name color instead of letting the agent
1513+
// SLURL re-parse to the default HTMLLinkColor link style.
1514+
link_params.use_default_link_style = false;
1515+
link_params.color = name_color;
1516+
link_params.readonly_color = name_color;
1517+
}
15031518

15041519
if (use_irssi_text_chat_history)
15051520
{

indra/newview/llviewerchat.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ void LLViewerChat::getChatColor(const LLChat& chat, LLUIColor& r_color, F32& r_c
5151
}
5252
else
5353
{
54+
if (!ALAvatarGroups::instance().getIRCChatColor(chat, r_color))
55+
{
5456
switch(chat.mSourceType)
5557
{
5658
case CHAT_SOURCE_SYSTEM:
@@ -96,6 +98,7 @@ void LLViewerChat::getChatColor(const LLChat& chat, LLUIColor& r_color, F32& r_c
9698
default:
9799
r_color = LLUIColorTable::instance().getColor("White");
98100
}
101+
}
99102

100103
if (!chat.mPosAgent.isExactlyZero())
101104
{

indra/newview/llvoavatar.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3999,7 +3999,10 @@ LLColor4 LLVOAvatar::getNameTagColor(bool is_friend)
39993999
#endif
40004000
static LLUIColor name_tag_match = LLUIColorTable::instance().getColor("NameTagMatch");
40014001
LLColor4 color_name = name_tag_match;
4002-
color_name = ALAvatarGroups::instance().getAvatarColor(getID(), color_name, ALAvatarGroups::COLOR_NAMETAG);
4002+
if (!ALAvatarGroups::instance().getIRCNameTagColor(getID(), color_name))
4003+
{
4004+
color_name = ALAvatarGroups::instance().getAvatarColor(getID(), color_name, ALAvatarGroups::COLOR_NAMETAG);
4005+
}
40034006

40044007
return color_name;
40054008
}

indra/newview/skins/default/xui/en/panel_preferences_colors.xml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,89 @@
814814
width="95">
815815
Lindens
816816
</text>
817+
<text
818+
type="string"
819+
length="1"
820+
follows="left|top"
821+
height="12"
822+
layout="topleft"
823+
left="30"
824+
name="irc_colors_label"
825+
top_pad="20"
826+
width="300">
827+
IRC-style chat colors:
828+
</text>
829+
<check_box
830+
control_name="AlchemyChatIRCColorsEnabled"
831+
follows="left|top"
832+
height="16"
833+
label="Give each other speaker a unique color"
834+
layout="topleft"
835+
left="40"
836+
name="irc_chat_colors_enabled"
837+
tool_tip="Colors other people's local chat by a stable per-avatar color. Your own, system, object and owner-say messages keep the colors set above."
838+
top_pad="12"
839+
width="440"/>
840+
<check_box
841+
control_name="AlchemyChatIRCColorNameTags"
842+
enabled_control="AlchemyChatIRCColorsEnabled"
843+
follows="left|top"
844+
height="16"
845+
label="Also color their name tag to match"
846+
layout="topleft"
847+
left="40"
848+
name="irc_chat_colors_name_tags"
849+
tool_tip="Overwrites each other person's floating name tag with the same per-avatar color used for their chat."
850+
top_pad="4"
851+
width="440"/>
852+
<slider
853+
control_name="AlchemyChatIRCAgentSaturation"
854+
decimal_digits="2"
855+
follows="left|top"
856+
height="16"
857+
increment="0.01"
858+
initial_value="0.7"
859+
label="Saturation"
860+
label_width="120"
861+
layout="topleft"
862+
left="40"
863+
max_val="1"
864+
min_val="0"
865+
name="irc_agent_saturation"
866+
top_pad="8"
867+
width="300"/>
868+
<slider
869+
control_name="AlchemyChatIRCAgentLightness"
870+
decimal_digits="2"
871+
follows="left|top"
872+
height="16"
873+
increment="0.01"
874+
initial_value="0.9"
875+
label="Lightness"
876+
label_width="120"
877+
layout="topleft"
878+
left="40"
879+
max_val="1"
880+
min_val="0"
881+
name="irc_agent_lightness"
882+
top_pad="4"
883+
width="300"/>
884+
<slider
885+
control_name="AlchemyChatIRCNameLightnessScale"
886+
decimal_digits="2"
887+
follows="left|top"
888+
height="16"
889+
increment="0.01"
890+
initial_value="0.8"
891+
label="Name dimming"
892+
label_width="120"
893+
layout="topleft"
894+
left="40"
895+
max_val="1"
896+
min_val="0"
897+
name="irc_name_lightness_scale"
898+
top_pad="4"
899+
width="300"/>
817900
</panel>
818901
</tab_container>
819902
</panel>

0 commit comments

Comments
 (0)