Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
16 changes: 12 additions & 4 deletions Intersect.Client.Core/Interface/Game/EventWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Intersect.Client.Interface.Game.Typewriting;
using Intersect.Client.Localization;
using Intersect.Client.Networking;
using Intersect.Client.Utilities;
using Intersect.Configuration;
using Intersect.Enums;
using Intersect.Utilities;
Expand Down Expand Up @@ -160,18 +161,25 @@ private EventWindow(Canvas gameCanvas, Dialog dialog) : base(gameCanvas, nameof(
SkipRender();

_promptLabel.ClearText();
_promptLabel.AddText(_dialog.Prompt ?? string.Empty, _promptTemplateLabel);
_promptLabel.ForceImmediateRebuild();
var parsedText = TextColorParser.Parse(_dialog.Prompt ?? string.Empty, Color.White);

foreach (var segment in parsedText)
{
_promptLabel.AddText(segment.Text, segment.Color, Alignments.Left, _promptTemplateLabel.Font);
}

_ = _promptLabel.SizeToChildren();

_typewriting = ClientConfiguration.Instance.TypewriterEnabled &&
Globals.Database.TypewriterBehavior != TypewriterBehavior.Off;
if (_typewriting)
{
_promptLabel.ClearText();
_writer = new Typewriter(_dialog.Prompt ?? string.Empty, text => _promptLabel.AppendText(text, _promptTemplateLabel));
_writer = new Typewriter(parsedText.ToArray(), (text, color) =>
{
_promptLabel.AddText(text, color, Alignments.Left, _promptTemplateLabel.Font);
});
}

Defer(
() =>
{
Expand Down
56 changes: 39 additions & 17 deletions Intersect.Client.Core/Interface/Game/Typewriting/Typewriter.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
using Intersect.Client.Core;
using Intersect.Client.Utilities;
using Intersect.Configuration;
using Intersect.Utilities;

namespace Intersect.Client.Interface.Game.Typewriting;

internal sealed class Typewriter
{
public delegate void TextWrittenHandler(string text);

public delegate void TextWrittenHandler(string text, Color color);
private static HashSet<string> FullStops => ClientConfiguration.Instance.TypewriterFullStops;
private static long FullStopSpeed => ClientConfiguration.Instance.TypewriterFullStopDelay;
private static HashSet<string> PartialStops => ClientConfiguration.Instance.TypewriterPauses;
private static long PartialStopSpeed => ClientConfiguration.Instance.TypewriterPauseDelay;
private static long TypingSpeed => ClientConfiguration.Instance.TypewriterPartDelay;

private int _offset;
private int _segmentIndex = 0;
private string? _lastText;
private long _nextUpdateTime;

private readonly TextWrittenHandler _textWrittenHandler;
private readonly string _text;
private readonly ColorizedText[] _segments;

public bool IsDone { get; private set; }
public long DoneAtMilliseconds { get; private set; }

public Typewriter(string text, TextWrittenHandler textWrittenHandler)
public Typewriter(ColorizedText[] segments, TextWrittenHandler textWrittenHandler)
{
_text = text.ReplaceLineEndings("\n");
_segments = segments;
_textWrittenHandler = textWrittenHandler;
_nextUpdateTime = Timing.Global.MillisecondsUtc;

_segmentIndex = 0;
_offset = 0;
_lastText = null;

IsDone = false;
}

Expand All @@ -43,7 +43,7 @@ public void Write(string? soundName)
return;
}

if (_offset >= _text.Length)
if (_segmentIndex >= _segments.Length)
{
End();
return;
Expand All @@ -57,28 +57,44 @@ public void Write(string? soundName)
var emitSound = false;
while (_nextUpdateTime <= Timing.Global.MillisecondsUtc)
{
if (_offset >= _text.Length)
if (_segmentIndex >= _segments.Length)
{
End();
break;
return;
}

var segment = _segments[_segmentIndex];

if (_offset >= segment.Text.Length)
{
_offset = 0;
_segmentIndex++;

if (_segmentIndex >= _segments.Length)
{
End();
return;
}

segment = _segments[_segmentIndex];
}

emitSound |= _offset % ClientConfiguration.Instance.TypewriterSoundFrequency == 0;

string nextText;
if (char.IsSurrogatePair(_text, _offset))
if (char.IsSurrogatePair(segment.Text, _offset))
{
nextText = _text[_offset..(_offset + 2)];
nextText = segment.Text[_offset..(_offset + 2)];
_offset += 2;
}
else
{
nextText = _text[_offset..(_offset + 1)];
nextText = segment.Text[_offset..(_offset + 1)];
++_offset;
}

_nextUpdateTime += GetTypingDelayFor(nextText, _lastText);
_textWrittenHandler(nextText);
_textWrittenHandler(nextText, segment.Color);
_lastText = nextText;
}

Expand Down Expand Up @@ -118,14 +134,20 @@ private static long GetTypingDelayFor(string next, string? last)

public void End()
{
if (IsDone || _text.Length < 1)
if (IsDone)
{
return;
}

if (_offset < _text.Length)
while (_segmentIndex < _segments.Length)
{
_textWrittenHandler(_text[_offset..]);
var segment = _segments[_segmentIndex];
if (_offset < segment.Text.Length)
{
_textWrittenHandler(segment.Text.Substring(_offset), segment.Color);
}
_segmentIndex++;
_offset = 0;
}

IsDone = true;
Expand Down
69 changes: 69 additions & 0 deletions Intersect.Client.Core/Utilities/TextColorParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Reflection;
using System.Text.RegularExpressions;

namespace Intersect.Client.Utilities
{
public static class TextColorParser
{
private static readonly Dictionary<string, Func<Color>> KnownColors = InitializeColorDictionary();
private static readonly Regex ColorTagRegex = new(@"\\c{([^}]*)}", RegexOptions.Compiled);
private static readonly string SplitPattern = @"(\\c{[^}]*})";

private static Dictionary<string, Func<Color>> InitializeColorDictionary()
{
return typeof(Color)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(p => p.PropertyType == typeof(Color))
.ToDictionary(
p => p.Name.ToLowerInvariant(),
p => (Func<Color>)(() => p.GetMethod?.Invoke(null, null) as Color ?? Color.White)
);
}

public static List<ColorizedText> Parse(string text, Color defaultColor)
{
var output = new List<ColorizedText>();
var segments = Regex.Split(text, SplitPattern)
.Where(s => !string.IsNullOrWhiteSpace(s));

var currentColor = defaultColor;

foreach (var segment in segments)
{
var match = ColorTagRegex.Match(segment);

if (match.Success)
{
string colorCode = match.Groups[1].Value.ToLowerInvariant();

if (KnownColors.TryGetValue(colorCode, out Func<Color>? colorFunc))
{
currentColor = colorFunc();
}
else
{
currentColor = Color.FromHex(colorCode, defaultColor) ?? defaultColor;
}
}
else
{
output.Add(new ColorizedText(segment, currentColor));
}
}

return output;
}
}

public class ColorizedText
{
public string Text { get; }
public Color Color { get; }

public ColorizedText(string text, Color color)
{
Text = text;
Color = color;
}
}
}
Loading