diff --git a/HintServiceExample/Plugin.cs b/HintServiceExample/Plugin.cs index 3f34ad5..d4e0a7d 100644 --- a/HintServiceExample/Plugin.cs +++ b/HintServiceExample/Plugin.cs @@ -8,6 +8,7 @@ using Hint = HintServiceMeow.Core.Models.Hints.Hint; using MEC; using System.Collections.Generic; +using HintServiceMeow.UI.Utilities; namespace HintServiceExample { @@ -41,6 +42,11 @@ public static void OnVerified(VerifiedEventArgs ev) ShowHintB(ev.Player); ShowDynamicHintA(ev.Player); ShowCommonHintA(ev.Player); + + var ui = ev.Player.GetPlayerUi(); + ui.Style.SetStyle(-50, 1080, Style.StyleType.Italic); + ui.Style.SetColor(-50, 1080, UnityEngine.Color.green); + } //How to use Hint diff --git a/HintServiceMeow/Commands/GetCompatAssemblyName.cs b/HintServiceMeow/Commands/GetCompatAssemblyName.cs new file mode 100644 index 0000000..e7d2c5e --- /dev/null +++ b/HintServiceMeow/Commands/GetCompatAssemblyName.cs @@ -0,0 +1,33 @@ +using CommandSystem; +using HintServiceMeow.Core.Utilities; +using HintServiceMeow.Core.Utilities.Patch; +using System; + +namespace HintServiceMeow.Commands +{ + [CommandHandler(typeof(RemoteAdminCommandHandler))] + internal class GetCompatAssemblyName: ICommand + { + public string Command => "GetCompatAssemblyName"; + + public string[] Aliases => new string[] { "GCAN" }; + + public string Description => "Get the name of all the assemblies that are using Compatibility Adaptor in HintServiceMeow"; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + var sb = new System.Text.StringBuilder(); + + sb.AppendLine("The following assemblies are using Compatibility Adaptor in HintServiceMeow:"); + + foreach(var name in CompatibilityAdaptor.RegisteredAssemblies) + { + sb.Append("- "); + sb.AppendLine(name); + } + + response = sb.ToString(); + return true; + } + } +} diff --git a/HintServiceMeow/Core/Models/HintCollection.cs b/HintServiceMeow/Core/Models/HintCollection.cs index b5d4c11..63ec5cd 100644 --- a/HintServiceMeow/Core/Models/HintCollection.cs +++ b/HintServiceMeow/Core/Models/HintCollection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using HintServiceMeow.Core.Models.Hints; namespace HintServiceMeow.Core.Models @@ -12,44 +13,114 @@ namespace HintServiceMeow.Core.Models internal class HintCollection { private readonly Dictionary> _hintGroups = new Dictionary>(); + private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); - public IEnumerable> AllGroups => _hintGroups.Values; + public IEnumerable> AllGroups + { + get + { + _lock.EnterReadLock(); + try + { + return _hintGroups.Values.ToList(); + } + finally + { + _lock.ExitReadLock(); + } + } + } - public IEnumerable AllHints => _hintGroups.Values.SelectMany(x => x); + public IEnumerable AllHints + { + get + { + _lock.EnterReadLock(); + try + { + return _hintGroups.Values.SelectMany(x => x).ToList(); + } + finally + { + _lock.ExitReadLock(); + } + } + } public void AddHint(string assemblyName, AbstractHint hint) { - if (!_hintGroups.ContainsKey(assemblyName)) - _hintGroups[assemblyName] = new List(); + _lock.EnterWriteLock(); + try + { + if (!_hintGroups.ContainsKey(assemblyName)) + _hintGroups[assemblyName] = new List(); - _hintGroups[assemblyName].Add(hint); + _hintGroups[assemblyName].Add(hint); + } + finally + { + _lock.ExitWriteLock(); + } } public void RemoveHint(string assemblyName, AbstractHint hint) { - if (!_hintGroups.TryGetValue(assemblyName, out var hintList)) - return; + _lock.EnterWriteLock(); + try + { + if (!_hintGroups.TryGetValue(assemblyName, out var hintList)) + return; - hintList.Remove(hint); + hintList.Remove(hint); + } + finally + { + _lock.ExitWriteLock(); + } } public void RemoveHint(string assemblyName, Predicate predicate) { - if (!_hintGroups.TryGetValue(assemblyName, out var hintList)) - return; + _lock.EnterWriteLock(); + try + { + if (!_hintGroups.TryGetValue(assemblyName, out var hintList)) + return; - hintList.RemoveAll(predicate); + hintList.RemoveAll(predicate); + } + finally + { + _lock.ExitWriteLock(); + } } public void ClearHints(string assemblyName) { - if (_hintGroups.TryGetValue(assemblyName, out var hintList)) - hintList.Clear(); + _lock.EnterWriteLock(); + try + { + if (_hintGroups.TryGetValue(assemblyName, out var hintList)) + hintList.Clear(); + } + finally + { + _lock.ExitWriteLock(); + } } public IEnumerable GetHints(string assemblyName) { - return _hintGroups.TryGetValue(assemblyName, out var hintList) ? hintList : new List(); + _lock.EnterReadLock(); + try + { + return _hintGroups.TryGetValue(assemblyName, out var hintList) ? hintList.ToList() : new List(); + } + finally + { + _lock.ExitReadLock(); + } } } + } diff --git a/HintServiceMeow/Core/Models/Hints/AbstractHint.cs b/HintServiceMeow/Core/Models/Hints/AbstractHint.cs index 7b0aa18..ed9460b 100644 --- a/HintServiceMeow/Core/Models/Hints/AbstractHint.cs +++ b/HintServiceMeow/Core/Models/Hints/AbstractHint.cs @@ -3,11 +3,14 @@ using HintServiceMeow.Core.Models.HintContent.HintContent; using HintServiceMeow.Core.Utilities; using System; +using System.Threading; namespace HintServiceMeow.Core.Models.Hints { public abstract class AbstractHint { + protected ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + internal readonly UpdateAnalyser Analyser = new UpdateAnalyser(); private readonly Guid _guid = Guid.NewGuid(); @@ -43,159 +46,306 @@ protected AbstractHint() protected AbstractHint(AbstractHint hint) { - this._id = hint._id; - this._syncSpeed = hint._syncSpeed; - this._fontSize = hint._fontSize; - this._lineHeight = hint._lineHeight; - this._content = hint._content; - this._hide = hint._hide; + Lock.EnterWriteLock(); + try + { + this._id = hint._id; + this._syncSpeed = hint._syncSpeed; + this._fontSize = hint._fontSize; + this._lineHeight = hint._lineHeight; + this._content = hint._content; + this._hide = hint._hide; + } + finally + { + Lock.ExitWriteLock(); + } } #endregion #region Properties - /// - /// A random id created by the system - /// - public Guid Guid => _guid; + public Guid Guid + { + get + { + Lock.EnterReadLock(); + try + { + return _guid; + } + finally + { + Lock.ExitReadLock(); + } + } + } - /// - /// The id of the hint, used to indicate the hint in player display - /// - public string Id { get => _id; set => _id = value; } + public string Id + { + get + { + Lock.EnterReadLock(); + try + { + return _id; + } + finally + { + Lock.ExitReadLock(); + } + } + set + { + Lock.EnterWriteLock(); + try + { + _id = value; + } + finally + { + Lock.ExitWriteLock(); + } + } + } - /// - /// The sync speed of the hint. Higher speed means faster to sync. - /// Overly high speed might jam the updater. - /// public HintSyncSpeed SyncSpeed { - get => _syncSpeed; + get + { + Lock.EnterReadLock(); + try + { + return _syncSpeed; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_syncSpeed == value) - return; + Lock.EnterWriteLock(); + try + { + if (_syncSpeed == value) + return; - _syncSpeed = value; - OnHintUpdated(); + _syncSpeed = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } - /// - /// The height of the Font - /// public int FontSize { - get => _fontSize; + get + { + Lock.EnterReadLock(); + try + { + return _fontSize; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_fontSize == value) - return; + Lock.EnterWriteLock(); + try + { + if (_fontSize == value) + return; - _fontSize = value; - OnHintUpdated(); + _fontSize = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } - /// - /// The line height of the hint. Default line height is 0. - /// public float LineHeight { - get => _lineHeight; + get + { + Lock.EnterReadLock(); + try + { + return _lineHeight; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_lineHeight.Equals(value)) - return; + Lock.EnterWriteLock(); + try + { + if (_lineHeight.Equals(value)) + return; - _lineHeight = value; - OnHintUpdated(); + _lineHeight = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } - /// - /// Get or set the content of the hint - /// public AbstractHintContent Content { - get => _content; + get + { + Lock.EnterReadLock(); + try + { + return _content; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_content == value) - return; + Lock.EnterWriteLock(); + try + { + if (_content == value) + return; - _content = value; - _content.ContentUpdated += OnHintUpdated; - OnHintUpdated(); + _content = value; + _content.ContentUpdated += OnHintUpdated; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } - /// - /// Set the text displayed by the hint. This will override current content - /// public string Text { get { - if (Content is StringContent) + Lock.EnterReadLock(); + try { - return Content.GetText(); - } + if (Content is StringContent) + { + return Content.GetText(); + } - return null; + return null; + } + finally + { + Lock.ExitReadLock(); + } } set { - if (Content is StringContent textContent) + Lock.EnterWriteLock(); + try { - textContent.Text = value; + if (Content is StringContent textContent) + { + textContent.Text = value; + } + else + { + Content = new StringContent(value); + } + + OnHintUpdated(); } - else + finally { - Content = new StringContent(value); + Lock.ExitWriteLock(); } - - OnHintUpdated(); } } - /// - /// Set the auto text of the hint. This will override current content - /// public AutoContent.TextUpdateHandler AutoText { get { - if (Content is AutoContent content) + Lock.EnterReadLock(); + try { - return content.AutoText; - } + if (Content is AutoContent content) + { + return content.AutoText; + } - return null; + return null; + } + finally + { + Lock.ExitReadLock(); + } } set { - Content = new AutoContent(value); - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + Content = new AutoContent(value); + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } - /// - /// Whether this hint was hided - /// public bool Hide { - get => _hide; + get + { + Lock.EnterReadLock(); + try + { + return _hide; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_hide == value) - return; + Lock.EnterWriteLock(); + try + { + if (_hide == value) + return; - _hide = value; - OnHintUpdated(); + _hide = value; + OnHintUpdated(); - if(_hide) HintUpdated?.Invoke(this); + if (_hide) HintUpdated?.Invoke(this); + } + finally + { + Lock.ExitWriteLock(); + } } } @@ -212,7 +362,7 @@ protected void OnHintUpdated() { Analyser.OnUpdate(); - if (!Hide) + if (!_hide) { HintUpdated?.Invoke(this); } @@ -222,7 +372,7 @@ protected void OnHintUpdated() public class TextUpdateArg { - public ReferenceHub Player => PlayerDisplay?.ReferenceHub; + public ReferenceHub Player => PlayerDisplay?.ReferenceHub; public AbstractHint Hint { get; } public PlayerDisplay PlayerDisplay { get; } diff --git a/HintServiceMeow/Core/Models/Hints/CompatAdapterHint.cs b/HintServiceMeow/Core/Models/Hints/CompatAdapterHint.cs index 83bffe4..657a6f7 100644 --- a/HintServiceMeow/Core/Models/Hints/CompatAdapterHint.cs +++ b/HintServiceMeow/Core/Models/Hints/CompatAdapterHint.cs @@ -8,6 +8,5 @@ namespace HintServiceMeow.Core.Models.Hints { internal class CompatAdapterHint : Hint { - public CompatAdapterHint(): base() { } } } diff --git a/HintServiceMeow/Core/Models/Hints/DynamicHint.cs b/HintServiceMeow/Core/Models/Hints/DynamicHint.cs index 520dbf0..912b7ca 100644 --- a/HintServiceMeow/Core/Models/Hints/DynamicHint.cs +++ b/HintServiceMeow/Core/Models/Hints/DynamicHint.cs @@ -24,13 +24,21 @@ public DynamicHint() public DynamicHint(DynamicHint hint) : base(hint) { - this._topBoundary = hint._topBoundary; - this._bottomBoundary = hint._bottomBoundary; + Lock.EnterWriteLock(); + try + { + this._topBoundary = hint._topBoundary; + this._bottomBoundary = hint._bottomBoundary; - this._leftBoundary = hint._leftBoundary; - this._rightBoundary = hint._rightBoundary; + this._leftBoundary = hint._leftBoundary; + this._rightBoundary = hint._rightBoundary; - this._priority = hint._priority; + this._priority = hint._priority; + } + finally + { + Lock.ExitWriteLock(); + } } #endregion @@ -40,15 +48,33 @@ public DynamicHint(DynamicHint hint) : base(hint) /// public float TopBoundary { - get => _topBoundary; + get + { + Lock.EnterReadLock(); + try + { + return _topBoundary; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_topBoundary.Equals(value)) - return; - - _topBoundary = value; - - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_topBoundary.Equals(value)) + return; + + _topBoundary = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } @@ -57,15 +83,33 @@ public float TopBoundary /// public float BottomBoundary { - get => _bottomBoundary; + get + { + Lock.EnterReadLock(); + try + { + return _bottomBoundary; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_bottomBoundary.Equals(value)) - return; - - _bottomBoundary = value; - - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_bottomBoundary.Equals(value)) + return; + + _bottomBoundary = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } @@ -74,15 +118,33 @@ public float BottomBoundary /// public float LeftBoundary { - get => _leftBoundary; + get + { + Lock.EnterReadLock(); + try + { + return _leftBoundary; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_leftBoundary.Equals(value)) - return; - - _leftBoundary = value; - - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_leftBoundary.Equals(value)) + return; + + _leftBoundary = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } @@ -91,43 +153,97 @@ public float LeftBoundary /// public float RightBoundary { - get => _rightBoundary; + get + { + Lock.EnterReadLock(); + try + { + return _rightBoundary; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_rightBoundary.Equals(value)) - return; - - _rightBoundary = value; - - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_rightBoundary.Equals(value)) + return; + + _rightBoundary = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } public float TargetY { - get => _targetY; + get + { + Lock.EnterReadLock(); + try + { + return _targetY; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_targetY.Equals(value)) - return; - - _targetY = value; - - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_targetY.Equals(value)) + return; + + _targetY = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } public float TargetX { - get => _targetX; + get + { + Lock.EnterReadLock(); + try + { + return _targetX; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_targetX.Equals(value)) - return; - - _targetX = value; - - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_targetX.Equals(value)) + return; + + _targetX = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } @@ -136,28 +252,67 @@ public float TargetX /// public HintPriority Priority { - get => _priority; + get + { + Lock.EnterReadLock(); + try + { + return _priority; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_priority == value) - return; - - _priority = value; - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_priority == value) + return; + + _priority = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } public DynamicHintStrategy Strategy { - get => _strategy; + get + { + Lock.EnterReadLock(); + try + { + return _strategy; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_strategy == value) - return; - - _strategy = value; - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_strategy == value) + return; + + _strategy = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } } + } diff --git a/HintServiceMeow/Core/Models/Hints/Hint.cs b/HintServiceMeow/Core/Models/Hints/Hint.cs index e0b1c07..953a634 100644 --- a/HintServiceMeow/Core/Models/Hints/Hint.cs +++ b/HintServiceMeow/Core/Models/Hints/Hint.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using HintServiceMeow.Core.Enum; namespace HintServiceMeow.Core.Models.Hints @@ -34,15 +35,33 @@ public Hint() : base() public Hint(Hint hint) : base(hint) { - this.YCoordinate = hint.YCoordinate; - this.XCoordinate = hint.XCoordinate; + Lock.EnterWriteLock(); + try + { + this.YCoordinate = hint.YCoordinate; + this.XCoordinate = hint.XCoordinate; + this.Alignment = hint.Alignment; + this.YCoordinateAlign = hint.YCoordinateAlign; + } + finally + { + Lock.ExitWriteLock(); + } } internal Hint(DynamicHint hint, float x, float y) : base(hint) { - this.YCoordinate = y; - this.XCoordinate = x; - _yCoordinateAlign = HintVerticalAlign.Bottom; + Lock.EnterWriteLock(); + try + { + this.YCoordinate = y; + this.XCoordinate = x; + _yCoordinateAlign = HintVerticalAlign.Bottom; + } + finally + { + Lock.ExitWriteLock(); + } } #endregion @@ -52,17 +71,36 @@ internal Hint(DynamicHint hint, float x, float y) : base(hint) /// public float YCoordinate { - get => _yCoordinate; + get + { + Lock.EnterReadLock(); + try + { + return _yCoordinate; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_yCoordinate.Equals(value)) - return; - - value = Math.Max(0, value); - value = Math.Min(1080, value); - - _yCoordinate = value; - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_yCoordinate.Equals(value)) + return; + + value = Math.Max(0, value); + value = Math.Min(1080, value); + + _yCoordinate = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } @@ -72,17 +110,36 @@ public float YCoordinate /// public float XCoordinate { - get => _xCoordinate; + get + { + Lock.EnterReadLock(); + try + { + return _xCoordinate; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_xCoordinate.Equals(value)) - return; - - value = Math.Max(-1200, value); - value = Math.Min(1200, value); - - _xCoordinate = value; - OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_xCoordinate.Equals(value)) + return; + + value = Math.Max(-1200, value); + value = Math.Min(1200, value); + + _xCoordinate = value; + OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } @@ -91,14 +148,33 @@ public float XCoordinate /// public HintAlignment Alignment { - get => _alignment; + get + { + Lock.EnterReadLock(); + try + { + return _alignment; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_alignment == value) - return; - - _alignment = value; - if (!Hide) OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_alignment == value) + return; + + _alignment = value; + if (!Hide) OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } @@ -107,15 +183,35 @@ public HintAlignment Alignment /// public HintVerticalAlign YCoordinateAlign { - get => _yCoordinateAlign; + get + { + Lock.EnterReadLock(); + try + { + return _yCoordinateAlign; + } + finally + { + Lock.ExitReadLock(); + } + } set { - if (_yCoordinateAlign == value) - return; - - _yCoordinateAlign = value; - if (!Hide) OnHintUpdated(); + Lock.EnterWriteLock(); + try + { + if (_yCoordinateAlign == value) + return; + + _yCoordinateAlign = value; + if (!Hide) OnHintUpdated(); + } + finally + { + Lock.ExitWriteLock(); + } } } } + } \ No newline at end of file diff --git a/HintServiceMeow/Core/Utilities/HintParser.cs b/HintServiceMeow/Core/Utilities/HintParser.cs index 150388d..5457205 100644 --- a/HintServiceMeow/Core/Utilities/HintParser.cs +++ b/HintServiceMeow/Core/Utilities/HintParser.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; - +using System.Threading.Tasks; using HintServiceMeow.Core.Enum; using HintServiceMeow.Core.Models; using HintServiceMeow.Core.Models.Hints; @@ -15,69 +15,74 @@ namespace HintServiceMeow.Core.Utilities { - internal static class HintParser + internal class HintParser { - private static readonly StringBuilder MessageBuilder = new StringBuilder(ushort.MaxValue); //Used to build display text + private object _lock = new object(); - private static readonly StringBuilder RichTextBuilder = new StringBuilder(500); //Used to build rich text from Hint + private readonly StringBuilder _messageBuilder = new StringBuilder(ushort.MaxValue); //Used to build display text - private static readonly List> HintList = new List>(); + private readonly StringBuilder _richTextBuilder = new StringBuilder(500); //Used to build rich text from Hint - private static readonly Dictionary> _dynamicHintPositionCache = new Dictionary>(); + private readonly List> _hintList = new List>(); - public static string GetMessage(HintCollection collection) - { - HintList.Clear(); + private readonly Dictionary> _dynamicHintPositionCache = new Dictionary>(); - foreach (var group in collection.AllGroups) + public string GetMessage(HintCollection collection) + { + lock (_lock) { - List orderedList = new List(); + _hintList.Clear(); - //Convert to Hint - foreach (var item in group) + foreach (var group in collection.AllGroups) { - if (item.Hide || string.IsNullOrEmpty(item.Content.GetText())) - continue; + List orderedList = new List(); - if (item is Hint hint) - orderedList.Add(hint); - else if (item is DynamicHint dynamicHint) - orderedList.Add(ConvertDynamicHint(dynamicHint, orderedList)); - } + //Convert to Hint + foreach (var item in group) + { + if (item.Hide || string.IsNullOrEmpty(item.Content.GetText())) + continue; - //Sort by y coordinate and priority - orderedList.Sort((x,y) => x.YCoordinate.CompareTo(y.YCoordinate)); + if (item is Hint hint) + orderedList.Add(hint); + else if (item is DynamicHint dynamicHint) + orderedList.Add(ConvertDynamicHint(dynamicHint, orderedList)); + } - HintList.Add(orderedList); - } + //Sort by y coordinate and priority + orderedList.Sort((x, y) => CoordinateTools.GetActualYCoordinate(x, x.YCoordinateAlign).CompareTo(CoordinateTools.GetActualYCoordinate(y, y.YCoordinateAlign))); + + _hintList.Add(orderedList); + } - MessageBuilder.Clear(); - MessageBuilder.AppendLine("P");//Place Holder + _messageBuilder.Clear(); + _messageBuilder.AppendLine("P");//Place Holder - foreach (List hintList in HintList) - { - foreach(Hint hint in hintList) + foreach (List hintList in _hintList) { - var text = ToRichText(hint); - if (!string.IsNullOrEmpty(text)) - MessageBuilder.AppendLine(text); + foreach (Hint hint in hintList) + { + var text = ToRichText(hint); + if (!string.IsNullOrEmpty(text)) + _messageBuilder.Append(text);//ToRichText already added \n at the end + } + + _messageBuilder.AppendLine(""); //Make sure one group will not affect another group } - MessageBuilder.Append(""); //Make sure one group will not affect another group - } - - MessageBuilder.AppendLine("P");//Place Holder + _messageBuilder.AppendLine("P");//Place Holder - var result = MessageBuilder.ToString(); - MessageBuilder.Clear(); + var result = _messageBuilder.ToString(); + _messageBuilder.Clear(); - //var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{DateTime.Now.Ticks}.txt"); - //File.WriteAllText(path, result); + //var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{DateTime.Now.Ticks}.txt"); + //File.WriteAllText(path, result); - return result; + return result; + } } - public static Hint ConvertDynamicHint(DynamicHint dynamicHint, List hintList) + public Hint ConvertDynamicHint(DynamicHint dynamicHint, List hintList) { bool HasIntersection(float x, float y) { @@ -149,7 +154,7 @@ bool HasIntersection(float x, float y) return null; } - private static string ToRichText(Hint hint) + private string ToRichText(Hint hint) { //Remove Illegal Tags string rawText = hint.Content.GetText() ?? string.Empty; @@ -161,7 +166,7 @@ private static string ToRichText(Hint hint) text = Regex .Replace( text, - @"||||||{|}", + @"]*>|]*>|]*>|]*>|||{|}", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Compiled ); @@ -171,7 +176,7 @@ private static string ToRichText(Hint hint) text = Regex .Replace( text, - @"|||||||{|}", + @"]*>|]*>|]*>|]*>||||{|}", string.Empty, RegexOptions.IgnoreCase | RegexOptions.Compiled ); @@ -184,32 +189,32 @@ private static string ToRichText(Hint hint) var lineList = text.Split('\n'); float yOffset = 0; - RichTextBuilder.Clear(); + _richTextBuilder.Clear(); foreach (var line in lineList) { float xCoordinate = hint.XCoordinate; float yCoordinate = CoordinateTools.GetVOffset(hint) - yOffset; - if (xCoordinate != 0) RichTextBuilder.AppendFormat("", xCoordinate); - if (hint.Alignment != HintAlignment.Center) RichTextBuilder.AppendFormat("", hint.Alignment); - RichTextBuilder.Append(""); - if (yCoordinate != 0) RichTextBuilder.AppendFormat("", yCoordinate); - RichTextBuilder.AppendFormat("", hint.FontSize); + if (xCoordinate != 0) _richTextBuilder.AppendFormat("", xCoordinate); + if (hint.Alignment != HintAlignment.Center) _richTextBuilder.AppendFormat("", hint.Alignment); + _richTextBuilder.Append(""); + if (yCoordinate != 0) _richTextBuilder.AppendFormat("", yCoordinate); + _richTextBuilder.AppendFormat("", hint.FontSize); - RichTextBuilder.Append(line); + _richTextBuilder.Append(line); - RichTextBuilder.Append(""); - if (yCoordinate != 0) RichTextBuilder.Append(""); - if (hint.Alignment != HintAlignment.Center) RichTextBuilder.Append(""); - RichTextBuilder.AppendLine(); + _richTextBuilder.Append(""); + if (yCoordinate != 0) _richTextBuilder.Append(""); + if (hint.Alignment != HintAlignment.Center) _richTextBuilder.Append(""); + _richTextBuilder.AppendLine(); yOffset += hint.FontSize + hint.LineHeight; } - var result = RichTextBuilder.ToString(); + var result = _richTextBuilder.ToString(); - RichTextBuilder.Clear(); + _richTextBuilder.Clear(); return result; } diff --git a/HintServiceMeow/Core/Utilities/Patch/CompatAdapter .cs b/HintServiceMeow/Core/Utilities/Patch/CompatAdapter .cs index a6a2cad..4959fa0 100644 --- a/HintServiceMeow/Core/Utilities/Patch/CompatAdapter .cs +++ b/HintServiceMeow/Core/Utilities/Patch/CompatAdapter .cs @@ -6,27 +6,60 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using PluginAPI.Core; namespace HintServiceMeow.Core.Utilities.Patch { /// - /// Compatibility adapter for the other plugins + /// Compatibility adaptor design to adapt other plugins' hint system to HintServiceMeow's hint system /// - internal static class CompatibilityAdapter + internal static class CompatibilityAdaptor { + private static object _lock = new object(); + + internal static readonly HashSet RegisteredAssemblies = new HashSet(); //Include all the assembly names that used this adaptor + private static readonly Dictionary RemoveTime = new Dictionary(); + private static readonly Dictionary ConvertingTask = new Dictionary(); + private static readonly string SizeTagRegex = @""; private static readonly string LineHeightTagRegex = @""; private static readonly string AlignTagRegex = @"|"; + private static readonly string PosTagRegex = @""; + private static readonly Dictionary> HintCache = new Dictionary>(); + private static readonly Dictionary CancellationTokens = new Dictionary(); + public static void ShowHint(ReferenceHub player, string assemblyName, string content, float timeToRemove) { - assemblyName = "CompatibilityAdapter-" + assemblyName; + if (CancellationTokens.TryGetValue(assemblyName, out var token)) + { + token.Cancel(); + token.Dispose(); + } + + var cancellationTokenSource = new CancellationTokenSource(); + CancellationTokens[assemblyName] = cancellationTokenSource; + + _ = InternalShowHint(player, assemblyName, content, timeToRemove, cancellationTokenSource.Token); + } + + public static async Task InternalShowHint(ReferenceHub player, string assemblyName, string content, float timeToRemove, CancellationToken cancellationToken) + { + if (Plugin.Config.DisabledCompatAdapter.Contains(assemblyName)) + return; + + lock(_lock) + RegisteredAssemblies.Add(assemblyName); + + assemblyName = "CompatibilityAdaptor-" + assemblyName; var playerDisplay = PlayerDisplay.Get(player); @@ -37,85 +70,109 @@ public static void ShowHint(ReferenceHub player, string assemblyName, string con playerDisplay.InternalClearHint(assemblyName); //Set the time to remove - RemoveTime[assemblyName] = DateTime.Now.AddSeconds(timeToRemove); + lock (_lock) + RemoveTime[assemblyName] = DateTime.Now.AddSeconds(timeToRemove); //Check if the hint is already cached - if (HintCache.TryGetValue(content, out var cachedHintList)) + List cachedHintList; + + lock (_lock) + HintCache.TryGetValue(content, out cachedHintList); + + if(cachedHintList != null) { playerDisplay.InternalAddHint(assemblyName, cachedHintList); - Timing.CallDelayed(timeToRemove + 0.1f, () => + try { - if(playerDisplay != null && RemoveTime[assemblyName] <= DateTime.Now) + await Task.Delay(TimeSpan.FromSeconds(timeToRemove + 0.1f), cancellationToken); + + if (!cancellationToken.IsCancellationRequested && RemoveTime[assemblyName] <= DateTime.Now) playerDisplay.InternalClearHint(assemblyName); - }); + } + catch (TaskCanceledException) { } return; } - var textList = content.Split('\n'); - var positions = new List(); - - HeightResult heightResult = new HeightResult + var hintList = await Task.Run(() => { - Height = 0, - LastingFontTags = new Stack() - }; + var textList = content.Split('\n'); + var positions = new List(); - AlignmentResult alignmentResult = new AlignmentResult - { - Alignment = HintAlignment.Center, - LastingAlignment = HintAlignment.Center - }; + HeightResult heightResult = new HeightResult + { + Height = 0, + LastingFontTags = new Stack() + }; - foreach (var text in textList) - { - int lastingHeight = heightResult.LastingFontTags.Count == 0 ? 40 : (int)ParseSizeTag(Regex.Match(heightResult.LastingFontTags.Peek(), SizeTagRegex)); - heightResult = GetHeight(text, heightResult.LastingFontTags); - alignmentResult = GetAlignment(text, alignmentResult.LastingAlignment); + AlignmentResult alignmentResult = new AlignmentResult + { + Alignment = HintAlignment.Center, + LastingAlignment = HintAlignment.Center + }; - positions.Add(new TextPosition + foreach (var text in textList) { - Text = text, - Height = heightResult.Height, - Alignment = alignmentResult.Alignment, - FontSize = lastingHeight //Apply lasting height from last line - }); - } + int lastingHeight = heightResult.LastingFontTags.Count == 0 ? 40 : (int)ParseSizeTag(Regex.Match(heightResult.LastingFontTags.Peek(), SizeTagRegex)); + heightResult = GetHeight(text, heightResult.LastingFontTags); + alignmentResult = GetAlignment(text, alignmentResult.LastingAlignment); - var totalHeight = positions.Count > 0 ? positions.Sum(x => x.Height) : 0; - var accumulatedHeight = 0f; - - List hintList = new List(); + positions.Add(new TextPosition + { + Text = text, + Alignment = alignmentResult.Alignment, + Pos = GetPos(text), + Height = heightResult.Height, + FontSize = lastingHeight, + }); + } - foreach(var textPosition in positions) - { - var hint = new CompatAdapterHint + var totalHeight = positions.Count > 0 ? positions.Sum(x => x.Height) : 0; + var accumulatedHeight = 0f; + + List generatedHintList = new List(); + + foreach (var textPosition in positions) { - Text = textPosition.Text, - YCoordinate = 700 - totalHeight / 2 + textPosition.Height + accumulatedHeight, - YCoordinateAlign = HintVerticalAlign.Bottom, - Alignment = textPosition.Alignment, - FontSize = (int)textPosition.FontSize, - }; + var hint = new CompatAdapterHint + { + Text = textPosition.Text, + YCoordinate = 700 - totalHeight / 2 + textPosition.Height + accumulatedHeight, + YCoordinateAlign = HintVerticalAlign.Bottom, + Alignment = textPosition.Alignment, + FontSize = (int)textPosition.FontSize, + }; + + accumulatedHeight += textPosition.Height; + generatedHintList.Add(hint); + } - accumulatedHeight += textPosition.Height; - hintList.Add(hint); - } + return generatedHintList; + }, cancellationToken); + + if(cancellationToken.IsCancellationRequested) + return; playerDisplay.InternalAddHint(assemblyName, hintList); + + lock (_lock) + HintCache.Add(content, new List(hintList)); - HintCache.Add(content, new List(hintList)); - Timing.CallDelayed(10f, () => + _ = Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith(_ => { - HintCache.Remove(content); + lock (_lock) + HintCache.Remove(content); });//Cache expires in 10 seconds - Timing.CallDelayed(timeToRemove + 0.1f, () => + try { - if(playerDisplay != null && RemoveTime[assemblyName] <= DateTime.Now) - playerDisplay.InternalClearHint(assemblyName); - }); + await Task.Delay(TimeSpan.FromSeconds(timeToRemove + 0.1f), cancellationToken); + + if (playerDisplay != null && RemoveTime[assemblyName] <= DateTime.Now) + playerDisplay.InternalClearHint(assemblyName); + } + catch (TaskCanceledException) { } } //Return a value tuple, value 1 is height, value 2 is last font size @@ -221,6 +278,18 @@ private static AlignmentResult GetAlignment(string text, HintAlignment lastAlign return result; } + private static float GetPos(string text) + { + var match = Regex.Match(text, PosTagRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + if (match.Success) + { + return float.Parse(match.Groups[1].Value); + } + + return 0; + } + private static float ParseSizeTag(Match match) { var value = int.Parse(match.Groups[1].Value); @@ -280,6 +349,8 @@ private class TextPosition public HintAlignment Alignment { get; set; } + public float Pos { get; set; } + public float Height { get; set; } public float FontSize { get; set; } diff --git a/HintServiceMeow/Core/Utilities/Patch/Patch.cs b/HintServiceMeow/Core/Utilities/Patch/Patch.cs index 933813d..e5196aa 100644 --- a/HintServiceMeow/Core/Utilities/Patch/Patch.cs +++ b/HintServiceMeow/Core/Utilities/Patch/Patch.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Reflection; using HarmonyLib; using Hints; @@ -10,10 +7,9 @@ namespace HintServiceMeow.Core.Utilities.Patch { - [HarmonyPatch(typeof(HintDisplay), nameof(HintDisplay.Show))] internal static class HintDisplayPatch { - private static bool Prefix(ref Hint hint, ref HintDisplay __instance) + public static bool Prefix(ref Hint hint, ref HintDisplay __instance) { if (!PluginConfig.Instance.UseHintCompatibilityAdapter) return false; @@ -27,7 +23,7 @@ private static bool Prefix(ref Hint hint, ref HintDisplay __instance) var content = textHint.Text; var timeToRemove = textHint.DurationScalar; - CompatibilityAdapter.ShowHint(referenceHub, assemblyName, content, timeToRemove); + CompatibilityAdaptor.ShowHint(referenceHub, assemblyName, content, timeToRemove); } } catch(Exception ex) @@ -39,12 +35,32 @@ private static bool Prefix(ref Hint hint, ref HintDisplay __instance) } } + internal static class NWAPIHintPatch + { + public static bool Prefix(ref string text, ref float duration, ref ReferenceHub __instance) + { + if (!PluginConfig.Instance.UseHintCompatibilityAdapter) + return false; + + try + { + var assemblyName = Assembly.GetCallingAssembly().GetName().Name; + + CompatibilityAdaptor.ShowHint(__instance, assemblyName, text, duration); + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + + return false; + } + } + #if EXILED - [HarmonyPatch(typeof(Exiled.API.Features.Player), nameof(Exiled.API.Features.Player.ShowHint))] - [HarmonyPatch(new Type[] { typeof(string), typeof(int) })] internal static class ExiledHintPatch { - private static bool Prefix(ref string message, ref float duration, ref Exiled.API.Features.Player __instance) + public static bool Prefix(ref string message, ref float duration, ref Exiled.API.Features.Player __instance) { if (!PluginConfig.Instance.UseHintCompatibilityAdapter) return false; @@ -53,7 +69,7 @@ private static bool Prefix(ref string message, ref float duration, ref Exiled.AP { var assemblyName = Assembly.GetCallingAssembly().GetName().Name; - CompatibilityAdapter.ShowHint(__instance.ReferenceHub, assemblyName, message, duration); + CompatibilityAdaptor.ShowHint(__instance.ReferenceHub, assemblyName, message, duration); } catch (Exception ex) { diff --git a/HintServiceMeow/Core/Utilities/Patch/Patcher.cs b/HintServiceMeow/Core/Utilities/Patch/Patcher.cs index 2fdda56..d26cc67 100644 --- a/HintServiceMeow/Core/Utilities/Patch/Patcher.cs +++ b/HintServiceMeow/Core/Utilities/Patch/Patcher.cs @@ -18,18 +18,32 @@ public static void Patch() Harmony = new Harmony("HintServiceMeowHarmony" + Plugin.Version); //Unpatch all other patches - var methodInfo = typeof(HintDisplay).GetMethod(nameof(HintDisplay.Show)); - Harmony.Unpatch(methodInfo, HarmonyPatchType.All); -#if EXILED - var methodInfo2 = typeof(Exiled.API.Features.Player).GetMethod(nameof(Exiled.API.Features.Player.ShowHint), new Type[] { typeof(string), typeof(float) }); - var methodInfo3 = typeof(Exiled.API.Features.Player).GetMethod(nameof(Exiled.API.Features.Player.ShowHint), new Type[] { typeof(Exiled.API.Features.Hint) }); - - Harmony.Unpatch(methodInfo2, HarmonyPatchType.All); - Harmony.Unpatch(methodInfo3, HarmonyPatchType.All); -#endif + var hintDisplayMethod = typeof(HintDisplay).GetMethod(nameof(HintDisplay.Show)); + var receiveHintMethod1 = typeof(PluginAPI.Core.Player).GetMethod(nameof(PluginAPI.Core.Player.ReceiveHint), new Type[] { typeof(string), typeof(float) }); + var receiveHintMethod2 = typeof(PluginAPI.Core.Player).GetMethod(nameof(PluginAPI.Core.Player.ReceiveHint), new Type[] { typeof(string), typeof(HintEffect[]), typeof(float) }); + Harmony.Unpatch(hintDisplayMethod, HarmonyPatchType.All); + Harmony.Unpatch(receiveHintMethod1, HarmonyPatchType.All); + Harmony.Unpatch(receiveHintMethod2, HarmonyPatchType.All); // Patch the method - Harmony.PatchAll(); + var hintDisplayPatch = typeof(HintDisplayPatch).GetMethod(nameof(HintDisplayPatch.Prefix)); + var receiveHintPatch = typeof(NWAPIHintPatch).GetMethod(nameof(NWAPIHintPatch.Prefix)); + + Harmony.Patch(hintDisplayMethod, new HarmonyMethod(hintDisplayPatch)); + Harmony.Patch(receiveHintMethod1, new HarmonyMethod(receiveHintPatch)); + Harmony.Patch(receiveHintMethod2, new HarmonyMethod(receiveHintPatch)); + + //Exiled methods +#if EXILED + var showHintMethod1 = typeof(Exiled.API.Features.Player).GetMethod(nameof(Exiled.API.Features.Player.ShowHint), new Type[] { typeof(string), typeof(float) }); + var showHintMethod2 = typeof(Exiled.API.Features.Player).GetMethod(nameof(Exiled.API.Features.Player.ShowHint), new Type[] { typeof(Exiled.API.Features.Hint) }); + + Harmony.Unpatch(showHintMethod1, HarmonyPatchType.All); + Harmony.Unpatch(showHintMethod2, HarmonyPatchType.All); + + var exiledHintPatch = typeof(ExiledHintPatch).GetMethod(nameof(ExiledHintPatch.Prefix)); + Harmony.Patch(showHintMethod1, new HarmonyMethod(exiledHintPatch)); +#endif } public static void Unpatch() diff --git a/HintServiceMeow/Core/Utilities/PlayerDisplay.cs b/HintServiceMeow/Core/Utilities/PlayerDisplay.cs index 4542167..d32e503 100644 --- a/HintServiceMeow/Core/Utilities/PlayerDisplay.cs +++ b/HintServiceMeow/Core/Utilities/PlayerDisplay.cs @@ -12,11 +12,14 @@ using Log = PluginAPI.Core.Log; using HintServiceMeow.Core.Models; using System.Reflection; +using System.Threading.Tasks; namespace HintServiceMeow.Core.Utilities { public class PlayerDisplay { + private static readonly object PlayerDisplayLock = new object(); + /// /// Instance Trackers /// @@ -24,6 +27,8 @@ public class PlayerDisplay private static readonly TextHint HintTemplate = new TextHint("", new HintParameter[] { new StringHintParameter("") }, new HintEffect[] { HintEffectPresets.TrailingPulseAlpha(1, 1, 1) }, float.MaxValue); + private readonly object _hintLock = new object(); + /// /// Groups of hints shows to the ReferenceHub. First group is used for regular hints, reset of the groups are used for compatibility hints /// @@ -48,12 +53,18 @@ public class PlayerDisplay private static CoroutineHandle _updateCoroutine; + private Task _currentUpdateTask; + + private readonly HintParser _hintParser = new HintParser(); + #region Update Rate Management + private readonly object _rateDataLock = new object(); + /// /// The text that was sent to the client in latest sync /// - internal string _lastText = string.Empty; + private string _lastText = string.Empty; /// /// Contains all the hints that had been arranged to update. Make sure that a hint's update time will not be calculated for twice @@ -135,10 +146,13 @@ private TimeSpan UpdateCoolDown internal void OnHintUpdate(AbstractHint hint) { - if (_updatingHints.Contains(hint)) - return; + lock (_rateDataLock) + { + if (_updatingHints.Contains(hint)) + return; - _updatingHints.Add(hint); + _updatingHints.Add(hint); + } switch (hint.SyncSpeed) { @@ -149,13 +163,13 @@ internal void OnHintUpdate(AbstractHint hint) UpdateWhenAvailable(); break; case HintSyncSpeed.Normal: - ArrangeUpdate(TimeSpan.FromSeconds(0.3f), hint); + Task.Run(() => ArrangeUpdate(TimeSpan.FromSeconds(0.3f), hint)); break; case HintSyncSpeed.Slow: - ArrangeUpdate(TimeSpan.FromSeconds(1f), hint); + Task.Run(() => ArrangeUpdate(TimeSpan.FromSeconds(1f), hint)); break; case HintSyncSpeed.Slowest: - ArrangeUpdate(TimeSpan.FromSeconds(3f), hint); + Task.Run(() => ArrangeUpdate(TimeSpan.FromSeconds(3f), hint)); break; case HintSyncSpeed.UnSync: break; @@ -167,12 +181,15 @@ internal void OnHintUpdate(AbstractHint hint) /// private void UpdateWhenAvailable(bool useFastUpdate = false) { - TimeSpan timeToWait = useFastUpdate ? FastUpdateCoolDown : UpdateCoolDown; + lock (_rateDataLock) + { + TimeSpan timeToWait = useFastUpdate ? FastUpdateCoolDown : UpdateCoolDown; - var newPlanUpdateTime = DateTime.Now + timeToWait; + var newPlanUpdateTime = DateTime.Now + timeToWait; - if (_planUpdateTime > newPlanUpdateTime) - _planUpdateTime = newPlanUpdateTime; + if (_planUpdateTime > newPlanUpdateTime) + _planUpdateTime = newPlanUpdateTime; + } } /// @@ -184,17 +201,25 @@ private void ArrangeUpdate(TimeSpan maxDelay, AbstractHint hint) { var now = DateTime.Now; - //Find the latest estimated update time within maxDelay - IEnumerable estimatedTime = _hints.AllHints - .Where(x => x.SyncSpeed >= hint.SyncSpeed && x != hint) - .Select(x => x.Analyser.EstimateNextUpdate()) - .Where(x => x - now >= TimeSpan.Zero && x - now <= maxDelay) - .DefaultIfEmpty(DateTime.Now); + DateTime newTime; - DateTime newTime = estimatedTime.Max(); + lock (_hintLock) + { + //Find the latest estimated update time within maxDelay + newTime = _hints.AllHints + .Where(h => h.SyncSpeed >= hint.SyncSpeed && h != hint) + .Select(h => h.Analyser.EstimateNextUpdate()) + .Where(x => x - now >= TimeSpan.Zero && x - now <= maxDelay) + .DefaultIfEmpty(DateTime.Now) + .Max(); + + } - if (_arrangedUpdateTime > newTime) - _arrangedUpdateTime = newTime; + lock (_rateDataLock) + { + if (_arrangedUpdateTime > newTime) + _arrangedUpdateTime = newTime; + } } catch (Exception ex) { @@ -208,15 +233,21 @@ private static IEnumerator CoroutineMethod() { try { - foreach (PlayerDisplay pd in PlayerDisplayList) + lock (PlayerDisplayLock) { - //Force update - if (pd.NeedPeriodicUpdate) - pd.UpdateHint(true); - - //Invoke UpdateAvailable event - if (pd.UpdateReady) - pd.UpdateAvailable?.Invoke(new UpdateAvailableEventArg(pd)); + foreach (PlayerDisplay pd in PlayerDisplayList) + { + lock (pd._rateDataLock) + { + //Force update + if (pd.NeedPeriodicUpdate && pd._currentUpdateTask == null) + pd._currentUpdateTask = Task.Run(() => pd.ParseHint()); + + //Invoke UpdateAvailable event + if (pd.UpdateReady) + pd.UpdateAvailable?.Invoke(new UpdateAvailableEventArg(pd)); + } + } } } catch (Exception ex) @@ -236,20 +267,61 @@ private static IEnumerator UpdateCoroutineMethod() { DateTime now = DateTime.Now; - foreach (PlayerDisplay pd in PlayerDisplayList) + lock (PlayerDisplayLock) { - //Check arranged update time - if (now > pd._arrangedUpdateTime) - { - pd._arrangedUpdateTime = DateTime.MaxValue; - pd.UpdateWhenAvailable(false); - } - - //Update based on plan - if (now > pd._planUpdateTime) + foreach (PlayerDisplay pd in PlayerDisplayList) { - pd._planUpdateTime = DateTime.MaxValue; - pd.UpdateHint(); + lock (pd._rateDataLock) + { + //Check arranged update time + if (now > pd._arrangedUpdateTime) + { + pd._arrangedUpdateTime = DateTime.MaxValue; + pd.UpdateWhenAvailable(); + } + + //Update based on plan + if (now > pd._planUpdateTime) + { + //If last update complete, then start a new update. Otherwise, wait for the last update to complete + if (pd._currentUpdateTask == null) + { + //Reset Update Plan + pd._planUpdateTime = DateTime.MaxValue; + pd._updatingHints.Clear(); + + pd._currentUpdateTask = Task.Run(() => pd.ParseHint()); + } + } + } + + if (pd._currentUpdateTask != null && pd._currentUpdateTask.IsCompleted) + { + var text = pd._currentUpdateTask.GetAwaiter().GetResult(); + bool shouldUpdate = false; + + lock (pd._rateDataLock) + { + //Check whether the text had changed since last update or if this is a force update + if (text != pd._lastText || pd.NeedPeriodicUpdate) + { + //Update text record + pd._lastText = text; + + //Reset CountDown + pd._secondLastTimeUpdate = pd._lastTimeUpdate; + pd._lastTimeUpdate = DateTime.Now; + + shouldUpdate = true; + } + } + + if (shouldUpdate) + pd.SendHint(text); + + pd._currentUpdateTask.Dispose(); + pd._currentUpdateTask = null; + } } } } @@ -275,32 +347,26 @@ public void ForceUpdate(bool useFastUpdate = false) UpdateWhenAvailable(useFastUpdate); } - private void UpdateHint(bool isForceUpdate = false) + private string ParseHint() + { + lock (_hintLock) + return _hintParser.GetMessage(_hints); + } + + private void SendHint(string text) { try { - //Reset Update Plan - _planUpdateTime = DateTime.MaxValue; - _arrangedUpdateTime = DateTime.MaxValue; + HintMessage message; - _updatingHints.Clear(); - - string text = HintParser.GetMessage(_hints); - - //Check whether the text had changed since last update or if this is a force update - if (text == _lastText && !isForceUpdate) - return; - - //Update text record - _lastText = text; - - //Reset CountDown - _secondLastTimeUpdate = _lastTimeUpdate; - _lastTimeUpdate = DateTime.Now; + lock (_rateDataLock) + { + //Display the hint + HintTemplate.Text = text; + message = new HintMessage(HintTemplate); + } - //Display the hint - HintTemplate.Text = text; - ReferenceHub.connectionToClient.Send(new HintMessage(HintTemplate)); + ReferenceHub.connectionToClient.Send(message); } catch (Exception ex) { @@ -321,24 +387,30 @@ private PlayerDisplay(ReferenceHub referenceHub) if (!_updateCoroutine.IsRunning) _updateCoroutine = Timing.RunCoroutine(UpdateCoroutineMethod()); - PlayerDisplayList.Add(this); + lock (PlayerDisplayLock) + PlayerDisplayList.Add(this); } internal static PlayerDisplay TryCreate(ReferenceHub referenceHub) { - var pd = PlayerDisplayList.FirstOrDefault(x => x.ReferenceHub == referenceHub); + PlayerDisplay pd; + + lock (PlayerDisplayLock) + pd = PlayerDisplayList.FirstOrDefault(x => x.ReferenceHub == referenceHub); return pd ?? new PlayerDisplay(referenceHub); } internal static void Destruct(ReferenceHub referenceHub) { - PlayerDisplayList.RemoveWhere(x => x.ReferenceHub == referenceHub); + lock (PlayerDisplayLock) + PlayerDisplayList.RemoveWhere(x => x.ReferenceHub == referenceHub); } internal static void ClearInstance() { - PlayerDisplayList.Clear(); + lock (PlayerDisplayLock) + PlayerDisplayList.Clear(); } #endregion @@ -353,7 +425,10 @@ public static PlayerDisplay Get(ReferenceHub referenceHub) if (referenceHub is null) throw new Exception("A null ReferenceHub had been passed to Get method"); - var pd = PlayerDisplayList.FirstOrDefault(x => x.ReferenceHub == referenceHub); + PlayerDisplay pd; + + lock (PlayerDisplayLock) + pd = PlayerDisplayList.FirstOrDefault(x => x.ReferenceHub == referenceHub); return pd ?? new PlayerDisplay(referenceHub);//TryCreate ReferenceHub display if it has not been created yet } @@ -385,7 +460,8 @@ public void AddHint(AbstractHint hint) hint.HintUpdated += OnHintUpdate; UpdateAvailable += hint.TryUpdateHint; - _hints.AddHint(name, hint); + lock(_hintLock) + _hints.AddHint(name, hint); UpdateWhenAvailable(); } @@ -402,7 +478,8 @@ public void AddHint(IEnumerable hints) hint.HintUpdated += OnHintUpdate; UpdateAvailable += hint.TryUpdateHint; - _hints.AddHint(name, hint); + lock (_hintLock) + _hints.AddHint(name, hint); } UpdateWhenAvailable(); @@ -418,7 +495,8 @@ public void RemoveHint(AbstractHint hint) hint.HintUpdated -= OnHintUpdate; UpdateAvailable -= hint.TryUpdateHint; - _hints.RemoveHint(name, hint); + lock (_hintLock) + _hints.RemoveHint(name, hint); UpdateWhenAvailable(); } @@ -435,7 +513,8 @@ public void RemoveHint(IEnumerable hints) hint.HintUpdated -= OnHintUpdate; UpdateAvailable -= hint.TryUpdateHint; - _hints.RemoveHint(name, hint); + lock (_hintLock) + _hints.RemoveHint(name, hint); } UpdateWhenAvailable(); @@ -448,7 +527,8 @@ public void RemoveHint(string id) var name = Assembly.GetCallingAssembly().FullName; - _hints.RemoveHint(name, x => x.Id.Equals(id)); + lock (_hintLock) + _hints.RemoveHint(name, x => x.Id.Equals(id)); UpdateWhenAvailable(); } @@ -457,7 +537,8 @@ public void ClearHint() { var name = Assembly.GetCallingAssembly().FullName; - _hints.ClearHints(name); + lock (_hintLock) + _hints.ClearHints(name); UpdateWhenAvailable(); } @@ -469,21 +550,26 @@ public AbstractHint GetHint(string id) var name = Assembly.GetCallingAssembly().FullName; - return _hints.GetHints(name).FirstOrDefault(x => x.Id == id); + lock (_hintLock) + return _hints.GetHints(name).FirstOrDefault(x => x.Id == id); } internal void InternalAddHint(string name, AbstractHint hint) { - _hints.AddHint(name, hint); + lock (_hintLock) + _hints.AddHint(name, hint); UpdateWhenAvailable(); } internal void InternalAddHint(string name, IEnumerable hints) { - foreach(var hint in hints) + lock (_hintLock) { - _hints.AddHint(name, hint); + foreach (var hint in hints) + { + _hints.AddHint(name, hint); + } } UpdateWhenAvailable(); @@ -491,7 +577,8 @@ internal void InternalAddHint(string name, IEnumerable hints) internal void InternalRemoveHint(string name, AbstractHint hint) { - _hints.RemoveHint(name, hint); + lock (_hintLock) + _hints.RemoveHint(name, hint); UpdateWhenAvailable(); } @@ -500,7 +587,8 @@ internal void InternalRemoveHint(string name, IEnumerable hints) { foreach (var hint in hints) { - _hints.RemoveHint(name, hint); + lock (_hintLock) + _hints.RemoveHint(name, hint); } UpdateWhenAvailable(); @@ -508,7 +596,8 @@ internal void InternalRemoveHint(string name, IEnumerable hints) internal void InternalClearHint(string name) { - _hints.ClearHints(name); + lock (_hintLock) + _hints.ClearHints(name); } #endregion diff --git a/HintServiceMeow/Core/Utilities/UpdateAnalyser.cs b/HintServiceMeow/Core/Utilities/UpdateAnalyser.cs index 7a9ec81..dc5a223 100644 --- a/HintServiceMeow/Core/Utilities/UpdateAnalyser.cs +++ b/HintServiceMeow/Core/Utilities/UpdateAnalyser.cs @@ -10,6 +10,8 @@ namespace HintServiceMeow.Core.Utilities { internal class UpdateAnalyser { + private object _lock = new object(); + private readonly TimeSpan _leastInterval = TimeSpan.FromMilliseconds(50f); //For update estimation @@ -17,74 +19,80 @@ internal class UpdateAnalyser public void OnUpdate() { - var now = DateTime.Now; + lock (_lock) + { + var now = DateTime.Now; - //Check if the interval is too short - if (!_updateTimestamps.IsEmpty() && now - _updateTimestamps.Last() < _leastInterval) - return; + //Check if the interval is too short + if (!_updateTimestamps.IsEmpty() && now - _updateTimestamps.Last() < _leastInterval) + return; - //Add timestamp and remove outdated ones - _updateTimestamps.Add(now); - _updateTimestamps.RemoveAll(x => now - x > TimeSpan.FromSeconds(60)); + //Add timestamp and remove outdated ones + _updateTimestamps.Add(now); + _updateTimestamps.RemoveAll(x => now - x > TimeSpan.FromSeconds(60)); + } } public DateTime EstimateNextUpdate() { - if (_updateTimestamps.Count < 2) - { - return DateTime.MaxValue; - } - - var now = DateTime.Now; - long nextTimestamp = long.MaxValue; - - _updateTimestamps.Add(now); - - try + lock (_lock) { - //Calculate interval - List intervals = new List(); - for (int i = 1; i < _updateTimestamps.Count; i++) + if (_updateTimestamps.Count < 2) { - intervals.Add(_updateTimestamps[i].Ticks - _updateTimestamps[i - 1].Ticks); + return DateTime.MaxValue; } - //Prepare data - long[] xData = new long[intervals.Count]; - long[] yData = intervals.ToArray(); - for (int i = 0; i < xData.Length; i++) + var now = DateTime.Now; + long nextTimestamp = long.MaxValue; + + _updateTimestamps.Add(now); + + try { - xData[i] = i; - } + //Calculate interval + List intervals = new List(); + for (int i = 1; i < _updateTimestamps.Count; i++) + { + intervals.Add(_updateTimestamps[i].Ticks - _updateTimestamps[i - 1].Ticks); + } - // Linear regression - int n = xData.Length; - float sumX = xData.Sum(); - float sumY = yData.Sum(); - float sumXY = 0; - float sumX2 = 0; + //Prepare data + long[] xData = new long[intervals.Count]; + long[] yData = intervals.ToArray(); + for (int i = 0; i < xData.Length; i++) + { + xData[i] = i; + } - for (int i = 0; i < n; i++) + // Linear regression + int n = xData.Length; + float sumX = xData.Sum(); + float sumY = yData.Sum(); + float sumXY = 0; + float sumX2 = 0; + + for (int i = 0; i < n; i++) + { + sumXY += xData[i] * yData[i]; + sumX2 += xData[i] * xData[i]; + } + + float slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); + float intercept = (sumY - slope * sumX) / n; + + float nextInterval = slope * n + intercept; + + nextTimestamp = _updateTimestamps.Last().Ticks + (long)nextInterval; + } + catch (Exception ex) { - sumXY += xData[i] * yData[i]; - sumX2 += xData[i] * xData[i]; + Log.Error(ex.ToString()); } - float slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX); - float intercept = (sumY - slope * sumX) / n; + _updateTimestamps.Remove(now); - float nextInterval = slope * n + intercept; - - nextTimestamp = _updateTimestamps.Last().Ticks + (long)nextInterval; - } - catch (Exception ex) - { - Log.Error(ex.ToString()); + return new DateTime(nextTimestamp); } - - _updateTimestamps.Remove(now); - - return new DateTime(nextTimestamp); } } } diff --git a/HintServiceMeow/HintServiceMeow.csproj b/HintServiceMeow/HintServiceMeow.csproj index c1e0bb4..b2885f1 100644 --- a/HintServiceMeow/HintServiceMeow.csproj +++ b/HintServiceMeow/HintServiceMeow.csproj @@ -358,6 +358,7 @@ + @@ -386,6 +387,7 @@ + diff --git a/HintServiceMeow/Plugin/Config/PluginConfig.cs b/HintServiceMeow/Plugin/Config/PluginConfig.cs index 098812d..d6f7687 100644 --- a/HintServiceMeow/Plugin/Config/PluginConfig.cs +++ b/HintServiceMeow/Plugin/Config/PluginConfig.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System.Collections.Generic; +using System.ComponentModel; namespace HintServiceMeow { @@ -12,6 +13,9 @@ internal class PluginConfig [Description("By using this feature, it might make plugin that is imcompatible with HintServiceMeow compatible. This is a experimental feature")] public bool UseHintCompatibilityAdapter { get; set; } = true; + [Description("The assembly that you do not want to included in the Compatibility Adapter. Use command GetCompatAssemblyName to get the name of all the assemblies")] + public List DisabledCompatAdapter { get; set; } = new List(); + [Description("The for CommonHint. CommonHint contains commonly used hints")] public PlayerUIConfig PlayerUIConfig { get; set; } = new PlayerUIConfig(); } diff --git a/HintServiceMeow/Plugin/Plugin.cs b/HintServiceMeow/Plugin/Plugin.cs index 33baa7e..8a16847 100644 --- a/HintServiceMeow/Plugin/Plugin.cs +++ b/HintServiceMeow/Plugin/Plugin.cs @@ -94,6 +94,10 @@ // * Add more methods to player display // * V5.2.5 // * Fix the problem that the compatibility adapter's cache might cause high memory usage +// * V5.3.0 +// * Add multi-thread support for Core functions +// * Add pos tag support in compatibility adapter +// * Add Style component in PlayerUI namespace HintServiceMeow { @@ -179,7 +183,7 @@ internal class NwapiPlugin : IPlugin public PluginType Type => PluginType.Exiled; public PluginConfig PluginConfig => null;//NW somehow cannot serialize the config for HintServiceMeow - [PluginEntryPoint("HintServiceMeow", "5.2.5", "A hint framework", "MeowServerOwner")] + [PluginEntryPoint("HintServiceMeow", "5.3.0", "A hint framework", "MeowServerOwner")] public void LoadPlugin() { Plugin.OnEnabled(this); @@ -230,7 +234,7 @@ internal static class Plugin { public static string Name => "HintServiceMeow"; public static string Author => "MeowServer"; - public static Version Version => new Version(5, 2, 5); + public static Version Version => new Version(5, 3, 0); public static PluginConfig Config = new PluginConfig();//Initialize if fail to initialize diff --git a/HintServiceMeow/Properties/AssemblyInfo.cs b/HintServiceMeow/Properties/AssemblyInfo.cs index be5dab0..bcb1dad 100644 --- a/HintServiceMeow/Properties/AssemblyInfo.cs +++ b/HintServiceMeow/Properties/AssemblyInfo.cs @@ -6,9 +6,9 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("HintServiceMeow")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("A hint framework for SCP:SL")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("MeowServer~")] [assembly: AssemblyProduct("HintServiceMeow")] [assembly: AssemblyCopyright("Copyright © 2024")] [assembly: AssemblyTrademark("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyVersion("5.2.4.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/HintServiceMeow/UI/Utilities/CommonHint.cs b/HintServiceMeow/UI/Utilities/CommonHint.cs index f266805..81e19da 100644 --- a/HintServiceMeow/UI/Utilities/CommonHint.cs +++ b/HintServiceMeow/UI/Utilities/CommonHint.cs @@ -7,6 +7,7 @@ using HintServiceMeow.Core.Utilities; using HintServiceMeow.UI.Utilities; using PluginAPI.Core; +using System.Diagnostics; namespace HintServiceMeow.UI.Utilities { @@ -14,10 +15,38 @@ public class CommonHint { private static PlayerUIConfig Config => PluginConfig.Instance.PlayerUIConfig; - public readonly ReferenceHub ReferenceHub; - public PlayerUI PlayerUI => PlayerUI.Get(ReferenceHub); - public PlayerDisplay PlayerDisplay => PlayerDisplay.Get(ReferenceHub); - + private readonly ReferenceHub ReferenceHub; + private PlayerDisplay PlayerDisplay => PlayerDisplay.Get(ReferenceHub); + + #region Constructor and Destructors Methods + + internal CommonHint(ReferenceHub referenceHub) + { + this.ReferenceHub = referenceHub; + + //Add hint + PlayerDisplay.AddHint(_itemHints); + PlayerDisplay.AddHint(_mapHints); + PlayerDisplay.AddHint(_roleHints); + + //Start coroutine + _commonHintUpdateCoroutine = Timing.RunCoroutine(CommonHintCoroutineMethod()); + } + + internal void Destruct() + { + if (_commonHintUpdateCoroutine.IsRunning) + { + Timing.KillCoroutines(_commonHintUpdateCoroutine); + } + + PlayerDisplay.RemoveHint(_itemHints); + PlayerDisplay.RemoveHint(_mapHints); + PlayerDisplay.RemoveHint(_roleHints); + } + + #endregion + #region Common Hints private CoroutineHandle _commonHintUpdateCoroutine; @@ -197,39 +226,11 @@ public void ShowOtherHint(string[] messages, float time) }); } } + #endregion Common Other Hints Methods #endregion Common Hint Methods - #region Constructor and Destructors Methods - - internal CommonHint(ReferenceHub referenceHub) - { - this.ReferenceHub = referenceHub; - - //Add hint - PlayerDisplay.AddHint(_itemHints); - PlayerDisplay.AddHint(_mapHints); - PlayerDisplay.AddHint(_roleHints); - - //Start coroutine - _commonHintUpdateCoroutine = Timing.RunCoroutine(CommonHintCoroutineMethod()); - } - - internal void Destruct() - { - if (_commonHintUpdateCoroutine.IsRunning) - { - Timing.KillCoroutines(_commonHintUpdateCoroutine); - } - - PlayerDisplay.RemoveHint(_itemHints); - PlayerDisplay.RemoveHint(_mapHints); - PlayerDisplay.RemoveHint(_roleHints); - } - - #endregion - # region Private Common Hints Methods private IEnumerator CommonHintCoroutineMethod() { diff --git a/HintServiceMeow/UI/Utilities/PlayerUI.cs b/HintServiceMeow/UI/Utilities/PlayerUI.cs index 566fe93..7598e32 100644 --- a/HintServiceMeow/UI/Utilities/PlayerUI.cs +++ b/HintServiceMeow/UI/Utilities/PlayerUI.cs @@ -9,10 +9,14 @@ public class PlayerUI { private static readonly HashSet PlayerUIList = new HashSet(); - public readonly ReferenceHub ReferenceHub; - public readonly PlayerDisplay PlayerDisplay; + private object _lock = new object(); - public CommonHint CommonHint { get; private set; } + public ReferenceHub ReferenceHub { get; } + public PlayerDisplay PlayerDisplay { get; } + + public CommonHint CommonHint { get; } + + public Style Style { get; } #region Constructor and Destructors Methods @@ -24,6 +28,7 @@ private PlayerUI(ReferenceHub referenceHub) //Initialize Components CommonHint = new CommonHint(referenceHub); + this.Style = new Style(referenceHub); //Add to list PlayerUIList.Add(this); diff --git a/HintServiceMeow/UI/Utilities/Style.cs b/HintServiceMeow/UI/Utilities/Style.cs new file mode 100644 index 0000000..d71a9ba --- /dev/null +++ b/HintServiceMeow/UI/Utilities/Style.cs @@ -0,0 +1,276 @@ +using HintServiceMeow.Core.Utilities; +using System; +using System.Collections.Generic; +using System.Reflection; +using HintServiceMeow.Core.Models.Hints; +using UnityEngine; +using System.Collections.ObjectModel; +using System.Linq; + +namespace HintServiceMeow.UI.Utilities +{ + public class Style + { + private Dictionary> _currentStyleHints = new Dictionary>(); + + private const string StyleHintSuffix = "BoldGroup"; + + private static readonly Hint BoldStartHint = new Hint() + { + Text = "", + FontSize = 0, + YCoordinateAlign = Core.Enum.HintVerticalAlign.Bottom + }; + + private static readonly Hint BoldEndHint = new Hint() + { + Text = "", + FontSize = 0, + YCoordinateAlign = Core.Enum.HintVerticalAlign.Bottom + }; + + private static readonly Hint ItalicStartHint = new Hint() + { + Text = "", + FontSize = 0, + YCoordinateAlign = Core.Enum.HintVerticalAlign.Bottom + }; + + private static readonly Hint ItalicEndHint = new Hint() + { + Text = "", + FontSize = 0, + YCoordinateAlign = Core.Enum.HintVerticalAlign.Bottom + }; + + private ReferenceHub ReferenceHub { get; set; } + private PlayerDisplay PlayerDisplay => PlayerDisplay.Get(ReferenceHub); + + private readonly Dictionary> _boldArea = new Dictionary>(); + private readonly Dictionary> _italicArea = new Dictionary>(); + + private readonly Dictionary> _colorArea = new Dictionary>(); + + [Flags] + public enum StyleType + { + None = 0b00, + Bold = 0b01, + Italic = 0b10, + BoldItalic = 0b11 + } + + internal Style(ReferenceHub referenceHub) + { + ReferenceHub = referenceHub; + } + + public void SetStyle(float topY, float bottomY, StyleType style) + { + var name = Assembly.GetCallingAssembly().FullName; + + TryInitialize(name); + + var area = new Area() + { + TopY = topY, + BottomY = bottomY + }; + + if((style & StyleType.Bold) != 0) + { + AddArea(area, _boldArea[name]); + } + else + { + RemoveArea(area, _boldArea[name]); + } + + if((style & StyleType.Italic) != 0) + { + AddArea(area, _italicArea[name]); + } + else + { + RemoveArea(area, _italicArea[name]); + } + + UpdateHint(name); + } + + public void SetColor(float topY, float bottomY, Color color) + { + var name = Assembly.GetCallingAssembly().FullName; + + TryInitialize(name); + + var area = new ColorArea() + { + TopY = topY, + BottomY = bottomY, + Color = color + }; + + RemoveArea(area, _colorArea[name].Select(x => (Area)x).ToList()); + AddArea(area, _colorArea[name].Select(x => (Area)x).ToList()); + + UpdateHint(name); + } + + private void TryInitialize(string name) + { + if(!_boldArea.ContainsKey(name)) + { + _boldArea.Add(name, new List()); + } + + if(!_italicArea.ContainsKey(name)) + { + _italicArea.Add(name, new List()); + } + + if(!_colorArea.ContainsKey(name)) + { + _colorArea.Add(name, new List()); + } + + if(!_currentStyleHints.ContainsKey(name)) + { + _currentStyleHints.Add(name, new List()); + } + } + + private void AddArea(Area areaToAdd, List existingArea) + { + existingArea.Add(areaToAdd); + existingArea.Sort((x, y) => x.TopY.CompareTo(y.TopY)); + + for(int i = 0; i < existingArea.Count - 1; i++) + { + if(IsIntersected(existingArea[i], existingArea[i + 1])) + { + existingArea[i].BottomY = Math.Max(existingArea[i].BottomY, existingArea[i + 1].BottomY); + existingArea.RemoveAt(i + 1); + i--; + } + } + + } + + private void RemoveArea(Area areaToRemove, List existingArea) + { + foreach(Area area in existingArea) + { + if(IsIntersected(area, areaToRemove)) + { + if(area.TopY > areaToRemove.TopY) + { + area.TopY = areaToRemove.BottomY; + } + + if(area.BottomY < areaToRemove.BottomY) + { + area.BottomY = areaToRemove.TopY; + } + } + } + + existingArea.RemoveAll(area => area.TopY >= area.BottomY || area.BottomY - area.TopY <= 0.1); + } + + private void UpdateHint(string assemblyName) + { + foreach(var hint in _currentStyleHints[assemblyName]) + { + PlayerDisplay.InternalRemoveHint(assemblyName, hint); + } + + foreach(var area in _boldArea[assemblyName]) + { + var startHint = new Hint(BoldStartHint) + { + YCoordinate = area.TopY - 0.01f + }; + var stopHint = new Hint(BoldEndHint) + { + YCoordinate = area.BottomY + 0.01f + }; + + PlayerDisplay.InternalAddHint(assemblyName, startHint); + PlayerDisplay.InternalAddHint(assemblyName, stopHint); + + _currentStyleHints[assemblyName].Add(startHint); + _currentStyleHints[assemblyName].Add(stopHint); + } + + foreach(var area in _italicArea[assemblyName]) + { + var startHint = new Hint(ItalicStartHint) + { + YCoordinate = area.TopY - 0.01f + }; + var stopHint = new Hint(ItalicEndHint) + { + YCoordinate = area.BottomY + 0.01f + }; + + PlayerDisplay.InternalAddHint(assemblyName, startHint); + PlayerDisplay.InternalAddHint(assemblyName, stopHint); + + _currentStyleHints[assemblyName].Add(startHint); + _currentStyleHints[assemblyName].Add(stopHint); + } + + foreach(var area in _colorArea[assemblyName]) + { + var startHint = GetColorStartHint(area.Color); + var stopHint = GetColorEndHint(); + + startHint.YCoordinate = area.TopY - 0.01f; + stopHint.YCoordinate = area.BottomY + 0.01f; + + PlayerDisplay.InternalAddHint(assemblyName, startHint); + PlayerDisplay.InternalAddHint(assemblyName, stopHint); + + _currentStyleHints[assemblyName].Add(startHint); + _currentStyleHints[assemblyName].Add(stopHint); + } + } + + private static Hint GetColorStartHint(Color color) + { + return new Hint() + { + Text = $"", + FontSize = 0, + YCoordinateAlign = Core.Enum.HintVerticalAlign.Bottom + }; + } + + private static Hint GetColorEndHint() + { + return new Hint() + { + Text = "", + FontSize = 0, + YCoordinateAlign = Core.Enum.HintVerticalAlign.Bottom + }; + } + + private bool IsIntersected(Area area1, Area area2) + { + return area1.TopY <= area2.BottomY && area1.BottomY >= area2.TopY; + } + + private class Area + { + public float TopY { get; set; } + public float BottomY { get; set; } + } + + private class ColorArea : Area + { + public Color Color { get; set; } + } + } +} \ No newline at end of file