Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ChatTwo/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ public class Configuration : IPluginConfiguration
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
SizePt = 12.75f,
};
// Black at 0x66 alpha; composites to roughly the vanilla glow opacity
// across the 8 outline stamps drawn by ImGuiUtil.DrawTextGlow.
public const uint DefaultOutlineColor = 0x00000066;

public bool RenderGlow = true;
public bool OutlineAllText;
public uint OutlineColor = DefaultOutlineColor;

public float TooltipOffset;
public float WindowAlpha = 100f;
Expand Down Expand Up @@ -198,6 +205,9 @@ public void UpdateFrom(Configuration other, bool backToOriginal)
JapaneseFontV2 = other.JapaneseFontV2;
ItalicFontV2 = other.ItalicFontV2;
SymbolsFontSizeV2 = other.SymbolsFontSizeV2;
RenderGlow = other.RenderGlow;
OutlineAllText = other.OutlineAllText;
OutlineColor = other.OutlineColor;
TooltipOffset = other.TooltipOffset;
WindowAlpha = other.WindowAlpha;
ChatColours = other.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value);
Expand Down
45 changes: 45 additions & 0 deletions ChatTwo/Resources/Language.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions ChatTwo/Resources/Language.resx
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,21 @@
<data name="Options_ItalicEnabled">
<value>Use italic font</value>
</data>
<data name="Options_RenderGlow_Name">
<value>Render text glow</value>
</data>
<data name="Options_RenderGlow_Description">
<value>Draw the glow colour the game specifies around text as a thin outline, like the vanilla chat log.</value>
</data>
<data name="Options_OutlineAllText_Name">
<value>Outline all text</value>
</data>
<data name="Options_OutlineAllText_Description">
<value>Draw an outline around all chat text to improve readability. Text with a game-specified glow colour keeps that colour.</value>
</data>
<data name="Options_OutlineAllText_Color">
<value>Outline colour</value>
</data>
<data name="AutoTranslate_Search_Hint">
<value>Search Auto Translate...</value>
</data>
Expand Down
5 changes: 5 additions & 0 deletions ChatTwo/Ui/Handler/ChunkHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Numerics;
using System.Text;
using ChatTwo.Code;
using ChatTwo.Util;
using Dalamud.Bindings.ImGui;
Expand Down Expand Up @@ -140,6 +141,10 @@ public void DrawChunk(Chunk chunk, bool wrap = true, PayloadHandler? handler = n
}
else
{
var glow = ImGuiUtil.GetGlowColor(chunk);
if (glow != null && content.Length > 0)
ImGuiUtil.DrawTextGlow(ImGui.GetCursorScreenPos(), glow.Value, Encoding.UTF8.GetBytes(content));

ImGui.TextUnformatted(content);
ImGuiUtil.PostPayload(chunk, handler);
}
Expand Down
5 changes: 3 additions & 2 deletions ChatTwo/Ui/SeStringDebugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Dalamud.Interface.Windowing;
using Dalamud.Bindings.ImGui;
using Dalamud.Utility;
using Lumina.Text.Payloads;
using Lumina.Text.ReadOnly;
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;

