-
Notifications
You must be signed in to change notification settings - Fork 24
Визуальные улучшения #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Визуальные улучшения #112
Changes from all commits
dcf21d1
6e051f2
1b32c32
79c38cf
7f029db
3c5b70d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| using System.Numerics; | ||
| using Content.Client.Chat.Managers; | ||
| using System.Text; // Arcane | ||
| using Content.Shared.CCVar; | ||
| using Content.Shared.Chat; | ||
| using Content.Shared.Speech; | ||
|
|
@@ -38,6 +39,13 @@ public enum SpeechType : byte | |
| /// </summary> | ||
| private static readonly TimeSpan FadeTime = TimeSpan.FromSeconds(0.25f); | ||
|
|
||
| // Arcane-start | ||
| private const float RevealRunesPerSecond = 15.5f; | ||
| private const float SpaceRevealWeight = 2.25f; | ||
| private static readonly TimeSpan MaxRevealTime = TimeSpan.FromSeconds(4); | ||
| protected virtual float RevealSpeedMultiplier => 1f; | ||
| // Arcane-end | ||
|
|
||
| /// <summary> | ||
| /// The distance in world space to offset the speech bubble from the center of the entity. | ||
| /// i.e. greater -> higher above the mob's head. | ||
|
|
@@ -55,6 +63,12 @@ public enum SpeechType : byte | |
| /// The time at which this bubble will die. | ||
| /// </summary> | ||
| private TimeSpan _deathTime; | ||
| // Arcane-start | ||
| private readonly TimeSpan _creationTime; | ||
| private readonly TimeSpan _revealTime; | ||
| private readonly float _maxRevealWeight; | ||
| private readonly List<SpeechTextReveal> _textReveals = new(); | ||
| // Arcane-end | ||
|
|
||
| public float VerticalOffset { get; set; } | ||
| private float _verticalOffsetAchieved; | ||
|
|
@@ -95,15 +109,27 @@ public SpeechBubble(ChatMessage message, EntityUid senderEntity, string speechSt | |
| RectClipContent = true; | ||
|
|
||
| var bubble = BuildBubble(message, speechStyleClass, fontColor); | ||
| // Arcane-start | ||
| bubble.HorizontalAlignment = HAlignment.Center; | ||
| bubble.VerticalAlignment = VAlignment.Bottom; | ||
| // Arcane-end | ||
|
|
||
| AddChild(bubble); | ||
|
|
||
| ForceRunStyleUpdate(); | ||
|
|
||
| bubble.Measure(Vector2Helpers.Infinity); | ||
| ContentSize = bubble.DesiredSize; | ||
| // Arcane-start | ||
| bubble.MinWidth = ContentSize.X; | ||
| _creationTime = _timing.RealTime; | ||
| _maxRevealWeight = GetMaxRevealWeight(); | ||
| _revealTime = GetRevealTime(_maxRevealWeight); | ||
| _deathTime = _creationTime + TotalTime + _revealTime; | ||
| UpdateTextReveal(); | ||
| // Arcane-end | ||
| _verticalOffsetAchieved = -ContentSize.Y; | ||
| _deathTime = _timing.RealTime + TotalTime; | ||
|
fox76055 marked this conversation as resolved.
|
||
| // _deathTime = _timing.RealTime + TotalTime; Arcane delete | ||
| } | ||
|
|
||
| protected abstract Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null); | ||
|
|
@@ -120,6 +146,10 @@ protected override void FrameUpdate(FrameEventArgs args) | |
| return; | ||
| } | ||
|
|
||
| // Arcane-start | ||
| UpdateTextReveal(); | ||
| // Arcane-end | ||
|
|
||
| // Lerp to our new vertical offset if it's been modified. | ||
| if (MathHelper.CloseToPercent(_verticalOffsetAchieved - VerticalOffset, 0, 0.1)) | ||
| { | ||
|
|
@@ -175,6 +205,173 @@ private void Die() | |
| OnDied?.Invoke(_senderEntity, this); | ||
| } | ||
|
|
||
| // Arcane-start | ||
| protected void SetRevealedMessage(RichTextLabel label, FormattedMessage message) | ||
| { | ||
| label.SetMessage(message); | ||
|
|
||
| var revealWeight = CountRevealWeight(message); | ||
| if (revealWeight <= 0f) | ||
| return; | ||
|
|
||
| _textReveals.Add(new SpeechTextReveal(label, message, revealWeight)); | ||
| } | ||
|
|
||
| private float GetMaxRevealWeight() | ||
| { | ||
| var revealWeight = 0f; | ||
|
|
||
| foreach (var reveal in _textReveals) | ||
| { | ||
| revealWeight = Math.Max(revealWeight, reveal.RevealWeight); | ||
| } | ||
|
|
||
| return revealWeight; | ||
| } | ||
|
|
||
| private TimeSpan GetRevealTime(float revealWeight) | ||
| { | ||
| if (revealWeight <= 0f) | ||
| return TimeSpan.Zero; | ||
|
|
||
| var seconds = revealWeight / (RevealRunesPerSecond * RevealSpeedMultiplier); | ||
| return TimeSpan.FromSeconds(MathF.Min(seconds, (float) MaxRevealTime.TotalSeconds)); | ||
| } | ||
|
|
||
| private void UpdateTextReveal() | ||
| { | ||
| if (_textReveals.Count == 0) | ||
| return; | ||
|
|
||
| var progress = _revealTime <= TimeSpan.Zero | ||
| ? 1f | ||
| : MathHelper.Clamp((float) ((_timing.RealTime - _creationTime).TotalSeconds / _revealTime.TotalSeconds), 0f, 1f); | ||
|
|
||
| var visibleWeight = _maxRevealWeight * progress; | ||
|
|
||
| foreach (var reveal in _textReveals) | ||
| { | ||
| var visibleRunes = CountVisibleRunes(reveal.Message, Math.Min(visibleWeight, reveal.RevealWeight)); | ||
| if (visibleRunes == reveal.LastVisibleRunes) | ||
| continue; | ||
|
|
||
| reveal.LastVisibleRunes = visibleRunes; | ||
| reveal.Label.SetMessage(CreateRevealedMessage(reveal.Message, visibleRunes)); | ||
| } | ||
| } | ||
|
|
||
| private static float CountRevealWeight(FormattedMessage message) | ||
| { | ||
| var weight = 0f; | ||
|
|
||
| foreach (var node in message.Nodes) | ||
| { | ||
| if (node.Name != null || node.Value.StringValue == null) | ||
| continue; | ||
|
|
||
| foreach (var rune in node.Value.StringValue.EnumerateRunes()) | ||
| { | ||
| weight += GetRevealWeight(rune); | ||
| } | ||
| } | ||
|
|
||
| return weight; | ||
| } | ||
|
|
||
| private static int CountVisibleRunes(FormattedMessage message, float visibleWeight) | ||
| { | ||
| var remaining = visibleWeight; | ||
| var count = 0; | ||
|
|
||
| foreach (var node in message.Nodes) | ||
| { | ||
| if (node.Name != null) | ||
| continue; | ||
|
|
||
| var text = node.Value.StringValue; | ||
| if (text == null || remaining <= 0f) | ||
| continue; | ||
|
|
||
| foreach (var rune in text.EnumerateRunes()) | ||
| { | ||
| if (remaining <= 0f) | ||
| break; | ||
|
|
||
| count++; | ||
| remaining -= GetRevealWeight(rune); | ||
| } | ||
| } | ||
|
|
||
| return count; | ||
| } | ||
|
|
||
| private static FormattedMessage CreateRevealedMessage(FormattedMessage message, int visibleRunes) | ||
| { | ||
| var result = new FormattedMessage(message.Count); | ||
| var remaining = visibleRunes; | ||
|
|
||
| foreach (var node in message.Nodes) | ||
| { | ||
| if (node.Name != null) | ||
| { | ||
| result.AddMarkupOrThrow(node.ToString()); | ||
| continue; | ||
| } | ||
|
|
||
| var text = node.Value.StringValue; | ||
| if (text == null) | ||
| continue; | ||
|
|
||
| AddRevealedText(result, text, ref remaining); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private static void AddRevealedText(FormattedMessage result, string text, ref int remainingVisibleRunes) | ||
| { | ||
| var visible = new StringBuilder(); | ||
| var hidden = new StringBuilder(); | ||
|
|
||
| foreach (var rune in text.EnumerateRunes()) | ||
| { | ||
| if (remainingVisibleRunes > 0) | ||
| { | ||
| visible.Append(rune); | ||
| remainingVisibleRunes--; | ||
| continue; | ||
| } | ||
|
|
||
| hidden.Append(rune); | ||
| } | ||
|
|
||
| if (visible.Length > 0) | ||
| result.AddText(visible.ToString()); | ||
|
|
||
| if (hidden.Length == 0) | ||
| return; | ||
|
|
||
| result.PushColor(Color.Transparent); | ||
| result.AddText(hidden.ToString()); | ||
| result.Pop(); | ||
| } | ||
|
|
||
| private static float GetRevealWeight(Rune rune) | ||
| { | ||
| return rune.Value is ' ' or '\n' or '\t' | ||
| ? SpaceRevealWeight | ||
| : 1f; | ||
| } | ||
|
|
||
| private sealed class SpeechTextReveal(RichTextLabel label, FormattedMessage message, float revealWeight) | ||
| { | ||
| public readonly RichTextLabel Label = label; | ||
| public readonly FormattedMessage Message = message; | ||
| public readonly float RevealWeight = revealWeight; | ||
| public int LastVisibleRunes = -1; | ||
| } | ||
| // Arcane-end | ||
|
|
||
| /// <summary> | ||
| /// Causes the speech bubble to start fading IMMEDIATELY. | ||
| /// </summary> | ||
|
|
@@ -204,6 +401,8 @@ protected FormattedMessage ExtractAndFormatSpeechSubstring(ChatMessage message, | |
|
|
||
| public sealed class TextSpeechBubble : SpeechBubble | ||
| { | ||
| protected override float RevealSpeedMultiplier => 3f; // Arcane | ||
|
|
||
| public TextSpeechBubble(ChatMessage message, EntityUid senderEntity, string speechStyleClass, Color? fontColor = null) | ||
| : base(message, senderEntity, speechStyleClass, fontColor) | ||
| { | ||
|
|
@@ -216,7 +415,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl | |
| MaxWidth = SpeechMaxWidth, | ||
| }; | ||
|
|
||
| label.SetMessage(FormatSpeech(message.WrappedMessage, fontColor)); | ||
| SetRevealedMessage(label, FormatSpeech(message.WrappedMessage, fontColor)); // Arcane label.SetMessage(FormatSpeech(message.WrappedMessage, fontColor)); -> SetRevealedMessage(label, FormatSpeech(message.WrappedMessage, fontColor)); | ||
|
|
||
| var panel = new PanelContainer | ||
| { | ||
|
|
@@ -246,7 +445,7 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl | |
| MaxWidth = SpeechMaxWidth | ||
| }; | ||
|
|
||
| label.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); | ||
| SetRevealedMessage(label, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); // Arcane label.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); -> SetRevealedMessage(label, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); | ||
|
|
||
| var unfanciedPanel = new PanelContainer | ||
| { | ||
|
|
@@ -267,13 +466,13 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl | |
| { | ||
| ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleTextOpacity)), | ||
| MaxWidth = SpeechMaxWidth, | ||
| Margin = new Thickness(2, 6, 2, 2), | ||
| Margin = new Thickness(2, 2, 2, 2), // LuaM Margin = new Thickness(2, 6, 2, 2), -> Margin = new Thickness(2, 2, 2, 2), | ||
| StyleClasses = { "bubbleContent" }, | ||
| }; | ||
|
|
||
| //We'll be honest. *Yes* this is hacky. Doing this in a cleaner way would require a bottom-up refactor of how saycode handles sending chat messages. -Myr | ||
| bubbleHeader.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleHeader", fontColor)); | ||
| bubbleContent.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); | ||
| SetRevealedMessage(bubbleContent, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); // Arcane bubbleContent.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); -> SetRevealedMessage(bubbleContent, ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor)); | ||
|
|
||
| //As for below: Some day this could probably be converted to xaml. But that is not today. -Myr | ||
| var mainPanel = new PanelContainer | ||
|
|
@@ -282,8 +481,8 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl | |
| Children = { bubbleContent }, | ||
| ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity)), | ||
| HorizontalAlignment = HAlignment.Center, | ||
| VerticalAlignment = VAlignment.Bottom, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Закомментить, а не удалить. |
||
| Margin = new Thickness(4, 14, 4, 2) | ||
| // VerticalAlignment = VAlignment.Bottom, Arcane delete | ||
| Margin = new Thickness(4, 0, 4, 2) // Arcane Margin = new Thickness(4, 14, 4, 2) -> Margin = new Thickness(4, 0, 4, 2) | ||
| }; | ||
|
|
||
| var headerPanel = new PanelContainer | ||
|
|
@@ -295,9 +494,11 @@ protected override Control BuildBubble(ChatMessage message, string speechStyleCl | |
| VerticalAlignment = VAlignment.Top | ||
| }; | ||
|
|
||
| var panel = new PanelContainer | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Закомментить, а не удалить. |
||
| var panel = new BoxContainer // Arcane var panel = new PanelContainer -> var panel = new BoxContainer | ||
| { | ||
| Children = { mainPanel, headerPanel } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Закомментить, а не удалить.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. переделал чуток коммент |
||
| Orientation = BoxContainer.LayoutOrientation.Vertical, // Arcane | ||
| HorizontalAlignment = HAlignment.Center, // Arcane | ||
| Children = { headerPanel, mainPanel } | ||
| }; | ||
|
|
||
| return panel; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.