Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
Leath Cooper
4ade957f62 Merge branch 'main' into feat-NATS 2024-11-16 14:13:42 -05:00
Leath Cooper
c06c84ca9c NATS driven broadcast 2024-10-19 17:52:53 -04:00
Leath Cooper
c9ae5e3bd2 NATS 2024-10-19 16:10:11 -04:00
9 changed files with 174 additions and 38 deletions

View file

@ -41,4 +41,7 @@ public static partial class EventSink
public static event Action ServerStarted; public static event Action ServerStarted;
public static void InvokeServerStarted() => ServerStarted?.Invoke(); public static void InvokeServerStarted() => ServerStarted?.Invoke();
public static event Action<string, int> IncomingMessage;
public static void InvokeIncomingMessage(string message, int hue) => IncomingMessage?.Invoke(message, hue);
} }

View file

@ -0,0 +1,20 @@
using NATS.Client.Core;
using NATS.Client.JetStream;
using NATS.Net;
namespace Server;
public static class MessageBus
{
private static NatsClient natsClient;
public static NatsClient Client => natsClient;
private static INatsJSContext jetstreamContext;
public static INatsJSContext Context => jetstreamContext;
public static void Start()
{
natsClient = new NatsClient();
jetstreamContext = natsClient.CreateJetStreamContext();
}
}

View file

@ -233,6 +233,8 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
SetLastMoved(); SetLastMoved();
World.AddEntity(this); World.AddEntity(this);
MessageBus.Publish("items.created", $"{Serial}:{itemID}");
} }
public Item(Serial serial) => Serial = serial; public Item(Serial serial) => Serial = serial;
@ -1072,6 +1074,8 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
{ {
writer.WriteEncodedInt(info.m_SavedFlags); writer.WriteEncodedInt(info.m_SavedFlags);
} }
MessageBus.Publish("items.serialize", $"{Serial}:{ItemID}");
} }
public void MoveToWorld(WorldLocation worldLocation) public void MoveToWorld(WorldLocation worldLocation)
@ -3036,6 +3040,8 @@ public class Item : IHued, IComparable<Item>, ISpawnable, IObjectPropertyListEnt
// if (version < 9) // if (version < 9)
VerifyCompactInfo(); VerifyCompactInfo();
MessageBus.Publish("items.deserialize", $"{Serial}:{ItemID}");
} }
private void FixHolding_Sandbox() private void FixHolding_Sandbox()

View file