Expand Down Expand Up @@ -183,10 +184,10 @@ private void ProcessPayloads(List<Payload> payloads)
}
case RawPayload raw:
{
var colorPayload = ColorPayload.From(raw.Data);
var colorPayload = ColorPayload.From(raw.Data) ?? ColorPayload.From(raw.Data, MacroCode.EdgeColor);
if (colorPayload != null)
{
RenderMetadataDictionary("Link ColorPayload", new Dictionary<string, string?>
RenderMetadataDictionary($"Link ColorPayload ({colorPayload.MacroCode})", new Dictionary<string, string?>
{
{ "Unshifted", colorPayload.UnshiftedColor.ToString("X8") },
{ "Color", colorPayload.Color.ToString("X8") },
Expand Down
19 changes: 19 additions & 0 deletions ChatTwo/Ui/SettingsTabs/Fonts.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using ChatTwo.Resources;
using ChatTwo.Util;
using Dalamud;
using Dalamud.Interface;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
Expand Down Expand Up @@ -93,5 +94,23 @@ public void Draw(bool _)
ImGuiUtil.HelpText(Language.Options_SymbolsFontSize_Description);

ImGui.Spacing();
ImGui.Separator();
ImGui.Spacing();

ImGuiUtil.OptionCheckbox(ref Mutable.RenderGlow, Language.Options_RenderGlow_Name, Language.Options_RenderGlow_Description);
ImGui.Spacing();

ImGuiUtil.OptionCheckbox(ref Mutable.OutlineAllText, Language.Options_OutlineAllText_Name, Language.Options_OutlineAllText_Description);
if (Mutable.OutlineAllText)
{
using var indent = ImRaii.PushIndent();
if (ImGuiUtil.IconButton(FontAwesomeIcon.UndoAlt, "outline", Language.Options_ChatColours_Reset))
Mutable.OutlineColor = Configuration.DefaultOutlineColor;
ImGui.SameLine();
var outlineColour = ColourUtil.RgbaToVector4(Mutable.OutlineColor)!.Value;
if (ImGui.ColorEdit4(Language.Options_OutlineAllText_Color, ref outlineColour, ImGuiColorEditFlags.NoInputs))
Mutable.OutlineColor = ColourUtil.Vector4ToRgba(outlineColour);
}
ImGui.Spacing();
}
}
35 changes: 27 additions & 8 deletions ChatTwo/Util/ChunkUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,16 +280,32 @@ void Append(string text)
case PayloadType.UIForeground:
var foregroundPayload = (UIForegroundPayload) payload;
if (foregroundPayload.IsEnabled)
foreground.Push(foregroundPayload.UIColor.Value.Dark);
{
// Rows missing from the UIColor sheet throw on RowRef.Value, which would
// drop the whole message; keep the previous color instead as we don't
// want invisible text.
if (foregroundPayload.UIColor.ValueNullable is { } foregroundRow)
foreground.Push(foregroundRow.Dark);
else if (foreground.Count > 0)
foreground.Push(foreground.Peek());
}
else if (foreground.Count > 0)
{
foreground.Pop();
}
break;
case PayloadType.UIGlow:
var glowPayload = (UIGlowPayload) payload;
if (glowPayload.IsEnabled)
glow.Push(glowPayload.UIColor.Value.Light);
{
// Rows missing from the UIColor sheet throw on RowRef.Value, which would
// drop the whole message; push a no-glow entry to keep the stack balanced.
glow.Push(glowPayload.UIColor.ValueNullable?.Dark ?? 0);
}
else if (glow.Count > 0)
{
glow.Pop();
}
break;
case PayloadType.AutoTranslateText:
chunks.Add(new IconChunk(source, payload, BitmapFontIcon.AutoTranslateBegin));
Expand Down Expand Up @@ -328,16 +344,19 @@ void Append(string text)
foreground.Pop();
}
}
else if (rawPayload.Data.Length > 1 && rawPayload.Data[1] == 0x14)
else if (ColorPayload.From(rawPayload.Data, MacroCode.EdgeColor) is { } edgeColorPayload)
{
if (glow.Count > 0)
if (edgeColorPayload.Enabled)
{
glow.Pop();
// The game pushes the resolved value even when it is 0, which draws
// no outline (verified against the vanilla renderer). A 0 entry keeps
// the stack balanced for the matching stackcolor pop; unlike the
// foreground there is no invisible-text concern.
glow.Push(edgeColorPayload.Color);
}
else if (rawPayload.Data.Length > 6 && rawPayload.Data[2] == 0x05 && rawPayload.Data[3] == 0xF6)
else if (glow.Count > 0)
{
var (r, g, b) = (rawPayload.Data[4], rawPayload.Data[5], rawPayload.Data[6]);
glow.Push(ColourUtil.ComponentsToRgba(r, g, b));
glow.Pop();
}
}
else if (rawPayload.Data.Length > 7 && rawPayload.Data[1] == 0x27 && rawPayload.Data[3] == 0x0A)
Expand Down
15 changes: 12 additions & 3 deletions ChatTwo/Util/ColourUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public static Vector3 RgbaToVector3(uint rgba)
public static uint Vector3ToRgba(Vector3 col)
=> ComponentsToRgba((byte)Math.Round(col.X * 255), (byte)Math.Round(col.Y * 255), (byte)Math.Round(col.Z * 255));

/// <summary>
/// Converts a Vector4 to an RGBA color value.
/// </summary>
/// <param name="col">The color</param>
/// <returns>Color as byte representation RR GG BB AA</returns>
public static uint Vector4ToRgba(Vector4 col)
=> ComponentsToRgba((byte)Math.Round(col.X * 255), (byte)Math.Round(col.Y * 255), (byte)Math.Round(col.Z * 255), (byte)Math.Round(col.W * 255));

/// <summary>
/// Converts a Vector4 to an ABGR color value.
/// </summary>
Expand All @@ -60,9 +68,10 @@ public static unsafe uint ArgbToRgba(uint col)
if (col == 0)
return 0;

// Check if Alpha is set, if not set to 255
if (col <= 0x00FFFFFFu)
col |= 0xFF000000u;
// The game ignores the alpha of pushed colors entirely and renders them
// fully opaque (verified against the vanilla renderer), so force alpha
// regardless of what the payload carries.
col |= 0xFF000000u;

var buf = (byte*)&col;
(buf[1], buf[2], buf[3], buf[0]) = (buf[0], buf[1], buf[2], buf[3]);
Expand Down
50 changes: 34 additions & 16 deletions ChatTwo/Util/ExtraPayload.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace ChatTwo.Util;
using Lumina.Text.Payloads;

namespace ChatTwo.Util;

public class ColorPayload
{
Expand All @@ -7,17 +9,18 @@ public class ColorPayload
public bool Enabled;
public uint Color;
public uint UnshiftedColor;
public MacroCode MacroCode;

public static ColorPayload? From(byte[] data)
public static ColorPayload? From(byte[] data, MacroCode macroCode = MacroCode.Color)
{
using var stream = new MemoryStream(data);
if (stream.ReadByte() != StartByte || stream.ReadByte() != 0x13)
if (stream.ReadByte() != StartByte || stream.ReadByte() != (byte) macroCode)
return null;

stream.ReadByte(); // skip the length byte;

var typeByte = stream.ReadByte();
var payload = new ColorPayload();
var payload = new ColorPayload { MacroCode = macroCode };
switch (typeByte)
{
case 0xEC:
Expand All @@ -33,32 +36,47 @@ public class ColorPayload
return payload;
case >= 0xF0 and <= 0xFE:
// From: https://github.com/NotAdam/Lumina/blob/master/src/Lumina/Text/Expressions/IntegerExpression.cs#L119-L128
uint ShiftAndThrowIfZero(int v, int shift)
// Component bytes are never zero in this encoding, so zero doubles
// as the truncation sentinel (EOF or premature null); payload data
// comes from the game or other plugins, so malformed input must
// return null rather than throw.
var truncated = false;
uint ReadComponent(int shift)
{
return v switch
var v = stream.ReadByte();
if (v <= 0)
{
// ReSharper disable once LocalizableElement
-1 => throw new ArgumentException("Encountered premature end of input (unexpected EOF).", nameof(v)),
// ReSharper disable once LocalizableElement
0 => throw new ArgumentException("Encountered premature end of input (unexpected null character).", nameof(v)),
_ => (uint)v << shift,
};
truncated = true;
return 0;
}

return (uint) v << shift;
}

typeByte += 1;
var argbValue = 0u;
if ((typeByte & 8) != 0)
argbValue |= ShiftAndThrowIfZero(stream.ReadByte(), 24);
argbValue |= ReadComponent(24);
else
argbValue |= 0xFF000000u;

if( (typeByte & 4) != 0 ) argbValue |= ShiftAndThrowIfZero( stream.ReadByte(), 16 );
if( (typeByte & 2) != 0 ) argbValue |= ShiftAndThrowIfZero( stream.ReadByte(), 8 );
if( (typeByte & 1) != 0 ) argbValue |= ShiftAndThrowIfZero( stream.ReadByte(), 0 );
if ((typeByte & 4) != 0) argbValue |= ReadComponent(16);
if ((typeByte & 2) != 0) argbValue |= ReadComponent(8);
if ((typeByte & 1) != 0) argbValue |= ReadComponent(0);

if (truncated)
return null;

payload.Enabled = true;
payload.Color = ColourUtil.ArgbToRgba(argbValue);

return payload;
case >= 0x01 and <= 0xCF:
// Inline small integer: a single byte encoding value + 1. The game pushes the
// resolved value as-is; 0 draws no edge (verified against the vanilla renderer).
payload.Enabled = true;
payload.Color = ColourUtil.ArgbToRgba((uint) (typeByte - 1));

return payload;
default:
return null;
Expand Down
Loading