Added hot reload

This commit is contained in:
erik 2025-06-22 12:10:15 +02:00
parent bb493febb4
commit 73ba7082d8
16 changed files with 1203 additions and 398 deletions

View file

@ -20,6 +20,32 @@ namespace MosswartMassacre
[FriendlyName("Mosswart Massacre")]
public class PluginCore : PluginBase
{
// Hot Reload Support Properties
private static string _assemblyDirectory = null;
public static string AssemblyDirectory
{
get
{
if (_assemblyDirectory == null)
{
try
{
_assemblyDirectory = System.IO.Path.GetDirectoryName(typeof(PluginCore).Assembly.Location);
}
catch
{
_assemblyDirectory = Environment.CurrentDirectory;
}
}
return _assemblyDirectory;
}
set
{
_assemblyDirectory = value;
}
}
public static bool IsHotReload { get; set; }
internal static PluginHost MyHost;
internal static int totalKills = 0;
internal static int rareCount = 0;
@ -88,6 +114,10 @@ namespace MosswartMassacre
public static bool AggressiveChatStreamingEnabled { get; set; } = true;
private MossyInventory _inventoryLogger;
public static NavVisualization navVisualization;
// Quest Management for always-on quest streaming
public static QuestManager questManager;
private static Timer questStreamingTimer;
private static Queue<string> rareMessageQueue = new Queue<string>();
private static DateTime _lastSent = DateTime.MinValue;
@ -97,9 +127,41 @@ namespace MosswartMassacre
{
try
{
MyHost = Host;
// Set MyHost - for hot reload scenarios, Host might be null
if (Host != null)
{
MyHost = Host;
}
else if (MyHost == null)
{
// Hot reload fallback - this is okay, WriteToChat will handle it
MyHost = null;
}
// Check if this is a hot reload
var isCharacterLoaded = CoreManager.Current.CharacterFilter.LoginStatus == 3;
if (IsHotReload || isCharacterLoaded)
{
// Hot reload detected - reinitialize connections and state
WriteToChat("[INFO] Hot reload detected - reinitializing plugin");
// Reload settings if character is already logged in
if (isCharacterLoaded)
{
try
{
WriteToChat("Hot reload - reinitializing character-dependent systems");
// Don't call LoginComplete - create hot reload specific initialization
InitializeForHotReload();
WriteToChat("[INFO] Hot reload initialization complete");
}
catch (Exception ex)
{
WriteToChat($"[ERROR] Hot reload initialization failed: {ex.Message}");
}
}
}
// Note: Startup messages will appear after character login
// Subscribe to chat message event
CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
@ -165,7 +227,7 @@ namespace MosswartMassacre
PluginSettings.Save();
if (TelemetryEnabled)
Telemetry.Stop(); // ensure no dangling timer / HttpClient
WriteToChat("Mosswart Massacre is shutting down...");
WriteToChat("Mosswart Massacre is shutting down!!!!!");
// Unsubscribe from chat message event
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
@ -203,6 +265,22 @@ namespace MosswartMassacre
commandTimer = null;
}
// Stop and dispose quest streaming timer
if (questStreamingTimer != null)
{
questStreamingTimer.Stop();
questStreamingTimer.Elapsed -= OnQuestStreamingUpdate;
questStreamingTimer.Dispose();
questStreamingTimer = null;
}
// Dispose quest manager
if (questManager != null)
{
questManager.Dispose();
questManager = null;
}
// Clean up the view
ViewManager.ViewDestroy();
//Disable vtank interface
@ -279,6 +357,160 @@ namespace MosswartMassacre
// Initialize cached Prismatic Taper count
InitializePrismaticTaperCount();
// Initialize quest manager for always-on quest streaming
try
{
questManager = new QuestManager();
questManager.RefreshQuests();
// Initialize quest streaming timer (30 seconds)
questStreamingTimer = new Timer(30000);
questStreamingTimer.Elapsed += OnQuestStreamingUpdate;
questStreamingTimer.AutoReset = true;
questStreamingTimer.Start();
WriteToChat("[OK] Quest streaming initialized");
}
catch (Exception ex)
{
WriteToChat($"[ERROR] Quest streaming initialization failed: {ex.Message}");
}
}
#region Quest Streaming Methods
private static void OnQuestStreamingUpdate(object sender, ElapsedEventArgs e)
{
try
{
// Stream high priority quest data via WebSocket
if (WebSocketEnabled && questManager?.QuestList != null && questManager.QuestList.Count > 0)
{
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
// Find and stream priority quests (deduplicated by quest ID)
var priorityQuests = questManager.QuestList
.Where(q => IsHighPriorityQuest(q.Id))
.GroupBy(q => q.Id)
.Select(g => g.First()) // Take first occurrence of each quest ID
.ToList();
foreach (var quest in priorityQuests)
{
try
{
string questName = questManager.GetFriendlyQuestName(quest.Id);
long timeRemaining = quest.ExpireTime - currentTime;
string countdown = FormatCountdown(timeRemaining);
// Stream quest data
System.Threading.Tasks.Task.Run(() => WebSocket.SendQuestDataAsync(questName, countdown));
}
catch (Exception)
{
// Silently handle individual quest streaming errors
}
}
}
}
catch (Exception)
{
// Silently handle quest streaming errors to avoid spam
}
}
private static bool IsHighPriorityQuest(string questId)
{
return questId == "stipendtimer_0812" || // Changed from stipendtimer_monthly to stipendtimer_0812
questId == "augmentationblankgemacquired" ||
questId == "insatiableeaterjaw";
}
private 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";
}
#endregion
private void InitializeForHotReload()
{
// This method handles initialization that depends on character being logged in
// Similar to LoginComplete but designed for hot reload scenarios
WriteToChat("Mosswart Massacre hot reload initialization started!");
// 1. Initialize settings - CRITICAL first step
PluginSettings.Initialize();
// 2. Apply the values from settings
RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled;
RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled;
HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled;
TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled;
CharTag = PluginSettings.Instance.CharTag;
// 3. Update UI with current settings
ViewManager.SetRareMetaToggleState(RareMetaEnabled);
ViewManager.RefreshSettingsFromConfig();
// 4. Restart services if they were enabled (stop first, then start)
if (TelemetryEnabled)
{
Telemetry.Stop(); // Stop existing
Telemetry.Start(); // Restart
}
if (WebSocketEnabled)
{
WebSocket.Stop(); // Stop existing
WebSocket.Start(); // Restart
}
if (HttpServerEnabled)
{
HttpCommandServer.Stop(); // Stop existing
HttpCommandServer.Start(); // Restart
}
// 5. Initialize Harmony patches (only if not already done)
// Note: Harmony patches are global and don't need reinitialization
if (!DecalHarmonyClean.IsActive())
{
try
{
bool success = DecalHarmonyClean.Initialize();
if (success)
WriteToChat("[OK] Plugin message interception active");
else
WriteToChat("[FAIL] Could not initialize message interception");
}
catch (Exception ex)
{
WriteToChat($"[ERROR] Harmony initialization failed: {ex.Message}");
}
}
// 6. Reinitialize death tracking
totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
// Don't reset sessionDeaths - keep the current session count
// 7. Reinitialize cached Prismatic Taper count
InitializePrismaticTaperCount();
WriteToChat("Hot reload initialization completed!");
}
private void InitializePrismaticTaperCount()
@ -658,7 +890,6 @@ namespace MosswartMassacre
{
try
{
// WriteToChat($"[Debug] Chat Color: {e.Color}, Message: {e.Text}");
if (IsKilledByMeMessage(e.Text))
{
@ -889,7 +1120,31 @@ namespace MosswartMassacre
}
public static void WriteToChat(string message)
{
MyHost.Actions.AddChatText("[Mosswart Massacre] " + message, 0, 1);
try
{
// For hot reload scenarios where MyHost might be null, use CoreManager directly
if (MyHost != null)
{
MyHost.Actions.AddChatText("[Mosswart Massacre] " + message, 0, 1);
}
else
{
// Hot reload fallback - use CoreManager directly like the original template
CoreManager.Current.Actions.AddChatText("[Mosswart Massacre] " + message, 1);
}
}
catch (Exception ex)
{
// Last resort fallback - try CoreManager even if MyHost was supposed to work
try
{
CoreManager.Current.Actions.AddChatText($"[Mosswart Massacre] {message} (WriteToChat error: {ex.Message})", 1);
}
catch
{
// Give up - can't write to chat at all
}
}
}
public static void RestartStats()
{
@ -997,6 +1252,7 @@ namespace MosswartMassacre
WriteToChat("Usage: /mm ws <enable|disable>");
}
}
else
{
WriteToChat("Usage: /mm ws <enable|disable>");
@ -1019,10 +1275,9 @@ namespace MosswartMassacre
WriteToChat("/mm harmonyraw - Show raw intercepted messages (debug output)");
WriteToChat("/mm testprismatic - Test Prismatic Taper detection and icon lookup");
WriteToChat("/mm deathstats - Show current death tracking statistics");
WriteToChat("/mm testdeath - Manual death tracking test and diagnostics");
WriteToChat("/mm testtaper - Test cached Prismatic Taper tracking");
WriteToChat("/mm debugtaper - Show detailed taper tracking debug info");
WriteToChat("/mm gui - Manually initialize/reinitialize GUI");
WriteToChat("/mm gui - Manually initialize/reinitialize GUI!!!");
break;
case "report":
TimeSpan elapsed = DateTime.Now - statsStartTime;
@ -1134,7 +1389,6 @@ namespace MosswartMassacre
WriteToChat("=== Harmony Patch Status (UtilityBelt Pattern) ===");
WriteToChat($"Patches Active: {DecalHarmonyClean.IsActive()}");
WriteToChat($"Messages Intercepted: {DecalHarmonyClean.GetMessagesIntercepted()}");
WriteToChat($"Debug Streaming: {AggressiveChatStreamingEnabled}");
WriteToChat($"WebSocket Streaming: {(AggressiveChatStreamingEnabled && WebSocketEnabled ? "ACTIVE" : "INACTIVE")}");
// Test Harmony availability
@ -1186,31 +1440,7 @@ namespace MosswartMassacre
case "harmonyraw":
try
{
WriteToChat("=== Raw Harmony Interception Log ===");
var debugEntries = DecalHarmonyClean.GetDebugLog();
if (debugEntries.Length == 0)
{
WriteToChat("No debug entries found. Enable debug streaming first: /mm decaldebug enable");
}
else
{
WriteToChat($"Last {debugEntries.Length} intercepted messages:");
foreach (var entry in debugEntries.Skip(Math.Max(0, debugEntries.Length - 10)))
{
WriteToChat($" {entry}");
}
if (debugEntries.Length > 10)
{
WriteToChat($"... ({debugEntries.Length - 10} more entries)");
}
}
}
catch (Exception ex)
{
WriteToChat($"Debug log error: {ex.Message}");
}
// Debug functionality removed
break;
case "initgui":
@ -1394,48 +1624,7 @@ namespace MosswartMassacre
break;
case "debugtaper":
try
{
WriteToChat("=== Taper Tracking Debug Info ===");
WriteToChat($"Cached Count: {cachedPrismaticCount}");
WriteToChat($"Last Count: {lastPrismaticCount}");
WriteToChat($"Tracked Containers: {trackedTaperContainers.Count}");
WriteToChat($"Known Stack Sizes: {lastKnownStackSizes.Count}");
if (trackedTaperContainers.Count > 0)
{
WriteToChat("=== Tracked Taper Details ===");
foreach (var kvp in trackedTaperContainers)
{
int itemId = kvp.Key;
int containerId = kvp.Value;
int stackSize = lastKnownStackSizes.TryGetValue(itemId, out int size) ? size : -1;
string containerType = containerId == CoreManager.Current.CharacterFilter.Id ? "main pack" : "side pack";
WriteToChat($" Item {itemId}: {containerType} (container {containerId}), stack: {stackSize}");
}
}
else
{
WriteToChat("No tapers currently tracked!");
}
// Cross-check with actual inventory
WriteToChat("=== Cross-Check with Actual Inventory ===");
int actualCount = Utils.GetItemStackSize("Prismatic Taper");
WriteToChat($"Utils.GetItemStackSize: {actualCount}");
if (cachedPrismaticCount != actualCount)
{
WriteToChat($"[WARNING] Count mismatch! Cached: {cachedPrismaticCount}, Actual: {actualCount}");
}
else
{
WriteToChat("[OK] Cached count matches actual count");
}
}
catch (Exception ex)
{
WriteToChat($"Debug taper error: {ex.Message}");
}
// Debug functionality removed
break;
case "finditem":