perf: Zero-alloc interpolation for SendMessage/Overhead APIs (#2434)

## Summary

Phase 1 of a multi-phase optimization to eliminate intermediate string allocations between `$"..."` interpolation and the packet text region for ModernUO's player-facing message APIs.

- Adds `[InterpolatedStringHandler]` overloads to every `Send*`/`Public/Local/Private/NonlocalOverheadMessage`/`Say`/`Emote`/`Whisper`/`Yell`/`SendLocalizedMessageTo` API in `OutgoingMessagePackets`, `Mobile`, and `Item`. Each overload is a 3-line shim that forwards `handler.Text` to the existing span-based path then calls `handler.Clear()` to return the rented `STArrayPool<char>` buffer (matches the established `SpanWriter.WriteAscii(ref RawInterpolatedStringHandler)` precedent).
- Converts `string text/args/affix/name` parameters to `ReadOnlySpan<char>` for consistency with the handler path. `lang` intentionally stays `string` (it's never interpolated and the `??= "ENU"` fallback stays cleaner).
- Adds `int charCount` overloads of the three `GetMaxMessage*Length` helpers so stackalloc sizing can avoid the redundant `ROS<char>` round-trip.
- Moves `Mobile` (17 methods) and `Item` (4 methods) message methods into new partial-class files (`Mobile.Messages.cs`, `Item.Messages.cs`) for organization.

No UOContent call sites change in this PR — existing `string`/`ROS<char>` calls compile unchanged via implicit conversion. Phase 2 (intermediate-string audit) and Phase 3 (cleanup PRs) follow.

## Files

- `Projects/Server/Network/Packets/OutgoingMessagePackets.cs` — `string` → `ROS<char>` for text params, `int charCount` length helpers added, class made `partial`
- `Projects/Server/Network/Packets/OutgoingMessagePackets.Interpolated.cs` (new) — 3 `ref RawInterpolatedStringHandler` extension overloads
- `Projects/Server/Mobiles/Mobile.cs` — message methods extracted (-262 lines)
- `Projects/Server/Mobiles/Mobile.Messages.cs` (new, 463 lines) — moved + ROS-converted methods + 25 handler overloads
- `Projects/Server/Items/Item.cs` — message methods extracted (-93 lines)
- `Projects/Server/Items/Item.Messages.cs` (new, 142 lines) — moved + ROS-converted methods + 4 handler overloads
- `Projects/Server.Tests/Tests/Network/Packets/Outgoing/MessagePacketTests.cs` — 3 new regression tests verifying byte-equivalence for the handler overloads
This commit is contained in:
Kamron Batman 2026-05-03 18:04:02 -07:00 committed by GitHub
parent b150c48328
commit 5f9fa88220
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 904 additions and 409 deletions

View file

@ -46,6 +46,29 @@ public class MessageTests
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact]
public void TestMessageLocalizedInterpolated()
{
var serial = (Serial)0x1024;
var graphic = 0x100;
var messageType = MessageType.Label;
var hue = 1024;
var font = 3;
var number = 150000;
var name = "Stuff";
var argValue = 42;
var expected = new MessageLocalized(
serial, graphic, messageType, hue, font, number, name, $"value: {argValue}"
).Compile();
using var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMessageLocalized(serial, graphic, messageType, hue, font, number, name, $"value: {argValue}");
var result = ns.SendBuffer.GetReadSpan();
AssertThat.Equal(result, expected);
}
[Fact] [Fact]
public void TestMessageLocalizedAffix() public void TestMessageLocalizedAffix()
{ {
@ -91,6 +114,59 @@ public class MessageTests
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact]
public void TestMessageLocalizedAffixInterpolated()
{
var serial = (Serial)0x1024;
var graphic = 0x100;
var messageType = MessageType.Label;
var hue = 1024;
var font = 3;
var number = 150000;
var name = "Stuff";
var affixType = AffixType.System;
var affix = "Affix";
var argValue = 42;
var expected = new MessageLocalizedAffix(
serial, graphic, messageType, hue, font, number, name, affixType, affix, $"value: {argValue}"
).Compile();
using var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMessageLocalizedAffix(
serial, graphic, messageType, hue, font, number, name, affixType, affix,
$"value: {argValue}"
);
var result = ns.SendBuffer.GetReadSpan();
AssertThat.Equal(result, expected);
}
[Fact]
public void TestUnicodeMessageInterpolated()
{
var serial = (Serial)0x1024;
var graphic = 0x100;
var messageType = MessageType.Label;
var hue = 1024;
var font = 3;
var lang = "ENU";
var name = "Stuff";
var who = "Alice";
var expected = new UnicodeMessage(
serial, graphic, messageType, hue, font, lang, name, $"hello, {who}"
).Compile();
using var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMessage(
serial, graphic, messageType, hue, font, false, lang, name, $"hello, {who}"
);
var result = ns.SendBuffer.GetReadSpan();
AssertThat.Equal(result, expected);
}
[Fact] [Fact]
public void TestAsciiMessage() public void TestAsciiMessage()
{ {

View file

@ -0,0 +1,139 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Item.Messages.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using Server.Buffers;
using Server.Network;
namespace Server;
public partial class Item
{
private readonly struct PublicVisibilityFilter : IBroadcastFilter
{
private readonly Item _source;
private readonly Point3D _worldLoc;
public PublicVisibilityFilter(Item source, Point3D worldLoc)
{
_source = source;
_worldLoc = worldLoc;
}
public bool Allow(NetState state)
{
var m = state.Mobile;
return m.CanSee(_source) && m.InRange(_worldLoc, _source.GetUpdateRange(m));
}
}
public void PublicOverheadMessage(MessageType type, int hue, bool ascii, ReadOnlySpan<char> text)
{
var worldLoc = GetWorldLocation();
OutgoingMessagePackets.BroadcastMessage(
m_Map, worldLoc, GetMaxUpdateRange(),
Serial, m_ItemID, type, hue, 3, ascii, "ENU", Name, text,
new PublicVisibilityFilter(this, worldLoc)
);
}
public void PublicOverheadMessage(MessageType type, int hue, int number, ReadOnlySpan<char> args = default)
{
var worldLoc = GetWorldLocation();
OutgoingMessagePackets.BroadcastMessageLocalized(
m_Map, worldLoc, GetMaxUpdateRange(),
Serial, m_ItemID, type, hue, 3, number, Name, args,
new PublicVisibilityFilter(this, worldLoc)
);
}
public void SendLocalizedMessageTo(Mobile to, int number, ReadOnlySpan<char> args = default)
{
if (Deleted || !to.CanSee(this))
{
return;
}
to?.NetState?.SendMessageLocalized(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", args);
}
public void SendLocalizedMessageTo(Mobile to, int number, AffixType affixType, ReadOnlySpan<char> affix, ReadOnlySpan<char> args)
{
if (Deleted || !to.CanSee(this))
{
return;
}
to?.NetState?.SendMessageLocalizedAffix(
Serial,
ItemID,
MessageType.Regular,
0x3B2,
3,
number,
"",
affixType,
affix,
args
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendLocalizedMessageTo(Mobile to, int number, int hue, ReadOnlySpan<char> args = default)
=> to?.NetState?.SendMessageLocalized(Serial, ItemID, MessageType.Regular, hue, 3, number, "", args);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendMessageTo(Mobile to, ReadOnlySpan<char> text, int hue = 0x3B2)
=> to?.NetState?.SendMessage(Serial, ItemID, MessageType.Regular, hue, 3, false, "ENU", "", text);
// ---------- Interpolated handler overloads ----------
public void PublicOverheadMessage(MessageType type, int hue, bool ascii, ref RawInterpolatedStringHandler text)
{
PublicOverheadMessage(type, hue, ascii, text.Text);
text.Clear();
}
public void PublicOverheadMessage(MessageType type, int hue, int number, ref RawInterpolatedStringHandler args)
{
PublicOverheadMessage(type, hue, number, args.Text);
args.Clear();
}
public void SendLocalizedMessageTo(Mobile to, int number, ref RawInterpolatedStringHandler args)
{
SendLocalizedMessageTo(to, number, args.Text);
args.Clear();
}
public void SendLocalizedMessageTo(Mobile to, int number, AffixType affixType, ReadOnlySpan<char> affix, ref RawInterpolatedStringHandler args)
{
SendLocalizedMessageTo(to, number, affixType, affix, args.Text);
args.Clear();
}
public void SendLocalizedMessageTo(Mobile to, int number, int hue, ref RawInterpolatedStringHandler args)
{
SendLocalizedMessageTo(to, number, hue, args.Text);
args.Clear();
}
public void SendMessageTo(Mobile to, int hue, ref RawInterpolatedStringHandler text)
{
SendMessageTo(to, text.Text, hue);
text.Clear();
}
}

View file

@ -3312,68 +3312,6 @@ public partial class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropert
Delete(); Delete();
} }
public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text)
{
if (m_Map == null)
{
return;
}
var worldLoc = GetWorldLocation();
var buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket();
foreach (var state in m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()))
{
var m = state.Mobile;
if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m)))
{
var length = OutgoingMessagePackets.CreateMessage(
buffer, Serial, m_ItemID, type, hue, 3, ascii, "ENU", Name, text
);
if (length != buffer.Length)
{
buffer = buffer[..length]; // Adjust to the actual size
}
state.Send(buffer);
}
}
}
public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "")
{
if (m_Map == null)
{
return;
}
var worldLoc = GetWorldLocation();
var buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket();
foreach (var state in m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()))
{
var m = state.Mobile;
if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m)))
{
var length = OutgoingMessagePackets.CreateMessageLocalized(
buffer, Serial, m_ItemID, type, hue, 3, number, Name, args
);
if (length != buffer.Length)
{
buffer = buffer[..length]; // Adjust to the actual size
}
state.Send(buffer);
}
}
}
public virtual void OnAfterDelete() public virtual void OnAfterDelete()
{ {
} }
@ -3898,37 +3836,6 @@ public partial class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropert
); );
} }
public void SendLocalizedMessageTo(Mobile to, int number, string args = "")
{
if (Deleted || !to.CanSee(this))
{
return;
}
to.NetState.SendMessageLocalized(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", args);
}
public void SendLocalizedMessageTo(Mobile to, int number, AffixType affixType, string affix, string args)
{
if (Deleted || !to.CanSee(this))
{
return;
}
to.NetState.SendMessageLocalizedAffix(
Serial,
ItemID,
MessageType.Regular,
0x3B2,
3,
number,
"",
affixType,
affix,
args
);
}
public virtual void OnSnoop(Mobile from) public virtual void OnSnoop(Mobile from)
{ {
} }

View file

@ -0,0 +1,389 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Mobile.Messages.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
using Server.Buffers;
using Server.Network;
namespace Server;
public partial class Mobile
{
// ---------- Broadcast filter structs ----------
private readonly struct PublicVisibilityFilter : IBroadcastFilter
{
private readonly Mobile _source;
private readonly bool _noLineOfSight;
private readonly AccessLevel _accessLevel;
public PublicVisibilityFilter(Mobile source, bool noLineOfSight, AccessLevel accessLevel)
{
_source = source;
_noLineOfSight = noLineOfSight;
_accessLevel = accessLevel;
}
public bool Allow(NetState state) =>
state.Mobile.AccessLevel >= _accessLevel &&
state.Mobile.CanSee(_source) &&
(_noLineOfSight || state.Mobile.InLOS(_source));
}
private readonly struct LocalizedVisibilityFilter : IBroadcastFilter
{
private readonly Mobile _source;
private readonly bool _noLineOfSight;
public LocalizedVisibilityFilter(Mobile source, bool noLineOfSight)
{
_source = source;
_noLineOfSight = noLineOfSight;
}
public bool Allow(NetState state) => state.Mobile.CanSee(_source) && (_noLineOfSight || state.Mobile.InLOS(_source));
}
private readonly struct NonlocalVisibilityFilter : IBroadcastFilter
{
private readonly Mobile _source;
private readonly NetState _exclude;
public NonlocalVisibilityFilter(Mobile source, NetState exclude)
{
_source = source;
_exclude = exclude;
}
public bool Allow(NetState state) => state != _exclude && state.Mobile.CanSee(_source);
}
// ---------- Say / Emote / Whisper / Yell ----------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Say(bool ascii, ReadOnlySpan<char> text) =>
PublicOverheadMessage(MessageType.Regular, SpeechHue, ascii, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Say(ReadOnlySpan<char> text) =>
PublicOverheadMessage(MessageType.Regular, SpeechHue, false, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Say(int number, AffixType type, ReadOnlySpan<char> affix, ReadOnlySpan<char> args) =>
PublicOverheadMessage(MessageType.Regular, SpeechHue, number, type, affix, args);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Say(int number, ReadOnlySpan<char> args = default) =>
PublicOverheadMessage(MessageType.Regular, SpeechHue, number, args);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Emote(ReadOnlySpan<char> text) =>
PublicOverheadMessage(MessageType.Emote, EmoteHue, false, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Emote(int number, ReadOnlySpan<char> args = default) =>
PublicOverheadMessage(MessageType.Emote, EmoteHue, number, args);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Whisper(ReadOnlySpan<char> text) =>
PublicOverheadMessage(MessageType.Whisper, WhisperHue, false, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Whisper(int number, ReadOnlySpan<char> args = default) =>
PublicOverheadMessage(MessageType.Whisper, WhisperHue, number, args);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Yell(ReadOnlySpan<char> text) =>
PublicOverheadMessage(MessageType.Yell, YellHue, false, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Yell(int number, ReadOnlySpan<char> args = default) =>
PublicOverheadMessage(MessageType.Yell, YellHue, number, args);
// ---------- PublicOverheadMessage ----------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PublicOverheadMessage(
MessageType type, int hue, bool ascii, ReadOnlySpan<char> text, bool noLineOfSight = true,
AccessLevel accessLevel = AccessLevel.Player
) => OutgoingMessagePackets.BroadcastMessage(
m_Map, m_Location,
Serial, Body, type, hue, 3, ascii, Language, Name, text,
new PublicVisibilityFilter(this, noLineOfSight, accessLevel)
);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PublicOverheadMessage(
MessageType type, int hue, int number, ReadOnlySpan<char> args = default, bool noLineOfSight = true
) => OutgoingMessagePackets.BroadcastMessageLocalized(
m_Map, m_Location,
Serial, Body, type, hue, 3, number, Name, args,
new LocalizedVisibilityFilter(this, noLineOfSight));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PublicOverheadMessage(
MessageType type, int hue, int number, AffixType affixType, ReadOnlySpan<char> affix,
ReadOnlySpan<char> args = default, bool noLineOfSight = false,
AccessLevel accessLevel = AccessLevel.Player
) => OutgoingMessagePackets.BroadcastMessageLocalizedAffix(
m_Map, m_Location,
Serial, Body, type, hue, 3, number, Name, affixType, affix, args,
new PublicVisibilityFilter(this, noLineOfSight, accessLevel)
);
// ---------- PrivateOverheadMessage ----------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrivateOverheadMessage(MessageType type, int hue, bool ascii, ReadOnlySpan<char> text, NetState state) =>
state.SendMessage(Serial, Body, type, hue, 3, ascii, m_Language, Name, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrivateOverheadMessage(MessageType type, int hue, int number, NetState state) =>
PrivateOverheadMessage(type, hue, number, default, state);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PrivateOverheadMessage(MessageType type, int hue, int number, ReadOnlySpan<char> args, NetState state) =>
state.SendMessageLocalized(Serial, Body, type, hue, 3, number, Name, args);
// ---------- LocalOverheadMessage ----------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LocalOverheadMessage(MessageType type, int hue, bool ascii, ReadOnlySpan<char> text) =>
m_NetState.SendMessage(Serial, Body, type, hue, 3, ascii, m_Language, Name, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LocalOverheadMessage(MessageType type, int hue, int number, ReadOnlySpan<char> args = default) =>
m_NetState.SendMessageLocalized(Serial, Body, type, hue, 3, number, Name, args);
// ---------- NonlocalOverheadMessage ----------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void NonlocalOverheadMessage(MessageType type, int hue, int number, ReadOnlySpan<char> args = default) =>
OutgoingMessagePackets.BroadcastMessageLocalized(
m_Map, m_Location,
Serial, Body, type, hue, 3, number, Name, args,
new NonlocalVisibilityFilter(this, m_NetState)
);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, ReadOnlySpan<char> text) =>
OutgoingMessagePackets.BroadcastMessage(
m_Map, m_Location,
Serial, Body, type, hue, 3, ascii, Language, Name, text,
new NonlocalVisibilityFilter(this, m_NetState)
);
// ---------- SendLocalizedMessage / SendMessage / SendAsciiMessage ----------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendLocalizedMessage(int number, ReadOnlySpan<char> args = default, int hue = 0x3B2) =>
m_NetState.SendMessageLocalized(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", args);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendLocalizedMessage(
int number, bool append, ReadOnlySpan<char> affix, ReadOnlySpan<char> args = default, int hue = 0x3B2
) => m_NetState.SendMessageLocalizedAffix(
Serial.MinusOne,
-1,
MessageType.Regular,
hue,
3,
number,
"System",
(append ? AffixType.Append : AffixType.Prepend) | AffixType.System,
affix,
args
);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendMessage(ReadOnlySpan<char> text) => SendMessage(0x3B2, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendMessage(int hue, ReadOnlySpan<char> text) =>
m_NetState.SendMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, false, "ENU", "System", text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendAsciiMessage(ReadOnlySpan<char> text) => SendAsciiMessage(0x3B2, text);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendAsciiMessage(int hue, ReadOnlySpan<char> text) =>
m_NetState.SendMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, true, null, "System", text);
// ---------- Interpolated handler overloads ----------
public void Say(bool ascii, ref RawInterpolatedStringHandler text)
{
PublicOverheadMessage(MessageType.Regular, SpeechHue, ascii, text.Text);
text.Clear();
}
public void Say(ref RawInterpolatedStringHandler text)
{
PublicOverheadMessage(MessageType.Regular, SpeechHue, false, text.Text);
text.Clear();
}
public void Say(int number, AffixType type, ReadOnlySpan<char> affix, ref RawInterpolatedStringHandler args)
{
PublicOverheadMessage(MessageType.Regular, SpeechHue, number, type, affix, args.Text);
args.Clear();
}
public void Say(int number, ref RawInterpolatedStringHandler args)
{
PublicOverheadMessage(MessageType.Regular, SpeechHue, number, args.Text);
args.Clear();
}
public void Emote(ref RawInterpolatedStringHandler text)
{
PublicOverheadMessage(MessageType.Emote, EmoteHue, false, text.Text);
text.Clear();
}
public void Emote(int number, ref RawInterpolatedStringHandler args)
{
PublicOverheadMessage(MessageType.Emote, EmoteHue, number, args.Text);
args.Clear();
}
public void Whisper(ref RawInterpolatedStringHandler text)
{
PublicOverheadMessage(MessageType.Whisper, WhisperHue, false, text.Text);
text.Clear();
}
public void Whisper(int number, ref RawInterpolatedStringHandler args)
{
PublicOverheadMessage(MessageType.Whisper, WhisperHue, number, args.Text);
args.Clear();
}
public void Yell(ref RawInterpolatedStringHandler text)
{
PublicOverheadMessage(MessageType.Yell, YellHue, false, text.Text);
text.Clear();
}
public void Yell(int number, ref RawInterpolatedStringHandler args)
{
PublicOverheadMessage(MessageType.Yell, YellHue, number, args.Text);
args.Clear();
}
public void PublicOverheadMessage(
MessageType type, int hue, bool ascii, ref RawInterpolatedStringHandler text, bool noLineOfSight = true,
AccessLevel accessLevel = AccessLevel.Player
)
{
PublicOverheadMessage(type, hue, ascii, text.Text, noLineOfSight, accessLevel);
text.Clear();
}
public void PublicOverheadMessage(MessageType type, int hue, int number, ref RawInterpolatedStringHandler args, bool noLineOfSight = true)
{
PublicOverheadMessage(type, hue, number, args.Text, noLineOfSight);
args.Clear();
}
public void PublicOverheadMessage(
MessageType type, int hue, int number, AffixType affixType, ReadOnlySpan<char> affix,
ref RawInterpolatedStringHandler args, bool noLineOfSight = false,
AccessLevel accessLevel = AccessLevel.Player
)
{
PublicOverheadMessage(type, hue, number, affixType, affix, args.Text, noLineOfSight, accessLevel);
args.Clear();
}
public void PrivateOverheadMessage(
MessageType type, int hue, bool ascii, ref RawInterpolatedStringHandler text, NetState state
)
{
PrivateOverheadMessage(type, hue, ascii, text.Text, state);
text.Clear();
}
public void PrivateOverheadMessage(
MessageType type, int hue, int number, ref RawInterpolatedStringHandler args, NetState state
)
{
PrivateOverheadMessage(type, hue, number, args.Text, state);
args.Clear();
}
public void LocalOverheadMessage(MessageType type, int hue, bool ascii, ref RawInterpolatedStringHandler text)
{
LocalOverheadMessage(type, hue, ascii, text.Text);
text.Clear();
}
public void LocalOverheadMessage(MessageType type, int hue, int number, ref RawInterpolatedStringHandler args)
{
LocalOverheadMessage(type, hue, number, args.Text);
args.Clear();
}
public void NonlocalOverheadMessage(MessageType type, int hue, int number, ref RawInterpolatedStringHandler args)
{
NonlocalOverheadMessage(type, hue, number, args.Text);
args.Clear();
}
public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, ref RawInterpolatedStringHandler text)
{
NonlocalOverheadMessage(type, hue, ascii, text.Text);
text.Clear();
}
public void SendLocalizedMessage(int number, ref RawInterpolatedStringHandler args, int hue = 0x3B2)
{
SendLocalizedMessage(number, args.Text, hue);
args.Clear();
}
public void SendLocalizedMessage(
int number, bool append, ReadOnlySpan<char> affix, ref RawInterpolatedStringHandler args, int hue = 0x3B2
)
{
SendLocalizedMessage(number, append, affix, args.Text, hue);
args.Clear();
}
public void SendMessage(ref RawInterpolatedStringHandler text)
{
SendMessage(0x3B2, text.Text);
text.Clear();
}
public void SendMessage(int hue, ref RawInterpolatedStringHandler text)
{
SendMessage(hue, text.Text);
text.Clear();
}
public void SendAsciiMessage(ref RawInterpolatedStringHandler text)
{
SendAsciiMessage(0x3B2, text.Text);
text.Clear();
}
public void SendAsciiMessage(int hue, ref RawInterpolatedStringHandler text)
{
SendAsciiMessage(hue, text.Text);
text.Clear();
}
}

View file

@ -5611,9 +5611,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
ProcessDelta(); ProcessDelta();
var regBuffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket(); var regBuffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text.Length)].InitializePacket();
var mutBuffer = var mutBuffer =
stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(mutatedText)].InitializePacket(); stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(mutatedText.Length)].InitializePacket();
// TODO: Should this be sorted like onSpeech is below? // TODO: Should this be sorted like onSpeech is below?
for (var i = 0; i < hears.Count; ++i) for (var i = 0; i < hears.Count; ++i)
@ -8089,29 +8089,6 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
public void SayTo(Mobile to, int number, string args = "") => public void SayTo(Mobile to, int number, string args = "") =>
to.NetState.SendMessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, args); to.NetState.SendMessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, args);
public void Say(bool ascii, string text) => PublicOverheadMessage(MessageType.Regular, SpeechHue, ascii, text);
public void Say(string text) => PublicOverheadMessage(MessageType.Regular, SpeechHue, false, text);
public void Say(int number, AffixType type, string affix, string args) =>
PublicOverheadMessage(MessageType.Regular, SpeechHue, number, type, affix, args);
public void Say(int number, string args = "") => PublicOverheadMessage(MessageType.Regular, SpeechHue, number, args);
public void Emote(string text) => PublicOverheadMessage(MessageType.Emote, EmoteHue, false, text);
public void Emote(int number, string args = "") => PublicOverheadMessage(MessageType.Emote, EmoteHue, number, args);
public void Whisper(string text) => PublicOverheadMessage(MessageType.Whisper, WhisperHue, false, text);
public void Whisper(int number, string args = "") =>
PublicOverheadMessage(MessageType.Whisper, WhisperHue, number, args);
public void Yell(string text) => PublicOverheadMessage(MessageType.Yell, YellHue, false, text);
public void Yell(int number, string args = "") =>
PublicOverheadMessage(MessageType.Yell, YellHue, number, args);
public bool SendHuePicker(HuePicker p) public bool SendHuePicker(HuePicker p)
{ {
if (m_NetState != null) if (m_NetState != null)
@ -8844,245 +8821,6 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
public Direction GetDirectionTo(IPoint2D p, bool run = false) => public Direction GetDirectionTo(IPoint2D p, bool run = false) =>
p == null ? Direction.North | (run ? Direction.Running : 0) : GetDirectionTo(p.X, p.Y, run); p == null ? Direction.North | (run ? Direction.Running : 0) : GetDirectionTo(p.X, p.Y, run);
public void PublicOverheadMessage(
MessageType type, int hue, bool ascii, string text, bool noLineOfSight = true,
AccessLevel accessLevel = AccessLevel.Player
)
{
if (m_Map == null)
{
return;
}
var buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket();
foreach (var state in m_Map.GetClientsInRange(m_Location))
{
if (
state.Mobile.AccessLevel >= accessLevel &&
state.Mobile.CanSee(this) &&
(noLineOfSight || state.Mobile.InLOS(this))
)
{
var length = OutgoingMessagePackets.CreateMessage(
buffer,
Serial,
Body,
type,
hue,
3,
ascii,
Language,
Name,
text
);
if (length != buffer.Length)
{
buffer = buffer[..length]; // Adjust to the actual size
}
state.Send(buffer);
}
}
}
public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "", bool noLineOfSight = true)
{
if (m_Map == null)
{
return;
}
var buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket();
foreach (var state in m_Map.GetClientsInRange(m_Location))
{
if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this)))
{
var length = OutgoingMessagePackets.CreateMessageLocalized(
buffer,
Serial,
Body,
type,
hue,
3,
number,
Name,
args
);
if (length != buffer.Length)
{
buffer = buffer[..length]; // Adjust to the actual size
}
state.Send(buffer);
}
}
}
public void PublicOverheadMessage(
MessageType type, int hue, int number, AffixType affixType, string affix,
string args = "", bool noLineOfSight = false,
AccessLevel accessLevel = AccessLevel.Player
)
{
if (m_Map == null)
{
return;
}
var buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedAffixLength(affix, args)]
.InitializePacket();
foreach (var state in m_Map.GetClientsInRange(m_Location))
{
if (
state.Mobile.AccessLevel >= accessLevel &&
state.Mobile.CanSee(this) &&
(noLineOfSight || state.Mobile.InLOS(this))
)
{
var length = OutgoingMessagePackets.CreateMessageLocalizedAffix(
buffer,
Serial,
Body,
type,
hue,
3,
number,
Name,
affixType,
affix,
args
);
if (length != buffer.Length)
{
buffer = buffer[..length]; // Adjust to the actual size
}
state.Send(buffer);
}
}
}
public void PrivateOverheadMessage(MessageType type, int hue, bool ascii, string text, NetState state)
{
state.SendMessage(Serial, Body, type, hue, 3, ascii, m_Language, Name, text);
}
public void PrivateOverheadMessage(MessageType type, int hue, int number, NetState state) =>
PrivateOverheadMessage(type, hue, number, "", state);
public void PrivateOverheadMessage(MessageType type, int hue, int number, string args, NetState state) =>
state.SendMessageLocalized(Serial, Body, type, hue, 3, number, Name, args);
public void LocalOverheadMessage(MessageType type, int hue, bool ascii, string text) =>
m_NetState.SendMessage(Serial, Body, type, hue, 3, ascii, m_Language, Name, text);
public void LocalOverheadMessage(MessageType type, int hue, int number, string args = "") =>
m_NetState.SendMessageLocalized(Serial, Body, type, hue, 3, number, Name, args);
public void NonlocalOverheadMessage(MessageType type, int hue, int number, string args = "")
{
if (m_Map == null)
{
return;
}
var buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket();
foreach (var state in m_Map.GetClientsInRange(m_Location))
{
if (state != m_NetState && state.Mobile.CanSee(this))
{
var length = OutgoingMessagePackets.CreateMessageLocalized(
buffer,
Serial,
Body,
type,
hue,
3,
number,
Name,
args
);
if (length != buffer.Length)
{
buffer = buffer[..length]; // Adjust to the actual size
}
state.Send(buffer);
}
}
}
public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, string text)
{
if (m_Map == null)
{
return;
}
var buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket();
foreach (var state in m_Map.GetClientsInRange(m_Location))
{
if (state != m_NetState && state.Mobile.CanSee(this))
{
var length = OutgoingMessagePackets.CreateMessage(
buffer,
Serial,
Body,
type,
hue,
3,
ascii,
Language,
Name,
text
);
if (length != buffer.Length)
{
buffer = buffer[..length]; // Adjust to the actual size
}
state.Send(buffer);
}
}
}
public void SendLocalizedMessage(int number, string args = "", int hue = 0x3B2) =>
m_NetState.SendMessageLocalized(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", args);
public void SendLocalizedMessage(int number, bool append, string affix, string args = "", int hue = 0x3B2) =>
m_NetState.SendMessageLocalizedAffix(
Serial.MinusOne,
-1,
MessageType.Regular,
hue,
3,
number,
"System",
(append ? AffixType.Append : AffixType.Prepend) | AffixType.System,
affix,
args
);
public void SendMessage(string text) => SendMessage(0x3B2, text);
public void SendMessage(int hue, string text) =>
m_NetState.SendMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, false, "ENU", "System", text);
public void SendAsciiMessage(string text) => SendAsciiMessage(0x3B2, text);
public void SendAsciiMessage(int hue, string text) =>
m_NetState.SendMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, true, null, "System", text);
/// <summary> /// <summary>
/// Overridable. Event invoked when the Mobile is double clicked. By default, this method can either dismount or open the /// Overridable. Event invoked when the Mobile is double clicked. By default, this method can either dismount or open the
/// paperdoll. /// paperdoll.

