terminalCode Example
Chat UI Pattern with RPC Broadcasting
Chat UI with RPC Broadcasting
A Razor UI component for game chat that handles local input, RPC broadcasting, Steam filtering, and automatic message expiration.
Implementation
CSHARP
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.Utility
@namespace Sandbox
@inherits PanelComponent
<root>
<div class="output">
@foreach (var entry in Entries)
{
<div class="chat_entry">
@if (entry.steamid > 0)
{
<div class="avatar" style="background-image: url( avatar:@entry.steamid )"></div>
}
<div class="author">@entry.author</div>
<div class="message">@entry.message</div>
</div>
}
</div>
<div class="input">
<TextEntry @ref="InputBox" onsubmit="@ChatFinished"></TextEntry>
</div>
</root>
@code
{
TextEntry InputBox = default;
public record Entry(ulong steamid, string author, string message, RealTimeSince timeSinceAdded);
List<Entry> Entries = new();
protected override void OnUpdate()
{
if (InputBox is null)
return;
Panel.AcceptsFocus = false;
// Toggle chat focus with input action
if (Input.Pressed("chat"))
{
InputBox.Focus();
}
// Auto-remove entries after 20 seconds
if (Entries.RemoveAll(x => x.timeSinceAdded > 20.0f) > 0)
{
StateHasChanged();
}
// CSS state management
SetClass("open", InputBox.HasFocus);
SetClass("hide", Player.FindLocalPlayer()?.WantsHideHud ?? false);
}
void ChatFinished()
{
var text = InputBox.Text;
InputBox.Text = "";
if (string.IsNullOrWhiteSpace(text))
return;
AddText(text);
}
[ConCmd("say")]
private static void Say(string msg)
{
var chat = Game.ActiveScene.GetAll<Chat>().FirstOrDefault();
if (Application.IsDedicatedServer)
{
chat.AddSystemText(msg);
}
else
{
chat.AddText(msg);
}
}
[Rpc.Broadcast]
public void AddText(string message)
{
message = message.Truncate(300);
if (string.IsNullOrWhiteSpace(message))
return;
var author = Rpc.Caller.DisplayName;
var steamid = Rpc.Caller.SteamId;
Log.Info($"{author}: {message}");
// Steam content filtering
var filteredName = Steam.FilterName(author, steamid);
message = Steam.FilterChat(message, steamid);
Entries.Add(new Entry(steamid, filteredName, message, 0.0f));
StateHasChanged();
}
[Rpc.Broadcast(NetFlags.HostOnly)]
public void AddSystemText(string message, string icon = "ℹ️")
{
message = message.Truncate(300);
if (string.IsNullOrWhiteSpace(message))
return;
Entries.Add(new Entry(0, icon, message, 0.0f));
StateHasChanged();
}
}Key Patterns
TextEntry Component
CSHARP
<TextEntry @ref="InputBox" onsubmit="@ChatFinished"></TextEntry>
// In code:
void ChatFinished()
{
var text = InputBox.Text;
InputBox.Text = "";
// Process the submitted text...
}RPC Broadcasting
CSHARP
// Called by client, executes on host, broadcasts to all clients
[Rpc.Broadcast]
public void AddText(string message)
{
// Rpc.Caller contains the connection that initiated the RPC
var author = Rpc.Caller.DisplayName;
var steamid = Rpc.Caller.SteamId;
// This runs on all clients
Entries.Add(new Entry(steamid, author, message, 0.0f));
StateHasChanged(); // Trigger UI re-render
}
// Host-only broadcast
[Rpc.Broadcast(NetFlags.HostOnly)]
public void AddSystemText(string message, string icon)
{
// Only the host can call this
}Steam Integration
CSHARP
// Filter names and chat through Steam's content filters
var filteredName = Steam.FilterName(author, steamid);
var filteredMessage = Steam.FilterChat(message, steamid);Avatar Display
CSS
/* CSS for Steam avatar display */
.avatar {
background-image: url(avatar:76561197991348132);
/* The avatar: protocol automatically fetches Steam avatars */
}RealTimeSince for Expiration
CSHARP
// RealTimeSince automatically tracks time since creation
public record Entry(..., RealTimeSince timeSinceAdded);
// Check if 20 seconds have passed
if (entry.timeSinceAdded > 20.0f)
// Remove entryInput Focus Management
CSHARP
// Prevent panel from stealing focus
Panel.AcceptsFocus = false;
// Check if TextEntry has focus
SetClass("open", InputBox.HasFocus);
// Blur all inputs
Sandbox.UI.InputFocus.Clear();CSS Classes for State
These are toggled via SetClass() in OnUpdate() for reactive styling.
Was this helpful?