-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Chat Refactor [DRAFT] #33858
base: master
Are you sure you want to change the base?
Chat Refactor [DRAFT] #33858
Conversation
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
I am here it is time |
First things first here's a shorter writeup of Slam's PR summary for those with Subway Surfer brains:
|
languages, coming to a station near you soon |
} | ||
|
||
public void SendMessage(string text, ChatSelectChannel channel) | ||
public void SendMessage(string text, ChatSelectChannel channel, RadioChannelPrototype? radioChannel) | ||
{ | ||
var str = text.ToString(); | ||
switch (channel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks like old-old code, is it left there just for backwards compatibility? If so i'd like to propos Proxy or Adapter design pattern here <_< low priority thing ofc...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe a little of todo with a link to separate issue for cleanup? even if issue will be placeholder - u would be able to fill it up later :godo:
{ | ||
var str = text.ToString(); | ||
|
||
_consoleHost.ExecuteCommand($"chat {channel.ID} \"{CommandParsing.Escape(str)}\""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would like to remove this in favour of direct message/event sending, launching some console commands looks ugly Q_Q
It also can cause unexpected results... but thats of none concern as we were living with this for so long...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a PR by ElectroSr that does this; it would have some minor merge conflicts, but I agree it would be nice to have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, valid. i agree.
{ | ||
public Dictionary<string, IContentMarkupTag> ContentMarkupTagTypes => new IContentMarkupTag[] | ||
{ | ||
new ColorValueContentTag(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be cool if code of DI would just scan assembly for all implementation of IContentMarkupTag and inject them there, and its easy to do! BUT! i am not sure if assembly-scanning is ok for sandboxing, and 2 - i'm not sure if maintainers are okay with that practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a similar implementation for RobustToolbox's MarkupTagTypes that does it this way, and cites sandboxing as the reason why. So it might be a limitation we have to live with. I agree it's not optimal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, i guess we are stuck with this. Well it is readable at least!
|
||
public sealed class ContentMarkupTagManager : ISharedContentMarkupTagManager | ||
{ | ||
public Dictionary<string, IContentMarkupTag> ContentMarkupTagTypes => new IContentMarkupTag[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
u decalare it as Dictionary, so u are OK with changing it in runtime? I'm not 100% sure what about maintainers policy on that but i'd better declare it as IReadOnlyDictionary if u want it initialized and never changed after creation. Maybe some frozen dictionary for bettere perf but same semantics?
|
||
namespace Content.Client.Chat.Managers | ||
{ | ||
public interface IChatManager : ISharedChatManager | ||
{ | ||
public void SendMessage(string text, ChatSelectChannel channel); | ||
public void SendMessage(string text, ChatSelectChannel channel, RadioChannelPrototype? radioChannel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but this ine is cursed :pepeHands: why do we leave it be and not kill it with fre? it is called RadioChannel but is changeling hiveming radio channel? thats all so confusing so i'd be better off without it... but oh well...
|
||
public List<MarkupNode>? OpenerProcessing(MarkupNode node) | ||
{ | ||
return new List<MarkupNode>() { new MarkupNode("color", new MarkupParameter(ColorValueLookup(node)), null) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm a little bit afraid of perf for heavily loaded server with all thouse lists - i'm not agitating to do some pre-emptive optimizations but it might be interesting to make all chat code allocation-less... maybe later!
public List<MarkupNode>? OpenerProcessing(MarkupNode node) | ||
{ | ||
|
||
var list = new List<MarkupNode>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dont allocate if u dont use! : D i'm not 100% sure it will be optimized by compiler so better move it down after IF yourself.
public List<MarkupNode>? OpenerProcessing(MarkupNode node) | ||
{ | ||
|
||
var list = new List<MarkupNode>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
@@ -15,6 +15,7 @@ public abstract class SpeechBubble : Control | |||
{ | |||
[Dependency] private readonly IEyeManager _eyeManager = default!; | |||
[Dependency] private readonly IEntityManager _entityManager = default!; | |||
[Dependency] protected readonly ISharedChatManager _chatManager = default!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd say this one needs updating to adapt usage of our protos >_> so maybe leave todo at least? SpeechType enum is irritating for example...
@@ -72,6 +73,7 @@ public sealed class EntryPoint : GameClient | |||
[Dependency] private readonly ILogManager _logManager = default!; | |||
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!; | |||
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!; | |||
[Dependency] private readonly ISharedContentMarkupTagManager _contentMarkupTagManager = default!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<_< what is that? i mean EntryPoint class... and why is manager needed here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good question; I think this is from me trying to get the content markup working, finding another fix and forgetting to remove this line.
@@ -17,6 +17,7 @@ public sealed partial class ChannelFilterPopup : Popup | |||
ChatChannel.Emotes, | |||
ChatChannel.Radio, | |||
ChatChannel.Notifications, | |||
ChatChannel.Announcements, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
read from protos too? ;w;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe leave todo...
@@ -33,8 +35,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) | |||
if (string.IsNullOrEmpty(message)) | |||
return; | |||
|
|||
var chat = _e.System<ChatSystem>(); | |||
chat.TrySendInGameOOCMessage(entity, message, InGameOOCChatType.Dead, false, shell, player); | |||
_chat.SendChannelMessage(message, "Dead", player, entity); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no magic strings pls, use protoId validation and maybe separate class for such dedicated constants :3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So maybe do some 'public static class ChatChannels' and declare all of channels that are 'usually required' there (stuff that u do not think people gonna easily remove from protos? : D). Declare them with ProtoIdValidate attribute and add xml-doc that this is just helper list for channel names constants (so ppl won't think they NEED to add const there for it to actually work')
@@ -35,7 +35,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) | |||
if (string.IsNullOrEmpty(message)) | |||
return; | |||
|
|||
_e.System<ChatSystem>().TrySendInGameOOCMessage(entity, message, InGameOOCChatType.Looc, false, shell, player); | |||
_e.System<ChatSystem>().TrySendInGameOOCMessage(entity, message, player); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wierd.... u have communication way (channel) for ooc but there u are using separate method... is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just a helper method; could very easily be replaced with SendChannelMessage entirely (as soon as I handle the whole magic strings issue)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh sorry then! if its just helper for usual way - its fine! i was just confused coz its kinda looking like old dirty methods.
@@ -26,5 +26,8 @@ | |||
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" /> | |||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be safely removed
@@ -115,7 +115,7 @@ public override void Shutdown() | |||
private void SendServerMessage(string message) | |||
{ | |||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); | |||
_chatManager.ChatMessageToAll(ChatChannel.Server, message, wrappedMessage, default, false, true); | |||
_chatManager.SendChannelMessage(wrappedMessage, "Server", null, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eeeeh, why use overload with exra null parameters? it looks wierd...
false, | ||
true, | ||
null); | ||
// CHAT-TODO: This is wrong - should not take the mapUid as an argument and instead it should be baked into the radio prototype |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is it even used! Q_Q
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've left random comments across the files, mostly just flagging magic strings... but the overall review is that this has a few revisions it needs to do:
- Avoidable API changes should be avoided for external callers. A substantial amount of the file changes are oneliners that change the API call from the old chat management call to the new system. This repetitive work is pointless, it makes the PR harder to review, and can be remedied by keeping the old API around when possible and adapting the old API to the new one via adaption functions.
- There's a few instances of code that's sitting next to old code when it would be better broken into one or more partial classes. This would allow the PR to be largely append-only, minimizing downstream impact and making what you're trying to do clearer.
- The "compose channels of specific conditions" design is brittle and prescriptive. Any change to any of the conditions could break the design of any number of channels. You can get the same level of flexibility here by encapsulating the send/receive/mutation rules of a given channel by creating C# classes that control the "rules" that a channel uses, with exposed variables and options to configure that ruleset. This has the advantage of balancing what you want to do - an extensible, modular system - with keeping complexity low.
- The process of sending a chat message from emission to reception requires domentation and graphing. As part of this you need to demonstrate how other developers would extend or listen in to parts of the system to customize it via raised events.
- Engineering "server-side" and "game-side" chat using the same systems I think is a dead end. They really should be different systems. It's part of the complexity of the blob-like current system.
- This really needs an understanding from the admins of what features the admins want. Chat is a key aspect of the game for admins and the current tools for dealing with it are insufficient; sanitization is only one half of the problem. This system needs to support per-message editing and deletion and mass deletion of chat messages. I'd encourage you to sit down with @nikthechampiongr and the rest of the admin team and get the requirements off them.
Overall this is movement in the right direction but it needs reigning in in places. Deffo for the best you didn't make my mistake of finishing an entire end-to-end attempt before asking for feedback.
@@ -12,26 +15,20 @@ internal sealed class ChatManager : IChatManager | |||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!; | |||
[Dependency] private readonly IClientAdminManager _adminMgr = default!; | |||
[Dependency] private readonly IEntitySystemManager _systems = default!; | |||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most important feedback is going to be that additions to the existing systems can be safely contained in a partial for the time being; this makes it easier to extend and review and has lower chance of causing merge conflicts on downstreams.
} | ||
|
||
public void SendMessage(string text, ChatSelectChannel channel) | ||
public void SendMessage(string text, ChatSelectChannel channel, RadioChannelPrototype? radioChannel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given if RadioChannelPrototype is set, it must be a radio message, this optional is superfluous, especially given there is already overloading of SendMessage below. It would make more sense to create an overload for SendMessage specifically for Radio.
public void SendMessage(string text, RadioChannelPrototype radioChannel)
case ChatSelectChannel.Radio: | ||
if (radioChannel != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general avoid nesting ifs inside of switches. Switches are there specifically to avoid cyclo. In this case this should be two cases; however this code should be moved to its own overload.
@@ -2,7 +2,10 @@ | |||
using Content.Client.Ghost; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file still running console commands for most chat methods is not ideal.
|
||
namespace Content.Client.Chat.Managers; | ||
|
||
public sealed class ContentMarkupTagManager : ISharedContentMarkupTagManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sticking a general comment here and won't repeat it; considering this is greenfield code in a notoriously horrible area of the codebase, ensure that the code has summary docstrings and in general has your intent communicated.
@@ -32,7 +34,7 @@ protected override void Added(EntityUid uid, MeteorSwarmComponent component, Gam | |||
Filter allPlayersInGame = Filter.Empty().AddWhere(GameTicker.UserHasJoinedGame); | |||
|
|||
if (component.Announcement is { } locId) | |||
_chat.DispatchFilteredAnnouncement(allPlayersInGame, Loc.GetString(locId), playSound: false, colorOverride: Color.Gold); | |||
_chatManager.SendChannelMessage(Loc.GetString(locId), "GameMessage", null, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic string needs removing
@@ -46,7 +48,7 @@ protected override void Added(EntityUid uid, T component, GameRuleComponent game | |||
Filter allPlayersInGame = Filter.Empty().AddWhere(GameTicker.UserHasJoinedGame); | |||
|
|||
if (stationEvent.StartAnnouncement != null) | |||
ChatSystem.DispatchFilteredAnnouncement(allPlayersInGame, Loc.GetString(stationEvent.StartAnnouncement), playSound: false, colorOverride: stationEvent.StartAnnouncementColor); | |||
ChatManager.SendChannelMessage(Loc.GetString(stationEvent.StartAnnouncement), "GameMessage", null, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic string needs removing
ChatManager.SendChannelMessage(Loc.GetString(stationEvent.EndAnnouncement), "GameMessage", null, null); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic string needs removing
@@ -198,8 +198,7 @@ private void AnnounceRandomTip() | |||
RaiseNetworkEvent(ev); | |||
} else | |||
{ | |||
_chat.ChatMessageToManyFiltered(Filter.Broadcast(), ChatChannel.OOC, tip, msg, | |||
EntityUid.Invalid, false, false, Color.MediumPurple); | |||
_chat.SendChannelMessage(msg, "Server", null, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic string needs removing
@@ -415,7 +415,7 @@ private async void CreateVotekickVote(ICommonSession? initiator, string[]? args) | |||
{ | |||
var message = Loc.GetString("ui-vote-votekick-not-enough-eligible", ("voters", eligibleVoterNumber.ToString()), ("requirement", eligibleVoterNumberRequirement.ToString())); | |||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); | |||
_chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, initiator.Channel); | |||
_chatManager.SendChannelMessage(wrappedMessage, "Server", null, null, new HashSet<ICommonSession>() { initiator }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic string needs removing
Does this currently support a BeforeChatSendEvent or similar? If not, it should! |
holy shit praise slambam |
About the PR
This is a refactor of the chat system! It is heavily in development and is not merge-ready; this PR is currently acting as a way for contributors and maintainers to preview the new system and provide feedback in areas where work has been done.
The goal of the chat refactor has been to achieve three things:
Due to the sheer scope of this PR and the many files changed just for the code to compile, below I will highlight the files and functions which are of importance in the refactor.
If you want to dive into the code and find the most important changes, I recommend:
ChatManager.cs
(server and client)CommunicationChannelPrototype.cs
SessionChatCondition.cs
EntityChatCondition.cs
ChatModifier.cs
IContentMarkupTag.cs
ChatUIController.cs
ISharedContentMarkupManager.cs
Note that I have built a lot of the code parallel to current chatcode, which means there remains a lot of unused old functions in the classes that will eventually be removed.
So where do we start? What's the core of this refactor?
CommunicationChannelPrototype
While the idea of having distinct channels (local speech, radio, OOC, adminchat) is nothing new in the codebase, previously their behaviors were largely spread out over multiple functions and classes that modified how they behaved. The new prototype CommunicationChannelPrototype aims to solve this.
A CommunicationChannel contains all information relevant for how a message in a channel should be treated; it contains checks for who may send and receive the message, how the message should be modified and if the message should be passed on to any other channels. This is all defined via .yaml, making it very easy to add to and modify the behaviors of the channels, as well as include inheritance for similar channels (i.e. radios).
The CommunicationChannel prototype contains the following:
PublishSessionChatConditions
This is a list of conditions that a player session must meet to be able to send a message over this channel. These conditions are defined via the new class
SessionChatCondition.cs
, and can be things like "Is this an admin?", "Is the OOC CCvar enabled?" or "Is the session a ghost?". SessionChatConditions can have other conditions as subconditions, creating an AND/OR tree that filters out any session that shouldn't be able to send to the channel.Additionally, SessionChatConditions can have EntityChatConditions as subconditions, which check the player entity as well (e.g. "Is the player entity alive?").
Note: Any message sent by the server skips evaluating these conditions, as it is assumed the server is always allowed to publish to a channel.
ConsumeCollections
A ConsumeCollection is a collection of conditions that defines a group of sessions and entities that may receive messages on the channel, and how that message should be modified for them. It does this via three lists:
Acts very similarly to PublishSessionChatConditions, in that you have a list of conditions that are evaluated against.
This list contains purely EntityChatConditions. While the ConsumeSessionChatConditions list may optionally evaluate player entities against these, the primary use of this list is to determine what non-player entities may react to messages on the channel. This could be defining a range for speech, or requiring a radio receiver on the entity.
The final list contains ChatModifiers, which take a message as an input and outputs a modified version of that message. This could be anything from accents to coloring texts. The reason why these are defined in the ConsumeCollection is because you might want different modifications based on different consumers, e.g. whispering should only have the text obfuscated for consumers at a certain range from the speaker (
Hello World
vs.Hel~~ Wo~ld
).ClientChatModifiers
This list contains ChatModifiers that are applied clientside. Previously nearly all message modifications were being on the server, which limited the ability to provide options where necessary. Having modifiers apply on the client opens up the possibility to give custom chat colors, accent accessibility options and other stuff like that.
AlwaysChildCommunicationChannels & BackupChildCommunicationChannels
These are lists of channels that will also receive the message and attempt to use it. This could be in the case of Deadchat, where attempting to speak in local chat always fails and instead gets redirected to deadchat instead. Another usecase would be radio usage, where you both speak in the radio channel and whisper channel at the same time.
That's most of the things in CommunicationChannelPrototype; if you want to see all properties, check out its class file. So how is this used? Well...
SendChannelMessage
If you want to send a message, either as part of a game system or as a session, you will now be using
SendChannelMessage
. This acts as a single pipeline for all messages being sent out to the player clients, as well as any message server systems should listen and react to. The basic use ofSendChannelMessage
is very simple. You include the message you want to send, the communication channel you want to target, and optionally the *sender session and sender entity:SendChannelMessage(string message, CommunicationChannelPrototype communicationChannel, ICommonSession? senderSession, EntityUid? senderEntity)
And that should be it! SendChannelMessage linearly goes through the prototype's conditions to see if you may send the message, gets a list of consumers, modifies the message and sends it out, all as defined in the chosen CommunicationChannelPrototype.
Some ChatConditions and ChatModifiers take in data from the optional
Dictionary<enum, object>? channelParameters
, which can be useful if you need granularity in the message you're sending, e.g. custom coloring of an announcement message. You can also supply a limited selection of sessions viaHashSet<ICommonSession>? targetSessions
, if you need granularity for who should receive the message (useful for admin messages that should only go out to a certain subset of admins).ChatFormattedMessageToHashset
is used as the output function and interfaces with the net manager.All old
say
,dsay
,me
,ooc
commands and similar have been converted to use SendChannelMessage, targetting the relevant channel.ContentMarkupTags
As part of the refactor, messages are now being sent over the network as FormattedMessages instead of plain strings. This makes it easier to distinguish between what's written text and what's markup. FormattedMessages contain a collection of MarkupNodes, which are either text nodes or markup nodes, with optional parameters. They're written out as
[ExampleNode]
.Previously these nodes were only used for actual markup formatting, such as the
[bold]
or[color="red"]
tags, but they are now being leveraged in the form of ContentMarkupTags. These tags are set via ChatModifiers and resolved on the client, and can be used to give more control over how a message is formatted. An example of this is the[EntityNameHeader="Urist McHands"]
tag, which is set on the server (allowing things like voicemasks to secretly modify the name) and then turned into plaintextUrist McHands
on the client.Other ChatModifiers may use this tag to provide finer formatting.
BoldTagChatModifier
is a clientsided chat modifier which looks for the EntityNameHeader tag and wraps it in a[bold]
tag, which is suitable for bolding names in the chatbox.Below is a commented example of the ICSpeech channel showing how the ChatModifiers apply the ContentMarkupTags, and how the conditions are evaluated.
ListenerEntitySystem & ListenerComponent
While player sessions are given messages via
ChatFormattedMessageToHashset
, entities (i.e. NPCs and items) are given the messages via events. To subscribe to these, any system aiming to utilize chat messages need to inheritListenerEntitySystem
, with the component inheritingListenerComponent
. This sets up the boilerplate to ensure you only get messages sent via the appropriate chat channel type (e.g. local, whispering, radio, all auditory channels and so on). OverrideOnListenerMessageReceived
to use the message in whatever way you see fit!That's a lot of words. Can I please have an image that explains the whole process better?
Yes, soon.
Areas of concern
Any big refactor is going to have some issues that will need to be resolved. In this case, I would ask reviewers to pay extra attention to the following areas, as they are places I am not very knowledgeable in:
IoC injection & simulation separation.
Due to ChatConditions/ChatModifiers not inheriting from any entity system, they are manually resolving system and manager dependencies. However, unlike a system, these classes are being instantiated seemingly every time a message is sent. I don't know how this would impact performance, but it seems like it should be better to somehow cache them? If so, how would one do that? I'm also concerned that this could trigger an entity check when outside the simulation and causing it to throw errors.
FormattedMessage over the network
SS14 is a very message-heavy game, and I am uncertain of the impact changing from sending just a simple string to sending a whole FormattedMessage has. If it ends up slowing down the game too much it might be worth considering going back to sending strings, and instead doing FormattedMessage parsing on the client instead.
Repeated iterations over FormattedMessage
Due to understandable protection limitations of FormattedMessage, a way to add new nodes and markup is to iterate over it and rebuild the FormattedMessage with the changes. This could possibly be unnecessary unoptimized work. Presumedly an engine change would allow smoother operations here, at the cost of safety cushions.
Prototype IDs and strings
I don't know if it's necessary to have a statically defined ProtoId variable in each system that intends to use SendChatMessage, or if it's fine to supply a normal string and let the ChatManager handle it. Let me know what the best practice is.
Missing features
Media
Requirements
Breaking changes
So what does this chat refactor mean for me as a fork?
radio_channels.yml
remains untouched, however if you have custom radio channels you will need to create a matching communication channel prototype. Ideally this would be resolved automatically, but the chat refactor must have some scope constraints. To do this, you can look atcommunication_channels.yml
in the radio section and copy an existing radio channel; of importance is that the prototype has the id:Radio[YourRadioPrototypelIdHere]
and theDefaultChannelParameters.RadioChannel
value be the radio prototype ID.You will also have to convert accents to Shared, but since I haven't done that part of the refactor yet, I can't explain how to do it. Note to self: remember to edit this part before merging, or it will be embarrassing.
Changelog
🆑