Files

307 lines
11 KiB
C#
Raw Permalink Normal View History

using System.Numerics;
2021-06-09 22:19:39 +02:00
using Content.Client.Chat.Managers;
using Content.Shared.CCVar;
using Content.Shared.Chat;
Holopads (#32711) * Initial resources commit * Initial code commit * Added additional resources * Continuing to build holopad and telephone systems * Added hologram shader * Added hologram system and entity * Holo calls now have a hologram of the user appear on them * Initial implementation of holopads transmitting nearby chatter * Added support for linking across multiple telephones/holopads/entities * Fixed a bunch of bugs * Tried simplifying holopad entity dependence, added support for mid-call user switching * Replaced PVS expansion with manually networked sprite states * Adjusted volume of ring tone * Added machine board * Minor features and tweaks * Resolving merge conflict * Recommit audio attributions * Telephone chat adjustments * Added support for AI interactions with holopads * Building the holopad UI * Holopad UI finished * Further UI tweaks * Station AI can hear local chatter when being projected from a holopad * Minor bug fixes * Added wire panels to holopads * Basic broadcasting * Start of emergency broadcasting code * Fixing issues with broadcasting * More work on emergency broadcasting * Updated holopad visuals * Added cooldown text to emergency broadcast and control lock out screen * Code clean up * Fixed issue with timing * Broadcasting now requires command access * Fixed some bugs * Added multiple holopad prototypes with different ranges * The AI no longer requires power to interact with holopads * Fixed some additional issues * Addressing more issues * Added emote support for holograms * Changed the broadcast lockout durations to their proper values * Added AI vision wire to holopads * Bug fixes * AI vision and interaction wires can be added to the same wire panel * Fixed error * More bug fixes * Fixed test fail * Embellished the emergency call lock out window * Holopads play borg sounds when speaking * Borg and AI names are listed as the caller ID on the holopad * Borg chassis can now be seen on holopad holograms * Holopad returns to a machine frame when badly damaged * Clarified some text * Fix merge conflict * Fixed merge conflict * Fixing merge conflict * Fixing merge conflict * Fixing merge conflict * Offset menu on open * AI can alt click on holopads to activate the projector * Bug fixes for intellicard interactions * Fixed speech issue with intellicards * The UI automatically opens for the AI when it alt-clicks on the holopad * Simplified shader math * Telephones will auto hang up 60 seconds after the last person on a call stops speaking * Added better support for AI requests when multiple AI cores are on the station * The call controls pop up for the AI when they accept a summons from a holopad * Compatibility mode fix for the hologram shader * Further shader fixes for compatibility mode * File clean up * More cleaning up * Removed access requirements from quantum holopads so they can used by nukies * The title of the holopad window now reflects the name of the device * Linked telephones will lose their connection if both move out of range of each other
2024-12-17 13:18:15 -06:00
using Content.Shared.Speech;
using Robust.Client.Graphics;
2019-07-30 23:13:05 +02:00
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
2019-08-04 01:08:55 +02:00
using Robust.Shared.Timing;
using Robust.Shared.Utility;
2019-07-30 23:13:05 +02:00
2021-06-09 22:19:39 +02:00
namespace Content.Client.Chat.UI
2019-07-30 23:13:05 +02:00
{
public abstract class SpeechBubble : Control
2019-07-30 23:13:05 +02:00
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
private readonly SharedTransformSystem _transformSystem;
2020-12-04 11:57:33 +01:00
public enum SpeechType : byte
{
Emote,
Say,
Whisper,
Looc
}
2019-07-30 23:13:05 +02:00
/// <summary>
/// The total time a speech bubble stays on screen.
/// </summary>
private static readonly TimeSpan TotalTime = TimeSpan.FromSeconds(4);
2019-07-30 23:13:05 +02:00
/// <summary>
/// The amount of time at the end of the bubble's life at which it starts fading.
/// </summary>
private static readonly TimeSpan FadeTime = TimeSpan.FromSeconds(0.25f);
2019-07-30 23:13:05 +02:00
/// <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.
/// </summary>
private const float EntityVerticalOffset = 0.5f;
/// <summary>
/// The default maximum width for speech bubbles.
/// </summary>
public const float SpeechMaxWidth = 256;
2021-12-05 18:09:01 +01:00
private readonly EntityUid _senderEntity;
2019-07-30 23:13:05 +02:00
/// <summary>
/// The time at which this bubble will die.
/// </summary>
private TimeSpan _deathTime;
2019-07-30 23:13:05 +02:00
public float VerticalOffset { get; set; }
private float _verticalOffsetAchieved;
public Vector2 ContentSize { get; private set; }
2019-07-30 23:13:05 +02:00
// man down
public event Action<EntityUid, SpeechBubble>? OnDied;
public static SpeechBubble CreateSpeechBubble(SpeechType type, ChatMessage message, EntityUid senderEntity)
{
switch (type)
{
case SpeechType.Emote:
return new TextSpeechBubble(message, senderEntity, "emoteBox");
case SpeechType.Say:
return new FancyTextSpeechBubble(message, senderEntity, "sayBox");
case SpeechType.Whisper:
return new FancyTextSpeechBubble(message, senderEntity, "whisperBox");
case SpeechType.Looc:
return new TextSpeechBubble(message, senderEntity, "emoteBox", Color.FromHex("#48d1cc"));
default:
throw new ArgumentOutOfRangeException();
}
}
public SpeechBubble(ChatMessage message, EntityUid senderEntity, string speechStyleClass, Color? fontColor = null)
2019-07-30 23:13:05 +02:00
{
IoCManager.InjectDependencies(this);
2019-07-30 23:13:05 +02:00
_senderEntity = senderEntity;
_transformSystem = _entityManager.System<SharedTransformSystem>();
2019-07-30 23:13:05 +02:00
// Use text clipping so new messages don't overlap old ones being pushed up.
RectClipContent = true;
var bubble = BuildBubble(message, speechStyleClass, fontColor);
2019-07-30 23:13:05 +02:00
AddChild(bubble);
2019-07-30 23:13:05 +02:00
2019-08-14 22:04:35 +02:00
ForceRunStyleUpdate();
bubble.Measure(Vector2Helpers.Infinity);
ContentSize = bubble.DesiredSize;
_verticalOffsetAchieved = -ContentSize.Y;
_deathTime = _timing.RealTime + TotalTime;
2019-07-30 23:13:05 +02:00
}
protected abstract Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null);
2019-08-04 01:08:55 +02:00
protected override void FrameUpdate(FrameEventArgs args)
2019-07-30 23:13:05 +02:00
{
base.FrameUpdate(args);
var timeLeft = (float)(_deathTime - _timing.RealTime).TotalSeconds;
if (_entityManager.Deleted(_senderEntity) || timeLeft <= 0)
{
// Timer spawn to prevent concurrent modification exception.
Timer.Spawn(0, Die);
return;
}
2019-07-30 23:13:05 +02:00
// Lerp to our new vertical offset if it's been modified.
if (MathHelper.CloseToPercent(_verticalOffsetAchieved - VerticalOffset, 0, 0.1))
2019-07-30 23:13:05 +02:00
{
_verticalOffsetAchieved = VerticalOffset;
}
else
{
_verticalOffsetAchieved = MathHelper.Lerp(_verticalOffsetAchieved, VerticalOffset, 10 * args.DeltaSeconds);
2019-07-30 23:13:05 +02:00
}
if (!_entityManager.TryGetComponent<TransformComponent>(_senderEntity, out var xform) || xform.MapID != _eyeManager.CurrentEye.Position.MapId)
2019-07-30 23:13:05 +02:00
{
Modulate = Color.White.WithAlpha(0);
2019-07-30 23:13:05 +02:00
return;
}
if (timeLeft <= FadeTime.TotalSeconds)
2019-07-30 23:13:05 +02:00
{
// Update alpha if we're fading.
Modulate = Color.White.WithAlpha(timeLeft / (float)FadeTime.TotalSeconds);
2019-07-30 23:13:05 +02:00
}
else
{
// Make opaque otherwise, because it might have been hidden before
Modulate = Color.White;
2019-07-30 23:13:05 +02:00
}
Holopads (#32711) * Initial resources commit * Initial code commit * Added additional resources * Continuing to build holopad and telephone systems * Added hologram shader * Added hologram system and entity * Holo calls now have a hologram of the user appear on them * Initial implementation of holopads transmitting nearby chatter * Added support for linking across multiple telephones/holopads/entities * Fixed a bunch of bugs * Tried simplifying holopad entity dependence, added support for mid-call user switching * Replaced PVS expansion with manually networked sprite states * Adjusted volume of ring tone * Added machine board * Minor features and tweaks * Resolving merge conflict * Recommit audio attributions * Telephone chat adjustments * Added support for AI interactions with holopads * Building the holopad UI * Holopad UI finished * Further UI tweaks * Station AI can hear local chatter when being projected from a holopad * Minor bug fixes * Added wire panels to holopads * Basic broadcasting * Start of emergency broadcasting code * Fixing issues with broadcasting * More work on emergency broadcasting * Updated holopad visuals * Added cooldown text to emergency broadcast and control lock out screen * Code clean up * Fixed issue with timing * Broadcasting now requires command access * Fixed some bugs * Added multiple holopad prototypes with different ranges * The AI no longer requires power to interact with holopads * Fixed some additional issues * Addressing more issues * Added emote support for holograms * Changed the broadcast lockout durations to their proper values * Added AI vision wire to holopads * Bug fixes * AI vision and interaction wires can be added to the same wire panel * Fixed error * More bug fixes * Fixed test fail * Embellished the emergency call lock out window * Holopads play borg sounds when speaking * Borg and AI names are listed as the caller ID on the holopad * Borg chassis can now be seen on holopad holograms * Holopad returns to a machine frame when badly damaged * Clarified some text * Fix merge conflict * Fixed merge conflict * Fixing merge conflict * Fixing merge conflict * Fixing merge conflict * Offset menu on open * AI can alt click on holopads to activate the projector * Bug fixes for intellicard interactions * Fixed speech issue with intellicards * The UI automatically opens for the AI when it alt-clicks on the holopad * Simplified shader math * Telephones will auto hang up 60 seconds after the last person on a call stops speaking * Added better support for AI requests when multiple AI cores are on the station * The call controls pop up for the AI when they accept a summons from a holopad * Compatibility mode fix for the hologram shader * Further shader fixes for compatibility mode * File clean up * More cleaning up * Removed access requirements from quantum holopads so they can used by nukies * The title of the holopad window now reflects the name of the device * Linked telephones will lose their connection if both move out of range of each other
2024-12-17 13:18:15 -06:00
var baseOffset = 0f;
if (_entityManager.TryGetComponent<SpeechComponent>(_senderEntity, out var speech))
Holopads (#32711) * Initial resources commit * Initial code commit * Added additional resources * Continuing to build holopad and telephone systems * Added hologram shader * Added hologram system and entity * Holo calls now have a hologram of the user appear on them * Initial implementation of holopads transmitting nearby chatter * Added support for linking across multiple telephones/holopads/entities * Fixed a bunch of bugs * Tried simplifying holopad entity dependence, added support for mid-call user switching * Replaced PVS expansion with manually networked sprite states * Adjusted volume of ring tone * Added machine board * Minor features and tweaks * Resolving merge conflict * Recommit audio attributions * Telephone chat adjustments * Added support for AI interactions with holopads * Building the holopad UI * Holopad UI finished * Further UI tweaks * Station AI can hear local chatter when being projected from a holopad * Minor bug fixes * Added wire panels to holopads * Basic broadcasting * Start of emergency broadcasting code * Fixing issues with broadcasting * More work on emergency broadcasting * Updated holopad visuals * Added cooldown text to emergency broadcast and control lock out screen * Code clean up * Fixed issue with timing * Broadcasting now requires command access * Fixed some bugs * Added multiple holopad prototypes with different ranges * The AI no longer requires power to interact with holopads * Fixed some additional issues * Addressing more issues * Added emote support for holograms * Changed the broadcast lockout durations to their proper values * Added AI vision wire to holopads * Bug fixes * AI vision and interaction wires can be added to the same wire panel * Fixed error * More bug fixes * Fixed test fail * Embellished the emergency call lock out window * Holopads play borg sounds when speaking * Borg and AI names are listed as the caller ID on the holopad * Borg chassis can now be seen on holopad holograms * Holopad returns to a machine frame when badly damaged * Clarified some text * Fix merge conflict * Fixed merge conflict * Fixing merge conflict * Fixing merge conflict * Fixing merge conflict * Offset menu on open * AI can alt click on holopads to activate the projector * Bug fixes for intellicard interactions * Fixed speech issue with intellicards * The UI automatically opens for the AI when it alt-clicks on the holopad * Simplified shader math * Telephones will auto hang up 60 seconds after the last person on a call stops speaking * Added better support for AI requests when multiple AI cores are on the station * The call controls pop up for the AI when they accept a summons from a holopad * Compatibility mode fix for the hologram shader * Further shader fixes for compatibility mode * File clean up * More cleaning up * Removed access requirements from quantum holopads so they can used by nukies * The title of the holopad window now reflects the name of the device * Linked telephones will lose their connection if both move out of range of each other
2024-12-17 13:18:15 -06:00
baseOffset = speech.SpeechBubbleOffset;
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset);
var worldPos = _transformSystem.GetWorldPosition(xform) + offset;
2019-07-30 23:13:05 +02:00
var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale;
var screenPos = lowerCenter - new Vector2(ContentSize.X / 2, ContentSize.Y + _verticalOffsetAchieved);
// Round to nearest 0.5
screenPos = (screenPos * 2).Rounded() / 2;
LayoutContainer.SetPosition(this, screenPos);
2019-07-30 23:13:05 +02:00
var height = MathF.Ceiling(MathHelper.Clamp(lowerCenter.Y - screenPos.Y, 0, ContentSize.Y));
2021-02-21 12:38:56 +01:00
SetHeight = height;
2019-07-30 23:13:05 +02:00
}
private void Die()
{
2019-07-31 13:17:06 +02:00
if (Disposed)
{
return;
}
OnDied?.Invoke(_senderEntity, this);
2019-07-30 23:13:05 +02:00
}
/// <summary>
/// Causes the speech bubble to start fading IMMEDIATELY.
/// </summary>
public void FadeNow()
{
if (_deathTime > _timing.RealTime)
2019-07-30 23:13:05 +02:00
{
_deathTime = _timing.RealTime + FadeTime;
2019-07-30 23:13:05 +02:00
}
}
protected FormattedMessage FormatSpeech(string message, Color? fontColor = null)
{
var msg = new FormattedMessage();
if (fontColor != null)
msg.PushColor(fontColor.Value);
msg.AddMarkupOrThrow(message);
return msg;
}
protected FormattedMessage ExtractAndFormatSpeechSubstring(ChatMessage message, string tag, Color? fontColor = null)
{
return FormatSpeech(SharedChatSystem.GetStringInsideTag(message, tag), fontColor);
}
2019-07-30 23:13:05 +02:00
}
public sealed class TextSpeechBubble : SpeechBubble
{
public TextSpeechBubble(ChatMessage message, EntityUid senderEntity, string speechStyleClass, Color? fontColor = null)
: base(message, senderEntity, speechStyleClass, fontColor)
{
}
protected override Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null)
{
var label = new RichTextLabel
{
MaxWidth = SpeechMaxWidth,
};
label.SetMessage(FormatSpeech(message.WrappedMessage, fontColor));
var panel = new PanelContainer
{
StyleClasses = { "speechBox", speechStyleClass },
Children = { label },
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity))
};
return panel;
}
}
public sealed class FancyTextSpeechBubble : SpeechBubble
{
public FancyTextSpeechBubble(ChatMessage message, EntityUid senderEntity, string speechStyleClass, Color? fontColor = null)
: base(message, senderEntity, speechStyleClass, fontColor)
{
}
protected override Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null)
{
if (!ConfigManager.GetCVar(CCVars.ChatEnableFancyBubbles))
{
var label = new RichTextLabel
{
MaxWidth = SpeechMaxWidth
};
label.SetMessage(ExtractAndFormatSpeechSubstring(message, "BubbleContent", fontColor));
var unfanciedPanel = new PanelContainer
{
StyleClasses = { "speechBox", speechStyleClass },
Children = { label },
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity)),
};
return unfanciedPanel;
}
var bubbleHeader = new RichTextLabel
{
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleSpeakerOpacity)),
Margin = new Thickness(1, 1, 1, 1),
};
var bubbleContent = new RichTextLabel
{
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleTextOpacity)),
MaxWidth = SpeechMaxWidth,
Margin = new Thickness(2, 6, 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));
//As for below: Some day this could probably be converted to xaml. But that is not today. -Myr
var mainPanel = new PanelContainer
{
StyleClasses = { "speechBox", speechStyleClass },
Children = { bubbleContent },
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity)),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Bottom,
Margin = new Thickness(4, 14, 4, 2)
};
var headerPanel = new PanelContainer
{
StyleClasses = { "speechBox", speechStyleClass },
Children = { bubbleHeader },
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.ChatFancyNameBackground) ? ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity) : 0f),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Top
};
var panel = new PanelContainer
{
Children = { mainPanel, headerPanel }
};
return panel;
}
}
2019-07-30 23:13:05 +02:00
}