Added hot reload
This commit is contained in:
parent
bb493febb4
commit
73ba7082d8
16 changed files with 1203 additions and 398 deletions
264
MosswartMassacre.Loader/LoaderCore.cs
Normal file
264
MosswartMassacre.Loader/LoaderCore.cs
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Decal.Adapter;
|
||||||
|
|
||||||
|
namespace MosswartMassacre.Loader
|
||||||
|
{
|
||||||
|
[FriendlyName("MosswartMassacre.Loader")]
|
||||||
|
public class LoaderCore : FilterBase
|
||||||
|
{
|
||||||
|
private Assembly pluginAssembly;
|
||||||
|
private Type pluginType;
|
||||||
|
private object pluginInstance;
|
||||||
|
private FileSystemWatcher pluginWatcher;
|
||||||
|
private bool isSubscribedToRenderFrame = false;
|
||||||
|
private bool needsReload;
|
||||||
|
|
||||||
|
public static string PluginAssemblyNamespace => "MosswartMassacre";
|
||||||
|
public static string PluginAssemblyName => $"{PluginAssemblyNamespace}.dll";
|
||||||
|
public static string PluginAssemblyGuid => "{8C97E839-4D05-4A5F-B0C8-E8E778654322}";
|
||||||
|
|
||||||
|
public static bool IsPluginLoaded { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly directory (contains both loader and plugin dlls)
|
||||||
|
/// </summary>
|
||||||
|
public static string AssemblyDirectory => System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(LoaderCore)).Location);
|
||||||
|
|
||||||
|
public DateTime LastDllChange { get; private set; }
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Core.PluginInitComplete += Core_PluginInitComplete;
|
||||||
|
Core.PluginTermComplete += Core_PluginTermComplete;
|
||||||
|
Core.FilterInitComplete += Core_FilterInitComplete;
|
||||||
|
|
||||||
|
// Set up assembly resolution for hot-loaded plugin dependencies
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||||
|
|
||||||
|
// watch the AssemblyDirectory for any .dll file changes
|
||||||
|
pluginWatcher = new FileSystemWatcher();
|
||||||
|
pluginWatcher.Path = AssemblyDirectory;
|
||||||
|
pluginWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
||||||
|
pluginWatcher.Filter = "*.dll";
|
||||||
|
pluginWatcher.Changed += PluginWatcher_Changed;
|
||||||
|
pluginWatcher.EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Core_FilterInitComplete(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Core.EchoFilter.ClientDispatch += EchoFilter_ClientDispatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EchoFilter_ClientDispatch(object sender, NetworkMessageEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Login_SendEnterWorldRequest
|
||||||
|
if (e.Message.Type == 0xF7C8)
|
||||||
|
{
|
||||||
|
//EnsurePluginIsDisabledInRegistry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Core_PluginInitComplete(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoadPluginAssembly();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Core_PluginTermComplete(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UnloadPluginAssembly();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Shutdown()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Core.PluginInitComplete -= Core_PluginInitComplete;
|
||||||
|
Core.PluginTermComplete -= Core_PluginTermComplete;
|
||||||
|
Core.FilterInitComplete -= Core_FilterInitComplete;
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
|
||||||
|
UnloadPluginAssembly();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Core_RenderFrame(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsPluginLoaded && needsReload && DateTime.UtcNow - LastDllChange > TimeSpan.FromSeconds(1))
|
||||||
|
{
|
||||||
|
needsReload = false;
|
||||||
|
Core.RenderFrame -= Core_RenderFrame;
|
||||||
|
isSubscribedToRenderFrame = false;
|
||||||
|
LoadPluginAssembly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PluginWatcher_Changed(object sender, FileSystemEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Only reload if it's the main plugin DLL
|
||||||
|
if (e.Name == PluginAssemblyName)
|
||||||
|
{
|
||||||
|
LastDllChange = DateTime.UtcNow;
|
||||||
|
needsReload = true;
|
||||||
|
|
||||||
|
if (!isSubscribedToRenderFrame)
|
||||||
|
{
|
||||||
|
isSubscribedToRenderFrame = true;
|
||||||
|
Core.RenderFrame += Core_RenderFrame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Extract just the assembly name (without version info)
|
||||||
|
string assemblyName = args.Name.Split(',')[0] + ".dll";
|
||||||
|
string assemblyPath = System.IO.Path.Combine(AssemblyDirectory, assemblyName);
|
||||||
|
|
||||||
|
// If the dependency exists in our plugin directory, load it
|
||||||
|
if (File.Exists(assemblyPath))
|
||||||
|
{
|
||||||
|
return Assembly.LoadFrom(assemblyPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"AssemblyResolve failed for {args.Name}: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return null to let default resolution continue
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Plugin Loading/Unloading
|
||||||
|
internal void LoadPluginAssembly()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsPluginLoaded)
|
||||||
|
{
|
||||||
|
UnloadPluginAssembly();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CoreManager.Current.Actions.AddChatText($"[MosswartMassacre] Reloading {PluginAssemblyName}", 5);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginAssembly = Assembly.Load(File.ReadAllBytes(System.IO.Path.Combine(AssemblyDirectory, PluginAssemblyName)));
|
||||||
|
pluginType = pluginAssembly.GetType($"{PluginAssemblyNamespace}.PluginCore");
|
||||||
|
pluginInstance = Activator.CreateInstance(pluginType);
|
||||||
|
|
||||||
|
// Set the AssemblyDirectory property if it exists
|
||||||
|
var assemblyDirProperty = pluginType.GetProperty("AssemblyDirectory", BindingFlags.Public | BindingFlags.Static);
|
||||||
|
assemblyDirProperty?.SetValue(null, AssemblyDirectory);
|
||||||
|
|
||||||
|
// Set the IsHotReload flag if it exists
|
||||||
|
var isHotReloadProperty = pluginType.GetProperty("IsHotReload", BindingFlags.Public | BindingFlags.Static);
|
||||||
|
isHotReloadProperty?.SetValue(null, true);
|
||||||
|
|
||||||
|
// The original template doesn't set up Host - it just calls Startup
|
||||||
|
// The plugin should use CoreManager.Current.Actions instead of MyHost for hot reload scenarios
|
||||||
|
// We'll set a flag so the plugin knows it's being hot loaded
|
||||||
|
|
||||||
|
// Call Startup method
|
||||||
|
var startupMethod = pluginType.GetMethod("Startup", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
startupMethod.Invoke(pluginInstance, new object[] { });
|
||||||
|
|
||||||
|
IsPluginLoaded = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnloadPluginAssembly()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (pluginInstance != null && pluginType != null)
|
||||||
|
{
|
||||||
|
MethodInfo shutdownMethod = pluginType.GetMethod("Shutdown", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
shutdownMethod.Invoke(pluginInstance, null);
|
||||||
|
pluginInstance = null;
|
||||||
|
pluginType = null;
|
||||||
|
pluginAssembly = null;
|
||||||
|
}
|
||||||
|
IsPluginLoaded = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void Log(Exception ex)
|
||||||
|
{
|
||||||
|
Log(ex.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Log(string message)
|
||||||
|
{
|
||||||
|
File.AppendAllText(System.IO.Path.Combine(AssemblyDirectory, "loader_log.txt"), $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}\n");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CoreManager.Current.Actions.AddChatText($"[MosswartMassacre.Loader] {message}", 3);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
MosswartMassacre.Loader/MosswartMassacre.Loader.csproj
Normal file
37
MosswartMassacre.Loader/MosswartMassacre.Loader.csproj
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net48</TargetFramework>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<LangVersion>8</LangVersion>
|
||||||
|
<ProjectGuid>{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}</ProjectGuid>
|
||||||
|
<RootNamespace>MosswartMassacre.Loader</RootNamespace>
|
||||||
|
<AssemblyName>MosswartMassacre.Loader</AssemblyName>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<OutputPath>..\MosswartMassacre\bin\Debug\</OutputPath>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<OutputPath>..\MosswartMassacre\bin\Release\</OutputPath>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Decal.Adapter">
|
||||||
|
<HintPath>..\MosswartMassacre\lib\Decal.Adapter.dll</HintPath>
|
||||||
|
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Decal.Interop.Core, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||||
|
<HintPath>..\..\..\..\..\..\Program Files (x86)\Decal 3.0\.NET 4.0 PIA\Decal.Interop.Core.DLL</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -62,10 +62,9 @@ namespace MosswartMassacre
|
||||||
// PATHWAY 2: Target Host.Actions.AddChatText (what our plugin uses)
|
// PATHWAY 2: Target Host.Actions.AddChatText (what our plugin uses)
|
||||||
PatchHostActions();
|
PatchHostActions();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
// Only log if completely unable to apply any patches
|
// Only log if completely unable to apply any patches
|
||||||
System.Diagnostics.Debug.WriteLine($"Patch application failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,15 +92,13 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
ApplySinglePatch(method, prefixMethodName);
|
ApplySinglePatch(method, prefixMethodName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"HooksWrapper patch failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"HooksWrapper patch failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,18 +139,16 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
ApplySinglePatch(method, prefixMethodName);
|
ApplySinglePatch(method, prefixMethodName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Host.Actions patch failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATHWAY 3: Try to patch at PluginHost level
|
// PATHWAY 3: Try to patch at PluginHost level
|
||||||
PatchPluginHost();
|
PatchPluginHost();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Host.Actions patch failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,21 +177,18 @@ namespace MosswartMassacre
|
||||||
string prefixMethodName = "AddChatTextPrefixCore" + parameters.Length;
|
string prefixMethodName = "AddChatTextPrefixCore" + parameters.Length;
|
||||||
ApplySinglePatch(method, prefixMethodName);
|
ApplySinglePatch(method, prefixMethodName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"CoreActions patch failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"CoreManager patch failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"PluginHost patch failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,9 +210,8 @@ namespace MosswartMassacre
|
||||||
patchesApplied = true;
|
patchesApplied = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Failed to apply patch {prefixMethodName}: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,7 +227,6 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
var parameters = method.GetParameters();
|
var parameters = method.GetParameters();
|
||||||
string paramInfo = string.Join(", ", parameters.Select(p => p.ParameterType.Name));
|
string paramInfo = string.Join(", ", parameters.Select(p => p.ParameterType.Name));
|
||||||
System.Diagnostics.Debug.WriteLine($"AddChatText({paramInfo})");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,9 +244,8 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
patchesApplied = false;
|
patchesApplied = false;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony cleanup error: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,10 +297,8 @@ namespace MosswartMassacre
|
||||||
// Always increment to verify patch is working
|
// Always increment to verify patch is working
|
||||||
DecalHarmonyClean.messagesIntercepted++;
|
DecalHarmonyClean.messagesIntercepted++;
|
||||||
|
|
||||||
// DEBUG: Log ALL intercepted messages for troubleshooting
|
|
||||||
if (PluginCore.AggressiveChatStreamingEnabled)
|
if (PluginCore.AggressiveChatStreamingEnabled)
|
||||||
{
|
{
|
||||||
DecalHarmonyClean.AddDebugLog($"[DEBUG] HOOKS-RAW #{DecalHarmonyClean.messagesIntercepted}: [{text ?? "NULL"}] (color={color})");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process ALL messages (including our own) for streaming
|
// Process ALL messages (including our own) for streaming
|
||||||
|
|
@ -326,10 +313,9 @@ namespace MosswartMassacre
|
||||||
// Always return true to let the original AddChatText continue
|
// Always return true to let the original AddChatText continue
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
// Never let our interception break other plugins
|
// Never let our interception break other plugins
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony interception error: {ex.Message}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,9 +338,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony interception3 error: {ex.Message}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -377,9 +362,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony generic interception error: {ex.Message}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -399,7 +383,6 @@ namespace MosswartMassacre
|
||||||
var fullMessage = $"{timestamp} [{sourcePlugin}] {text}";
|
var fullMessage = $"{timestamp} [{sourcePlugin}] {text}";
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
System.Diagnostics.Debug.WriteLine($"[TARGET] HARMONY CAPTURED ({source}): {fullMessage}");
|
|
||||||
|
|
||||||
// Stream to WebSocket if both debug streaming AND WebSocket are enabled
|
// Stream to WebSocket if both debug streaming AND WebSocket are enabled
|
||||||
if (PluginCore.AggressiveChatStreamingEnabled && PluginCore.WebSocketEnabled)
|
if (PluginCore.AggressiveChatStreamingEnabled && PluginCore.WebSocketEnabled)
|
||||||
|
|
@ -407,9 +390,8 @@ namespace MosswartMassacre
|
||||||
Task.Run(() => WebSocket.SendChatTextAsync(color, text));
|
Task.Run(() => WebSocket.SendChatTextAsync(color, text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Message processing error: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -447,10 +429,8 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
DecalHarmonyClean.messagesIntercepted++;
|
DecalHarmonyClean.messagesIntercepted++;
|
||||||
|
|
||||||
// DEBUG: Log ALL intercepted messages for troubleshooting
|
|
||||||
if (PluginCore.AggressiveChatStreamingEnabled)
|
if (PluginCore.AggressiveChatStreamingEnabled)
|
||||||
{
|
{
|
||||||
DecalHarmonyClean.AddDebugLog($"[DEBUG] HOST-RAW #{DecalHarmonyClean.messagesIntercepted}: [{text ?? "NULL"}] (color={color}, target={target})");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
||||||
|
|
@ -462,9 +442,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony Host.Actions interception error: {ex.Message}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -487,9 +466,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony Host.Actions 4param interception error: {ex.Message}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -512,9 +490,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony Host.Actions generic interception error: {ex.Message}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -532,7 +509,6 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
if (PluginCore.AggressiveChatStreamingEnabled)
|
if (PluginCore.AggressiveChatStreamingEnabled)
|
||||||
{
|
{
|
||||||
DecalHarmonyClean.AddDebugLog($"[DEBUG] CORE2-RAW #{DecalHarmonyClean.messagesIntercepted}: [{text ?? "NULL"}] (color={color})");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
||||||
|
|
@ -544,9 +520,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony Core2 interception error: {ex.Message}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -562,7 +537,6 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
if (PluginCore.AggressiveChatStreamingEnabled)
|
if (PluginCore.AggressiveChatStreamingEnabled)
|
||||||
{
|
{
|
||||||
DecalHarmonyClean.AddDebugLog($"[DEBUG] CORE3-RAW #{DecalHarmonyClean.messagesIntercepted}: [{text ?? "NULL"}] (color={color}, target={target})");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
||||||
|
|
@ -574,9 +548,8 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Harmony Core3 interception error: {ex.Message}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -391,9 +391,8 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
// DECAL API uses CharacterFilter.GetCharProperty for character properties
|
// DECAL API uses CharacterFilter.GetCharProperty for character properties
|
||||||
aug.CurrentValue = characterFilter.GetCharProperty(aug.IntId.Value);
|
aug.CurrentValue = characterFilter.GetCharProperty(aug.IntId.Value);
|
||||||
// Debug disabled: PluginCore.WriteToChat($"Debug: {aug.Name} = {aug.CurrentValue} (Property: {aug.IntId.Value})");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
// Try alternative access using reflection
|
// Try alternative access using reflection
|
||||||
try
|
try
|
||||||
|
|
@ -402,37 +401,31 @@ namespace MosswartMassacre
|
||||||
if (valuesMethod != null)
|
if (valuesMethod != null)
|
||||||
{
|
{
|
||||||
aug.CurrentValue = (int)valuesMethod.Invoke(playerObject, new object[] { aug.IntId.Value });
|
aug.CurrentValue = (int)valuesMethod.Invoke(playerObject, new object[] { aug.IntId.Value });
|
||||||
PluginCore.WriteToChat($"Debug: {aug.Name} = {aug.CurrentValue} via reflection (ID: {aug.IntId.Value})");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
aug.CurrentValue = 0;
|
aug.CurrentValue = 0;
|
||||||
PluginCore.WriteToChat($"Debug: {aug.Name} - Values method not found");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex2)
|
catch
|
||||||
{
|
{
|
||||||
aug.CurrentValue = 0;
|
aug.CurrentValue = 0;
|
||||||
PluginCore.WriteToChat($"Debug: {aug.Name} - Failed: {ex.Message}, Reflection: {ex2.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
aug.CurrentValue = 0;
|
aug.CurrentValue = 0;
|
||||||
PluginCore.WriteToChat($"Debug: {aug.Name} - Player object is null");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
aug.CurrentValue = 0;
|
aug.CurrentValue = 0;
|
||||||
PluginCore.WriteToChat($"Debug: {aug.Name} - CharacterFilter is null");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
aug.CurrentValue = 0;
|
aug.CurrentValue = 0;
|
||||||
PluginCore.WriteToChat($"Debug: {aug.Name} - Exception: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -512,12 +505,10 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
// Use CharacterFilter.GetCharProperty for luminance auras
|
// Use CharacterFilter.GetCharProperty for luminance auras
|
||||||
aura.CurrentValue = characterFilter.GetCharProperty(aura.IntId);
|
aura.CurrentValue = characterFilter.GetCharProperty(aura.IntId);
|
||||||
// Debug disabled: PluginCore.WriteToChat($"Debug: {aura.Name} = {aura.CurrentValue}/{aura.Cap} (Property: {aura.IntId})");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
aura.CurrentValue = 0;
|
aura.CurrentValue = 0;
|
||||||
PluginCore.WriteToChat($"Debug: {aura.Name} - Failed to read: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -552,14 +543,12 @@ namespace MosswartMassacre
|
||||||
recall.IconId = GetSpellIcon(recall.SpellId);
|
recall.IconId = GetSpellIcon(recall.SpellId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
recall.IsKnown = false;
|
recall.IsKnown = false;
|
||||||
PluginCore.WriteToChat($"Debug: Error checking {recall.Name}: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginCore.WriteToChat($"Debug: Recall spells refresh completed - {knownCount}/{RecallSpells.Count} known"); // Debug disabled
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -705,9 +694,8 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: GetRealSpellIcon FileService error: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method 2: Use known spell icon mappings for cantrips
|
// Method 2: Use known spell icon mappings for cantrips
|
||||||
|
|
@ -848,7 +836,6 @@ namespace MosswartMassacre
|
||||||
// Add offset for spell icons to display correctly in VVS
|
// Add offset for spell icons to display correctly in VVS
|
||||||
// Based on MagTools pattern, spell icons need the offset for display
|
// Based on MagTools pattern, spell icons need the offset for display
|
||||||
int finalIconId = iconId + 0x6000000;
|
int finalIconId = iconId + 0x6000000;
|
||||||
PluginCore.WriteToChat($"Debug: Found cantrip spell {spellId} -> raw icon {iconId} -> final icon 0x{finalIconId:X}");
|
|
||||||
return finalIconId;
|
return finalIconId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -900,11 +887,9 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat("Debug: RefreshCantrips() starting");
|
|
||||||
|
|
||||||
if (CoreManager.Current?.CharacterFilter?.Name == null)
|
if (CoreManager.Current?.CharacterFilter?.Name == null)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat("Debug: No character filter available");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -913,11 +898,9 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
if (playerObject == null)
|
if (playerObject == null)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat("Debug: No player object found");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginCore.WriteToChat($"Debug: Character {characterFilter.Name} found, {playerObject.ActiveSpellCount} active spells");
|
|
||||||
|
|
||||||
// Clear dynamic skill lists
|
// Clear dynamic skill lists
|
||||||
Cantrips["Specialized Skills"].Clear();
|
Cantrips["Specialized Skills"].Clear();
|
||||||
|
|
@ -940,24 +923,20 @@ namespace MosswartMassacre
|
||||||
var enchantments = characterFilter.Enchantments;
|
var enchantments = characterFilter.Enchantments;
|
||||||
if (enchantments != null)
|
if (enchantments != null)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found {enchantments.Count} active enchantments");
|
|
||||||
for (int i = 0; i < enchantments.Count; i++)
|
for (int i = 0; i < enchantments.Count; i++)
|
||||||
{
|
{
|
||||||
var ench = enchantments[i];
|
var ench = enchantments[i];
|
||||||
var spell = SpellManager.GetSpell(ench.SpellId);
|
var spell = SpellManager.GetSpell(ench.SpellId);
|
||||||
if (spell != null && spell.CantripLevel != Mag.Shared.Spells.Spell.CantripLevels.None)
|
if (spell != null && spell.CantripLevel != Mag.Shared.Spells.Spell.CantripLevels.None)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found cantrip - Spell {ench.SpellId}: {spell.Name} (Level: {spell.CantripLevel})");
|
|
||||||
DetectCantrip(ench.SpellId);
|
DetectCantrip(ench.SpellId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat("Debug: No enchantments to scan");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Always show spell info regardless of count
|
|
||||||
// Compute final icon IDs for all cantrips after refresh
|
// Compute final icon IDs for all cantrips after refresh
|
||||||
foreach (var category in Cantrips)
|
foreach (var category in Cantrips)
|
||||||
{
|
{
|
||||||
|
|
@ -1057,7 +1036,6 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
if (skillInfo.Training == Decal.Adapter.Wrappers.TrainingType.Specialized)
|
if (skillInfo.Training == Decal.Adapter.Wrappers.TrainingType.Specialized)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Adding specialized skill: {skillName} (ID {skillId})");
|
|
||||||
Cantrips["Specialized Skills"][skillName] = new CantripInfo
|
Cantrips["Specialized Skills"][skillName] = new CantripInfo
|
||||||
{
|
{
|
||||||
Name = skillName,
|
Name = skillName,
|
||||||
|
|
@ -1068,7 +1046,6 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
else if (skillInfo.Training == Decal.Adapter.Wrappers.TrainingType.Trained)
|
else if (skillInfo.Training == Decal.Adapter.Wrappers.TrainingType.Trained)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Adding trained skill: {skillName} (ID {skillId})");
|
|
||||||
Cantrips["Trained Skills"][skillName] = new CantripInfo
|
Cantrips["Trained Skills"][skillName] = new CantripInfo
|
||||||
{
|
{
|
||||||
Name = skillName,
|
Name = skillName,
|
||||||
|
|
@ -1097,14 +1074,12 @@ namespace MosswartMassacre
|
||||||
var characterFilter = CoreManager.Current.CharacterFilter;
|
var characterFilter = CoreManager.Current.CharacterFilter;
|
||||||
if (characterFilter == null)
|
if (characterFilter == null)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: CharacterFilter is null for skill {skillId}");
|
|
||||||
return GetFallbackSkillIcon(skillId);
|
return GetFallbackSkillIcon(skillId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate skillId range for DECAL API
|
// Validate skillId range for DECAL API
|
||||||
if (skillId < 1 || skillId > 54)
|
if (skillId < 1 || skillId > 54)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Invalid skill ID {skillId} (must be 1-54)");
|
|
||||||
return GetFallbackSkillIcon(skillId);
|
return GetFallbackSkillIcon(skillId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1113,21 +1088,17 @@ namespace MosswartMassacre
|
||||||
var skillInfo = characterFilter.Skills[(Decal.Adapter.Wrappers.CharFilterSkillType)skillId];
|
var skillInfo = characterFilter.Skills[(Decal.Adapter.Wrappers.CharFilterSkillType)skillId];
|
||||||
if (skillInfo == null)
|
if (skillInfo == null)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: No skill info found for skill {skillId}");
|
|
||||||
return GetFallbackSkillIcon(skillId);
|
return GetFallbackSkillIcon(skillId);
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginCore.WriteToChat($"Debug: Attempting icon access for skill {skillId} ({GetSkillName(skillId)})");
|
|
||||||
|
|
||||||
// Try to access skill icon via reflection (DECAL's SkillInfoWrapper.Dat property)
|
// Try to access skill icon via reflection (DECAL's SkillInfoWrapper.Dat property)
|
||||||
var skillType = skillInfo.GetType();
|
var skillType = skillInfo.GetType();
|
||||||
PluginCore.WriteToChat($"Debug: SkillInfo type: {skillType.Name}");
|
|
||||||
|
|
||||||
// Method 1: Try FileService SkillTable approach (most reliable)
|
// Method 1: Try FileService SkillTable approach (most reliable)
|
||||||
int realIconId = GetRealSkillIconFromDat(skillId);
|
int realIconId = GetRealSkillIconFromDat(skillId);
|
||||||
if (realIconId > 0)
|
if (realIconId > 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found real skill icon {realIconId} for skill {skillId}, applying offset");
|
|
||||||
return realIconId + 0x6000000;
|
return realIconId + 0x6000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1139,7 +1110,6 @@ namespace MosswartMassacre
|
||||||
if (datObject != null)
|
if (datObject != null)
|
||||||
{
|
{
|
||||||
var datType = datObject.GetType();
|
var datType = datObject.GetType();
|
||||||
PluginCore.WriteToChat($"Debug: Dat object type: {datType.Name}");
|
|
||||||
|
|
||||||
// Try the exact property names from AC system
|
// Try the exact property names from AC system
|
||||||
string[] iconPropertyNames = { "IconID", "Icon", "IconId", "uiGraphic", "GraphicID" };
|
string[] iconPropertyNames = { "IconID", "Icon", "IconId", "uiGraphic", "GraphicID" };
|
||||||
|
|
@ -1152,15 +1122,12 @@ namespace MosswartMassacre
|
||||||
var iconValue = iconProperty.GetValue(datObject, null);
|
var iconValue = iconProperty.GetValue(datObject, null);
|
||||||
if (iconValue != null)
|
if (iconValue != null)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found {propName} property with value {iconValue} (type: {iconValue.GetType().Name})");
|
|
||||||
if (iconValue is int iconId && iconId > 0)
|
if (iconValue is int iconId && iconId > 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Using icon {iconId} from {propName} for skill {skillId}");
|
|
||||||
return iconId + 0x6000000;
|
return iconId + 0x6000000;
|
||||||
}
|
}
|
||||||
else if (iconValue is uint uiconId && uiconId > 0)
|
else if (iconValue is uint uiconId && uiconId > 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Using uint icon {uiconId} from {propName} for skill {skillId}");
|
|
||||||
return (int)uiconId + 0x6000000;
|
return (int)uiconId + 0x6000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1174,15 +1141,12 @@ namespace MosswartMassacre
|
||||||
var iconValue = iconField.GetValue(datObject);
|
var iconValue = iconField.GetValue(datObject);
|
||||||
if (iconValue != null)
|
if (iconValue != null)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found {propName} field with value {iconValue} (type: {iconValue.GetType().Name})");
|
|
||||||
if (iconValue is int iconId && iconId > 0)
|
if (iconValue is int iconId && iconId > 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Using icon {iconId} from field {propName} for skill {skillId}");
|
|
||||||
return iconId + 0x6000000;
|
return iconId + 0x6000000;
|
||||||
}
|
}
|
||||||
else if (iconValue is uint uiconId && uiconId > 0)
|
else if (iconValue is uint uiconId && uiconId > 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Using uint icon {uiconId} from field {propName} for skill {skillId}");
|
|
||||||
return (int)uiconId + 0x6000000;
|
return (int)uiconId + 0x6000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1190,8 +1154,6 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: List all available properties and fields on Dat object
|
|
||||||
PluginCore.WriteToChat($"Debug: Available properties on {datType.Name}:");
|
|
||||||
foreach (var prop in datType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
foreach (var prop in datType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -1207,12 +1169,10 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Dat property exists but returns null for skill {skillId}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: No Dat property found on SkillInfoWrapper for skill {skillId}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method 3: Try direct properties on SkillInfoWrapper
|
// Method 3: Try direct properties on SkillInfoWrapper
|
||||||
|
|
@ -1225,24 +1185,20 @@ namespace MosswartMassacre
|
||||||
var iconValue = iconProperty.GetValue(skillInfo, null);
|
var iconValue = iconProperty.GetValue(skillInfo, null);
|
||||||
if (iconValue is int iconId && iconId > 0)
|
if (iconValue is int iconId && iconId > 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Using direct icon {iconId} from {propName} for skill {skillId}");
|
|
||||||
return iconId + 0x6000000;
|
return iconId + 0x6000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Skill access failed for skill {skillId}: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to predefined mapping
|
// Fallback to predefined mapping
|
||||||
PluginCore.WriteToChat($"Debug: Using fallback icon for skill {skillId}");
|
|
||||||
return GetFallbackSkillIcon(skillId);
|
return GetFallbackSkillIcon(skillId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Error in GetSkillIconId for skill {skillId}: {ex.Message}");
|
|
||||||
return GetFallbackSkillIcon(skillId);
|
return GetFallbackSkillIcon(skillId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1280,7 +1236,6 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
// Access SkillTable via reflection to get skill data
|
// Access SkillTable via reflection to get skill data
|
||||||
var skillTableType = fileService.SkillTable.GetType();
|
var skillTableType = fileService.SkillTable.GetType();
|
||||||
PluginCore.WriteToChat($"Debug: SkillTable type: {skillTableType.Name}");
|
|
||||||
|
|
||||||
// Look for methods that can get skill by ID
|
// Look for methods that can get skill by ID
|
||||||
var methods = skillTableType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
var methods = skillTableType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
@ -1296,7 +1251,6 @@ namespace MosswartMassacre
|
||||||
var skillData = method.Invoke(fileService.SkillTable, new object[] { skillId });
|
var skillData = method.Invoke(fileService.SkillTable, new object[] { skillId });
|
||||||
if (skillData != null)
|
if (skillData != null)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found skill data via {method.Name}: {skillData.GetType().Name}");
|
|
||||||
|
|
||||||
// Look for icon properties on the skill data
|
// Look for icon properties on the skill data
|
||||||
var skillDataType = skillData.GetType();
|
var skillDataType = skillData.GetType();
|
||||||
|
|
@ -1310,42 +1264,36 @@ namespace MosswartMassacre
|
||||||
var iconValue = iconProp.GetValue(skillData, null);
|
var iconValue = iconProp.GetValue(skillData, null);
|
||||||
if (iconValue is int iconInt && iconInt > 0)
|
if (iconValue is int iconInt && iconInt > 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found skill icon {iconInt} via FileService.{method.Name}.{propName}");
|
|
||||||
return iconInt;
|
return iconInt;
|
||||||
}
|
}
|
||||||
else if (iconValue is uint iconUint && iconUint > 0)
|
else if (iconValue is uint iconUint && iconUint > 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found skill icon {iconUint} via FileService.{method.Name}.{propName}");
|
|
||||||
return (int)iconUint;
|
return (int)iconUint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
// Method call failed, try next one
|
// Method call failed, try next one
|
||||||
PluginCore.WriteToChat($"Debug: Method {method.Name} failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: FileService SkillTable access failed: {ex.Message}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: FileService or SkillTable is null");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0; // No icon found
|
return 0; // No icon found
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: GetRealSkillIconFromDat failed: {ex.Message}");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1420,12 +1368,10 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
if (skillIconMap.ContainsKey(skillId))
|
if (skillIconMap.ContainsKey(skillId))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Using fallback icon 0x{skillIconMap[skillId]:X} for skill {skillId} ({GetSkillName(skillId)})");
|
|
||||||
return skillIconMap[skillId];
|
return skillIconMap[skillId];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final fallback to proven working icon from recalls system
|
// Final fallback to proven working icon from recalls system
|
||||||
PluginCore.WriteToChat($"Debug: Using default fallback icon 0x6002D14 for unknown skill {skillId}");
|
|
||||||
return 0x6002D14; // Portal icon - confirmed working in recalls
|
return 0x6002D14; // Portal icon - confirmed working in recalls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1457,12 +1403,10 @@ namespace MosswartMassacre
|
||||||
string spellName = GetSpellName(spellId);
|
string spellName = GetSpellName(spellId);
|
||||||
if (string.IsNullOrEmpty(spellName))
|
if (string.IsNullOrEmpty(spellName))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: FAILED to get spell name for spell ID {spellId}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug output to see what spells we're processing
|
// Debug output to see what spells we're processing
|
||||||
PluginCore.WriteToChat($"Debug: Processing spell ID {spellId}: '{spellName}'");
|
|
||||||
|
|
||||||
// Define cantrip levels and their patterns
|
// Define cantrip levels and their patterns
|
||||||
var cantripPatterns = new Dictionary<string, (string level, System.Drawing.Color color)>
|
var cantripPatterns = new Dictionary<string, (string level, System.Drawing.Color color)>
|
||||||
|
|
@ -1484,42 +1428,35 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
// Remove the level prefix to get the skill/attribute name
|
// Remove the level prefix to get the skill/attribute name
|
||||||
string skillPart = spellName.Substring(pattern.Length + 1);
|
string skillPart = spellName.Substring(pattern.Length + 1);
|
||||||
PluginCore.WriteToChat($"Debug: Found {level} cantrip, skillPart='{skillPart}'");
|
|
||||||
|
|
||||||
// Get the spell icon for this cantrip spell
|
// Get the spell icon for this cantrip spell
|
||||||
int spellIconId = GetRealSpellIcon(spellId);
|
int spellIconId = GetRealSpellIcon(spellId);
|
||||||
if (spellIconId == 0)
|
if (spellIconId == 0)
|
||||||
{
|
{
|
||||||
spellIconId = 0x6002D14; // Default fallback icon
|
spellIconId = 0x6002D14; // Default fallback icon
|
||||||
PluginCore.WriteToChat($"Debug: No real icon found for spell {spellId}, using default");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Got spell icon 0x{spellIconId:X} for spell {spellId}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match Protection Auras first (exact format: "Minor Armor", "Epic Bludgeoning Ward")
|
// Try to match Protection Auras first (exact format: "Minor Armor", "Epic Bludgeoning Ward")
|
||||||
if (MatchProtectionAura(skillPart, level, color, spellIconId))
|
if (MatchProtectionAura(skillPart, level, color, spellIconId))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Matched as Protection Aura: '{skillPart}' with spell icon {spellIconId}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match Attributes (exact format: "Minor Strength", "Epic Focus")
|
// Try to match Attributes (exact format: "Minor Strength", "Epic Focus")
|
||||||
if (MatchAttribute(skillPart, level, color, spellIconId))
|
if (MatchAttribute(skillPart, level, color, spellIconId))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Matched as Attribute: '{skillPart}' with spell icon {spellIconId}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match Skills using the replacement mappings
|
// Try to match Skills using the replacement mappings
|
||||||
if (MatchSkill(skillPart, level, color, spellIconId))
|
if (MatchSkill(skillPart, level, color, spellIconId))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Matched as Skill: '{skillPart}' with spell icon {spellIconId}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginCore.WriteToChat($"Debug: No match found for: '{skillPart}' (level: {level}) - Full spell: '{spellName}'");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -1610,13 +1547,11 @@ namespace MosswartMassacre
|
||||||
["Willpower"] = "Willpower" // "Epic Willpower" -> Willpower
|
["Willpower"] = "Willpower" // "Epic Willpower" -> Willpower
|
||||||
};
|
};
|
||||||
|
|
||||||
PluginCore.WriteToChat($"Debug: MatchAttribute checking '{cleanedSkillPart}' for {level}");
|
|
||||||
|
|
||||||
foreach (var mapping in attributeMappings)
|
foreach (var mapping in attributeMappings)
|
||||||
{
|
{
|
||||||
if (cleanedSkillPart.Equals(mapping.Key, StringComparison.OrdinalIgnoreCase))
|
if (cleanedSkillPart.Equals(mapping.Key, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found mapping match! '{mapping.Key}' -> '{mapping.Value}'");
|
|
||||||
|
|
||||||
// Create the cantrip entry if it doesn't exist
|
// Create the cantrip entry if it doesn't exist
|
||||||
if (!Cantrips["Attributes"].ContainsKey(mapping.Value))
|
if (!Cantrips["Attributes"].ContainsKey(mapping.Value))
|
||||||
|
|
@ -1632,7 +1567,6 @@ namespace MosswartMassacre
|
||||||
var cantrip = Cantrips["Attributes"][mapping.Value];
|
var cantrip = Cantrips["Attributes"][mapping.Value];
|
||||||
if (cantrip.Value == "N/A" || IsHigherCantripLevel(level, cantrip.Value))
|
if (cantrip.Value == "N/A" || IsHigherCantripLevel(level, cantrip.Value))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Setting {mapping.Value} to {level}");
|
|
||||||
cantrip.Value = level;
|
cantrip.Value = level;
|
||||||
cantrip.Color = color;
|
cantrip.Color = color;
|
||||||
cantrip.SpellIconId = spellIconId; // Use the actual spell icon from the cantrip
|
cantrip.SpellIconId = spellIconId; // Use the actual spell icon from the cantrip
|
||||||
|
|
@ -1646,7 +1580,6 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
if (cleanedSkillPart.IndexOf(mapping.Key, StringComparison.OrdinalIgnoreCase) >= 0)
|
if (cleanedSkillPart.IndexOf(mapping.Key, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Found partial mapping match! '{cleanedSkillPart}' contains '{mapping.Key}' -> '{mapping.Value}'");
|
|
||||||
|
|
||||||
// Create the cantrip entry if it doesn't exist
|
// Create the cantrip entry if it doesn't exist
|
||||||
if (!Cantrips["Attributes"].ContainsKey(mapping.Value))
|
if (!Cantrips["Attributes"].ContainsKey(mapping.Value))
|
||||||
|
|
@ -1662,7 +1595,6 @@ namespace MosswartMassacre
|
||||||
var cantrip = Cantrips["Attributes"][mapping.Value];
|
var cantrip = Cantrips["Attributes"][mapping.Value];
|
||||||
if (cantrip.Value == "N/A" || IsHigherCantripLevel(level, cantrip.Value))
|
if (cantrip.Value == "N/A" || IsHigherCantripLevel(level, cantrip.Value))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Setting {mapping.Value} to {level} via partial match");
|
|
||||||
cantrip.Value = level;
|
cantrip.Value = level;
|
||||||
cantrip.Color = color;
|
cantrip.Color = color;
|
||||||
cantrip.SpellIconId = spellIconId; // Use the actual spell icon from the cantrip
|
cantrip.SpellIconId = spellIconId; // Use the actual spell icon from the cantrip
|
||||||
|
|
@ -1764,9 +1696,8 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Error getting spell {spellId}: {ex.Message}");
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1812,7 +1743,6 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
foreach (var spellName in testSpells)
|
foreach (var spellName in testSpells)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Testing detection for: {spellName}");
|
|
||||||
TestDetectCantripByName(spellName);
|
TestDetectCantripByName(spellName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1827,7 +1757,6 @@ namespace MosswartMassacre
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Simulate the detection logic with a fake spell name
|
// Simulate the detection logic with a fake spell name
|
||||||
PluginCore.WriteToChat($"Debug: Processing spell: {spellName}");
|
|
||||||
|
|
||||||
// Define cantrip levels and their patterns
|
// Define cantrip levels and their patterns
|
||||||
var cantripPatterns = new Dictionary<string, (string level, System.Drawing.Color color)>
|
var cantripPatterns = new Dictionary<string, (string level, System.Drawing.Color color)>
|
||||||
|
|
@ -1850,7 +1779,6 @@ namespace MosswartMassacre
|
||||||
// Remove the level prefix to get the skill/attribute name
|
// Remove the level prefix to get the skill/attribute name
|
||||||
string skillPart = spellName.Substring(pattern.Length + 1);
|
string skillPart = spellName.Substring(pattern.Length + 1);
|
||||||
|
|
||||||
PluginCore.WriteToChat($"Debug: Detected {level} cantrip for {skillPart}");
|
|
||||||
|
|
||||||
// Get a test spell icon (use default for testing)
|
// Get a test spell icon (use default for testing)
|
||||||
int testSpellIconId = 0x6002D14;
|
int testSpellIconId = 0x6002D14;
|
||||||
|
|
@ -1858,25 +1786,21 @@ namespace MosswartMassacre
|
||||||
// Try to match Protection Auras first
|
// Try to match Protection Auras first
|
||||||
if (MatchProtectionAura(skillPart, level, color, testSpellIconId))
|
if (MatchProtectionAura(skillPart, level, color, testSpellIconId))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Matched protection aura: {skillPart}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match Attributes
|
// Try to match Attributes
|
||||||
if (MatchAttribute(skillPart, level, color, testSpellIconId))
|
if (MatchAttribute(skillPart, level, color, testSpellIconId))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Matched attribute: {skillPart}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to match Skills
|
// Try to match Skills
|
||||||
if (MatchSkill(skillPart, level, color, testSpellIconId))
|
if (MatchSkill(skillPart, level, color, testSpellIconId))
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Debug: Matched skill: {skillPart}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginCore.WriteToChat($"Debug: No match found for: {skillPart}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
9
MosswartMassacre/FodyWeavers.xml
Normal file
9
MosswartMassacre/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||||
|
<Costura>
|
||||||
|
<IncludeAssemblies>
|
||||||
|
YamlDotNet
|
||||||
|
Newtonsoft.Json
|
||||||
|
</IncludeAssemblies>
|
||||||
|
</Costura>
|
||||||
|
</Weavers>
|
||||||
|
|
@ -178,6 +178,7 @@
|
||||||
<Compile Include="HttpCommandServer.cs" />
|
<Compile Include="HttpCommandServer.cs" />
|
||||||
<Compile Include="DelayedCommandManager.cs" />
|
<Compile Include="DelayedCommandManager.cs" />
|
||||||
<Compile Include="PluginCore.cs" />
|
<Compile Include="PluginCore.cs" />
|
||||||
|
<Compile Include="QuestNames.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Properties\Resources.Designer.cs">
|
<Compile Include="Properties\Resources.Designer.cs">
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
|
|
@ -229,4 +230,13 @@
|
||||||
</COMReference>
|
</COMReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Import Project="..\packages\Fody.6.8.0\build\Fody.targets" Condition="Exists('..\packages\Fody.6.8.0\build\Fody.targets')" />
|
||||||
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ErrorText>This project references NuGet package(s) that are missing on this machine. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Error Condition="!Exists('..\packages\Fody.6.8.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.6.8.0\build\Fody.targets'))" />
|
||||||
|
<Error Condition="!Exists('..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets'))" />
|
||||||
|
</Target>
|
||||||
|
<Import Project="..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets" Condition="Exists('..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -22,12 +22,20 @@ namespace MosswartMassacre
|
||||||
// 1) Character name
|
// 1) Character name
|
||||||
var characterName = CoreManager.Current.CharacterFilter.Name;
|
var characterName = CoreManager.Current.CharacterFilter.Name;
|
||||||
|
|
||||||
// 2) Plugin folder
|
// 2) Plugin folder - handle hot reload scenarios
|
||||||
var pluginFolder = Path.GetDirectoryName(
|
string pluginFolder;
|
||||||
|
if (!string.IsNullOrEmpty(PluginCore.AssemblyDirectory))
|
||||||
|
{
|
||||||
|
pluginFolder = PluginCore.AssemblyDirectory;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pluginFolder = Path.GetDirectoryName(
|
||||||
System.Reflection.Assembly
|
System.Reflection.Assembly
|
||||||
.GetExecutingAssembly()
|
.GetExecutingAssembly()
|
||||||
.Location
|
.Location
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 3) Character-specific folder path
|
// 3) Character-specific folder path
|
||||||
var characterFolder = Path.Combine(pluginFolder, characterName);
|
var characterFolder = Path.Combine(pluginFolder, characterName);
|
||||||
|
|
@ -86,7 +94,20 @@ namespace MosswartMassacre
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
loginComplete = true;
|
loginComplete = true;
|
||||||
if (!PluginSettings.Instance.InventoryLog)
|
|
||||||
|
// Defensive check - settings might not be initialized yet due to event handler order
|
||||||
|
bool inventoryLogEnabled;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
inventoryLogEnabled = PluginSettings.Instance.InventoryLog;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat("[INV] Settings not ready, skipping inventory check");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inventoryLogEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!File.Exists(InventoryFileName))
|
if (!File.Exists(InventoryFileName))
|
||||||
|
|
@ -112,7 +133,16 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
private void WorldFilter_CreateObject(object sender, CreateObjectEventArgs e)
|
private void WorldFilter_CreateObject(object sender, CreateObjectEventArgs e)
|
||||||
{
|
{
|
||||||
if (!loginComplete || !PluginSettings.Instance.InventoryLog) return;
|
if (!loginComplete) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!PluginSettings.Instance.InventoryLog) return;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return; // Settings not ready, skip silently
|
||||||
|
}
|
||||||
|
|
||||||
if (!e.New.HasIdData && ObjectClassNeedsIdent(e.New.ObjectClass, e.New.Name)
|
if (!e.New.HasIdData && ObjectClassNeedsIdent(e.New.ObjectClass, e.New.Name)
|
||||||
&& !requestedIds.Contains(e.New.Id)
|
&& !requestedIds.Contains(e.New.Id)
|
||||||
|
|
@ -125,7 +155,16 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
private void WorldFilter_ChangeObject(object sender, ChangeObjectEventArgs e)
|
private void WorldFilter_ChangeObject(object sender, ChangeObjectEventArgs e)
|
||||||
{
|
{
|
||||||
if (!loginComplete || !PluginSettings.Instance.InventoryLog) return;
|
if (!loginComplete) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!PluginSettings.Instance.InventoryLog) return;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return; // Settings not ready, skip silently
|
||||||
|
}
|
||||||
|
|
||||||
if (loggedInAndWaitingForIdData)
|
if (loggedInAndWaitingForIdData)
|
||||||
{
|
{
|
||||||
|
|
@ -159,10 +198,17 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CharacterFilter_Logoff(object sender, Decal.Adapter.Wrappers.LogoffEventArgs e)
|
private void CharacterFilter_Logoff(object sender, Decal.Adapter.Wrappers.LogoffEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!PluginSettings.Instance.InventoryLog) return;
|
if (!PluginSettings.Instance.InventoryLog) return;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return; // Settings not ready, skip silently
|
||||||
|
}
|
||||||
DumpInventoryToFile();
|
DumpInventoryToFile();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,32 @@ namespace MosswartMassacre
|
||||||
[FriendlyName("Mosswart Massacre")]
|
[FriendlyName("Mosswart Massacre")]
|
||||||
public class PluginCore : PluginBase
|
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 PluginHost MyHost;
|
||||||
internal static int totalKills = 0;
|
internal static int totalKills = 0;
|
||||||
internal static int rareCount = 0;
|
internal static int rareCount = 0;
|
||||||
|
|
@ -89,6 +115,10 @@ namespace MosswartMassacre
|
||||||
private MossyInventory _inventoryLogger;
|
private MossyInventory _inventoryLogger;
|
||||||
public static NavVisualization navVisualization;
|
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 Queue<string> rareMessageQueue = new Queue<string>();
|
||||||
private static DateTime _lastSent = DateTime.MinValue;
|
private static DateTime _lastSent = DateTime.MinValue;
|
||||||
private static readonly Queue<string> _chatQueue = new Queue<string>();
|
private static readonly Queue<string> _chatQueue = new Queue<string>();
|
||||||
|
|
@ -96,9 +126,41 @@ namespace MosswartMassacre
|
||||||
protected override void Startup()
|
protected override void Startup()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
// Set MyHost - for hot reload scenarios, Host might be null
|
||||||
|
if (Host != null)
|
||||||
{
|
{
|
||||||
MyHost = Host;
|
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
|
// Note: Startup messages will appear after character login
|
||||||
// Subscribe to chat message event
|
// Subscribe to chat message event
|
||||||
|
|
@ -165,7 +227,7 @@ namespace MosswartMassacre
|
||||||
PluginSettings.Save();
|
PluginSettings.Save();
|
||||||
if (TelemetryEnabled)
|
if (TelemetryEnabled)
|
||||||
Telemetry.Stop(); // ensure no dangling timer / HttpClient
|
Telemetry.Stop(); // ensure no dangling timer / HttpClient
|
||||||
WriteToChat("Mosswart Massacre is shutting down...");
|
WriteToChat("Mosswart Massacre is shutting down!!!!!");
|
||||||
|
|
||||||
// Unsubscribe from chat message event
|
// Unsubscribe from chat message event
|
||||||
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
|
CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
|
||||||
|
|
@ -203,6 +265,22 @@ namespace MosswartMassacre
|
||||||
commandTimer = null;
|
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
|
// Clean up the view
|
||||||
ViewManager.ViewDestroy();
|
ViewManager.ViewDestroy();
|
||||||
//Disable vtank interface
|
//Disable vtank interface
|
||||||
|
|
@ -279,6 +357,160 @@ namespace MosswartMassacre
|
||||||
// Initialize cached Prismatic Taper count
|
// Initialize cached Prismatic Taper count
|
||||||
InitializePrismaticTaperCount();
|
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()
|
private void InitializePrismaticTaperCount()
|
||||||
|
|
@ -658,7 +890,6 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// WriteToChat($"[Debug] Chat Color: {e.Color}, Message: {e.Text}");
|
|
||||||
|
|
||||||
if (IsKilledByMeMessage(e.Text))
|
if (IsKilledByMeMessage(e.Text))
|
||||||
{
|
{
|
||||||
|
|
@ -888,9 +1119,33 @@ namespace MosswartMassacre
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
public static void WriteToChat(string message)
|
public static void WriteToChat(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// For hot reload scenarios where MyHost might be null, use CoreManager directly
|
||||||
|
if (MyHost != null)
|
||||||
{
|
{
|
||||||
MyHost.Actions.AddChatText("[Mosswart Massacre] " + message, 0, 1);
|
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()
|
public static void RestartStats()
|
||||||
{
|
{
|
||||||
totalKills = 0;
|
totalKills = 0;
|
||||||
|
|
@ -997,6 +1252,7 @@ namespace MosswartMassacre
|
||||||
WriteToChat("Usage: /mm ws <enable|disable>");
|
WriteToChat("Usage: /mm ws <enable|disable>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WriteToChat("Usage: /mm ws <enable|disable>");
|
WriteToChat("Usage: /mm ws <enable|disable>");
|
||||||
|
|
@ -1019,10 +1275,9 @@ namespace MosswartMassacre
|
||||||
WriteToChat("/mm harmonyraw - Show raw intercepted messages (debug output)");
|
WriteToChat("/mm harmonyraw - Show raw intercepted messages (debug output)");
|
||||||
WriteToChat("/mm testprismatic - Test Prismatic Taper detection and icon lookup");
|
WriteToChat("/mm testprismatic - Test Prismatic Taper detection and icon lookup");
|
||||||
WriteToChat("/mm deathstats - Show current death tracking statistics");
|
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 testtaper - Test cached Prismatic Taper tracking");
|
||||||
WriteToChat("/mm debugtaper - Show detailed taper tracking debug info");
|
WriteToChat("/mm debugtaper - Show detailed taper tracking debug info");
|
||||||
WriteToChat("/mm gui - Manually initialize/reinitialize GUI");
|
WriteToChat("/mm gui - Manually initialize/reinitialize GUI!!!");
|
||||||
break;
|
break;
|
||||||
case "report":
|
case "report":
|
||||||
TimeSpan elapsed = DateTime.Now - statsStartTime;
|
TimeSpan elapsed = DateTime.Now - statsStartTime;
|
||||||
|
|
@ -1134,7 +1389,6 @@ namespace MosswartMassacre
|
||||||
WriteToChat("=== Harmony Patch Status (UtilityBelt Pattern) ===");
|
WriteToChat("=== Harmony Patch Status (UtilityBelt Pattern) ===");
|
||||||
WriteToChat($"Patches Active: {DecalHarmonyClean.IsActive()}");
|
WriteToChat($"Patches Active: {DecalHarmonyClean.IsActive()}");
|
||||||
WriteToChat($"Messages Intercepted: {DecalHarmonyClean.GetMessagesIntercepted()}");
|
WriteToChat($"Messages Intercepted: {DecalHarmonyClean.GetMessagesIntercepted()}");
|
||||||
WriteToChat($"Debug Streaming: {AggressiveChatStreamingEnabled}");
|
|
||||||
WriteToChat($"WebSocket Streaming: {(AggressiveChatStreamingEnabled && WebSocketEnabled ? "ACTIVE" : "INACTIVE")}");
|
WriteToChat($"WebSocket Streaming: {(AggressiveChatStreamingEnabled && WebSocketEnabled ? "ACTIVE" : "INACTIVE")}");
|
||||||
|
|
||||||
// Test Harmony availability
|
// Test Harmony availability
|
||||||
|
|
@ -1186,31 +1440,7 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
|
|
||||||
case "harmonyraw":
|
case "harmonyraw":
|
||||||
try
|
// Debug functionality removed
|
||||||
{
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "initgui":
|
case "initgui":
|
||||||
|
|
@ -1394,48 +1624,7 @@ namespace MosswartMassacre
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "debugtaper":
|
case "debugtaper":
|
||||||
try
|
// Debug functionality removed
|
||||||
{
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "finditem":
|
case "finditem":
|
||||||
|
|
|
||||||
|
|
@ -32,16 +32,34 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
// determine plugin folder and character-specific folder
|
// determine plugin folder and character-specific folder
|
||||||
string characterName = CoreManager.Current.CharacterFilter.Name;
|
string characterName = CoreManager.Current.CharacterFilter.Name;
|
||||||
string pluginFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
|
||||||
|
// For hot reload scenarios, use the AssemblyDirectory set by the Loader
|
||||||
|
// For normal loading, fall back to the executing assembly location
|
||||||
|
string pluginFolder;
|
||||||
|
if (!string.IsNullOrEmpty(PluginCore.AssemblyDirectory))
|
||||||
|
{
|
||||||
|
pluginFolder = PluginCore.AssemblyDirectory;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pluginFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||||
|
}
|
||||||
|
|
||||||
// Path for character-specific folder
|
// Path for character-specific folder
|
||||||
string characterFolder = Path.Combine(pluginFolder, characterName);
|
string characterFolder = Path.Combine(pluginFolder, characterName);
|
||||||
|
|
||||||
// Create the character folder if it doesn't exist
|
// Create the character folder if it doesn't exist
|
||||||
if (!Directory.Exists(characterFolder))
|
if (!Directory.Exists(characterFolder))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(characterFolder);
|
Directory.CreateDirectory(characterFolder);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.DispatchChatToBoxWithPluginIntercept($"[Settings] Failed to create character folder: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// YAML file is now in the character-specific folder
|
// YAML file is now in the character-specific folder
|
||||||
_filePath = Path.Combine(characterFolder, $"{characterName}.yaml");
|
_filePath = Path.Combine(characterFolder, $"{characterName}.yaml");
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,23 @@ namespace MosswartMassacre
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Quest Name Mapping
|
||||||
|
public string GetFriendlyQuestName(string questStamp)
|
||||||
|
{
|
||||||
|
return QuestNames.GetFriendlyName(questStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetQuestDisplayName(string questStamp)
|
||||||
|
{
|
||||||
|
return QuestNames.GetDisplayName(questStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetQuestNameMappingsCount()
|
||||||
|
{
|
||||||
|
return QuestNames.QuestStampToName.Count;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Quest Parsing
|
#region Quest Parsing
|
||||||
private void OnChatBoxMessage(object sender, ChatTextInterceptEventArgs e)
|
private void OnChatBoxMessage(object sender, ChatTextInterceptEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
228
MosswartMassacre/QuestNames.cs
Normal file
228
MosswartMassacre/QuestNames.cs
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MosswartMassacre
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Static quest name mappings from quest stamp to friendly display name
|
||||||
|
/// Based on questtracker repository data
|
||||||
|
/// </summary>
|
||||||
|
public static class QuestNames
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Dictionary mapping quest stamps to friendly quest names
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Dictionary<string, string> QuestStampToName = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
// Character-specific Quest Stamps (from actual /myquests output)
|
||||||
|
["30minattributes"] = "30 Minute Attribute Gems Timer",
|
||||||
|
["academeyexittokengiven"] = "Academy Exit Token Received",
|
||||||
|
["aerbaxchestkey2pickup"] = "Aerbax Chest Key #2 Pickup",
|
||||||
|
["anekshaygemofknowledgetimer_monthly"] = "A'nekshay Gem of Knowledge Monthly Timer",
|
||||||
|
["anekshaygemoflesserknowledgecollectedinamonth"] = "A'nekshay Gems of Lesser Knowledge Monthly Count",
|
||||||
|
["anekshaygemoflesserknowledgetimer_monthly"] = "A'nekshay Gem of Lesser Knowledge Monthly Timer",
|
||||||
|
["attributereset30day"] = "30-Day Attribute Reset Timer",
|
||||||
|
["augmentationblankgemacquired"] = "Blank Augmentation Gem Pickup Timer",
|
||||||
|
["bellowsnewbieturnedin"] = "Blacksmith's Bellows Turned In",
|
||||||
|
["bonecrunchkeypickuptimer"] = "Bonecrunch's Key Pickup Timer",
|
||||||
|
["callingstonegiven"] = "Calling Stone Turned Over",
|
||||||
|
["defeatedbonecrunch"] = "Bonecrunch Defeated",
|
||||||
|
["efmlcentermanafieldused"] = "EF Middle Level Center Mana Field Used",
|
||||||
|
["efmleastmanafieldused"] = "EF Middle Level East Mana Field Used",
|
||||||
|
["efmlnorthmanafieldused"] = "EF Middle Level North Mana Field Used",
|
||||||
|
["efmlsouthmanafieldused"] = "EF Middle Level South Mana Field Used",
|
||||||
|
["efmlwestmanafieldused"] = "EF Middle Level West Mana Field Used",
|
||||||
|
["efulcentermanafieldused"] = "EF Upper Level Center Mana Field Used",
|
||||||
|
["efuleastmanafieldused"] = "EF Upper Level East Mana Field Used",
|
||||||
|
["efulnorthmanafieldused"] = "EF Upper Level North Mana Field Used",
|
||||||
|
["efulsouthmanafieldused"] = "EF Upper Level South Mana Field Used",
|
||||||
|
["efulwestmanafieldused"] = "EF Upper Level West Mana Field Used",
|
||||||
|
["insatiableeaterjaw"] = "Insatiable Eater Jaw Collection",
|
||||||
|
["pathwardencomplete"] = "Pathwarden Visit Complete",
|
||||||
|
["pathwardenfound1111"] = "Pathwarden Greeter Encountered",
|
||||||
|
["recallsingularitycaul"] = "Recall Singularity Bore Pickup",
|
||||||
|
["stipendscollectedinamonth"] = "Monthly Stipends Collected Count",
|
||||||
|
["stipendtimer_0812"] = "Stipend Collection Timer",
|
||||||
|
["stipendtimer_monthly"] = "Monthly Stipend Timer",
|
||||||
|
["upperinsatiablejaw"] = "Upper Insatiable Eater Jaw Collection",
|
||||||
|
["usedattributereset"] = "Attribute Reset Used",
|
||||||
|
["usedfreeattributereset"] = "Free Attribute Reset Used",
|
||||||
|
["usedfreeskillreset"] = "Free Skill Reset Used",
|
||||||
|
["usedskillreset"] = "Skill Reset Used",
|
||||||
|
["virindiisland"] = "Singularity Island Visit",
|
||||||
|
|
||||||
|
// Kill Tasks
|
||||||
|
["turshscalp"] = "Tursh Scalp",
|
||||||
|
["polarursuin"] = "Polar Ursuin Kill Task Main Flag Timer",
|
||||||
|
["polarursuinkillcount"] = "Polar Ursuin Kill Counter",
|
||||||
|
["polardillotask"] = "Polar Dillo Kill Task Main Flag",
|
||||||
|
["polardillokills"] = "Polar Dillo Kill Counter",
|
||||||
|
["repugnanteaterkilltask"] = "Repugnant Eater Kill Task",
|
||||||
|
["repugeaterkillcount"] = "Repugnant Eater Kill Counter",
|
||||||
|
["deathcap"] = "Deathcap Thrungus Kill Task",
|
||||||
|
["deathcapkillcount"] = "Deathcap Thrungus Kill Counter",
|
||||||
|
["grievverv"] = "Grievver Violator Kill Task",
|
||||||
|
["grievvervkillcount"] = "Grievver Violator Kill Counter",
|
||||||
|
["tuskerg"] = "Tusker Guard Kill Task Main Flag",
|
||||||
|
["tuskergkillcount"] = "Tusker Guard Kill Counter",
|
||||||
|
|
||||||
|
// Quest Timers and Pickups
|
||||||
|
["blankaug"] = "Blank Aug Gem Pickup Timer",
|
||||||
|
["greatcavepenguinegg"] = "Great Cave Penguin Egg Pickup Timer",
|
||||||
|
["deathallurecd"] = "Death's Allure Timer Flag",
|
||||||
|
["brewmastercover"] = "Brew Master Quest Pickup Timer Cover",
|
||||||
|
["brewmasterback"] = "Brew Master Quest Pickup Timer Back",
|
||||||
|
["brewmasterpages"] = "Brew Master Quest Pickup Timer Pages",
|
||||||
|
["brewmasterspine"] = "Brew Master Quest Pickup Timer Spine",
|
||||||
|
["eleonorasheart"] = "Elanora's Heart Quest Pickup Timer",
|
||||||
|
["beacongemobtained"] = "Cooldown for obtaining another beacon gem",
|
||||||
|
["beaconcomplete"] = "Beacon Quest Complete Timer",
|
||||||
|
["sirginaziosword"] = "Pick up of Sir Ginazio's Sword",
|
||||||
|
|
||||||
|
// Major Quests
|
||||||
|
["maraudersjaw"] = "Marauder's Lair Quest",
|
||||||
|
["fledgemastertusk"] = "Fledge Master's Tusk Quest",
|
||||||
|
["crystallinekiller"] = "Crystalline Killer",
|
||||||
|
["darkisledelivery"] = "Dark Isle Delivery",
|
||||||
|
["defeatingvaeshok"] = "Defeating Vaeshok",
|
||||||
|
["hollyjollyhelperquest"] = "Holly Jolly Helper Quest",
|
||||||
|
["moarsmenjailbreak"] = "Moarsmen Jailbreak",
|
||||||
|
["shamblingarchivistdestroyer"] = "Shambling Archivist Destroyer",
|
||||||
|
["tracingthestone"] = "Tracing The Stone",
|
||||||
|
["undeadjawcollection"] = "Undead Jaw Collection",
|
||||||
|
["weedingofthederutree"] = "Weeding of the Deru Tree",
|
||||||
|
["ironbladecommander"] = "Iron Blade Commander",
|
||||||
|
["mumiyahhuntingneftet"] = "Mumiyah Hunting Neftet",
|
||||||
|
["torgashstasks"] = "Torgash's Tasks",
|
||||||
|
|
||||||
|
// Thrungus Hovels Items
|
||||||
|
["stolenfryingpan"] = "Thrungus Hovels",
|
||||||
|
["stolenring"] = "Thrungus Hovels",
|
||||||
|
["stolenbrewkettle"] = "Thrungus Hovels",
|
||||||
|
["stolenamulet"] = "Thrungus Hovels",
|
||||||
|
["stolenewer"] = "Thrungus Hovels",
|
||||||
|
["stolennecklace"] = "Thrungus Hovels",
|
||||||
|
["stolenplatter"] = "Thrungus Hovels",
|
||||||
|
["stolenbracelet"] = "Thrungus Hovels",
|
||||||
|
|
||||||
|
// Special Items and Flags
|
||||||
|
["ringofkarlun"] = "Knights of Karlun",
|
||||||
|
["trainingacademycomplete"] = "Completion of Training Academy for Exit",
|
||||||
|
["cowtipcounter"] = "Counter for Cow Tipping",
|
||||||
|
["cowtip"] = "Main Timed Flag for Cow Tipping",
|
||||||
|
["skillloweringgempickedup"] = "Picked up a forgetfulness gem",
|
||||||
|
|
||||||
|
// Healing Machine Components
|
||||||
|
["orbhealingmachine"] = "Healing Machine Orb",
|
||||||
|
["pedestalhealingmachine"] = "Healing Machine Pedestal",
|
||||||
|
["tihnhealingmachine"] = "Healing Machine Tihn",
|
||||||
|
["lavushealingmachine"] = "Healing Machine Lavus",
|
||||||
|
["hookhealingmachine"] = "Healing Machine Hook",
|
||||||
|
|
||||||
|
// Eater Jaws
|
||||||
|
["ravenouseaterjaw"] = "Ravenous Eater Jaw",
|
||||||
|
["insatiableeaterjaw"] = "Insatiable Eater Jaw",
|
||||||
|
["engorgedeaterjaw"] = "Engorged Eater Jaw",
|
||||||
|
["voraciouseaterjaw"] = "Voracious Eater Jaw",
|
||||||
|
["abhorrenteaterjaw"] = "Abhorrent Eater Jaw",
|
||||||
|
|
||||||
|
// Kill Tasks (Extended)
|
||||||
|
["altereddrudgekilltask"] = "Altered Drudge Kill Task",
|
||||||
|
["altereddrudgekillcount"] = "Altered Drudge Kill Counter",
|
||||||
|
["arcticmattekarkilltask"] = "Arctic Mattekar Kill Task",
|
||||||
|
["arcticmattekarkillcount"] = "Arctic Mattekar Kill Counter",
|
||||||
|
["armoredillohuntingneftetkilltask"] = "Armoredillo Hunting Neftet Kill Task",
|
||||||
|
["armoredillohuntingneftetkillcount"] = "Armoredillo Hunting Neftet Kill Counter",
|
||||||
|
["augmenteddrudgekilltask"] = "Augmented Drudge Kill Task",
|
||||||
|
["augmenteddrudgekillcount"] = "Augmented Drudge Kill Counter",
|
||||||
|
["banishedcreaturekilltask"] = "Banished Creature Kill Task",
|
||||||
|
["banishedcreaturekillcount"] = "Banished Creature Kill Counter",
|
||||||
|
["benekniffiskilltask"] = "Benek Niffis Kill Task",
|
||||||
|
["benekniffiskillcount"] = "Benek Niffis Kill Counter",
|
||||||
|
["blackcoralgolemkilltask"] = "Black Coral Golem Kill Task",
|
||||||
|
["blackcoralgolemkillcount"] = "Black Coral Golem Kill Counter",
|
||||||
|
["blessedmoarsmankilltask"] = "Blessed Moarsman Kill Task",
|
||||||
|
["blessedmoarsmankillcount"] = "Blessed Moarsman Kill Counter",
|
||||||
|
["blightedcoralgolemkilltask"] = "Blighted Coral Golem Kill Task",
|
||||||
|
["blightedcoralgolemkillcount"] = "Blighted Coral Golem Kill Counter",
|
||||||
|
["bloodshrethkilltask"] = "Blood Shreth Kill Task",
|
||||||
|
["bloodshrethkillcount"] = "Blood Shreth Kill Counter",
|
||||||
|
["bronzegauntlettrooperkilltask"] = "Bronze Gauntlet Trooper Kill Task",
|
||||||
|
["bronzegauntlettrooperkillcount"] = "Bronze Gauntlet Trooper Kill Counter",
|
||||||
|
["coppercogtrooperkilltask"] = "Copper Cog Trooper Kill Task",
|
||||||
|
["coppercogtrooperkillcount"] = "Copper Cog Trooper Kill Counter",
|
||||||
|
["coppergolemkingpinkilltask"] = "Copper Golem Kingpin Kill Task",
|
||||||
|
["coppergolemkingpinkillcount"] = "Copper Golem Kingpin Kill Counter",
|
||||||
|
["coralgolemkilltask"] = "Coral Golem Kill Task",
|
||||||
|
["coralgolemkillcount"] = "Coral Golem Kill Counter",
|
||||||
|
["coralgolemviceroykilltask"] = "Coral Golem Viceroy Kill Task",
|
||||||
|
["coralgolemviceroykillcount"] = "Coral Golem Viceroy Kill Counter",
|
||||||
|
["corruptedgravestonekilltask"] = "Corrupted Gravestone Kill Task",
|
||||||
|
["corruptedgravestonekillcount"] = "Corrupted Gravestone Kill Counter",
|
||||||
|
["deathcapthrunguskilltask"] = "Deathcap Thrungus Kill Task",
|
||||||
|
["deathcapthrunguskillcount"] = "Deathcap Thrungus Kill Counter",
|
||||||
|
["desertcactuskilltask"] = "Desert Cactus Kill Task",
|
||||||
|
["desertcactuskillcount"] = "Desert Cactus Kill Counter",
|
||||||
|
["devourermargulkilltask"] = "Devourer Margul Kill Task",
|
||||||
|
["devourermargulkillcount"] = "Devourer Margul Kill Counter",
|
||||||
|
|
||||||
|
// Society and Faction Quests
|
||||||
|
["celestialhandintroductioncomplete"] = "Celestial Hand Introduction Complete",
|
||||||
|
["eldrytchwebintroductioncomplete"] = "Eldrytch Web Introduction Complete",
|
||||||
|
["radiantbloodintroductioncomplete"] = "Radiant Blood Introduction Complete",
|
||||||
|
["celestialhandinitiatetest"] = "Celestial Hand Initiate Test",
|
||||||
|
["eldrytchwebinitiatetest"] = "Eldrytch Web Initiate Test",
|
||||||
|
["radiantbloodinitiatetest"] = "Radiant Blood Initiate Test",
|
||||||
|
|
||||||
|
// Luminance Aura Related
|
||||||
|
["aetheriaredemption"] = "Aetheria Redemption",
|
||||||
|
["aegisofmerc"] = "Aegis of Merc",
|
||||||
|
["lumaugtradein"] = "Luminance Augmentation Trade In",
|
||||||
|
|
||||||
|
// Common AC Quests
|
||||||
|
["holtburgtraderskill"] = "Holtburg Trader Skill Quest",
|
||||||
|
["shoushitraderskill"] = "Shoushi Trader Skill Quest",
|
||||||
|
["yaraqtraderskill"] = "Yaraq Trader Skill Quest",
|
||||||
|
["newbiequests"] = "Newbie Academy Quests",
|
||||||
|
["moarsmanraid"] = "Moarsman Raid",
|
||||||
|
["virindiparadox"] = "Virindi Paradox",
|
||||||
|
["portalspace"] = "Portal Space Exploration"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get friendly name for a quest stamp, with fallback to original stamp
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="questStamp">The quest stamp to lookup</param>
|
||||||
|
/// <returns>Friendly name if found, otherwise the original quest stamp</returns>
|
||||||
|
public static string GetFriendlyName(string questStamp)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(questStamp))
|
||||||
|
return questStamp;
|
||||||
|
|
||||||
|
return QuestStampToName.TryGetValue(questStamp.ToLower(), out string friendlyName)
|
||||||
|
? friendlyName
|
||||||
|
: questStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get display name with friendly name and original stamp in parentheses
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="questStamp">The quest stamp to format</param>
|
||||||
|
/// <returns>Formatted display name</returns>
|
||||||
|
public static string GetDisplayName(string questStamp)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(questStamp))
|
||||||
|
return questStamp;
|
||||||
|
|
||||||
|
string friendlyName = GetFriendlyName(questStamp);
|
||||||
|
|
||||||
|
// If we found a mapping, show friendly name with original in parentheses
|
||||||
|
if (!string.Equals(friendlyName, questStamp, System.StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return $"{friendlyName} ({questStamp})";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise just show the original
|
||||||
|
return questStamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,10 +34,6 @@ namespace MosswartMassacre.Views
|
||||||
private HudList lstCantrips;
|
private HudList lstCantrips;
|
||||||
private HudButton btnRefreshCantrips;
|
private HudButton btnRefreshCantrips;
|
||||||
|
|
||||||
// Weapons Tab
|
|
||||||
private HudList lstWeapons;
|
|
||||||
private HudButton btnRefreshWeapons;
|
|
||||||
|
|
||||||
// Quests Tab
|
// Quests Tab
|
||||||
private HudList lstQuests;
|
private HudList lstQuests;
|
||||||
private HudButton btnRefreshQuests;
|
private HudButton btnRefreshQuests;
|
||||||
|
|
@ -46,14 +42,25 @@ namespace MosswartMassacre.Views
|
||||||
|
|
||||||
#region Data Management
|
#region Data Management
|
||||||
private FlagTrackerData data;
|
private FlagTrackerData data;
|
||||||
private QuestManager questManager;
|
private System.Timers.Timer questUpdateTimer;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public FlagTrackerView(PluginCore core) : base(core)
|
public FlagTrackerView(PluginCore core) : base(core)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
instance = this;
|
instance = this;
|
||||||
data = new FlagTrackerData();
|
data = new FlagTrackerData();
|
||||||
questManager = new QuestManager();
|
|
||||||
|
// Initialize quest update timer for real-time countdown
|
||||||
|
questUpdateTimer = new System.Timers.Timer(5000); // Update every 5 seconds
|
||||||
|
questUpdateTimer.Elapsed += OnQuestTimerUpdate;
|
||||||
|
questUpdateTimer.AutoReset = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[MossyTracker] Failed to initialize: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Static Interface
|
#region Static Interface
|
||||||
|
|
@ -108,17 +115,18 @@ namespace MosswartMassacre.Views
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Create view from XML layout
|
|
||||||
CreateFromXMLResource("MosswartMassacre.ViewXML.flagTracker.xml");
|
CreateFromXMLResource("MosswartMassacre.ViewXML.flagTracker.xml");
|
||||||
|
|
||||||
// Initialize all tab controls
|
if (view == null)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat("[MossyTracker] Failed to create view");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
InitializeTabControls();
|
InitializeTabControls();
|
||||||
InitializeEventHandlers();
|
InitializeEventHandlers();
|
||||||
|
|
||||||
// Initialize the base view
|
|
||||||
Initialize();
|
Initialize();
|
||||||
|
|
||||||
// Make the view visible
|
|
||||||
if (view != null)
|
if (view != null)
|
||||||
{
|
{
|
||||||
view.Visible = true;
|
view.Visible = true;
|
||||||
|
|
@ -126,12 +134,19 @@ namespace MosswartMassacre.Views
|
||||||
view.Title = "Mossy Tracker v3.0.1.1";
|
view.Title = "Mossy Tracker v3.0.1.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial data refresh
|
|
||||||
RefreshAllData();
|
RefreshAllData();
|
||||||
|
|
||||||
|
// Start quest update timer
|
||||||
|
if (questUpdateTimer != null)
|
||||||
|
{
|
||||||
|
questUpdateTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginCore.WriteToChat("[MossyTracker] Initialized successfully");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Error initializing Flag Tracker view: {ex.Message}");
|
PluginCore.WriteToChat($"[MossyTracker] Failed to initialize: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,37 +154,21 @@ namespace MosswartMassacre.Views
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get main tab view
|
|
||||||
mainTabView = GetControl<HudTabView>("mainTabView");
|
mainTabView = GetControl<HudTabView>("mainTabView");
|
||||||
|
|
||||||
// Augmentations Tab
|
|
||||||
lstAugmentations = GetControl<HudList>("lstAugmentations");
|
lstAugmentations = GetControl<HudList>("lstAugmentations");
|
||||||
btnRefreshAugs = GetControl<HudButton>("btnRefreshAugs");
|
btnRefreshAugs = GetControl<HudButton>("btnRefreshAugs");
|
||||||
|
|
||||||
// Luminance Tab
|
|
||||||
lstLuminanceAuras = GetControl<HudList>("lstLuminanceAuras");
|
lstLuminanceAuras = GetControl<HudList>("lstLuminanceAuras");
|
||||||
btnRefreshLum = GetControl<HudButton>("btnRefreshLum");
|
btnRefreshLum = GetControl<HudButton>("btnRefreshLum");
|
||||||
|
|
||||||
// Recalls Tab
|
|
||||||
lstRecallSpells = GetControl<HudList>("lstRecallSpells");
|
lstRecallSpells = GetControl<HudList>("lstRecallSpells");
|
||||||
btnRefreshRecalls = GetControl<HudButton>("btnRefreshRecalls");
|
btnRefreshRecalls = GetControl<HudButton>("btnRefreshRecalls");
|
||||||
|
|
||||||
// Cantrips Tab
|
|
||||||
lstCantrips = GetControl<HudList>("lstCantrips");
|
lstCantrips = GetControl<HudList>("lstCantrips");
|
||||||
btnRefreshCantrips = GetControl<HudButton>("btnRefreshCantrips");
|
btnRefreshCantrips = GetControl<HudButton>("btnRefreshCantrips");
|
||||||
|
|
||||||
// Weapons Tab
|
|
||||||
lstWeapons = GetControl<HudList>("lstWeapons");
|
|
||||||
btnRefreshWeapons = GetControl<HudButton>("btnRefreshWeapons");
|
|
||||||
|
|
||||||
// Quests Tab
|
|
||||||
lstQuests = GetControl<HudList>("lstQuests");
|
lstQuests = GetControl<HudList>("lstQuests");
|
||||||
btnRefreshQuests = GetControl<HudButton>("btnRefreshQuests");
|
btnRefreshQuests = GetControl<HudButton>("btnRefreshQuests");
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Error initializing tab controls: {ex.Message}");
|
PluginCore.WriteToChat($"[MossyTracker] Failed to initialize controls: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,7 +181,6 @@ namespace MosswartMassacre.Views
|
||||||
if (btnRefreshLum != null) btnRefreshLum.Hit += OnRefreshLuminance;
|
if (btnRefreshLum != null) btnRefreshLum.Hit += OnRefreshLuminance;
|
||||||
if (btnRefreshRecalls != null) btnRefreshRecalls.Hit += OnRefreshRecalls;
|
if (btnRefreshRecalls != null) btnRefreshRecalls.Hit += OnRefreshRecalls;
|
||||||
if (btnRefreshCantrips != null) btnRefreshCantrips.Hit += OnRefreshCantrips;
|
if (btnRefreshCantrips != null) btnRefreshCantrips.Hit += OnRefreshCantrips;
|
||||||
if (btnRefreshWeapons != null) btnRefreshWeapons.Hit += OnRefreshWeapons;
|
|
||||||
if (btnRefreshQuests != null) btnRefreshQuests.Hit += OnRefreshQuests;
|
if (btnRefreshQuests != null) btnRefreshQuests.Hit += OnRefreshQuests;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -193,6 +191,68 @@ namespace MosswartMassacre.Views
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Window Management Overrides
|
||||||
|
protected override void View_VisibleChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Call base implementation first
|
||||||
|
base.View_VisibleChanged(sender, e);
|
||||||
|
|
||||||
|
// If window becomes invisible and we're not already disposed, dispose it
|
||||||
|
// This handles the X button click case
|
||||||
|
if (view != null && !view.Visible && !_disposed)
|
||||||
|
{
|
||||||
|
// Use a small delay to ensure this isn't just a temporary hide
|
||||||
|
System.Threading.Timer disposeTimer = null;
|
||||||
|
disposeTimer = new System.Threading.Timer(_ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Double-check the window is still hidden and not disposed
|
||||||
|
if (view != null && !view.Visible && !_disposed)
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"Error in delayed disposal: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
disposeTimer?.Dispose();
|
||||||
|
}
|
||||||
|
}, null, 100, System.Threading.Timeout.Infinite); // 100ms delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"Error in FlagTracker VisibleChanged: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Timer Event Handlers
|
||||||
|
private void OnQuestTimerUpdate(object sender, System.Timers.ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Update quest list display if quests tab is visible and we have quest data
|
||||||
|
if (view != null && view.Visible && PluginCore.questManager?.QuestList != null && PluginCore.questManager.QuestList.Count > 0)
|
||||||
|
{
|
||||||
|
// Only update the quest list to refresh countdown timers
|
||||||
|
PopulateQuestsList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Silently handle timer update errors to avoid spam
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Event Handlers
|
#region Event Handlers
|
||||||
private void OnRefreshAugmentations(object sender, EventArgs e)
|
private void OnRefreshAugmentations(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
|
@ -247,29 +307,41 @@ namespace MosswartMassacre.Views
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRefreshWeapons(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
data.RefreshWeapons();
|
|
||||||
PopulateWeaponsList();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PluginCore.WriteToChat($"Error refreshing weapons: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRefreshQuests(object sender, EventArgs e)
|
private void OnRefreshQuests(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
questManager.RefreshQuests();
|
if (PluginCore.questManager != null)
|
||||||
|
{
|
||||||
|
PluginCore.questManager.RefreshQuests();
|
||||||
PopulateQuestsList();
|
PopulateQuestsList();
|
||||||
|
|
||||||
|
// Schedule another refresh in a few seconds to catch any data
|
||||||
|
System.Threading.Timer refreshTimer = null;
|
||||||
|
refreshTimer = new System.Threading.Timer(_ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PopulateQuestsList();
|
||||||
|
}
|
||||||
|
catch (Exception timerEx)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[MossyTracker] Refresh failed: {timerEx.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
refreshTimer?.Dispose();
|
||||||
|
}
|
||||||
|
}, null, 4000, System.Threading.Timeout.Infinite);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat("[MossyTracker] Quest manager not available");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Error refreshing quests: {ex.Message}");
|
PluginCore.WriteToChat($"[MossyTracker] Refresh failed: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,16 +362,18 @@ namespace MosswartMassacre.Views
|
||||||
{
|
{
|
||||||
((HudStaticText)control).Text = text ?? "";
|
((HudStaticText)control).Text = text ?? "";
|
||||||
}
|
}
|
||||||
|
// Column control is null - ignore silently
|
||||||
}
|
}
|
||||||
catch (IndexOutOfRangeException)
|
catch (IndexOutOfRangeException)
|
||||||
{
|
{
|
||||||
// Column doesn't exist - ignore silently
|
// Column doesn't exist - ignore silently
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Invalid parameters - ignore silently
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Error setting list text at column {columnIndex}: {ex.Message}");
|
// Ignore text setting errors silently
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -362,6 +436,24 @@ namespace MosswartMassacre.Views
|
||||||
return "[?]"; // Unknown category
|
return "[?]"; // Unknown category
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private 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
|
#endregion
|
||||||
|
|
||||||
#region Data Population Methods
|
#region Data Population Methods
|
||||||
|
|
@ -369,8 +461,22 @@ namespace MosswartMassacre.Views
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
questManager.RefreshQuests();
|
if (PluginCore.questManager != null)
|
||||||
|
{
|
||||||
|
PluginCore.questManager.RefreshQuests();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
data.RefreshAll();
|
data.RefreshAll();
|
||||||
|
}
|
||||||
|
catch (Exception dataEx)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[MossyTracker] Data refresh failed: {dataEx.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PopulateAugmentationsList();
|
PopulateAugmentationsList();
|
||||||
PopulateLuminanceList();
|
PopulateLuminanceList();
|
||||||
|
|
@ -380,7 +486,7 @@ namespace MosswartMassacre.Views
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Error refreshing all data: {ex.Message}");
|
PluginCore.WriteToChat($"[MossyTracker] Refresh failed: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,10 +494,20 @@ namespace MosswartMassacre.Views
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (lstAugmentations == null || data?.AugmentationCategories == null) return;
|
if (lstAugmentations == null) return;
|
||||||
|
|
||||||
lstAugmentations.ClearRows();
|
lstAugmentations.ClearRows();
|
||||||
|
|
||||||
|
if (data?.AugmentationCategories == null)
|
||||||
|
{
|
||||||
|
var row = lstAugmentations.AddRow();
|
||||||
|
SafeSetListText(row, 0, "No augmentation data available");
|
||||||
|
SafeSetListText(row, 1, "Click Refresh to load data");
|
||||||
|
SafeSetListText(row, 2, "");
|
||||||
|
SafeSetListText(row, 3, "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var category in data.AugmentationCategories)
|
foreach (var category in data.AugmentationCategories)
|
||||||
{
|
{
|
||||||
// Add category header
|
// Add category header
|
||||||
|
|
@ -495,10 +611,19 @@ namespace MosswartMassacre.Views
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (lstRecallSpells == null || data?.RecallSpells == null) return;
|
if (lstRecallSpells == null) return;
|
||||||
|
|
||||||
lstRecallSpells.ClearRows();
|
lstRecallSpells.ClearRows();
|
||||||
|
|
||||||
|
if (data?.RecallSpells == null)
|
||||||
|
{
|
||||||
|
var row = lstRecallSpells.AddRow();
|
||||||
|
SafeSetListImage(row, 0, 0x6002D14); // Default portal icon
|
||||||
|
SafeSetListText(row, 1, "No recall data available - Click Refresh");
|
||||||
|
SafeSetListText(row, 2, "Loading...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var recall in data.RecallSpells)
|
foreach (var recall in data.RecallSpells)
|
||||||
{
|
{
|
||||||
var row = lstRecallSpells.AddRow();
|
var row = lstRecallSpells.AddRow();
|
||||||
|
|
@ -567,128 +692,63 @@ namespace MosswartMassacre.Views
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PopulateWeaponsList()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (lstWeapons == null || data?.WeaponCategories == null) return;
|
|
||||||
|
|
||||||
lstWeapons.ClearRows();
|
|
||||||
|
|
||||||
foreach (var category in data.WeaponCategories)
|
|
||||||
{
|
|
||||||
// Add category header
|
|
||||||
var headerRow = lstWeapons.AddRow();
|
|
||||||
SafeSetListText(headerRow, 0, $"--- {category.Key} ---");
|
|
||||||
SafeSetListText(headerRow, 1, "");
|
|
||||||
SafeSetListText(headerRow, 2, "");
|
|
||||||
SafeSetListText(headerRow, 3, "");
|
|
||||||
|
|
||||||
// Add weapons in this category
|
|
||||||
foreach (var weapon in category.Value)
|
|
||||||
{
|
|
||||||
var row = lstWeapons.AddRow();
|
|
||||||
|
|
||||||
// Column 0: Category
|
|
||||||
SafeSetListText(row, 0, weapon.Category);
|
|
||||||
|
|
||||||
// Column 1: Weapon Type
|
|
||||||
SafeSetListText(row, 1, weapon.WeaponType);
|
|
||||||
|
|
||||||
// Column 2: Weapon Name
|
|
||||||
SafeSetListText(row, 2, weapon.Name);
|
|
||||||
|
|
||||||
// Column 3: Status
|
|
||||||
SafeSetListText(row, 3, weapon.Status);
|
|
||||||
|
|
||||||
// Color code based on acquisition status
|
|
||||||
System.Drawing.Color statusColor = weapon.IsAcquired ?
|
|
||||||
System.Drawing.Color.Green : System.Drawing.Color.Red;
|
|
||||||
SafeSetListColor(row, 3, statusColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.WeaponCategories.Count == 0)
|
|
||||||
{
|
|
||||||
var row = lstWeapons.AddRow();
|
|
||||||
SafeSetListText(row, 0, "No weapon data - click Refresh");
|
|
||||||
SafeSetListText(row, 1, "");
|
|
||||||
SafeSetListText(row, 2, "");
|
|
||||||
SafeSetListText(row, 3, "");
|
|
||||||
SafeSetListColor(row, 0, System.Drawing.Color.Gray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
PluginCore.WriteToChat($"Error populating weapons list: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulateQuestsList()
|
private void PopulateQuestsList()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (lstQuests == null)
|
if (lstQuests == null) return;
|
||||||
{
|
|
||||||
PluginCore.WriteToChat("Quest list control is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lstQuests.ClearRows();
|
lstQuests.ClearRows();
|
||||||
|
|
||||||
// Always show debug info for now
|
// Add column headers - New order: Quest Name, Countdown, Last Solved, Cooldown, Solves
|
||||||
var row = lstQuests.AddRow();
|
var headerRow = lstQuests.AddRow();
|
||||||
SafeSetListText(row, 0, $"Quest Manager: {(questManager != null ? "OK" : "NULL")}");
|
SafeSetListText(headerRow, 0, "--- Quest Name ---");
|
||||||
SafeSetListText(row, 1, $"Quest Count: {questManager?.QuestList?.Count ?? 0}");
|
SafeSetListText(headerRow, 1, "Countdown");
|
||||||
SafeSetListText(row, 2, "Click Refresh to load quest data");
|
SafeSetListText(headerRow, 2, "Last Solved");
|
||||||
SafeSetListText(row, 3, "");
|
SafeSetListText(headerRow, 3, "Cooldown");
|
||||||
SafeSetListText(row, 4, "");
|
SafeSetListText(headerRow, 4, "Solves");
|
||||||
SafeSetListText(row, 5, "");
|
|
||||||
|
|
||||||
if (questManager?.QuestList != null && questManager.QuestList.Count > 0)
|
if (PluginCore.questManager?.QuestList != null && PluginCore.questManager.QuestList.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var quest in questManager.QuestList.OrderBy(q => q.Id))
|
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
|
|
||||||
|
// Filter out maxed quests and sort by solve count (highest to lowest)
|
||||||
|
var visibleQuests = PluginCore.questManager.QuestList
|
||||||
|
.Where(q => !(q.MaxSolves > 0 && q.Solves >= q.MaxSolves)) // Hide maxed quests
|
||||||
|
.OrderByDescending(q => q.Solves)
|
||||||
|
.ThenBy(q => PluginCore.questManager.GetFriendlyQuestName(q.Id));
|
||||||
|
|
||||||
|
foreach (var quest in visibleQuests)
|
||||||
{
|
{
|
||||||
var questRow = lstQuests.AddRow();
|
var questRow = lstQuests.AddRow();
|
||||||
|
|
||||||
// Column 0: Quest Name
|
// Column 0: Quest Name (friendly name only, wider)
|
||||||
SafeSetListText(questRow, 0, quest.Id);
|
string questName = PluginCore.questManager.GetFriendlyQuestName(quest.Id);
|
||||||
|
SafeSetListText(questRow, 0, questName);
|
||||||
|
|
||||||
// Column 1: Solves
|
// Column 1: Countdown Timer
|
||||||
SafeSetListText(questRow, 1, quest.Solves.ToString());
|
long timeRemaining = quest.ExpireTime - currentTime;
|
||||||
|
string countdownText = FormatCountdown(timeRemaining);
|
||||||
|
SafeSetListText(questRow, 1, countdownText);
|
||||||
|
|
||||||
// Column 2: Completed date
|
// Column 2: Last Solved (date) - moved from Column 3
|
||||||
SafeSetListText(questRow, 2, questManager.FormatTimeStamp(quest.Timestamp));
|
SafeSetListText(questRow, 2, PluginCore.questManager.FormatTimeStamp(quest.Timestamp));
|
||||||
|
|
||||||
// Column 3: Max solves
|
// Column 3: Cooldown (formatted duration) - moved from Column 4
|
||||||
string maxText = quest.MaxSolves < 0 ? "∞" : quest.MaxSolves.ToString();
|
SafeSetListText(questRow, 3, PluginCore.questManager.FormatSeconds(quest.Delta));
|
||||||
SafeSetListText(questRow, 3, maxText);
|
|
||||||
|
|
||||||
// Column 4: Delta (cooldown in seconds)
|
// Column 4: Solves (white text) - moved from Column 5
|
||||||
SafeSetListText(questRow, 4, questManager.FormatSeconds(quest.Delta));
|
SafeSetListText(questRow, 4, quest.Solves.ToString());
|
||||||
|
SafeSetListColor(questRow, 4, System.Drawing.Color.White);
|
||||||
|
|
||||||
// Column 5: Expire time
|
// Color code the countdown based on availability
|
||||||
string expireText = questManager.GetTimeUntilExpire(quest);
|
if (quest.ExpireTime <= currentTime)
|
||||||
SafeSetListText(questRow, 5, expireText);
|
|
||||||
|
|
||||||
// Color coding based on availability
|
|
||||||
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
|
||||||
|
|
||||||
if (quest.MaxSolves > 0 && quest.Solves >= quest.MaxSolves)
|
|
||||||
{
|
{
|
||||||
// Quest is maxed out - red
|
SafeSetListColor(questRow, 1, System.Drawing.Color.Green); // Ready - green countdown
|
||||||
SafeSetListColor(questRow, 1, System.Drawing.Color.Red);
|
|
||||||
SafeSetListColor(questRow, 5, System.Drawing.Color.Red);
|
|
||||||
}
|
|
||||||
else if (quest.ExpireTime <= currentTime)
|
|
||||||
{
|
|
||||||
// Quest is available - green
|
|
||||||
SafeSetListColor(questRow, 5, System.Drawing.Color.Green);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Quest is on cooldown - yellow
|
SafeSetListColor(questRow, 1, System.Drawing.Color.Yellow); // On cooldown - yellow countdown
|
||||||
SafeSetListColor(questRow, 5, System.Drawing.Color.Yellow);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -702,23 +762,47 @@ namespace MosswartMassacre.Views
|
||||||
|
|
||||||
|
|
||||||
#region Cleanup
|
#region Cleanup
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
if (_disposed) return; // Prevent double disposal
|
||||||
|
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Clear static instance reference if this is the current instance
|
||||||
|
if (instance == this)
|
||||||
|
{
|
||||||
|
instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event handlers will be cleaned up by base class
|
||||||
|
|
||||||
|
// Remove button event handlers
|
||||||
|
if (btnRefreshAugs != null) btnRefreshAugs.Hit -= OnRefreshAugmentations;
|
||||||
|
if (btnRefreshLum != null) btnRefreshLum.Hit -= OnRefreshLuminance;
|
||||||
|
if (btnRefreshRecalls != null) btnRefreshRecalls.Hit -= OnRefreshRecalls;
|
||||||
|
if (btnRefreshCantrips != null) btnRefreshCantrips.Hit -= OnRefreshCantrips;
|
||||||
|
if (btnRefreshQuests != null) btnRefreshQuests.Hit -= OnRefreshQuests;
|
||||||
|
|
||||||
if (data != null)
|
if (data != null)
|
||||||
{
|
{
|
||||||
data.Dispose();
|
data.Dispose();
|
||||||
data = null;
|
data = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (questManager != null)
|
// Stop and dispose quest update timer
|
||||||
|
if (questUpdateTimer != null)
|
||||||
{
|
{
|
||||||
questManager.Dispose();
|
questUpdateTimer.Stop();
|
||||||
questManager = null;
|
questUpdateTimer.Elapsed -= OnQuestTimerUpdate;
|
||||||
|
questUpdateTimer.Dispose();
|
||||||
|
questUpdateTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ namespace MosswartMassacre.Views
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void View_VisibleChanged(object sender, EventArgs e)
|
protected virtual void View_VisibleChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ namespace MosswartMassacre.Views
|
||||||
|
|
||||||
#region Flag Tracker Tab Controls
|
#region Flag Tracker Tab Controls
|
||||||
private HudButton btnOpenFlagTracker;
|
private HudButton btnOpenFlagTracker;
|
||||||
private HudStaticText lblFlagTrackerStatus;
|
// lblFlagTrackerStatus removed - not present in XML
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Statistics Tracking
|
#region Statistics Tracking
|
||||||
|
|
@ -280,15 +280,10 @@ namespace MosswartMassacre.Views
|
||||||
{
|
{
|
||||||
// Flag Tracker tab controls
|
// Flag Tracker tab controls
|
||||||
btnOpenFlagTracker = GetControl<HudButton>("btnOpenFlagTracker");
|
btnOpenFlagTracker = GetControl<HudButton>("btnOpenFlagTracker");
|
||||||
lblFlagTrackerStatus = GetControl<HudStaticText>("lblFlagTrackerStatus");
|
|
||||||
|
|
||||||
// Hook up Flag Tracker events
|
// Hook up Flag Tracker events
|
||||||
if (btnOpenFlagTracker != null)
|
if (btnOpenFlagTracker != null)
|
||||||
btnOpenFlagTracker.Hit += OnOpenFlagTrackerClick;
|
btnOpenFlagTracker.Hit += OnOpenFlagTrackerClick;
|
||||||
|
|
||||||
// Update initial status
|
|
||||||
if (lblFlagTrackerStatus != null)
|
|
||||||
lblFlagTrackerStatus.Text = "Status: Click to open the Flag Tracker window";
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -463,22 +458,13 @@ namespace MosswartMassacre.Views
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Update status to show opening
|
|
||||||
if (lblFlagTrackerStatus != null)
|
|
||||||
lblFlagTrackerStatus.Text = "Status: Opening Flag Tracker window...";
|
|
||||||
|
|
||||||
// Open the Flag Tracker window
|
// Open the Flag Tracker window
|
||||||
FlagTrackerView.OpenFlagTracker();
|
FlagTrackerView.OpenFlagTracker();
|
||||||
|
PluginCore.WriteToChat("Flag Tracker window opened");
|
||||||
// Update status
|
|
||||||
if (lblFlagTrackerStatus != null)
|
|
||||||
lblFlagTrackerStatus.Text = "Status: Flag Tracker window is open";
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
PluginCore.WriteToChat($"Error opening Flag Tracker: {ex.Message}");
|
PluginCore.WriteToChat($"Error opening Flag Tracker: {ex.Message}");
|
||||||
if (lblFlagTrackerStatus != null)
|
|
||||||
lblFlagTrackerStatus.Text = "Status: Error opening Flag Tracker";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,20 @@ namespace MosswartMassacre
|
||||||
await SendEncodedAsync(json, CancellationToken.None);
|
await SendEncodedAsync(json, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task SendQuestDataAsync(string questName, string countdown)
|
||||||
|
{
|
||||||
|
var envelope = new
|
||||||
|
{
|
||||||
|
type = "quest",
|
||||||
|
timestamp = DateTime.UtcNow.ToString("o"),
|
||||||
|
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||||
|
quest_name = questName,
|
||||||
|
countdown = countdown
|
||||||
|
};
|
||||||
|
var json = JsonConvert.SerializeObject(envelope);
|
||||||
|
await SendEncodedAsync(json, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── shared send helper with locking ───────────────
|
// ─── shared send helper with locking ───────────────
|
||||||
|
|
||||||
private static async Task SendEncodedAsync(string text, CancellationToken token)
|
private static async Task SendEncodedAsync(string text, CancellationToken token)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ VisualStudioVersion = 17.13.35919.96
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MosswartMassacre", "MosswartMassacre\MosswartMassacre.csproj", "{8C97E839-4D05-4A5F-B0C8-E8E778654322}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MosswartMassacre", "MosswartMassacre\MosswartMassacre.csproj", "{8C97E839-4D05-4A5F-B0C8-E8E778654322}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MosswartMassacre.Loader", "MosswartMassacre.Loader\MosswartMassacre.Loader.csproj", "{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}"
|
||||||
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
|
|
@ -17,6 +19,10 @@ Global
|
||||||
{8C97E839-4D05-4A5F-B0C8-E8E778654322}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{8C97E839-4D05-4A5F-B0C8-E8E778654322}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{8C97E839-4D05-4A5F-B0C8-E8E778654322}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{8C97E839-4D05-4A5F-B0C8-E8E778654322}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{8C97E839-4D05-4A5F-B0C8-E8E778654322}.Release|Any CPU.Build.0 = Release|Any CPU
|
{8C97E839-4D05-4A5F-B0C8-E8E778654322}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue