Add emotes as borderless speech "bubbles"
This commit is contained in:
@@ -19,6 +19,12 @@ namespace Content.Client.Chat
|
|||||||
{
|
{
|
||||||
internal sealed class ChatManager : IChatManager
|
internal sealed class ChatManager : IChatManager
|
||||||
{
|
{
|
||||||
|
private struct SpeechBubbleData
|
||||||
|
{
|
||||||
|
public string Message;
|
||||||
|
public SpeechBubble.SpeechType Type;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The max amount of chars allowed to fit in a single speech bubble.
|
/// The max amount of chars allowed to fit in a single speech bubble.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -117,7 +123,7 @@ namespace Content.Client.Chat
|
|||||||
|
|
||||||
var msg = queueData.MessageQueue.Dequeue();
|
var msg = queueData.MessageQueue.Dequeue();
|
||||||
|
|
||||||
queueData.TimeLeft += BubbleDelayBase + msg.Length * BubbleDelayFactor;
|
queueData.TimeLeft += BubbleDelayBase + msg.Message.Length * BubbleDelayFactor;
|
||||||
|
|
||||||
// We keep the queue around while it has 0 items. This allows us to keep the timer.
|
// We keep the queue around while it has 0 items. This allows us to keep the timer.
|
||||||
// When the timer hits 0 and there's no messages left, THEN we can clear it up.
|
// When the timer hits 0 and there's no messages left, THEN we can clear it up.
|
||||||
@@ -291,13 +297,23 @@ namespace Content.Client.Chat
|
|||||||
WriteChatMessage(storedMessage);
|
WriteChatMessage(storedMessage);
|
||||||
|
|
||||||
// Local messages that have an entity attached get a speech bubble.
|
// Local messages that have an entity attached get a speech bubble.
|
||||||
if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Dead) && msg.SenderEntity != default)
|
if (msg.SenderEntity == default)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (msg.Channel)
|
||||||
{
|
{
|
||||||
AddSpeechBubble(msg);
|
case ChatChannel.Local:
|
||||||
|
case ChatChannel.Dead:
|
||||||
|
AddSpeechBubble(msg, SpeechBubble.SpeechType.Say);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ChatChannel.Emotes:
|
||||||
|
AddSpeechBubble(msg, SpeechBubble.SpeechType.Emote);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSpeechBubble(MsgChatMessage msg)
|
private void AddSpeechBubble(MsgChatMessage msg, SpeechBubble.SpeechType speechType)
|
||||||
{
|
{
|
||||||
if (!_entityManager.TryGetEntity(msg.SenderEntity, out var entity))
|
if (!_entityManager.TryGetEntity(msg.SenderEntity, out var entity))
|
||||||
{
|
{
|
||||||
@@ -305,8 +321,18 @@ namespace Content.Client.Chat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var messages = SplitMessage(msg.Message);
|
||||||
|
|
||||||
|
foreach (var message in messages)
|
||||||
|
{
|
||||||
|
EnqueueSpeechBubble(entity, message, speechType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> SplitMessage(string msg)
|
||||||
|
{
|
||||||
// Split message into words separated by spaces.
|
// Split message into words separated by spaces.
|
||||||
var words = msg.Message.Split(' ');
|
var words = msg.Split(' ');
|
||||||
var messages = new List<string>();
|
var messages = new List<string>();
|
||||||
var currentBuffer = new List<string>();
|
var currentBuffer = new List<string>();
|
||||||
|
|
||||||
@@ -346,13 +372,10 @@ namespace Content.Client.Chat
|
|||||||
messages.Add(string.Join(" ", currentBuffer));
|
messages.Add(string.Join(" ", currentBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var message in messages)
|
return messages;
|
||||||
{
|
|
||||||
EnqueueSpeechBubble(entity, message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnqueueSpeechBubble(IEntity entity, string contents)
|
private void EnqueueSpeechBubble(IEntity entity, string contents, SpeechBubble.SpeechType speechType)
|
||||||
{
|
{
|
||||||
if (!_queuedSpeechBubbles.TryGetValue(entity.Uid, out var queueData))
|
if (!_queuedSpeechBubbles.TryGetValue(entity.Uid, out var queueData))
|
||||||
{
|
{
|
||||||
@@ -360,12 +383,16 @@ namespace Content.Client.Chat
|
|||||||
_queuedSpeechBubbles.Add(entity.Uid, queueData);
|
_queuedSpeechBubbles.Add(entity.Uid, queueData);
|
||||||
}
|
}
|
||||||
|
|
||||||
queueData.MessageQueue.Enqueue(contents);
|
queueData.MessageQueue.Enqueue(new SpeechBubbleData
|
||||||
|
{
|
||||||
|
Message = contents,
|
||||||
|
Type = speechType,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateSpeechBubble(IEntity entity, string contents)
|
private void CreateSpeechBubble(IEntity entity, SpeechBubbleData speechData)
|
||||||
{
|
{
|
||||||
var bubble = new SpeechBubble(contents, entity, _eyeManager, this);
|
var bubble = SpeechBubble.CreateSpeechBubble(speechData.Type, speechData.Message, entity, _eyeManager, this);
|
||||||
|
|
||||||
if (_activeSpeechBubbles.TryGetValue(entity.Uid, out var existing))
|
if (_activeSpeechBubbles.TryGetValue(entity.Uid, out var existing))
|
||||||
{
|
{
|
||||||
@@ -405,7 +432,7 @@ namespace Content.Client.Chat
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float TimeLeft { get; set; }
|
public float TimeLeft { get; set; }
|
||||||
|
|
||||||
public Queue<string> MessageQueue { get; } = new Queue<string>();
|
public Queue<SpeechBubbleData> MessageQueue { get; } = new Queue<SpeechBubbleData>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Client.Interfaces.Chat;
|
using System;
|
||||||
|
using Content.Client.Interfaces.Chat;
|
||||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
@@ -9,8 +10,14 @@ using Robust.Shared.Timing;
|
|||||||
|
|
||||||
namespace Content.Client.Chat
|
namespace Content.Client.Chat
|
||||||
{
|
{
|
||||||
public class SpeechBubble : Control
|
public abstract class SpeechBubble : Control
|
||||||
{
|
{
|
||||||
|
public enum SpeechType
|
||||||
|
{
|
||||||
|
Emote,
|
||||||
|
Say
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total time a speech bubble stays on screen.
|
/// The total time a speech bubble stays on screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -38,6 +45,21 @@ namespace Content.Client.Chat
|
|||||||
|
|
||||||
public float ContentHeight { get; private set; }
|
public float ContentHeight { get; private set; }
|
||||||
|
|
||||||
|
public static SpeechBubble CreateSpeechBubble(SpeechType type, string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case SpeechType.Emote:
|
||||||
|
return new EmoteSpeechBubble(text, senderEntity, eyeManager, chatManager);
|
||||||
|
|
||||||
|
case SpeechType.Say:
|
||||||
|
return new SaySpeechBubble(text, senderEntity, eyeManager, chatManager);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SpeechBubble(string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager)
|
public SpeechBubble(string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager)
|
||||||
{
|
{
|
||||||
_chatManager = chatManager;
|
_chatManager = chatManager;
|
||||||
@@ -47,27 +69,18 @@ namespace Content.Client.Chat
|
|||||||
// Use text clipping so new messages don't overlap old ones being pushed up.
|
// Use text clipping so new messages don't overlap old ones being pushed up.
|
||||||
RectClipContent = true;
|
RectClipContent = true;
|
||||||
|
|
||||||
var label = new RichTextLabel
|
var bubble = BuildBubble(text);
|
||||||
{
|
|
||||||
MaxWidth = 256,
|
|
||||||
};
|
|
||||||
label.SetMessage(text);
|
|
||||||
|
|
||||||
var panel = new PanelContainer
|
AddChild(bubble);
|
||||||
{
|
|
||||||
StyleClasses = { "tooltipBox" },
|
|
||||||
Children = { label },
|
|
||||||
ModulateSelfOverride = Color.White.WithAlpha(0.75f)
|
|
||||||
};
|
|
||||||
|
|
||||||
AddChild(panel);
|
|
||||||
|
|
||||||
ForceRunStyleUpdate();
|
ForceRunStyleUpdate();
|
||||||
|
|
||||||
ContentHeight = panel.CombinedMinimumSize.Y;
|
ContentHeight = bubble.CombinedMinimumSize.Y;
|
||||||
_verticalOffsetAchieved = -ContentHeight;
|
_verticalOffsetAchieved = -ContentHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Control BuildBubble(string text);
|
||||||
|
|
||||||
protected override Vector2 CalculateMinimumSize()
|
protected override Vector2 CalculateMinimumSize()
|
||||||
{
|
{
|
||||||
return (base.CalculateMinimumSize().X, 0);
|
return (base.CalculateMinimumSize().X, 0);
|
||||||
@@ -134,4 +147,57 @@ namespace Content.Client.Chat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class EmoteSpeechBubble : SpeechBubble
|
||||||
|
|
||||||
|
{
|
||||||
|
public EmoteSpeechBubble(string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager)
|
||||||
|
: base(text, senderEntity, eyeManager, chatManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Control BuildBubble(string text)
|
||||||
|
{
|
||||||
|
var label = new RichTextLabel
|
||||||
|
{
|
||||||
|
MaxWidth = 256,
|
||||||
|
};
|
||||||
|
label.SetMessage(text);
|
||||||
|
|
||||||
|
var panel = new PanelContainer
|
||||||
|
{
|
||||||
|
StyleClasses = { "speechBox", "emoteBox" },
|
||||||
|
Children = { label },
|
||||||
|
ModulateSelfOverride = Color.White.WithAlpha(0.75f)
|
||||||
|
};
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SaySpeechBubble : SpeechBubble
|
||||||
|
{
|
||||||
|
public SaySpeechBubble(string text, IEntity senderEntity, IEyeManager eyeManager, IChatManager chatManager)
|
||||||
|
: base(text, senderEntity, eyeManager, chatManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Control BuildBubble(string text)
|
||||||
|
{
|
||||||
|
var label = new RichTextLabel
|
||||||
|
{
|
||||||
|
MaxWidth = 256,
|
||||||
|
};
|
||||||
|
label.SetMessage(text);
|
||||||
|
|
||||||
|
var panel = new PanelContainer
|
||||||
|
{
|
||||||
|
StyleClasses = { "speechBox", "sayBox" },
|
||||||
|
Children = { label },
|
||||||
|
ModulateSelfOverride = Color.White.WithAlpha(0.75f)
|
||||||
|
};
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
var notoSans10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 10);
|
var notoSans10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 10);
|
||||||
var notoSansItalic10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Italic.ttf", 10);
|
var notoSansItalic10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Italic.ttf", 10);
|
||||||
var notoSans12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 12);
|
var notoSans12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 12);
|
||||||
|
var notoSansItalic12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Italic.ttf", 12);
|
||||||
var notoSansBold12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Bold.ttf", 12);
|
var notoSansBold12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Bold.ttf", 12);
|
||||||
var notoSansDisplayBold14 = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 14);
|
var notoSansDisplayBold14 = resCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 14);
|
||||||
var notoSans16 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 16);
|
var notoSans16 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 16);
|
||||||
@@ -451,6 +452,19 @@ namespace Content.Client.UserInterface.Stylesheets
|
|||||||
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
|
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {"speechBox", "sayBox"}, null, null), new[]
|
||||||
|
{
|
||||||
|
new StyleProperty(PanelContainer.StylePropertyPanel, tooltipBox)
|
||||||
|
}),
|
||||||
|
|
||||||
|
new StyleRule(new SelectorChild(
|
||||||
|
new SelectorElement(typeof(PanelContainer), new[] {"speechBox", "emoteBox"}, null, null),
|
||||||
|
new SelectorElement(typeof(RichTextLabel), null, null, null)),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new StyleProperty("font", notoSansItalic12),
|
||||||
|
}),
|
||||||
|
|
||||||
// Entity tooltip
|
// Entity tooltip
|
||||||
new StyleRule(
|
new StyleRule(
|
||||||
new SelectorElement(typeof(PanelContainer), new[] {ExamineSystem.StyleClassEntityTooltip}, null,
|
new SelectorElement(typeof(PanelContainer), new[] {ExamineSystem.StyleClassEntityTooltip}, null,
|
||||||
|
|||||||
Reference in New Issue
Block a user