View file

@ -0,0 +1,21 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: IBroadcastFilter.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
namespace Server.Network;
public interface IBroadcastFilter
{
bool Allow(NetState state);
}

View file

@ -0,0 +1,182 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: OutgoingMessagePackets.Broadcast.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
namespace Server.Network;
public static partial class OutgoingMessagePackets
{
public static void BroadcastMessageLocalized<TFilter>(
Map map, Point3D location,
Serial serial, int graphic, MessageType type, int hue, int font,
int number, ReadOnlySpan<char> name, ReadOnlySpan<char> args,
TFilter filter
) where TFilter : struct, IBroadcastFilter
{
if (map == null)
{
return;
}
var buffer = stackalloc byte[GetMaxMessageLocalizedLength(args.Length)].InitializePacket();
foreach (var state in map.GetClientsInRange(location))
{
if (filter.Allow(state))
{
var length = CreateMessageLocalized(
buffer, serial, graphic, type, hue, font, number, name, args
);
if (length != buffer.Length)
{
buffer = buffer[..length];
}
state.Send(buffer);
}
}
}
public static void BroadcastMessageLocalized<TFilter>(
Map map, Point3D location, int range,
Serial serial, int graphic, MessageType type, int hue, int font,
int number, ReadOnlySpan<char> name, ReadOnlySpan<char> args,
TFilter filter
) where TFilter : struct, IBroadcastFilter
{
if (map == null)
{
return;
}
var buffer = stackalloc byte[GetMaxMessageLocalizedLength(args.Length)].InitializePacket();
foreach (var state in map.GetClientsInRange(location, range))
{
if (filter.Allow(state))
{
var length = CreateMessageLocalized(
buffer, serial, graphic, type, hue, font, number, name, args
);
if (length != buffer.Length)
{
buffer = buffer[..length];
}
state.Send(buffer);
}
}
}
public static void BroadcastMessageLocalizedAffix<TFilter>(
Map map, Point3D location,
Serial serial, int graphic, MessageType type, int hue, int font,
int number, ReadOnlySpan<char> name, AffixType affixType,
ReadOnlySpan<char> affix, ReadOnlySpan<char> args,
TFilter filter
) where TFilter : struct, IBroadcastFilter
{
if (map == null)
{
return;
}
var buffer = stackalloc byte[GetMaxMessageLocalizedAffixLength(affix.Length, args.Length)].InitializePacket();
foreach (var state in map.GetClientsInRange(location))
{
if (filter.Allow(state))
{
var length = CreateMessageLocalizedAffix(
buffer, serial, graphic, type, hue, font, number, name, affixType, affix, args
);
if (length != buffer.Length)
{
buffer = buffer[..length];
}
state.Send(buffer);
}
}
}
public static void BroadcastMessage<TFilter>(
Map map, Point3D location,
Serial serial, int graphic, MessageType type, int hue, int font, bool ascii,
string lang, ReadOnlySpan<char> name, ReadOnlySpan<char> text,
TFilter filter
) where TFilter : struct, IBroadcastFilter
{
if (map == null)
{
return;
}
var buffer = stackalloc byte[GetMaxMessageLength(text.Length)].InitializePacket();
foreach (var state in map.GetClientsInRange(location))
{
if (filter.Allow(state))
{
var length = CreateMessage(
buffer, serial, graphic, type, hue, font, ascii, lang, name, text
);
if (length != buffer.Length)
{
buffer = buffer[..length];
}
state.Send(buffer);
}
}
}
public static void BroadcastMessage<TFilter>(
Map map, Point3D location, int range,
Serial serial, int graphic, MessageType type, int hue, int font, bool ascii,
string lang, ReadOnlySpan<char> name, ReadOnlySpan<char> text,
TFilter filter
) where TFilter : struct, IBroadcastFilter
{
if (map == null)
{
return;
}
var buffer = stackalloc byte[GetMaxMessageLength(text.Length)].InitializePacket();
foreach (var state in map.GetClientsInRange(location, range))
{
if (filter.Allow(state))
{
var length = CreateMessage(
buffer, serial, graphic, type, hue, font, ascii, lang, name, text
);
if (length != buffer.Length)
{
buffer = buffer[..length];
}
state.Send(buffer);
}
}
}
}

