Added Mossy tracker, know issue

This commit is contained in:
erik 2025-06-08 00:15:55 +02:00
parent 7eb98491d3
commit 23e33599ca
8 changed files with 3372 additions and 6 deletions

File diff suppressed because it is too large Load diff

View file

@ -104,6 +104,9 @@
<Compile Include="..\Shared\Constants\Dictionaries.cs"> <Compile Include="..\Shared\Constants\Dictionaries.cs">
<Link>Shared\Constants\Dictionaries.cs</Link> <Link>Shared\Constants\Dictionaries.cs</Link>
</Compile> </Compile>
<Compile Include="..\Shared\Spells\Spell.cs">
<Link>Shared\Spells\Spell.cs</Link>
</Compile>
<Compile Include="..\Shared\Constants\DoubleValueKey.cs"> <Compile Include="..\Shared\Constants\DoubleValueKey.cs">
<Link>Shared\Constants\DoubleValueKey.cs</Link> <Link>Shared\Constants\DoubleValueKey.cs</Link>
</Compile> </Compile>
@ -160,9 +163,11 @@
</Compile> </Compile>
<Compile Include="ClientTelemetry.cs" /> <Compile Include="ClientTelemetry.cs" />
<Compile Include="DecalHarmonyClean.cs" /> <Compile Include="DecalHarmonyClean.cs" />
<Compile Include="FlagTrackerData.cs" />
<Compile Include="MossyInventory.cs" /> <Compile Include="MossyInventory.cs" />
<Compile Include="NavRoute.cs" /> <Compile Include="NavRoute.cs" />
<Compile Include="NavVisualization.cs" /> <Compile Include="NavVisualization.cs" />
<Compile Include="QuestManager.cs" />
<Compile Include="vTank.cs" /> <Compile Include="vTank.cs" />
<Compile Include="VtankControl.cs" /> <Compile Include="VtankControl.cs" />
<Compile Include="Telemetry.cs" /> <Compile Include="Telemetry.cs" />
@ -179,6 +184,8 @@
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="SpellManager.cs" />
<Compile Include="Views\FlagTrackerView.cs" />
<Compile Include="Views\VVSBaseView.cs" /> <Compile Include="Views\VVSBaseView.cs" />
<Compile Include="Views\VVSTabbedMainView.cs" /> <Compile Include="Views\VVSTabbedMainView.cs" />
<Compile Include="WebSocket.cs" /> <Compile Include="WebSocket.cs" />
@ -190,8 +197,12 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="ViewXML\flagTracker.xml" />
<EmbeddedResource Include="ViewXML\mainView.xml" /> <EmbeddedResource Include="ViewXML\mainView.xml" />
<EmbeddedResource Include="ViewXML\mainViewTabbed.xml" /> <EmbeddedResource Include="ViewXML\mainViewTabbed.xml" />
<EmbeddedResource Include="..\Shared\Spells\Spells.csv">
<Link>Shared\Spells\Spells.csv</Link>
</EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />

View file

@ -96,15 +96,15 @@ namespace MosswartMassacre
CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete; CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
CoreManager.Current.WorldFilter.CreateObject += OnSpawn; CoreManager.Current.WorldFilter.CreateObject += OnSpawn;
CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn; CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn;
// Initialize VVS view after character login
ViewManager.ViewInit();
// Initialize the timer // Initialize the timer
updateTimer = new Timer(1000); // Update every second updateTimer = new Timer(1000); // Update every second
updateTimer.Elapsed += UpdateStats; updateTimer.Elapsed += UpdateStats;
updateTimer.Start(); updateTimer.Start();
// Initialize the view (UI) - use tabbed interface by default // Note: View initialization moved to LoginComplete for VVS compatibility
ViewManager.ViewInit();
// Enable TLS1.2 // Enable TLS1.2
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
@ -187,6 +187,8 @@ namespace MosswartMassacre
WriteToChat("Mosswart Massacre has started!"); WriteToChat("Mosswart Massacre has started!");
PluginSettings.Initialize(); // Safe to call now PluginSettings.Initialize(); // Safe to call now
// Apply the values // Apply the values
@ -605,7 +607,8 @@ namespace MosswartMassacre
WriteToChat("/mm nextwp - Advance VTank to next waypoint"); WriteToChat("/mm nextwp - Advance VTank to next waypoint");
WriteToChat("/mm decalstatus - Check Harmony patch status (UtilityBelt version)"); WriteToChat("/mm decalstatus - Check Harmony patch status (UtilityBelt version)");
WriteToChat("/mm decaldebug - Enable/disable plugin message debug output + WebSocket streaming"); WriteToChat("/mm decaldebug - Enable/disable plugin message debug output + WebSocket streaming");
WriteToChat("/mm harmonyraw - Show raw intercepted messages (debug output)"); WriteToChat("/mm harmonyraw - Show raw intercepted messages (debug output)");
WriteToChat("/mm gui - Manually initialize/reinitialize GUI");
break; break;
case "report": case "report":
TimeSpan elapsed = DateTime.Now - statsStartTime; TimeSpan elapsed = DateTime.Now - statsStartTime;
@ -797,6 +800,20 @@ namespace MosswartMassacre
} }
break; break;
case "initgui":
case "gui":
try
{
WriteToChat("Attempting to manually initialize GUI...");
ViewManager.ViewDestroy(); // Clean up any existing view
ViewManager.ViewInit(); // Reinitialize
WriteToChat("GUI initialization attempt completed.");
}
catch (Exception ex)
{
WriteToChat($"GUI initialization error: {ex.Message}");
}
break;
default: default:
WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help"); WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help");

