From c30704aaa7304f29782cef4fdbc93a40b97b46ab Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 27 Feb 2026 08:10:39 +0000 Subject: [PATCH] Remove files that should not be in repo - Remove .claude/ settings (local Claude Code config) - Remove CLAUDE.md files (local project instructions) - Remove PluginCore.backup.cs (pre-refactoring backup, no longer needed) - Remove FINDINGS.md (scratch analysis file) - Add .claude/ and CLAUDE.md to .gitignore Co-Authored-By: Claude Opus 4.6 --- .claude/settings.local.json | 22 - .gitignore | 5 + CLAUDE.md | 81 - MosswartMassacre/.claude/settings.local.json | 3 - MosswartMassacre/CLAUDE.md | 375 ---- MosswartMassacre/FINDINGS.md | 304 ---- MosswartMassacre/PluginCore.backup.cs | 1654 ------------------ 7 files changed, 5 insertions(+), 2439 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 CLAUDE.md delete mode 100644 MosswartMassacre/.claude/settings.local.json delete mode 100644 MosswartMassacre/CLAUDE.md delete mode 100644 MosswartMassacre/FINDINGS.md delete mode 100644 MosswartMassacre/PluginCore.backup.cs diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index ffbff04..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(mkdir:*)", - "Bash(grep:*)", - "Bash(rg:*)", - "Bash(sed:*)", - "Bash(find:*)", - "Bash(true)", - "Bash(rm:*)", - "Bash(cp:*)", - "Bash(chmod:*)", - "Bash(./cleanup.sh:*)", - "Bash(bash:*)", - "Bash(ls:*)", - "Bash(python3:*)", - "Bash(msbuild:*)", - "Bash(dotnet build:*)" - ] - }, - "enableAllProjectMcpServers": false -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9fb5b30..8cb9ff0 100644 --- a/.gitignore +++ b/.gitignore @@ -366,3 +366,8 @@ FodyWeavers.xsd /MosswartMassacre/Unused /MosswartMassacre/Decal.Adapter.csproj /MosswartMassacre/Decal.Interop.Core.csproj + +# Claude Code +.claude/ +CLAUDE.md +**/CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 20acc58..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,81 +0,0 @@ -- when we porting flagtracker there mus be no simplification or trunkation of the code -- you can use utility belt for references about how to check views and create them perhaps also for quests. Same with the folder Mag-Tools -- we need to figure out a better way for this. By doing ID might be one thing, but ID changes when a stack run out - -# Icon Implementation Discoveries - -## DECAL Icon System - CRITICAL DIFFERENCES - -### Spell Icons vs Item Icons -**SPELL ICONS**: Use RAW icon values from DECAL FileService SpellTable -- Access via: `fileService.SpellTable.GetById(spellId)` then reflection to find icon property -- **NO OFFSET REQUIRED** - Use raw icon value directly -- Matches original Lua: `game.Character.SpellBook.Get(spellID).Icon` (no offset) - -**ITEM ICONS**: Require MagTools-style offset -- Format: `itemIcon + 0x6000000` -- Used for inventory items, weapons, armor, etc. - -### Working Implementation -```csharp -// For SPELL icons - NO offset -private int GetRealSpellIcon(int spellId) -{ - var fileService = CoreManager.Current.Filter(); - var spell = fileService.SpellTable.GetById(spellId); - // Use reflection to find icon property - return iconValue; // RAW value, no offset -} - -// For ITEM icons - WITH offset -int itemIconId = worldObject.Icon + 0x6000000; -``` - -### VVS Icon Display -- Use `DecalControls.IconColumn` in XML (NOT PictureColumn) -- IconColumn creates HudPictureBox controls automatically -- Set icon via: `((HudPictureBox)control).Image = iconId;` - -### Successful Applications -✅ **Flag Tracker Recalls Tab**: Real spell icons working perfectly -✅ **MagTools Reference**: Item icons with 0x6000000 offset -✅ **Original Lua Compatibility**: Exact replication of flagtracker behavior - -### Implemented Icon Applications -✅ **Flag Tracker Recalls Tab**: Real spell icons working perfectly -✅ **Flag Tracker Cantrips Tab**: Multi-layered icon system implemented -- **Attributes**: Spell icons (Strength, Endurance, etc.) with background support -- **Protection Auras**: Spell icons (Armor, Flame Ward, etc.) -- **Skills**: Dynamic skill icons from character training data - -### Cantrips Tab Icon System (Advanced Implementation) -Based on original Lua flagtracker's sophisticated 3-icon approach: - -```csharp -// Attributes: Background + Spell Icon overlay -["Strength"] = new CantripInfo { - SpellIconId = 1354, // StrengthSelf8 spell icon - BackgroundIconId = 0x060013F9 // UI background icon -}; - -// Protection Auras: Spell icons only -["Armor"] = new CantripInfo { - SpellIconId = 1397 // ArmorSelf8 spell icon -}; - -// Skills: Dynamic skill icons from character data -IconId = GetSkillIconId(skillId) // Real skill icon + 0x6000000 offset -``` - -### Icon Priority System -1. **Background + Spell Overlay**: For attributes (complex layering) -2. **Spell Icons Only**: For protection auras -3. **Skill Icons**: From character skill data (item-style offset) -4. **Fallback**: Default portal icon (0x6002D14) - -### Future Icon Usage -- **Weapons Tab**: 3-layer system (underlay + icon + overlay) from original Lua -- **Luminance Auras**: Could use custom UI icons -- **Society Items**: Could use item icons with proper offset - -- you cant build application. I will build it for you \ No newline at end of file diff --git a/MosswartMassacre/.claude/settings.local.json b/MosswartMassacre/.claude/settings.local.json deleted file mode 100644 index e8f289d..0000000 --- a/MosswartMassacre/.claude/settings.local.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "enableAllProjectMcpServers": false -} \ No newline at end of file diff --git a/MosswartMassacre/CLAUDE.md b/MosswartMassacre/CLAUDE.md deleted file mode 100644 index 1c3329b..0000000 --- a/MosswartMassacre/CLAUDE.md +++ /dev/null @@ -1,375 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Build Commands - -Build the project: -``` -msbuild MosswartMassacre.csproj /p:Configuration=Release /p:Platform=AnyCPU -``` - -Build debug version: -``` -msbuild MosswartMassacre.csproj /p:Configuration=Debug /p:Platform=AnyCPU -``` - -Restore NuGet packages: -``` -nuget restore packages.config -``` - -Generate installer (post-build, Windows only): -``` -powershell -File scripts/post-build.ps1 -NuGetPackageRoot "%USERPROFILE%\.nuget\packages" -ProjectDir "./" -``` - -Build entire solution from parent directory: -``` -msbuild ../mossy.sln -``` - -## Architecture Overview - -This is a DECAL plugin for Asheron's Call that tracks monster kills and rare item discoveries. The plugin architecture consists of several key components: - -### Core Plugin Framework -- **PluginCore.cs**: Main plugin entry point inheriting from `PluginBase`. Handles Startup/Shutdown lifecycle, event subscriptions (chat, spawn/despawn, login), and coordinates all subsystems. -- **PluginSettings.cs**: Per-character YAML configuration management using YamlDotNet. Settings are stored as `.yaml` in the plugin directory. - -### UI System (VVS Direct Integration) -- **Views/VVSTabbedMainView.cs**: Main tabbed UI using direct VirindiViewService integration. Displays kill stats, rare counts, elapsed time, settings, statistics, and navigation controls. -- **Views/VVSBaseView.cs**: Base class for VVS-based views providing common functionality like window positioning, control access, and resource management. -- **ViewXML/mainViewTabbed.xml**: XML layout definition for the tabbed UI overlay. - -### Communication Systems -- **WebSocket.cs**: Bidirectional WebSocket client connecting to `wss://overlord.snakedesert.se/websocket/` for real-time data streaming (spawns, chat, rares) and remote command reception. -- **HttpCommandServer.cs**: Local HTTP server on port 8085 accepting POST commands for remote control. -- **Telemetry.cs**: Periodic HTTP POST of player stats/position to configured endpoint. - -### Game Integration -- **vTank.cs + VtankControl.cs**: Interface with vTank automation bot for meta state control and **NAVIGATION CONTROL** ✅ -- **MossyInventory.cs**: Inventory change monitoring and logging. -- **DelayedCommandManager.cs**: Queue system for executing chat commands with delays. - -### Utility Components -- **Coordinates.cs + Geometry.cs**: Game coordinate system handling and spatial calculations. -- **Utils.cs**: General utility functions for player position, distance calculations, etc. -- **ManyHook.cs**: Low-level Windows message hooking functionality. - -### Event Processing Flow -1. **Chat Events**: `OnChatText()` in PluginCore.cs:217 parses kill messages using regex patterns, rare discovery messages, and remote commands from allegiance chat. -2. **World Events**: `OnSpawn()`/`OnDespawn()` in PluginCore.cs:150,171 track monster spawning for WebSocket streaming. -3. **Command Processing**: `/mm` command handler in `HandleMmCommand()` at PluginCore.cs:448 provides comprehensive plugin control interface. - -### Important Implementation Notes -- All regex patterns for kill detection are in `IsKilledByMeMessage()` at PluginCore.cs:337 -- Settings auto-save on shutdown and load after character login complete -- WebSocket uses session-based authentication with SharedSecret -- Plugin requires `AllowUnsafeBlocks=true` for P/Invoke operations - -### Dependencies -- **DECAL Framework**: Core AC plugin framework (Decal.Adapter, Decal.Interop.Core, Decal.Interop.D3DService) -- **VirindiViewService**: UI system for game overlays - used directly without wrapper abstraction -- **Newtonsoft.Json**: JSON serialization for WebSocket/HTTP communication -- **YamlDotNet**: YAML configuration file handling -- **utank2-i.dll**: vTank integration library - -### Key Configuration -- Target Framework: .NET Framework 4.8 -- Platform: x86 (required for AC integration) -- Unsafe blocks enabled for low-level operations -- VVS direct integration (no wrapper abstraction layer) -- Post-build script generates NSIS installer - -### Shared Code Structure -The project links to shared code in `../Shared/` containing common utilities for multiple AC plugins including constants, world object wrappers, settings management, and VCS chat system integration. - -## Navigation Visualization Feature ✅ COMPLETED - -### Overview -Successfully implemented complete navigation route comparison feature allowing visualization of VTank .nav files alongside UtilityBelt's active route visualization. Feature is **FULLY FUNCTIONAL** as of May 29, 2025. - -### Key Components Added -1. **NavRoute.cs** - VTank nav file parser and D3D line visualizer -2. **NavVisualization.cs** - Route management and file discovery -3. **Enhanced TabbedMainView.cs** - Navigation tab with controls -4. **Navigation Tab UI** - Dropdown selection, enable/disable, status display - -### Features Implemented -- ✅ Complete VTank .nav file format parsing (all waypoint types) -- ✅ 3D route visualization with red colored lines -- ✅ Support for all nav types: Circular, Linear, Target, Once -- ✅ Registry-based VTank directory detection -- ✅ Tabbed UI integration with existing interface -- ✅ Enable/disable visualization controls -- ✅ Route loading and clearing functionality -- ✅ Comprehensive debug logging system - -### Usage Instructions -1. **Enable**: Check "Enable Navigation Visualization" in Navigation tab -2. **Configure**: Set VTank profiles path in Settings tab (auto-detected) -3. **Select**: Choose route from dropdown and click "Load Route" -4. **View**: Red lines appear in 3D game world showing route path - -### Technical Details -- **Dependencies**: Added Decal.Interop.D3DService reference -- **Performance**: Optimized for large routes (max 500 line segments) -- **Integration**: Seamless addition to existing plugin architecture -- **Memory Management**: Proper D3D object disposal and cleanup - -### Reference Documentation -See `NAVIGATION_IMPLEMENTATION_SUMMARY.md` for complete technical details, implementation notes, and future enhancement opportunities. - ---- - -## GUI Architecture (Enhanced with Navigation Feature) - -### Current Implementation Status -✅ **Tabbed Interface**: Successfully implemented with Navigation, Settings, and Statistics tabs -✅ **BaseView Architecture**: Inherited from UtilityBelt's BaseView pattern -✅ **Advanced Controls**: Dropdown lists, checkboxes, buttons with proper event handling -✅ **Settings Integration**: VTank path configuration in Settings tab -✅ **Resource Management**: Proper disposal and memory management - -### Tabs Implemented -1. **Main Tab**: Kill tracking functionality (original) -2. **Settings Tab**: Configuration including VTank profiles path -3. **Statistics Tab**: Enhanced statistics display -4. **Navigation Tab**: ✅ Route visualization controls (NEW) - -### Future Enhancement Opportunities -1. **Multiple Route Support**: Visualize several routes simultaneously -2. **Route Analysis**: Distance calculations and efficiency metrics -3. **Advanced Visualization**: Waypoint icons and route optimization -4. **Export Features**: Save custom routes or export comparisons - -## VTank Navigation Control Feature ✅ COMPLETED - -### Overview -Successfully implemented complete programmatic control of VTank's navigation system, allowing remote waypoint advancement through the `/mm nextwp` command. Feature is **FULLY FUNCTIONAL** as of May 29, 2025. - -### Breakthrough Achievement -Cracked VTank's internal architecture through reverse engineering and reflection to enable seamless integration between Mosswart Massacre and VTank's navigation system. - -### Key Implementation -- **VtankControl.cs:114-223** - `VtAdvanceWaypoint()` method using precise reflection -- **PluginCore.cs:661-671** - `/mm nextwp` command handler with user feedback -- **Decompiled Analysis** - Complete VTank internal structure mapping - -### Technical Solution -Resolved "Ambiguous match found" reflection error by specifying exact method signatures: -```csharp -Type[] parameterTypes = new Type[] { typeof(object), assembly.GetType("MetaViewWrappers.MVControlEventArgs") }; -var advanceMethod = pluginCoreType.GetMethod("i", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, - null, parameterTypes, null); -``` - -### Features Implemented -- ✅ Programmatic waypoint advancement via `/mm nextwp` -- ✅ Navigation state validation (waypoint count, position, nav type) -- ✅ VTank business logic compliance (Target/Once type restrictions) -- ✅ Comprehensive error handling and user feedback -- ✅ Fallback mechanisms for maximum reliability -- ✅ Assembly-based reflection for internal access - -### Architecture Discovery -**VTank Internal Structure**: -``` -vTank.Instance (cExternalInterfaceTrustedRelay) - └── Assembly: utank2-i.dll - └── Type: uTank2.PluginCore - ├── Static Field: PC (PluginCore instance) - │ └── Method: i(object, MVControlEventArgs) ← TARGET - └── Static Field: dz (fallback direct access) - └── Field: o.l (current waypoint index) -``` - -### Usage -Simply type `/mm nextwp` in-game to advance VTank to the next waypoint. Works with Circular and Linear navigation types. - -### Reference Documentation -See `FINDINGS.md` for complete technical breakthrough details, implementation analysis, and reverse engineering discoveries. - -## Universal Plugin Message Interception via Harmony ✅ SUCCESSFULLY IMPLEMENTED - -### Current Status: FULLY FUNCTIONAL - Harmony 1.2.0.1 Integration Complete - -Successfully implemented **universal plugin message interception** using UtilityBelt's Harmony 1.2.0.1 DLL to patch DECAL's core chat methods. All plugin messages are now being captured and streamed! - -### ✅ **IMPLEMENTATION SUCCESS**: -- ✅ **UtilityBelt Compatibility Resolved**: Using same Harmony 1.2.0.1 DLL as UB -- ✅ **Plugin Message Capture Working**: Successfully intercepting [UB], [VI], [VGI], [VTank] messages -- ✅ **WebSocket Streaming Active**: All intercepted messages stream to remote server -- ✅ **No Plugin Conflicts**: All plugins functioning normally with our patches - -### 🎯 What's Working -- **Harmony Framework**: Complete DECAL method interception using Harmony 1.2.0.1 (old API) -- **Dual-Mode Patching**: Successfully patches `HooksWrapper.AddChatText()` and `AddChatTextRaw()` -- **Universal Capture**: Capturing plugin messages from UtilityBelt, VI, VGI, VTank -- **Debug System**: `/mm decalstatus`, `/mm decaldebug`, `/mm harmonyraw` commands -- **WebSocket Integration**: Real-time streaming of all intercepted messages -- **Duplicate Prevention**: Smart logic prevents double-patching same instance - -### 🔧 Technical Solution Implemented - -#### **Harmony Version Resolution** -- **Solution Applied**: Using UtilityBelt's 0Harmony.dll (version 1.2.0.1) -- **API Conversion**: Changed from Harmony 2.x API to Harmony 1.x API -- **Key Changes**: - - `HarmonyLib.Harmony` → `Harmony.HarmonyInstance` - - `new Harmony()` → `HarmonyInstance.Create()` - - Reference points to `C:\Games\Decal Plugins\UtilityBelt\0Harmony.dll` - -#### **Current Patching Architecture** - -1. **DecalHarmonyClean.cs** ✅ WORKING - - **Harmony 1.x Integration**: Uses `Harmony.HarmonyInstance` API - - **Smart Patching**: Avoids duplicates by checking if Host.Actions == HooksWrapper - - **Methods Patched**: `AddChatText(string, int)`, `AddChatText(string, int, int)`, `AddChatTextRaw` variants - - **Debug Logging**: Internal queue-based logging with `/mm harmonyraw` command - -2. **Message Processing Flow** - - All messages (including [Mosswart Massacre]) are captured and streamed - - Debug output filtered to prevent spam but streaming remains active - - WebSocket streaming requires both `AggressiveChatStreamingEnabled` and `WebSocketEnabled` - -### 📊 Performance & Results -- **Messages Intercepted**: Successfully capturing 100+ messages per session -- **Plugin Coverage**: [UB], [VI], [VGI], [VTank] messages all captured -- **Streaming Reliability**: All messages successfully sent to WebSocket endpoint -- **No Performance Impact**: Harmony patches execute efficiently - -### ✅ **COMPLETED: Code Cleanup Plan - All Phases Done (May 31, 2025)** - -**Successfully completed comprehensive codebase cleanup to resolve API JSON encoding errors and remove experimental code:** - -#### **✅ Phase 1: Remove Unused Harmony Experiments** - COMPLETED -**Files Removed (Previously):** -- All experimental Harmony files removed in earlier cleanup sessions -- Kept: `DecalHarmonyClean.cs` (working implementation) - -#### **✅ Phase 2: Remove Failed Direct Hook Attempts** - COMPLETED -**Files Removed (Previously):** -- All failed hook attempt files removed in earlier cleanup sessions -- Direct hooks, COM interceptors, and window monitoring approaches removed - -#### **✅ Phase 3: Clean Debug & Test Commands** - COMPLETED -**Status:** All debug commands were already cleaned up, no obsolete commands found - -#### **✅ Phase 4: Remove Rich Text Formatting** - COMPLETED -**API Error Resolution:** -- **Root Cause Found:** Emoji characters (🎯, ✅, ❌, 🔧, 🔍, 🔄) in chat messages were causing JSON encoding errors -- **Fixed:** All emoji replaced with plain text markers ([OK], [FAIL], [INFO], [WARN]) -- **Files Updated:** `PluginCore.cs`, `VTankChatIntegration.cs`, `VCSIntegration.cs`, `PluginLoadDiagnostic.cs` -- **Result:** API JSON encoding errors resolved - -#### **✅ Phase 5: Clean Project References** - COMPLETED -**Status:** Project references and packages.config were already clean, no unnecessary dependencies found - -#### **✅ Phase 6: Consolidate Integration Classes** - COMPLETED -**Files Removed:** -- `UtilityBeltControl.cs` - Unused UtilityBelt integration (superseded by Harmony) -- `VTankChatIntegration.cs` - Unused VTank chat integration (superseded by Harmony) -- `VCSIntegration.cs` - Unused VCS integration (superseded by Harmony) -- **Project File Updated:** Removed references to deleted integration classes - -#### **✅ Phase 7: Clean Verbose Debug Output** - COMPLETED -**Issue:** Harmony initialization was producing 20+ lines of debug spam in chat -**Resolution:** -- **DecalHarmonyClean.cs:** Removed all verbose `PluginCore.WriteToChat()` debug messages -- **PluginCore.cs:** Simplified Harmony initialization to single success/failure message -- **Result:** Clean startup with just `[OK] Plugin message interception active` - -#### **✅ Phase 8: Remove Development Scaffolding** - COMPLETED -**Files Removed:** -- `PluginLoadDiagnostic.cs` - Development testing utility no longer needed -- **Syntax Errors Fixed:** Corrected string interpolation issues in `DecalHarmonyClean.cs` -- **Project File Updated:** Removed reference to diagnostic file - -### 📋 **Final Testing Checklist** -1. ✅ `/mm decalstatus` - Verify Harmony patches still active -2. ✅ `/mm decaldebug enable` - Test plugin message capture -3. ✅ WebSocket streaming - Confirm no JSON encoding errors -4. ✅ Plugin compatibility - No conflicts with UtilityBelt/VTank -5. ✅ Build verification - All syntax errors resolved -6. ✅ Clean startup - No verbose debug spam, single initialization message - -### 🏗️ Build Configuration -- **Harmony Reference**: `C:\Games\Decal Plugins\UtilityBelt\0Harmony.dll` -- **Copy Local**: False (uses UB's loaded assembly) -- **Target Framework**: .NET 4.8 -- **Platform**: x86 - -## Flag Tracker Feature ✅ IN PROGRESS - VVS Integration - -### Overview -Successfully implemented comprehensive Flag Tracker feature porting the complete flagtracker Lua plugin from UBS to C# using VirindiViewService (VVS). This is a major new feature providing character tracking for augmentations, luminance auras, recall spells, society quests, character flags, cantrips, and weapons. - -### Implementation Status -✅ **Core Framework Completed**: -- FlagTrackerView.cs - Complete 10-tab VVS window -- FlagTrackerData.cs - All data structures ported from Lua -- QuestManager.cs - Quest tracking system -- flagTracker.xml - Comprehensive UI layout - -✅ **Integration Completed**: -- Added Flag Tracker tab to main tabbed view -- Button launches dedicated Flag Tracker window -- VVS inheritance from VVSBaseView -- Proper event handling and resource management - -### Current Issue: VVS HudList Column Access -**Problem**: "Column out of range" errors when populating VVS HudList controls -**Root Cause**: VVS HudList column creation from XML may not be working as expected -**Status**: Implementing safe column access patterns with detailed debugging - -### Files Implemented -1. **Views/FlagTrackerView.cs** - Main Flag Tracker window - - 10-tab structure: Augs, Lum, Recalls, Society, FacHub, Flags, Cantrips, Weapons, Quests, Settings - - VVS HudList population methods with SafeSetListText() helper - - Complete event handling for all refresh buttons - -2. **FlagTrackerData.cs** - Data management - - Complete augmentation data (Death, Skill, Rating, Stat, Resistance, Spec, Misc) - - Luminance aura categories (Nalicana, Seer) - - Recall spell definitions - - Society quest structures - - Character flag categories - - Cantrip and weapon data structures - - Reflection-based DECAL property access - -3. **QuestManager.cs** - Quest tracking system - - DECAL CoreManager integration - - Quest refresh and parsing - -4. **ViewXML/flagTracker.xml** - UI Layout - - 10-tab notebook control - - HudList controls with column definitions - - Comprehensive button and settings controls - -### Technical Approach -- **VVS Direct Integration**: No wrapper abstraction, direct VirindiViewService usage -- **Safe Column Access**: SafeSetListText() method with IndexOutOfRangeException handling -- **Reflection-Based Data Access**: Bypasses DECAL type system issues using raw integer property access -- **Complete Data Preservation**: "No simplification or truncation" as requested by user - -### Architecture Integration -- **Main View Integration**: Flag Tracker button added to mainViewTabbed.xml -- **Event Handling**: OnOpenFlagTrackerClick() in VVSTabbedMainView.cs -- **Static Interface**: OpenFlagTracker(), CloseFlagTracker(), IsOpen() methods -- **Resource Management**: Proper disposal in base VVSBaseView pattern - -### Debug Features Added -- Control initialization status reporting -- Safe column access with detailed error messages -- Column existence validation before access - -### Next Steps -1. Resolve VVS HudList column creation from XML -2. Complete remaining tab data population -3. Implement actual character data retrieval -4. Add settings persistence - ---- \ No newline at end of file diff --git a/MosswartMassacre/FINDINGS.md b/MosswartMassacre/FINDINGS.md deleted file mode 100644 index 0af01a5..0000000 --- a/MosswartMassacre/FINDINGS.md +++ /dev/null @@ -1,304 +0,0 @@ -# VTank Next Waypoint Control - BREAKTHROUGH FINDINGS - -## SUCCESS: VTank Waypoint Advancement WORKING ✅ - -**Date**: May 29, 2025 -**Status**: FULLY FUNCTIONAL -**Command**: `/mm nextwp` - -## The Challenge - -Implementing programmatic control of VTank's "Advance Current Point" button functionality to skip to the next waypoint in a navigation route. This required deep reverse engineering of VTank's internal architecture. - -## Key Technical Breakthrough - -### The "Ambiguous Match Found" Problem -The critical issue was that VTank's `PluginCore` class contains multiple methods named `i`, causing reflection to fail with "Ambiguous match found" when trying to invoke the waypoint advance method. - -### The Solution -Specified exact method signature using parameter types in the `GetMethod()` call: - -```csharp -Type[] parameterTypes = new Type[] { typeof(object), assembly.GetType("MetaViewWrappers.MVControlEventArgs") }; -var advanceMethod = pluginCoreType.GetMethod("i", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, - null, parameterTypes, null); -``` - -## Complete VTank Architecture Discovery - -### Core Architecture Components - -#### 1. External Interface Layer -- **Type**: `cExternalInterfaceTrustedRelay` -- **Access**: `vTank.Instance` (singleton pattern) -- **Purpose**: Safe public API facade with permission checking -- **Assembly**: `utank2-i.dll` -- **Permission Levels**: Uses `eExternalsPermissionLevel` enum for access control - -#### 2. Internal Core Logic -- **Type**: `uTank2.PluginCore` -- **Static Instance**: `PC` field (PluginCore singleton) -- **Purpose**: Main plugin implementation containing all core functionality -- **Access Pattern**: External interface delegates to internal PluginCore methods - -#### 3. Navigation System Architecture -``` -Navigation Data Flow: -ExternalInterface.NavCurrent - └── calls: a(eExternalsPermissionLevel.FullUnderlying) - └── returns: PC.NavCurrent - └── accesses: dz.o.l (actual waypoint index storage) -``` - -### Deep Navigation Implementation Details - -#### Navigation State Management -- **Current Waypoint**: `dz.o.l` (int field chain) -- **Total Waypoints**: `dz.o` contains waypoint collection -- **Navigation Type**: Enum values (0=Circular, 1=Linear, 2=Target, 4=Once) -- **Meta State Integration**: `dz.at.f()` and `dz.at.b(value)` for meta state control - -#### Waypoint Advancement Logic (from decompiled source) -```csharp -// VTank's internal advance logic: -if (navType != 2 && navType != 4) // Not Target or Once -{ - if (dz.o.l < totalWaypoints - 1) // Not at end - { - dz.o.l++; // Advance waypoint - } -} -``` - -#### Button Click Handler Chain -1. **UI Button**: "Advance Current Point" in VTank interface -2. **Event Handler**: `i(object A_0, MVControlEventArgs A_1)` method -3. **Permission Check**: Validates navigation state and type -4. **Core Logic**: Calls internal waypoint advancement -5. **State Update**: Updates `dz.o.l` and refreshes UI - -### Permission and Security Model - -#### Access Levels (eExternalsPermissionLevel) -- **LogicObject**: Basic access to cLogic object -- **FullUnderlying**: Complete access to internal state (required for navigation) -- **Permission Validation**: `a(level)` method checks permissions before access - -#### Trust Relationship -- External plugins must be "trusted" to access navigation functions -- VTank validates calling assembly and permission levels -- Reflection bypasses normal permission checks (security consideration) - -### Method Signature Discovery - -#### Navigation Methods in PluginCore -- **Multiple 'i' Methods**: Caused "Ambiguous match found" error -- **Target Method**: `i(object A_0, MVControlEventArgs A_1)` - button event handler -- **Parameter Types**: - - `object` - sender/source object (can be null) - - `MetaViewWrappers.MVControlEventArgs` - VVS event args (can be null) - -#### Reflection Access Pattern -```csharp -// Critical discovery: Must specify exact parameter types -Type[] parameterTypes = new Type[] { - typeof(object), - assembly.GetType("MetaViewWrappers.MVControlEventArgs") -}; -var method = type.GetMethod("i", flags, null, parameterTypes, null); -``` - -### Navigation Type Behaviors - -#### Circular Navigation (Type 0) -- ✅ Advance allowed at any waypoint -- Wraps from last waypoint back to first -- Most common navigation pattern - -#### Linear Navigation (Type 1) -- ✅ Advance allowed until last waypoint -- Stops at final waypoint (no wrap-around) -- Used for point-to-point routes - -#### Target Navigation (Type 2) -- ❌ Advance blocked by design -- Single destination waypoint -- Navigation automatically stops at target - -#### Once Navigation (Type 4) -- ❌ Advance blocked by design -- One-time route execution -- Cannot manually advance waypoints - -### Meta State Integration - -#### Current Meta State Access -- **Getter**: `dz.at.f()` returns current meta state string -- **Setter**: `dz.at.b(value)` sets new meta state -- **Integration**: Navigation events can trigger meta state changes -- **Example**: Rare discoveries trigger "loot_rare" meta state - -### Assembly and Type Discovery - -#### Key Assembly Information -- **File**: `utank2-i.dll` (interface assembly) -- **Namespace**: `uTank2` -- **Main Type**: `PluginCore` -- **Static Fields**: `PC` (PluginCore instance), `dz` (navigation data) - -#### Critical Type Relationships -``` -uTank2.PluginCore (main logic) -├── Static PC: PluginCore instance -├── Static dz: Navigation data container -│ └── Field o: Navigation object -│ └── Field l: Current waypoint index (int) -└── Method i(object, MVControlEventArgs): Advance handler -``` - -### Error Patterns and Solutions - -#### Common Reflection Errors -1. **"Ambiguous match found"**: Multiple methods with same name - - **Solution**: Specify exact parameter types in GetMethod() -2. **Permission Denied**: External interface blocks access - - **Solution**: Use assembly-level reflection to bypass interface -3. **Null Reference**: Static fields not initialized - - **Solution**: Validate object hierarchy before access - -#### Debugging Techniques Used -1. **Type Inspection**: `GetType().Name` to identify actual types -2. **Assembly Analysis**: Loading and inspecting utank2-i.dll -3. **Field Enumeration**: Discovering static fields via reflection -4. **Method Signature Analysis**: Identifying parameter requirements - -### Access Pattern Hierarchy -``` -Application Level: - /mm nextwp command - └── VtankControl.VtAdvanceWaypoint() - └── Validation (waypoints, nav type, position) - └── Assembly.GetType("uTank2.PluginCore") - └── GetField("PC").GetValue(null) - └── GetMethod("i", paramTypes).Invoke(instance, params) - └── VTank Internal Navigation Logic - └── dz.o.l++ (waypoint advancement) -``` - -### Performance Characteristics -- **Reflection Overhead**: Minimal (only on command execution) -- **Memory Impact**: No persistent references to VTank objects -- **UI Responsiveness**: No blocking operations -- **Error Recovery**: Graceful fallback to direct field manipulation - -## Implementation Details - -### File: VtankControl.cs:114-223 -Added `VtAdvanceWaypoint()` method with: -- Navigation validation (waypoint count, current position, nav type) -- Assembly-based reflection to access internal VTank types -- Primary approach: Method invocation via PluginCore.PC.i() -- Fallback approach: Direct field manipulation of dz.o.l -- Comprehensive error handling and user feedback - -### File: PluginCore.cs:661-671 -Added `/mm nextwp` command handler with success/failure messaging. - -## Navigation Type Restrictions -Correctly implemented VTank's business logic: -- **Circular/Linear**: ✅ Advance allowed -- **Target/Once**: ❌ Advance blocked (matches VTank UI behavior) - -## Testing Results -- ✅ Successfully advances waypoint in Circular navigation -- ✅ Properly validates navigation state before advancing -- ✅ Provides clear user feedback on success/failure -- ✅ Handles edge cases (no waypoints, at end of route) -- ✅ Respects VTank's navigation type restrictions - -## Technical Lessons Learned - -1. **Reflection Precision**: When dealing with obfuscated/complex assemblies, always specify exact method signatures using parameter types to avoid ambiguity. - -2. **Assembly Navigation**: External interfaces often provide facade access to internal implementations via assembly type lookups. - -3. **Static Field Patterns**: Many plugin architectures use static fields (like `PC`) to maintain singleton instances for cross-component access. - -4. **Decompiled Code Value**: Reverse engineering provided exact field names (`dz.o.l`) and method signatures crucial for implementation. - -5. **Fallback Strategies**: Implementing multiple access approaches (method invocation + direct field access) increases reliability. - -## Future Enhancement Opportunities - -1. **Previous Waypoint**: Implement `/mm prevwp` command using similar reflection techniques -2. **Jump to Waypoint**: `/mm gotowp ` for direct waypoint targeting -3. **Route Management**: Integration with VTank's route loading/saving functionality -4. **Navigation State Monitoring**: Real-time tracking of waypoint advancement - -## Security Considerations -- Uses private reflection to access VTank internals -- Requires appropriate permissions and trust levels -- No modification of VTank files or persistent state -- Read-only access to navigation configuration - -## Chat Capture System - CRITICAL LOOP PREVENTION ⚠️ - -### The Recursive Loop Problem -When implementing unified chat capture (native + plugin-generated), there's a serious risk of infinite recursion: - -1. Plugin calls `WriteToChat()` → Adds text to chat window -2. Window monitor detects new text → Fires chat event -3. Chat handler encounters error → Calls `WriteToChat()` again -4. **INFINITE LOOP** leading to stack overflow and crash - -### Prevention Mechanisms Implemented - -#### 1. Message Filtering -```csharp -// Block our own plugin messages from being processed -if (text.Contains("[Mosswart Massacre]")) return false; -if (text.Contains("[UnifiedChat]")) return false; -// ... other plugin prefixes -``` - -#### 2. Safe Error Handling -```csharp -catch (Exception ex) -{ - // NEVER use WriteToChat in error handlers! - System.Diagnostics.Debug.WriteLine($"Error: {ex}"); // Safe alternative -} -``` - -#### 3. Recursion Detection -```csharp -private static bool DetectPotentialRecursion() -{ - // Monitor call frequency - if >10 calls in 100ms, likely a loop - if (_recursionDetectionCount > 10) return true; -} -``` - -#### 4. Multiple Safety Layers -- **Content filtering**: Block plugin-generated messages -- **Debug logging**: Use `Debug.WriteLine()` instead of `WriteToChat()` in handlers -- **Frequency monitoring**: Detect rapid-fire calls indicating loops -- **Circuit breaker**: Temporarily disable monitor if recursion detected - -### Implementation Notes -- **NEVER** call `WriteToChat()` from within chat event handlers -- **ALWAYS** filter out your own plugin's messages -- **USE** `System.Diagnostics.Debug.WriteLine()` for error logging in chat handlers -- **TEST** thoroughly with `/mm chattest` command - -This prevention system is **CRITICAL** for stability - without it, the plugin would crash AC client on any WebSocket error. - -## Performance Impact -- Minimal: Reflection calls only on command execution -- No continuous polling or background processing -- Lightweight validation checks before expensive reflection operations - ---- - -**CONCLUSION**: Complete programmatic control of VTank navigation achieved through precise reflection engineering. The `/mm nextwp` command now provides seamless integration between Mosswart Massacre and VTank's navigation system. \ No newline at end of file diff --git a/MosswartMassacre/PluginCore.backup.cs b/MosswartMassacre/PluginCore.backup.cs deleted file mode 100644 index 543fa88..0000000 --- a/MosswartMassacre/PluginCore.backup.cs +++ /dev/null @@ -1,1654 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Timers; -using Decal.Adapter; -using Decal.Adapter.Wrappers; -using MosswartMassacre.Views; -using Mag.Shared.Constants; - -namespace MosswartMassacre -{ - [FriendlyName("Mosswart Massacre")] - public class PluginCore : PluginBase - { - // Hot Reload Support Properties - public static string AssemblyDirectory { get; set; } - public static bool IsHotReload { get; set; } - - internal static PluginHost MyHost; - internal static int totalKills = 0; - internal static int rareCount = 0; - internal static int sessionDeaths = 0; // Deaths this session - internal static int totalDeaths = 0; // Total deaths from character - internal static int cachedPrismaticCount = 0; // Cached Prismatic Taper count - internal static int lastPrismaticCount = 0; // For delta calculation - - // Track taper items and their containers for accurate release detection - private static readonly Dictionary trackedTaperContainers = new Dictionary(); - private static readonly Dictionary lastKnownStackSizes = new Dictionary(); - internal static DateTime lastKillTime = DateTime.Now; - internal static double killsPer5Min = 0; - internal static double killsPerHour = 0; - internal static DateTime statsStartTime = DateTime.Now; - internal static Timer updateTimer; - private static Timer vitalsTimer; - private static System.Windows.Forms.Timer commandTimer; - private static readonly Queue pendingCommands = new Queue(); - public static bool RareMetaEnabled { get; set; } = true; - - // VVS View Management - private static class ViewManager - { - public static void ViewInit() - { - Views.VVSTabbedMainView.ViewInit(); - } - - public static void ViewDestroy() - { - Views.VVSTabbedMainView.ViewDestroy(); - } - - public static void UpdateKillStats(int totalKills, double killsPer5Min, double killsPerHour) - { - Views.VVSTabbedMainView.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); - } - - public static void UpdateElapsedTime(TimeSpan elapsed) - { - Views.VVSTabbedMainView.UpdateElapsedTime(elapsed); - } - - public static void UpdateRareCount(int rareCount) - { - Views.VVSTabbedMainView.UpdateRareCount(rareCount); - } - - public static void SetRareMetaToggleState(bool enabled) - { - Views.VVSTabbedMainView.SetRareMetaToggleState(enabled); - } - - public static void RefreshSettingsFromConfig() - { - Views.VVSTabbedMainView.RefreshSettingsFromConfig(); - } - } - public static bool RemoteCommandsEnabled { get; set; } = false; - public static bool HttpServerEnabled { get; set; } = false; - public static string CharTag { get; set; } = ""; - public static bool TelemetryEnabled { get; set; } = false; - public static bool WebSocketEnabled { get; set; } = false; - public bool InventoryLogEnabled { get; set; } = false; - public static bool AggressiveChatStreamingEnabled { get; set; } = true; - private MossyInventory _inventoryLogger; - public static NavVisualization navVisualization; - - // Quest Management for always-on quest streaming - public static QuestManager questManager; - private static Timer questStreamingTimer; - - private static Queue rareMessageQueue = new Queue(); - private static DateTime _lastSent = DateTime.MinValue; - private static readonly Queue _chatQueue = new Queue(); - - protected override void Startup() - { - try - { - // DEBUG: Add startup debug message - WriteToChat("[DEBUG] PluginCore.Startup() called"); - - // Set MyHost - for hot reload scenarios, Host might be null - if (Host != null) - { - MyHost = Host; - } - else if (MyHost == null) - { - // Hot reload fallback - this is okay, WriteToChat will handle it - MyHost = null; - } - - // Check if this is a hot reload - var isCharacterLoaded = CoreManager.Current.CharacterFilter.LoginStatus == 3; - if (IsHotReload || isCharacterLoaded) - { - // Hot reload detected - reinitialize connections and state - WriteToChat("[INFO] Hot reload detected - reinitializing plugin"); - - // Reload settings if character is already logged in - if (isCharacterLoaded) - { - try - { - WriteToChat("Hot reload - reinitializing character-dependent systems"); - // Don't call LoginComplete - create hot reload specific initialization - InitializeForHotReload(); - WriteToChat("[INFO] Hot reload initialization complete"); - } - catch (Exception ex) - { - WriteToChat($"[ERROR] Hot reload initialization failed: {ex.Message}"); - } - } - } - - // Note: Startup messages will appear after character login - // Subscribe to chat message event - WriteToChat("[DEBUG] Subscribing to events..."); - CoreManager.Current.ChatBoxMessage += new EventHandler(OnChatText); - CoreManager.Current.ChatBoxMessage += new EventHandler(AllChatText); - CoreManager.Current.CommandLineText += OnChatCommand; - WriteToChat("[DEBUG] About to subscribe to LoginComplete event"); - CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete; - WriteToChat("[DEBUG] LoginComplete event subscription successful"); - CoreManager.Current.CharacterFilter.Death += OnCharacterDeath; - CoreManager.Current.WorldFilter.CreateObject += OnSpawn; - CoreManager.Current.WorldFilter.CreateObject += OnPortalDetected; - CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn; - // Subscribe to inventory change events for taper tracking - CoreManager.Current.WorldFilter.CreateObject += OnInventoryCreate; - CoreManager.Current.WorldFilter.ReleaseObject += OnInventoryRelease; - CoreManager.Current.WorldFilter.ChangeObject += OnInventoryChange; - // Initialize VVS view after character login - ViewManager.ViewInit(); - - // Initialize the timer - updateTimer = new Timer(1000); // Update every second - updateTimer.Elapsed += UpdateStats; - updateTimer.Start(); - - // Initialize vitals streaming timer - vitalsTimer = new Timer(5000); // Send vitals every 5 seconds - vitalsTimer.Elapsed += SendVitalsUpdate; - vitalsTimer.Start(); - - // Initialize command processing timer (Windows Forms timer for main thread) - commandTimer = new System.Windows.Forms.Timer(); - commandTimer.Interval = 10; // Process commands every 10ms - commandTimer.Tick += ProcessPendingCommands; - commandTimer.Start(); - - // Note: View initialization moved to LoginComplete for VVS compatibility - - // Enable TLS1.2 - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - //Enable vTank interface - vTank.Enable(); - //lyssna på commands - WebSocket.OnServerCommand += HandleServerCommand; - //starta inventory. Hanterar subscriptions i den med - - _inventoryLogger = new MossyInventory(); - - // Initialize navigation visualization system - navVisualization = new NavVisualization(); - - // Note: DECAL Harmony patches will be initialized in LoginComplete event - // where the chat system is available for error messages - - } - catch (Exception ex) - { - WriteToChat("Error during startup: " + ex.Message); - } - } - - protected override void Shutdown() - { - try - { - PluginSettings.Save(); - if (TelemetryEnabled) - Telemetry.Stop(); // ensure no dangling timer / HttpClient - WriteToChat("Mosswart Massacre is shutting down..."); - - // Unsubscribe from chat message event - CoreManager.Current.ChatBoxMessage -= new EventHandler(OnChatText); - CoreManager.Current.CommandLineText -= OnChatCommand; - CoreManager.Current.ChatBoxMessage -= new EventHandler(AllChatText); - CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath; - CoreManager.Current.WorldFilter.CreateObject -= OnSpawn; - CoreManager.Current.WorldFilter.CreateObject -= OnPortalDetected; - CoreManager.Current.WorldFilter.ReleaseObject -= OnDespawn; - // Unsubscribe from inventory change events - CoreManager.Current.WorldFilter.CreateObject -= OnInventoryCreate; - CoreManager.Current.WorldFilter.ReleaseObject -= OnInventoryRelease; - CoreManager.Current.WorldFilter.ChangeObject -= OnInventoryChange; - - - // Stop and dispose of the timers - if (updateTimer != null) - { - updateTimer.Stop(); - updateTimer.Dispose(); - updateTimer = null; - } - - if (vitalsTimer != null) - { - vitalsTimer.Stop(); - vitalsTimer.Dispose(); - vitalsTimer = null; - } - - if (commandTimer != null) - { - commandTimer.Stop(); - commandTimer.Dispose(); - commandTimer = null; - } - - // Stop and dispose quest streaming timer - if (questStreamingTimer != null) - { - questStreamingTimer.Stop(); - questStreamingTimer.Elapsed -= OnQuestStreamingUpdate; - questStreamingTimer.Dispose(); - questStreamingTimer = null; - } - - // Dispose quest manager - if (questManager != null) - { - questManager.Dispose(); - questManager = null; - } - - // Clean up the view - ViewManager.ViewDestroy(); - //Disable vtank interface - vTank.Disable(); - // sluta lyssna på commands - WebSocket.OnServerCommand -= HandleServerCommand; - WebSocket.Stop(); - //shutdown inv - _inventoryLogger.Dispose(); - - // Clean up navigation visualization - if (navVisualization != null) - { - navVisualization.Dispose(); - navVisualization = null; - } - - // Clean up taper tracking - trackedTaperContainers.Clear(); - lastKnownStackSizes.Clear(); - - // Clean up Harmony patches - DecalHarmonyClean.Cleanup(); - - MyHost = null; - } - catch (Exception ex) - { - WriteToChat("Error during shutdown: " + ex.Message); - } - } - private void CharacterFilter_LoginComplete(object sender, EventArgs e) - { - WriteToChat("[DEBUG] CharacterFilter_LoginComplete event fired!"); - CoreManager.Current.CharacterFilter.LoginComplete -= CharacterFilter_LoginComplete; - - WriteToChat("Mosswart Massacre has started!"); - - - - PluginSettings.Initialize(); // Safe to call now - - // Apply the values - RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; - WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled; - RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled; - HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled; - TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled; - CharTag = PluginSettings.Instance.CharTag; - ViewManager.SetRareMetaToggleState(RareMetaEnabled); - ViewManager.RefreshSettingsFromConfig(); // Refresh all UI settings after loading - if (TelemetryEnabled) - Telemetry.Start(); - if (WebSocketEnabled) - WebSocket.Start(); - - // Initialize Harmony patches using UtilityBelt's loaded DLL - 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}"); - } - - // Initialize death tracking - totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths); - sessionDeaths = 0; - - // Initialize cached Prismatic Taper count - InitializePrismaticTaperCount(); - - // Initialize quest manager for always-on quest streaming - try - { - questManager = new QuestManager(); - questManager.RefreshQuests(); - - // Initialize quest streaming timer (30 seconds) - questStreamingTimer = new Timer(30000); - questStreamingTimer.Elapsed += OnQuestStreamingUpdate; - questStreamingTimer.AutoReset = true; - questStreamingTimer.Start(); - - WriteToChat("[OK] Quest streaming initialized"); - } - catch (Exception ex) - { - WriteToChat($"[ERROR] Quest streaming initialization failed: {ex.Message}"); - } - - } - - #region Quest Streaming Methods - private static void OnQuestStreamingUpdate(object sender, ElapsedEventArgs e) - { - try - { - // Stream high priority quest data via WebSocket - if (WebSocketEnabled && questManager?.QuestList != null && questManager.QuestList.Count > 0) - { - var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - - // Find and stream priority quests (deduplicated by quest ID) - var priorityQuests = questManager.QuestList - .Where(q => IsHighPriorityQuest(q.Id)) - .GroupBy(q => q.Id) - .Select(g => g.First()) // Take first occurrence of each quest ID - .ToList(); - - foreach (var quest in priorityQuests) - { - try - { - string questName = questManager.GetFriendlyQuestName(quest.Id); - long timeRemaining = quest.ExpireTime - currentTime; - string countdown = FormatCountdown(timeRemaining); - - // Stream quest data - System.Threading.Tasks.Task.Run(() => WebSocket.SendQuestDataAsync(questName, countdown)); - } - catch (Exception) - { - // Silently handle individual quest streaming errors - } - } - } - } - catch (Exception) - { - // Silently handle quest streaming errors to avoid spam - } - } - - private static bool IsHighPriorityQuest(string questId) - { - return questId == "stipendtimer_0812" || // Changed from stipendtimer_monthly to stipendtimer_0812 - questId == "augmentationblankgemacquired" || - questId == "insatiableeaterjaw"; - } - - private static string FormatCountdown(long seconds) - { - if (seconds <= 0) - return "READY"; - - var timeSpan = TimeSpan.FromSeconds(seconds); - - if (timeSpan.TotalDays >= 1) - return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours:D2}h"; - else if (timeSpan.TotalHours >= 1) - return $"{timeSpan.Hours}h {timeSpan.Minutes:D2}m"; - else if (timeSpan.TotalMinutes >= 1) - return $"{timeSpan.Minutes}m {timeSpan.Seconds:D2}s"; - else - return $"{timeSpan.Seconds}s"; - } - #endregion - - private void InitializeForHotReload() - { - // This method handles initialization that depends on character being logged in - // Similar to LoginComplete but designed for hot reload scenarios - - WriteToChat("Mosswart Massacre hot reload initialization started!"); - - // 1. Initialize settings - CRITICAL first step - PluginSettings.Initialize(); - - // 2. Apply the values from settings - RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; - WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled; - RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled; - HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled; - TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled; - CharTag = PluginSettings.Instance.CharTag; - - // 3. Update UI with current settings - ViewManager.SetRareMetaToggleState(RareMetaEnabled); - ViewManager.RefreshSettingsFromConfig(); - - // 4. Restart services if they were enabled (stop first, then start) - if (TelemetryEnabled) - { - Telemetry.Stop(); // Stop existing - Telemetry.Start(); // Restart - } - - if (WebSocketEnabled) - { - WebSocket.Stop(); // Stop existing - WebSocket.Start(); // Restart - } - - if (HttpServerEnabled) - { - HttpCommandServer.Stop(); // Stop existing - HttpCommandServer.Start(); // Restart - } - - // 5. Initialize Harmony patches (only if not already done) - // Note: Harmony patches are global and don't need reinitialization - if (!DecalHarmonyClean.IsActive()) - { - try - { - bool success = DecalHarmonyClean.Initialize(); - if (success) - WriteToChat("[OK] Plugin message interception active"); - else - WriteToChat("[FAIL] Could not initialize message interception"); - } - catch (Exception ex) - { - WriteToChat($"[ERROR] Harmony initialization failed: {ex.Message}"); - } - } - - // 6. Reinitialize death tracking - totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths); - // Don't reset sessionDeaths - keep the current session count - - // 7. Reinitialize cached Prismatic Taper count - InitializePrismaticTaperCount(); - - WriteToChat("Hot reload initialization completed!"); - } - - private void InitializePrismaticTaperCount() - { - try - { - lastPrismaticCount = cachedPrismaticCount; - cachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper"); - - // Initialize tracking for existing tapers - trackedTaperContainers.Clear(); - lastKnownStackSizes.Clear(); - - foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory()) - { - if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase) && - IsPlayerOwnedContainer(wo.Container)) - { - int stackCount = wo.Values(LongValueKey.StackCount, 1); - trackedTaperContainers[wo.Id] = wo.Container; - lastKnownStackSizes[wo.Id] = stackCount; - } - } - - } - catch (Exception ex) - { - WriteToChat($"[TAPER] Error initializing count: {ex.Message}"); - cachedPrismaticCount = 0; - lastPrismaticCount = 0; - trackedTaperContainers.Clear(); - lastKnownStackSizes.Clear(); - } - } - - private bool IsPlayerOwnedContainer(int containerId) - { - try - { - // Check if it's the player's main inventory - if (containerId == CoreManager.Current.CharacterFilter.Id) - return true; - - // Check if it's one of the player's containers (side packs) - // Get the container object to verify it belongs to the player - WorldObject container = CoreManager.Current.WorldFilter[containerId]; - if (container != null && - container.ObjectClass == ObjectClass.Container && - container.Container == CoreManager.Current.CharacterFilter.Id) - { - return true; - } - - return false; - } - catch - { - return false; - } - } - - private void OnInventoryCreate(object sender, CreateObjectEventArgs e) - { - try - { - var item = e.New; - if (IsPlayerOwnedContainer(item.Container) && - item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase)) - { - lastPrismaticCount = cachedPrismaticCount; - int stackCount = item.Values(LongValueKey.StackCount, 1); - cachedPrismaticCount += stackCount; - int delta = cachedPrismaticCount - lastPrismaticCount; - - // Initialize tracking for this new taper - trackedTaperContainers[item.Id] = item.Container; - lastKnownStackSizes[item.Id] = stackCount; - - } - } - catch (Exception ex) - { - WriteToChat($"[TAPER] Error in OnInventoryCreate: {ex.Message}"); - } - } - - private void OnInventoryRelease(object sender, ReleaseObjectEventArgs e) - { - try - { - var item = e.Released; - if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase)) - { - // Check where this taper WAS before being released (not where it's going) - if (trackedTaperContainers.TryGetValue(item.Id, out int previousContainer)) - { - if (IsPlayerOwnedContainer(previousContainer)) - { - // This taper was in our inventory and is now being released - lastPrismaticCount = cachedPrismaticCount; - int stackCount = item.Values(LongValueKey.StackCount, 1); - cachedPrismaticCount -= stackCount; - } - - // Clean up tracking - trackedTaperContainers.Remove(item.Id); - lastKnownStackSizes.Remove(item.Id); - } - else - { - // Fallback: recalculate total count when untracked taper is released - lastPrismaticCount = cachedPrismaticCount; - cachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper"); - } - } - } - catch (Exception ex) - { - WriteToChat($"[TAPER] Error in OnInventoryRelease: {ex.Message}"); - } - } - - private void OnInventoryChange(object sender, ChangeObjectEventArgs e) - { - try - { - var item = e.Changed; - if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase)) - { - bool isInPlayerContainer = IsPlayerOwnedContainer(item.Container); - - // Track container location for release detection - if (isInPlayerContainer) - { - bool wasAlreadyTracked = trackedTaperContainers.ContainsKey(item.Id); - trackedTaperContainers[item.Id] = item.Container; - - // Handle stack size changes with pure delta math - int currentStack = item.Values(LongValueKey.StackCount, 1); - - // Check if this is a pickup from ground (item not previously tracked) - if (!wasAlreadyTracked) - { - // This is likely a pickup from ground - increment count - lastPrismaticCount = cachedPrismaticCount; - cachedPrismaticCount += currentStack; - } - else if (lastKnownStackSizes.TryGetValue(item.Id, out int previousStack)) - { - int stackDelta = currentStack - previousStack; - if (stackDelta != 0) - { - lastPrismaticCount = cachedPrismaticCount; - cachedPrismaticCount += stackDelta; - } - } - - lastKnownStackSizes[item.Id] = currentStack; - } - // Item is no longer in player containers - // DON'T clean up tracking here - let OnInventoryRelease handle cleanup - // This ensures tracking data is available for the Release event - } - } - catch (Exception ex) - { - WriteToChat($"[TAPER] Error in OnInventoryChange: {ex.Message}"); - } - } - - private async void OnSpawn(object sender, CreateObjectEventArgs e) - { - var mob = e.New; - if (mob.ObjectClass != ObjectClass.Monster) return; - - try - { - // Get DECAL coordinates - var decalCoords = mob.Coordinates(); - if (decalCoords == null) return; - - const string fmt = "F7"; - string ns = decalCoords.NorthSouth.ToString(fmt, CultureInfo.InvariantCulture); - string ew = decalCoords.EastWest.ToString(fmt, CultureInfo.InvariantCulture); - - // Get Z coordinate using RawCoordinates() for accurate world Z position - string zCoord = "0"; - try - { - var rawCoords = mob.RawCoordinates(); - if (rawCoords != null) - { - zCoord = rawCoords.Z.ToString("F2", CultureInfo.InvariantCulture); - } - else - { - // Fallback to player Z approximation if RawCoordinates fails - var playerCoords = Coordinates.Me; - if (Math.Abs(playerCoords.Z) > 0.1) - { - zCoord = playerCoords.Z.ToString("F2", CultureInfo.InvariantCulture); - } - } - } - catch - { - // Fallback to player Z approximation on error - try - { - var playerCoords = Coordinates.Me; - if (Math.Abs(playerCoords.Z) > 0.1) - { - zCoord = playerCoords.Z.ToString("F2", CultureInfo.InvariantCulture); - } - } - catch - { - zCoord = "0"; - } - } - - await WebSocket.SendSpawnAsync(ns, ew, zCoord, mob.Name); - } - catch (Exception ex) - { - PluginCore.WriteToChat($"[WS] Spawn send failed: {ex}"); - } - } - - private async void OnPortalDetected(object sender, CreateObjectEventArgs e) - { - var portal = e.New; - if (portal.ObjectClass != ObjectClass.Portal) return; - - try - { - // Get portal coordinates from DECAL - var decalCoords = portal.Coordinates(); - if (decalCoords == null) return; - - const string fmt = "F7"; - string ns = decalCoords.NorthSouth.ToString(fmt, CultureInfo.InvariantCulture); - string ew = decalCoords.EastWest.ToString(fmt, CultureInfo.InvariantCulture); - - // Get Z coordinate using RawCoordinates() for accurate world Z position - string zCoord = "0"; - try - { - var rawCoords = portal.RawCoordinates(); - if (rawCoords != null) - { - zCoord = rawCoords.Z.ToString("F2", CultureInfo.InvariantCulture); - } - else - { - // Fallback to player Z approximation if RawCoordinates fails - var playerCoords = Coordinates.Me; - if (Math.Abs(playerCoords.Z) > 0.1) - { - zCoord = playerCoords.Z.ToString("F2", CultureInfo.InvariantCulture); - } - } - } - catch - { - // Fallback to player Z approximation on error - try - { - var playerCoords = Coordinates.Me; - if (Math.Abs(playerCoords.Z) > 0.1) - { - zCoord = playerCoords.Z.ToString("F2", CultureInfo.InvariantCulture); - } - } - catch - { - zCoord = "0"; - } - } - - await WebSocket.SendPortalAsync(ns, ew, zCoord, portal.Name); - } - catch (Exception ex) - { - PluginCore.WriteToChat($"[PORTAL ERROR] {ex.Message}"); - PluginCore.WriteToChat($"[WS] Portal send failed: {ex}"); - } - } - - - private void OnDespawn(object sender, ReleaseObjectEventArgs e) - { - var mob = e.Released; - if (mob.ObjectClass != ObjectClass.Monster) return; - - - // var c = mob.Coordinates(); - // PluginCore.WriteToChat( - // $"[Despawn] {mob.Name} @ (NS={c.NorthSouth:F1}, EW={c.EastWest:F1})"); - } - - private async void AllChatText(object sender, ChatTextInterceptEventArgs e) - { - try - { - string cleaned = NormalizeChatLine(e.Text); - - // Send to WebSocket - await WebSocket.SendChatTextAsync(e.Color, cleaned); - - // Note: Plugin message analysis is now handled by Harmony patches - } - catch (Exception ex) - { - PluginCore.WriteToChat($"[WS] Chat send failed: {ex}"); - } - } - - private static string NormalizeChatLine(string raw) - { - if (string.IsNullOrEmpty(raw)) - return raw; - - // 1) Remove all <…> tags - var noTags = Regex.Replace(raw, "<[^>]+>", ""); - - // 2) Trim trailing newline or carriage-return - var trimmed = noTags.TrimEnd('\r', '\n'); - - // 3) Collapse multiple spaces into one - var collapsed = Regex.Replace(trimmed, @"[ ]{2,}", " "); - - return collapsed; - } - - private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e) - { - sessionDeaths++; - totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths); - } - - private void HandleServerCommand(CommandEnvelope env) - { - // This is called from WebSocket thread - queue for main thread execution - lock (pendingCommands) - { - pendingCommands.Enqueue(env.Command); - } - } - - private void ProcessPendingCommands(object sender, EventArgs e) - { - // This runs on the main UI thread via Windows Forms timer - string command = null; - - lock (pendingCommands) - { - if (pendingCommands.Count > 0) - command = pendingCommands.Dequeue(); - } - - if (command != null) - { - try - { - // Execute ALL WebSocket commands on main thread - fast and reliable - DispatchChatToBoxWithPluginIntercept(command); - } - catch (Exception ex) - { - WriteToChat($"[WS] Command execution error: {ex.Message}"); - } - } - } - - private void OnChatText(object sender, ChatTextInterceptEventArgs e) - { - try - { - - if (IsKilledByMeMessage(e.Text)) - { - totalKills++; - lastKillTime = DateTime.Now; - CalculateKillsPerInterval(); - ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); - } - - if (IsRareDiscoveryMessage(e.Text, out string rareText)) - { - rareCount++; - ViewManager.UpdateRareCount(rareCount); - - if (RareMetaEnabled) - { - Decal_DispatchOnChatCommand("/vt setmetastate loot_rare"); - } - - DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000); - // Fire and forget: we don't await, since sending is not critical and we don't want to block. - _ = WebSocket.SendRareAsync(rareText); - } - - if (e.Color == 18 && e.Text.EndsWith("!report\"")) - { - TimeSpan elapsed = DateTime.Now - statsStartTime; - string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}"; - WriteToChat($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}"); - MyHost.Actions.InvokeChatParser($"/a {reportMessage}"); - } - if (RemoteCommandsEnabled && e.Color == 18) - { - string characterName = Regex.Escape(CoreManager.Current.CharacterFilter.Name); - string pattern = $@"^\[Allegiance\].*Dunking Rares.*say[s]?, \""!do {characterName} (?.+)\""$"; - string tag = Regex.Escape(PluginCore.CharTag); - string patterntag = $@"^\[Allegiance\].*Dunking Rares.*say[s]?, \""!dot {tag} (?.+)\""$"; - - - var match = Regex.Match(e.Text, pattern); - var matchtag = Regex.Match(e.Text, patterntag); - - if (match.Success) - { - string command = match.Groups["command"].Value; - DispatchChatToBoxWithPluginIntercept(command); - DelayedCommandManager.AddDelayedCommand($"/a [Remote] Executing: {command}", 2000); - } - else if (matchtag.Success) - { - string command = matchtag.Groups["command"].Value; - DispatchChatToBoxWithPluginIntercept(command); - DelayedCommandManager.AddDelayedCommand($"/a [Remote] Executing: {command}", 2000); - } - - } - - - - - - - - - } - catch (Exception ex) - { - WriteToChat("Error processing chat message: " + ex.Message); - } - } - private void OnChatCommand(object sender, ChatParserInterceptEventArgs e) - { - try - { - if (e.Text.StartsWith("/mm", StringComparison.OrdinalIgnoreCase)) - { - e.Eat = true; // Prevent the message from showing in chat - HandleMmCommand(e.Text); - } - } - catch (Exception ex) - { - PluginCore.WriteToChat($"[Error] Failed to process /mm command: {ex.Message}"); - } - } - - private void UpdateStats(object sender, ElapsedEventArgs e) - { - try - { - // Update the elapsed time - TimeSpan elapsed = DateTime.Now - statsStartTime; - ViewManager.UpdateElapsedTime(elapsed); - - // Recalculate kill rates - CalculateKillsPerInterval(); - ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); - - } - catch (Exception ex) - { - WriteToChat("Error updating stats: " + ex.Message); - } - } - - private static void SendVitalsUpdate(object sender, ElapsedEventArgs e) - { - try - { - // Only send if WebSocket is enabled - if (!WebSocketEnabled) - return; - - // Collect vitals data - int currentHealth = CoreManager.Current.Actions.Vital[VitalType.CurrentHealth]; - int currentStamina = CoreManager.Current.Actions.Vital[VitalType.CurrentStamina]; - int currentMana = CoreManager.Current.Actions.Vital[VitalType.CurrentMana]; - - int maxHealth = CoreManager.Current.Actions.Vital[VitalType.MaximumHealth]; - int maxStamina = CoreManager.Current.Actions.Vital[VitalType.MaximumStamina]; - int maxMana = CoreManager.Current.Actions.Vital[VitalType.MaximumMana]; - - int vitae = CoreManager.Current.CharacterFilter.Vitae; - - // Create vitals data structure - var vitalsData = new - { - type = "vitals", - timestamp = DateTime.UtcNow.ToString("o"), - character_name = CoreManager.Current.CharacterFilter.Name, - health_current = currentHealth, - health_max = maxHealth, - health_percentage = maxHealth > 0 ? Math.Round((double)currentHealth / maxHealth * 100, 1) : 0, - stamina_current = currentStamina, - stamina_max = maxStamina, - stamina_percentage = maxStamina > 0 ? Math.Round((double)currentStamina / maxStamina * 100, 1) : 0, - mana_current = currentMana, - mana_max = maxMana, - mana_percentage = maxMana > 0 ? Math.Round((double)currentMana / maxMana * 100, 1) : 0, - vitae = vitae - }; - - // Send via WebSocket - _ = WebSocket.SendVitalsAsync(vitalsData); - } - catch (Exception ex) - { - WriteToChat($"Error sending vitals: {ex.Message}"); - } - } - - private void CalculateKillsPerInterval() - { - double minutesElapsed = (DateTime.Now - statsStartTime).TotalMinutes; - - if (minutesElapsed > 0) - { - killsPer5Min = (totalKills / minutesElapsed) * 5; - killsPerHour = (totalKills / minutesElapsed) * 60; - } - } - - private bool IsKilledByMeMessage(string text) - { - string[] killPatterns = new string[] - { - @"^You flatten (?.+)'s body with the force of your assault!$", - @"^You bring (?.+) to a fiery end!$", - @"^You beat (?.+) to a lifeless pulp!$", - @"^You smite (?.+) mightily!$", - @"^You obliterate (?.+)!$", - @"^You run (?.+) through!$", - @"^You reduce (?.+) to a sizzling, oozing mass!$", - @"^You knock (?.+) into next Morningthaw!$", - @"^You split (?.+) apart!$", - @"^You cleave (?.+) in twain!$", - @"^You slay (?.+) viciously enough to impart death several times over!$", - @"^You reduce (?.+) to a drained, twisted corpse!$", - @"^Your killing blow nearly turns (?.+) inside-out!$", - @"^Your attack stops (?.+) cold!$", - @"^Your lightning coruscates over (?.+)'s mortal remains!$", - @"^Your assault sends (?.+) to an icy death!$", - @"^You killed (?.+)!$", - @"^The thunder of crushing (?.+) is followed by the deafening silence of death!$", - @"^The deadly force of your attack is so strong that (?.+)'s ancestors feel it!$", - @"^(?.+)'s seared corpse smolders before you!$", - @"^(?.+) is reduced to cinders!$", - @"^(?.+) is shattered by your assault!$", - @"^(?.+) catches your attack, with dire consequences!$", - @"^(?.+) is utterly destroyed by your attack!$", - @"^(?.+) suffers a frozen fate!$", - @"^(?.+)'s perforated corpse falls before you!$", - @"^(?.+) is fatally punctured!$", - @"^(?.+)'s death is preceded by a sharp, stabbing pain!$", - @"^(?.+) is torn to ribbons by your assault!$", - @"^(?.+) is liquified by your attack!$", - @"^(?.+)'s last strength dissolves before you!$", - @"^Electricity tears (?.+) apart!$", - @"^Blistered by lightning, (?.+) falls!$", - @"^(?.+)'s last strength withers before you!$", - @"^(?.+) is dessicated by your attack!$", - @"^(?.+) is incinerated by your assault!$" - }; - - foreach (string pattern in killPatterns) - { - if (Regex.IsMatch(text, pattern)) - return true; - } - - return false; - } - private bool IsRareDiscoveryMessage(string text, out string rareTextOnly) - { - rareTextOnly = null; - - // Match pattern: " has discovered the !" - string pattern = @"^(?['A-Za-z ]+)\shas discovered the (?.*?)!$"; - Match match = Regex.Match(text, pattern); - - if (match.Success && match.Groups["name"].Value == CoreManager.Current.CharacterFilter.Name) - { - rareTextOnly = match.Groups["item"].Value; // just "Ancient Pickle" - return true; - } - - return false; - } - 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); - } - 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() - { - totalKills = 0; - rareCount = 0; - sessionDeaths = 0; // Reset session deaths only - statsStartTime = DateTime.Now; - killsPer5Min = 0; - killsPerHour = 0; - - WriteToChat($"Stats have been reset. Session deaths: {sessionDeaths}, Total deaths: {totalDeaths}"); - ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour); - ViewManager.UpdateRareCount(rareCount); - } - public static void ToggleRareMeta() - { - PluginSettings.Instance.RareMetaEnabled = !PluginSettings.Instance.RareMetaEnabled; - RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled; - ViewManager.SetRareMetaToggleState(RareMetaEnabled); - } - - [DllImport("Decal.dll")] - private static extern int DispatchOnChatCommand(ref IntPtr str, [MarshalAs(UnmanagedType.U4)] int target); - - public static bool Decal_DispatchOnChatCommand(string cmd) - { - IntPtr bstr = Marshal.StringToBSTR(cmd); - - try - { - bool eaten = (DispatchOnChatCommand(ref bstr, 1) & 0x1) > 0; - return eaten; - } - finally - { - Marshal.FreeBSTR(bstr); - } - } - public static void DispatchChatToBoxWithPluginIntercept(string cmd) - { - if (!Decal_DispatchOnChatCommand(cmd)) - CoreManager.Current.Actions.InvokeChatParser(cmd); - } - private void HandleMmCommand(string text) - { - // Remove the /mm prefix and trim extra whitespace - string[] args = text.Substring(3).Trim().Split(' '); - - if (args.Length == 0 || string.IsNullOrEmpty(args[0])) - { - WriteToChat("Usage: /mm . Try /mm help"); - return; - } - - string subCommand = args[0].ToLower(); - - switch (subCommand) - { - case "telemetry": - if (args.Length > 1) - { - if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) - { - TelemetryEnabled = true; - Telemetry.Start(); - PluginSettings.Instance.TelemetryEnabled = true; - WriteToChat("Telemetry streaming ENABLED."); - } - else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) - { - TelemetryEnabled = false; - Telemetry.Stop(); - PluginSettings.Instance.TelemetryEnabled = false; - WriteToChat("Telemetry streaming DISABLED."); - } - else - { - WriteToChat("Usage: /mm telemetry "); - } - } - else - { - WriteToChat("Usage: /mm telemetry "); - } - break; - case "ws": - if (args.Length > 1) - { - if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) - { - WebSocketEnabled = true; - WebSocket.Start(); - PluginSettings.Instance.WebSocketEnabled = true; - WriteToChat("WS streaming ENABLED."); - } - else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) - { - WebSocketEnabled = false; - WebSocket.Stop(); - PluginSettings.Instance.WebSocketEnabled = false; - WriteToChat("WS streaming DISABLED."); - } - else - { - WriteToChat("Usage: /mm ws "); - } - } - - else - { - WriteToChat("Usage: /mm ws "); - } - break; - case "help": - WriteToChat("Mosswart Massacre Commands:"); - WriteToChat("/mm report - Show current stats"); - WriteToChat("/mm loc - Show current location"); - WriteToChat("/mm telemetry - Telemetry streaming enable|disable"); - WriteToChat("/mm ws - Websocket streaming enable|disable"); - WriteToChat("/mm reset - Reset all counters"); - WriteToChat("/mm meta - Toggle rare meta state"); - WriteToChat("/mm http - Local http-command server enable|disable"); - WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable"); - WriteToChat("/mm getmetastate - Gets the current metastate"); - WriteToChat("/mm nextwp - Advance VTank to next waypoint"); - WriteToChat("/mm decalstatus - Check Harmony patch status (UtilityBelt version)"); - WriteToChat("/mm decaldebug - Enable/disable plugin message debug output + WebSocket streaming"); - WriteToChat("/mm harmonyraw - Show raw intercepted messages (debug output)"); - WriteToChat("/mm testprismatic - Test Prismatic Taper detection and icon lookup"); - WriteToChat("/mm deathstats - Show current death tracking statistics"); - WriteToChat("/mm testtaper - Test cached Prismatic Taper tracking"); - WriteToChat("/mm debugtaper - Show detailed taper tracking debug info"); - WriteToChat("/mm gui - Manually initialize/reinitialize GUI!!!"); - break; - case "report": - TimeSpan elapsed = DateTime.Now - statsStartTime; - string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}, Session Deaths: {sessionDeaths}, Total Deaths: {totalDeaths}"; - WriteToChat(reportMessage); - break; - case "getmetastate": - string metaState = VtankControl.VtGetMetaState(); - WriteToChat(metaState); - break; - - case "loc": - Coordinates here = Coordinates.Me; - var pos = Utils.GetPlayerPosition(); - WriteToChat($"Location: {here} (X={pos.X:F1}, Y={pos.Y:F1}, Z={pos.Z:F1})"); - break; - case "reset": - RestartStats(); - break; - case "meta": - RareMetaEnabled = !RareMetaEnabled; - WriteToChat($"Rare meta state is now {(RareMetaEnabled ? "ON" : "OFF")}"); - ViewManager.SetRareMetaToggleState(RareMetaEnabled); // <-- sync the UI - break; - case "http": - if (args.Length > 1) - { - if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) - { - PluginSettings.Instance.HttpServerEnabled = true; - HttpServerEnabled = true; - HttpCommandServer.Start(); - } - else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) - { - PluginSettings.Instance.HttpServerEnabled = false; - HttpServerEnabled = false; - HttpCommandServer.Stop(); - } - else - { - WriteToChat("Usage: /mm http "); - } - } - else - { - WriteToChat("Usage: /mm http "); - } - break; - - case "remotecommands": - if (args.Length > 1) - { - if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) - { - PluginSettings.Instance.RemoteCommandsEnabled = true; - RemoteCommandsEnabled = true; - WriteToChat("Remote command listening is now ENABLED."); - } - else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) - { - PluginSettings.Instance.RemoteCommandsEnabled = false; - RemoteCommandsEnabled = false; - WriteToChat("Remote command listening is now DISABLED."); - } - else - { - WriteToChat("Invalid remotecommands argument. Use 'enable' or 'disable'."); - } - } - else - { - WriteToChat("Usage: /mm remotecommands "); - } - break; - - case "nextwp": - double result = VtankControl.VtAdvanceWaypoint(); - if (result == 1) - { - WriteToChat("Advanced VTank to next waypoint."); - } - else - { - WriteToChat("Failed to advance VTank waypoint. Is VTank running?"); - } - break; - - case "vtanktest": - try - { - WriteToChat("Testing VTank interface..."); - WriteToChat($"VTank Instance: {(vTank.Instance != null ? "Found" : "NULL")}"); - WriteToChat($"VTank Type: {vTank.Instance?.GetType()?.Name ?? "NULL"}"); - WriteToChat($"NavCurrent: {vTank.Instance?.NavCurrent ?? -1}"); - WriteToChat($"NavNumPoints: {vTank.Instance?.NavNumPoints ?? -1}"); - WriteToChat($"NavType: {vTank.Instance?.NavType}"); - WriteToChat($"MacroEnabled: {vTank.Instance?.MacroEnabled}"); - } - catch (Exception ex) - { - WriteToChat($"VTank test error: {ex.Message}"); - } - break; - - case "decalstatus": - try - { - WriteToChat("=== Harmony Patch Status (UtilityBelt Pattern) ==="); - WriteToChat($"Patches Active: {DecalHarmonyClean.IsActive()}"); - WriteToChat($"Messages Intercepted: {DecalHarmonyClean.GetMessagesIntercepted()}"); - WriteToChat($"WebSocket Streaming: {(AggressiveChatStreamingEnabled && WebSocketEnabled ? "ACTIVE" : "INACTIVE")}"); - - // Test Harmony availability - WriteToChat("=== Harmony Version Status ==="); - try - { - var harmonyTest = Harmony.HarmonyInstance.Create("test.version.check"); - WriteToChat($"[OK] Harmony Available (ID: {harmonyTest.Id})"); - - // Check Harmony assembly version - var harmonyAssembly = typeof(Harmony.HarmonyInstance).Assembly; - WriteToChat($"[OK] Harmony Version: {harmonyAssembly.GetName().Version}"); - WriteToChat($"[OK] Harmony Location: {harmonyAssembly.Location}"); - } - catch (Exception harmonyEx) - { - WriteToChat($"[FAIL] Harmony Test Failed: {harmonyEx.Message}"); - } - } - catch (Exception ex) - { - WriteToChat($"Status check error: {ex.Message}"); - } - break; - - case "decaldebug": - if (args.Length > 1) - { - if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase)) - { - AggressiveChatStreamingEnabled = true; - WriteToChat("[OK] DECAL debug streaming ENABLED - will show captured messages + stream via WebSocket"); - } - else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase)) - { - AggressiveChatStreamingEnabled = false; - WriteToChat("[FAIL] DECAL debug streaming DISABLED - WebSocket streaming also disabled"); - } - else - { - WriteToChat("Usage: /mm decaldebug "); - } - } - else - { - WriteToChat("Usage: /mm decaldebug "); - } - break; - - - case "harmonyraw": - // Debug functionality removed - 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; - - case "testprismatic": - try - { - WriteToChat("=== FULL INVENTORY DUMP ==="); - var worldFilter = CoreManager.Current.WorldFilter; - var playerInv = CoreManager.Current.CharacterFilter.Id; - - WriteToChat("Listing ALL items in your main inventory:"); - int itemNum = 1; - - foreach (WorldObject item in worldFilter.GetByContainer(playerInv)) - { - if (!string.IsNullOrEmpty(item.Name)) - { - int stackCount = item.Values(LongValueKey.StackCount, 0); - WriteToChat($"{itemNum:D2}: '{item.Name}' (count: {stackCount}, icon: 0x{item.Icon:X}, class: {item.ObjectClass})"); - itemNum++; - - // Highlight anything that might be a taper - string nameLower = item.Name.ToLower(); - if (nameLower.Contains("taper") || nameLower.Contains("prismatic") || - nameLower.Contains("prism") || nameLower.Contains("component")) - { - WriteToChat($" *** POSSIBLE MATCH: '{item.Name}' ***"); - } - } - } - - WriteToChat($"=== Total items listed: {itemNum - 1} ==="); - - // Now test our utility functions on the found Prismatic Taper - WriteToChat("=== Testing Utility Functions on Prismatic Taper ==="); - var foundItem = Utils.FindItemByName("Prismatic Taper"); - if (foundItem != null) - { - WriteToChat($"SUCCESS! Found: '{foundItem.Name}'"); - WriteToChat($"Utils.GetItemStackSize: {Utils.GetItemStackSize("Prismatic Taper")}"); - WriteToChat($"Utils.GetItemIcon: 0x{Utils.GetItemIcon("Prismatic Taper"):X}"); - WriteToChat($"Utils.GetItemDisplayIcon: 0x{Utils.GetItemDisplayIcon("Prismatic Taper"):X}"); - WriteToChat("=== TELEMETRY WILL NOW WORK! ==="); - } - else - { - WriteToChat("ERROR: Still can't find Prismatic Taper with utility functions!"); - } - } - catch (Exception ex) - { - WriteToChat($"Search error: {ex.Message}"); - } - break; - - case "deathstats": - try - { - WriteToChat("=== Death Tracking Statistics ==="); - WriteToChat($"Session Deaths: {sessionDeaths}"); - WriteToChat($"Total Deaths: {totalDeaths}"); - - // Get current character death count to verify sync - int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths); - WriteToChat($"Character Property NumDeaths: {currentCharDeaths}"); - - if (currentCharDeaths != totalDeaths) - { - WriteToChat($"[WARNING] Death count sync issue detected!"); - WriteToChat($"Updating totalDeaths from {totalDeaths} to {currentCharDeaths}"); - totalDeaths = currentCharDeaths; - } - - WriteToChat("Death tracking is active and will increment on character death."); - } - catch (Exception ex) - { - WriteToChat($"Death stats error: {ex.Message}"); - } - break; - - case "testdeath": - try - { - WriteToChat("=== Manual Death Test ==="); - WriteToChat($"Current sessionDeaths variable: {sessionDeaths}"); - WriteToChat($"Current totalDeaths variable: {totalDeaths}"); - - // Read directly from character property - int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths); - WriteToChat($"Character Property NumDeaths (43): {currentCharDeaths}"); - - // Manually increment session deaths for testing - sessionDeaths++; - WriteToChat($"Manually incremented sessionDeaths to: {sessionDeaths}"); - WriteToChat("Note: This doesn't simulate a real death, just tests the tracking variables."); - - // Check if death event is properly subscribed - WriteToChat($"Death event subscription check:"); - var deathEvent = typeof(Decal.Adapter.Wrappers.CharacterFilter).GetEvent("Death"); - WriteToChat($"Death event exists: {deathEvent != null}"); - } - catch (Exception ex) - { - WriteToChat($"Test death error: {ex.Message}"); - } - break; - - case "testtaper": - try - { - WriteToChat("=== Cached Taper Tracking Test ==="); - WriteToChat($"Cached Count: {cachedPrismaticCount}"); - WriteToChat($"Last Count: {lastPrismaticCount}"); - - // Compare with Utils function - int utilsCount = Utils.GetItemStackSize("Prismatic Taper"); - WriteToChat($"Utils Count: {utilsCount}"); - - if (cachedPrismaticCount == utilsCount) - { - WriteToChat("[OK] Cached count matches Utils count"); - } - else - { - WriteToChat($"[WARNING] Count mismatch! Cached: {cachedPrismaticCount}, Utils: {utilsCount}"); - WriteToChat("Refreshing cached count..."); - InitializePrismaticTaperCount(); - } - - WriteToChat("=== Container Analysis ==="); - int mainPackCount = 0; - int sidePackCount = 0; - int playerId = CoreManager.Current.CharacterFilter.Id; - - foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory()) - { - if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase)) - { - int stackCount = wo.Values(LongValueKey.StackCount, 1); - if (wo.Container == playerId) - { - mainPackCount += stackCount; - } - else - { - sidePackCount += stackCount; - } - } - } - - WriteToChat($"Main Pack Tapers: {mainPackCount}"); - WriteToChat($"Side Pack Tapers: {sidePackCount}"); - WriteToChat($"Total: {mainPackCount + sidePackCount}"); - - WriteToChat("=== Event System Status ==="); - WriteToChat($"Tracking {trackedTaperContainers.Count} taper stacks for delta detection"); - WriteToChat($"Known stack sizes: {lastKnownStackSizes.Count} items"); - WriteToChat("Pure delta tracking - NO expensive inventory scans during events!"); - WriteToChat("Now tracks: consumption, drops, trades, container moves"); - WriteToChat("Try moving tapers between containers and casting spells!"); - } - catch (Exception ex) - { - WriteToChat($"Taper test error: {ex.Message}"); - } - break; - - case "debugtaper": - // Debug functionality removed - break; - - case "finditem": - if (args.Length > 1) - { - string itemName = string.Join(" ", args, 1, args.Length - 1).Trim('"'); - WriteToChat($"=== Searching for: '{itemName}' ==="); - - var foundItem = Utils.FindItemByName(itemName); - if (foundItem != null) - { - WriteToChat($"FOUND: '{foundItem.Name}'"); - WriteToChat($"Count: {foundItem.Values(LongValueKey.StackCount, 0)}"); - WriteToChat($"Icon: 0x{foundItem.Icon:X}"); - WriteToChat($"Display Icon: 0x{(foundItem.Icon + 0x6000000):X}"); - WriteToChat($"Object Class: {foundItem.ObjectClass}"); - } - else - { - WriteToChat($"NOT FOUND: '{itemName}'"); - WriteToChat("Make sure the name is exactly as it appears in-game."); - } - } - else - { - WriteToChat("Usage: /mm finditem \"Item Name\""); - WriteToChat("Example: /mm finditem \"Prismatic Taper\""); - } - break; - - - default: - WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help"); - break; - } - } - - - } -} - -