View file

@ -0,0 +1,57 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: OutgoingMessagePackets.Interpolated.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using Server.Buffers;
namespace Server.Network;
public static partial class OutgoingMessagePackets
{
public static void SendMessageLocalized(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, int number,
ReadOnlySpan<char> name,
ref RawInterpolatedStringHandler args
)
{
ns.SendMessageLocalized(serial, graphic, type, hue, font, number, name, args.Text);
args.Clear();
}
public static void SendMessageLocalizedAffix(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, int number,
ReadOnlySpan<char> name, AffixType affixType, ReadOnlySpan<char> affix,
ref RawInterpolatedStringHandler args
)
{
ns.SendMessageLocalizedAffix(
serial, graphic, type, hue, font, number, name, affixType, affix, args.Text
);
args.Clear();
}
public static void SendMessage(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, bool ascii,
string lang, ReadOnlySpan<char> name,
ref RawInterpolatedStringHandler text
)
{
ns.SendMessage(serial, graphic, type, hue, font, ascii, lang, name, text.Text);
text.Clear();
}
}

View file

@ -29,11 +29,12 @@ public enum AffixType : byte
System = 0x02 System = 0x02
} }
public static class OutgoingMessagePackets public static partial class OutgoingMessagePackets
{ {
public static void SendMessageLocalized( public static void SendMessageLocalized(
this NetState ns, this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name = "", string args = "" Serial serial, int graphic, MessageType type, int hue, int font, int number,
ReadOnlySpan<char> name = default, ReadOnlySpan<char> args = default
) )
{ {
if (ns.CannotSendPackets()) if (ns.CannotSendPackets())
@ -41,7 +42,7 @@ public static class OutgoingMessagePackets
return; return;
} }
var buffer = stackalloc byte[GetMaxMessageLocalizedLength(args)].InitializePacket(); var buffer = stackalloc byte[GetMaxMessageLocalizedLength(args.Length)].InitializePacket();
var length = CreateMessageLocalized( var length = CreateMessageLocalized(
buffer, serial, graphic, type, hue, font, number, name, args buffer, serial, graphic, type, hue, font, number, name, args
); );
@ -50,11 +51,16 @@ public static class OutgoingMessagePackets
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLocalizedLength(string args) => 50 + (args?.Length ?? 0) * 2; public static int GetMaxMessageLocalizedLength(int argsCharCount) => 50 + argsCharCount * 2;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLocalizedLength(ReadOnlySpan<char> args) =>
GetMaxMessageLocalizedLength(args.Length);
public static int CreateMessageLocalized( public static int CreateMessageLocalized(
Span<byte> buffer, Span<byte> buffer,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name = "", string args = "" Serial serial, int graphic, MessageType type, int hue, int font, int number,
ReadOnlySpan<char> name = default, ReadOnlySpan<char> args = default
) )
{ {
if (buffer[0] != 0) if (buffer[0] != 0)
@ -62,9 +68,6 @@ public static class OutgoingMessagePackets
return buffer.Length; return buffer.Length;
} }
name ??= "";
args ??= "";
if (hue == 0) if (hue == 0)
{ {
hue = 0x3B2; hue = 0x3B2;
@ -88,8 +91,9 @@ public static class OutgoingMessagePackets
public static void SendMessageLocalizedAffix( public static void SendMessageLocalizedAffix(
this NetState ns, this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name, Serial serial, int graphic, MessageType type, int hue, int font, int number,
AffixType affixType, string affix = "", string args = "" ReadOnlySpan<char> name, AffixType affixType,
ReadOnlySpan<char> affix = default, ReadOnlySpan<char> args = default
) )
{ {
if (ns.CannotSendPackets()) if (ns.CannotSendPackets())
@ -97,7 +101,7 @@ public static class OutgoingMessagePackets
return; return;
} }
var buffer = stackalloc byte[GetMaxMessageLocalizedAffixLength(affix, args)].InitializePacket(); var buffer = stackalloc byte[GetMaxMessageLocalizedAffixLength(affix.Length, args.Length)].InitializePacket();
var length = CreateMessageLocalizedAffix( var length = CreateMessageLocalizedAffix(
buffer, serial, graphic, type, hue, font, number, name, affixType, affix, args buffer, serial, graphic, type, hue, font, number, name, affixType, affix, args
); );
@ -106,13 +110,18 @@ public static class OutgoingMessagePackets
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLocalizedAffixLength(string affix, string args) => public static int GetMaxMessageLocalizedAffixLength(int affixCharCount, int argsCharCount) =>
52 + (affix?.Length ?? 0) + (args?.Length ?? 0) * 2; 52 + affixCharCount + argsCharCount * 2;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLocalizedAffixLength(ReadOnlySpan<char> affix, ReadOnlySpan<char> args) =>
GetMaxMessageLocalizedAffixLength(affix.Length, args.Length);
public static int CreateMessageLocalizedAffix( public static int CreateMessageLocalizedAffix(
Span<byte> buffer, Span<byte> buffer,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name, Serial serial, int graphic, MessageType type, int hue, int font, int number,
AffixType affixType, string affix = "", string args = "" ReadOnlySpan<char> name, AffixType affixType,
ReadOnlySpan<char> affix = default, ReadOnlySpan<char> args = default
) )
{ {
if (buffer[0] != 0) if (buffer[0] != 0)
@ -120,10 +129,6 @@ public static class OutgoingMessagePackets
return buffer.Length; return buffer.Length;
} }
name ??= "";
affix ??= "";
args ??= "";
if (hue == 0) if (hue == 0)
{ {
hue = 0x3B2; hue = 0x3B2;
@ -149,7 +154,8 @@ public static class OutgoingMessagePackets
public static void SendMessage( public static void SendMessage(
this NetState ns, this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, bool ascii, string lang, string name, string text Serial serial, int graphic, MessageType type, int hue, int font, bool ascii,
string lang, ReadOnlySpan<char> name, ReadOnlySpan<char> text
) )
{ {
if (ns.CannotSendPackets()) if (ns.CannotSendPackets())
@ -157,7 +163,7 @@ public static class OutgoingMessagePackets
return; return;
} }
var buffer = stackalloc byte[GetMaxMessageLength(text)].InitializePacket(); var buffer = stackalloc byte[GetMaxMessageLength(text.Length)].InitializePacket();
var length = CreateMessage( var length = CreateMessage(
buffer, buffer,
serial, serial,
@ -175,7 +181,10 @@ public static class OutgoingMessagePackets
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLength(string text) => 50 + (text?.Length ?? 0) * 2; public static int GetMaxMessageLength(int textCharCount) => 50 + textCharCount * 2;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLength(ReadOnlySpan<char> text) => GetMaxMessageLength(text.Length);
public static int CreateMessage( public static int CreateMessage(
Span<byte> buffer, Span<byte> buffer,
@ -186,8 +195,8 @@ public static class OutgoingMessagePackets
int font, int font,
bool ascii, bool ascii,
string lang, string lang,
string name, ReadOnlySpan<char> name,
string text ReadOnlySpan<char> text
) )
{ {
if (buffer[0] != 0) if (buffer[0] != 0)
@ -195,8 +204,6 @@ public static class OutgoingMessagePackets
return buffer.Length; return buffer.Length;
} }
name ??= "";
text ??= "";
lang ??= "ENU"; lang ??= "ENU";
if (hue == 0) if (hue == 0)

View file

@ -110,7 +110,7 @@ public static class World
public static void Broadcast(int hue, bool ascii, string text) public static void Broadcast(int hue, bool ascii, string text)
{ {
var length = OutgoingMessagePackets.GetMaxMessageLength(text); var length = OutgoingMessagePackets.GetMaxMessageLength(text.Length);
var buffer = stackalloc byte[length].InitializePacket(); var buffer = stackalloc byte[length].InitializePacket();
@ -140,7 +140,7 @@ public static class World
public static void BroadcastStaff(int hue, bool ascii, string text) public static void BroadcastStaff(int hue, bool ascii, string text)
{ {
var length = OutgoingMessagePackets.GetMaxMessageLength(text); var length = OutgoingMessagePackets.GetMaxMessageLength(text.Length);
var buffer = stackalloc byte[length].InitializePacket(); var buffer = stackalloc byte[length].InitializePacket();

View file

@ -730,7 +730,7 @@ namespace Server.Engines.MLQuests
} }
else if (quester is Item item) else if (quester is Item item)
{ {
item.SendLocalizedMessageTo(pm, cliloc, args, SpeechColor); item.SendLocalizedMessageTo(pm, cliloc, SpeechColor, args);
} }
else else
{ {

View file

@ -88,11 +88,11 @@ public partial class PowderOfTranslocation : Item
// The ~1_translocationItem~ glows with green energy and absorbs magical power from the powder. // The ~1_translocationItem~ glows with green energy and absorbs magical power from the powder.
if (_transItemName.Number > 0) if (_transItemName.Number > 0)
{ {
item.SendLocalizedMessageTo(from, 1054139, $"#{_transItemName.Number}", 0x43); item.SendLocalizedMessageTo(from, 1054139, 0x43, $"#{_transItemName.Number}");
} }
else if (_transItemName.String != null) else if (_transItemName.String != null)
{ {
item.SendLocalizedMessageTo(from, 1054139, _transItemName.String, 0x43); item.SendLocalizedMessageTo(from, 1054139, 0x43, _transItemName.String);
} }
} }
} }

View file

@ -1,21 +0,0 @@
using Server.Network;
namespace Server;
public static class MessageHelper
{
public static void SendLocalizedMessageTo(this Item from, Mobile to, int number, int hue)
{
SendLocalizedMessageTo(from, to, number, "", hue);
}
public static void SendLocalizedMessageTo(this Item from, Mobile to, int number, string args, int hue)
{
to?.NetState.SendMessageLocalized(from.Serial, from.ItemID, MessageType.Regular, hue, 3, number, "", args);
}
public static void SendMessageTo(this Item from, Mobile to, string text, int hue)
{
to?.NetState.SendMessage(from.Serial, from.ItemID, MessageType.Regular, hue, 3, false, "ENU", "", text);
}
}