View file

@ -26,5 +26,5 @@ using System.Runtime.InteropServices;
// Minor Version // Minor Version
// Build Number // Build Number
// Revision // Revision
[assembly: AssemblyVersion("3.0.1.1")] [assembly: AssemblyVersion("3.0.1.2")]
[assembly: AssemblyFileVersion("3.0.1.1")] [assembly: AssemblyFileVersion("3.0.1.2")]

View file

@ -0,0 +1,296 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Decal.Adapter;
using Decal.Adapter.Wrappers;
namespace MosswartMassacre
{
/// <summary>
/// Quest tracking and management system
/// Ported from UBS Lua quest system
/// </summary>
public class QuestManager : IDisposable
{
#region Quest Data Structures
public class Quest
{
public string Id { get; set; }
public int Solves { get; set; }
public int Timestamp { get; set; }
public string Description { get; set; }
public int MaxSolves { get; set; }
public int Delta { get; set; }
public int ExpireTime { get; set; }
}
#endregion
#region Properties
public List<Quest> QuestList { get; private set; }
public Dictionary<string, Quest> QuestDictionary { get; private set; }
#endregion
#region Events and State
private bool isRefreshing = false;
private DateTime lastRefreshTime = DateTime.MinValue;
#endregion
public QuestManager()
{
QuestList = new List<Quest>();
QuestDictionary = new Dictionary<string, Quest>();
// Hook into chat events for quest parsing
InitializeChatHooks();
}
#region Initialization
private void InitializeChatHooks()
{
try
{
if (CoreManager.Current != null)
{
CoreManager.Current.ChatBoxMessage += OnChatBoxMessage;
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error initializing quest chat hooks: {ex.Message}");
}
}
#endregion
#region Quest Parsing
private void OnChatBoxMessage(object sender, ChatTextInterceptEventArgs e)
{
try
{
if (!isRefreshing || string.IsNullOrEmpty(e.Text))
return;
// Parse quest information from /myquests output
ParseQuestLine(e.Text);
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error parsing quest line: {ex.Message}");
}
}
private void ParseQuestLine(string text)
{
try
{
// Quest line format: TaskName - Solves solves (Timestamp)"Description" MaxSolves Delta
// Example: "SomeQuest - 5 solves (1640995200)"Quest description here" 10 3600
var pattern = @"([^\-]+) - (\d+) solves \((\d+)\)""([^""]+)"" (-?\d+) (\d+)";
var match = Regex.Match(text, pattern);
if (match.Success)
{
var quest = new Quest
{
Id = match.Groups[1].Value.Trim(),
Solves = int.Parse(match.Groups[2].Value),
Timestamp = int.Parse(match.Groups[3].Value),
Description = match.Groups[4].Value,
MaxSolves = int.Parse(match.Groups[5].Value),
Delta = int.Parse(match.Groups[6].Value)
};
quest.ExpireTime = quest.Timestamp + quest.Delta;
// Add to collections
QuestList.Add(quest);
QuestDictionary[quest.Id] = quest;
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error parsing quest line '{text}': {ex.Message}");
}
}
#endregion
#region Quest Management
public void RefreshQuests()
{
try
{
if (isRefreshing)
return;
ClearQuests();
isRefreshing = true;
// Issue /myquests command to refresh quest data
CoreManager.Current.Actions.InvokeChatParser("/myquests");
// Stop listening after a delay
System.Threading.Timer stopTimer = null;
stopTimer = new System.Threading.Timer(_ =>
{
isRefreshing = false;
stopTimer?.Dispose();
lastRefreshTime = DateTime.Now;
}, null, 3000, System.Threading.Timeout.Infinite);
}
catch (Exception ex)
{
isRefreshing = false;
PluginCore.WriteToChat($"Error refreshing quests: {ex.Message}");
}
}
public void ClearQuests()
{
QuestList.Clear();
QuestDictionary.Clear();
}
public bool IsQuestAvailable(string questStamp)
{
if (!QuestDictionary.TryGetValue(questStamp, out Quest quest))
return true; // If quest not found, assume available
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
return quest.ExpireTime < currentTime;
}
public bool IsQuestMaxSolved(string questStamp)
{
if (!QuestDictionary.TryGetValue(questStamp, out Quest quest))
return false;
return quest.Solves >= quest.MaxSolves;
}
public bool HasQuestFlag(string questStamp)
{
return QuestDictionary.ContainsKey(questStamp);
}
public string GetTimeUntilExpire(Quest quest)
{
if (quest == null)
return "Unknown";
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var timeLeft = quest.ExpireTime - currentTime;
if (timeLeft <= 0)
return "Ready";
return FormatSeconds((int)timeLeft);
}
public string FormatTimeStamp(int timestamp)
{
try
{
var dateTime = DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime;
return dateTime.ToString("MM/dd/yyyy HH:mm:ss");
}
catch
{
return "Invalid";
}
}
public string FormatSeconds(int seconds)
{
if (seconds <= 0)
return "0s";
var days = seconds / 86400;
seconds %= 86400;
var hours = seconds / 3600;
seconds %= 3600;
var minutes = seconds / 60;
seconds %= 60;
var result = "";
if (days > 0) result += $"{days}d ";
if (hours > 0) result += $"{hours}h ";
if (minutes > 0) result += $"{minutes}m ";
if (seconds > 0 || string.IsNullOrEmpty(result)) result += $"{seconds}s";
return result.Trim();
}
public object GetFieldByID(Quest quest, int id)
{
if (quest == null)
return null;
switch (id)
{
case 1: return quest.Id;
case 2: return quest.Solves;
case 3: return quest.Timestamp;
case 4: return quest.MaxSolves;
case 5: return quest.Delta;
case 6: return quest.ExpireTime;
default: return quest.Id;
}
}
#endregion
#region Society Quest Helpers
public string GetSocietyName(int factionBits)
{
switch (factionBits)
{
case 1: return "Celestial Hand";
case 2: return "Eldrytch Web";
case 4: return "Radiant Blood";
default: return "Unknown";
}
}
public string GetSocietyRank(int ribbons)
{
if (ribbons >= 1001) return "Master";
if (ribbons >= 601) return "Lord";
if (ribbons >= 301) return "Knight";
if (ribbons >= 101) return "Adept";
if (ribbons >= 1) return "Initiate";
return "None";
}
public int GetMaxRibbonsPerDay(string rank)
{
switch (rank)
{
case "Initiate": return 50;
case "Adept": return 100;
case "Knight": return 150;
case "Lord": return 200;
case "Master": return 250;
default: return 0;
}
}
#endregion
#region Cleanup
public void Dispose()
{
try
{
if (CoreManager.Current != null)
{
CoreManager.Current.ChatBoxMessage -= OnChatBoxMessage;
}
ClearQuests();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error disposing quest manager: {ex.Message}");
}
}
#endregion
}
}

View file

@ -0,0 +1,227 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Mag.Shared.Spells;
namespace MosswartMassacre
{
/// <summary>
/// Manages spell identification and cantrip detection for the Flag Tracker
/// </summary>
public static class SpellManager
{
private static readonly Dictionary<int, Spell> SpellsById = new Dictionary<int, Spell>();
private static readonly List<string[]> SpellData = new List<string[]>();
private static bool isInitialized = false;
static SpellManager()
{
Initialize();
}
private static void Initialize()
{
if (isInitialized) return;
try
{
// Load spell data from embedded CSV resource
var assembly = Assembly.GetExecutingAssembly();
// Try to find the resource with different naming patterns
var availableResources = assembly.GetManifestResourceNames();
var spellResource = availableResources.FirstOrDefault(r => r.Contains("Spells.csv"));
if (string.IsNullOrEmpty(spellResource))
{
// If not embedded, try to load from file system
var csvPath = Path.Combine(Path.GetDirectoryName(assembly.Location), "..", "Shared", "Spells", "Spells.csv");
if (File.Exists(csvPath))
{
LoadFromFile(csvPath);
isInitialized = true;
return;
}
}
else
{
using (var stream = assembly.GetManifestResourceStream(spellResource))
{
if (stream != null)
{
using (var reader = new StreamReader(stream))
{
LoadFromReader(reader);
}
}
}
}
isInitialized = true;
}
catch (Exception ex)
{
PluginCore.WriteToChat($"SpellManager initialization error: {ex.Message}");
}
}
private static void LoadFromFile(string path)
{
using (var reader = new StreamReader(path))
{
LoadFromReader(reader);
}
}
private static void LoadFromReader(StreamReader reader)
{
// Skip header line
var header = reader.ReadLine();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (!string.IsNullOrWhiteSpace(line))
{
var parts = line.Split(',');
if (parts.Length >= 6) // Minimum required fields
{
SpellData.Add(parts);
// Parse spell data
if (int.TryParse(parts[0], out int id))
{
var name = parts[1];
int.TryParse(parts[3], out int difficulty);
int.TryParse(parts[4], out int duration);
int.TryParse(parts[5], out int family);
var spell = new Spell(id, name, difficulty, duration, family);
SpellsById[id] = spell;
}
}
}
}
}
/// <summary>
/// Gets a spell by its ID
/// </summary>
public static Spell GetSpell(int id)
{
if (SpellsById.TryGetValue(id, out var spell))
return spell;
return null;
}
/// <summary>
/// Gets a spell by its name (case-insensitive)
/// </summary>
public static Spell GetSpell(string name)
{
return SpellsById.Values.FirstOrDefault(s =>
string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Gets the total number of spells loaded
/// </summary>
public static int GetSpellCount()
{
return SpellsById.Count;
}
/// <summary>
/// Detects if a spell is a cantrip and returns its info
/// </summary>
public static CantripInfo DetectCantrip(Spell spell)
{
if (spell == null || spell.CantripLevel == Spell.CantripLevels.None)
return null;
var info = new CantripInfo
{
SpellId = spell.Id,
Name = spell.Name,
Level = GetCantripLevelName(spell.CantripLevel),
Color = GetCantripColor(spell.CantripLevel)
};
// Extract skill/attribute name from spell name
info.SkillName = ExtractSkillFromSpellName(spell.Name, info.Level);
return info;
}
private static string GetCantripLevelName(Spell.CantripLevels level)
{
switch (level)
{
case Spell.CantripLevels.Minor: return "Minor";
case Spell.CantripLevels.Moderate: return "Moderate";
case Spell.CantripLevels.Major: return "Major";
case Spell.CantripLevels.Epic: return "Epic";
case Spell.CantripLevels.Legendary: return "Legendary";
default: return "N/A";
}
}
private static System.Drawing.Color GetCantripColor(Spell.CantripLevels level)
{
switch (level)
{
case Spell.CantripLevels.Minor: return System.Drawing.Color.White;
case Spell.CantripLevels.Moderate: return System.Drawing.Color.Green;
case Spell.CantripLevels.Major: return System.Drawing.Color.Blue;
case Spell.CantripLevels.Epic: return System.Drawing.Color.Purple;
case Spell.CantripLevels.Legendary: return System.Drawing.Color.Orange;
default: return System.Drawing.Color.White;
}
}
private static string ExtractSkillFromSpellName(string spellName, string level)
{
// Remove the cantrip level prefix
var skillPart = spellName;
if (!string.IsNullOrEmpty(level) && skillPart.StartsWith(level + " "))
{
skillPart = skillPart.Substring(level.Length + 1);
}
// Map common spell name patterns to skill names
if (skillPart.Contains("Strength")) return "Strength";
if (skillPart.Contains("Endurance")) return "Endurance";
if (skillPart.Contains("Coordination")) return "Coordination";
if (skillPart.Contains("Quickness")) return "Quickness";
if (skillPart.Contains("Focus")) return "Focus";
if (skillPart.Contains("Self") || skillPart.Contains("Willpower")) return "Willpower";
// Protection mappings
if (skillPart.Contains("Armor")) return "Armor";
if (skillPart.Contains("Bludgeoning")) return "Bludgeoning Ward";
if (skillPart.Contains("Piercing")) return "Piercing Ward";
if (skillPart.Contains("Slashing")) return "Slashing Ward";
if (skillPart.Contains("Flame") || skillPart.Contains("Fire")) return "Flame Ward";
if (skillPart.Contains("Frost") || skillPart.Contains("Cold")) return "Frost Ward";
if (skillPart.Contains("Acid")) return "Acid Ward";
if (skillPart.Contains("Lightning") || skillPart.Contains("Electric")) return "Storm Ward";
// Return the skill part as-is if no mapping found
return skillPart;
}
/// <summary>
/// Information about a detected cantrip
/// </summary>
public class CantripInfo
{
public int SpellId { get; set; }
public string Name { get; set; }
public string SkillName { get; set; }
public string Level { get; set; }
public System.Drawing.Color Color { get; set; }
}
}
}

View file

@ -0,0 +1,733 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using VirindiViewService.Controls;
namespace MosswartMassacre.Views
{
/// <summary>
/// Dedicated Flag Tracker window with comprehensive character tracking
/// Ported from UBS Lua flagtracker with full functionality preservation
/// </summary>
internal class FlagTrackerView : VVSBaseView
{
private static FlagTrackerView instance;
#region Tab Control References
private HudTabView mainTabView;
// Augmentations Tab
private HudList lstAugmentations;
private HudButton btnRefreshAugs;
// Luminance Tab
private HudList lstLuminanceAuras;
private HudButton btnRefreshLum;
// Recalls Tab
private HudList lstRecallSpells;
private HudButton btnRefreshRecalls;
// Cantrips Tab
private HudList lstCantrips;
private HudButton btnRefreshCantrips;
// Weapons Tab
private HudList lstWeapons;
private HudButton btnRefreshWeapons;
// Quests Tab
private HudList lstQuests;
private HudButton btnRefreshQuests;
#endregion
#region Data Management
private FlagTrackerData data;
private QuestManager questManager;
#endregion
public FlagTrackerView(PluginCore core) : base(core)
{
instance = this;
data = new FlagTrackerData();
questManager = new QuestManager();
}
#region Static Interface
public static void OpenFlagTracker()
{
try
{
if (instance == null)
{
instance = new FlagTrackerView(null);
instance.InitializeView();
}
else
{
// Bring existing window to front
if (instance.view != null)
{
instance.view.Visible = true;
}
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error opening Flag Tracker: {ex.Message}");
}
}
public static void CloseFlagTracker()
{
try
{
if (instance != null)
{
instance.Dispose();
instance = null;
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error closing Flag Tracker: {ex.Message}");
}
}
public static bool IsOpen()
{
return instance != null && instance.view != null && instance.view.Visible;
}
#endregion
#region Initialization
private void InitializeView()
{
try
{
// Create view from XML layout
CreateFromXMLResource("MosswartMassacre.ViewXML.flagTracker.xml");
// Initialize all tab controls
InitializeTabControls();
InitializeEventHandlers();
// Initialize the base view
Initialize();
// Make the view visible
if (view != null)
{
view.Visible = true;
view.ShowInBar = true;
view.Title = "Mossy Tracker v3.0.1.1";
}
// Initial data refresh
RefreshAllData();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error initializing Flag Tracker view: {ex.Message}");
}
}
private void InitializeTabControls()
{
try
{
// Get main tab view
mainTabView = GetControl<HudTabView>("mainTabView");
// Augmentations Tab
lstAugmentations = GetControl<HudList>("lstAugmentations");
btnRefreshAugs = GetControl<HudButton>("btnRefreshAugs");
// Luminance Tab
lstLuminanceAuras = GetControl<HudList>("lstLuminanceAuras");
btnRefreshLum = GetControl<HudButton>("btnRefreshLum");
// Recalls Tab
lstRecallSpells = GetControl<HudList>("lstRecallSpells");
btnRefreshRecalls = GetControl<HudButton>("btnRefreshRecalls");
// Cantrips Tab
lstCantrips = GetControl<HudList>("lstCantrips");
btnRefreshCantrips = GetControl<HudButton>("btnRefreshCantrips");
// Weapons Tab
lstWeapons = GetControl<HudList>("lstWeapons");
btnRefreshWeapons = GetControl<HudButton>("btnRefreshWeapons");
// Quests Tab
lstQuests = GetControl<HudList>("lstQuests");
btnRefreshQuests = GetControl<HudButton>("btnRefreshQuests");
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error initializing tab controls: {ex.Message}");
}
}
private void InitializeEventHandlers()
{
try
{
// Refresh button events
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 (btnRefreshWeapons != null) btnRefreshWeapons.Hit += OnRefreshWeapons;
if (btnRefreshQuests != null) btnRefreshQuests.Hit += OnRefreshQuests;
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error initializing event handlers: {ex.Message}");
}
}
#endregion
#region Event Handlers
private void OnRefreshAugmentations(object sender, EventArgs e)
{
try
{
data.RefreshAugmentations();
PopulateAugmentationsList();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error refreshing augmentations: {ex.Message}");
}
}
private void OnRefreshLuminance(object sender, EventArgs e)
{
try
{
data.RefreshLuminanceAuras();
PopulateLuminanceList();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error refreshing luminance auras: {ex.Message}");
}
}
private void OnRefreshRecalls(object sender, EventArgs e)
{
try
{
data.RefreshRecallSpells();
PopulateRecallsList();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error refreshing recall spells: {ex.Message}");
}
}
private void OnRefreshCantrips(object sender, EventArgs e)
{
try
{
data.RefreshCantrips();
PopulateCantripsList();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error refreshing cantrips: {ex.Message}");
}
}
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)
{
try
{
questManager.RefreshQuests();
PopulateQuestsList();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error refreshing quests: {ex.Message}");
}
}
#endregion
#region Helper Methods
private void SafeSetListText(HudList.HudListRowAccessor row, int columnIndex, string text)
{
try
{
if (row != null && columnIndex >= 0)
{
// Check if the column exists
try
{
var control = row[columnIndex];
if (control != null)
{
((HudStaticText)control).Text = text ?? "";
}
}
catch (IndexOutOfRangeException)
{
// Column doesn't exist - ignore silently
}
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error setting list text at column {columnIndex}: {ex.Message}");
}
}
private void SafeSetListColor(HudList.HudListRowAccessor row, int columnIndex, Color color)
{
try
{
if (row != null && columnIndex >= 0 && row[columnIndex] != null)
{
((HudStaticText)row[columnIndex]).TextColor = color;
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error setting list color at column {columnIndex}: {ex.Message}");
}
}
private void SafeSetListImage(HudList.HudListRowAccessor row, int columnIndex, int iconId)
{
try
{
if (row != null && columnIndex >= 0)
{
try
{
var control = row[columnIndex];
if (control != null && control is VirindiViewService.Controls.HudPictureBox)
{
var pictureBox = (VirindiViewService.Controls.HudPictureBox)control;
pictureBox.Image = iconId;
}
}
catch (IndexOutOfRangeException)
{
// Column doesn't exist - ignore silently
}
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error setting list image at column {columnIndex}: {ex.Message}");
}
}
private string GetIconSymbol(string category)
{
switch (category)
{
case "Basic Recalls":
return "[B]"; // Basic recalls
case "Island Recalls":
return "[I]"; // Island recalls
case "Town Recalls":
return "[T]"; // Town recalls
case "Special Recalls":
return "[S]"; // Special recalls
default:
return "[?]"; // Unknown category
}
}
#endregion
#region Data Population Methods
private void RefreshAllData()
{
try
{
questManager.RefreshQuests();
data.RefreshAll();
PopulateAugmentationsList();
PopulateLuminanceList();
PopulateRecallsList();
PopulateCantripsList();
PopulateQuestsList();
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error refreshing all data: {ex.Message}");
}
}
private void PopulateAugmentationsList()
{
try
{
if (lstAugmentations == null || data?.AugmentationCategories == null) return;
lstAugmentations.ClearRows();
foreach (var category in data.AugmentationCategories)
{
// Add category header
var headerRow = lstAugmentations.AddRow();
SafeSetListText(headerRow, 0, $"--- {category.Key} ---");
SafeSetListText(headerRow, 1, "");
SafeSetListText(headerRow, 2, "");
SafeSetListText(headerRow, 3, "");
// Add augmentations in this category
foreach (var aug in category.Value)
{
var row = lstAugmentations.AddRow();
// Augmentation name with progress indicator
string progressText = aug.IsMaxed ? "[MAX]" : $"[{aug.CurrentValue}/{aug.Repeatable}]";
SafeSetListText(row, 0, aug.Name);
SafeSetListText(row, 1, progressText);
SafeSetListText(row, 2, aug.Trainer);
SafeSetListText(row, 3, aug.Location);
// Color code based on completion status
Color progressColor = Color.Red;
if (aug.IsMaxed)
{
progressColor = Color.Green;
}
else if (aug.CurrentValue > 0)
{
progressColor = Color.Yellow;
}
// Apply color to progress text
SafeSetListColor(row, 1, progressColor);
}
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error populating augmentations list: {ex.Message}");
}
}
private void PopulateLuminanceList()
{
try
{
if (lstLuminanceAuras == null || data?.LuminanceAuraCategories == null) return;
lstLuminanceAuras.ClearRows();
foreach (var category in data.LuminanceAuraCategories)
{
// Add category header
var headerRow = lstLuminanceAuras.AddRow();
SafeSetListText(headerRow, 0, $"--- {category.Key} ---");
SafeSetListText(headerRow, 1, "");
SafeSetListText(headerRow, 2, "");
// Add luminance auras in this category
foreach (var aura in category.Value)
{
var row = lstLuminanceAuras.AddRow();
// Aura name
SafeSetListText(row, 0, aura.Name);
// Progress (current/cap)
string progressText = $"{aura.CurrentValue}/{aura.Cap}";
SafeSetListText(row, 1, progressText);
// Category or quest flag for Seer auras
string categoryText = category.Key == "Seer Auras" && !string.IsNullOrEmpty(aura.QuestFlag)
? aura.QuestFlag
: category.Key;
SafeSetListText(row, 2, categoryText);
// Color code based on progress
Color progressColor = Color.Red;
if (aura.CurrentValue >= aura.Cap)
{
progressColor = Color.Green;
}
else if (aura.CurrentValue > 0)
{
progressColor = Color.Yellow;
}
// Apply color to progress text
SafeSetListColor(row, 1, progressColor);
}
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error populating luminance list: {ex.Message}");
}
}
private void PopulateRecallsList()
{
try
{
if (lstRecallSpells == null || data?.RecallSpells == null) return;
lstRecallSpells.ClearRows();
foreach (var recall in data.RecallSpells)
{
var row = lstRecallSpells.AddRow();
// Column 0: Spell icon using MagTools approach
SafeSetListImage(row, 0, recall.IconId);
// Column 1: Recall spell name
SafeSetListText(row, 1, recall.Name);
// Column 2: Known status
string status = recall.IsKnown ? "Known" : "Unknown";
SafeSetListText(row, 2, status);
// Color code based on known status
Color statusColor = recall.IsKnown ? Color.Green : Color.Red;
SafeSetListColor(row, 2, statusColor);
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error populating recalls list: {ex.Message}");
}
}
private void PopulateCantripsList()
{
try
{
if (lstCantrips == null || data?.Cantrips == null) return;
lstCantrips.ClearRows();
foreach (var category in data.Cantrips)
{
// Add category header
var headerRow = lstCantrips.AddRow();
SafeSetListImage(headerRow, 0, 0x6002856); // Star icon for category headers
SafeSetListText(headerRow, 1, $"--- {category.Key} ---");
SafeSetListText(headerRow, 2, "");
// Add cantrips in this category
foreach (var cantrip in category.Value)
{
var row = lstCantrips.AddRow();
// Column 0: Icon (green/red circle based on status)
SafeSetListImage(row, 0, cantrip.Value.ComputedIconId);
// Column 1: Skill/Attribute name
SafeSetListText(row, 1, cantrip.Key);
// Column 2: Cantrip level
SafeSetListText(row, 2, cantrip.Value.Value);
// Apply color coding based on cantrip level
SafeSetListColor(row, 2, cantrip.Value.Color);
}
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error populating cantrips list: {ex.Message}");
}
}
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()
{
try
{
if (lstQuests == null)
{
PluginCore.WriteToChat("Quest list control is null");
return;
}
lstQuests.ClearRows();
// Always show debug info for now
var row = lstQuests.AddRow();
SafeSetListText(row, 0, $"Quest Manager: {(questManager != null ? "OK" : "NULL")}");
SafeSetListText(row, 1, $"Quest Count: {questManager?.QuestList?.Count ?? 0}");
SafeSetListText(row, 2, "Click Refresh to load quest data");
SafeSetListText(row, 3, "");
SafeSetListText(row, 4, "");
SafeSetListText(row, 5, "");
if (questManager?.QuestList != null && questManager.QuestList.Count > 0)
{
foreach (var quest in questManager.QuestList.OrderBy(q => q.Id))
{
var questRow = lstQuests.AddRow();
// Column 0: Quest Name
SafeSetListText(questRow, 0, quest.Id);
// Column 1: Solves
SafeSetListText(questRow, 1, quest.Solves.ToString());
// Column 2: Completed date
SafeSetListText(questRow, 2, questManager.FormatTimeStamp(quest.Timestamp));
// Column 3: Max solves
string maxText = quest.MaxSolves < 0 ? "∞" : quest.MaxSolves.ToString();
SafeSetListText(questRow, 3, maxText);
// Column 4: Delta (cooldown in seconds)
SafeSetListText(questRow, 4, questManager.FormatSeconds(quest.Delta));
// Column 5: Expire time
string expireText = questManager.GetTimeUntilExpire(quest);
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.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
{
// Quest is on cooldown - yellow
SafeSetListColor(questRow, 5, System.Drawing.Color.Yellow);
}
}
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error populating quests list: {ex.Message}");
}
}
#endregion
#region Cleanup
protected override void Dispose(bool disposing)
{
if (disposing)
{
try
{
if (data != null)
{
data.Dispose();
data = null;
}
if (questManager != null)
{
questManager.Dispose();
questManager = null;
}
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error disposing Flag Tracker: {ex.Message}");
}
}
base.Dispose(disposing);
}
#endregion
}
}

View file

@ -54,6 +54,11 @@ namespace MosswartMassacre.Views
private HudButton btnClearRoute; private HudButton btnClearRoute;
#endregion #endregion
#region Flag Tracker Tab Controls
private HudButton btnOpenFlagTracker;
private HudStaticText lblFlagTrackerStatus;
#endregion
#region Statistics Tracking #region Statistics Tracking
private double bestHourlyKills = 0; private double bestHourlyKills = 0;
private DateTime sessionStartTime; private DateTime sessionStartTime;
@ -70,15 +75,28 @@ namespace MosswartMassacre.Views
{ {
try try
{ {
// Check if VVS is available
try
{
var testParser = new VirindiViewService.XMLParsers.Decal3XMLParser();
}
catch (Exception vvsEx)
{
PluginCore.WriteToChat("[ERROR] VVS not available: " + vvsEx.Message);
return;
}
if (instance == null) if (instance == null)
{ {
instance = new VVSTabbedMainView(null); instance = new VVSTabbedMainView(null);
} }
instance.InitializeView(); instance.InitializeView();
} }
catch (Exception ex) catch (Exception ex)
{ {
PluginCore.WriteToChat("Error initializing VVS tabbed view: " + ex.Message); PluginCore.WriteToChat("Error initializing VVS tabbed view: " + ex.Message);
PluginCore.WriteToChat("Stack trace: " + ex.StackTrace);
} }
} }
@ -107,11 +125,18 @@ namespace MosswartMassacre.Views
// Create view from working original XML layout // Create view from working original XML layout
CreateFromXMLResource("MosswartMassacre.ViewXML.mainViewTabbed.xml"); CreateFromXMLResource("MosswartMassacre.ViewXML.mainViewTabbed.xml");
if (view == null)
{
PluginCore.WriteToChat("[ERROR] View creation failed - view is null!");
return;
}
// Initialize all tab controls // Initialize all tab controls
InitializeMainTabControls(); InitializeMainTabControls();
InitializeSettingsTabControls(); InitializeSettingsTabControls();
InitializeStatisticsTabControls(); InitializeStatisticsTabControls();
InitializeNavigationTabControls(); InitializeNavigationTabControls();
InitializeFlagTrackerTabControls();
// Initialize the base view and set initial position // Initialize the base view and set initial position
Initialize(); Initialize();
@ -121,11 +146,13 @@ namespace MosswartMassacre.Views
{ {
view.Visible = true; view.Visible = true;
view.ShowInBar = true; view.ShowInBar = true;
PluginCore.WriteToChat("GUI initialized successfully!");
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
PluginCore.WriteToChat("Error in VVS InitializeView: " + ex.Message); PluginCore.WriteToChat("Error in VVS InitializeView: " + ex.Message);
PluginCore.WriteToChat("Stack trace: " + ex.StackTrace);
} }
} }
@ -246,6 +273,28 @@ namespace MosswartMassacre.Views
PluginCore.WriteToChat($"Error initializing navigation controls: {ex.Message}"); PluginCore.WriteToChat($"Error initializing navigation controls: {ex.Message}");
} }
} }
private void InitializeFlagTrackerTabControls()
{
try
{
// Flag Tracker tab controls
btnOpenFlagTracker = GetControl<HudButton>("btnOpenFlagTracker");
lblFlagTrackerStatus = GetControl<HudStaticText>("lblFlagTrackerStatus");
// Hook up Flag Tracker events
if (btnOpenFlagTracker != null)
btnOpenFlagTracker.Hit += OnOpenFlagTrackerClick;
// Update initial status
if (lblFlagTrackerStatus != null)
lblFlagTrackerStatus.Text = "Status: Click to open the Flag Tracker window";
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error initializing flag tracker controls: {ex.Message}");
}
}
#endregion #endregion
#region Event Handlers - Settings Tab #region Event Handlers - Settings Tab
@ -409,6 +458,31 @@ namespace MosswartMassacre.Views
} }
#endregion #endregion
#region Event Handlers - Flag Tracker Tab
private void OnOpenFlagTrackerClick(object sender, EventArgs e)
{
try
{
// Update status to show opening
if (lblFlagTrackerStatus != null)
lblFlagTrackerStatus.Text = "Status: Opening Flag Tracker window...";
// Open the Flag Tracker window
FlagTrackerView.OpenFlagTracker();
// Update status
if (lblFlagTrackerStatus != null)
lblFlagTrackerStatus.Text = "Status: Flag Tracker window is open";
}
catch (Exception ex)
{
PluginCore.WriteToChat($"Error opening Flag Tracker: {ex.Message}");
if (lblFlagTrackerStatus != null)
lblFlagTrackerStatus.Text = "Status: Error opening Flag Tracker";
}
}
#endregion
#region Event Handlers - Navigation Tab #region Event Handlers - Navigation Tab
private void OnNavVisualizationEnabledChanged(object sender, EventArgs e) private void OnNavVisualizationEnabledChanged(object sender, EventArgs e)
{ {