Phase 4: Extract ChatEventRouter and GameEventRouter
- ChatEventRouter.cs: routes chat events to KillTracker, RareTracker, handles allegiance report trigger and WebSocket chat streaming - GameEventRouter.cs: routes ServerDispatch messages (0xF7B0, 0x02CF) to CharacterStats - PluginCore no longer contains OnChatText, AllChatText, NormalizeChatLine, or EchoFilter_ServerDispatch methods Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c90e888d32
commit
f9264f2767
4 changed files with 185 additions and 124 deletions
90
MosswartMassacre/ChatEventRouter.cs
Normal file
90
MosswartMassacre/ChatEventRouter.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Decal.Adapter;
|
||||||
|
using Decal.Adapter.Wrappers;
|
||||||
|
|
||||||
|
namespace MosswartMassacre
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Routes chat events to the appropriate handler (KillTracker, RareTracker, etc.)
|
||||||
|
/// Replaces the big if/else chain in PluginCore.OnChatText.
|
||||||
|
/// </summary>
|
||||||
|
internal class ChatEventRouter
|
||||||
|
{
|
||||||
|
private readonly IPluginLogger _logger;
|
||||||
|
private readonly KillTracker _killTracker;
|
||||||
|
private RareTracker _rareTracker;
|
||||||
|
private readonly Action<int> _onRareCountChanged;
|
||||||
|
private readonly Action<string> _onAllegianceReport;
|
||||||
|
|
||||||
|
internal void SetRareTracker(RareTracker rareTracker) => _rareTracker = rareTracker;
|
||||||
|
|
||||||
|
internal ChatEventRouter(
|
||||||
|
IPluginLogger logger,
|
||||||
|
KillTracker killTracker,
|
||||||
|
RareTracker rareTracker,
|
||||||
|
Action<int> onRareCountChanged,
|
||||||
|
Action<string> onAllegianceReport)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_killTracker = killTracker;
|
||||||
|
_rareTracker = rareTracker;
|
||||||
|
_onRareCountChanged = onRareCountChanged;
|
||||||
|
_onAllegianceReport = onAllegianceReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnChatText(object sender, ChatTextInterceptEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_killTracker.CheckForKill(e.Text);
|
||||||
|
|
||||||
|
if (_rareTracker != null && _rareTracker.CheckForRare(e.Text, out string rareText))
|
||||||
|
{
|
||||||
|
_killTracker.RareCount = _rareTracker.RareCount;
|
||||||
|
_onRareCountChanged?.Invoke(_rareTracker.RareCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.Color == 18 && e.Text.EndsWith("!report\""))
|
||||||
|
{
|
||||||
|
TimeSpan elapsed = DateTime.Now - _killTracker.StatsStartTime;
|
||||||
|
string reportMessage = $"Total Kills: {_killTracker.TotalKills}, Kills per Hour: {_killTracker.KillsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {_rareTracker?.RareCount ?? 0}";
|
||||||
|
_logger?.Log($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}");
|
||||||
|
_onAllegianceReport?.Invoke(reportMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Log("Error processing chat message: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Streams all chat text to WebSocket (separate handler from the filtered one above).
|
||||||
|
/// </summary>
|
||||||
|
internal static async void AllChatText(object sender, ChatTextInterceptEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string cleaned = NormalizeChatLine(e.Text);
|
||||||
|
await WebSocket.SendChatTextAsync(e.Color, cleaned);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[WS] Chat send failed: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeChatLine(string raw)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(raw))
|
||||||
|
return raw;
|
||||||
|
|
||||||
|
var noTags = Regex.Replace(raw, "<[^>]+>", "");
|
||||||
|
var trimmed = noTags.TrimEnd('\r', '\n');
|
||||||
|
var collapsed = Regex.Replace(trimmed, @"[ ]{2,}", " ");
|
||||||
|
|
||||||
|
return collapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
MosswartMassacre/GameEventRouter.cs
Normal file
55
MosswartMassacre/GameEventRouter.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
using Decal.Adapter;
|
||||||
|
|
||||||
|
namespace MosswartMassacre
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Routes EchoFilter.ServerDispatch network messages to the appropriate handlers.
|
||||||
|
/// Owns the routing of 0xF7B0 sub-events and 0x02CF to CharacterStats.
|
||||||
|
/// </summary>
|
||||||
|
internal class GameEventRouter
|
||||||
|
{
|
||||||
|
private readonly IPluginLogger _logger;
|
||||||
|
|
||||||
|
internal GameEventRouter(IPluginLogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnServerDispatch(object sender, NetworkMessageEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (e.Message.Type == Constants.GameEventMessageType)
|
||||||
|
{
|
||||||
|
int eventId = (int)e.Message["event"];
|
||||||
|
|
||||||
|
if (eventId == Constants.AllegianceInfoEvent)
|
||||||
|
{
|
||||||
|
CharacterStats.ProcessAllegianceInfoMessage(e);
|
||||||
|
}
|
||||||
|
else if (eventId == Constants.LoginCharacterEvent)
|
||||||
|
{
|
||||||
|
CharacterStats.ProcessCharacterPropertyData(e);
|
||||||
|
}
|
||||||
|
else if (eventId == Constants.TitlesListEvent)
|
||||||
|
{
|
||||||
|
CharacterStats.ProcessTitlesMessage(e);
|
||||||
|
}
|
||||||
|
else if (eventId == Constants.SetTitleEvent)
|
||||||
|
{
|
||||||
|
CharacterStats.ProcessSetTitleMessage(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.Message.Type == Constants.PrivateUpdatePropertyInt64)
|
||||||
|
{
|
||||||
|
CharacterStats.ProcessPropertyInt64Update(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Log($"[CharStats] ServerDispatch error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -304,8 +304,10 @@
|
||||||
<Compile Include="..\Shared\VCS_Connector.cs">
|
<Compile Include="..\Shared\VCS_Connector.cs">
|
||||||
<Link>Shared\VCS_Connector.cs</Link>
|
<Link>Shared\VCS_Connector.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="ChatEventRouter.cs" />
|
||||||
<Compile Include="CommandRouter.cs" />
|
<Compile Include="CommandRouter.cs" />
|
||||||
<Compile Include="Constants.cs" />
|
<Compile Include="Constants.cs" />
|
||||||
|
<Compile Include="GameEventRouter.cs" />
|
||||||
<Compile Include="IPluginLogger.cs" />
|
<Compile Include="IPluginLogger.cs" />
|
||||||
<Compile Include="InventoryMonitor.cs" />
|
<Compile Include="InventoryMonitor.cs" />
|
||||||
<Compile Include="KillTracker.cs" />
|
<Compile Include="KillTracker.cs" />
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,8 @@ namespace MosswartMassacre
|
||||||
private KillTracker _killTracker;
|
private KillTracker _killTracker;
|
||||||
private RareTracker _rareTracker;
|
private RareTracker _rareTracker;
|
||||||
private InventoryMonitor _inventoryMonitor;
|
private InventoryMonitor _inventoryMonitor;
|
||||||
|
private ChatEventRouter _chatEventRouter;
|
||||||
|
private GameEventRouter _gameEventRouter;
|
||||||
private CommandRouter _commandRouter;
|
private CommandRouter _commandRouter;
|
||||||
|
|
||||||
protected override void Startup()
|
protected override void Startup()
|
||||||
|
|
@ -171,25 +173,6 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Startup messages will appear after character login
|
|
||||||
// Subscribe to chat message event
|
|
||||||
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
|
|
||||||
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(AllChatText);
|
|
||||||
CoreManager.Current.CommandLineText += OnChatCommand;
|
|
||||||
CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
|
|
||||||
CoreManager.Current.CharacterFilter.Death += OnCharacterDeath;
|
|
||||||
CoreManager.Current.WorldFilter.CreateObject += OnSpawn;
|
|
||||||
CoreManager.Current.WorldFilter.CreateObject += OnPortalDetected;
|
|
||||||
CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn;
|
|
||||||
// Initialize inventory monitor (taper tracking)
|
|
||||||
_inventoryMonitor = new InventoryMonitor(this);
|
|
||||||
_staticInventoryMonitor = _inventoryMonitor;
|
|
||||||
CoreManager.Current.WorldFilter.CreateObject += _inventoryMonitor.OnInventoryCreate;
|
|
||||||
CoreManager.Current.WorldFilter.ReleaseObject += _inventoryMonitor.OnInventoryRelease;
|
|
||||||
CoreManager.Current.WorldFilter.ChangeObject += _inventoryMonitor.OnInventoryChange;
|
|
||||||
// Initialize VVS view after character login
|
|
||||||
ViewManager.ViewInit();
|
|
||||||
|
|
||||||
// Initialize kill tracker (owns the 1-sec stats timer)
|
// Initialize kill tracker (owns the 1-sec stats timer)
|
||||||
_killTracker = new KillTracker(
|
_killTracker = new KillTracker(
|
||||||
this,
|
this,
|
||||||
|
|
@ -198,6 +181,36 @@ namespace MosswartMassacre
|
||||||
_staticKillTracker = _killTracker;
|
_staticKillTracker = _killTracker;
|
||||||
_killTracker.Start();
|
_killTracker.Start();
|
||||||
|
|
||||||
|
// Initialize inventory monitor (taper tracking)
|
||||||
|
_inventoryMonitor = new InventoryMonitor(this);
|
||||||
|
_staticInventoryMonitor = _inventoryMonitor;
|
||||||
|
|
||||||
|
// Initialize chat event router (rareTracker set later in LoginComplete)
|
||||||
|
_chatEventRouter = new ChatEventRouter(
|
||||||
|
this, _killTracker, null,
|
||||||
|
count => ViewManager.UpdateRareCount(count),
|
||||||
|
msg => MyHost?.Actions.InvokeChatParser($"/a {msg}"));
|
||||||
|
|
||||||
|
// Initialize game event router
|
||||||
|
_gameEventRouter = new GameEventRouter(this);
|
||||||
|
|
||||||
|
// Note: Startup messages will appear after character login
|
||||||
|
// Subscribe to events
|
||||||
|
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(_chatEventRouter.OnChatText);
|
||||||
|
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(ChatEventRouter.AllChatText);
|
||||||
|
CoreManager.Current.CommandLineText += OnChatCommand;
|
||||||
|
CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
|
||||||
|
CoreManager.Current.CharacterFilter.Death += OnCharacterDeath;
|
||||||
|
CoreManager.Current.WorldFilter.CreateObject += OnSpawn;
|
||||||
|
CoreManager.Current.WorldFilter.CreateObject += OnPortalDetected;
|
||||||
|
CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn;
|
||||||
|
CoreManager.Current.WorldFilter.CreateObject += _inventoryMonitor.OnInventoryCreate;
|
||||||
|
CoreManager.Current.WorldFilter.ReleaseObject += _inventoryMonitor.OnInventoryRelease;
|
||||||
|
CoreManager.Current.WorldFilter.ChangeObject += _inventoryMonitor.OnInventoryChange;
|
||||||
|
|
||||||
|
// Initialize VVS view after character login
|
||||||
|
ViewManager.ViewInit();
|
||||||
|
|
||||||
// Initialize vitals streaming timer
|
// Initialize vitals streaming timer
|
||||||
vitalsTimer = new Timer(Constants.VitalsUpdateIntervalMs);
|
vitalsTimer = new Timer(Constants.VitalsUpdateIntervalMs);
|
||||||
vitalsTimer.Elapsed += SendVitalsUpdate;
|
vitalsTimer.Elapsed += SendVitalsUpdate;
|
||||||
|
|
@ -215,7 +228,7 @@ namespace MosswartMassacre
|
||||||
// 0x0013 (character properties with luminance) fires DURING login,
|
// 0x0013 (character properties with luminance) fires DURING login,
|
||||||
// BEFORE LoginComplete — must hook here to catch it
|
// BEFORE LoginComplete — must hook here to catch it
|
||||||
CharacterStats.Init(this);
|
CharacterStats.Init(this);
|
||||||
CoreManager.Current.EchoFilter.ServerDispatch += EchoFilter_ServerDispatch;
|
CoreManager.Current.EchoFilter.ServerDispatch += _gameEventRouter.OnServerDispatch;
|
||||||
|
|
||||||
// Enable TLS1.2
|
// Enable TLS1.2
|
||||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||||
|
|
@ -258,9 +271,9 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
|
|
||||||
// Unsubscribe from chat message event
|
// Unsubscribe from chat message event
|
||||||
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
|
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(_chatEventRouter.OnChatText);
|
||||||
CoreManager.Current.CommandLineText -= OnChatCommand;
|
CoreManager.Current.CommandLineText -= OnChatCommand;
|
||||||
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(AllChatText);
|
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(ChatEventRouter.AllChatText);
|
||||||
CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath;
|
CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath;
|
||||||
CoreManager.Current.WorldFilter.CreateObject -= OnSpawn;
|
CoreManager.Current.WorldFilter.CreateObject -= OnSpawn;
|
||||||
CoreManager.Current.WorldFilter.CreateObject -= OnPortalDetected;
|
CoreManager.Current.WorldFilter.CreateObject -= OnPortalDetected;
|
||||||
|
|
@ -273,7 +286,7 @@ namespace MosswartMassacre
|
||||||
CoreManager.Current.WorldFilter.ChangeObject -= _inventoryMonitor.OnInventoryChange;
|
CoreManager.Current.WorldFilter.ChangeObject -= _inventoryMonitor.OnInventoryChange;
|
||||||
}
|
}
|
||||||
// Unsubscribe from server dispatch
|
// Unsubscribe from server dispatch
|
||||||
CoreManager.Current.EchoFilter.ServerDispatch -= EchoFilter_ServerDispatch;
|
CoreManager.Current.EchoFilter.ServerDispatch -= _gameEventRouter.OnServerDispatch;
|
||||||
|
|
||||||
// Stop kill tracker
|
// Stop kill tracker
|
||||||
_killTracker?.Stop();
|
_killTracker?.Stop();
|
||||||
|
|
@ -372,9 +385,10 @@ namespace MosswartMassacre
|
||||||
WriteToChat($"[ChestLooter] Initialization failed: {ex.Message}");
|
WriteToChat($"[ChestLooter] Initialization failed: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize rare tracker
|
// Initialize rare tracker and wire to chat router
|
||||||
_rareTracker = new RareTracker(this);
|
_rareTracker = new RareTracker(this);
|
||||||
_staticRareTracker = _rareTracker;
|
_staticRareTracker = _rareTracker;
|
||||||
|
_chatEventRouter.SetRareTracker(_rareTracker);
|
||||||
|
|
||||||
// Apply the values
|
// Apply the values
|
||||||
_rareTracker.RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
|
_rareTracker.RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
|
||||||
|
|
@ -800,39 +814,6 @@ namespace MosswartMassacre
|
||||||
// $"[Despawn] {mob.Name} @ (NS={c.NorthSouth:F1}, EW={c.EastWest:F1})");
|
// $"[Despawn] {mob.Name} @ (NS={c.NorthSouth:F1}, EW={c.EastWest:F1})");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void AllChatText(object sender, ChatTextInterceptEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string cleaned = NormalizeChatLine(e.Text);
|
|
||||||
|
|
||||||
// Send to WebSocket
|
|
||||||
await WebSocket.SendChatTextAsync(e.Color, cleaned);
|
|
||||||
|
|
||||||
// Note: Plugin message analysis is now handled by Harmony patches
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PluginCore.WriteToChat($"[WS] Chat send failed: {ex}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeChatLine(string raw)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(raw))
|
|
||||||
return raw;
|
|
||||||
|
|
||||||
// 1) Remove all <…> tags
|
|
||||||
var noTags = Regex.Replace(raw, "<[^>]+>", "");
|
|
||||||
|
|
||||||
// 2) Trim trailing newline or carriage-return
|
|
||||||
var trimmed = noTags.TrimEnd('\r', '\n');
|
|
||||||
|
|
||||||
// 3) Collapse multiple spaces into one
|
|
||||||
var collapsed = Regex.Replace(trimmed, @"[ ]{2,}", " ");
|
|
||||||
|
|
||||||
return collapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e)
|
private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
@ -874,40 +855,8 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnChatText(object sender, ChatTextInterceptEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
_killTracker.CheckForKill(e.Text);
|
|
||||||
|
|
||||||
if (_rareTracker != null && _rareTracker.CheckForRare(e.Text, out string rareText))
|
|
||||||
{
|
|
||||||
_killTracker.RareCount = _rareTracker.RareCount;
|
|
||||||
ViewManager.UpdateRareCount(_rareTracker.RareCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.Color == 18 && e.Text.EndsWith("!report\""))
|
|
||||||
{
|
|
||||||
TimeSpan elapsed = DateTime.Now - _killTracker.StatsStartTime;
|
|
||||||
string reportMessage = $"Total Kills: {_killTracker.TotalKills}, Kills per Hour: {_killTracker.KillsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {_rareTracker?.RareCount ?? 0}";
|
|
||||||
WriteToChat($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}");
|
|
||||||
MyHost.Actions.InvokeChatParser($"/a {reportMessage}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
WriteToChat("Error processing chat message: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void OnChatCommand(object sender, ChatParserInterceptEventArgs e)
|
private void OnChatCommand(object sender, ChatParserInterceptEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -983,41 +932,6 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EchoFilter_ServerDispatch(object sender, NetworkMessageEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (e.Message.Type == Constants.GameEventMessageType)
|
|
||||||
{
|
|
||||||
int eventId = (int)e.Message["event"];
|
|
||||||
|
|
||||||
if (eventId == Constants.AllegianceInfoEvent)
|
|
||||||
{
|
|
||||||
CharacterStats.ProcessAllegianceInfoMessage(e);
|
|
||||||
}
|
|
||||||
else if (eventId == Constants.LoginCharacterEvent)
|
|
||||||
{
|
|
||||||
CharacterStats.ProcessCharacterPropertyData(e);
|
|
||||||
}
|
|
||||||
else if (eventId == Constants.TitlesListEvent)
|
|
||||||
{
|
|
||||||
CharacterStats.ProcessTitlesMessage(e);
|
|
||||||
}
|
|
||||||
else if (eventId == Constants.SetTitleEvent)
|
|
||||||
{
|
|
||||||
CharacterStats.ProcessSetTitleMessage(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e.Message.Type == Constants.PrivateUpdatePropertyInt64)
|
|
||||||
{
|
|
||||||
CharacterStats.ProcessPropertyInt64Update(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
WriteToChat($"[CharStats] ServerDispatch error: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void WriteToChat(string message)
|
public static void WriteToChat(string message)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue