Examine prediction (#23565)

* Initial prediction

* new group handling

* groups for all examines that use multiple rn

* compile

* why was it doing this??

* handle newlines with sorting properly
This commit is contained in:
Kara
2024-01-05 23:53:13 -07:00
committed by GitHub
parent 731cfc278a
commit 0ae3858b69
41 changed files with 693 additions and 431 deletions

View File

@@ -1,4 +1,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Components;
@@ -238,42 +241,60 @@ namespace Content.Shared.Examine
return message;
}
var doNewline = false;
var hasDescription = false;
//Add an entity description if one is declared
if (!string.IsNullOrEmpty(EntityManager.GetComponent<MetaDataComponent>(entity).EntityDescription))
{
message.AddText(EntityManager.GetComponent<MetaDataComponent>(entity).EntityDescription);
doNewline = true;
hasDescription = true;
}
message.PushColor(Color.DarkGray);
// Raise the event and let things that subscribe to it change the message...
var isInDetailsRange = IsInDetailsRange(examiner.Value, entity);
var examinedEvent = new ExaminedEvent(message, entity, examiner.Value, isInDetailsRange, doNewline);
RaiseLocalEvent(entity, examinedEvent, true);
var examinedEvent = new ExaminedEvent(message, entity, examiner.Value, isInDetailsRange, hasDescription);
RaiseLocalEvent(entity, examinedEvent);
message.Pop();
var newMessage = examinedEvent.GetTotalMessage();
return message;
// pop color tag
newMessage.Pop();
return newMessage;
}
}
/// <summary>
/// Raised when an entity is examined.
/// If you're pushing multiple messages that should be grouped together (or ordered in some way),
/// call <see cref="PushGroup"/> before pushing and <see cref="PopGroup"/> when finished.
/// </summary>
public sealed class ExaminedEvent : EntityEventArgs
{
/// <summary>
/// The message that will be displayed as the examine text.
/// For most use cases, you probably want to use <see cref="PushMarkup"/> and similar instead to modify this,
/// since it handles newlines and such correctly.
/// You should use <see cref="PushMarkup"/> and similar instead to modify this,
/// since it handles newlines/priority and such correctly.
/// </summary>
/// <seealso cref="PushMessage"/>
/// <seealso cref="PushMarkup"/>
/// <seealso cref="PushText"/>
public FormattedMessage Message { get; }
/// <seealso cref="AddMessage"/>
/// <seealso cref="AddMarkup"/>
/// <seealso cref="AddText"/>
private FormattedMessage Message { get; }
/// <summary>
/// Parts of the examine message that will later be sorted by priority and pushed onto <see cref="Message"/>.
/// </summary>
private List<ExamineMessagePart> Parts { get; } = new();
/// <summary>
/// Whether the examiner is in range of the entity to get some extra details.
/// </summary>
public bool IsInDetailsRange { get; }
/// <summary>
/// The entity performing the examining.
@@ -285,62 +306,206 @@ namespace Content.Shared.Examine
/// </summary>
public EntityUid Examined { get; }
/// <summary>
/// Whether the examiner is in range of the entity to get some extra details.
/// </summary>
public bool IsInDetailsRange { get; }
private bool _hasDescription;
private bool _doNewLine;
private ExamineMessagePart? _currentGroupPart;
public ExaminedEvent(FormattedMessage message, EntityUid examined, EntityUid examiner, bool isInDetailsRange, bool doNewLine)
public ExaminedEvent(FormattedMessage message, EntityUid examined, EntityUid examiner, bool isInDetailsRange, bool hasDescription)
{
Message = message;
Examined = examined;
Examiner = examiner;
IsInDetailsRange = isInDetailsRange;
_doNewLine = doNewLine;
_hasDescription = hasDescription;
}
/// <summary>
/// Returns <see cref="Message"/> with all <see cref="Parts"/> appended according to their priority.
/// </summary>
public FormattedMessage GetTotalMessage()
{
int Comparison(ExamineMessagePart a, ExamineMessagePart b)
{
// Try sort by priority, then group, then by string contents
if (a.Priority != b.Priority)
{
// negative so that expected behavior is consistent with what makes sense
// i.e. a negative priority should mean its at the bottom of the list, right?
return -a.Priority.CompareTo(b.Priority);
}
if (a.Group != b.Group)
{
return string.Compare(a.Group, b.Group, StringComparison.Ordinal);
}
return string.Compare(a.Message.ToString(), b.Message.ToString(), StringComparison.Ordinal);
}
// tolist/clone formatted message so calling this multiple times wont fuck shit up
// (if that happens for some reason)
var parts = Parts.ToList();
var totalMessage = new FormattedMessage(Message);
parts.Sort(Comparison);
if (_hasDescription)
{
totalMessage.PushNewline();
}
foreach (var part in parts)
{
totalMessage.AddMessage(part.Message);
if (part.DoNewLine && parts.Last() != part)
totalMessage.PushNewline();
}
return totalMessage;
}
/// <summary>
/// Message group handling. Call this if you want the next set of examine messages that you're adding to have
/// a consistent order with regards to each other. This is done so that client & server will always
/// sort messages the same as well as grouped together properly, even if subscriptions are different.
/// You should wrap it in a using() block so popping automatically occurs.
/// </summary>
public ExamineGroupDisposable PushGroup(string groupName, int priority=0)
{
// Ensure that other examine events correctly ended their groups.
DebugTools.Assert(_currentGroupPart == null);
_currentGroupPart = new ExamineMessagePart(new FormattedMessage(), priority, false, groupName);
return new ExamineGroupDisposable(this);
}
/// <summary>
/// Ends the current group and pushes its groups contents to the message.
/// This will be called automatically if in using a `using` block with <see cref="PushGroup"/>.
/// </summary>
private void PopGroup()
{
DebugTools.Assert(_currentGroupPart != null);
if (_currentGroupPart != null)
Parts.Add(_currentGroupPart);
_currentGroupPart = null;
}
/// <summary>
/// Push another message into this examine result, on its own line.
/// End message will be grouped by <see cref="priority"/>, then by group if one was started
/// then by ordinal comparison.
/// </summary>
/// <seealso cref="PushMarkup"/>
/// <seealso cref="PushText"/>
public void PushMessage(FormattedMessage message)
public void PushMessage(FormattedMessage message, int priority=0)
{
if (message.Nodes.Count == 0)
return;
if (_doNewLine)
Message.AddText("\n");
Message.AddMessage(message);
_doNewLine = true;
if (_currentGroupPart != null)
{
message.PushNewline();
_currentGroupPart.Message.AddMessage(message);
}
else
{
Parts.Add(new ExamineMessagePart(message, priority, true, null));
}
}
/// <summary>
/// Push another message parsed from markup into this examine result, on its own line.
/// End message will be grouped by <see cref="priority"/>, then by group if one was started
/// then by ordinal comparison.
/// </summary>
/// <seealso cref="PushText"/>
/// <seealso cref="PushMessage"/>
public void PushMarkup(string markup)
public void PushMarkup(string markup, int priority=0)
{
PushMessage(FormattedMessage.FromMarkup(markup));
PushMessage(FormattedMessage.FromMarkup(markup), priority);
}
/// <summary>
/// Push another message containing raw text into this examine result, on its own line.
/// End message will be grouped by <see cref="priority"/>, then by group if one was started
/// then by ordinal comparison.
/// </summary>
/// <seealso cref="PushMarkup"/>
/// <seealso cref="PushMessage"/>
public void PushText(string text)
public void PushText(string text, int priority=0)
{
var msg = new FormattedMessage();
msg.AddText(text);
PushMessage(msg);
PushMessage(msg, priority);
}
/// <summary>
/// Adds a message directly without starting a newline after.
/// End message will be grouped by <see cref="priority"/>, then by group if one was started
/// then by ordinal comparison.
/// </summary>
/// <seealso cref="AddMarkup"/>
/// <seealso cref="AddText"/>
public void AddMessage(FormattedMessage message, int priority = 0)
{
if (message.Nodes.Count == 0)
return;
if (_currentGroupPart != null)
{
_currentGroupPart.Message.AddMessage(message);
}
else
{
Parts.Add(new ExamineMessagePart(message, priority, false, null));
}
}
/// <summary>
/// Adds markup directly without starting a newline after.
/// End message will be grouped by <see cref="priority"/>, then by group if one was started
/// then by ordinal comparison.
/// </summary>
/// <seealso cref="AddText"/>
/// <seealso cref="AddMessage"/>
public void AddMarkup(string markup, int priority=0)
{
AddMessage(FormattedMessage.FromMarkup(markup), priority);
}
/// <summary>
/// Adds text directly without starting a newline after.
/// End message will be grouped by <see cref="priority"/>, then by group if one was started
/// then by ordinal comparison.
/// </summary>
/// <seealso cref="AddMarkup"/>
/// <seealso cref="AddMessage"/>
public void AddText(string text, int priority=0)
{
var msg = new FormattedMessage();
msg.AddText(text);
AddMessage(msg, priority);
}
public struct ExamineGroupDisposable : IDisposable
{
private ExaminedEvent _event;
public ExamineGroupDisposable(ExaminedEvent @event)
{
_event = @event;
}
public void Dispose()
{
_event.PopGroup();
}
}
private record ExamineMessagePart(FormattedMessage Message, int Priority, bool DoNewLine, string? Group);
}
/// <summary>
/// Event raised directed at an entity that someone is attempting to examine
/// </summary>