MosswartMassacre/MosswartMassacre/QuestStreamingService.cs
erik 0713e96a99 Phase 5: Extract QuestStreamingService and introduce IGameStats
- Extract QuestStreamingService.cs from PluginCore (timer, IsHighPriorityQuest, FormatCountdown)
- Create IGameStats interface for WebSocket telemetry decoupling
- PluginCore implements IGameStats, WebSocket.BuildPayloadJson reads from IGameStats
- WebSocket.cs no longer references PluginCore directly
- Update queststatus command to use QuestStreamingService
- Static bridge properties remain for VVSTabbedMainView compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:56:13 +00:00

133 lines
4.5 KiB
C#

using System;
using System.Linq;
using System.Timers;
namespace MosswartMassacre
{
/// <summary>
/// Streams high-priority quest timer data via WebSocket on a 30-second interval.
/// </summary>
internal class QuestStreamingService
{
private readonly IPluginLogger _logger;
private Timer _timer;
internal QuestStreamingService(IPluginLogger logger)
{
_logger = logger;
}
internal void Start()
{
_timer = new Timer(Constants.QuestStreamingIntervalMs);
_timer.Elapsed += OnTimerElapsed;
_timer.AutoReset = true;
_timer.Start();
}
internal void Stop()
{
if (_timer != null)
{
_timer.Stop();
_timer.Elapsed -= OnTimerElapsed;
_timer.Dispose();
_timer = null;
}
}
internal bool IsRunning => _timer != null && _timer.Enabled;
private void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
try
{
if (PluginSettings.Instance?.VerboseLogging == true)
{
_logger?.Log("[QUEST-STREAM] Timer fired, checking conditions...");
}
if (!PluginCore.WebSocketEnabled)
{
if (PluginSettings.Instance?.VerboseLogging == true)
{
_logger?.Log("[QUEST-STREAM] WebSocket not enabled, skipping");
}
return;
}
var questManager = PluginCore.questManager;
if (questManager?.QuestList == null || questManager.QuestList.Count == 0)
{
if (PluginSettings.Instance?.VerboseLogging == true)
{
_logger?.Log($"[QUEST-STREAM] No quest data available (null: {questManager?.QuestList == null}, count: {questManager?.QuestList?.Count ?? 0})");
}
return;
}
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var priorityQuests = questManager.QuestList
.Where(q => IsHighPriorityQuest(q.Id))
.GroupBy(q => q.Id)
.Select(g => g.First())
.ToList();
if (PluginSettings.Instance?.VerboseLogging == true)
{
_logger?.Log($"[QUEST-STREAM] Found {priorityQuests.Count} priority quests to stream");
}
foreach (var quest in priorityQuests)
{
try
{
string questName = questManager.GetFriendlyQuestName(quest.Id);
long timeRemaining = quest.ExpireTime - currentTime;
string countdown = FormatCountdown(timeRemaining);
if (PluginSettings.Instance?.VerboseLogging == true)
{
_logger?.Log($"[QUEST-STREAM] Sending: {questName} - {countdown}");
}
System.Threading.Tasks.Task.Run(() => WebSocket.SendQuestDataAsync(questName, countdown));
}
catch (Exception ex)
{
_logger?.Log($"[QUEST-STREAM] Error streaming quest {quest.Id}: {ex.Message}");
}
}
}
catch (Exception ex)
{
_logger?.Log($"[QUEST-STREAM] Error in timer handler: {ex.Message}");
}
}
internal static bool IsHighPriorityQuest(string questId)
{
return questId == "stipendtimer_0812" ||
questId == "augmentationblankgemacquired" ||
questId == "insatiableeaterjaw";
}
internal static string FormatCountdown(long seconds)
{
if (seconds <= 0)
return "READY";
var timeSpan = TimeSpan.FromSeconds(seconds);
if (timeSpan.TotalDays >= 1)
return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours:D2}h";
else if (timeSpan.TotalHours >= 1)
return $"{timeSpan.Hours}h {timeSpan.Minutes:D2}m";
else if (timeSpan.TotalMinutes >= 1)
return $"{timeSpan.Minutes}m {timeSpan.Seconds:D2}s";
else
return $"{timeSpan.Seconds}s";
}
}
}