@ -190,9 +190,11 @@ public static class Core
if (fi.Directory != null && Directory.Exists(fi.Directory.FullName)) if (fi.Directory != null && Directory.Exists(fi.Directory.FullName))
{ {
fullPath = fi.Directory.EnumerateFiles( fullPath = fi.Directory.EnumerateFiles(
fi.Name, fi.Name,
new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive } new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }
).FirstOrDefault()?.FullName; )
.FirstOrDefault()
?.FullName;
} }
} }
@ -318,6 +320,7 @@ public static class Core
process.Start(); process.Start();
} }
logger.Information("Restart done"); logger.Information("Restart done");
} }
catch (Exception e) catch (Exception e)
@ -383,13 +386,15 @@ public static class Core
Utility.PopColor(); Utility.PopColor();
Utility.PushColor(ConsoleColor.DarkGray); Utility.PushColor(ConsoleColor.DarkGray);
Console.WriteLine(@"Copyright 2019-2023 ModernUO Development Team Console.WriteLine(
@"Copyright 2019-2023 ModernUO Development Team
This program comes with ABSOLUTELY NO WARRANTY; This program comes with ABSOLUTELY NO WARRANTY;
This is free software, and you are welcome to redistribute it under certain conditions. This is free software, and you are welcome to redistribute it under certain conditions.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
".TrimMultiline()); ".TrimMultiline()
);
Utility.PopColor(); Utility.PopColor();
Console.CancelKeyPress += Console_CancelKeyPressed; Console.CancelKeyPress += Console_CancelKeyPressed;
@ -429,6 +434,8 @@ public static class Core
TileMatrixLoader.LoadTileMatrix(); TileMatrixLoader.LoadTileMatrix();
MessageBus.Start();
RegionJsonSerializer.LoadRegions(); RegionJsonSerializer.LoadRegions();
World.Load(); World.Load();
@ -436,6 +443,7 @@ public static class Core
TcpServer.Start(); TcpServer.Start();
PingServer.Start(); PingServer.Start();
EventSink.InvokeServerStarted(); EventSink.InvokeServerStarted();
RunEventLoop(); RunEventLoop();
} }
@ -564,7 +572,8 @@ public static class Core
if (World.DirtyTrackingEnabled) if (World.DirtyTrackingEnabled)
{ {
var manualDirtyCheckingAttribute = type.GetCustomAttribute<ManualDirtyCheckingAttribute>(false); var manualDirtyCheckingAttribute = type.GetCustomAttribute<ManualDirtyCheckingAttribute>(false);
var codeGennedAttribute = type.GetCustomAttribute<ModernUO.Serialization.SerializationGeneratorAttribute>(false); var codeGennedAttribute =
type.GetCustomAttribute<ModernUO.Serialization.SerializationGeneratorAttribute>(false);
if (manualDirtyCheckingAttribute == null && codeGennedAttribute == null) if (manualDirtyCheckingAttribute == null && codeGennedAttribute == null)
{ {

View file

@ -35,6 +35,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.3.2" /> <PackageReference Include="CommunityToolkit.HighPerformance" Version="8.3.2" />
<PackageReference Include="LibDeflate.Bindings" Version="1.0.2.120" /> <PackageReference Include="LibDeflate.Bindings" Version="1.0.2.120" />
<PackageReference Include="NATS.Net" Version="2.5.1" />
<PackageReference Include="PollGroup" Version="1.5.1" /> <PackageReference Include="PollGroup" Version="1.5.1" />
<PackageReference Include="System.IO.Hashing" Version="8.0.0" /> <PackageReference Include="System.IO.Hashing" Version="8.0.0" />

View file

@ -0,0 +1,119 @@
using System;
using System.Text.Json.Serialization;
using System.Threading;
using Server.Network;
using NATS.Client;
using NATS.Client.Core;
using NATS.Client.JetStream;
using NATS.Client.JetStream.Models;
using NATS.Net;
namespace Server.Commands;
[JsonSerializable(typeof(BroadcastPayload))]
internal partial class BroadcastJsonContext : JsonSerializerContext;
public record BroadcastPayload
{
[JsonPropertyName("hue")]
public int Hue { get; set; } = 0x482;
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public static class Broadcast
{
private static INatsJSConsumer incomingBroadcastsConsumer;
private static readonly NatsJsonContextSerializer<BroadcastPayload> broadcastSerializer =
new(BroadcastJsonContext.Default);
public static void Configure()
{
CommandSystem.Register("BCast", AccessLevel.GameMaster, BroadcastMessage_OnCommand);
CommandSystem.Register("SMsg", AccessLevel.Counselor, StaffMessage_OnCommand);
}
public static void Initialize()
{
StartBroadcasts();
}
[Usage("BCast <text>")]
[Aliases("B", "BC")]
[Description("Broadcasts a message to everyone online.")]
public static void BroadcastMessage_OnCommand(CommandEventArgs e)
{
EmitBroadcast($"Staff message from {e.Mobile.Name}:", 0x482);
EmitBroadcast($"{e.ArgString}", 0x21);
// BroadcastMessage(AccessLevel.Player, 0x482, $"Staff message from {e.Mobile.Name}:");
// BroadcastMessage(AccessLevel.Player, 0x482, e.ArgString);
}
[Usage("SMsg <text>")]
[Aliases("S", "SM")]
[Description("Broadcasts a message to all online staff.")]
public static void StaffMessage_OnCommand(CommandEventArgs e)
{
BroadcastMessage(AccessLevel.Counselor, e.Mobile.SpeechHue, $"[{e.Mobile.Name}] {e.ArgString}");
}
public static async void EmitBroadcast(string message, int hue)
{
await MessageBus.Client.PublishAsync(
subject: "broadcast.all",
data: new BroadcastPayload()
{
Message = message,
Hue = hue,
},
serializer: broadcastSerializer
);
}
public static void BroadcastMessage(AccessLevel ac, int hue, string message)
{
foreach (var state in NetState.Instances)
{
var m = state.Mobile;
if (m?.AccessLevel >= ac)
{
m.SendMessage(hue, message);
}
}
}
private static async void StartBroadcasts()
{
await MessageBus.Context.CreateStreamAsync(
new StreamConfig(name: "BROADCASTS", subjects: ["broadcast.>"])
);
incomingBroadcastsConsumer = await MessageBus.Context.CreateOrUpdateConsumerAsync(
"BROADCASTS",
new ConsumerConfig("broadcast_incoming_consumer")
);
CancellationTokenSource cancellationTokenSource = new();
await foreach (var msg in incomingBroadcastsConsumer.ConsumeAsync(
serializer: broadcastSerializer,
cancellationToken: cancellationTokenSource.Token
))
{
if (msg.Data == null)
{
Console.WriteLine($"Message Received w/ null Data");
continue;
}
Console.WriteLine($"Message Received {msg.Data}");
EventSink.InvokeIncomingMessage(msg.Data.Message, msg.Data.Hue);
await msg.AckAsync(
cancellationToken: cancellationTokenSource.Token
);
}
}
}

View file

@ -33,8 +33,6 @@ namespace Server.Commands
Register("Help", AccessLevel.Player, Help_OnCommand); Register("Help", AccessLevel.Player, Help_OnCommand);
Register("Move", AccessLevel.GameMaster, Move_OnCommand); Register("Move", AccessLevel.GameMaster, Move_OnCommand);
Register("Client", AccessLevel.Counselor, Client_OnCommand); Register("Client", AccessLevel.Counselor, Client_OnCommand);
Register("SMsg", AccessLevel.Counselor, StaffMessage_OnCommand);
Register("BCast", AccessLevel.GameMaster, BroadcastMessage_OnCommand);
Register("Bank", AccessLevel.GameMaster, Bank_OnCommand); Register("Bank", AccessLevel.GameMaster, Bank_OnCommand);
Register("Echo", AccessLevel.Counselor, Echo_OnCommand); Register("Echo", AccessLevel.Counselor, Echo_OnCommand);
Register("Sound", AccessLevel.GameMaster, Sound_OnCommand); Register("Sound", AccessLevel.GameMaster, Sound_OnCommand);
@ -625,36 +623,6 @@ namespace Server.Commands
} }
} }
[Usage("SMsg <text>")]
[Aliases("S", "SM")]
[Description("Broadcasts a message to all online staff.")]
public static void StaffMessage_OnCommand(CommandEventArgs e)
{
BroadcastMessage(AccessLevel.Counselor, e.Mobile.SpeechHue, $"[{e.Mobile.Name}] {e.ArgString}");
}
[Usage("BCast <text>")]
[Aliases("B", "BC")]
[Description("Broadcasts a message to everyone online.")]
public static void BroadcastMessage_OnCommand(CommandEventArgs e)
{
BroadcastMessage(AccessLevel.Player, 0x482, $"Staff message from {e.Mobile.Name}:");
BroadcastMessage(AccessLevel.Player, 0x482, e.ArgString);
}
public static void BroadcastMessage(AccessLevel ac, int hue, string message)
{
foreach (var state in NetState.Instances)
{
var m = state.Mobile;
if (m?.AccessLevel >= ac)
{
m.SendMessage(hue, message);
}
}
}
[Usage("AutoPageNotify")] [Usage("AutoPageNotify")]
[Aliases("APN")] [Aliases("APN")]
[Description("Toggles your auto-page-notify status.")] [Description("Toggles your auto-page-notify status.")]

View file

@ -1,3 +1,5 @@
using System;
namespace Server.Misc namespace Server.Misc
{ {
public static class Broadcasts public static class Broadcasts
@ -6,6 +8,13 @@ namespace Server.Misc
{ {
EventSink.ServerCrashed += EventSink_Crashed; EventSink.ServerCrashed += EventSink_Crashed;
EventSink.Shutdown += EventSink_Shutdown; EventSink.Shutdown += EventSink_Shutdown;
EventSink.IncomingMessage += EventSink_IncomingMessage;
}
public static void EventSink_IncomingMessage(string message, int hue)
{
Console.WriteLine("Incoming Message");
World.Broadcast(hue, true, message);
} }
public static void EventSink_Crashed(ServerCrashedEventArgs e) public static void EventSink_Crashed(ServerCrashedEventArgs e)

View file

@ -34,6 +34,7 @@
<Delete Files="..\..\Distribution\Assemblies\ModernUO.Serialization.Annotations.dll" ContinueOnError="true" /> <Delete Files="..\..\Distribution\Assemblies\ModernUO.Serialization.Annotations.dll" ContinueOnError="true" />
</Target> </Target>
<ItemGroup> <ItemGroup>
<PackageReference Include="NATS.Net" Version="2.5.1" />
<ProjectReference Include="..\Server\Server.csproj" Private="false" PrivateAssets="All" IncludeAssets="None"> <ProjectReference Include="..\Server\Server.csproj" Private="false" PrivateAssets="All" IncludeAssets="None">
<IncludeInPackage>false</IncludeInPackage> <IncludeInPackage>false</IncludeInPackage>
</ProjectReference> </ProjectReference>