Compare commits
75 commits
master
...
spawn-dete
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d722deeefc | ||
|
|
0d57c527dd | ||
|
|
d9a1ef9e68 | ||
|
|
c90a11fc29 | ||
|
|
2eb9a7773e | ||
|
|
5fe0f85369 | ||
|
|
bc68d29ba5 | ||
|
|
8b3c800b3f | ||
|
|
be7e8302cd | ||
|
|
6120966c05 | ||
|
|
e9925096f0 | ||
|
|
c174c143c6 | ||
|
|
553a2388d1 | ||
|
|
e9a113abdd | ||
|
|
ab425a04cc | ||
|
|
6b631c3fe8 | ||
|
|
130615c141 | ||
|
|
4ab0992979 | ||
|
|
741d17af0c | ||
|
|
52633e2a1a | ||
|
|
7cb917ce67 | ||
|
|
af98555052 | ||
|
|
7e80fff4b6 | ||
|
|
73ba7082d8 | ||
|
|
bb493febb4 | ||
|
|
91cd934878 | ||
|
|
31c9042ed3 | ||
|
|
f9644baf1e | ||
|
|
ecea5af243 | ||
|
|
1142a012ef | ||
|
|
57b2f0400e | ||
|
|
01151e679b | ||
|
|
2e6ac9553f | ||
|
|
a3ce9ce2df | ||
|
|
c61912607a | ||
|
|
8cf9a59061 | ||
|
|
fda5c0417e | ||
|
|
01c762d669 | ||
|
|
28bdf7f312 | ||
|
|
ebf6fd0bf7 | ||
|
|
e9b5378ba6 | ||
|
|
23e33599ca | ||
|
|
7eb98491d3 | ||
|
|
c7a684eacd | ||
|
|
b662e360a2 | ||
|
|
96b85ed226 | ||
|
|
591da42d36 | ||
|
|
8c43ed676c | ||
|
|
afabfdef0e | ||
|
|
79304baaad | ||
|
|
c05d6c9d1b | ||
|
|
1ddfc9fbdf | ||
|
|
1f85d9c6f0 | ||
|
|
037e5cd940 | ||
|
|
f3da44901f | ||
|
|
c3d158aabb | ||
|
|
985b69fe01 | ||
|
|
e68f2c9801 | ||
|
|
a070075c1f | ||
|
|
19442301bc | ||
|
|
052fc1b71e | ||
|
|
a91556c949 | ||
|
|
6fcfe5fc21 | ||
|
|
a0f40cf2cd | ||
|
|
f4ec57a44d | ||
|
|
0c539bc023 | ||
|
|
1e8e134593 | ||
|
|
de2057789a | ||
|
|
29fba4b7cb | ||
|
|
781a7767ee | ||
|
|
ff3cb69f98 | ||
|
|
9a6fa191a0 | ||
|
|
33fb228654 | ||
|
|
56b09f509a | ||
|
|
d2e9988bdd |
358 changed files with 47402 additions and 7695 deletions
22
.claude/settings.local.json
Normal file
22
.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -361,3 +361,8 @@ MigrationBackup/
|
|||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
/UI_Creation_Manual.md
|
||||
/UI_Creation_VirindiViewService_Manual.md
|
||||
/MosswartMassacre/Unused
|
||||
/MosswartMassacre/Decal.Adapter.csproj
|
||||
/MosswartMassacre/Decal.Interop.Core.csproj
|
||||
|
|
|
|||
81
CLAUDE.md
Normal file
81
CLAUDE.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
- 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<Decal.Filters.FileService>();
|
||||
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
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
using VirindiViewService;
|
||||
using VirindiViewService.Controls;
|
||||
|
||||
namespace GearCycler
|
||||
{
|
||||
[ComVisible(true)]
|
||||
[Guid("9b6a07e1-ae78-47f4-b09c-174f6a27d7a3")] // Replace with your own unique GUID if needed
|
||||
[FriendlyName("GearCycler")]
|
||||
public class GearCore : PluginBase
|
||||
{
|
||||
public HudView view;
|
||||
private HudButton btnCycle;
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
try
|
||||
{
|
||||
string xml = File.ReadAllText("ViewXML\\mainview.xml");
|
||||
view = HudView.ReadXmlLayout(xml);
|
||||
view.Visible = true;
|
||||
|
||||
btnCycle = (HudButton)view.Controls["btnCycle"];
|
||||
btnCycle.Hit += (s, e) =>
|
||||
{
|
||||
CoreManager.Current.Actions.AddChatText("[GearCycler] Button clicked!", 1);
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CoreManager.Current.Actions.AddChatText($"[GearCycler] Failed to load UI: {ex.Message}", 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
btnCycle?.Dispose();
|
||||
view?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{1293560E-2A56-417F-8116-8CE0420DC97C}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GearCycler</RootNamespace>
|
||||
<AssemblyName>GearCycler</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;VVS_REFERENCED;DECAL_INTEROP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Decal.Adapter">
|
||||
<HintPath>..\MosswartMassacre\lib\Decal.Adapter.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Core, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
<HintPath>..\MosswartMassacre\lib\Decal.Interop.Core.DLL</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Inject, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
<HintPath>..\MosswartMassacre\lib\Decal.Interop.Inject.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="VirindiViewService">
|
||||
<HintPath>..\MosswartMassacre\lib\VirindiViewService.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GearCore.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="ViewXML\mainView.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="mainView.xml" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("GearCycler")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("GearCycler")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("f5462318-d26a-4ab0-8981-80edd9ec9c99")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
63
GearCycler/Properties/Resources.Designer.cs
generated
63
GearCycler/Properties/Resources.Designer.cs
generated
|
|
@ -1,63 +0,0 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace GearCycler.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GearCycler.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<view icon="112" title="GearCycler" width="300" height="200">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<control progid="DecalControls.PushButton" name="btnCycle" left="10" top="10" width="150" height="30" text="Cycle Gear"/>
|
||||
</control>
|
||||
</view>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,386 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>VirindiViewService</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="F:VirindiViewService.WriteTextFormats.None">
|
||||
<summary>
|
||||
Implies Top and Left
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.HudViewDrawStyle">
|
||||
<summary>
|
||||
Provides theme elements, which can be drawn by controls.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudThemeElement">
|
||||
<summary>
|
||||
Displays an element from the current theme.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudControl">
|
||||
<summary>
|
||||
The base class for all Virindi Views controls.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.Initialize">
|
||||
<summary>
|
||||
Called after this control is added to a ControlGroup. This is when the Name and details have been set.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.AddChild(VirindiViewService.Controls.HudControl)">
|
||||
<summary>
|
||||
Add and initialize a child control of this control. The child may be removed by disposing it.
|
||||
</summary>
|
||||
<param name="ctrl"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.RemovedChild(VirindiViewService.Controls.HudControl)">
|
||||
<summary>
|
||||
Called when a child of this control is disposed.
|
||||
</summary>
|
||||
<param name="ch"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.Dispose">
|
||||
<summary>
|
||||
Recursively disposes all children and removes this control from the view, if it is initialized.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.MouseWheel(System.Drawing.Point,System.Int32)">
|
||||
<summary>
|
||||
Handles a mouse wheel event. Parent controls must pass this on to applicable children if necessary.
|
||||
</summary>
|
||||
<param name="pt"></param>
|
||||
<param name="amt"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.MouseDown(System.Drawing.Point)">
|
||||
<summary>
|
||||
Fires the MouseEvent event for mouse down, and sets this control as the focus control if CanTakeFocus is true.
|
||||
|
||||
Parent controls must pass this on to applicable children if necessary.
|
||||
</summary>
|
||||
<param name="pt"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.MouseUp(System.Drawing.Point,System.Drawing.Point)">
|
||||
<summary>
|
||||
Fires the MouseEvent event for mouse up as well as the Hit event.
|
||||
|
||||
Parent controls must pass this on to applicable children if necessary.
|
||||
</summary>
|
||||
<param name="pt"></param>
|
||||
<param name="orig"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.ExternalMouseUp(System.Drawing.Point)">
|
||||
<summary>
|
||||
Fired when the mousedown originated outside the current view. The base version of this method
|
||||
passes on the event to all children if the 'up' point is within its saved rect.
|
||||
</summary>
|
||||
<param name="pt">Mouseup point</param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.MouseMove(System.Drawing.Point)">
|
||||
<summary>
|
||||
Tracks mouseover and fires the MouseOverChange event, as well as the MouseEvent event for mouse move.
|
||||
|
||||
Parent controls must pass this on to applicable children if necessary.
|
||||
</summary>
|
||||
<param name="pt"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.RawKeyAction(System.Int16,System.Int32,System.Int32,System.Boolean@)">
|
||||
<summary>
|
||||
Parses a key message and fires the specific key event methods.
|
||||
|
||||
Key events are only sent to the control with focus.
|
||||
</summary>
|
||||
<param name="Msg"></param>
|
||||
<param name="WParam"></param>
|
||||
<param name="LParam"></param>
|
||||
<param name="Eat"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.DrawNow(VirindiViewService.DxTexture)">
|
||||
<summary>
|
||||
WARNING: ONLY A PARENT CONTROL SHOULD CALL THIS METHOD.
|
||||
|
||||
This method is overridden in derived controls to handle the actual control drawing. Overridden methods should call
|
||||
the base, draw, and recursively call this method on all child controls.
|
||||
</summary>
|
||||
<param name="iSavedTarget"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Controls.HudControl.SetClipRegion(System.Drawing.Rectangle,VirindiViewService.HudViewDrawStyle,VirindiViewService.DrawOptions,System.Drawing.Rectangle)">
|
||||
<summary>
|
||||
WARNING: ONLY A PARENT CONTROL SHOULD CALL THIS METHOD.
|
||||
|
||||
Notifies a control of changed saved draw options. This method saves its parameters in the Savedxxx properties.
|
||||
Parent controls should override this method and recursively notify children of their new draw options, altering
|
||||
their pClipRegion to reflect their new position in the View.
|
||||
|
||||
This base method also fires the DrawStateChange and ThemeChanged events.
|
||||
</summary>
|
||||
<param name="pClipRegion">This control's area, relative to the view area.</param>
|
||||
<param name="pStyle">The theme applied to this control.</param>
|
||||
<param name="pContext">The context of this control, eg. inside a listbox.</param>
|
||||
<param name="pViewRect">The position of the View, in game window coordinates.</param>
|
||||
</member>
|
||||
<member name="P:VirindiViewService.Controls.HudControl.CanDraw">
|
||||
<summary>
|
||||
WARNING: ONLY A PARENT CONTROL SHOULD SET THIS PROPERTY.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:VirindiViewService.Controls.HudControl.XMLAttributes">
|
||||
<summary>
|
||||
List of XmlAttributes present on the XmlNode that was used to construct this control, if the control was loaded from XML. Otherwise, empty.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:VirindiViewService.Controls.HudControl.XMLNode">
|
||||
<summary>
|
||||
The XmlNode used to construct this control, if the control was loaded from XML. Otherwise, null.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:VirindiViewService.Controls.HudControl.InternalName">
|
||||
<summary>
|
||||
The name that this control will be initialized with.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudConsole">
|
||||
<summary>
|
||||
A multiline uneditable scrolling text box.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudPictureBox">
|
||||
<summary>
|
||||
A single image control.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudImageButton">
|
||||
<summary>
|
||||
A button using custom images.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:MyClasses.HashedList`1">
|
||||
<summary>
|
||||
A doubly-linked list with a Dictionary index. Duplicate items are not allowed.
|
||||
-Add is O(1)
|
||||
-Contains is O(1)
|
||||
-Remove is O(1)
|
||||
-Get/set by index is O(n)
|
||||
-Insert is O(n)
|
||||
-RemoveAt is O(n)
|
||||
Additionally, a cached pointer (with associated index) is kept pointing to the last used index item.
|
||||
When looking up an item by index, the list is walked from the head, tail, or cached index pointer.
|
||||
Thus, doing multiple operations in index order is O(1) even without an enumerator.
|
||||
</summary>
|
||||
<typeparam name="T"></typeparam>
|
||||
</member>
|
||||
<member name="M:MyClasses.HashedList`1.RunToIndex(System.Int32)">
|
||||
<summary>
|
||||
This method gets the node corresponding to a particular index. To get there,
|
||||
the list is traversed from the head, tail, or cached index pointer (if valid).
|
||||
</summary>
|
||||
<param name="ind"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudBrowser">
|
||||
<summary>
|
||||
Web browser control, using Awesomium (free license version)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudHScrollBar">
|
||||
<summary>
|
||||
A horizontal scrollbar.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Ciper.AC.AC_Text">
|
||||
<summary>
|
||||
Summary description for ByteCursor.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudCheckBox">
|
||||
<summary>
|
||||
A checkbox with optional associated text. Uses its parent to provide the background.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudTextBox">
|
||||
<summary>
|
||||
A single-line text input box.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.IElementRenderer.Measure(VirindiViewService.HudViewDrawStyle,System.Drawing.Size)">
|
||||
<summary>
|
||||
Called before render so the required size of the new target area can be calculated.
|
||||
The returned value is the size of the desired draw area, not including outer borders and
|
||||
style-dependent padding. This size must be less than or equal to MaximumSize in each dimension.
|
||||
</summary>
|
||||
<param name="style"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.IElementRenderer.Render(VirindiViewService.IRenderTarget,System.Drawing.Rectangle,VirindiViewService.HudViewDrawStyle)">
|
||||
<summary>
|
||||
Draw this element. When this is called, the background and borders will already have been drawn, and
|
||||
target will already be in BeginRender. This method should leave the target in render mode.
|
||||
</summary>
|
||||
<param name="target"></param>
|
||||
<param name="drawregion"></param>
|
||||
<param name="style"></param>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.cTipStringRenderer">
|
||||
<summary>
|
||||
A renderer for string-only tooltips.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:MyClasses.HashedSet`1">
|
||||
<summary>
|
||||
Represents an unordered set of items. Duplicates are not allowed.
|
||||
(This is really just a dictionary which only holds keys.)
|
||||
Should be used when a collection of non-duplicate items is needed and
|
||||
the order doesn't matter.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudTabView">
|
||||
<summary>
|
||||
A series of titled tabs along the top, each one having an associated control which appears
|
||||
on the bottom when its tab is enabled.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudProgressBar">
|
||||
<summary>
|
||||
A progressbar.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudButton">
|
||||
<summary>
|
||||
A regular pushbutton-style control.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Service.Game_D3DBeginSceneOriginal">
|
||||
<summary>
|
||||
Calls the non-hooked IDirect3DDevice9::BeginScene function. When rendering inside a VVS view or texture, use DxTexture.BeginRender() instead.
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.Service.Game_D3DEndSceneOriginal">
|
||||
<summary>
|
||||
Calls the non-hooked IDirect3DDevice9::EndScene function. When rendering inside a VVS view or texture, use DxTexture.EndRender() instead.
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="P:VirindiViewService.Service.HudBarInstance">
|
||||
<summary>
|
||||
Gets the current instance of the VVS bar.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudChatbox">
|
||||
<summary>
|
||||
A console containing game chat.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.DxTexture.BeginRender(System.Boolean,System.Boolean,System.Int32,System.Int32,System.Int32)">
|
||||
<summary>
|
||||
Initializes Direct3D drawing and sets the rendertarget to this texture. Calls to this method should be minimized to improve performance. DxTexture.EndRender() must be called after calling this method.
|
||||
</summary>
|
||||
<param name="AlphaTestEnable"></param>
|
||||
<param name="SeparateAlphaEnable"></param>
|
||||
<param name="SourceBlendAlpha"></param>
|
||||
<param name="DestinationBlendAlpha"></param>
|
||||
<param name="BlendOperation"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.DxTexture.EndRender">
|
||||
<summary>
|
||||
Ends Direct3D rendering and resets the rendertarget. Must be called after DxTexture.BeginRender().
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.DxTexture.DrawLine(System.Drawing.PointF,System.Drawing.PointF,System.Drawing.Color,System.Single)">
|
||||
<summary>
|
||||
Note: Before use, FlushSprite() may need to be called to ensure correct ordering.
|
||||
</summary>
|
||||
<param name="p1"></param>
|
||||
<param name="p2"></param>
|
||||
<param name="color"></param>
|
||||
<param name="width"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.DxTexture.DXDrawUserPrimitives(Microsoft.DirectX.Direct3D.PrimitiveType,System.Int32,System.Object,Microsoft.DirectX.Direct3D.VertexFormats)">
|
||||
<summary>
|
||||
Note: Before use, you must call BeginUserDrawOperation().
|
||||
</summary>
|
||||
<param name="ptype"></param>
|
||||
<param name="count"></param>
|
||||
<param name="data"></param>
|
||||
<param name="vertexformat"></param>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.DxTexture.DXDrawUserPrimitives(Microsoft.DirectX.Direct3D.PrimitiveType,System.Int32,System.Object,Microsoft.DirectX.Direct3D.VertexFormats,VirindiViewService.DxTexture)">
|
||||
<summary>
|
||||
Note: Before use, you must call BeginUserDrawOperation().
|
||||
</summary>
|
||||
<param name="ptype"></param>
|
||||
<param name="count"></param>
|
||||
<param name="data"></param>
|
||||
<param name="vertexformat"></param>
|
||||
<param name="texture"></param>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudList">
|
||||
<summary>
|
||||
A vertically scrolling list, containing a number of rows and columns. Every row
|
||||
has the same number and types of columns. Each column contains a specified control type.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudImageStack">
|
||||
<summary>
|
||||
A number of images on top of each other, which always draw in the proper order.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudStaticText">
|
||||
<summary>
|
||||
A simple text display control. Uses its parent to provide the background.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudFixedLayout">
|
||||
<summary>
|
||||
A container for multiple controls with set locations and sizes within.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudCombo">
|
||||
<summary>
|
||||
A dropdown list.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.ContextMenu`1.Show(System.Drawing.Point)">
|
||||
<summary>
|
||||
If the context menu is not visible, it is created at the specified point.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:VirindiViewService.ContextMenu`1.Show(System.Drawing.Point,VirindiViewService.HudViewDrawStyle)">
|
||||
<summary>
|
||||
If the context menu is not visible, it is created at the specified point with the specified theme.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.TooltipSystem.cTooltipInfo">
|
||||
<summary>
|
||||
Provides information about an associated tooltip.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:VirindiViewService.TooltipSystem.cTooltipInfo.Control">
|
||||
<summary>
|
||||
The HudControl that the tip is attached to.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:VirindiViewService.TooltipSystem.cTooltipInfo.Text">
|
||||
<summary>
|
||||
Deprecated.
|
||||
Returns the text associated with a tooltip only if the tip contains a cStringRenderer.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudVScrollBar">
|
||||
<summary>
|
||||
A vertical scrollbar.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudHSlider">
|
||||
<summary>
|
||||
A horizontal slider.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:VirindiViewService.Controls.HudEmulator">
|
||||
<summary>
|
||||
A control that allows easy access to underlying draw methods.
|
||||
</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<View>
|
||||
<HudButton Name="btnCycle" Text="Cycle Gear" Location="10,10" Size="120,30" />
|
||||
</View>
|
||||
264
MosswartMassacre.Loader/LoaderCore.cs
Normal file
264
MosswartMassacre.Loader/LoaderCore.cs
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
using Decal.Adapter;
|
||||
|
||||
namespace MosswartMassacre.Loader
|
||||
{
|
||||
[FriendlyName("MosswartMassacre.Loader")]
|
||||
public class LoaderCore : FilterBase
|
||||
{
|
||||
private Assembly pluginAssembly;
|
||||
private Type pluginType;
|
||||
private object pluginInstance;
|
||||
private FileSystemWatcher pluginWatcher;
|
||||
private bool isSubscribedToRenderFrame = false;
|
||||
private bool needsReload;
|
||||
|
||||
public static string PluginAssemblyNamespace => "MosswartMassacre";
|
||||
public static string PluginAssemblyName => $"{PluginAssemblyNamespace}.dll";
|
||||
public static string PluginAssemblyGuid => "{8C97E839-4D05-4A5F-B0C8-E8E778654322}";
|
||||
|
||||
public static bool IsPluginLoaded { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Assembly directory (contains both loader and plugin dlls)
|
||||
/// </summary>
|
||||
public static string AssemblyDirectory => System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(LoaderCore)).Location);
|
||||
|
||||
public DateTime LastDllChange { get; private set; }
|
||||
|
||||
#region Event Handlers
|
||||
protected override void Startup()
|
||||
{
|
||||
try
|
||||
{
|
||||
Core.PluginInitComplete += Core_PluginInitComplete;
|
||||
Core.PluginTermComplete += Core_PluginTermComplete;
|
||||
Core.FilterInitComplete += Core_FilterInitComplete;
|
||||
|
||||
// Set up assembly resolution for hot-loaded plugin dependencies
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
|
||||
// watch the AssemblyDirectory for any .dll file changes
|
||||
pluginWatcher = new FileSystemWatcher();
|
||||
pluginWatcher.Path = AssemblyDirectory;
|
||||
pluginWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
||||
pluginWatcher.Filter = "*.dll";
|
||||
pluginWatcher.Changed += PluginWatcher_Changed;
|
||||
pluginWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void Core_FilterInitComplete(object sender, EventArgs e)
|
||||
{
|
||||
Core.EchoFilter.ClientDispatch += EchoFilter_ClientDispatch;
|
||||
}
|
||||
|
||||
private void EchoFilter_ClientDispatch(object sender, NetworkMessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Login_SendEnterWorldRequest
|
||||
if (e.Message.Type == 0xF7C8)
|
||||
{
|
||||
//EnsurePluginIsDisabledInRegistry();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void Core_PluginInitComplete(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadPluginAssembly();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void Core_PluginTermComplete(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
UnloadPluginAssembly();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
try
|
||||
{
|
||||
Core.PluginInitComplete -= Core_PluginInitComplete;
|
||||
Core.PluginTermComplete -= Core_PluginTermComplete;
|
||||
Core.FilterInitComplete -= Core_FilterInitComplete;
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
|
||||
UnloadPluginAssembly();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void Core_RenderFrame(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPluginLoaded && needsReload && DateTime.UtcNow - LastDllChange > TimeSpan.FromSeconds(1))
|
||||
{
|
||||
needsReload = false;
|
||||
Core.RenderFrame -= Core_RenderFrame;
|
||||
isSubscribedToRenderFrame = false;
|
||||
LoadPluginAssembly();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginWatcher_Changed(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Only reload if it's the main plugin DLL
|
||||
if (e.Name == PluginAssemblyName)
|
||||
{
|
||||
LastDllChange = DateTime.UtcNow;
|
||||
needsReload = true;
|
||||
|
||||
if (!isSubscribedToRenderFrame)
|
||||
{
|
||||
isSubscribedToRenderFrame = true;
|
||||
Core.RenderFrame += Core_RenderFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Extract just the assembly name (without version info)
|
||||
string assemblyName = args.Name.Split(',')[0] + ".dll";
|
||||
string assemblyPath = System.IO.Path.Combine(AssemblyDirectory, assemblyName);
|
||||
|
||||
// If the dependency exists in our plugin directory, load it
|
||||
if (File.Exists(assemblyPath))
|
||||
{
|
||||
return Assembly.LoadFrom(assemblyPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"AssemblyResolve failed for {args.Name}: {ex.Message}");
|
||||
}
|
||||
|
||||
// Return null to let default resolution continue
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Plugin Loading/Unloading
|
||||
internal void LoadPluginAssembly()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPluginLoaded)
|
||||
{
|
||||
UnloadPluginAssembly();
|
||||
try
|
||||
{
|
||||
CoreManager.Current.Actions.AddChatText($"[MosswartMassacre] Reloading {PluginAssemblyName}", 5);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
pluginAssembly = Assembly.Load(File.ReadAllBytes(System.IO.Path.Combine(AssemblyDirectory, PluginAssemblyName)));
|
||||
pluginType = pluginAssembly.GetType($"{PluginAssemblyNamespace}.PluginCore");
|
||||
pluginInstance = Activator.CreateInstance(pluginType);
|
||||
|
||||
// Set the AssemblyDirectory property if it exists
|
||||
var assemblyDirProperty = pluginType.GetProperty("AssemblyDirectory", BindingFlags.Public | BindingFlags.Static);
|
||||
assemblyDirProperty?.SetValue(null, AssemblyDirectory);
|
||||
|
||||
// Set the IsHotReload flag if it exists
|
||||
var isHotReloadProperty = pluginType.GetProperty("IsHotReload", BindingFlags.Public | BindingFlags.Static);
|
||||
isHotReloadProperty?.SetValue(null, true);
|
||||
|
||||
// The original template doesn't set up Host - it just calls Startup
|
||||
// The plugin should use CoreManager.Current.Actions instead of MyHost for hot reload scenarios
|
||||
// We'll set a flag so the plugin knows it's being hot loaded
|
||||
|
||||
// Call Startup method
|
||||
var startupMethod = pluginType.GetMethod("Startup", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
startupMethod.Invoke(pluginInstance, new object[] { });
|
||||
|
||||
IsPluginLoaded = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnloadPluginAssembly()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pluginInstance != null && pluginType != null)
|
||||
{
|
||||
MethodInfo shutdownMethod = pluginType.GetMethod("Shutdown", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
shutdownMethod.Invoke(pluginInstance, null);
|
||||
pluginInstance = null;
|
||||
pluginType = null;
|
||||
pluginAssembly = null;
|
||||
}
|
||||
IsPluginLoaded = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log(ex);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void Log(Exception ex)
|
||||
{
|
||||
Log(ex.ToString());
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
File.AppendAllText(System.IO.Path.Combine(AssemblyDirectory, "loader_log.txt"), $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}\n");
|
||||
try
|
||||
{
|
||||
CoreManager.Current.Actions.AddChatText($"[MosswartMassacre.Loader] {message}", 3);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
37
MosswartMassacre.Loader/MosswartMassacre.Loader.csproj
Normal file
37
MosswartMassacre.Loader/MosswartMassacre.Loader.csproj
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Version>1.0.0</Version>
|
||||
<LangVersion>8</LangVersion>
|
||||
<ProjectGuid>{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}</ProjectGuid>
|
||||
<RootNamespace>MosswartMassacre.Loader</RootNamespace>
|
||||
<AssemblyName>MosswartMassacre.Loader</AssemblyName>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<OutputPath>..\MosswartMassacre\bin\Debug\</OutputPath>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<OutputPath>..\MosswartMassacre\bin\Release\</OutputPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Decal.Adapter">
|
||||
<HintPath>..\MosswartMassacre\lib\Decal.Adapter.dll</HintPath>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Core, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Decal 3.0\.NET 4.0 PIA\Decal.Interop.Core.DLL</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
3
MosswartMassacre/.claude/settings.local.json
Normal file
3
MosswartMassacre/.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"enableAllProjectMcpServers": false
|
||||
}
|
||||
375
MosswartMassacre/CLAUDE.md
Normal file
375
MosswartMassacre/CLAUDE.md
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
# 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 `<CharacterName>.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
|
||||
|
||||
---
|
||||
1346
MosswartMassacre/ChestLooter.cs
Normal file
1346
MosswartMassacre/ChestLooter.cs
Normal file
File diff suppressed because it is too large
Load diff
122
MosswartMassacre/ChestLooterSettings.cs
Normal file
122
MosswartMassacre/ChestLooterSettings.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings for the Chest Looter feature
|
||||
/// These settings are persisted per-character via PluginSettings
|
||||
/// </summary>
|
||||
public class ChestLooterSettings
|
||||
{
|
||||
// Target configuration
|
||||
public string ChestName { get; set; } = "";
|
||||
public string KeyName { get; set; } = "";
|
||||
|
||||
// Feature toggles
|
||||
public bool Enabled { get; set; } = false;
|
||||
public bool EnableChests { get; set; } = true;
|
||||
public bool AutoSalvageAfterLooting { get; set; } = false;
|
||||
public bool JumpWhenLooting { get; set; } = false;
|
||||
public bool BlockVtankMelee { get; set; } = false;
|
||||
public bool TestMode { get; set; } = false;
|
||||
public bool VerboseLogging { get; set; } = false;
|
||||
|
||||
// Timing and retry settings
|
||||
public int DelaySpeed { get; set; } = 1000; // Delay for unlock/open/close in ms
|
||||
public int OverallSpeed { get; set; } = 100; // Overall looter tick rate in ms
|
||||
public int MaxUnlockAttempts { get; set; } = 10; // Max attempts to unlock chest
|
||||
public int MaxOpenAttempts { get; set; } = 10; // Max attempts to open chest
|
||||
public int AttemptsBeforeBlacklisting { get; set; } = 500; // Item loot attempts before giving up
|
||||
|
||||
// Jump looting settings
|
||||
public int JumpHeight { get; set; } = 100; // Jump height (full bar is 1000)
|
||||
|
||||
// UI state
|
||||
public bool ShowChestLooterTab { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with default values
|
||||
/// </summary>
|
||||
public ChestLooterSettings()
|
||||
{
|
||||
// All defaults set via property initializers above
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate settings and apply constraints
|
||||
/// </summary>
|
||||
public void Validate()
|
||||
{
|
||||
// Ensure OverallSpeed isn't too fast (can cause issues)
|
||||
if (OverallSpeed < 100)
|
||||
OverallSpeed = 100;
|
||||
|
||||
// Ensure delays are reasonable
|
||||
if (DelaySpeed < 500)
|
||||
DelaySpeed = 500;
|
||||
|
||||
// Ensure attempt limits are positive
|
||||
if (MaxUnlockAttempts < 1)
|
||||
MaxUnlockAttempts = 1;
|
||||
if (MaxOpenAttempts < 1)
|
||||
MaxOpenAttempts = 1;
|
||||
if (AttemptsBeforeBlacklisting < 1)
|
||||
AttemptsBeforeBlacklisting = 1;
|
||||
|
||||
// Clamp jump height to reasonable range
|
||||
if (JumpHeight < 0)
|
||||
JumpHeight = 0;
|
||||
if (JumpHeight > 1000)
|
||||
JumpHeight = 1000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset all settings to default values
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
ChestName = "";
|
||||
KeyName = "";
|
||||
Enabled = false;
|
||||
EnableChests = true;
|
||||
AutoSalvageAfterLooting = false;
|
||||
JumpWhenLooting = false;
|
||||
BlockVtankMelee = false;
|
||||
TestMode = false;
|
||||
VerboseLogging = false;
|
||||
DelaySpeed = 1000;
|
||||
OverallSpeed = 100;
|
||||
MaxUnlockAttempts = 10;
|
||||
MaxOpenAttempts = 10;
|
||||
AttemptsBeforeBlacklisting = 500;
|
||||
JumpHeight = 100;
|
||||
ShowChestLooterTab = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy of these settings
|
||||
/// </summary>
|
||||
public ChestLooterSettings Clone()
|
||||
{
|
||||
return new ChestLooterSettings
|
||||
{
|
||||
ChestName = this.ChestName,
|
||||
KeyName = this.KeyName,
|
||||
Enabled = this.Enabled,
|
||||
EnableChests = this.EnableChests,
|
||||
AutoSalvageAfterLooting = this.AutoSalvageAfterLooting,
|
||||
JumpWhenLooting = this.JumpWhenLooting,
|
||||
BlockVtankMelee = this.BlockVtankMelee,
|
||||
TestMode = this.TestMode,
|
||||
VerboseLogging = this.VerboseLogging,
|
||||
DelaySpeed = this.DelaySpeed,
|
||||
OverallSpeed = this.OverallSpeed,
|
||||
MaxUnlockAttempts = this.MaxUnlockAttempts,
|
||||
MaxOpenAttempts = this.MaxOpenAttempts,
|
||||
AttemptsBeforeBlacklisting = this.AttemptsBeforeBlacklisting,
|
||||
JumpHeight = this.JumpHeight,
|
||||
ShowChestLooterTab = this.ShowChestLooterTab
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
35
MosswartMassacre/ClientTelemetry.cs
Normal file
35
MosswartMassacre/ClientTelemetry.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System;
|
||||
|
||||
public class ClientTelemetry
|
||||
{
|
||||
private readonly Process _proc;
|
||||
|
||||
public ClientTelemetry()
|
||||
{
|
||||
_proc = Process.GetCurrentProcess();
|
||||
}
|
||||
|
||||
/// <summary>Working-set memory in bytes.</summary>
|
||||
public long MemoryBytes => _proc.WorkingSet64;
|
||||
|
||||
/// <summary>Total open handles.</summary>
|
||||
public int HandleCount => _proc.HandleCount;
|
||||
|
||||
/// <summary>CPU utilisation (%) averaged over <paramref name="sampleMs"/>.</summary>
|
||||
public float GetCpuUsage(int sampleMs = 500)
|
||||
{
|
||||
// you can keep your PerformanceCounter variant, but here’s a simpler PID-based way:
|
||||
var startCpu = _proc.TotalProcessorTime;
|
||||
var start = DateTime.UtcNow;
|
||||
Thread.Sleep(sampleMs);
|
||||
var endCpu = _proc.TotalProcessorTime;
|
||||
var end = DateTime.UtcNow;
|
||||
|
||||
// CPU‐time used across all cores:
|
||||
var cpuMs = (endCpu - startCpu).TotalMilliseconds;
|
||||
var elapsedMs = (end - start).TotalMilliseconds * Environment.ProcessorCount;
|
||||
return (float)(cpuMs / elapsedMs * 100.0);
|
||||
}
|
||||
}
|
||||
557
MosswartMassacre/DecalHarmonyClean.cs
Normal file
557
MosswartMassacre/DecalHarmonyClean.cs
Normal file
|
|
@ -0,0 +1,557 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
using Harmony; // UtilityBelt's Harmony 1.2.0.1 uses old Harmony namespace
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Clean Harmony implementation using UtilityBelt's exact pattern
|
||||
/// Uses same namespace convention, same API, same Harmony package (Lib.Harmony 2.2.2)
|
||||
/// </summary>
|
||||
public static class DecalHarmonyClean
|
||||
{
|
||||
// Use UtilityBelt's namespace pattern
|
||||
private static readonly string harmonyNamespace = "com.mosswartmassacre.decal";
|
||||
private static HarmonyInstance harmonyDecal;
|
||||
private static bool patchesApplied = false;
|
||||
internal static int messagesIntercepted = 0;
|
||||
|
||||
// Debug logging for troubleshooting
|
||||
private static readonly Queue<string> debugLog = new Queue<string>();
|
||||
private const int MAX_DEBUG_ENTRIES = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize Harmony patches using UtilityBelt's exact pattern
|
||||
/// </summary>
|
||||
public static bool Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use UtilityBelt's exact pattern: lazy initialization check + new Harmony()
|
||||
if (harmonyDecal == null)
|
||||
harmonyDecal = HarmonyInstance.Create(harmonyNamespace + ".patches");
|
||||
|
||||
// Apply patches using UtilityBelt's approach
|
||||
ApplyDecalPatches();
|
||||
|
||||
return patchesApplied;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Only log critical initialization failures
|
||||
PluginCore.WriteToChat($"[DECAL] Critical initialization error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply patches to DECAL's AddChatText methods using UtilityBelt approach
|
||||
/// </summary>
|
||||
private static void ApplyDecalPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
// PATHWAY 1: Target HooksWrapper.AddChatText
|
||||
PatchHooksWrapper();
|
||||
|
||||
// PATHWAY 2: Target Host.Actions.AddChatText (what our plugin uses)
|
||||
PatchHostActions();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Only log if completely unable to apply any patches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch HooksWrapper.AddChatText methods
|
||||
/// </summary>
|
||||
private static void PatchHooksWrapper()
|
||||
{
|
||||
try
|
||||
{
|
||||
Type hooksWrapperType = typeof(HooksWrapper);
|
||||
|
||||
var allAddChatTextMethods = hooksWrapperType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => m.Name == "AddChatText" || m.Name == "AddChatTextRaw").ToArray();
|
||||
|
||||
foreach (var method in allAddChatTextMethods)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
string prefixMethodName = parameters.Length == 2 ? "AddChatTextPrefixHooks" :
|
||||
parameters.Length == 3 ? "AddChatTextPrefixHooks3" :
|
||||
"AddChatTextPrefixHooksGeneric";
|
||||
|
||||
try
|
||||
{
|
||||
ApplySinglePatch(method, prefixMethodName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch Host.Actions.AddChatText methods (what our plugin uses)
|
||||
/// </summary>
|
||||
private static void PatchHostActions()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PluginCore.MyHost?.Actions == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Type actionsType = PluginCore.MyHost.Actions.GetType();
|
||||
|
||||
// Check if Host.Actions is already a HooksWrapper (to avoid double patching)
|
||||
if (actionsType == typeof(HooksWrapper))
|
||||
{
|
||||
// PATHWAY 3: Try to patch at PluginHost level
|
||||
PatchPluginHost();
|
||||
return;
|
||||
}
|
||||
|
||||
var hostAddChatTextMethods = actionsType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => m.Name == "AddChatText" || m.Name == "AddChatTextRaw").ToArray();
|
||||
|
||||
foreach (var method in hostAddChatTextMethods)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
string prefixMethodName = parameters.Length == 3 ? "AddChatTextPrefixHost" :
|
||||
parameters.Length == 4 ? "AddChatTextPrefixHost4" :
|
||||
"AddChatTextPrefixHostGeneric";
|
||||
|
||||
try
|
||||
{
|
||||
ApplySinglePatch(method, prefixMethodName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// PATHWAY 3: Try to patch at PluginHost level
|
||||
PatchPluginHost();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try a different approach - patch the actual chat system or find global instances
|
||||
/// </summary>
|
||||
private static void PatchPluginHost()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to patch CoreManager.Current.Actions if it's different
|
||||
try
|
||||
{
|
||||
var coreActions = CoreManager.Current?.Actions;
|
||||
if (coreActions != null && coreActions != PluginCore.MyHost?.Actions)
|
||||
{
|
||||
var coreActionsMethods = coreActions.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => m.Name == "AddChatText" || m.Name == "AddChatTextRaw" || m.Name == "AddStatusText").ToArray();
|
||||
|
||||
foreach (var method in coreActionsMethods)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
try
|
||||
{
|
||||
string prefixMethodName = "AddChatTextPrefixCore" + parameters.Length;
|
||||
ApplySinglePatch(method, prefixMethodName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a single patch using UtilityBelt's method
|
||||
/// </summary>
|
||||
private static void ApplySinglePatch(MethodInfo targetMethod, string prefixMethodName)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get our prefix method
|
||||
var prefixMethod = typeof(DecalPatchMethods).GetMethod(prefixMethodName,
|
||||
BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
if (prefixMethod != null)
|
||||
{
|
||||
// Use UtilityBelt's exact approach
|
||||
harmonyDecal.Patch(targetMethod, new HarmonyMethod(prefixMethod));
|
||||
patchesApplied = true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List available methods for debugging
|
||||
/// </summary>
|
||||
private static void ListAvailableMethods(Type type)
|
||||
{
|
||||
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(m => m.Name == "AddChatText").ToArray();
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
string paramInfo = string.Join(", ", parameters.Select(p => p.ParameterType.Name));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean up patches using UtilityBelt's pattern
|
||||
/// </summary>
|
||||
public static void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (harmonyDecal != null && patchesApplied)
|
||||
{
|
||||
// Use UtilityBelt's cleanup pattern
|
||||
harmonyDecal.UnpatchAll(harmonyNamespace + ".patches");
|
||||
}
|
||||
patchesApplied = false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status checks
|
||||
/// </summary>
|
||||
public static bool IsActive() => patchesApplied && harmonyDecal != null;
|
||||
public static int GetMessagesIntercepted() => messagesIntercepted;
|
||||
|
||||
/// <summary>
|
||||
/// Add debug log entry
|
||||
/// </summary>
|
||||
public static void AddDebugLog(string message)
|
||||
{
|
||||
lock (debugLog)
|
||||
{
|
||||
debugLog.Enqueue($"{DateTime.Now:HH:mm:ss.fff} {message}");
|
||||
while (debugLog.Count > MAX_DEBUG_ENTRIES)
|
||||
{
|
||||
debugLog.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get debug log entries
|
||||
/// </summary>
|
||||
public static string[] GetDebugLog()
|
||||
{
|
||||
lock (debugLog)
|
||||
{
|
||||
return debugLog.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch methods for DECAL interception using UtilityBelt's approach
|
||||
/// </summary>
|
||||
public static class DecalPatchMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Prefix method for HooksWrapper.AddChatText(string, int) - intercepts plugin messages via HooksWrapper
|
||||
/// </summary>
|
||||
public static bool AddChatTextPrefixHooks(string text, int color)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Always increment to verify patch is working
|
||||
DecalHarmonyClean.messagesIntercepted++;
|
||||
|
||||
if (PluginCore.AggressiveChatStreamingEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
// Process ALL messages (including our own) for streaming
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
// Process the intercepted message
|
||||
ProcessInterceptedMessage(text, color, "HooksWrapper-2param");
|
||||
|
||||
// Silent operation - debug output removed
|
||||
}
|
||||
|
||||
// Always return true to let the original AddChatText continue
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Never let our interception break other plugins
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefix method for HooksWrapper.AddChatText(string, int, int) - 3-parameter version
|
||||
/// </summary>
|
||||
public static bool AddChatTextPrefixHooks3(string text, int color, int target)
|
||||
{
|
||||
try
|
||||
{
|
||||
DecalHarmonyClean.messagesIntercepted++;
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
ProcessInterceptedMessage(text, color, $"HooksWrapper-3param(target={target})");
|
||||
|
||||
// Silent operation - debug output removed
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic prefix method for any other HooksWrapper AddChatText overloads
|
||||
/// </summary>
|
||||
public static bool AddChatTextPrefixHooksGeneric(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
DecalHarmonyClean.messagesIntercepted++;
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
ProcessInterceptedMessage(text, 0, "HooksWrapper-generic");
|
||||
|
||||
// Silent operation - debug output removed
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process intercepted plugin messages
|
||||
/// </summary>
|
||||
private static void ProcessInterceptedMessage(string text, int color, string source)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Identify source plugin
|
||||
string sourcePlugin = IdentifySourcePlugin(text);
|
||||
|
||||
// Add timestamp
|
||||
var timestamp = DateTime.Now.ToString("HH:mm:ss");
|
||||
var fullMessage = $"{timestamp} [{sourcePlugin}] {text}";
|
||||
|
||||
// Debug logging
|
||||
|
||||
// Stream to WebSocket if both debug streaming AND WebSocket are enabled
|
||||
if (PluginCore.AggressiveChatStreamingEnabled && PluginCore.WebSocketEnabled)
|
||||
{
|
||||
Task.Run(() => WebSocket.SendChatTextAsync(color, text));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identify which plugin sent the message using UtilityBelt's patterns
|
||||
/// </summary>
|
||||
private static string IdentifySourcePlugin(string text)
|
||||
{
|
||||
// Known plugin prefixes
|
||||
if (text.StartsWith("[VTank]")) return "VTank";
|
||||
if (text.StartsWith("[UB]") || text.Contains("UtilityBelt")) return "UtilityBelt";
|
||||
if (text.StartsWith("[VGI]")) return "VGI";
|
||||
if (text.StartsWith("[VI]")) return "VirindiIntegrator";
|
||||
if (text.StartsWith("[GoArrow]")) return "GoArrow";
|
||||
if (text.StartsWith("[Meta]")) return "Meta";
|
||||
if (text.StartsWith("[VTClassic]")) return "VTClassic";
|
||||
|
||||
// Pattern-based detection for messages without prefixes
|
||||
if (text.Contains("Macro started") || text.Contains("Macro stopped")) return "VTank";
|
||||
if (text.Contains("Quest data updated")) return "UtilityBelt";
|
||||
if (text.Contains("Database read complete")) return "VGI";
|
||||
if (text.Contains("Disconnected, retry")) return "VI";
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
// ===== HOST.ACTIONS PREFIX METHODS =====
|
||||
|
||||
/// <summary>
|
||||
/// Prefix method for Host.Actions.AddChatText(string, int, int) - 3-parameter version
|
||||
/// </summary>
|
||||
public static bool AddChatTextPrefixHost(string text, int color, int target)
|
||||
{
|
||||
try
|
||||
{
|
||||
DecalHarmonyClean.messagesIntercepted++;
|
||||
|
||||
if (PluginCore.AggressiveChatStreamingEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
||||
{
|
||||
ProcessInterceptedMessage(text, color, $"Host.Actions-3param(target={target})");
|
||||
|
||||
// Silent operation - debug output removed
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefix method for Host.Actions.AddChatText(string, int, int, int) - 4-parameter version
|
||||
/// </summary>
|
||||
public static bool AddChatTextPrefixHost4(string text, int color, int target, int window)
|
||||
{
|
||||
try
|
||||
{
|
||||
DecalHarmonyClean.messagesIntercepted++;
|
||||
|
||||
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
||||
{
|
||||
ProcessInterceptedMessage(text, color, $"Host.Actions-4param(target={target},window={window})");
|
||||
|
||||
// Silent operation - debug output removed
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic prefix method for any other Host.Actions.AddChatText overloads
|
||||
/// </summary>
|
||||
public static bool AddChatTextPrefixHostGeneric(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
DecalHarmonyClean.messagesIntercepted++;
|
||||
|
||||
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
||||
{
|
||||
ProcessInterceptedMessage(text, 0, "Host.Actions-generic");
|
||||
|
||||
// Silent operation - debug output removed
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== COREMANAGER.ACTIONS PREFIX METHODS =====
|
||||
|
||||
/// <summary>
|
||||
/// Prefix method for CoreManager.Actions methods - trying different instances
|
||||
/// </summary>
|
||||
public static bool AddChatTextPrefixCore2(string text, int color)
|
||||
{
|
||||
try
|
||||
{
|
||||
DecalHarmonyClean.messagesIntercepted++;
|
||||
|
||||
if (PluginCore.AggressiveChatStreamingEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
||||
{
|
||||
ProcessInterceptedMessage(text, color, "CoreActions-2param");
|
||||
|
||||
// Silent operation - debug output removed
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefix method for CoreManager.Actions 3-param methods
|
||||
/// </summary>
|
||||
public static bool AddChatTextPrefixCore3(string text, int color, int target)
|
||||
{
|
||||
try
|
||||
{
|
||||
DecalHarmonyClean.messagesIntercepted++;
|
||||
|
||||
if (PluginCore.AggressiveChatStreamingEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text) && !text.Contains("[Mosswart Massacre]"))
|
||||
{
|
||||
ProcessInterceptedMessage(text, color, $"CoreActions-3param(target={target})");
|
||||
|
||||
// Silent operation - debug output removed
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,8 +28,8 @@ namespace MosswartMassacre
|
|||
{
|
||||
while (delayedCommands.Count > 0 && delayedCommands[0].RunAt <= DateTime.UtcNow)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Debug] Executing delayed: {delayedCommands[0].Command}");
|
||||
CoreManager.Current.Actions.InvokeChatParser(delayedCommands[0].Command);
|
||||
// Use Decal_DispatchOnChatCommand to ensure other plugins can intercept
|
||||
PluginCore.DispatchChatToBoxWithPluginIntercept(delayedCommands[0].Command);
|
||||
delayedCommands.RemoveAt(0);
|
||||
}
|
||||
|
||||
|
|
|
|||
304
MosswartMassacre/FINDINGS.md
Normal file
304
MosswartMassacre/FINDINGS.md
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
# 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 <index>` 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.
|
||||
1932
MosswartMassacre/FlagTrackerData.cs
Normal file
1932
MosswartMassacre/FlagTrackerData.cs
Normal file
File diff suppressed because it is too large
Load diff
9
MosswartMassacre/FodyWeavers.xml
Normal file
9
MosswartMassacre/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura>
|
||||
<IncludeAssemblies>
|
||||
YamlDotNet
|
||||
Newtonsoft.Json
|
||||
</IncludeAssemblies>
|
||||
</Costura>
|
||||
</Weavers>
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
using System;
|
||||
using MyClasses.MetaViewWrappers;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
internal static class MainView
|
||||
{
|
||||
private static IView View;
|
||||
private static IStaticText lblTotalKills;
|
||||
private static IStaticText lblKillsPer5Min;
|
||||
private static IStaticText lblKillsPerHour;
|
||||
private static IStaticText lblElapsedTime;
|
||||
private static IStaticText lblRareCount;
|
||||
private static IButton btnRestart;
|
||||
private static IButton btnToggleRareMeta;
|
||||
|
||||
public static void ViewInit()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load the view from the embedded XML resource
|
||||
View = MyClasses.MetaViewWrappers.ViewSystemSelector.CreateViewResource(
|
||||
PluginCore.MyHost, "MosswartMassacre.ViewXML.mainView.xml");
|
||||
|
||||
// Get references to controls
|
||||
lblTotalKills = (IStaticText)View["lblTotalKills"];
|
||||
lblKillsPer5Min = (IStaticText)View["lblKillsPer5Min"];
|
||||
lblKillsPerHour = (IStaticText)View["lblKillsPerHour"];
|
||||
lblElapsedTime = (IStaticText)View["lblElapsedTime"];
|
||||
lblRareCount = (IStaticText)View["lblRareCount"];
|
||||
btnRestart = (IButton)View["btnRestart"];
|
||||
btnRestart.Hit += OnRestartClick;
|
||||
btnToggleRareMeta = (IButton)View["btnToggleRareMeta"];
|
||||
btnToggleRareMeta.Hit += OnToggleRareMetaClick;
|
||||
btnToggleRareMeta.Text = "Meta: ON";
|
||||
|
||||
PluginCore.WriteToChat("View initialized.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat("Error initializing view: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ViewDestroy()
|
||||
{
|
||||
try
|
||||
{
|
||||
View.Dispose();
|
||||
PluginCore.WriteToChat("View destroyed.");
|
||||
btnRestart.Hit -= OnRestartClick;
|
||||
btnToggleRareMeta.Hit -= OnToggleRareMetaClick;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat("Error destroying view: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateKillStats(int totalKills, double killsPer5Min, double killsPerHour)
|
||||
{
|
||||
lblTotalKills.Text = $"Total Kills: {totalKills}";
|
||||
lblKillsPer5Min.Text = $"Kills per 5 Min: {killsPer5Min:F2}";
|
||||
lblKillsPerHour.Text = $"Kills per Hour: {killsPerHour:F2}";
|
||||
}
|
||||
|
||||
public static void UpdateElapsedTime(TimeSpan elapsed)
|
||||
{
|
||||
int days = elapsed.Days;
|
||||
int hours = elapsed.Hours;
|
||||
int minutes = elapsed.Minutes;
|
||||
int seconds = elapsed.Seconds;
|
||||
|
||||
if (days > 0)
|
||||
lblElapsedTime.Text = $"Time: {days}d {hours:D2}:{minutes:D2}:{seconds:D2}";
|
||||
else
|
||||
lblElapsedTime.Text = $"Time: {hours:D2}:{minutes:D2}:{seconds:D2}";
|
||||
}
|
||||
public static void UpdateRareCount(int rareCount)
|
||||
{
|
||||
lblRareCount.Text = $"Rare Count: {rareCount}";
|
||||
}
|
||||
private static void OnRestartClick(object sender, EventArgs e)
|
||||
{
|
||||
PluginCore.RestartStats();
|
||||
}
|
||||
private static void OnToggleRareMetaClick(object sender, EventArgs e)
|
||||
{
|
||||
PluginCore.ToggleRareMeta();
|
||||
}
|
||||
public static void SetRareMetaToggleState(bool enabled)
|
||||
{
|
||||
btnToggleRareMeta.Text = enabled ? "Meta: ON" : "Meta: OFF";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Costura.Fody.5.7.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
|
@ -13,6 +14,8 @@
|
|||
<LangVersion>8.0</LangVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
|
@ -29,43 +32,205 @@
|
|||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<DefineConstants>TRACE;VVS_REFERENCED;DECAL_INTEROP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>..\..\..\..\Documents\Decal Plugins\UtilityBelt\0Harmony.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Costura, Version=5.7.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Costura.Fody.5.7.0\lib\netstandard1.0\Costura.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Adapter">
|
||||
<HintPath>lib\Decal.Adapter.dll</HintPath>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="Decal.FileService">
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Decal 3.0\Decal.FileService.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Core, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>lib\Decal.Interop.Core.DLL</HintPath>
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Decal 3.0\.NET 4.0 PIA\Decal.Interop.Core.DLL</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Filters, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Decal 3.0\.NET 4.0 PIA\Decal.Interop.Filters.DLL</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Inject, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>lib\Decal.Interop.Inject.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.D3DService, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Decal 3.0\.NET 4.0 PIA\Decal.Interop.D3DService.DLL</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Input">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Decal 3.0\.NET 4.0 PIA\Decal.Interop.Input.DLL</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Win32.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.AppContext, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Console.4.3.0\lib\net46\System.Console.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.Tracing, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Globalization.Calendars, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IO, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq.Expressions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Sockets, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Reflection, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.InteropServices, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Remoting" />
|
||||
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="utank2-i, Version=1.0.0.0, Culture=neutral, processorArchitecture=x86">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>bin\Debug\utank2-i.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="VCS5">
|
||||
<HintPath>..\..\..\..\..\..\Games\Decal Plugins\Virindi\VirindiChatSystem5\VCS5.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="VirindiViewService">
|
||||
<HintPath>lib\VirindiViewService.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
@ -74,6 +239,76 @@
|
|||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Shared\Constants\BoolValueKey.cs">
|
||||
<Link>Shared\Constants\BoolValueKey.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Constants\Dictionaries.cs">
|
||||
<Link>Shared\Constants\Dictionaries.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Spells\Spell.cs">
|
||||
<Link>Shared\Spells\Spell.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Constants\DoubleValueKey.cs">
|
||||
<Link>Shared\Constants\DoubleValueKey.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Constants\EphemeralAttribute.cs">
|
||||
<Link>Shared\Constants\EphemeralAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Constants\IntValueKey.cs">
|
||||
<Link>Shared\Constants\IntValueKey.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Constants\SendOnLoginAttribute.cs">
|
||||
<Link>Shared\Constants\SendOnLoginAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Constants\ServerOnlyAttribute.cs">
|
||||
<Link>Shared\Constants\ServerOnlyAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Constants\StringValueKey.cs">
|
||||
<Link>Shared\Constants\StringValueKey.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Debug.cs">
|
||||
<Link>Shared\Debug.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\DecalProxy.cs">
|
||||
<Link>Shared\DecalProxy.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\MyWorldObject.cs">
|
||||
<Link>Shared\MyWorldObject.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\MyWorldObjectCreator.cs">
|
||||
<Link>Shared\MyWorldObjectCreator.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\PostMessageTools.cs">
|
||||
<Link>Shared\PostMessageTools.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\RateLimiter.cs">
|
||||
<Link>Shared\RateLimiter.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\SerializableDictionary.cs">
|
||||
<Link>Shared\SerializableDictionary.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Settings\Setting.cs">
|
||||
<Link>Shared\Settings\Setting.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Settings\SettingsFile.cs">
|
||||
<Link>Shared\Settings\SettingsFile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\User32.cs">
|
||||
<Link>Shared\User32.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\Util.cs">
|
||||
<Link>Shared\Util.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Shared\VCS_Connector.cs">
|
||||
<Link>Shared\VCS_Connector.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="ClientTelemetry.cs" />
|
||||
<Compile Include="DecalHarmonyClean.cs" />
|
||||
<Compile Include="FlagTrackerData.cs" />
|
||||
<Compile Include="MossyInventory.cs" />
|
||||
<Compile Include="NavRoute.cs" />
|
||||
<Compile Include="NavVisualization.cs" />
|
||||
<Compile Include="QuestManager.cs" />
|
||||
<Compile Include="vTank.cs" />
|
||||
<Compile Include="VtankControl.cs" />
|
||||
<Compile Include="Telemetry.cs" />
|
||||
|
|
@ -83,19 +318,22 @@
|
|||
<Compile Include="PluginSettings.cs" />
|
||||
<Compile Include="HttpCommandServer.cs" />
|
||||
<Compile Include="DelayedCommandManager.cs" />
|
||||
<Compile Include="MainView.cs" />
|
||||
<Compile Include="PluginCore.cs" />
|
||||
<Compile Include="QuestNames.cs" />
|
||||
<Compile Include="UpdateManager.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ViewSystemSelector.cs" />
|
||||
<Compile Include="Wrapper.cs" />
|
||||
<Compile Include="Wrapper_Decal.cs" />
|
||||
<Compile Include="Wrapper_MyHuds.cs" />
|
||||
<Compile Include="Wrapper_WireupHelper.cs" />
|
||||
<Compile Include="ChestLooter.cs" />
|
||||
<Compile Include="ChestLooterSettings.cs" />
|
||||
<Compile Include="SpellManager.cs" />
|
||||
<Compile Include="Views\FlagTrackerView.cs" />
|
||||
<Compile Include="Views\VVSBaseView.cs" />
|
||||
<Compile Include="Views\VVSTabbedMainView.cs" />
|
||||
<Compile Include="WebSocket.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
|
|
@ -104,11 +342,46 @@
|
|||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="ViewXML\flagTracker.xml" />
|
||||
<EmbeddedResource Include="ViewXML\mainView.xml" />
|
||||
<EmbeddedResource Include="ViewXML\mainViewTabbed.xml" />
|
||||
<EmbeddedResource Include="..\Shared\Spells\Spells.csv">
|
||||
<Link>Shared\Spells\Spells.csv</Link>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="Decal">
|
||||
<Guid>{FF7F5F6D-34E0-4B6F-B3BB-8141DE2EF732}</Guid>
|
||||
<VersionMajor>2</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>primary</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="DecalNet">
|
||||
<Guid>{572B87C4-93BD-46B3-A291-CD58181D25DC}</Guid>
|
||||
<VersionMajor>2</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>primary</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\Fody.6.9.3\build\Fody.targets" Condition="Exists('..\packages\Fody.6.9.3\build\Fody.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Fody.6.9.3\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.6.9.3\build\Fody.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.5.7.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.5.7.0\build\Costura.Fody.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets" Condition="Exists('..\packages\Costura.Fody.5.7.0\build\Costura.Fody.targets')" />
|
||||
</Project>
|
||||
336
MosswartMassacre/MossyInventory.cs
Normal file
336
MosswartMassacre/MossyInventory.cs
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Mag.Shared;
|
||||
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
using System.Diagnostics;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
class MossyInventory : IDisposable
|
||||
{
|
||||
|
||||
private string InventoryFileName
|
||||
{
|
||||
get
|
||||
{
|
||||
// 1) Character name
|
||||
var characterName = CoreManager.Current.CharacterFilter.Name;
|
||||
|
||||
// 2) Plugin folder - handle hot reload scenarios
|
||||
string pluginFolder;
|
||||
if (!string.IsNullOrEmpty(PluginCore.AssemblyDirectory))
|
||||
{
|
||||
pluginFolder = PluginCore.AssemblyDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginFolder = Path.GetDirectoryName(
|
||||
System.Reflection.Assembly
|
||||
.GetExecutingAssembly()
|
||||
.Location
|
||||
);
|
||||
}
|
||||
|
||||
// 3) Character-specific folder path
|
||||
var characterFolder = Path.Combine(pluginFolder, characterName);
|
||||
|
||||
// 4) Ensure directory exists (can do it here, thread-safe for most single-user plugin cases)
|
||||
if (!Directory.Exists(characterFolder))
|
||||
Directory.CreateDirectory(characterFolder);
|
||||
|
||||
// 5) Return full path to the .json file inside the character folder
|
||||
return Path.Combine(characterFolder, $"{characterName}.json");
|
||||
}
|
||||
}
|
||||
|
||||
public MossyInventory()
|
||||
{
|
||||
try
|
||||
{
|
||||
CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
|
||||
CoreManager.Current.WorldFilter.CreateObject += WorldFilter_CreateObject;
|
||||
CoreManager.Current.WorldFilter.ChangeObject += WorldFilter_ChangeObject;
|
||||
CoreManager.Current.CharacterFilter.Logoff += CharacterFilter_Logoff;
|
||||
PluginCore.WriteToChat($"[INV] {InventoryFileName}");
|
||||
PluginCore.WriteToChat("Started MOSSY!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[INV] {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool disposed;
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
CoreManager.Current.CharacterFilter.LoginComplete -= CharacterFilter_LoginComplete;
|
||||
CoreManager.Current.WorldFilter.CreateObject -= WorldFilter_CreateObject;
|
||||
CoreManager.Current.WorldFilter.ChangeObject -= WorldFilter_ChangeObject;
|
||||
CoreManager.Current.CharacterFilter.Logoff -= CharacterFilter_Logoff;
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private bool loginComplete;
|
||||
private bool loggedInAndWaitingForIdData;
|
||||
private readonly List<int> requestedIds = new List<int>();
|
||||
|
||||
private void CharacterFilter_LoginComplete(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
loginComplete = true;
|
||||
|
||||
// Defensive check - settings might not be initialized yet due to event handler order
|
||||
bool inventoryLogEnabled;
|
||||
try
|
||||
{
|
||||
inventoryLogEnabled = PluginSettings.Instance.InventoryLog;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
PluginCore.WriteToChat("[INV] Settings not ready, skipping inventory check");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inventoryLogEnabled)
|
||||
return;
|
||||
|
||||
if (!File.Exists(InventoryFileName))
|
||||
{
|
||||
PluginCore.WriteToChat("Requesting id information for all armor/weapon inventory. This will take a few minutes...");
|
||||
foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
|
||||
{
|
||||
if (!wo.HasIdData && ObjectClassNeedsIdent(wo.ObjectClass, wo.Name))
|
||||
CoreManager.Current.Actions.RequestId(wo.Id);
|
||||
}
|
||||
loggedInAndWaitingForIdData = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DumpInventoryToFile(true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[INV] {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void WorldFilter_CreateObject(object sender, CreateObjectEventArgs e)
|
||||
{
|
||||
if (!loginComplete) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (!PluginSettings.Instance.InventoryLog) return;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return; // Settings not ready, skip silently
|
||||
}
|
||||
|
||||
if (!e.New.HasIdData && ObjectClassNeedsIdent(e.New.ObjectClass, e.New.Name)
|
||||
&& !requestedIds.Contains(e.New.Id)
|
||||
&& e.New.Container == CoreManager.Current.CharacterFilter.Id)
|
||||
{
|
||||
requestedIds.Add(e.New.Id);
|
||||
CoreManager.Current.Actions.RequestId(e.New.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private void WorldFilter_ChangeObject(object sender, ChangeObjectEventArgs e)
|
||||
{
|
||||
if (!loginComplete) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (!PluginSettings.Instance.InventoryLog) return;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return; // Settings not ready, skip silently
|
||||
}
|
||||
|
||||
if (loggedInAndWaitingForIdData)
|
||||
{
|
||||
bool allHaveId = true;
|
||||
foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
|
||||
{
|
||||
if (!wo.HasIdData && ObjectClassNeedsIdent(wo.ObjectClass, wo.Name))
|
||||
{
|
||||
allHaveId = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allHaveId)
|
||||
{
|
||||
loggedInAndWaitingForIdData = false;
|
||||
DumpInventoryToFile();
|
||||
PluginCore.WriteToChat("Requesting id information for all armor/weapon inventory completed. Log file written.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!e.Changed.HasIdData && ObjectClassNeedsIdent(e.Changed.ObjectClass, e.Changed.Name)
|
||||
&& !requestedIds.Contains(e.Changed.Id)
|
||||
&& e.Changed.Container == CoreManager.Current.CharacterFilter.Id)
|
||||
{
|
||||
requestedIds.Add(e.Changed.Id);
|
||||
CoreManager.Current.Actions.RequestId(e.Changed.Id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void CharacterFilter_Logoff(object sender, Decal.Adapter.Wrappers.LogoffEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!PluginSettings.Instance.InventoryLog) return;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return; // Settings not ready, skip silently
|
||||
}
|
||||
DumpInventoryToFile(true); // Request IDs if missing to ensure complete data
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[INV] {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void DumpInventoryToFile(bool requestIdsIfMissing = false)
|
||||
{
|
||||
var previouslySaved = new List<MyWorldObject>();
|
||||
|
||||
if (File.Exists(InventoryFileName))
|
||||
{
|
||||
try
|
||||
{
|
||||
string oldJson = File.ReadAllText(InventoryFileName);
|
||||
previouslySaved = JsonConvert.DeserializeObject<List<MyWorldObject>>(oldJson)
|
||||
?? new List<MyWorldObject>();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
PluginCore.WriteToChat("Inventory file is corrupt.");
|
||||
}
|
||||
}
|
||||
|
||||
var currentList = new List<MyWorldObject>();
|
||||
foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
|
||||
{
|
||||
// Check to see if we already have some information for this item
|
||||
foreach (var prev in previouslySaved)
|
||||
{
|
||||
if (prev.Id == wo.Id && prev.ObjectClass == (int)wo.ObjectClass)
|
||||
{
|
||||
// If neither our past nor our current item HadIdData, but it should, lets request it
|
||||
if (requestIdsIfMissing && !prev.HasIdData && !wo.HasIdData && ObjectClassNeedsIdent(wo.ObjectClass, wo.Name))
|
||||
{
|
||||
CoreManager.Current.Actions.RequestId(wo.Id);
|
||||
currentList.Add(MyWorldObjectCreator.Create(wo));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the WorldObject to the MyWorldObject data so we have up to date information
|
||||
currentList.Add(MyWorldObjectCreator.Combine(prev, wo));
|
||||
}
|
||||
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
if (requestIdsIfMissing && !wo.HasIdData && ObjectClassNeedsIdent(wo.ObjectClass, wo.Name))
|
||||
CoreManager.Current.Actions.RequestId(wo.Id);
|
||||
|
||||
currentList.Add(MyWorldObjectCreator.Create(wo));
|
||||
|
||||
end: ;
|
||||
}
|
||||
|
||||
var fi = new FileInfo(InventoryFileName);
|
||||
if (fi.Directory != null && !fi.Directory.Exists)
|
||||
fi.Directory.Create();
|
||||
|
||||
string json = JsonConvert.SerializeObject(currentList, Formatting.Indented);
|
||||
File.WriteAllText(InventoryFileName, json);
|
||||
|
||||
// Send full inventory via WebSocket
|
||||
if (PluginCore.WebSocketEnabled)
|
||||
{
|
||||
_ = WebSocket.SendFullInventoryAsync(currentList);
|
||||
PluginCore.WriteToChat("Inventory sent to MosswartOverlord");
|
||||
}
|
||||
}
|
||||
|
||||
private bool ObjectClassNeedsIdent(ObjectClass oc, string name)
|
||||
{
|
||||
return oc == ObjectClass.Armor
|
||||
|| oc == ObjectClass.Clothing
|
||||
|| oc == ObjectClass.MeleeWeapon
|
||||
|| oc == ObjectClass.MissileWeapon
|
||||
|| oc == ObjectClass.WandStaffOrb
|
||||
|| oc == ObjectClass.Jewelry
|
||||
|| (oc == ObjectClass.Gem && !string.IsNullOrEmpty(name) && name.Contains("Aetheria"))
|
||||
|| (oc == ObjectClass.Misc && !string.IsNullOrEmpty(name) && name.Contains("Essence"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces an inventory upload with ID requests - guarantees complete data
|
||||
/// </summary>
|
||||
public void ForceInventoryUpload()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if inventory logging is enabled
|
||||
try
|
||||
{
|
||||
if (!PluginSettings.Instance.InventoryLog)
|
||||
{
|
||||
PluginCore.WriteToChat("[INV] Inventory logging is disabled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
PluginCore.WriteToChat("[INV] Settings not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if WebSocket is enabled
|
||||
if (!PluginCore.WebSocketEnabled)
|
||||
{
|
||||
PluginCore.WriteToChat("[INV] WebSocket streaming is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
PluginCore.WriteToChat("[INV] Forcing inventory upload with ID requests...");
|
||||
DumpInventoryToFile(true); // Request IDs if missing
|
||||
PluginCore.WriteToChat("[INV] Inventory upload completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[INV] Force upload failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
412
MosswartMassacre/NavRoute.cs
Normal file
412
MosswartMassacre/NavRoute.cs
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
public class NavWaypoint
|
||||
{
|
||||
public double NS { get; set; }
|
||||
public double EW { get; set; }
|
||||
public double Z { get; set; }
|
||||
public int Type { get; set; }
|
||||
public NavWaypoint Previous { get; set; }
|
||||
}
|
||||
|
||||
public class NavRoute : IDisposable
|
||||
{
|
||||
private bool disposed = false;
|
||||
private List<NavWaypoint> waypoints = new List<NavWaypoint>();
|
||||
private List<D3DObj> lineObjects = new List<D3DObj>();
|
||||
private Color routeColor;
|
||||
private bool isVisible = false;
|
||||
|
||||
public string FilePath { get; private set; }
|
||||
public string FileName => Path.GetFileNameWithoutExtension(FilePath);
|
||||
public bool IsVisible => isVisible;
|
||||
public int WaypointCount => waypoints.Count;
|
||||
|
||||
public NavRoute(string filePath, Color color)
|
||||
{
|
||||
FilePath = filePath;
|
||||
routeColor = color;
|
||||
}
|
||||
|
||||
public bool LoadFromFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
ClearRoute();
|
||||
waypoints.Clear();
|
||||
|
||||
if (!File.Exists(FilePath))
|
||||
{
|
||||
PluginCore.WriteToChat($"Nav file not found: {FilePath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginCore.WriteToChat($"Navigation: Loading {FileName}...");
|
||||
|
||||
using (StreamReader sr = File.OpenText(FilePath))
|
||||
{
|
||||
// Read header
|
||||
string header = sr.ReadLine();
|
||||
if (string.IsNullOrEmpty(header) || !header.StartsWith("uTank2 NAV"))
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Invalid file format - {FileName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read nav type
|
||||
string navTypeLine = sr.ReadLine();
|
||||
if (string.IsNullOrEmpty(navTypeLine) || !int.TryParse(navTypeLine.Trim(), out int navType))
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Failed to parse route type - {FileName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
string navTypeDescription = "";
|
||||
switch (navType)
|
||||
{
|
||||
case 0:
|
||||
navTypeDescription = "Linear";
|
||||
break;
|
||||
case 1:
|
||||
navTypeDescription = "Circular";
|
||||
break;
|
||||
case 2:
|
||||
navTypeDescription = "Linear";
|
||||
break;
|
||||
case 3:
|
||||
navTypeDescription = "Target (follow player/object)";
|
||||
break;
|
||||
case 4:
|
||||
navTypeDescription = "Once";
|
||||
break;
|
||||
default:
|
||||
navTypeDescription = $"Unknown ({navType})";
|
||||
PluginCore.WriteToChat($"Navigation: Unknown route type {navType} in {FileName}");
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle target nav (type 3) - follows a specific player/object
|
||||
if (navType == 3)
|
||||
{
|
||||
if (sr.EndOfStream)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Target route file is empty - {FileName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
string targetName = sr.ReadLine();
|
||||
if (sr.EndOfStream)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Target route missing target ID - {FileName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
string targetIdLine = sr.ReadLine();
|
||||
|
||||
PluginCore.WriteToChat($"Navigation: Target route '{targetName}' cannot be visualized");
|
||||
return true; // Successfully loaded but can't visualize
|
||||
}
|
||||
|
||||
// Read record count
|
||||
string recordCountLine = sr.ReadLine();
|
||||
if (string.IsNullOrEmpty(recordCountLine) || !int.TryParse(recordCountLine.Trim(), out int recordCount))
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Failed to parse waypoint count - {FileName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recordCount <= 0 || recordCount > 10000) // Sanity check
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Invalid waypoint count {recordCount} - {FileName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
NavWaypoint previous = null;
|
||||
int waypointsRead = 0;
|
||||
|
||||
while (!sr.EndOfStream && waypointsRead < recordCount)
|
||||
{
|
||||
// Read waypoint type
|
||||
string waypointTypeLine = sr.ReadLine();
|
||||
|
||||
if (string.IsNullOrEmpty(waypointTypeLine) || !int.TryParse(waypointTypeLine.Trim(), out int waypointType))
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Failed to parse waypoint {waypointsRead + 1} in {FileName}");
|
||||
break; // Skip this waypoint, don't fail entirely
|
||||
}
|
||||
|
||||
// Read coordinates (all waypoint types have EW, NS, Z, Unknown)
|
||||
string ewLine = sr.ReadLine();
|
||||
string nsLine = sr.ReadLine();
|
||||
string zLine = sr.ReadLine();
|
||||
string unknownLine = sr.ReadLine(); // Unknown value (always 0)
|
||||
|
||||
if (string.IsNullOrEmpty(ewLine) || string.IsNullOrEmpty(nsLine) || string.IsNullOrEmpty(zLine) || string.IsNullOrEmpty(unknownLine))
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Missing coordinates at waypoint {waypointsRead + 1} in {FileName}");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!double.TryParse(ewLine.Trim(), out double ew) ||
|
||||
!double.TryParse(nsLine.Trim(), out double ns) ||
|
||||
!double.TryParse(zLine.Trim(), out double z))
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Invalid coordinates at waypoint {waypointsRead + 1} in {FileName}");
|
||||
break; // Skip this waypoint
|
||||
}
|
||||
|
||||
var waypoint = new NavWaypoint
|
||||
{
|
||||
NS = ns,
|
||||
EW = ew,
|
||||
Z = z,
|
||||
Type = waypointType,
|
||||
Previous = previous
|
||||
};
|
||||
|
||||
waypoints.Add(waypoint);
|
||||
previous = waypoint;
|
||||
waypointsRead++;
|
||||
|
||||
// Skip additional data based on waypoint type
|
||||
if (!SkipWaypointData(sr, waypointType))
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Failed to parse waypoint {waypointsRead + 1} data in {FileName}");
|
||||
break; // Don't continue if we can't parse properly
|
||||
}
|
||||
}
|
||||
|
||||
if (waypoints.Count > 0)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Loaded {FileName} ({waypoints.Count} waypoints)");
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: No valid waypoints found in {FileName}");
|
||||
}
|
||||
|
||||
return waypoints.Count > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Error loading {FileName} - {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool SkipWaypointData(StreamReader sr, int waypointType)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Skip additional lines based on waypoint type (base 4 lines already read)
|
||||
switch (waypointType)
|
||||
{
|
||||
case 0: // Point - no additional data (4 lines total)
|
||||
break;
|
||||
case 1: // Portal - 5 additional lines (9 lines total)
|
||||
sr.ReadLine(); // Name
|
||||
sr.ReadLine(); // ObjectClass
|
||||
sr.ReadLine(); // "true"
|
||||
sr.ReadLine(); // PortalNS
|
||||
sr.ReadLine(); // PortalEW
|
||||
sr.ReadLine(); // PortalZ
|
||||
break;
|
||||
case 2: // Recall - 1 additional line (5 lines total)
|
||||
sr.ReadLine(); // RecallSpellId
|
||||
break;
|
||||
case 3: // Pause - 1 additional line (5 lines total)
|
||||
sr.ReadLine(); // Pause milliseconds
|
||||
break;
|
||||
case 4: // ChatCommand - 1 additional line (5 lines total)
|
||||
sr.ReadLine(); // Message
|
||||
break;
|
||||
case 5: // OpenVendor - 2 additional lines (6 lines total)
|
||||
sr.ReadLine(); // Id
|
||||
sr.ReadLine(); // Name
|
||||
break;
|
||||
case 6: // Portal2 - same as Portal (9 lines total)
|
||||
sr.ReadLine(); // Name
|
||||
sr.ReadLine(); // ObjectClass
|
||||
sr.ReadLine(); // "true"
|
||||
sr.ReadLine(); // PortalNS
|
||||
sr.ReadLine(); // PortalEW
|
||||
sr.ReadLine(); // PortalZ
|
||||
break;
|
||||
case 7: // UseNPC - 5 additional lines (9 lines total)
|
||||
sr.ReadLine(); // Name
|
||||
sr.ReadLine(); // ObjectClass
|
||||
sr.ReadLine(); // "true"
|
||||
sr.ReadLine(); // NpcEW
|
||||
sr.ReadLine(); // NpcNS
|
||||
sr.ReadLine(); // NpcZ
|
||||
break;
|
||||
case 8: // Checkpoint - no additional data (4 lines total)
|
||||
break;
|
||||
case 9: // Jump - 3 additional lines (7 lines total)
|
||||
sr.ReadLine(); // Heading
|
||||
sr.ReadLine(); // ShiftJump
|
||||
sr.ReadLine(); // Milliseconds
|
||||
break;
|
||||
default:
|
||||
// Unknown waypoint type - skip silently
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently handle parsing errors
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
if (isVisible) return;
|
||||
|
||||
if (waypoints.Count == 0)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: No waypoints to visualize in {FileName}");
|
||||
return;
|
||||
}
|
||||
|
||||
CreateLineObjects();
|
||||
isVisible = true;
|
||||
PluginCore.WriteToChat($"Navigation: Showing {FileName} ({waypoints.Count} waypoints)");
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
if (!isVisible) return;
|
||||
|
||||
ClearRoute();
|
||||
isVisible = false;
|
||||
PluginCore.WriteToChat($"Navigation: Hidden {FileName}");
|
||||
}
|
||||
|
||||
private void CreateLineObjects()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check D3DService availability
|
||||
if (CoreManager.Current?.D3DService == null)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: 3D service unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit the number of lines to prevent lag
|
||||
int maxLines = Math.Min(waypoints.Count - 1, 500); // Max 500 lines
|
||||
|
||||
int linesCreated = 0;
|
||||
for (int i = 1; i <= maxLines; i++)
|
||||
{
|
||||
var current = waypoints[i];
|
||||
var previous = waypoints[i - 1];
|
||||
|
||||
if (CreateLineBetweenWaypoints(previous, current))
|
||||
{
|
||||
linesCreated++;
|
||||
}
|
||||
|
||||
// Add small delay every 50 lines to prevent UI freezing
|
||||
if (i % 50 == 0)
|
||||
{
|
||||
System.Threading.Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (waypoints.Count > 501)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Large route - showing {maxLines} of {waypoints.Count} segments");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Error creating visualization - {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateLineBetweenWaypoints(NavWaypoint from, NavWaypoint to)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Calculate distance
|
||||
double distance = Math.Sqrt(
|
||||
Math.Pow((to.NS - from.NS) * 240, 2) +
|
||||
Math.Pow((to.EW - from.EW) * 240, 2) +
|
||||
Math.Pow((to.Z - from.Z) * 240, 2)
|
||||
);
|
||||
|
||||
if (distance <= 0) return false;
|
||||
|
||||
// Create D3D line object
|
||||
var lineObj = CoreManager.Current.D3DService.NewD3DObj();
|
||||
if (lineObj == null) return false;
|
||||
|
||||
lineObj.SetShape(D3DShape.Cube);
|
||||
lineObj.Color = routeColor.ToArgb();
|
||||
|
||||
// Position at midpoint between waypoints
|
||||
float midNS = (float)(from.NS + to.NS) / 2;
|
||||
float midEW = (float)(from.EW + to.EW) / 2;
|
||||
float midZ = (float)((from.Z + to.Z) * 120) + 0.1f; // Slightly higher than UtilityBelt
|
||||
|
||||
lineObj.Anchor(midNS, midEW, midZ);
|
||||
|
||||
// Orient toward destination
|
||||
float orientNS = (float)from.NS;
|
||||
float orientEW = (float)from.EW;
|
||||
float orientZ = (float)(from.Z * 240) + 0.1f;
|
||||
|
||||
lineObj.OrientToCoords(orientNS, orientEW, orientZ, true);
|
||||
|
||||
// Scale to create line effect
|
||||
lineObj.ScaleX = 0.3f; // Slightly thicker than UtilityBelt
|
||||
lineObj.ScaleZ = 0.3f;
|
||||
lineObj.ScaleY = (float)distance;
|
||||
|
||||
lineObj.Visible = true;
|
||||
lineObjects.Add(lineObj);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearRoute()
|
||||
{
|
||||
foreach (var obj in lineObjects)
|
||||
{
|
||||
try
|
||||
{
|
||||
obj.Visible = false;
|
||||
obj.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
lineObjects.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
ClearRoute();
|
||||
waypoints.Clear();
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
246
MosswartMassacre/NavVisualization.cs
Normal file
246
MosswartMassacre/NavVisualization.cs
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Decal.Adapter;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
public class NavVisualization : IDisposable
|
||||
{
|
||||
private bool disposed = false;
|
||||
private NavRoute currentRoute = null;
|
||||
private string vtankProfilesDirectory = "";
|
||||
private List<string> availableNavFiles = new List<string>();
|
||||
|
||||
// Default comparison route color (red)
|
||||
private readonly Color comparisonRouteColor = Color.FromArgb(255, 255, 100, 100);
|
||||
|
||||
public bool IsEnabled { get; private set; } = false;
|
||||
public bool HasRouteLoaded => currentRoute != null && currentRoute.WaypointCount > 0;
|
||||
public string CurrentRouteFile => currentRoute?.FileName ?? "None";
|
||||
public List<string> AvailableNavFiles => availableNavFiles.ToList();
|
||||
|
||||
public event EventHandler RouteChanged;
|
||||
|
||||
public NavVisualization()
|
||||
{
|
||||
InitializeVTankDirectory();
|
||||
RefreshNavFileList();
|
||||
}
|
||||
|
||||
private void InitializeVTankDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
// First, check if user has configured a custom path
|
||||
if (!string.IsNullOrEmpty(PluginSettings.Instance?.VTankProfilesPath))
|
||||
{
|
||||
vtankProfilesDirectory = PluginSettings.Instance.VTankProfilesPath;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to get VTank directory from Windows Registry (same method as UtilityBelt)
|
||||
var defaultPath = @"C:\Games\VirindiPlugins\VirindiTank\";
|
||||
try
|
||||
{
|
||||
var regKey = Registry.LocalMachine.OpenSubKey("Software\\Decal\\Plugins\\{642F1F48-16BE-48BF-B1D4-286652C4533E}");
|
||||
if (regKey != null)
|
||||
{
|
||||
var profilePath = regKey.GetValue("ProfilePath")?.ToString();
|
||||
if (!string.IsNullOrEmpty(profilePath))
|
||||
{
|
||||
vtankProfilesDirectory = profilePath;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// Fall back to default path
|
||||
vtankProfilesDirectory = defaultPath;
|
||||
// Using default path - user can configure in Settings if needed
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[NavViz] Error finding VTank directory: {ex.Message}");
|
||||
vtankProfilesDirectory = "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan VTank directory for .nav files and populate available routes list
|
||||
/// Filters out follow files and temporary files, sorts alphabetically
|
||||
/// </summary>
|
||||
public void RefreshNavFileList()
|
||||
{
|
||||
// Re-initialize directory in case settings changed
|
||||
InitializeVTankDirectory();
|
||||
|
||||
availableNavFiles.Clear();
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(vtankProfilesDirectory))
|
||||
{
|
||||
PluginCore.WriteToChat("VTank directory not configured. Set path in Settings tab.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(vtankProfilesDirectory))
|
||||
{
|
||||
PluginCore.WriteToChat($"VTank directory not found: {vtankProfilesDirectory}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Get all files and filter for .nav files only, excluding follow/temporary files
|
||||
var allFiles = Directory.GetFiles(vtankProfilesDirectory);
|
||||
var navFiles = allFiles
|
||||
.Where(file => Path.GetExtension(file).Equals(".nav", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(file => Path.GetFileNameWithoutExtension(file))
|
||||
.Where(name => !string.IsNullOrEmpty(name) &&
|
||||
!name.StartsWith("follow", StringComparison.OrdinalIgnoreCase) &&
|
||||
!name.StartsWith("--", StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(name => name)
|
||||
.ToList();
|
||||
|
||||
availableNavFiles.AddRange(navFiles);
|
||||
|
||||
// Only report summary - no need to spam chat with every file
|
||||
if (navFiles.Count > 0)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Found {navFiles.Count} route files");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Error scanning files - {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a specific navigation route file for visualization
|
||||
/// Clears current route if "None" specified, otherwise loads .nav file
|
||||
/// </summary>
|
||||
/// <param name="navFileName">Name of .nav file (without extension) or "None"</param>
|
||||
/// <returns>True if route loaded successfully, false otherwise</returns>
|
||||
public bool LoadRoute(string navFileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Clear current route
|
||||
if (currentRoute != null)
|
||||
{
|
||||
currentRoute.Dispose();
|
||||
currentRoute = null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(navFileName) || navFileName == "None")
|
||||
{
|
||||
RouteChanged?.Invoke(this, EventArgs.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
string fullPath = Path.Combine(vtankProfilesDirectory, navFileName + ".nav");
|
||||
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation file '{navFileName}' not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
currentRoute = new NavRoute(fullPath, comparisonRouteColor);
|
||||
|
||||
if (!currentRoute.LoadFromFile())
|
||||
{
|
||||
currentRoute.Dispose();
|
||||
currentRoute = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show route if visualization is enabled
|
||||
if (IsEnabled)
|
||||
{
|
||||
currentRoute.Show();
|
||||
}
|
||||
|
||||
RouteChanged?.Invoke(this, EventArgs.Empty);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Navigation: Failed to load '{navFileName}' - {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable or disable navigation route visualization in 3D world
|
||||
/// Shows/hides the currently loaded route based on enabled state
|
||||
/// </summary>
|
||||
/// <param name="enabled">True to show route lines, false to hide</param>
|
||||
public void SetEnabled(bool enabled)
|
||||
{
|
||||
// No change needed if already in desired state
|
||||
if (IsEnabled == enabled) return;
|
||||
|
||||
IsEnabled = enabled;
|
||||
|
||||
if (currentRoute != null)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
currentRoute.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentRoute.Hide();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
PluginCore.WriteToChat($"Navigation visualization {(enabled ? "enabled" : "disabled")}");
|
||||
}
|
||||
|
||||
public void ToggleEnabled()
|
||||
{
|
||||
SetEnabled(!IsEnabled);
|
||||
}
|
||||
|
||||
public string GetStatus()
|
||||
{
|
||||
if (currentRoute == null)
|
||||
return "No route loaded";
|
||||
|
||||
string status = $"{currentRoute.FileName} ({currentRoute.WaypointCount} points)";
|
||||
if (IsEnabled && currentRoute.IsVisible)
|
||||
status += " - Visible";
|
||||
else if (IsEnabled)
|
||||
status += " - Hidden";
|
||||
else
|
||||
status += " - Disabled";
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
if (currentRoute != null)
|
||||
{
|
||||
currentRoute.Dispose();
|
||||
currentRoute = null;
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1654
MosswartMassacre/PluginCore.backup.cs
Normal file
1654
MosswartMassacre/PluginCore.backup.cs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -17,17 +17,54 @@ namespace MosswartMassacre
|
|||
private bool _rareMetaEnabled = true;
|
||||
private bool _httpServerEnabled = false;
|
||||
private bool _telemetryEnabled = false;
|
||||
private bool _webSocketEnabled = false;
|
||||
private bool _inventorylog = true;
|
||||
private string _charTag = "default";
|
||||
private int _mainWindowX = 100;
|
||||
private int _mainWindowY = 100;
|
||||
private bool _useTabbedInterface = true;
|
||||
private string _vtankProfilesPath = "";
|
||||
private bool _verboseLogging = false;
|
||||
private ChestLooterSettings _chestLooterSettings = new ChestLooterSettings();
|
||||
|
||||
public static PluginSettings Instance => _instance
|
||||
?? throw new InvalidOperationException("PluginSettings not initialized");
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// determine settings file path
|
||||
// determine plugin folder and character-specific folder
|
||||
string characterName = CoreManager.Current.CharacterFilter.Name;
|
||||
string pluginFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
_filePath = Path.Combine(pluginFolder, $"{characterName}.yaml");
|
||||
|
||||
// For hot reload scenarios, use the AssemblyDirectory set by the Loader
|
||||
// For normal loading, fall back to the executing assembly location
|
||||
string pluginFolder;
|
||||
if (!string.IsNullOrEmpty(PluginCore.AssemblyDirectory))
|
||||
{
|
||||
pluginFolder = PluginCore.AssemblyDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
}
|
||||
|
||||
// Path for character-specific folder
|
||||
string characterFolder = Path.Combine(pluginFolder, characterName);
|
||||
|
||||
// Create the character folder if it doesn't exist
|
||||
if (!Directory.Exists(characterFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(characterFolder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.DispatchChatToBoxWithPluginIntercept($"[Settings] Failed to create character folder: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// YAML file is now in the character-specific folder
|
||||
_filePath = Path.Combine(characterFolder, $"{characterName}.yaml");
|
||||
|
||||
// build serializer/deserializer once
|
||||
var builder = new DeserializerBuilder()
|
||||
|
|
@ -123,11 +160,67 @@ namespace MosswartMassacre
|
|||
get => _telemetryEnabled;
|
||||
set { _telemetryEnabled = value; Save(); }
|
||||
}
|
||||
|
||||
public bool WebSocketEnabled
|
||||
{
|
||||
get => _webSocketEnabled;
|
||||
set { _webSocketEnabled = value; Save(); }
|
||||
}
|
||||
public string CharTag
|
||||
{
|
||||
get => _charTag;
|
||||
set { _charTag = value; Save(); }
|
||||
}
|
||||
public bool InventoryLog
|
||||
{
|
||||
get => _inventorylog;
|
||||
set { _inventorylog = value; Save(); }
|
||||
}
|
||||
|
||||
public int MainWindowX
|
||||
{
|
||||
get => _mainWindowX;
|
||||
set { _mainWindowX = value; Save(); }
|
||||
}
|
||||
|
||||
public int MainWindowY
|
||||
{
|
||||
get => _mainWindowY;
|
||||
set { _mainWindowY = value; Save(); }
|
||||
}
|
||||
|
||||
public bool UseTabbedInterface
|
||||
{
|
||||
get => _useTabbedInterface;
|
||||
set { _useTabbedInterface = value; Save(); }
|
||||
}
|
||||
|
||||
public string VTankProfilesPath
|
||||
{
|
||||
get => _vtankProfilesPath;
|
||||
set { _vtankProfilesPath = value; Save(); }
|
||||
}
|
||||
|
||||
public bool VerboseLogging
|
||||
{
|
||||
get => _verboseLogging;
|
||||
set { _verboseLogging = value; Save(); }
|
||||
}
|
||||
|
||||
public ChestLooterSettings ChestLooterSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_chestLooterSettings == null)
|
||||
{
|
||||
_chestLooterSettings = new ChestLooterSettings();
|
||||
}
|
||||
return _chestLooterSettings;
|
||||
}
|
||||
set
|
||||
{
|
||||
_chestLooterSettings = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,5 +26,5 @@ using System.Runtime.InteropServices;
|
|||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
[assembly: AssemblyVersion("2.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.0.0.0")]
|
||||
[assembly: AssemblyVersion("4.0.0.2")]
|
||||
[assembly: AssemblyFileVersion("4.0.0.2")]
|
||||
313
MosswartMassacre/QuestManager.cs
Normal file
313
MosswartMassacre/QuestManager.cs
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
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 Name Mapping
|
||||
public string GetFriendlyQuestName(string questStamp)
|
||||
{
|
||||
return QuestNames.GetFriendlyName(questStamp);
|
||||
}
|
||||
|
||||
public string GetQuestDisplayName(string questStamp)
|
||||
{
|
||||
return QuestNames.GetDisplayName(questStamp);
|
||||
}
|
||||
|
||||
public int GetQuestNameMappingsCount()
|
||||
{
|
||||
return QuestNames.QuestStampToName.Count;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Quest Parsing
|
||||
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
|
||||
}
|
||||
}
|
||||
228
MosswartMassacre/QuestNames.cs
Normal file
228
MosswartMassacre/QuestNames.cs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Static quest name mappings from quest stamp to friendly display name
|
||||
/// Based on questtracker repository data
|
||||
/// </summary>
|
||||
public static class QuestNames
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary mapping quest stamps to friendly quest names
|
||||
/// </summary>
|
||||
public static readonly Dictionary<string, string> QuestStampToName = new Dictionary<string, string>
|
||||
{
|
||||
// Character-specific Quest Stamps (from actual /myquests output)
|
||||
["30minattributes"] = "30 Minute Attribute Gems Timer",
|
||||
["academeyexittokengiven"] = "Academy Exit Token Received",
|
||||
["aerbaxchestkey2pickup"] = "Aerbax Chest Key #2 Pickup",
|
||||
["anekshaygemofknowledgetimer_monthly"] = "A'nekshay Gem of Knowledge Monthly Timer",
|
||||
["anekshaygemoflesserknowledgecollectedinamonth"] = "A'nekshay Gems of Lesser Knowledge Monthly Count",
|
||||
["anekshaygemoflesserknowledgetimer_monthly"] = "A'nekshay Gem of Lesser Knowledge Monthly Timer",
|
||||
["attributereset30day"] = "30-Day Attribute Reset Timer",
|
||||
["augmentationblankgemacquired"] = "Blank Augmentation Gem Pickup Timer",
|
||||
["bellowsnewbieturnedin"] = "Blacksmith's Bellows Turned In",
|
||||
["bonecrunchkeypickuptimer"] = "Bonecrunch's Key Pickup Timer",
|
||||
["callingstonegiven"] = "Calling Stone Turned Over",
|
||||
["defeatedbonecrunch"] = "Bonecrunch Defeated",
|
||||
["efmlcentermanafieldused"] = "EF Middle Level Center Mana Field Used",
|
||||
["efmleastmanafieldused"] = "EF Middle Level East Mana Field Used",
|
||||
["efmlnorthmanafieldused"] = "EF Middle Level North Mana Field Used",
|
||||
["efmlsouthmanafieldused"] = "EF Middle Level South Mana Field Used",
|
||||
["efmlwestmanafieldused"] = "EF Middle Level West Mana Field Used",
|
||||
["efulcentermanafieldused"] = "EF Upper Level Center Mana Field Used",
|
||||
["efuleastmanafieldused"] = "EF Upper Level East Mana Field Used",
|
||||
["efulnorthmanafieldused"] = "EF Upper Level North Mana Field Used",
|
||||
["efulsouthmanafieldused"] = "EF Upper Level South Mana Field Used",
|
||||
["efulwestmanafieldused"] = "EF Upper Level West Mana Field Used",
|
||||
["insatiableeaterjaw"] = "Insatiable Eater Jaw Collection",
|
||||
["pathwardencomplete"] = "Pathwarden Visit Complete",
|
||||
["pathwardenfound1111"] = "Pathwarden Greeter Encountered",
|
||||
["recallsingularitycaul"] = "Recall Singularity Bore Pickup",
|
||||
["stipendscollectedinamonth"] = "Monthly Stipends Collected Count",
|
||||
["stipendtimer_0812"] = "Stipend Collection Timer",
|
||||
["stipendtimer_monthly"] = "Monthly Stipend Timer",
|
||||
["upperinsatiablejaw"] = "Upper Insatiable Eater Jaw Collection",
|
||||
["usedattributereset"] = "Attribute Reset Used",
|
||||
["usedfreeattributereset"] = "Free Attribute Reset Used",
|
||||
["usedfreeskillreset"] = "Free Skill Reset Used",
|
||||
["usedskillreset"] = "Skill Reset Used",
|
||||
["virindiisland"] = "Singularity Island Visit",
|
||||
|
||||
// Kill Tasks
|
||||
["turshscalp"] = "Tursh Scalp",
|
||||
["polarursuin"] = "Polar Ursuin Kill Task Main Flag Timer",
|
||||
["polarursuinkillcount"] = "Polar Ursuin Kill Counter",
|
||||
["polardillotask"] = "Polar Dillo Kill Task Main Flag",
|
||||
["polardillokills"] = "Polar Dillo Kill Counter",
|
||||
["repugnanteaterkilltask"] = "Repugnant Eater Kill Task",
|
||||
["repugeaterkillcount"] = "Repugnant Eater Kill Counter",
|
||||
["deathcap"] = "Deathcap Thrungus Kill Task",
|
||||
["deathcapkillcount"] = "Deathcap Thrungus Kill Counter",
|
||||
["grievverv"] = "Grievver Violator Kill Task",
|
||||
["grievvervkillcount"] = "Grievver Violator Kill Counter",
|
||||
["tuskerg"] = "Tusker Guard Kill Task Main Flag",
|
||||
["tuskergkillcount"] = "Tusker Guard Kill Counter",
|
||||
|
||||
// Quest Timers and Pickups
|
||||
["blankaug"] = "Blank Aug Gem Pickup Timer",
|
||||
["greatcavepenguinegg"] = "Great Cave Penguin Egg Pickup Timer",
|
||||
["deathallurecd"] = "Death's Allure Timer Flag",
|
||||
["brewmastercover"] = "Brew Master Quest Pickup Timer Cover",
|
||||
["brewmasterback"] = "Brew Master Quest Pickup Timer Back",
|
||||
["brewmasterpages"] = "Brew Master Quest Pickup Timer Pages",
|
||||
["brewmasterspine"] = "Brew Master Quest Pickup Timer Spine",
|
||||
["eleonorasheart"] = "Elanora's Heart Quest Pickup Timer",
|
||||
["beacongemobtained"] = "Cooldown for obtaining another beacon gem",
|
||||
["beaconcomplete"] = "Beacon Quest Complete Timer",
|
||||
["sirginaziosword"] = "Pick up of Sir Ginazio's Sword",
|
||||
|
||||
// Major Quests
|
||||
["maraudersjaw"] = "Marauder's Lair Quest",
|
||||
["fledgemastertusk"] = "Fledge Master's Tusk Quest",
|
||||
["crystallinekiller"] = "Crystalline Killer",
|
||||
["darkisledelivery"] = "Dark Isle Delivery",
|
||||
["defeatingvaeshok"] = "Defeating Vaeshok",
|
||||
["hollyjollyhelperquest"] = "Holly Jolly Helper Quest",
|
||||
["moarsmenjailbreak"] = "Moarsmen Jailbreak",
|
||||
["shamblingarchivistdestroyer"] = "Shambling Archivist Destroyer",
|
||||
["tracingthestone"] = "Tracing The Stone",
|
||||
["undeadjawcollection"] = "Undead Jaw Collection",
|
||||
["weedingofthederutree"] = "Weeding of the Deru Tree",
|
||||
["ironbladecommander"] = "Iron Blade Commander",
|
||||
["mumiyahhuntingneftet"] = "Mumiyah Hunting Neftet",
|
||||
["torgashstasks"] = "Torgash's Tasks",
|
||||
|
||||
// Thrungus Hovels Items
|
||||
["stolenfryingpan"] = "Thrungus Hovels",
|
||||
["stolenring"] = "Thrungus Hovels",
|
||||
["stolenbrewkettle"] = "Thrungus Hovels",
|
||||
["stolenamulet"] = "Thrungus Hovels",
|
||||
["stolenewer"] = "Thrungus Hovels",
|
||||
["stolennecklace"] = "Thrungus Hovels",
|
||||
["stolenplatter"] = "Thrungus Hovels",
|
||||
["stolenbracelet"] = "Thrungus Hovels",
|
||||
|
||||
// Special Items and Flags
|
||||
["ringofkarlun"] = "Knights of Karlun",
|
||||
["trainingacademycomplete"] = "Completion of Training Academy for Exit",
|
||||
["cowtipcounter"] = "Counter for Cow Tipping",
|
||||
["cowtip"] = "Main Timed Flag for Cow Tipping",
|
||||
["skillloweringgempickedup"] = "Picked up a forgetfulness gem",
|
||||
|
||||
// Healing Machine Components
|
||||
["orbhealingmachine"] = "Healing Machine Orb",
|
||||
["pedestalhealingmachine"] = "Healing Machine Pedestal",
|
||||
["tihnhealingmachine"] = "Healing Machine Tihn",
|
||||
["lavushealingmachine"] = "Healing Machine Lavus",
|
||||
["hookhealingmachine"] = "Healing Machine Hook",
|
||||
|
||||
// Eater Jaws
|
||||
["ravenouseaterjaw"] = "Ravenous Eater Jaw",
|
||||
["insatiableeaterjaw"] = "Insatiable Eater Jaw",
|
||||
["engorgedeaterjaw"] = "Engorged Eater Jaw",
|
||||
["voraciouseaterjaw"] = "Voracious Eater Jaw",
|
||||
["abhorrenteaterjaw"] = "Abhorrent Eater Jaw",
|
||||
|
||||
// Kill Tasks (Extended)
|
||||
["altereddrudgekilltask"] = "Altered Drudge Kill Task",
|
||||
["altereddrudgekillcount"] = "Altered Drudge Kill Counter",
|
||||
["arcticmattekarkilltask"] = "Arctic Mattekar Kill Task",
|
||||
["arcticmattekarkillcount"] = "Arctic Mattekar Kill Counter",
|
||||
["armoredillohuntingneftetkilltask"] = "Armoredillo Hunting Neftet Kill Task",
|
||||
["armoredillohuntingneftetkillcount"] = "Armoredillo Hunting Neftet Kill Counter",
|
||||
["augmenteddrudgekilltask"] = "Augmented Drudge Kill Task",
|
||||
["augmenteddrudgekillcount"] = "Augmented Drudge Kill Counter",
|
||||
["banishedcreaturekilltask"] = "Banished Creature Kill Task",
|
||||
["banishedcreaturekillcount"] = "Banished Creature Kill Counter",
|
||||
["benekniffiskilltask"] = "Benek Niffis Kill Task",
|
||||
["benekniffiskillcount"] = "Benek Niffis Kill Counter",
|
||||
["blackcoralgolemkilltask"] = "Black Coral Golem Kill Task",
|
||||
["blackcoralgolemkillcount"] = "Black Coral Golem Kill Counter",
|
||||
["blessedmoarsmankilltask"] = "Blessed Moarsman Kill Task",
|
||||
["blessedmoarsmankillcount"] = "Blessed Moarsman Kill Counter",
|
||||
["blightedcoralgolemkilltask"] = "Blighted Coral Golem Kill Task",
|
||||
["blightedcoralgolemkillcount"] = "Blighted Coral Golem Kill Counter",
|
||||
["bloodshrethkilltask"] = "Blood Shreth Kill Task",
|
||||
["bloodshrethkillcount"] = "Blood Shreth Kill Counter",
|
||||
["bronzegauntlettrooperkilltask"] = "Bronze Gauntlet Trooper Kill Task",
|
||||
["bronzegauntlettrooperkillcount"] = "Bronze Gauntlet Trooper Kill Counter",
|
||||
["coppercogtrooperkilltask"] = "Copper Cog Trooper Kill Task",
|
||||
["coppercogtrooperkillcount"] = "Copper Cog Trooper Kill Counter",
|
||||
["coppergolemkingpinkilltask"] = "Copper Golem Kingpin Kill Task",
|
||||
["coppergolemkingpinkillcount"] = "Copper Golem Kingpin Kill Counter",
|
||||
["coralgolemkilltask"] = "Coral Golem Kill Task",
|
||||
["coralgolemkillcount"] = "Coral Golem Kill Counter",
|
||||
["coralgolemviceroykilltask"] = "Coral Golem Viceroy Kill Task",
|
||||
["coralgolemviceroykillcount"] = "Coral Golem Viceroy Kill Counter",
|
||||
["corruptedgravestonekilltask"] = "Corrupted Gravestone Kill Task",
|
||||
["corruptedgravestonekillcount"] = "Corrupted Gravestone Kill Counter",
|
||||
["deathcapthrunguskilltask"] = "Deathcap Thrungus Kill Task",
|
||||
["deathcapthrunguskillcount"] = "Deathcap Thrungus Kill Counter",
|
||||
["desertcactuskilltask"] = "Desert Cactus Kill Task",
|
||||
["desertcactuskillcount"] = "Desert Cactus Kill Counter",
|
||||
["devourermargulkilltask"] = "Devourer Margul Kill Task",
|
||||
["devourermargulkillcount"] = "Devourer Margul Kill Counter",
|
||||
|
||||
// Society and Faction Quests
|
||||
["celestialhandintroductioncomplete"] = "Celestial Hand Introduction Complete",
|
||||
["eldrytchwebintroductioncomplete"] = "Eldrytch Web Introduction Complete",
|
||||
["radiantbloodintroductioncomplete"] = "Radiant Blood Introduction Complete",
|
||||
["celestialhandinitiatetest"] = "Celestial Hand Initiate Test",
|
||||
["eldrytchwebinitiatetest"] = "Eldrytch Web Initiate Test",
|
||||
["radiantbloodinitiatetest"] = "Radiant Blood Initiate Test",
|
||||
|
||||
// Luminance Aura Related
|
||||
["aetheriaredemption"] = "Aetheria Redemption",
|
||||
["aegisofmerc"] = "Aegis of Merc",
|
||||
["lumaugtradein"] = "Luminance Augmentation Trade In",
|
||||
|
||||
// Common AC Quests
|
||||
["holtburgtraderskill"] = "Holtburg Trader Skill Quest",
|
||||
["shoushitraderskill"] = "Shoushi Trader Skill Quest",
|
||||
["yaraqtraderskill"] = "Yaraq Trader Skill Quest",
|
||||
["newbiequests"] = "Newbie Academy Quests",
|
||||
["moarsmanraid"] = "Moarsman Raid",
|
||||
["virindiparadox"] = "Virindi Paradox",
|
||||
["portalspace"] = "Portal Space Exploration"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Get friendly name for a quest stamp, with fallback to original stamp
|
||||
/// </summary>
|
||||
/// <param name="questStamp">The quest stamp to lookup</param>
|
||||
/// <returns>Friendly name if found, otherwise the original quest stamp</returns>
|
||||
public static string GetFriendlyName(string questStamp)
|
||||
{
|
||||
if (string.IsNullOrEmpty(questStamp))
|
||||
return questStamp;
|
||||
|
||||
return QuestStampToName.TryGetValue(questStamp.ToLower(), out string friendlyName)
|
||||
? friendlyName
|
||||
: questStamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get display name with friendly name and original stamp in parentheses
|
||||
/// </summary>
|
||||
/// <param name="questStamp">The quest stamp to format</param>
|
||||
/// <returns>Formatted display name</returns>
|
||||
public static string GetDisplayName(string questStamp)
|
||||
{
|
||||
if (string.IsNullOrEmpty(questStamp))
|
||||
return questStamp;
|
||||
|
||||
string friendlyName = GetFriendlyName(questStamp);
|
||||
|
||||
// If we found a mapping, show friendly name with original in parentheses
|
||||
if (!string.Equals(friendlyName, questStamp, System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return $"{friendlyName} ({questStamp})";
|
||||
}
|
||||
|
||||
// Otherwise just show the original
|
||||
return questStamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
227
MosswartMassacre/SpellManager.cs
Normal file
227
MosswartMassacre/SpellManager.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,9 +85,10 @@ namespace MosswartMassacre
|
|||
kills = PluginCore.totalKills,
|
||||
onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"),
|
||||
kills_per_hour = PluginCore.killsPerHour.ToString("F0"),
|
||||
deaths = 0,
|
||||
deaths = PluginCore.sessionDeaths.ToString(),
|
||||
total_deaths = PluginCore.totalDeaths.ToString(),
|
||||
rares_found = PluginCore.rareCount,
|
||||
prismatic_taper_count = 0,
|
||||
prismatic_taper_count = PluginCore.cachedPrismaticCount.ToString(),
|
||||
vt_state = VtankControl.VtGetMetaState(),
|
||||
};
|
||||
|
||||
|
|
|
|||
233
MosswartMassacre/UpdateManager.cs
Normal file
233
MosswartMassacre/UpdateManager.cs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
public static class UpdateManager
|
||||
{
|
||||
private const string UPDATE_URL = "https://git.snakedesert.se/SawatoMosswartsEnjoyersClub/MosswartMassacre/raw/branch/spawn-detection/MosswartMassacre/bin/Release/MosswartMassacre.dll";
|
||||
|
||||
private static bool updateAvailable = false;
|
||||
private static string remoteFileHash = string.Empty;
|
||||
private static string localFileHash = string.Empty;
|
||||
private static DateTime lastCheckTime = DateTime.MinValue;
|
||||
|
||||
public static bool IsUpdateAvailable => updateAvailable;
|
||||
public static DateTime LastCheckTime => lastCheckTime;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate SHA256 hash of a file
|
||||
/// </summary>
|
||||
private static string CalculateFileHash(string filePath)
|
||||
{
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
using (var stream = File.OpenRead(filePath))
|
||||
{
|
||||
byte[] hashBytes = sha256.ComputeHash(stream);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate SHA256 hash of byte array
|
||||
/// </summary>
|
||||
private static string CalculateHash(byte[] data)
|
||||
{
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] hashBytes = sha256.ComputeHash(data);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> CheckForUpdateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] Checking for updates...");
|
||||
|
||||
// Get local file hash
|
||||
string localPath = GetLocalDllPath();
|
||||
if (!File.Exists(localPath))
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] Error: Could not find local DLL file");
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginCore.WriteToChat("[Update] Calculating local file hash...");
|
||||
localFileHash = CalculateFileHash(localPath);
|
||||
|
||||
// Download remote file and calculate hash
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
PluginCore.WriteToChat("[Update] Downloading remote file for comparison...");
|
||||
var remoteData = await client.GetByteArrayAsync(UPDATE_URL);
|
||||
|
||||
if (remoteData == null || remoteData.Length == 0)
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] Error: Could not download remote file");
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginCore.WriteToChat("[Update] Calculating remote file hash...");
|
||||
remoteFileHash = CalculateHash(remoteData);
|
||||
}
|
||||
|
||||
// Compare hashes
|
||||
updateAvailable = !string.Equals(localFileHash, remoteFileHash, StringComparison.OrdinalIgnoreCase);
|
||||
lastCheckTime = DateTime.Now;
|
||||
|
||||
if (updateAvailable)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Update] Update available!");
|
||||
PluginCore.WriteToChat($"[Update] Local hash: {localFileHash}");
|
||||
PluginCore.WriteToChat($"[Update] Remote hash: {remoteFileHash}");
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] Up to date - hashes match");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Update] Network error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] Request timed out");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Update] Check failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> DownloadAndInstallUpdateAsync()
|
||||
{
|
||||
if (!updateAvailable)
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] No update available. Run /mm checkforupdate first.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] Downloading update...");
|
||||
|
||||
string localPath = GetLocalDllPath();
|
||||
string tempPath = localPath + ".tmp";
|
||||
string backupPath = localPath + ".bak";
|
||||
|
||||
// Download to temp file
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
var response = await client.GetAsync(UPDATE_URL);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
using (var fileStream = File.Create(tempPath))
|
||||
{
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate downloaded file by hash
|
||||
PluginCore.WriteToChat("[Update] Validating downloaded file...");
|
||||
var downloadedHash = CalculateFileHash(tempPath);
|
||||
if (!string.Equals(downloadedHash, remoteFileHash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
PluginCore.WriteToChat($"[Update] Download validation failed. Hash mismatch!");
|
||||
PluginCore.WriteToChat($"[Update] Expected: {remoteFileHash}");
|
||||
PluginCore.WriteToChat($"[Update] Got: {downloadedHash}");
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginCore.WriteToChat("[Update] Download complete, installing...");
|
||||
|
||||
// Atomically replace current file with new version (creates backup automatically)
|
||||
File.Replace(tempPath, localPath, backupPath);
|
||||
|
||||
// Clear update flag
|
||||
updateAvailable = false;
|
||||
|
||||
PluginCore.WriteToChat("[Update] Update installed successfully!");
|
||||
PluginCore.WriteToChat("[Update] Previous version backed up as MosswartMassacre.dll.bak");
|
||||
|
||||
// Wait a moment for file system to settle, then trigger hot reload
|
||||
await System.Threading.Tasks.Task.Delay(1000);
|
||||
|
||||
try
|
||||
{
|
||||
// Touch the file to ensure FileSystemWatcher detects the change
|
||||
File.SetLastWriteTime(localPath, DateTime.Now);
|
||||
PluginCore.WriteToChat("[Update] Triggering hot reload...");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Update] Could not trigger hot reload: {ex.Message}");
|
||||
PluginCore.WriteToChat("[Update] Please use /mm gui to reload manually");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Update] Download error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] Download timed out");
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] File access denied. Make sure the plugin directory is writable.");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Update] Install failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLocalDllPath()
|
||||
{
|
||||
// Get the path to the current DLL
|
||||
string assemblyPath = typeof(PluginCore).Assembly.Location;
|
||||
|
||||
// If empty (hot reload scenario), use AssemblyDirectory + filename
|
||||
if (string.IsNullOrEmpty(assemblyPath))
|
||||
{
|
||||
return Path.Combine(PluginCore.AssemblyDirectory, "MosswartMassacre.dll");
|
||||
}
|
||||
|
||||
return assemblyPath;
|
||||
}
|
||||
|
||||
public static string GetUpdateStatus()
|
||||
{
|
||||
if (lastCheckTime == DateTime.MinValue)
|
||||
{
|
||||
return "Update Status: Not checked";
|
||||
}
|
||||
|
||||
return updateAvailable ? "Update Status: Update available" : "Update Status: Up to date";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
using System.Numerics;
|
||||
using Mag.Shared.Constants;
|
||||
using System.Linq;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
|
|
@ -39,6 +41,29 @@ namespace MosswartMassacre
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return any WorldObject's raw world position by reading the
|
||||
/// physics-object pointer (same offsets: +0x84/88/8C).
|
||||
/// </summary>
|
||||
public static unsafe Vector3 GetWorldObjectPosition(int objectId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CoreManager.Current.Actions.IsValidObject(objectId))
|
||||
return new Vector3();
|
||||
|
||||
byte* p = (byte*)CoreManager.Current.Actions.Underlying.GetPhysicsObjectPtr(objectId);
|
||||
return new Vector3(
|
||||
*(float*)(p + 0x84), // X
|
||||
*(float*)(p + 0x88), // Y
|
||||
*(float*)(p + 0x8C)); // Z
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Vector3();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience: returns the current landcell (upper 16 bits of landblock).
|
||||
/// </summary>
|
||||
|
|
@ -63,6 +88,26 @@ namespace MosswartMassacre
|
|||
return new Coordinates(ew, ns, pos.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get AC-style coordinates (EW/NS/Z) for any WorldObject.
|
||||
/// </summary>
|
||||
public static Coordinates GetWorldObjectCoordinates(WorldObject wo)
|
||||
{
|
||||
if (wo == null) return new Coordinates();
|
||||
|
||||
Vector3 pos = GetWorldObjectPosition(wo.Id);
|
||||
|
||||
// Get landcell from the object's coordinates
|
||||
var coordsObj = wo.Coordinates();
|
||||
if (coordsObj == null) return new Coordinates();
|
||||
|
||||
// Convert DECAL coords to our Coordinates with Z
|
||||
double ew = coordsObj.EastWest;
|
||||
double ns = coordsObj.NorthSouth;
|
||||
|
||||
return new Coordinates(ew, ns, pos.Z);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------
|
||||
* 3) Generic math helpers you may want later
|
||||
* -------------------------------------------------------- */
|
||||
|
|
@ -72,5 +117,212 @@ namespace MosswartMassacre
|
|||
|
||||
public static double DegToRad(double deg) => deg * Math.PI / 180.0;
|
||||
public static double RadToDeg(double rad) => rad * 180.0 / Math.PI;
|
||||
|
||||
/* ----------------------------------------------------------
|
||||
* 4) Generic item property access
|
||||
* -------------------------------------------------------- */
|
||||
|
||||
/// <summary>
|
||||
/// Find a WorldObject item by name in inventory
|
||||
/// </summary>
|
||||
/// <param name="itemName">Name of the item to find</param>
|
||||
/// <returns>WorldObject or null if not found</returns>
|
||||
public static WorldObject FindItemByName(string itemName)
|
||||
{
|
||||
try
|
||||
{
|
||||
//var worldFilter = CoreManager.Current.WorldFilter;
|
||||
//var playerInv = CoreManager.Current.CharacterFilter.Id;
|
||||
|
||||
// Search inventory
|
||||
|
||||
foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
|
||||
{
|
||||
if (string.Equals(wo.Name, itemName, StringComparison.OrdinalIgnoreCase))
|
||||
return wo;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the stack size/quantity of a specific item by name
|
||||
/// </summary>
|
||||
/// <param name="itemName">Name of the item to find</param>
|
||||
/// <returns>Stack size or 0 if not found</returns>
|
||||
/// <summary>
|
||||
/// Return the total quantity of an item in the character’s inventory,
|
||||
/// adding up every stack that shares <paramref name="itemName"/>.
|
||||
/// </summary>
|
||||
public static int GetItemStackSize(string itemName)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. Pull every WorldObject in bags + containers
|
||||
var inv = CoreManager.Current.WorldFilter.GetInventory();
|
||||
|
||||
// 2. Keep only those whose display name matches (case-insensitive)
|
||||
// 3. For each one, use StackCount if it exists, otherwise treat as 1
|
||||
return inv.Where(wo =>
|
||||
string.Equals(wo.Name, itemName,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
.Sum(wo =>
|
||||
{
|
||||
// Some items (weapons, armor) aren’t stackable;
|
||||
// Values(LongValueKey.StackCount) throws if the key is absent.
|
||||
try
|
||||
{
|
||||
return wo.Values(LongValueKey.StackCount);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 1; // non-stackable item = quantity 1
|
||||
}
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the icon ID of a specific item by name
|
||||
/// </summary>
|
||||
/// <param name="itemName">Name of the item to find</param>
|
||||
/// <returns>Icon ID or 0 if not found</returns>
|
||||
public static int GetItemIcon(string itemName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = FindItemByName(itemName);
|
||||
return item?.Icon ?? 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the display icon ID (with 0x6000000 offset) for an item by name
|
||||
/// </summary>
|
||||
/// <param name="itemName">Name of the item to find</param>
|
||||
/// <returns>Display icon ID or 0x6002D14 (default icon) if not found</returns>
|
||||
public static int GetItemDisplayIcon(string itemName)
|
||||
{
|
||||
int rawIcon = GetItemIcon(itemName);
|
||||
return rawIcon != 0 ? rawIcon + 0x6000000 : 0x6002D14;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------
|
||||
* 5) Chest Looter helper methods
|
||||
* -------------------------------------------------------- */
|
||||
|
||||
/// <summary>
|
||||
/// Calculate 3D distance from player to a world object
|
||||
/// </summary>
|
||||
/// <param name="objectId">World object ID</param>
|
||||
/// <returns>Distance in meters, or float.MaxValue if object is invalid</returns>
|
||||
public static float GetDistanceToWorldObject(int objectId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CoreManager.Current.Actions.IsValidObject(objectId))
|
||||
return float.MaxValue;
|
||||
|
||||
Vector3 playerPos = GetPlayerPosition();
|
||||
Vector3 objectPos = GetWorldObjectPosition(objectId);
|
||||
|
||||
return Vector3.Distance(playerPos, objectPos);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return float.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the closest chest with the specified name in the game world
|
||||
/// </summary>
|
||||
/// <param name="chestName">Name of the chest to find</param>
|
||||
/// <returns>WorldObject of the closest chest, or null if not found</returns>
|
||||
public static WorldObject FindClosestChestByName(string chestName)
|
||||
{
|
||||
try
|
||||
{
|
||||
WorldObject closestChest = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
// Search all objects in WorldFilter
|
||||
using (var objects = CoreManager.Current.WorldFilter.GetAll())
|
||||
{
|
||||
foreach (WorldObject wo in objects)
|
||||
{
|
||||
// Check if this is a container (chest)
|
||||
if (wo.ObjectClass != ObjectClass.Container)
|
||||
continue;
|
||||
|
||||
// Check if name matches (case-insensitive, partial match allowed)
|
||||
if (!wo.Name.Contains(chestName) &&
|
||||
!string.Equals(wo.Name, chestName, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
// Calculate distance
|
||||
float distance = GetDistanceToWorldObject(wo.Id);
|
||||
|
||||
// Update closest if this is nearer
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestChest = wo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestChest;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a key in the player's inventory by name
|
||||
/// </summary>
|
||||
/// <param name="keyName">Name of the key to find</param>
|
||||
/// <returns>WorldObject of the key, or null if not found</returns>
|
||||
public static WorldObject FindKeyInInventory(string keyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
|
||||
{
|
||||
// Check if this is a key
|
||||
if (wo.ObjectClass != ObjectClass.Key)
|
||||
continue;
|
||||
|
||||
// Check if name matches (case-insensitive, partial match allowed)
|
||||
if (wo.Name.Contains(keyName) ||
|
||||
string.Equals(wo.Name, keyName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return wo;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,262 +0,0 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//File: ViewSystemSelector.cs
|
||||
//
|
||||
//Description: Contains the MyClasses.MetaViewWrappers.ViewSystemSelector class,
|
||||
// which is used to determine whether the Virindi View Service is enabled.
|
||||
// As with all the VVS wrappers, the VVS_REFERENCED compilation symbol must be
|
||||
// defined for the VVS code to be compiled. Otherwise, only Decal views are used.
|
||||
//
|
||||
//References required:
|
||||
// VirindiViewService (if VVS_REFERENCED is defined)
|
||||
// Decal.Adapter
|
||||
// Decal.Interop.Core
|
||||
//
|
||||
//This file is Copyright (c) 2009 VirindiPlugins
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
|
||||
#if METAVIEW_PUBLIC_NS
|
||||
namespace MetaViewWrappers
|
||||
#else
|
||||
namespace MyClasses.MetaViewWrappers
|
||||
#endif
|
||||
{
|
||||
internal static class ViewSystemSelector
|
||||
{
|
||||
public enum eViewSystem
|
||||
{
|
||||
DecalInject,
|
||||
VirindiViewService,
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////System presence detection///////////////////////////////
|
||||
|
||||
public static bool IsPresent(Decal.Adapter.Wrappers.PluginHost pHost, eViewSystem VSystem)
|
||||
{
|
||||
switch (VSystem)
|
||||
{
|
||||
case eViewSystem.DecalInject:
|
||||
return true;
|
||||
case eViewSystem.VirindiViewService:
|
||||
return VirindiViewsPresent(pHost);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static bool VirindiViewsPresent(Decal.Adapter.Wrappers.PluginHost pHost)
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
System.Reflection.Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (System.Reflection.Assembly a in asms)
|
||||
{
|
||||
AssemblyName nmm = a.GetName();
|
||||
if ((nmm.Name == "VirindiViewService") && (nmm.Version >= new System.Version("1.0.0.37")))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Curtain_VVS_Running();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
public static bool VirindiViewsPresent(Decal.Adapter.Wrappers.PluginHost pHost, Version minver)
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
System.Reflection.Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (System.Reflection.Assembly a in asms)
|
||||
{
|
||||
AssemblyName nm = a.GetName();
|
||||
if ((nm.Name == "VirindiViewService") && (nm.Version >= minver))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Curtain_VVS_Running();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if VVS_REFERENCED
|
||||
static bool Curtain_VVS_Running()
|
||||
{
|
||||
return VirindiViewService.Service.Running;
|
||||
}
|
||||
#endif
|
||||
|
||||
///////////////////////////////CreateViewResource///////////////////////////////
|
||||
|
||||
public static IView CreateViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource)
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
if (IsPresent(pHost, eViewSystem.VirindiViewService))
|
||||
return CreateViewResource(pHost, pXMLResource, eViewSystem.VirindiViewService);
|
||||
else
|
||||
#endif
|
||||
return CreateViewResource(pHost, pXMLResource, eViewSystem.DecalInject);
|
||||
}
|
||||
public static IView CreateViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource, eViewSystem VSystem)
|
||||
{
|
||||
if (!IsPresent(pHost, VSystem)) return null;
|
||||
switch (VSystem)
|
||||
{
|
||||
case eViewSystem.DecalInject:
|
||||
return CreateDecalViewResource(pHost, pXMLResource);
|
||||
case eViewSystem.VirindiViewService:
|
||||
#if VVS_REFERENCED
|
||||
return CreateMyHudViewResource(pHost, pXMLResource);
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static IView CreateDecalViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource)
|
||||
{
|
||||
IView ret = new DecalControls.View();
|
||||
ret.Initialize(pHost, pXMLResource);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if VVS_REFERENCED
|
||||
static IView CreateMyHudViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource)
|
||||
{
|
||||
IView ret = new VirindiViewServiceHudControls.View();
|
||||
ret.Initialize(pHost, pXMLResource);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////CreateViewXML///////////////////////////////
|
||||
|
||||
public static IView CreateViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML)
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
if (IsPresent(pHost, eViewSystem.VirindiViewService))
|
||||
return CreateViewXML(pHost, pXML, eViewSystem.VirindiViewService);
|
||||
else
|
||||
#endif
|
||||
return CreateViewXML(pHost, pXML, eViewSystem.DecalInject);
|
||||
}
|
||||
|
||||
public static IView CreateViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML, eViewSystem VSystem)
|
||||
{
|
||||
if (!IsPresent(pHost, VSystem)) return null;
|
||||
switch (VSystem)
|
||||
{
|
||||
case eViewSystem.DecalInject:
|
||||
return CreateDecalViewXML(pHost, pXML);
|
||||
case eViewSystem.VirindiViewService:
|
||||
#if VVS_REFERENCED
|
||||
return CreateMyHudViewXML(pHost, pXML);
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static IView CreateDecalViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML)
|
||||
{
|
||||
IView ret = new DecalControls.View();
|
||||
ret.InitializeRawXML(pHost, pXML);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if VVS_REFERENCED
|
||||
static IView CreateMyHudViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML)
|
||||
{
|
||||
IView ret = new VirindiViewServiceHudControls.View();
|
||||
ret.InitializeRawXML(pHost, pXML);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////HasChatOpen///////////////////////////////
|
||||
|
||||
public static bool AnySystemHasChatOpen(Decal.Adapter.Wrappers.PluginHost pHost)
|
||||
{
|
||||
if (IsPresent(pHost, eViewSystem.VirindiViewService))
|
||||
if (HasChatOpen_VirindiViews()) return true;
|
||||
if (pHost.Actions.ChatState) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HasChatOpen_VirindiViews()
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
if (VirindiViewService.HudView.FocusControl != null)
|
||||
{
|
||||
if (VirindiViewService.HudView.FocusControl.GetType() == typeof(VirindiViewService.Controls.HudTextBox))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public delegate void delConditionalSplit(object data);
|
||||
public static void ViewConditionalSplit(IView v, delConditionalSplit onDecal, delConditionalSplit onVVS, object data)
|
||||
{
|
||||
Type vtype = v.GetType();
|
||||
|
||||
#if VVS_REFERENCED
|
||||
if (vtype == typeof(VirindiViewServiceHudControls.View))
|
||||
{
|
||||
if (onVVS != null)
|
||||
onVVS(data);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (vtype == typeof(DecalControls.View))
|
||||
{
|
||||
if (onDecal != null)
|
||||
onDecal(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
MosswartMassacre/ViewXML/flagTracker.xml
Normal file
74
MosswartMassacre/ViewXML/flagTracker.xml
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<view icon="7735" title="Mossy Tracker v4.0.0.5" width="800" height="600">
|
||||
<control progid="DecalControls.Notebook" name="mainTabView">
|
||||
|
||||
<!-- Augmentations Tab -->
|
||||
<page label="Augs">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<control progid="DecalControls.PushButton" name="btnRefreshAugs" left="10" top="10" width="100" height="24" text="Refresh"/>
|
||||
<control progid="DecalControls.List" name="lstAugmentations" left="10" top="40" width="760" height="520">
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="200" name="Augmentation" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="100" name="Progress" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="200" name="Trainer" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="260" name="Location" />
|
||||
</control>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
<!-- Luminance Auras Tab -->
|
||||
<page label="Lum">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<control progid="DecalControls.PushButton" name="btnRefreshLum" left="10" top="10" width="100" height="24" text="Refresh"/>
|
||||
<control progid="DecalControls.List" name="lstLuminanceAuras" left="10" top="40" width="760" height="520">
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="300" name="Luminance Aura" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="150" name="Progress" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="310" name="Category" />
|
||||
</control>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
<!-- Recall Spells Tab -->
|
||||
<page label="Recalls">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<control progid="DecalControls.PushButton" name="btnRefreshRecalls" left="10" top="10" width="100" height="24" text="Refresh"/>
|
||||
<control progid="DecalControls.List" name="lstRecallSpells" left="10" top="40" width="760" height="520">
|
||||
<column progid="DecalControls.IconColumn" fixedwidth="20" name="Icon" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="500" name="Recall Spell" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="240" name="Status" />
|
||||
</control>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
<!-- Cantrips Tab -->
|
||||
<page label="Cantrips">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<control progid="DecalControls.PushButton" name="btnRefreshCantrips" left="10" top="10" width="100" height="24" text="Refresh"/>
|
||||
|
||||
<control progid="DecalControls.List" name="lstCantrips" left="10" top="40" width="760" height="520">
|
||||
<column progid="DecalControls.IconColumn" fixedwidth="20" name="Icon" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="520" name="Skill/Effect" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="220" name="Cantrip Level" />
|
||||
</control>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
|
||||
<!-- Quests Tab -->
|
||||
<page label="Quests">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<control progid="DecalControls.PushButton" name="btnRefreshQuests" left="10" top="10" width="100" height="24" text="Refresh Quests"/>
|
||||
|
||||
<control progid="DecalControls.List" name="lstQuests" left="10" top="40" width="760" height="520">
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="200" name="Quest" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="80" name="Solves" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="100" name="Completed" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="80" name="Max" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="100" name="Delta" />
|
||||
<column progid="DecalControls.TextColumn" fixedwidth="200" name="Expire" />
|
||||
</control>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
|
||||
</control>
|
||||
</view>
|
||||
159
MosswartMassacre/ViewXML/mainViewTabbed.xml
Normal file
159
MosswartMassacre/ViewXML/mainViewTabbed.xml
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<view icon="7735" title="Mosswart Massacre v4.0.0.5" width="420" height="320">
|
||||
<control progid="DecalControls.Notebook" name="MainNotebook">
|
||||
<page label="Main">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<!-- Current kill tracking display -->
|
||||
<control progid="DecalControls.StaticText" name="lblTotalKills" left="10" top="10" width="250" height="20" text="Total Kills: 0"/>
|
||||
<control progid="DecalControls.StaticText" name="lblKillsPer5Min" left="10" top="30" width="250" height="20" text="Kills per 5 Min: 0"/>
|
||||
<control progid="DecalControls.StaticText" name="lblKillsPerHour" left="10" top="50" width="250" height="20" text="Kills per Hour: 0"/>
|
||||
<control progid="DecalControls.StaticText" name="lblElapsedTime" left="10" top="70" width="250" height="20" text="Time: 00:00:00"/>
|
||||
<control progid="DecalControls.StaticText" name="lblRareCount" left="10" top="90" width="250" height="20" text="Rare Count: 0"/>
|
||||
|
||||
<!-- Auto loot rare indicator -->
|
||||
<control progid="DecalControls.StaticText" name="lblAutoLootRare" left="10" top="120" width="200" height="20" text="Auto Loot Rare: [ON]"/>
|
||||
|
||||
<!-- Enhanced status display -->
|
||||
<control progid="DecalControls.StaticText" name="lblStatus" left="10" top="145" width="380" height="20" text="Status: Ready"/>
|
||||
<control progid="DecalControls.StaticText" name="lblWebSocketStatus" left="10" top="165" width="380" height="20" text="WebSocket: [DISCONNECTED]"/>
|
||||
|
||||
<!-- Update controls on Main tab -->
|
||||
<control progid="DecalControls.PushButton" name="btnCheckUpdate" left="10" top="190" width="120" height="25" text="Check for Updates"/>
|
||||
<control progid="DecalControls.PushButton" name="btnInstallUpdate" left="140" top="190" width="120" height="25" text="Install Update"/>
|
||||
<control progid="DecalControls.StaticText" name="lblUpdateStatus" left="10" top="220" width="250" height="20" text=""/>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
<page label="Settings">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<!-- Plugin settings -->
|
||||
<control progid="DecalControls.StaticText" name="lblSettingsHeader" left="10" top="10" width="200" height="20" text="Plugin Configuration" style="FontBold"/>
|
||||
|
||||
<!-- Meta state setting -->
|
||||
<control progid="DecalControls.Checkbox" name="chkRareMetaEnabled" left="20" top="35" width="300" height="20" text="Auto rare meta state" checked="true"/>
|
||||
|
||||
<!-- Remote commands setting -->
|
||||
<control progid="DecalControls.Checkbox" name="chkRemoteCommandsEnabled" left="20" top="60" width="300" height="20" text="Remote commands (!do/!dot)" checked="false"/>
|
||||
|
||||
<!-- HTTP server setting -->
|
||||
<control progid="DecalControls.Checkbox" name="chkHttpServerEnabled" left="20" top="85" width="300" height="20" text="HTTP server (port 8085)" checked="false"/>
|
||||
|
||||
<!-- WebSocket setting -->
|
||||
<control progid="DecalControls.Checkbox" name="chkWebSocketEnabled" left="20" top="110" width="300" height="20" text="WebSocket streaming" checked="false"/>
|
||||
|
||||
<!-- Telemetry setting -->
|
||||
<control progid="DecalControls.Checkbox" name="chkTelemetryEnabled" left="20" top="135" width="300" height="20" text="Telemetry reporting" checked="false"/>
|
||||
|
||||
<!-- Character tag setting -->
|
||||
<control progid="DecalControls.StaticText" name="lblCharTag" left="20" top="165" width="100" height="16" text="Character Tag:"/>
|
||||
<control progid="DecalControls.Edit" name="txtCharTag" left="125" top="163" width="150" height="20" text="default"/>
|
||||
|
||||
<!-- VTank profiles path setting -->
|
||||
<control progid="DecalControls.StaticText" name="lblVTankPath" left="20" top="190" width="100" height="16" text="VTank Profiles:"/>
|
||||
<control progid="DecalControls.Edit" name="txtVTankPath" left="125" top="188" width="200" height="20" text=""/>
|
||||
<control progid="DecalControls.StaticText" name="lblVTankPathHelp" left="20" top="210" width="350" height="16" text="Leave empty for auto-detection."/>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
<page label="Statistics">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<!-- Enhanced statistics display -->
|
||||
<control progid="DecalControls.StaticText" name="lblStatsHeader" left="10" top="10" width="200" height="20" text="Detailed Statistics" style="FontBold"/>
|
||||
|
||||
<!-- Kill statistics -->
|
||||
<control progid="DecalControls.StaticText" name="lblDetailedKills" left="10" top="35" width="180" height="16" text="Total Kills This Session:"/>
|
||||
<control progid="DecalControls.StaticText" name="lblDetailedKillsValue" left="200" top="35" width="100" height="16" text="0"/>
|
||||
|
||||
<control progid="DecalControls.StaticText" name="lblBestHour" left="10" top="55" width="180" height="16" text="Best Hour Performance:"/>
|
||||
<control progid="DecalControls.StaticText" name="lblBestHourValue" left="200" top="55" width="100" height="16" text="0 kills/hr"/>
|
||||
|
||||
<control progid="DecalControls.StaticText" name="lblAverageKills" left="10" top="75" width="180" height="16" text="Average Kills/Hour:"/>
|
||||
<control progid="DecalControls.StaticText" name="lblAverageKillsValue" left="200" top="75" width="100" height="16" text="0 kills/hr"/>
|
||||
|
||||
<!-- Rare statistics -->
|
||||
<control progid="DecalControls.StaticText" name="lblRareStats" left="10" top="105" width="180" height="16" text="Rares Found:"/>
|
||||
<control progid="DecalControls.StaticText" name="lblRareStatsValue" left="200" top="105" width="100" height="16" text="0"/>
|
||||
|
||||
<control progid="DecalControls.StaticText" name="lblRareRate" left="10" top="125" width="180" height="16" text="Rare Drop Rate:"/>
|
||||
<control progid="DecalControls.StaticText" name="lblRareRateValue" left="200" top="125" width="100" height="16" text="0.00%"/>
|
||||
|
||||
<!-- Time statistics -->
|
||||
<control progid="DecalControls.StaticText" name="lblSessionTime" left="10" top="155" width="180" height="16" text="Session Duration:"/>
|
||||
<control progid="DecalControls.StaticText" name="lblSessionTimeValue" left="200" top="155" width="100" height="16" text="00:00:00"/>
|
||||
|
||||
<control progid="DecalControls.StaticText" name="lblLastKill" left="10" top="175" width="180" height="16" text="Last Kill:"/>
|
||||
<control progid="DecalControls.StaticText" name="lblLastKillValue" left="200" top="175" width="100" height="16" text="Never"/>
|
||||
|
||||
<!-- Reset button -->
|
||||
<control progid="DecalControls.PushButton" name="btnResetStats" left="10" top="205" width="80" height="24" text="Reset All"/>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
<page label="Navigation">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<!-- Navigation visualization controls -->
|
||||
<control progid="DecalControls.StaticText" name="lblNavHeader" left="10" top="10" width="250" height="20" text="Route Comparison Visualization" style="FontBold"/>
|
||||
|
||||
<!-- Enable/disable visualization -->
|
||||
<control progid="DecalControls.Checkbox" name="chkNavVisualizationEnabled" left="20" top="35" width="300" height="20" text="Show route visualization" checked="false"/>
|
||||
|
||||
<!-- Nav file selection -->
|
||||
<control progid="DecalControls.StaticText" name="lblNavFile" left="20" top="65" width="120" height="16" text="Route:"/>
|
||||
<control progid="DecalControls.Choice" name="cmbNavFiles" left="145" top="63" width="200" height="20"/>
|
||||
<control progid="DecalControls.PushButton" name="btnRefreshNavFiles" left="350" top="62" width="60" height="22" text="Refresh"/>
|
||||
|
||||
<!-- Route status -->
|
||||
<control progid="DecalControls.StaticText" name="lblNavStatus" left="20" top="95" width="350" height="16" text="Status: No route loaded"/>
|
||||
|
||||
<!-- Color indicator -->
|
||||
<control progid="DecalControls.StaticText" name="lblNavColorInfo" left="20" top="115" width="350" height="16" text="Route displays in red"/>
|
||||
|
||||
<!-- Instructions -->
|
||||
<control progid="DecalControls.StaticText" name="lblNavInstructions" left="10" top="145" width="370" height="40" text="Visualize VTank nav files alongside UtilityBelt routes. Compare different paths and plan optimal routes."/>
|
||||
|
||||
<!-- Control buttons -->
|
||||
<control progid="DecalControls.PushButton" name="btnLoadRoute" left="20" top="210" width="90" height="24" text="Load Route"/>
|
||||
<control progid="DecalControls.PushButton" name="btnClearRoute" left="110" top="210" width="90" height="24" text="Clear Route"/>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
<page label="Mossy Tracker">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<!-- Launch button -->
|
||||
<control progid="DecalControls.PushButton" name="btnOpenFlagTracker" left="10" top="10" width="150" height="30" text="Open Mossy Tracker"/>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
<page label="Chest Looter">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<!-- Header -->
|
||||
<control progid="DecalControls.StaticText" name="lblChestLooterHeader" left="10" top="10" width="250" height="20" text="Automated Chest Looting" style="FontBold"/>
|
||||
|
||||
<!-- Chest Name Configuration -->
|
||||
<control progid="DecalControls.StaticText" name="lblChestNameLabel" left="20" top="40" width="100" height="16" text="Chest Name:"/>
|
||||
<control progid="DecalControls.Edit" name="txtChestName" left="125" top="38" width="180" height="20" text=""/>
|
||||
<control progid="DecalControls.PushButton" name="btnSetChest" left="310" top="37" width="100" height="22" text="Set from Selection"/>
|
||||
|
||||
<!-- Key Name Configuration -->
|
||||
<control progid="DecalControls.StaticText" name="lblKeyNameLabel" left="20" top="70" width="100" height="16" text="Key Name:"/>
|
||||
<control progid="DecalControls.Edit" name="txtKeyName" left="125" top="68" width="180" height="20" text=""/>
|
||||
<control progid="DecalControls.PushButton" name="btnSetKey" left="310" top="67" width="100" height="22" text="Set from Selection"/>
|
||||
|
||||
<!-- Control Buttons -->
|
||||
<control progid="DecalControls.PushButton" name="btnStartLooter" left="20" top="105" width="120" height="30" text="Start Looting"/>
|
||||
<control progid="DecalControls.PushButton" name="btnStopLooter" left="145" top="105" width="120" height="30" text="Stop"/>
|
||||
|
||||
<!-- Settings -->
|
||||
<control progid="DecalControls.StaticText" name="lblLooterSettingsHeader" left="10" top="145" width="200" height="16" text="Options" style="FontBold"/>
|
||||
<control progid="DecalControls.Checkbox" name="chkEnableChests" left="20" top="165" width="200" height="20" text="Enable Chest Looting" checked="true"/>
|
||||
|
||||
<!-- Status Display -->
|
||||
<control progid="DecalControls.StaticText" name="lblLooterStatus" left="10" top="195" width="380" height="20" text="Status: Ready"/>
|
||||
|
||||
<!-- Instructions -->
|
||||
<control progid="DecalControls.StaticText" name="lblLooterInstructions" left="10" top="220" width="380" height="30" text="Set chest/key names, then click Start. Uses VTank loot profile. Commands: /mm setchest, /mm setkey, /mm lootchest"/>
|
||||
</control>
|
||||
</page>
|
||||
|
||||
</control>
|
||||
</view>
|
||||
863
MosswartMassacre/Views/FlagTrackerView.cs
Normal file
863
MosswartMassacre/Views/FlagTrackerView.cs
Normal file
|
|
@ -0,0 +1,863 @@
|
|||
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;
|
||||
|
||||
// Quests Tab
|
||||
private HudList lstQuests;
|
||||
private HudButton btnRefreshQuests;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Management
|
||||
private FlagTrackerData data;
|
||||
private System.Timers.Timer questUpdateTimer;
|
||||
#endregion
|
||||
|
||||
public FlagTrackerView(PluginCore core) : base(core)
|
||||
{
|
||||
try
|
||||
{
|
||||
instance = this;
|
||||
data = new FlagTrackerData();
|
||||
|
||||
// Initialize quest update timer for real-time countdown
|
||||
questUpdateTimer = new System.Timers.Timer(5000); // Update every 5 seconds
|
||||
questUpdateTimer.Elapsed += OnQuestTimerUpdate;
|
||||
questUpdateTimer.AutoReset = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Failed to initialize: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#region Static Interface
|
||||
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;
|
||||
}
|
||||
|
||||
public static void RefreshQuestData()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PluginCore.questManager != null)
|
||||
{
|
||||
PluginCore.questManager.RefreshQuests();
|
||||
|
||||
// If Flag Tracker window is open, also refresh the UI
|
||||
if (instance != null)
|
||||
{
|
||||
instance.PopulateQuestsList();
|
||||
|
||||
// Schedule another refresh in a few seconds to catch any data
|
||||
System.Threading.Timer refreshTimer = null;
|
||||
refreshTimer = new System.Threading.Timer(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
instance.PopulateQuestsList();
|
||||
}
|
||||
}
|
||||
catch (Exception timerEx)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Delayed refresh failed: {timerEx.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
refreshTimer?.Dispose();
|
||||
}
|
||||
}, null, 4000, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginCore.WriteToChat("[MossyTracker] Quest manager not available for refresh");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Quest refresh failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
private void InitializeView()
|
||||
{
|
||||
try
|
||||
{
|
||||
CreateFromXMLResource("MosswartMassacre.ViewXML.flagTracker.xml");
|
||||
|
||||
if (view == null)
|
||||
{
|
||||
PluginCore.WriteToChat("[MossyTracker] Failed to create view");
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeTabControls();
|
||||
InitializeEventHandlers();
|
||||
Initialize();
|
||||
|
||||
if (view != null)
|
||||
{
|
||||
view.Visible = true;
|
||||
view.ShowInBar = true;
|
||||
view.Title = "Mossy Tracker v5.0.0.0";
|
||||
}
|
||||
|
||||
RefreshAllData();
|
||||
|
||||
// Start quest update timer
|
||||
if (questUpdateTimer != null)
|
||||
{
|
||||
questUpdateTimer.Start();
|
||||
}
|
||||
|
||||
PluginCore.WriteToChat("[MossyTracker] Initialized successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Failed to initialize: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTabControls()
|
||||
{
|
||||
try
|
||||
{
|
||||
mainTabView = GetControl<HudTabView>("mainTabView");
|
||||
lstAugmentations = GetControl<HudList>("lstAugmentations");
|
||||
btnRefreshAugs = GetControl<HudButton>("btnRefreshAugs");
|
||||
lstLuminanceAuras = GetControl<HudList>("lstLuminanceAuras");
|
||||
btnRefreshLum = GetControl<HudButton>("btnRefreshLum");
|
||||
lstRecallSpells = GetControl<HudList>("lstRecallSpells");
|
||||
btnRefreshRecalls = GetControl<HudButton>("btnRefreshRecalls");
|
||||
lstCantrips = GetControl<HudList>("lstCantrips");
|
||||
btnRefreshCantrips = GetControl<HudButton>("btnRefreshCantrips");
|
||||
lstQuests = GetControl<HudList>("lstQuests");
|
||||
btnRefreshQuests = GetControl<HudButton>("btnRefreshQuests");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Failed to initialize 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 (btnRefreshQuests != null) btnRefreshQuests.Hit += OnRefreshQuests;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error initializing event handlers: {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Window Management Overrides
|
||||
protected override void View_VisibleChanged(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Call base implementation first
|
||||
base.View_VisibleChanged(sender, e);
|
||||
|
||||
// If window becomes invisible and we're not already disposed, dispose it
|
||||
// This handles the X button click case
|
||||
if (view != null && !view.Visible && !_disposed)
|
||||
{
|
||||
// Use a small delay to ensure this isn't just a temporary hide
|
||||
System.Threading.Timer disposeTimer = null;
|
||||
disposeTimer = new System.Threading.Timer(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Double-check the window is still hidden and not disposed
|
||||
if (view != null && !view.Visible && !_disposed)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error in delayed disposal: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
disposeTimer?.Dispose();
|
||||
}
|
||||
}, null, 100, System.Threading.Timeout.Infinite); // 100ms delay
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error in FlagTracker VisibleChanged: {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Timer Event Handlers
|
||||
private void OnQuestTimerUpdate(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Update quest list display if quests tab is visible and we have quest data
|
||||
if (view != null && view.Visible && PluginCore.questManager?.QuestList != null && PluginCore.questManager.QuestList.Count > 0)
|
||||
{
|
||||
// Only update the quest list to refresh countdown timers
|
||||
PopulateQuestsList();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Silently handle timer update errors to avoid spam
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
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 OnRefreshQuests(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PluginCore.questManager != null)
|
||||
{
|
||||
PluginCore.questManager.RefreshQuests();
|
||||
PopulateQuestsList();
|
||||
|
||||
// Schedule another refresh in a few seconds to catch any data
|
||||
System.Threading.Timer refreshTimer = null;
|
||||
refreshTimer = new System.Threading.Timer(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
PopulateQuestsList();
|
||||
}
|
||||
catch (Exception timerEx)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Refresh failed: {timerEx.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
refreshTimer?.Dispose();
|
||||
}
|
||||
}, null, 4000, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginCore.WriteToChat("[MossyTracker] Quest manager not available");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Refresh failed: {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 ?? "";
|
||||
}
|
||||
// Column control is null - ignore silently
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
// Column doesn't exist - ignore silently
|
||||
}
|
||||
}
|
||||
// Invalid parameters - ignore silently
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore text setting errors silently
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatCountdown(long seconds)
|
||||
{
|
||||
if (seconds <= 0)
|
||||
return "READY";
|
||||
|
||||
var timeSpan = TimeSpan.FromSeconds(seconds);
|
||||
|
||||
if (timeSpan.TotalDays >= 1)
|
||||
return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours:D2}h";
|
||||
else if (timeSpan.TotalHours >= 1)
|
||||
return $"{timeSpan.Hours}h {timeSpan.Minutes:D2}m";
|
||||
else if (timeSpan.TotalMinutes >= 1)
|
||||
return $"{timeSpan.Minutes}m {timeSpan.Seconds:D2}s";
|
||||
else
|
||||
return $"{timeSpan.Seconds}s";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Population Methods
|
||||
private void RefreshAllData()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PluginCore.questManager != null)
|
||||
{
|
||||
PluginCore.questManager.RefreshQuests();
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
data.RefreshAll();
|
||||
}
|
||||
catch (Exception dataEx)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Data refresh failed: {dataEx.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
PopulateAugmentationsList();
|
||||
PopulateLuminanceList();
|
||||
PopulateRecallsList();
|
||||
PopulateCantripsList();
|
||||
PopulateQuestsList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[MossyTracker] Refresh failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateAugmentationsList()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lstAugmentations == null) return;
|
||||
|
||||
lstAugmentations.ClearRows();
|
||||
|
||||
if (data?.AugmentationCategories == null)
|
||||
{
|
||||
var row = lstAugmentations.AddRow();
|
||||
SafeSetListText(row, 0, "No augmentation data available");
|
||||
SafeSetListText(row, 1, "Click Refresh to load data");
|
||||
SafeSetListText(row, 2, "");
|
||||
SafeSetListText(row, 3, "");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var category in data.AugmentationCategories)
|
||||
{
|
||||
// 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) return;
|
||||
|
||||
lstRecallSpells.ClearRows();
|
||||
|
||||
if (data?.RecallSpells == null)
|
||||
{
|
||||
var row = lstRecallSpells.AddRow();
|
||||
SafeSetListImage(row, 0, 0x6002D14); // Default portal icon
|
||||
SafeSetListText(row, 1, "No recall data available - Click Refresh");
|
||||
SafeSetListText(row, 2, "Loading...");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var recall in data.RecallSpells)
|
||||
{
|
||||
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 PopulateQuestsList()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lstQuests == null) return;
|
||||
|
||||
lstQuests.ClearRows();
|
||||
|
||||
// Add column headers - New order: Quest Name, Countdown, Last Solved, Cooldown, Solves
|
||||
var headerRow = lstQuests.AddRow();
|
||||
SafeSetListText(headerRow, 0, "--- Quest Name ---");
|
||||
SafeSetListText(headerRow, 1, "Countdown");
|
||||
SafeSetListText(headerRow, 2, "Last Solved");
|
||||
SafeSetListText(headerRow, 3, "Cooldown");
|
||||
SafeSetListText(headerRow, 4, "Solves");
|
||||
|
||||
if (PluginCore.questManager?.QuestList != null && PluginCore.questManager.QuestList.Count > 0)
|
||||
{
|
||||
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
// Filter out maxed quests and sort by solve count (highest to lowest)
|
||||
var visibleQuests = PluginCore.questManager.QuestList
|
||||
.Where(q => !(q.MaxSolves > 0 && q.Solves >= q.MaxSolves)) // Hide maxed quests
|
||||
.OrderByDescending(q => q.Solves)
|
||||
.ThenBy(q => PluginCore.questManager.GetFriendlyQuestName(q.Id));
|
||||
|
||||
foreach (var quest in visibleQuests)
|
||||
{
|
||||
var questRow = lstQuests.AddRow();
|
||||
|
||||
// Column 0: Quest Name (friendly name only, wider)
|
||||
string questName = PluginCore.questManager.GetFriendlyQuestName(quest.Id);
|
||||
SafeSetListText(questRow, 0, questName);
|
||||
|
||||
// Column 1: Countdown Timer
|
||||
long timeRemaining = quest.ExpireTime - currentTime;
|
||||
string countdownText = FormatCountdown(timeRemaining);
|
||||
SafeSetListText(questRow, 1, countdownText);
|
||||
|
||||
// Column 2: Last Solved (date) - moved from Column 3
|
||||
SafeSetListText(questRow, 2, PluginCore.questManager.FormatTimeStamp(quest.Timestamp));
|
||||
|
||||
// Column 3: Cooldown (formatted duration) - moved from Column 4
|
||||
SafeSetListText(questRow, 3, PluginCore.questManager.FormatSeconds(quest.Delta));
|
||||
|
||||
// Column 4: Solves (white text) - moved from Column 5
|
||||
SafeSetListText(questRow, 4, quest.Solves.ToString());
|
||||
SafeSetListColor(questRow, 4, System.Drawing.Color.White);
|
||||
|
||||
// Color code the countdown based on availability
|
||||
if (quest.ExpireTime <= currentTime)
|
||||
{
|
||||
SafeSetListColor(questRow, 1, System.Drawing.Color.Green); // Ready - green countdown
|
||||
}
|
||||
else
|
||||
{
|
||||
SafeSetListColor(questRow, 1, System.Drawing.Color.Yellow); // On cooldown - yellow countdown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error populating quests list: {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Cleanup
|
||||
private bool _disposed = false;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return; // Prevent double disposal
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Clear static instance reference if this is the current instance
|
||||
if (instance == this)
|
||||
{
|
||||
instance = null;
|
||||
}
|
||||
|
||||
// Event handlers will be cleaned up by base class
|
||||
|
||||
// Remove button event handlers
|
||||
if (btnRefreshAugs != null) btnRefreshAugs.Hit -= OnRefreshAugmentations;
|
||||
if (btnRefreshLum != null) btnRefreshLum.Hit -= OnRefreshLuminance;
|
||||
if (btnRefreshRecalls != null) btnRefreshRecalls.Hit -= OnRefreshRecalls;
|
||||
if (btnRefreshCantrips != null) btnRefreshCantrips.Hit -= OnRefreshCantrips;
|
||||
if (btnRefreshQuests != null) btnRefreshQuests.Hit -= OnRefreshQuests;
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
data.Dispose();
|
||||
data = null;
|
||||
}
|
||||
|
||||
// Stop and dispose quest update timer
|
||||
if (questUpdateTimer != null)
|
||||
{
|
||||
questUpdateTimer.Stop();
|
||||
questUpdateTimer.Elapsed -= OnQuestTimerUpdate;
|
||||
questUpdateTimer.Dispose();
|
||||
questUpdateTimer = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error disposing Flag Tracker: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
395
MosswartMassacre/Views/VVSBaseView.cs
Normal file
395
MosswartMassacre/Views/VVSBaseView.cs
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Timers;
|
||||
using VirindiViewService;
|
||||
using VirindiViewService.XMLParsers;
|
||||
|
||||
namespace MosswartMassacre.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for VVS (VirindiViewService) based views.
|
||||
/// Replaces the wrapper-based BaseView with direct VVS integration.
|
||||
/// </summary>
|
||||
public class VVSBaseView : IDisposable
|
||||
{
|
||||
#region Windows API for boundary checking
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
public int Width { get { return Right - Left; } }
|
||||
public int Height { get { return Bottom - Top; } }
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
|
||||
#endregion
|
||||
|
||||
#region Core VVS Components
|
||||
protected HudView view;
|
||||
protected ViewProperties properties;
|
||||
protected ControlGroup controls;
|
||||
protected PluginCore pluginCore;
|
||||
#endregion
|
||||
|
||||
#region Position Management
|
||||
private Timer positionSaveTimer;
|
||||
#endregion
|
||||
|
||||
public VVSBaseView(PluginCore core)
|
||||
{
|
||||
pluginCore = core;
|
||||
InitializePositionTimer();
|
||||
}
|
||||
|
||||
#region VVS Initialization
|
||||
protected void CreateFromXMLResource(string resourcePath, bool doIcon = true, bool doTitle = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse XML using VVS Decal3XMLParser
|
||||
new Decal3XMLParser().ParseFromResource(resourcePath, out properties, out controls);
|
||||
|
||||
// Set window properties
|
||||
if (doTitle)
|
||||
{
|
||||
properties.Title = "Mosswart Massacre v5.0.0.0";
|
||||
}
|
||||
|
||||
if (doIcon)
|
||||
{
|
||||
// Use default icon for now - can be customized later
|
||||
properties.Icon = 7735; // Same icon as in XML
|
||||
}
|
||||
|
||||
// Create the HudView
|
||||
view = new HudView(properties, controls);
|
||||
|
||||
// Subscribe to essential events
|
||||
view.VisibleChanged += View_VisibleChanged;
|
||||
view.Moved += View_Moved;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error creating VVS view from {resourcePath}: {ex.Message}");
|
||||
PluginCore.WriteToChat($"Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
protected void CreateFromXMLString(string xmlString, bool doIcon = true, bool doTitle = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse XML string using VVS Decal3XMLParser
|
||||
new Decal3XMLParser().Parse(xmlString, out properties, out controls);
|
||||
|
||||
if (doTitle)
|
||||
{
|
||||
properties.Title = "Mosswart Massacre v5.0.0.0";
|
||||
}
|
||||
|
||||
if (doIcon)
|
||||
{
|
||||
properties.Icon = 7735;
|
||||
}
|
||||
|
||||
view = new HudView(properties, controls);
|
||||
view.VisibleChanged += View_VisibleChanged;
|
||||
view.Moved += View_Moved;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error creating VVS view from XML string: {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Control Access
|
||||
/// <summary>
|
||||
/// Get a control by name with proper type casting.
|
||||
/// Usage: var button = GetControl<HudButton>("btnExample");
|
||||
/// </summary>
|
||||
protected T GetControl<T>(string controlName) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
if (view != null && view[controlName] != null)
|
||||
{
|
||||
return view[controlName] as T;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error getting control '{controlName}': {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a control by name (alternative syntax)
|
||||
/// Usage: var button = (HudButton)GetControl("btnExample");
|
||||
/// </summary>
|
||||
protected object GetControl(string controlName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return view?[controlName];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error getting control '{controlName}': {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Window Management
|
||||
protected virtual void SaveWindowPosition()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (view != null && PluginSettings.Instance != null)
|
||||
{
|
||||
PluginSettings.Instance.MainWindowX = view.Location.X;
|
||||
PluginSettings.Instance.MainWindowY = view.Location.Y;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error saving window position: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RestoreWindowPosition()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (view != null && PluginSettings.Instance != null)
|
||||
{
|
||||
view.Location = new Point(
|
||||
PluginSettings.Instance.MainWindowX,
|
||||
PluginSettings.Instance.MainWindowY
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error restoring window position: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
protected void KeepWindowInBounds()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (view == null) return;
|
||||
|
||||
RECT rect = new RECT();
|
||||
IntPtr gameWindowHandle = PluginCore.MyHost?.Decal?.Hwnd ?? IntPtr.Zero;
|
||||
|
||||
if (gameWindowHandle != IntPtr.Zero && GetWindowRect(gameWindowHandle, ref rect))
|
||||
{
|
||||
Point currentLocation = view.Location;
|
||||
int viewWidth = view.Width;
|
||||
int viewHeight = view.Height;
|
||||
|
||||
bool needsUpdate = false;
|
||||
|
||||
// Check right boundary
|
||||
if (currentLocation.X + viewWidth > rect.Width)
|
||||
{
|
||||
currentLocation.X = rect.Width - viewWidth;
|
||||
needsUpdate = true;
|
||||
}
|
||||
// Check left boundary
|
||||
else if (currentLocation.X < 0)
|
||||
{
|
||||
currentLocation.X = 20;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
// Check bottom boundary
|
||||
if (currentLocation.Y + viewHeight > rect.Height)
|
||||
{
|
||||
currentLocation.Y = rect.Height - viewHeight;
|
||||
needsUpdate = true;
|
||||
}
|
||||
// Check top boundary
|
||||
else if (currentLocation.Y < 0)
|
||||
{
|
||||
currentLocation.Y = 20;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUpdate)
|
||||
{
|
||||
view.Location = currentLocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently ignore boundary check errors
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Position Timer Management
|
||||
private void InitializePositionTimer()
|
||||
{
|
||||
positionSaveTimer = new Timer(2000); // 2 second delay after movement stops
|
||||
positionSaveTimer.Elapsed += (s, e) => {
|
||||
SaveWindowPosition();
|
||||
positionSaveTimer.Stop();
|
||||
};
|
||||
}
|
||||
|
||||
private void View_Moved(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Reset timer when window moves
|
||||
if (positionSaveTimer != null)
|
||||
{
|
||||
if (positionSaveTimer.Enabled)
|
||||
positionSaveTimer.Stop();
|
||||
|
||||
positionSaveTimer.Start();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore timer errors
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void View_VisibleChanged(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (view.Visible)
|
||||
{
|
||||
KeepWindowInBounds();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore visibility change errors
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Interface
|
||||
public virtual void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
RestoreWindowPosition();
|
||||
KeepWindowInBounds();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error initializing VVS view: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Show()
|
||||
{
|
||||
if (view != null)
|
||||
{
|
||||
view.Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Hide()
|
||||
{
|
||||
if (view != null)
|
||||
{
|
||||
view.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Toggle()
|
||||
{
|
||||
if (view != null)
|
||||
{
|
||||
view.Visible = !view.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
get { return view?.Visible ?? false; }
|
||||
}
|
||||
|
||||
public Point Location
|
||||
{
|
||||
get { return view?.Location ?? Point.Empty; }
|
||||
set { if (view != null) view.Location = value; }
|
||||
}
|
||||
|
||||
public Size Size
|
||||
{
|
||||
get { return view != null ? new Size(view.Width, view.Height) : Size.Empty; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable Support
|
||||
private bool disposedValue = false;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Save final position before disposal
|
||||
SaveWindowPosition();
|
||||
|
||||
// Clean up timers
|
||||
if (positionSaveTimer != null)
|
||||
{
|
||||
positionSaveTimer.Stop();
|
||||
positionSaveTimer.Dispose();
|
||||
positionSaveTimer = null;
|
||||
}
|
||||
|
||||
// Clean up VVS view
|
||||
if (view != null)
|
||||
{
|
||||
view.VisibleChanged -= View_VisibleChanged;
|
||||
view.Moved -= View_Moved;
|
||||
view.Dispose();
|
||||
view = null;
|
||||
}
|
||||
|
||||
// Clean up VVS objects
|
||||
properties = null;
|
||||
controls = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"Error disposing VVS view: {ex.Message}");
|
||||
}
|
||||
}
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1299
MosswartMassacre/Views/VVSTabbedMainView.cs
Normal file
1299
MosswartMassacre/Views/VVSTabbedMainView.cs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,262 +0,0 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//File: ViewSystemSelector.cs
|
||||
//
|
||||
//Description: Contains the MyClasses.MetaViewWrappers.ViewSystemSelector class,
|
||||
// which is used to determine whether the Virindi View Service is enabled.
|
||||
// As with all the VVS wrappers, the VVS_REFERENCED compilation symbol must be
|
||||
// defined for the VVS code to be compiled. Otherwise, only Decal views are used.
|
||||
//
|
||||
//References required:
|
||||
// VirindiViewService (if VVS_REFERENCED is defined)
|
||||
// Decal.Adapter
|
||||
// Decal.Interop.Core
|
||||
//
|
||||
//This file is Copyright (c) 2009 VirindiPlugins
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
|
||||
#if METAVIEW_PUBLIC_NS
|
||||
namespace MetaViewWrappers
|
||||
#else
|
||||
namespace MyClasses.MetaViewWrappers
|
||||
#endif
|
||||
{
|
||||
internal static class ViewSystemSelector
|
||||
{
|
||||
public enum eViewSystem
|
||||
{
|
||||
DecalInject,
|
||||
VirindiViewService,
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////System presence detection///////////////////////////////
|
||||
|
||||
public static bool IsPresent(Decal.Adapter.Wrappers.PluginHost pHost, eViewSystem VSystem)
|
||||
{
|
||||
switch (VSystem)
|
||||
{
|
||||
case eViewSystem.DecalInject:
|
||||
return true;
|
||||
case eViewSystem.VirindiViewService:
|
||||
return VirindiViewsPresent(pHost);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static bool VirindiViewsPresent(Decal.Adapter.Wrappers.PluginHost pHost)
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
System.Reflection.Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (System.Reflection.Assembly a in asms)
|
||||
{
|
||||
AssemblyName nmm = a.GetName();
|
||||
if ((nmm.Name == "VirindiViewService") && (nmm.Version >= new System.Version("1.0.0.37")))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Curtain_VVS_Running();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
public static bool VirindiViewsPresent(Decal.Adapter.Wrappers.PluginHost pHost, Version minver)
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
System.Reflection.Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (System.Reflection.Assembly a in asms)
|
||||
{
|
||||
AssemblyName nm = a.GetName();
|
||||
if ((nm.Name == "VirindiViewService") && (nm.Version >= minver))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Curtain_VVS_Running();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if VVS_REFERENCED
|
||||
static bool Curtain_VVS_Running()
|
||||
{
|
||||
return VirindiViewService.Service.Running;
|
||||
}
|
||||
#endif
|
||||
|
||||
///////////////////////////////CreateViewResource///////////////////////////////
|
||||
|
||||
public static IView CreateViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource)
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
if (IsPresent(pHost, eViewSystem.VirindiViewService))
|
||||
return CreateViewResource(pHost, pXMLResource, eViewSystem.VirindiViewService);
|
||||
else
|
||||
#endif
|
||||
return CreateViewResource(pHost, pXMLResource, eViewSystem.DecalInject);
|
||||
}
|
||||
public static IView CreateViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource, eViewSystem VSystem)
|
||||
{
|
||||
if (!IsPresent(pHost, VSystem)) return null;
|
||||
switch (VSystem)
|
||||
{
|
||||
case eViewSystem.DecalInject:
|
||||
return CreateDecalViewResource(pHost, pXMLResource);
|
||||
case eViewSystem.VirindiViewService:
|
||||
#if VVS_REFERENCED
|
||||
return CreateMyHudViewResource(pHost, pXMLResource);
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static IView CreateDecalViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource)
|
||||
{
|
||||
IView ret = new DecalControls.View();
|
||||
ret.Initialize(pHost, pXMLResource);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if VVS_REFERENCED
|
||||
static IView CreateMyHudViewResource(Decal.Adapter.Wrappers.PluginHost pHost, string pXMLResource)
|
||||
{
|
||||
IView ret = new VirindiViewServiceHudControls.View();
|
||||
ret.Initialize(pHost, pXMLResource);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////CreateViewXML///////////////////////////////
|
||||
|
||||
public static IView CreateViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML)
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
if (IsPresent(pHost, eViewSystem.VirindiViewService))
|
||||
return CreateViewXML(pHost, pXML, eViewSystem.VirindiViewService);
|
||||
else
|
||||
#endif
|
||||
return CreateViewXML(pHost, pXML, eViewSystem.DecalInject);
|
||||
}
|
||||
|
||||
public static IView CreateViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML, eViewSystem VSystem)
|
||||
{
|
||||
if (!IsPresent(pHost, VSystem)) return null;
|
||||
switch (VSystem)
|
||||
{
|
||||
case eViewSystem.DecalInject:
|
||||
return CreateDecalViewXML(pHost, pXML);
|
||||
case eViewSystem.VirindiViewService:
|
||||
#if VVS_REFERENCED
|
||||
return CreateMyHudViewXML(pHost, pXML);
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
return null;
|
||||
}
|
||||
static IView CreateDecalViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML)
|
||||
{
|
||||
IView ret = new DecalControls.View();
|
||||
ret.InitializeRawXML(pHost, pXML);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if VVS_REFERENCED
|
||||
static IView CreateMyHudViewXML(Decal.Adapter.Wrappers.PluginHost pHost, string pXML)
|
||||
{
|
||||
IView ret = new VirindiViewServiceHudControls.View();
|
||||
ret.InitializeRawXML(pHost, pXML);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////HasChatOpen///////////////////////////////
|
||||
|
||||
public static bool AnySystemHasChatOpen(Decal.Adapter.Wrappers.PluginHost pHost)
|
||||
{
|
||||
if (IsPresent(pHost, eViewSystem.VirindiViewService))
|
||||
if (HasChatOpen_VirindiViews()) return true;
|
||||
if (pHost.Actions.ChatState) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool HasChatOpen_VirindiViews()
|
||||
{
|
||||
#if VVS_REFERENCED
|
||||
if (VirindiViewService.HudView.FocusControl != null)
|
||||
{
|
||||
if (VirindiViewService.HudView.FocusControl.GetType() == typeof(VirindiViewService.Controls.HudTextBox))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public delegate void delConditionalSplit(object data);
|
||||
public static void ViewConditionalSplit(IView v, delConditionalSplit onDecal, delConditionalSplit onVVS, object data)
|
||||
{
|
||||
Type vtype = v.GetType();
|
||||
|
||||
#if VVS_REFERENCED
|
||||
if (vtype == typeof(VirindiViewServiceHudControls.View))
|
||||
{
|
||||
if (onVVS != null)
|
||||
onVVS(data);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (vtype == typeof(DecalControls.View))
|
||||
{
|
||||
if (onDecal != null)
|
||||
onDecal(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,427 +0,0 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//File: Wrapper.cs
|
||||
//
|
||||
//Description: Contains the interface definitions for the MetaViewWrappers classes.
|
||||
//
|
||||
//References required:
|
||||
// System.Drawing
|
||||
//
|
||||
//This file is Copyright (c) 2010 VirindiPlugins
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
#if METAVIEW_PUBLIC_NS
|
||||
namespace MetaViewWrappers
|
||||
#else
|
||||
namespace MyClasses.MetaViewWrappers
|
||||
#endif
|
||||
{
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
delegate void dClickedList(object sender, int row, int col);
|
||||
|
||||
|
||||
#region EventArgs Classes
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVControlEventArgs : EventArgs
|
||||
{
|
||||
private int id;
|
||||
|
||||
internal MVControlEventArgs(int ID)
|
||||
{
|
||||
this.id = ID;
|
||||
}
|
||||
|
||||
public int Id
|
||||
{
|
||||
get { return this.id; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVIndexChangeEventArgs : MVControlEventArgs
|
||||
{
|
||||
private int index;
|
||||
|
||||
internal MVIndexChangeEventArgs(int ID, int Index)
|
||||
: base(ID)
|
||||
{
|
||||
this.index = Index;
|
||||
}
|
||||
|
||||
public int Index
|
||||
{
|
||||
get { return this.index; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVListSelectEventArgs : MVControlEventArgs
|
||||
{
|
||||
private int row;
|
||||
private int col;
|
||||
|
||||
internal MVListSelectEventArgs(int ID, int Row, int Column)
|
||||
: base(ID)
|
||||
{
|
||||
this.row = Row;
|
||||
this.col = Column;
|
||||
}
|
||||
|
||||
public int Row
|
||||
{
|
||||
get { return this.row; }
|
||||
}
|
||||
|
||||
public int Column
|
||||
{
|
||||
get { return this.col; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVCheckBoxChangeEventArgs : MVControlEventArgs
|
||||
{
|
||||
private bool check;
|
||||
|
||||
internal MVCheckBoxChangeEventArgs(int ID, bool Check)
|
||||
: base(ID)
|
||||
{
|
||||
this.check = Check;
|
||||
}
|
||||
|
||||
public bool Checked
|
||||
{
|
||||
get { return this.check; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVTextBoxChangeEventArgs : MVControlEventArgs
|
||||
{
|
||||
private string text;
|
||||
|
||||
internal MVTextBoxChangeEventArgs(int ID, string text)
|
||||
: base(ID)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return this.text; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVTextBoxEndEventArgs : MVControlEventArgs
|
||||
{
|
||||
private bool success;
|
||||
|
||||
internal MVTextBoxEndEventArgs(int ID, bool success)
|
||||
: base(ID)
|
||||
{
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public bool Success
|
||||
{
|
||||
get { return this.success; }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion EventArgs Classes
|
||||
|
||||
|
||||
#region View
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IView: IDisposable
|
||||
{
|
||||
void Initialize(Decal.Adapter.Wrappers.PluginHost p, string pXML);
|
||||
void InitializeRawXML(Decal.Adapter.Wrappers.PluginHost p, string pXML);
|
||||
void Initialize(Decal.Adapter.Wrappers.PluginHost p, string pXML, string pWindowKey);
|
||||
void InitializeRawXML(Decal.Adapter.Wrappers.PluginHost p, string pXML, string pWindowKey);
|
||||
|
||||
void SetIcon(int icon, int iconlibrary);
|
||||
void SetIcon(int portalicon);
|
||||
|
||||
string Title { get; set; }
|
||||
bool Visible { get; set; }
|
||||
#if !VVS_WRAPPERS_PUBLIC
|
||||
ViewSystemSelector.eViewSystem ViewType { get; }
|
||||
#endif
|
||||
|
||||
System.Drawing.Point Location { get; set; }
|
||||
System.Drawing.Rectangle Position { get; set; }
|
||||
System.Drawing.Size Size { get; }
|
||||
|
||||
IControl this[string id] { get; }
|
||||
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
bool Activated { get; set; }
|
||||
}
|
||||
|
||||
#endregion View
|
||||
|
||||
#region Controls
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IControl : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
bool Visible { get; set; }
|
||||
string TooltipText { get; set;}
|
||||
int Id { get; }
|
||||
System.Drawing.Rectangle LayoutPosition { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IButton : IControl
|
||||
{
|
||||
string Text { get; set; }
|
||||
event EventHandler Hit;
|
||||
event EventHandler<MVControlEventArgs> Click;
|
||||
System.Drawing.Color TextColor { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface ICheckBox : IControl
|
||||
{
|
||||
string Text { get; set; }
|
||||
bool Checked { get; set; }
|
||||
event EventHandler<MVCheckBoxChangeEventArgs> Change;
|
||||
event EventHandler Change_Old;
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface ITextBox : IControl
|
||||
{
|
||||
string Text { get; set; }
|
||||
event EventHandler<MVTextBoxChangeEventArgs> Change;
|
||||
event EventHandler Change_Old;
|
||||
event EventHandler<MVTextBoxEndEventArgs> End;
|
||||
int Caret { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface ICombo : IControl
|
||||
{
|
||||
IComboIndexer Text { get; }
|
||||
IComboDataIndexer Data { get; }
|
||||
int Count { get; }
|
||||
int Selected { get; set; }
|
||||
event EventHandler<MVIndexChangeEventArgs> Change;
|
||||
event EventHandler Change_Old;
|
||||
void Add(string text);
|
||||
void Add(string text, object obj);
|
||||
void Insert(int index, string text);
|
||||
void RemoveAt(int index);
|
||||
void Remove(int index);
|
||||
void Clear();
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IComboIndexer
|
||||
{
|
||||
string this[int index] { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IComboDataIndexer
|
||||
{
|
||||
object this[int index] { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface ISlider : IControl
|
||||
{
|
||||
int Position { get; set; }
|
||||
event EventHandler<MVIndexChangeEventArgs> Change;
|
||||
event EventHandler Change_Old;
|
||||
int Maximum { get; set; }
|
||||
int Minimum { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IList : IControl
|
||||
{
|
||||
event EventHandler<MVListSelectEventArgs> Selected;
|
||||
event dClickedList Click;
|
||||
void Clear();
|
||||
IListRow this[int row] { get; }
|
||||
IListRow AddRow();
|
||||
IListRow Add();
|
||||
IListRow InsertRow(int pos);
|
||||
IListRow Insert(int pos);
|
||||
int RowCount { get; }
|
||||
void RemoveRow(int index);
|
||||
void Delete(int index);
|
||||
int ColCount { get; }
|
||||
int ScrollPosition { get; set;}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IListRow
|
||||
{
|
||||
IListCell this[int col] { get; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IListCell
|
||||
{
|
||||
System.Drawing.Color Color { get; set; }
|
||||
int Width { get; set; }
|
||||
object this[int subval] { get; set; }
|
||||
void ResetColor();
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IStaticText : IControl
|
||||
{
|
||||
string Text { get; set; }
|
||||
event EventHandler<MVControlEventArgs> Click;
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface INotebook : IControl
|
||||
{
|
||||
event EventHandler<MVIndexChangeEventArgs> Change;
|
||||
int ActiveTab { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IProgressBar : IControl
|
||||
{
|
||||
int Position { get; set; }
|
||||
int Value { get; set; }
|
||||
string PreText { get; set; }
|
||||
int MaxValue { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IImageButton : IControl
|
||||
{
|
||||
event EventHandler<MVControlEventArgs> Click;
|
||||
void SetImages(int unpressed, int pressed);
|
||||
void SetImages(int hmodule, int unpressed, int pressed);
|
||||
int Background { set; }
|
||||
System.Drawing.Color Matte { set; }
|
||||
}
|
||||
|
||||
#endregion Controls
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,329 +0,0 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//File: Wrapper_WireupHelper.cs
|
||||
//
|
||||
//Description: A helper utility that emulates Decal.Adapter's automagic view
|
||||
// creation and control/event wireup with the MetaViewWrappers. A separate set
|
||||
// of attributes is used.
|
||||
//
|
||||
//References required:
|
||||
// Wrapper.cs
|
||||
//
|
||||
//This file is Copyright (c) 2010 VirindiPlugins
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
|
||||
#if METAVIEW_PUBLIC_NS
|
||||
namespace MetaViewWrappers
|
||||
#else
|
||||
namespace MyClasses.MetaViewWrappers
|
||||
#endif
|
||||
{
|
||||
#region Attribute Definitions
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVWireUpControlEventsAttribute : Attribute
|
||||
{
|
||||
public MVWireUpControlEventsAttribute() { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVControlReferenceAttribute : Attribute
|
||||
{
|
||||
string ctrl;
|
||||
|
||||
// Summary:
|
||||
// Construct a new ControlReference
|
||||
//
|
||||
// Parameters:
|
||||
// control:
|
||||
// Control to reference
|
||||
public MVControlReferenceAttribute(string control)
|
||||
{
|
||||
ctrl = control;
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// The Control Name
|
||||
public string Control
|
||||
{
|
||||
get
|
||||
{
|
||||
return ctrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVControlReferenceArrayAttribute : Attribute
|
||||
{
|
||||
private System.Collections.ObjectModel.Collection<string> myControls;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new ControlReference array
|
||||
/// </summary>
|
||||
/// <param name="controls">Names of the controls to put in the array</param>
|
||||
public MVControlReferenceArrayAttribute(params string[] controls)
|
||||
: base()
|
||||
{
|
||||
this.myControls = new System.Collections.ObjectModel.Collection<string>(controls);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control collection
|
||||
/// </summary>
|
||||
public System.Collections.ObjectModel.Collection<string> Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.myControls;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVViewAttribute : Attribute
|
||||
{
|
||||
string res;
|
||||
|
||||
// Summary:
|
||||
// Constructs a new view from the specified resource
|
||||
//
|
||||
// Parameters:
|
||||
// Resource:
|
||||
// Embedded resource path
|
||||
public MVViewAttribute(string resource)
|
||||
{
|
||||
res = resource;
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// The resource to load
|
||||
public string Resource
|
||||
{
|
||||
get
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVControlEventAttribute : Attribute
|
||||
{
|
||||
string c;
|
||||
string e;
|
||||
// Summary:
|
||||
// Constructs the ControlEvent
|
||||
//
|
||||
// Parameters:
|
||||
// control:
|
||||
// Control Name
|
||||
//
|
||||
// controlEvent:
|
||||
// Event to Wire
|
||||
public MVControlEventAttribute(string control, string eventName)
|
||||
{
|
||||
c = control;
|
||||
e = eventName;
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// Control Name
|
||||
public string Control
|
||||
{
|
||||
get
|
||||
{
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Event to Wire
|
||||
public string EventName
|
||||
{
|
||||
get
|
||||
{
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Attribute Definitions
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class MVWireupHelper
|
||||
{
|
||||
private class ViewObjectInfo
|
||||
{
|
||||
public List<MyClasses.MetaViewWrappers.IView> Views = new List<IView>();
|
||||
}
|
||||
static Dictionary<object, ViewObjectInfo> VInfo = new Dictionary<object, ViewObjectInfo>();
|
||||
|
||||
public static MyClasses.MetaViewWrappers.IView GetDefaultView(object ViewObj)
|
||||
{
|
||||
if (!VInfo.ContainsKey(ViewObj))
|
||||
return null;
|
||||
if (VInfo[ViewObj].Views.Count == 0)
|
||||
return null;
|
||||
return VInfo[ViewObj].Views[0];
|
||||
}
|
||||
|
||||
public static void WireupStart(object ViewObj, Decal.Adapter.Wrappers.PluginHost Host)
|
||||
{
|
||||
if (VInfo.ContainsKey(ViewObj))
|
||||
WireupEnd(ViewObj);
|
||||
ViewObjectInfo info = new ViewObjectInfo();
|
||||
VInfo[ViewObj] = info;
|
||||
|
||||
Type ObjType = ViewObj.GetType();
|
||||
|
||||
//Start views
|
||||
object[] viewattrs = ObjType.GetCustomAttributes(typeof(MVViewAttribute), true);
|
||||
foreach (MVViewAttribute a in viewattrs)
|
||||
{
|
||||
info.Views.Add(MyClasses.MetaViewWrappers.ViewSystemSelector.CreateViewResource(Host, a.Resource));
|
||||
}
|
||||
|
||||
//Wire up control references
|
||||
foreach (FieldInfo fi in ObjType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
|
||||
{
|
||||
if (Attribute.IsDefined(fi, typeof(MVControlReferenceAttribute)))
|
||||
{
|
||||
MVControlReferenceAttribute attr = (MVControlReferenceAttribute)Attribute.GetCustomAttribute(fi, typeof(MVControlReferenceAttribute));
|
||||
MetaViewWrappers.IControl mycontrol = null;
|
||||
|
||||
//Try each view
|
||||
foreach (MyClasses.MetaViewWrappers.IView v in info.Views)
|
||||
{
|
||||
try
|
||||
{
|
||||
mycontrol = v[attr.Control];
|
||||
}
|
||||
catch { }
|
||||
if (mycontrol != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (mycontrol == null)
|
||||
throw new Exception("Invalid control reference \"" + attr.Control + "\"");
|
||||
|
||||
if (!fi.FieldType.IsAssignableFrom(mycontrol.GetType()))
|
||||
throw new Exception("Control reference \"" + attr.Control + "\" is of wrong type");
|
||||
|
||||
fi.SetValue(ViewObj, mycontrol);
|
||||
}
|
||||
else if (Attribute.IsDefined(fi, typeof(MVControlReferenceArrayAttribute)))
|
||||
{
|
||||
MVControlReferenceArrayAttribute attr = (MVControlReferenceArrayAttribute)Attribute.GetCustomAttribute(fi, typeof(MVControlReferenceArrayAttribute));
|
||||
|
||||
//Only do the first view
|
||||
if (info.Views.Count == 0)
|
||||
throw new Exception("No views to which a control reference can attach");
|
||||
|
||||
Array controls = Array.CreateInstance(fi.FieldType.GetElementType(), attr.Controls.Count);
|
||||
|
||||
IView view = info.Views[0];
|
||||
for (int i = 0; i < attr.Controls.Count; ++i)
|
||||
{
|
||||
controls.SetValue(view[attr.Controls[i]], i);
|
||||
}
|
||||
|
||||
fi.SetValue(ViewObj, controls);
|
||||
}
|
||||
}
|
||||
|
||||
//Wire up events
|
||||
foreach (MethodInfo mi in ObjType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
|
||||
{
|
||||
if (!Attribute.IsDefined(mi, typeof(MVControlEventAttribute)))
|
||||
continue;
|
||||
Attribute[] attrs = Attribute.GetCustomAttributes(mi, typeof(MVControlEventAttribute));
|
||||
|
||||
foreach (MVControlEventAttribute attr in attrs)
|
||||
{
|
||||
MetaViewWrappers.IControl mycontrol = null;
|
||||
//Try each view
|
||||
foreach (MyClasses.MetaViewWrappers.IView v in info.Views)
|
||||
{
|
||||
try
|
||||
{
|
||||
mycontrol = v[attr.Control];
|
||||
}
|
||||
catch { }
|
||||
if (mycontrol != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (mycontrol == null)
|
||||
throw new Exception("Invalid control reference \"" + attr.Control + "\"");
|
||||
|
||||
EventInfo ei = mycontrol.GetType().GetEvent(attr.EventName);
|
||||
ei.AddEventHandler(mycontrol, Delegate.CreateDelegate(ei.EventHandlerType, ViewObj, mi.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void WireupEnd(object ViewObj)
|
||||
{
|
||||
if (!VInfo.ContainsKey(ViewObj))
|
||||
return;
|
||||
|
||||
foreach (MyClasses.MetaViewWrappers.IView v in VInfo[ViewObj].Views)
|
||||
v.Dispose();
|
||||
|
||||
VInfo.Remove(ViewObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,5 +104,125 @@ namespace MosswartMassacre
|
|||
{
|
||||
return vTank.Instance.MacroEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances VTank to the next waypoint in the current navigation route.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// 1 if the waypoint was advanced successfully; 0 on failure.
|
||||
/// </returns>
|
||||
public static double VtAdvanceWaypoint()
|
||||
{
|
||||
try
|
||||
{
|
||||
var externalInterface = vTank.Instance;
|
||||
|
||||
// Basic validation
|
||||
if (externalInterface.NavNumPoints == 0)
|
||||
{
|
||||
return 0; // No waypoints
|
||||
}
|
||||
|
||||
int currentWaypoint = externalInterface.NavCurrent;
|
||||
int totalWaypoints = externalInterface.NavNumPoints;
|
||||
|
||||
// Check if we can advance
|
||||
if (currentWaypoint >= totalWaypoints - 1)
|
||||
{
|
||||
return 0; // Already at last waypoint
|
||||
}
|
||||
|
||||
// Check navigation type
|
||||
var navType = (int)externalInterface.NavType;
|
||||
if (navType == 2 || navType == 4) // Target or Once
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Access the cExternalInterfaceTrustedRelay and get the PC (PluginCore) reference
|
||||
// From decompiled code: external interface uses PC.NavCurrent which references dz.o.l
|
||||
var interfaceType = externalInterface.GetType();
|
||||
|
||||
// Look for any way to get to the PluginCore instance
|
||||
// The interface should have access to PC or some way to reach it
|
||||
|
||||
// Try to get the underlying assembly and find the PC static field
|
||||
var assembly = interfaceType.Assembly;
|
||||
var pluginCoreType = assembly.GetType("uTank2.PluginCore");
|
||||
|
||||
if (pluginCoreType != null)
|
||||
{
|
||||
// Get the static PC field
|
||||
var pcField = pluginCoreType.GetField("PC", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
|
||||
if (pcField != null)
|
||||
{
|
||||
var pluginCoreInstance = pcField.GetValue(null);
|
||||
|
||||
if (pluginCoreInstance != null)
|
||||
{
|
||||
// Try to call the advance method 'i' on the PluginCore instance
|
||||
// Need to specify parameter types to avoid "Ambiguous match found"
|
||||
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);
|
||||
|
||||
if (advanceMethod != null)
|
||||
{
|
||||
// Call with parameters matching: i(object A_0, MVControlEventArgs A_1)
|
||||
advanceMethod.Invoke(pluginCoreInstance, new object[] { null, null });
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try to access dz static field directly
|
||||
var dzField = pluginCoreType.GetField("dz", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||
|
||||
if (dzField != null)
|
||||
{
|
||||
var dzObject = dzField.GetValue(null);
|
||||
|
||||
if (dzObject != null)
|
||||
{
|
||||
// Navigate the dz.o.l path
|
||||
var dzType = dzObject.GetType();
|
||||
var oField = dzType.GetField("o", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
|
||||
if (oField != null)
|
||||
{
|
||||
var oObject = oField.GetValue(dzObject);
|
||||
if (oObject != null)
|
||||
{
|
||||
var oType = oObject.GetType();
|
||||
var lField = oType.GetField("l", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
|
||||
if (lField != null)
|
||||
{
|
||||
// Get current value and increment it
|
||||
int current = (int)lField.GetValue(oObject);
|
||||
current++;
|
||||
if (current >= totalWaypoints)
|
||||
{
|
||||
current = totalWaypoints - 1;
|
||||
}
|
||||
lField.SetValue(oObject, current);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat("VTank advance error: " + ex.Message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
375
MosswartMassacre/WebSocket.cs
Normal file
375
MosswartMassacre/WebSocket.cs
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
// WebSocket.cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Decal.Adapter;
|
||||
using Newtonsoft.Json;
|
||||
using uTank2;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
internal static class SessionInfo
|
||||
{
|
||||
|
||||
internal static readonly Guid Guid = Guid.NewGuid();
|
||||
|
||||
internal static readonly string GuidString = Guid.ToString("N");
|
||||
}
|
||||
// 1) The envelope type for incoming commands
|
||||
public class CommandEnvelope
|
||||
{
|
||||
[JsonProperty("player_name")]
|
||||
public string PlayerName { get; set; }
|
||||
|
||||
[JsonProperty("command")]
|
||||
public string Command { get; set; }
|
||||
}
|
||||
|
||||
public static class WebSocket
|
||||
{
|
||||
// ─── configuration ──────────────────────────
|
||||
private static readonly Uri WsEndpoint = new Uri("wss://overlord.snakedesert.se/websocket/");
|
||||
private const string SharedSecret = "your_shared_secret";
|
||||
private const int IntervalSec = 5;
|
||||
private static string SessionId = "";
|
||||
|
||||
// ─── cached prismatic taper count ─── (now handled by PluginCore event system)
|
||||
|
||||
// ─── runtime state ──────────────────────────
|
||||
private static ClientWebSocket _ws;
|
||||
private static CancellationTokenSource _cts;
|
||||
private static bool _enabled;
|
||||
private static readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Fires when a valid CommandEnvelope arrives for this character.
|
||||
/// </summary>
|
||||
public static event Action<CommandEnvelope> OnServerCommand;
|
||||
|
||||
// ─── public API ─────────────────────────────
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
if (_enabled) return;
|
||||
_enabled = true;
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
PluginCore.WriteToChat("[WebSocket] connecting…");
|
||||
_ = Task.Run(ConnectAndLoopAsync);
|
||||
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
if (!_enabled) return;
|
||||
_enabled = false;
|
||||
|
||||
_cts.Cancel();
|
||||
_ws?.Abort();
|
||||
_ws?.Dispose();
|
||||
_ws = null;
|
||||
|
||||
PluginCore.WriteToChat("[WebSocket] DISABLED");
|
||||
}
|
||||
|
||||
// ─── connect / receive / telemetry loop ──────────────────────
|
||||
|
||||
private static async Task ConnectAndLoopAsync()
|
||||
{
|
||||
while (_enabled && !_cts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1) Establish connection
|
||||
_ws = new ClientWebSocket();
|
||||
_ws.Options.SetRequestHeader("X-Plugin-Secret", SharedSecret);
|
||||
await _ws.ConnectAsync(WsEndpoint, _cts.Token);
|
||||
PluginCore.WriteToChat("[WebSocket] CONNECTED");
|
||||
SessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||
|
||||
// ─── Register this socket under our character name ───
|
||||
var registerEnvelope = new
|
||||
{
|
||||
type = "register",
|
||||
player_name = CoreManager.Current.CharacterFilter.Name
|
||||
};
|
||||
var regJson = JsonConvert.SerializeObject(registerEnvelope);
|
||||
await SendEncodedAsync(regJson, _cts.Token);
|
||||
PluginCore.WriteToChat("[WebSocket] REGISTERED");
|
||||
|
||||
var buffer = new byte[4096];
|
||||
|
||||
// 2) Fire-and-forget receive loop
|
||||
var receiveTask = Task.Run(async () =>
|
||||
{
|
||||
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
WebSocketReceiveResult result;
|
||||
try
|
||||
{
|
||||
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[WebSocket] receive error: {ex.Message}");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
break;
|
||||
|
||||
var msg = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim();
|
||||
|
||||
// 3) Parse into CommandEnvelope
|
||||
CommandEnvelope env;
|
||||
try
|
||||
{
|
||||
env = JsonConvert.DeserializeObject<CommandEnvelope>(msg);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
continue; // skip malformed JSON
|
||||
}
|
||||
|
||||
// 4) Filter by this character name
|
||||
if (string.Equals(
|
||||
env.PlayerName,
|
||||
CoreManager.Current.CharacterFilter.Name,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Fire event immediately - let PluginCore handle threading
|
||||
OnServerCommand?.Invoke(env);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 5) Inline telemetry loop
|
||||
PluginCore.WriteToChat("[WebSocket] Starting telemetry loop");
|
||||
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = BuildPayloadJson();
|
||||
await SendEncodedAsync(json, _cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[WebSocket] Telemetry failed: {ex.Message}");
|
||||
break; // Exit telemetry loop on failure
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(IntervalSec), _cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
PluginCore.WriteToChat("[WebSocket] Telemetry loop cancelled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Log why telemetry loop exited
|
||||
PluginCore.WriteToChat($"[WebSocket] Telemetry loop ended - State: {_ws?.State}, Cancelled: {_cts.Token.IsCancellationRequested}");
|
||||
|
||||
// Wait for receive loop to finish
|
||||
await receiveTask;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
PluginCore.WriteToChat("[WebSocket] Connection cancelled");
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[WebSocket] Connection error: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
var finalState = _ws?.State.ToString() ?? "null";
|
||||
PluginCore.WriteToChat($"[WebSocket] Cleaning up connection - Final state: {finalState}");
|
||||
_ws?.Abort();
|
||||
_ws?.Dispose();
|
||||
_ws = null;
|
||||
}
|
||||
|
||||
// Pause before reconnecting
|
||||
if (_enabled)
|
||||
{
|
||||
PluginCore.WriteToChat("[WebSocket] Reconnecting in 2 seconds...");
|
||||
try { await Task.Delay(2000, CancellationToken.None); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ─── fire-and-forget chat sender ────────────────────
|
||||
|
||||
public static async Task SendChatTextAsync(int colorIndex, string chatText)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "chat",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
text = chatText,
|
||||
color = colorIndex
|
||||
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
public static async Task SendSpawnAsync(string nsCoord, string ewCoord, string zCoord, string monster)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "spawn",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
mob = monster,
|
||||
ns = nsCoord,
|
||||
ew = ewCoord,
|
||||
z = zCoord
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task SendPortalAsync(string nsCoord, string ewCoord, string zCoord, string portalName)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "portal",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
portal_name = portalName,
|
||||
ns = nsCoord,
|
||||
ew = ewCoord,
|
||||
z = zCoord
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
public static async Task SendRareAsync(string rare)
|
||||
{
|
||||
var coords = Coordinates.Me;
|
||||
var envelope = new
|
||||
{
|
||||
type = "rare",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
name = rare,
|
||||
ew = coords.EW,
|
||||
ns = coords.NS,
|
||||
z = coords.Z
|
||||
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task SendFullInventoryAsync(List<Mag.Shared.MyWorldObject> inventory)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "full_inventory",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
item_count = inventory.Count,
|
||||
items = inventory
|
||||
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task SendVitalsAsync(object vitalsData)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(vitalsData);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task SendQuestDataAsync(string questName, string countdown)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "quest",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
quest_name = questName,
|
||||
countdown = countdown
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
// ─── shared send helper with locking ───────────────
|
||||
|
||||
private static async Task SendEncodedAsync(string text, CancellationToken token)
|
||||
{
|
||||
await _sendLock.WaitAsync(token);
|
||||
try
|
||||
{
|
||||
if (_ws == null || _ws.State != WebSocketState.Open)
|
||||
return;
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(text);
|
||||
await _ws.SendAsync(new ArraySegment<byte>(bytes),
|
||||
WebSocketMessageType.Text,
|
||||
true,
|
||||
token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[WebSocket] Send error: {ex.Message}");
|
||||
_ws?.Abort();
|
||||
_ws?.Dispose();
|
||||
_ws = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── payload builder ──────────────────────────────
|
||||
|
||||
// Removed old cache system - now using PluginCore.cachedPrismaticCount
|
||||
|
||||
private static string BuildPayloadJson()
|
||||
{
|
||||
var tele = new ClientTelemetry();
|
||||
var coords = Coordinates.Me;
|
||||
var payload = new
|
||||
{
|
||||
type = "telemetry",
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
char_tag = PluginCore.CharTag,
|
||||
session_id = SessionInfo.GuidString,
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
ew = coords.EW,
|
||||
ns = coords.NS,
|
||||
z = coords.Z,
|
||||
kills = PluginCore.totalKills,
|
||||
kills_per_hour = PluginCore.killsPerHour.ToString("F0"),
|
||||
onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"),
|
||||
deaths = PluginCore.sessionDeaths.ToString(),
|
||||
total_deaths = PluginCore.totalDeaths.ToString(),
|
||||
prismatic_taper_count = PluginCore.cachedPrismaticCount.ToString(),
|
||||
vt_state = VtankControl.VtGetMetaState(),
|
||||
mem_mb = tele.MemoryBytes,
|
||||
cpu_pct = tele.GetCpuUsage(),
|
||||
mem_handles = tele.HandleCount
|
||||
|
||||
};
|
||||
return JsonConvert.SerializeObject(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,427 +0,0 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//File: Wrapper.cs
|
||||
//
|
||||
//Description: Contains the interface definitions for the MetaViewWrappers classes.
|
||||
//
|
||||
//References required:
|
||||
// System.Drawing
|
||||
//
|
||||
//This file is Copyright (c) 2010 VirindiPlugins
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
#if METAVIEW_PUBLIC_NS
|
||||
namespace MetaViewWrappers
|
||||
#else
|
||||
namespace MyClasses.MetaViewWrappers
|
||||
#endif
|
||||
{
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
delegate void dClickedList(object sender, int row, int col);
|
||||
|
||||
|
||||
#region EventArgs Classes
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVControlEventArgs : EventArgs
|
||||
{
|
||||
private int id;
|
||||
|
||||
internal MVControlEventArgs(int ID)
|
||||
{
|
||||
this.id = ID;
|
||||
}
|
||||
|
||||
public int Id
|
||||
{
|
||||
get { return this.id; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVIndexChangeEventArgs : MVControlEventArgs
|
||||
{
|
||||
private int index;
|
||||
|
||||
internal MVIndexChangeEventArgs(int ID, int Index)
|
||||
: base(ID)
|
||||
{
|
||||
this.index = Index;
|
||||
}
|
||||
|
||||
public int Index
|
||||
{
|
||||
get { return this.index; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVListSelectEventArgs : MVControlEventArgs
|
||||
{
|
||||
private int row;
|
||||
private int col;
|
||||
|
||||
internal MVListSelectEventArgs(int ID, int Row, int Column)
|
||||
: base(ID)
|
||||
{
|
||||
this.row = Row;
|
||||
this.col = Column;
|
||||
}
|
||||
|
||||
public int Row
|
||||
{
|
||||
get { return this.row; }
|
||||
}
|
||||
|
||||
public int Column
|
||||
{
|
||||
get { return this.col; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVCheckBoxChangeEventArgs : MVControlEventArgs
|
||||
{
|
||||
private bool check;
|
||||
|
||||
internal MVCheckBoxChangeEventArgs(int ID, bool Check)
|
||||
: base(ID)
|
||||
{
|
||||
this.check = Check;
|
||||
}
|
||||
|
||||
public bool Checked
|
||||
{
|
||||
get { return this.check; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVTextBoxChangeEventArgs : MVControlEventArgs
|
||||
{
|
||||
private string text;
|
||||
|
||||
internal MVTextBoxChangeEventArgs(int ID, string text)
|
||||
: base(ID)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return this.text; }
|
||||
}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
class MVTextBoxEndEventArgs : MVControlEventArgs
|
||||
{
|
||||
private bool success;
|
||||
|
||||
internal MVTextBoxEndEventArgs(int ID, bool success)
|
||||
: base(ID)
|
||||
{
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public bool Success
|
||||
{
|
||||
get { return this.success; }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion EventArgs Classes
|
||||
|
||||
|
||||
#region View
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IView: IDisposable
|
||||
{
|
||||
void Initialize(Decal.Adapter.Wrappers.PluginHost p, string pXML);
|
||||
void InitializeRawXML(Decal.Adapter.Wrappers.PluginHost p, string pXML);
|
||||
void Initialize(Decal.Adapter.Wrappers.PluginHost p, string pXML, string pWindowKey);
|
||||
void InitializeRawXML(Decal.Adapter.Wrappers.PluginHost p, string pXML, string pWindowKey);
|
||||
|
||||
void SetIcon(int icon, int iconlibrary);
|
||||
void SetIcon(int portalicon);
|
||||
|
||||
string Title { get; set; }
|
||||
bool Visible { get; set; }
|
||||
#if !VVS_WRAPPERS_PUBLIC
|
||||
ViewSystemSelector.eViewSystem ViewType { get; }
|
||||
#endif
|
||||
|
||||
System.Drawing.Point Location { get; set; }
|
||||
System.Drawing.Rectangle Position { get; set; }
|
||||
System.Drawing.Size Size { get; }
|
||||
|
||||
IControl this[string id] { get; }
|
||||
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
bool Activated { get; set; }
|
||||
}
|
||||
|
||||
#endregion View
|
||||
|
||||
#region Controls
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IControl : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
bool Visible { get; set; }
|
||||
string TooltipText { get; set;}
|
||||
int Id { get; }
|
||||
System.Drawing.Rectangle LayoutPosition { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IButton : IControl
|
||||
{
|
||||
string Text { get; set; }
|
||||
event EventHandler Hit;
|
||||
event EventHandler<MVControlEventArgs> Click;
|
||||
System.Drawing.Color TextColor { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface ICheckBox : IControl
|
||||
{
|
||||
string Text { get; set; }
|
||||
bool Checked { get; set; }
|
||||
event EventHandler<MVCheckBoxChangeEventArgs> Change;
|
||||
event EventHandler Change_Old;
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface ITextBox : IControl
|
||||
{
|
||||
string Text { get; set; }
|
||||
event EventHandler<MVTextBoxChangeEventArgs> Change;
|
||||
event EventHandler Change_Old;
|
||||
event EventHandler<MVTextBoxEndEventArgs> End;
|
||||
int Caret { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface ICombo : IControl
|
||||
{
|
||||
IComboIndexer Text { get; }
|
||||
IComboDataIndexer Data { get; }
|
||||
int Count { get; }
|
||||
int Selected { get; set; }
|
||||
event EventHandler<MVIndexChangeEventArgs> Change;
|
||||
event EventHandler Change_Old;
|
||||
void Add(string text);
|
||||
void Add(string text, object obj);
|
||||
void Insert(int index, string text);
|
||||
void RemoveAt(int index);
|
||||
void Remove(int index);
|
||||
void Clear();
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IComboIndexer
|
||||
{
|
||||
string this[int index] { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IComboDataIndexer
|
||||
{
|
||||
object this[int index] { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface ISlider : IControl
|
||||
{
|
||||
int Position { get; set; }
|
||||
event EventHandler<MVIndexChangeEventArgs> Change;
|
||||
event EventHandler Change_Old;
|
||||
int Maximum { get; set; }
|
||||
int Minimum { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IList : IControl
|
||||
{
|
||||
event EventHandler<MVListSelectEventArgs> Selected;
|
||||
event dClickedList Click;
|
||||
void Clear();
|
||||
IListRow this[int row] { get; }
|
||||
IListRow AddRow();
|
||||
IListRow Add();
|
||||
IListRow InsertRow(int pos);
|
||||
IListRow Insert(int pos);
|
||||
int RowCount { get; }
|
||||
void RemoveRow(int index);
|
||||
void Delete(int index);
|
||||
int ColCount { get; }
|
||||
int ScrollPosition { get; set;}
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IListRow
|
||||
{
|
||||
IListCell this[int col] { get; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IListCell
|
||||
{
|
||||
System.Drawing.Color Color { get; set; }
|
||||
int Width { get; set; }
|
||||
object this[int subval] { get; set; }
|
||||
void ResetColor();
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IStaticText : IControl
|
||||
{
|
||||
string Text { get; set; }
|
||||
event EventHandler<MVControlEventArgs> Click;
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface INotebook : IControl
|
||||
{
|
||||
event EventHandler<MVIndexChangeEventArgs> Change;
|
||||
int ActiveTab { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IProgressBar : IControl
|
||||
{
|
||||
int Position { get; set; }
|
||||
int Value { get; set; }
|
||||
string PreText { get; set; }
|
||||
int MaxValue { get; set; }
|
||||
}
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
interface IImageButton : IControl
|
||||
{
|
||||
event EventHandler<MVControlEventArgs> Click;
|
||||
void SetImages(int unpressed, int pressed);
|
||||
void SetImages(int hmodule, int unpressed, int pressed);
|
||||
int Background { set; }
|
||||
System.Drawing.Color Matte { set; }
|
||||
}
|
||||
|
||||
#endregion Controls
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,329 +0,0 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//File: Wrapper_WireupHelper.cs
|
||||
//
|
||||
//Description: A helper utility that emulates Decal.Adapter's automagic view
|
||||
// creation and control/event wireup with the MetaViewWrappers. A separate set
|
||||
// of attributes is used.
|
||||
//
|
||||
//References required:
|
||||
// Wrapper.cs
|
||||
//
|
||||
//This file is Copyright (c) 2010 VirindiPlugins
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
|
||||
#if METAVIEW_PUBLIC_NS
|
||||
namespace MetaViewWrappers
|
||||
#else
|
||||
namespace MyClasses.MetaViewWrappers
|
||||
#endif
|
||||
{
|
||||
#region Attribute Definitions
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVWireUpControlEventsAttribute : Attribute
|
||||
{
|
||||
public MVWireUpControlEventsAttribute() { }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVControlReferenceAttribute : Attribute
|
||||
{
|
||||
string ctrl;
|
||||
|
||||
// Summary:
|
||||
// Construct a new ControlReference
|
||||
//
|
||||
// Parameters:
|
||||
// control:
|
||||
// Control to reference
|
||||
public MVControlReferenceAttribute(string control)
|
||||
{
|
||||
ctrl = control;
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// The Control Name
|
||||
public string Control
|
||||
{
|
||||
get
|
||||
{
|
||||
return ctrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVControlReferenceArrayAttribute : Attribute
|
||||
{
|
||||
private System.Collections.ObjectModel.Collection<string> myControls;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new ControlReference array
|
||||
/// </summary>
|
||||
/// <param name="controls">Names of the controls to put in the array</param>
|
||||
public MVControlReferenceArrayAttribute(params string[] controls)
|
||||
: base()
|
||||
{
|
||||
this.myControls = new System.Collections.ObjectModel.Collection<string>(controls);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Control collection
|
||||
/// </summary>
|
||||
public System.Collections.ObjectModel.Collection<string> Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.myControls;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVViewAttribute : Attribute
|
||||
{
|
||||
string res;
|
||||
|
||||
// Summary:
|
||||
// Constructs a new view from the specified resource
|
||||
//
|
||||
// Parameters:
|
||||
// Resource:
|
||||
// Embedded resource path
|
||||
public MVViewAttribute(string resource)
|
||||
{
|
||||
res = resource;
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// The resource to load
|
||||
public string Resource
|
||||
{
|
||||
get
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
sealed class MVControlEventAttribute : Attribute
|
||||
{
|
||||
string c;
|
||||
string e;
|
||||
// Summary:
|
||||
// Constructs the ControlEvent
|
||||
//
|
||||
// Parameters:
|
||||
// control:
|
||||
// Control Name
|
||||
//
|
||||
// controlEvent:
|
||||
// Event to Wire
|
||||
public MVControlEventAttribute(string control, string eventName)
|
||||
{
|
||||
c = control;
|
||||
e = eventName;
|
||||
}
|
||||
|
||||
// Summary:
|
||||
// Control Name
|
||||
public string Control
|
||||
{
|
||||
get
|
||||
{
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Summary:
|
||||
// Event to Wire
|
||||
public string EventName
|
||||
{
|
||||
get
|
||||
{
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Attribute Definitions
|
||||
|
||||
#if VVS_WRAPPERS_PUBLIC
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class MVWireupHelper
|
||||
{
|
||||
private class ViewObjectInfo
|
||||
{
|
||||
public List<MyClasses.MetaViewWrappers.IView> Views = new List<IView>();
|
||||
}
|
||||
static Dictionary<object, ViewObjectInfo> VInfo = new Dictionary<object, ViewObjectInfo>();
|
||||
|
||||
public static MyClasses.MetaViewWrappers.IView GetDefaultView(object ViewObj)
|
||||
{
|
||||
if (!VInfo.ContainsKey(ViewObj))
|
||||
return null;
|
||||
if (VInfo[ViewObj].Views.Count == 0)
|
||||
return null;
|
||||
return VInfo[ViewObj].Views[0];
|
||||
}
|
||||
|
||||
public static void WireupStart(object ViewObj, Decal.Adapter.Wrappers.PluginHost Host)
|
||||
{
|
||||
if (VInfo.ContainsKey(ViewObj))
|
||||
WireupEnd(ViewObj);
|
||||
ViewObjectInfo info = new ViewObjectInfo();
|
||||
VInfo[ViewObj] = info;
|
||||
|
||||
Type ObjType = ViewObj.GetType();
|
||||
|
||||
//Start views
|
||||
object[] viewattrs = ObjType.GetCustomAttributes(typeof(MVViewAttribute), true);
|
||||
foreach (MVViewAttribute a in viewattrs)
|
||||
{
|
||||
info.Views.Add(MyClasses.MetaViewWrappers.ViewSystemSelector.CreateViewResource(Host, a.Resource));
|
||||
}
|
||||
|
||||
//Wire up control references
|
||||
foreach (FieldInfo fi in ObjType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
|
||||
{
|
||||
if (Attribute.IsDefined(fi, typeof(MVControlReferenceAttribute)))
|
||||
{
|
||||
MVControlReferenceAttribute attr = (MVControlReferenceAttribute)Attribute.GetCustomAttribute(fi, typeof(MVControlReferenceAttribute));
|
||||
MetaViewWrappers.IControl mycontrol = null;
|
||||
|
||||
//Try each view
|
||||
foreach (MyClasses.MetaViewWrappers.IView v in info.Views)
|
||||
{
|
||||
try
|
||||
{
|
||||
mycontrol = v[attr.Control];
|
||||
}
|
||||
catch { }
|
||||
if (mycontrol != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (mycontrol == null)
|
||||
throw new Exception("Invalid control reference \"" + attr.Control + "\"");
|
||||
|
||||
if (!fi.FieldType.IsAssignableFrom(mycontrol.GetType()))
|
||||
throw new Exception("Control reference \"" + attr.Control + "\" is of wrong type");
|
||||
|
||||
fi.SetValue(ViewObj, mycontrol);
|
||||
}
|
||||
else if (Attribute.IsDefined(fi, typeof(MVControlReferenceArrayAttribute)))
|
||||
{
|
||||
MVControlReferenceArrayAttribute attr = (MVControlReferenceArrayAttribute)Attribute.GetCustomAttribute(fi, typeof(MVControlReferenceArrayAttribute));
|
||||
|
||||
//Only do the first view
|
||||
if (info.Views.Count == 0)
|
||||
throw new Exception("No views to which a control reference can attach");
|
||||
|
||||
Array controls = Array.CreateInstance(fi.FieldType.GetElementType(), attr.Controls.Count);
|
||||
|
||||
IView view = info.Views[0];
|
||||
for (int i = 0; i < attr.Controls.Count; ++i)
|
||||
{
|
||||
controls.SetValue(view[attr.Controls[i]], i);
|
||||
}
|
||||
|
||||
fi.SetValue(ViewObj, controls);
|
||||
}
|
||||
}
|
||||
|
||||
//Wire up events
|
||||
foreach (MethodInfo mi in ObjType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
|
||||
{
|
||||
if (!Attribute.IsDefined(mi, typeof(MVControlEventAttribute)))
|
||||
continue;
|
||||
Attribute[] attrs = Attribute.GetCustomAttributes(mi, typeof(MVControlEventAttribute));
|
||||
|
||||
foreach (MVControlEventAttribute attr in attrs)
|
||||
{
|
||||
MetaViewWrappers.IControl mycontrol = null;
|
||||
//Try each view
|
||||
foreach (MyClasses.MetaViewWrappers.IView v in info.Views)
|
||||
{
|
||||
try
|
||||
{
|
||||
mycontrol = v[attr.Control];
|
||||
}
|
||||
catch { }
|
||||
if (mycontrol != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (mycontrol == null)
|
||||
throw new Exception("Invalid control reference \"" + attr.Control + "\"");
|
||||
|
||||
EventInfo ei = mycontrol.GetType().GetEvent(attr.EventName);
|
||||
ei.AddEventHandler(mycontrol, Delegate.CreateDelegate(ei.EventHandlerType, ViewObj, mi.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void WireupEnd(object ViewObj)
|
||||
{
|
||||
if (!VInfo.ContainsKey(ViewObj))
|
||||
return;
|
||||
|
||||
foreach (MyClasses.MetaViewWrappers.IView v in VInfo[ViewObj].Views)
|
||||
v.Dispose();
|
||||
|
||||
VInfo.Remove(ViewObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,10 @@
|
|||
<assemblyIdentity name="Decal.Interop.Inject" publicKeyToken="481f17d392f1fb65" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.9.8.3" newVersion="2.9.8.3" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Decal.FileService" publicKeyToken="bd1c8ce002ce221e" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.9.8.3" newVersion="2.9.8.3" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
MosswartMassacre/lib/0Harmony.dll
Normal file
BIN
MosswartMassacre/lib/0Harmony.dll
Normal file
Binary file not shown.
BIN
MosswartMassacre/lib/utank2-i.dll
Normal file
BIN
MosswartMassacre/lib/utank2-i.dll
Normal file
Binary file not shown.
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<view icon="26075" title="Mosswart Massacre" width="300" height="200">
|
||||
<control progid="DecalControls.FixedLayout" clipped="">
|
||||
<control progid="DecalControls.PushButton" name="btnActivate" left="10" top="20" width="100" height="30" text="Activate"/>
|
||||
<control progid="DecalControls.PushButton" name="btnReport" left="10" top="60" width="100" height="30" text="Report"/>
|
||||
<control progid="DecalControls.PushButton" name="btnReset" left="10" top="100" width="100" height="30" text="Reset"/>
|
||||
<control progid="DecalControls.StaticText" name="lblStatus" left="10" top="150" width="280" height="30" text="Mosswart Massacre is ready!"/>
|
||||
</control>
|
||||
</view>
|
||||
|
|
@ -1,5 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Costura.Fody" version="5.7.0" targetFramework="net48" developmentDependency="true" />
|
||||
<package id="Fody" version="6.9.3" targetFramework="net48" developmentDependency="true" />
|
||||
<package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net48" />
|
||||
<package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="NETStandard.Library" version="1.6.1" targetFramework="net48" />
|
||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||
<package id="System.AppContext" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Collections" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Console" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Diagnostics.DiagnosticSource" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Globalization" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.IO" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.IO.Compression" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Linq" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Net.Http" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Net.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Net.Sockets" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.ObjectModel" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Reflection" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime.Handles" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Threading" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Threading.Timer" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net48" />
|
||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net48" />
|
||||
<package id="YamlDotNet" version="16.3.0" targetFramework="net48" />
|
||||
</packages>
|
||||
234
MosswartMassacre/scripts/installer.nsi
Normal file
234
MosswartMassacre/scripts/installer.nsi
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
; Define your application name
|
||||
|
||||
!define APPNAME "MosswartMassacre"
|
||||
!define SOFTWARECOMPANY "MosswartMassacre"
|
||||
!define APPGUID "{8C97E839-4D05-4A5F-B0C8-E8E778654322}"
|
||||
!define CLASSNAME "MosswartMassacre.PluginCore"
|
||||
!define ASSEMBLY "MosswartMassacre.dll"
|
||||
!define LOADERGUID "{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}"
|
||||
!define LOADERCLASS "MosswartMassacre.Loader.LoaderCore"
|
||||
!define LOADERASSEMBLY "MosswartMassacre.Loader.dll"
|
||||
InstallDir "C:\Games\DecalPlugins\${APPNAME}"
|
||||
;Icon "Installer\Res\Decal.ico"
|
||||
|
||||
!define BUILDPATH ".\..\bin\Release"
|
||||
|
||||
!getdllversion "${BUILDPATH}\${ASSEMBLY}" Expv_
|
||||
!define VERSION ${Expv_1}.${Expv_2}.${Expv_3}
|
||||
|
||||
OutFile "${BUILDPATH}\${APPNAME}Installer-${VERSION}.exe"
|
||||
|
||||
; Main Install settings
|
||||
; compressor goes first
|
||||
SetCompressor LZMA
|
||||
|
||||
Name "${APPNAME} ${VERSION}"
|
||||
InstallDirRegKey HKLM "Software\${SOFTWARECOMPANY}\${APPNAME}" ""
|
||||
;SetFont "Verdana" 8
|
||||
|
||||
; Use compression
|
||||
|
||||
; Modern interface settings
|
||||
!include "MUI.nsh"
|
||||
|
||||
!define MUI_ABORTWARNING
|
||||
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
;!insertmacro MUI_PAGE_COMPONENTS
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
; Set languages (first is default language)
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
!insertmacro MUI_RESERVEFILE_LANGDLL
|
||||
|
||||
; https://nsis.sourceforge.io/Download_and_Install_dotNET_45
|
||||
Function CheckAndDownloadDotNet48
|
||||
# Set up our Variables
|
||||
Var /GLOBAL dotNET48IsThere
|
||||
Var /GLOBAL dotNET_CMD_LINE
|
||||
Var /GLOBAL EXIT_CODE
|
||||
|
||||
# We are reading a version release DWORD that Microsoft says is the documented
|
||||
# way to determine if .NET Framework 4.8 is installed
|
||||
ReadRegDWORD $dotNET48IsThere HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" "Release"
|
||||
IntCmp $dotNET48IsThere 528049 is_equal is_less is_greater
|
||||
|
||||
is_equal:
|
||||
Goto done_compare_not_needed
|
||||
is_greater:
|
||||
Goto done_compare_not_needed
|
||||
is_less:
|
||||
Goto done_compare_needed
|
||||
|
||||
done_compare_needed:
|
||||
#.NET Framework 4.8 install is *NEEDED*
|
||||
|
||||
# Microsoft Download Center EXE:
|
||||
# Web Bootstrapper: https://go.microsoft.com/fwlink/?LinkId=2085155
|
||||
# Full Download: https://go.microsoft.com/fwlink/?linkid=2088631
|
||||
|
||||
# Setup looks for components\dotNET48Full.exe relative to the install EXE location
|
||||
# This allows the installer to be placed on a USB stick (for computers without internet connections)
|
||||
# If the .NET Framework 4.8 installer is *NOT* found, Setup will connect to Microsoft's website
|
||||
# and download it for you
|
||||
|
||||
# Reboot Required with these Exit Codes:
|
||||
# 1641 or 3010
|
||||
|
||||
# Command Line Switches:
|
||||
# /showrmui /passive /norestart
|
||||
|
||||
# Silent Command Line Switches:
|
||||
# /q /norestart
|
||||
|
||||
|
||||
# Let's see if the user is doing a Silent install or not
|
||||
IfSilent is_quiet is_not_quiet
|
||||
|
||||
is_quiet:
|
||||
StrCpy $dotNET_CMD_LINE "/q /norestart"
|
||||
Goto LookForLocalFile
|
||||
is_not_quiet:
|
||||
StrCpy $dotNET_CMD_LINE "/showrmui /passive /norestart"
|
||||
Goto LookForLocalFile
|
||||
|
||||
LookForLocalFile:
|
||||
# Let's see if the user stored the Full Installer
|
||||
IfFileExists "$EXEPATH\components\dotNET48Full.exe" do_local_install do_network_install
|
||||
|
||||
do_local_install:
|
||||
# .NET Framework found on the local disk. Use this copy
|
||||
|
||||
ExecWait '"$EXEPATH\components\dotNET48Full.exe" $dotNET_CMD_LINE' $EXIT_CODE
|
||||
Goto is_reboot_requested
|
||||
|
||||
# Now, let's Download the .NET
|
||||
do_network_install:
|
||||
|
||||
Var /GLOBAL dotNetDidDownload
|
||||
NSISdl::download "https://go.microsoft.com/fwlink/?linkid=2088631" "$TEMP\dotNET48Web.exe" $dotNetDidDownload
|
||||
|
||||
StrCmp $dotNetDidDownload success fail
|
||||
success:
|
||||
ExecWait '"$TEMP\dotNET45Web.exe" $dotNET_CMD_LINE' $EXIT_CODE
|
||||
Goto is_reboot_requested
|
||||
|
||||
fail:
|
||||
MessageBox MB_OK|MB_ICONEXCLAMATION "Unable to download .NET Framework. ${PRODUCT_NAME} will be installed, but will not function without the Framework!"
|
||||
Goto done_dotNET_function
|
||||
|
||||
# $EXIT_CODE contains the return codes. 1641 and 3010 means a Reboot has been requested
|
||||
is_reboot_requested:
|
||||
${If} $EXIT_CODE = 1641
|
||||
${OrIf} $EXIT_CODE = 3010
|
||||
SetRebootFlag true
|
||||
${EndIf}
|
||||
|
||||
done_compare_not_needed:
|
||||
# Done dotNET Install
|
||||
Goto done_dotNET_function
|
||||
|
||||
#exit the function
|
||||
done_dotNET_function:
|
||||
|
||||
FunctionEnd
|
||||
|
||||
|
||||
Section "" CoreSection
|
||||
; Set Section properties
|
||||
SetOverwrite on
|
||||
|
||||
; Set Section Files and Shortcuts
|
||||
SetOutPath "$INSTDIR\"
|
||||
|
||||
File "${BUILDPATH}\${ASSEMBLY}"
|
||||
File "${BUILDPATH}\${APPNAME}.pdb"
|
||||
File "${BUILDPATH}\${LOADERASSEMBLY}"
|
||||
File "${BUILDPATH}\${APPNAME}.Loader.pdb"
|
||||
; File "${BUILDPATH}\UtilityBelt.Service.Installer.exe"
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section -FinishSection
|
||||
|
||||
WriteRegStr HKLM "Software\${SOFTWARECOMPANY}\${APPNAME}" "" "$INSTDIR"
|
||||
WriteRegStr HKLM "Software\${SOFTWARECOMPANY}\${APPNAME}" "Version" "${VERSION}"
|
||||
|
||||
;Register in decal
|
||||
ClearErrors
|
||||
ReadRegStr $0 HKLM "Software\Decal\Plugins\${APPGUID}" ""
|
||||
${If} ${Errors}
|
||||
WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "" "${APPNAME}"
|
||||
WriteRegDWORD HKLM "Software\Decal\Plugins\${APPGUID}" "Enabled" "1"
|
||||
WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Object" "${CLASSNAME}"
|
||||
WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Assembly" "${ASSEMBLY}"
|
||||
WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Path" "$INSTDIR"
|
||||
WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Surrogate" "{71A69713-6593-47EC-0002-0000000DECA1}"
|
||||
WriteRegStr HKLM "Software\Decal\Plugins\${APPGUID}" "Uninstaller" "${APPNAME}"
|
||||
${Else}
|
||||
${IF} $0 != "${APPNAME}"
|
||||
MESSAGEBOX MB_OK|MB_ICONSTOP "Skipped decal plugin registration. A decal plugin with this GUID already exists ($0), and is not ${APPNAME}."
|
||||
${ENDIF}
|
||||
${EndIf}
|
||||
|
||||
;Register loader in decal as network filter
|
||||
ClearErrors
|
||||
ReadRegStr $0 HKLM "Software\Decal\NetworkFilters\${LOADERGUID}" ""
|
||||
${If} ${Errors}
|
||||
WriteRegStr HKLM "Software\Decal\NetworkFilters\${LOADERGUID}" "" "${APPNAME}.Loader"
|
||||
WriteRegDWORD HKLM "Software\Decal\NetworkFilters\${LOADERGUID}" "Enabled" "0" ; Disabled by default for normal use
|
||||
WriteRegStr HKLM "Software\Decal\NetworkFilters\${LOADERGUID}" "Object" "${LOADERCLASS}"
|
||||
WriteRegStr HKLM "Software\Decal\NetworkFilters\${LOADERGUID}" "Assembly" "${LOADERASSEMBLY}"
|
||||
WriteRegStr HKLM "Software\Decal\NetworkFilters\${LOADERGUID}" "Path" "$INSTDIR"
|
||||
WriteRegStr HKLM "Software\Decal\NetworkFilters\${LOADERGUID}" "Surrogate" "{71A69713-6593-47EC-0002-0000000DECA1}"
|
||||
WriteRegStr HKLM "Software\Decal\NetworkFilters\${LOADERGUID}" "Uninstaller" "${APPNAME}"
|
||||
${Else}
|
||||
${IF} $0 != "${APPNAME}.Loader"
|
||||
MESSAGEBOX MB_OK|MB_ICONSTOP "Skipped decal loader registration. A decal network filter with this GUID already exists ($0), and is not ${APPNAME}.Loader."
|
||||
${ENDIF}
|
||||
${EndIf}
|
||||
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
; make sure dotnet 4.8 is installed
|
||||
Call CheckAndDownloadDotNet48
|
||||
|
||||
SectionEnd
|
||||
|
||||
; Modern install component descriptions
|
||||
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
|
||||
!insertmacro MUI_DESCRIPTION_TEXT ${CoreSection} ""
|
||||
!insertmacro MUI_FUNCTION_DESCRIPTION_END
|
||||
|
||||
;Uninstall section
|
||||
Section Uninstall
|
||||
|
||||
;Remove from registry...
|
||||
DeleteRegKey HKLM "Software\${SOFTWARECOMPANY}\${APPNAME}"
|
||||
DeleteRegKey HKLM "Software\Decal\Plugins\${APPGUID}"
|
||||
DeleteRegKey HKLM "Software\Decal\NetworkFilters\${LOADERGUID}"
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
|
||||
|
||||
; Delete self
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
;Clean up
|
||||
Delete "$INSTDIR\${ASSEMBLY}"
|
||||
Delete "$INSTDIR\${APPNAME}.pdb"
|
||||
Delete "$INSTDIR\${LOADERASSEMBLY}"
|
||||
Delete "$INSTDIR\${APPNAME}.Loader.pdb"
|
||||
Delete "$INSTDIR\loader_log.txt"
|
||||
; Delete "$INSTDIR\UtilityBelt.Service.Installer.exe"
|
||||
|
||||
;RMDir "$INSTDIR\"
|
||||
|
||||
SectionEnd
|
||||
|
||||
; eof
|
||||
17
MosswartMassacre/scripts/post-build.ps1
Normal file
17
MosswartMassacre/scripts/post-build.ps1
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
param(
|
||||
[string]$NuGetPackageRoot,
|
||||
[string]$ProjectDir
|
||||
)
|
||||
|
||||
if ($Env:OS -and $Env:OS -like '*Windows*') {
|
||||
|
||||
$makensis = Join-Path $NuGetPackageRoot 'nsis-tool\3.0.8\tools\makensis.exe'
|
||||
$installer = Join-Path $ProjectDir 'scripts\installer.nsi'
|
||||
|
||||
Write-Verbose "Using makensis at $makensis"
|
||||
& $makensis $installer
|
||||
}
|
||||
else {
|
||||
# Only runs when building on Linux/macOS with makensis in PATH
|
||||
& makensis "$ProjectDir/scripts/installer.nsi"
|
||||
}
|
||||
270
README.md
270
README.md
|
|
@ -1,80 +1,220 @@
|
|||
# Mossy Plugins
|
||||
# MosswartMassacre - Advanced DECAL Plugin for Asheron's Call
|
||||
|
||||
A collection of DECAL plugins for Asheron's Call, providing utility overlays and automation features.
|
||||
> **Status**: Production Ready | VVS Direct Integration | Navigation Visualization Complete
|
||||
|
||||
## Contents
|
||||
- `mossy.sln`: Visual Studio solution containing both projects.
|
||||
- `GearCycler/`: Simple plugin with a UI button to cycle gear (placeholder behavior).
|
||||
- `MosswartMassacre/`: Advanced plugin tracking monster kills, rare discoveries, and offering HTTP/telemetry features.
|
||||
- `packages/`: Vendored NuGet packages (Newtonsoft.Json, YamlDotNet).
|
||||
A comprehensive DECAL plugin for Asheron's Call that tracks monster kills, rare item discoveries, and provides advanced navigation route visualization with 3D rendering.
|
||||
|
||||
## Prerequisites
|
||||
## 🚀 Features
|
||||
|
||||
### Core Functionality
|
||||
- **Kill Tracking**: Real-time monster kill counting with rate calculations (kills/5min, kills/hour)
|
||||
- **Rare Item Discovery**: Automatic rare detection and counter with optional meta state control
|
||||
- **Statistics Dashboard**: Detailed session statistics with best hourly performance tracking
|
||||
- **Multi-System Integration**: WebSocket streaming, HTTP command server, and telemetry support
|
||||
|
||||
### 🗺️ Navigation Visualization
|
||||
**Advanced VTank route visualization with 3D rendering capabilities**
|
||||
- **3D Route Display**: Renders VTank .nav files as red lines in the game world
|
||||
- **Route Comparison**: Side-by-side visualization with UtilityBelt's active navigation
|
||||
- **Full Format Support**: All VTank nav types (Circular, Linear, Target, Once) and waypoint types
|
||||
- **Auto-Discovery**: Automatically detects VTank installation and scans for .nav files
|
||||
- **Performance Optimized**: Smart rendering limits and memory management
|
||||
|
||||
### 🎛️ User Interface
|
||||
**Modern tabbed interface using direct VirindiViewService integration**
|
||||
- **Main Tab**: Live kill stats, rare counts, elapsed time, and status indicators
|
||||
- **Settings Tab**: Plugin configuration with real-time updates
|
||||
- **Statistics Tab**: Enhanced analytics and session management
|
||||
- **Navigation Tab**: Route selection, visualization controls, and status display
|
||||
|
||||
## 📥 Installation
|
||||
|
||||
### Prerequisites
|
||||
- Windows with .NET Framework 4.8
|
||||
- Visual Studio 2017+ (MSBuild Tools 15.0) or equivalent MSBuild environment
|
||||
- DECAL Adapter installed for Asheron's Call
|
||||
- VirindiViewService (included in each project's `lib/` folder)
|
||||
- Asheron's Call with DECAL Adapter installed
|
||||
- VirindiViewService (included in lib/ folder)
|
||||
|
||||
## Setup & Build
|
||||
1. Clone this repository.
|
||||
2. Ensure the DECAL and Virindi DLLs are present under `MosswartMassacre/lib/` and referenced by each project.
|
||||
3. Restore NuGet packages if needed (`nuget restore mossy.sln`).
|
||||
4. Open `mossy.sln` in Visual Studio and build the solution.
|
||||
5. The output DLLs will be in each project’s `bin/Debug/` or `bin/Release/` folder.
|
||||
6. Deploy the plugin DLLs (and any required XML or YAML files) to your DECAL plugin directory.
|
||||
### Quick Setup
|
||||
1. Download the latest release from the releases page
|
||||
2. Extract to your DECAL plugins directory
|
||||
3. Restart DECAL and enable the plugin
|
||||
4. Configure settings through the in-game UI
|
||||
|
||||
## GearCycler
|
||||
A minimal plugin demonstrating a VirindiViewService-based UI.
|
||||
- UI layout: `GearCycler/ViewXML/mainView.xml`.
|
||||
- Core logic in `GearCycler/GearCore.cs`.
|
||||
- On button click, it logs a chat message; extend the `btnCycle.Hit` handler to add gear-cycling logic.
|
||||
### Building from Source
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone [repository-url]
|
||||
cd MosswartMassacre
|
||||
|
||||
## MosswartMassacre
|
||||
Tracks monster kills and rare drops, with multiple utility features.
|
||||
# Restore packages and build
|
||||
nuget restore packages.config
|
||||
msbuild MosswartMassacre.csproj /p:Configuration=Release /p:Platform=AnyCPU
|
||||
```
|
||||
|
||||
### Features
|
||||
- **Kill Tracking**: Counts total kills and computes rates (kills/5 min, kills/hour).
|
||||
- **Rare Discoveries**: Increments rare count and can automatically set rare meta state.
|
||||
- **UI Overlay**: Displays stats and provides buttons to reset stats or toggle rare meta.
|
||||
- **Command Interface** (`/mm` commands):
|
||||
- `/mm help` : Show available commands.
|
||||
- `/mm report` : Display current stats in chat.
|
||||
- `/mm loc` : Show current map coordinates.
|
||||
- `/mm reset` : Reset kill counters and timers.
|
||||
- `/mm meta` : Toggle automatic rare meta state.
|
||||
- `/mm http <enable|disable>` : Start/stop local HTTP command server (port 8085).
|
||||
- `/mm remotecommands <enable|disable>` : Listen for remote commands from your allegiance chat.
|
||||
- `/mm telemetry <enable|disable>` : Enable/disable periodic telemetry streaming.
|
||||
## 🎮 Usage
|
||||
|
||||
### HTTP Command Server
|
||||
- Listens on `http://localhost:8085/`.
|
||||
- Accepts POST data: `target=<player>&command=<text>`, then sends a /tell and executes the command.
|
||||
### Basic Commands
|
||||
Access all features through the `/mm` command interface:
|
||||
|
||||
```
|
||||
/mm help - Show available commands
|
||||
/mm report - Display current kill statistics
|
||||
/mm loc - Show current map coordinates
|
||||
/mm reset - Reset kill counters and timers
|
||||
/mm meta - Toggle automatic rare meta state
|
||||
/mm http <on/off> - Control HTTP command server (port 8085)
|
||||
/mm telemetry <on/off> - Control telemetry streaming
|
||||
```
|
||||
|
||||
### Navigation Visualization
|
||||
1. **Enable**: Check "Enable Navigation Visualization" in Navigation tab
|
||||
2. **Configure**: Set VTank profiles path in Settings (auto-detected)
|
||||
3. **Select Route**: Choose from dropdown and click "Load Route"
|
||||
4. **View**: Red route lines appear in 3D game world
|
||||
|
||||
### Configuration
|
||||
- Per-character YAML config stored at `<PluginDir>/<CharacterName>.yaml`.
|
||||
- Settings include:
|
||||
- `remote_commands_enabled`
|
||||
- `rare_meta_enabled`
|
||||
- `http_server_enabled`
|
||||
- `telemetry_enabled`
|
||||
- `char_tag`
|
||||
- Config is auto-generated on first run; modify it or use UI/commands to update.
|
||||
Settings are stored per-character in YAML format at `<PluginDir>/<CharacterName>.yaml`:
|
||||
|
||||
### Telemetry
|
||||
- Periodically posts JSON snapshots of position and stats to a configurable endpoint.
|
||||
- Configure `Endpoint`, `SharedSecret`, and `IntervalSec` in `Telemetry.cs`.
|
||||
```yaml
|
||||
rare_meta_enabled: true
|
||||
remote_commands_enabled: false
|
||||
http_server_enabled: false
|
||||
websocket_enabled: true
|
||||
telemetry_enabled: false
|
||||
char_tag: "default"
|
||||
vtank_profiles_path: "C:\\Games\\VirindiPlugins\\VirindiTank\\"
|
||||
main_window_x: 100
|
||||
main_window_y: 100
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Decal.Adapter (v2.9.8.3)
|
||||
- Decal.Interop.Core & Decal.Interop.Inject
|
||||
- VirindiViewService
|
||||
- Newtonsoft.Json (v13.0.3)
|
||||
- YamlDotNet (v16.3.0)
|
||||
## 🏗️ Architecture
|
||||
|
||||
## Contributing
|
||||
1. Fork the repository.
|
||||
2. Create a feature branch.
|
||||
3. Commit your changes and ensure the solution builds.
|
||||
4. Submit a pull request with a description of your changes.
|
||||
### Core Components
|
||||
- **PluginCore.cs**: Main entry point and event coordination
|
||||
- **PluginSettings.cs**: YAML-based per-character configuration
|
||||
- **Views/VVSTabbedMainView.cs**: Main tabbed UI with direct VVS integration
|
||||
- **Views/VVSBaseView.cs**: Base class for VVS-based views
|
||||
|
||||
--
|
||||
_This README provides a high-level overview to get up and running quickly._
|
||||
### Navigation System
|
||||
- **NavRoute.cs**: VTank .nav file parser and 3D renderer
|
||||
- **NavVisualization.cs**: Route management and file discovery
|
||||
- **Registry Integration**: Automatic VTank directory detection
|
||||
|
||||
### Communication Systems
|
||||
- **WebSocket.cs**: Real-time data streaming to external services
|
||||
- **HttpCommandServer.cs**: Local HTTP API for remote control
|
||||
- **Telemetry.cs**: Periodic statistics reporting
|
||||
|
||||
### Game Integration
|
||||
- **VtankControl.cs**: vTank automation interface
|
||||
- **MossyInventory.cs**: Inventory monitoring and rare detection
|
||||
- **Utils.cs**: Game coordinate systems and utility functions
|
||||
|
||||
## 🔧 Technical Details
|
||||
|
||||
### Dependencies
|
||||
- **DECAL Framework**: Core plugin system (Decal.Adapter, Decal.Interop.Core, Decal.Interop.D3DService)
|
||||
- **VirindiViewService**: UI framework for game overlays
|
||||
- **Newtonsoft.Json**: JSON serialization for APIs
|
||||
- **YamlDotNet**: Configuration file management
|
||||
|
||||
### Build Configuration
|
||||
- **Target**: .NET Framework 4.8, x86 platform
|
||||
- **Architecture**: Direct VVS integration (no wrapper abstraction)
|
||||
- **Features**: Unsafe blocks enabled for P/Invoke operations
|
||||
|
||||
### Navigation File Format Support
|
||||
**Complete VTank .nav format compatibility:**
|
||||
- **Nav Types**: Circular (1), Linear (0/2), Target (3), Once (4)
|
||||
- **Waypoint Types**: Point, Portal, Recall, Pause, ChatCommand, OpenVendor, Portal2, UseNPC, Checkpoint, Jump
|
||||
- **Performance**: Optimized for routes up to 10,000 waypoints with 500-segment rendering limit
|
||||
|
||||
## 🔌 API Integration
|
||||
|
||||
### HTTP Command Server
|
||||
```bash
|
||||
# Enable server
|
||||
curl -X POST http://localhost:8085/ -d "target=PlayerName&command=report"
|
||||
|
||||
# Available endpoints
|
||||
POST / - Execute command for target player
|
||||
```
|
||||
|
||||
### WebSocket Streaming
|
||||
Real-time data streaming to `wss://overlord.snakedesert.se/websocket/` including:
|
||||
- Monster spawn/despawn events
|
||||
- Chat messages and rare discoveries
|
||||
- Player position and statistics
|
||||
- Session-based authentication with SharedSecret
|
||||
|
||||
### Telemetry Data
|
||||
Periodic JSON snapshots posted to configurable endpoints:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-12-19T10:30:00Z",
|
||||
"character": "PlayerName",
|
||||
"position": {"x": 59.2, "y": -28.7, "z": 0.05},
|
||||
"stats": {"kills": 150, "rares": 3, "session_time": "02:15:30"}
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
MosswartMassacre/
|
||||
├── Views/ # VVS-based UI components
|
||||
│ ├── VVSBaseView.cs # Base view foundation
|
||||
│ └── VVSTabbedMainView.cs # Main tabbed interface
|
||||
├── ViewXML/ # UI layout definitions
|
||||
│ └── mainViewTabbed.xml # Current layout
|
||||
├── NavRoute.cs # Navigation file parser
|
||||
├── NavVisualization.cs # Route visualization manager
|
||||
├── PluginCore.cs # Main plugin logic
|
||||
├── PluginSettings.cs # Configuration management
|
||||
└── lib/ # External dependencies
|
||||
```
|
||||
|
||||
### Development Environment
|
||||
- **IDE**: Visual Studio 2017+ or VS Code with C# extension
|
||||
- **Tools**: MSBuild, NuGet Package Manager
|
||||
- **Testing**: In-game with Asheron's Call client and DECAL
|
||||
|
||||
### Contributing
|
||||
1. Fork the repository
|
||||
2. Create feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit changes (`git commit -m 'Add amazing feature'`)
|
||||
4. Push to branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
## 📚 Related Documentation
|
||||
|
||||
- **CLAUDE.md**: Claude AI development guidance and build commands
|
||||
- **Development History**: Successful VVS migration completed, wrapper system removed
|
||||
- **Architecture Evolution**: Migrated from wrapper-based to direct VVS integration
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
## 🎯 Roadmap
|
||||
|
||||
### Completed ✅
|
||||
- [x] VVS Direct Integration Migration
|
||||
- [x] Navigation Visualization System
|
||||
- [x] Tabbed UI Interface
|
||||
- [x] WebSocket Streaming
|
||||
- [x] HTTP Command API
|
||||
- [x] Telemetry System
|
||||
- [x] Architecture Cleanup (Phase 3)
|
||||
|
||||
### Future Enhancements
|
||||
- [ ] Multiple route visualization
|
||||
- [ ] Route analysis and optimization tools
|
||||
- [ ] Enhanced UI controls and themes
|
||||
- [ ] Plugin integration marketplace
|
||||
- [ ] Advanced statistics and reporting
|
||||
|
||||
---
|
||||
|
||||
*Built with ❤️ for the Asheron's Call community*
|
||||
187
Shared/Constants/BoolValueKey.cs
Normal file
187
Shared/Constants/BoolValueKey.cs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
// https://github.com/ACEmulator/ACE/blob/master/Source/ACE.Entity/Enum/Properties/PropertyBool.cs
|
||||
public enum BoolValueKey
|
||||
{
|
||||
// properties marked as ServerOnly are properties we never saw in PCAPs, from here:
|
||||
// http://ac.yotesfan.com/ace_object/not_used_enums.php
|
||||
// source: @OptimShi
|
||||
// description attributes are used by the weenie editor for a cleaner display name
|
||||
|
||||
Undef = 0,
|
||||
[Ephemeral][ServerOnly]
|
||||
Stuck = 1,
|
||||
[Ephemeral]
|
||||
Open = 2,
|
||||
Locked = 3,
|
||||
RotProof = 4,
|
||||
AllegianceUpdateRequest = 5,
|
||||
AiUsesMana = 6,
|
||||
AiUseHumanMagicAnimations = 7,
|
||||
AllowGive = 8,
|
||||
CurrentlyAttacking = 9,
|
||||
AttackerAi = 10,
|
||||
[ServerOnly]
|
||||
IgnoreCollisions = 11,
|
||||
[ServerOnly]
|
||||
ReportCollisions = 12,
|
||||
[ServerOnly]
|
||||
Ethereal = 13,
|
||||
[ServerOnly]
|
||||
GravityStatus = 14,
|
||||
[ServerOnly]
|
||||
LightsStatus = 15,
|
||||
[ServerOnly]
|
||||
ScriptedCollision = 16,
|
||||
[ServerOnly]
|
||||
Inelastic = 17,
|
||||
[ServerOnly][Ephemeral]
|
||||
Visibility = 18,
|
||||
[ServerOnly]
|
||||
Attackable = 19,
|
||||
SafeSpellComponents = 20,
|
||||
AdvocateState = 21,
|
||||
Inscribable = 22,
|
||||
DestroyOnSell = 23,
|
||||
UiHidden = 24,
|
||||
IgnoreHouseBarriers = 25,
|
||||
HiddenAdmin = 26,
|
||||
PkWounder = 27,
|
||||
PkKiller = 28,
|
||||
NoCorpse = 29,
|
||||
UnderLifestoneProtection = 30,
|
||||
ItemManaUpdatePending = 31,
|
||||
[Ephemeral]
|
||||
GeneratorStatus = 32,
|
||||
[Ephemeral]
|
||||
ResetMessagePending = 33,
|
||||
DefaultOpen = 34,
|
||||
DefaultLocked = 35,
|
||||
DefaultOn = 36,
|
||||
OpenForBusiness = 37,
|
||||
IsFrozen = 38,
|
||||
DealMagicalItems = 39,
|
||||
LogoffImDead = 40,
|
||||
ReportCollisionsAsEnvironment = 41,
|
||||
AllowEdgeSlide = 42,
|
||||
AdvocateQuest = 43,
|
||||
[Ephemeral][SendOnLogin]
|
||||
IsAdmin = 44,
|
||||
[Ephemeral][SendOnLogin]
|
||||
IsArch = 45,
|
||||
[Ephemeral][SendOnLogin]
|
||||
IsSentinel = 46,
|
||||
[SendOnLogin]
|
||||
IsAdvocate = 47,
|
||||
CurrentlyPoweringUp = 48,
|
||||
[Ephemeral]
|
||||
GeneratorEnteredWorld = 49,
|
||||
NeverFailCasting = 50,
|
||||
VendorService = 51,
|
||||
AiImmobile = 52,
|
||||
DamagedByCollisions = 53,
|
||||
IsDynamic = 54,
|
||||
IsHot = 55,
|
||||
IsAffecting = 56,
|
||||
AffectsAis = 57,
|
||||
SpellQueueActive = 58,
|
||||
[Ephemeral]
|
||||
GeneratorDisabled = 59,
|
||||
IsAcceptingTells = 60,
|
||||
LoggingChannel = 61,
|
||||
OpensAnyLock = 62,
|
||||
UnlimitedUse = 63,
|
||||
GeneratedTreasureItem = 64,
|
||||
IgnoreMagicResist = 65,
|
||||
IgnoreMagicArmor = 66,
|
||||
AiAllowTrade = 67,
|
||||
[SendOnLogin]
|
||||
SpellComponentsRequired = 68,
|
||||
IsSellable = 69,
|
||||
IgnoreShieldsBySkill = 70,
|
||||
NoDraw = 71,
|
||||
ActivationUntargeted = 72,
|
||||
HouseHasGottenPriorityBootPos = 73,
|
||||
[Ephemeral]
|
||||
GeneratorAutomaticDestruction = 74,
|
||||
HouseHooksVisible = 75,
|
||||
HouseRequiresMonarch = 76,
|
||||
HouseHooksEnabled = 77,
|
||||
HouseNotifiedHudOfHookCount = 78,
|
||||
AiAcceptEverything = 79,
|
||||
IgnorePortalRestrictions = 80,
|
||||
RequiresBackpackSlot = 81,
|
||||
DontTurnOrMoveWhenGiving = 82,
|
||||
[ServerOnly]
|
||||
NpcLooksLikeObject = 83,
|
||||
IgnoreCloIcons = 84,
|
||||
AppraisalHasAllowedWielder = 85,
|
||||
ChestRegenOnClose = 86,
|
||||
LogoffInMinigame = 87,
|
||||
PortalShowDestination = 88,
|
||||
PortalIgnoresPkAttackTimer = 89,
|
||||
NpcInteractsSilently = 90,
|
||||
Retained = 91,
|
||||
IgnoreAuthor = 92,
|
||||
Limbo = 93,
|
||||
AppraisalHasAllowedActivator = 94,
|
||||
ExistedBeforeAllegianceXpChanges = 95,
|
||||
IsDeaf = 96,
|
||||
[Ephemeral][SendOnLogin]
|
||||
IsPsr = 97,
|
||||
Invincible = 98,
|
||||
Ivoryable = 99,
|
||||
Dyable = 100,
|
||||
CanGenerateRare = 101,
|
||||
CorpseGeneratedRare = 102,
|
||||
NonProjectileMagicImmune = 103,
|
||||
[SendOnLogin]
|
||||
ActdReceivedItems = 104,
|
||||
Unknown105 = 105,
|
||||
[Ephemeral]
|
||||
FirstEnterWorldDone = 106,
|
||||
RecallsDisabled = 107,
|
||||
RareUsesTimer = 108,
|
||||
ActdPreorderReceivedItems = 109,
|
||||
Afk = 110,
|
||||
IsGagged = 111,
|
||||
ProcSpellSelfTargeted = 112,
|
||||
IsAllegianceGagged = 113,
|
||||
EquipmentSetTriggerPiece = 114,
|
||||
Uninscribe = 115,
|
||||
WieldOnUse = 116,
|
||||
ChestClearedWhenClosed = 117,
|
||||
NeverAttack = 118,
|
||||
SuppressGenerateEffect = 119,
|
||||
TreasureCorpse = 120,
|
||||
EquipmentSetAddLevel = 121,
|
||||
BarberActive = 122,
|
||||
TopLayerPriority = 123,
|
||||
NoHeldItemShown = 124,
|
||||
LoginAtLifestone = 125,
|
||||
OlthoiPk = 126,
|
||||
[SendOnLogin]
|
||||
Account15Days = 127,
|
||||
HadNoVitae = 128,
|
||||
NoOlthoiTalk = 129,
|
||||
AutowieldLeft = 130,
|
||||
|
||||
|
||||
// ACE Specific
|
||||
/* custom */
|
||||
[ServerOnly]
|
||||
LinkedPortalOneSummon = 9001,
|
||||
[ServerOnly]
|
||||
LinkedPortalTwoSummon = 9002,
|
||||
[ServerOnly]
|
||||
HouseEvicted = 9003,
|
||||
[ServerOnly]
|
||||
UntrainedSkills = 9004,
|
||||
|
||||
|
||||
// Decal Specific
|
||||
Lockable_Decal = 201326592,
|
||||
Inscribable_Decal = 201326593,
|
||||
}
|
||||
}
|
||||
129
Shared/Constants/CoverageMask.cs
Normal file
129
Shared/Constants/CoverageMask.cs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the mapping for LongValueKey.Coverage.
|
||||
/// It represents what body parts an armor piece covers when used in defensive/armor calculations.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum CoverageMask
|
||||
{
|
||||
None = 0,
|
||||
|
||||
Unknown = 0x00000001, // Original pants abdomen?
|
||||
|
||||
UnderwearUpperLegs = 0x00000002, // I think... 0x13 = Abdomen/UpperLegs
|
||||
UnderwearLowerLegs = 0x00000004, // I think... 0x16 = Abdomen/UpperLegs/LowerLegs
|
||||
UnderwearChest = 0x00000008,
|
||||
UnderwearAbdomen = 0x00000010, // Original shirt abdomen?
|
||||
UnderwearUpperArms = 0x00000020,
|
||||
UnderwearLowerArms = 0x00000040,
|
||||
// = 0x00000080,
|
||||
|
||||
OuterwearUpperLegs = 0x00000100,
|
||||
OuterwearLowerLegs = 0x00000200,
|
||||
OuterwearChest = 0x00000400,
|
||||
OuterwearAbdomen = 0x00000800,
|
||||
OuterwearUpperArms = 0x00001000,
|
||||
OuterwearLowerArms = 0x00002000,
|
||||
|
||||
Head = 0x00004000,
|
||||
Hands = 0x00008000,
|
||||
Feet = 0x00010000,
|
||||
|
||||
Cloak = 0x00020000,
|
||||
}
|
||||
|
||||
public enum CoverageMaskHelper : uint
|
||||
{
|
||||
// for server comparison only
|
||||
Underwear = CoverageMask.UnderwearUpperLegs | CoverageMask.UnderwearLowerLegs | CoverageMask.UnderwearChest | CoverageMask.UnderwearAbdomen | CoverageMask.UnderwearUpperArms | CoverageMask.UnderwearLowerArms,
|
||||
Outerwear = CoverageMask.OuterwearUpperLegs | CoverageMask.OuterwearLowerLegs | CoverageMask.OuterwearChest | CoverageMask.OuterwearAbdomen | CoverageMask.OuterwearUpperArms | CoverageMask.OuterwearLowerArms | CoverageMask.Head | CoverageMask.Hands | CoverageMask.Feet,
|
||||
|
||||
UnderwearLegs = CoverageMask.UnderwearUpperLegs | CoverageMask.UnderwearLowerLegs,
|
||||
UnderwearArms = CoverageMask.UnderwearUpperArms | CoverageMask.UnderwearLowerArms,
|
||||
|
||||
OuterwearLegs = CoverageMask.OuterwearUpperLegs | CoverageMask.OuterwearLowerLegs,
|
||||
OuterwearArms = CoverageMask.OuterwearUpperArms | CoverageMask.OuterwearLowerArms,
|
||||
|
||||
// exclude abdomen for searching
|
||||
UnderwearShirt = CoverageMask.UnderwearChest | CoverageMask.UnderwearUpperArms | CoverageMask.UnderwearLowerArms,
|
||||
UnderwearPants = CoverageMask.UnderwearUpperLegs | CoverageMask.UnderwearLowerLegs
|
||||
}
|
||||
|
||||
public static class CoverageMaskExtensions
|
||||
{
|
||||
public static int GetTotalBitsSet(this CoverageMask value)
|
||||
{
|
||||
int slotFlags = (int)value;
|
||||
int bitsSet = 0;
|
||||
|
||||
while (slotFlags != 0)
|
||||
{
|
||||
if ((slotFlags & 1) == 1)
|
||||
bitsSet++;
|
||||
slotFlags >>= 1;
|
||||
}
|
||||
|
||||
return bitsSet;
|
||||
}
|
||||
|
||||
public static bool IsBodyArmor(this CoverageMask value) { return ((int)value & 0x0001FF00) != 0; }
|
||||
public static bool IsRobe(this CoverageMask value) { return ((int)value == 0x00013F00); }
|
||||
public static bool IsUnderwear(this CoverageMask value) { return ((int)value & 0x0000007F) != 0; }
|
||||
public static bool IsShirt(this CoverageMask value) { return ((int)value & 0x00000078) != 0; }
|
||||
public static bool IsPants(this CoverageMask value) { return ((int)value & 0x00000017) != 0; }
|
||||
|
||||
public static List<CoverageMask> ReductionOptions(this CoverageMask value)
|
||||
{
|
||||
List<CoverageMask> options = new List<CoverageMask>();
|
||||
|
||||
if (value.GetTotalBitsSet() <= 1 || !value.IsBodyArmor() || value.IsRobe())
|
||||
options.Add(value);
|
||||
else
|
||||
{
|
||||
if (value == (CoverageMask.OuterwearUpperArms | CoverageMask.OuterwearLowerArms))
|
||||
{
|
||||
options.Add(CoverageMask.OuterwearUpperArms);
|
||||
options.Add(CoverageMask.OuterwearLowerArms);
|
||||
}
|
||||
else if (value == (CoverageMask.OuterwearUpperLegs | CoverageMask.OuterwearLowerLegs))
|
||||
{
|
||||
options.Add(CoverageMask.OuterwearUpperLegs);
|
||||
options.Add(CoverageMask.OuterwearLowerLegs);
|
||||
}
|
||||
else if (value == (CoverageMask.OuterwearLowerLegs | CoverageMask.Feet))
|
||||
options.Add(CoverageMask.Feet);
|
||||
else if (value == (CoverageMask.OuterwearChest | CoverageMask.OuterwearAbdomen))
|
||||
options.Add(CoverageMask.OuterwearChest);
|
||||
else if (value == (CoverageMask.OuterwearChest | CoverageMask.OuterwearAbdomen | CoverageMask.OuterwearUpperArms))
|
||||
options.Add(CoverageMask.OuterwearChest);
|
||||
else if (value == (CoverageMask.OuterwearChest | CoverageMask.OuterwearUpperArms | CoverageMask.OuterwearLowerArms))
|
||||
options.Add(CoverageMask.OuterwearChest);
|
||||
else if (value == (CoverageMask.OuterwearChest | CoverageMask.OuterwearUpperArms))
|
||||
options.Add(CoverageMask.OuterwearChest);
|
||||
else if (value == (CoverageMask.OuterwearAbdomen | CoverageMask.OuterwearUpperLegs | CoverageMask.OuterwearLowerLegs))
|
||||
{
|
||||
options.Add(CoverageMask.OuterwearAbdomen);
|
||||
options.Add(CoverageMask.OuterwearUpperLegs);
|
||||
options.Add(CoverageMask.OuterwearLowerLegs);
|
||||
}
|
||||
else if (value == (CoverageMask.OuterwearChest | CoverageMask.OuterwearAbdomen | CoverageMask.OuterwearUpperArms | CoverageMask.OuterwearLowerArms))
|
||||
options.Add(CoverageMask.OuterwearChest);
|
||||
else if (value == (CoverageMask.OuterwearAbdomen | CoverageMask.OuterwearUpperLegs))
|
||||
{
|
||||
// This is a emu piece that follows the pre-2010 retail guidelines
|
||||
// https://asheron.fandom.com/wiki/Announcements_-_2010/04_-_Shedding_Skin
|
||||
// For now, we assume only abdomen reduction
|
||||
options.Add(CoverageMask.OuterwearAbdomen);
|
||||
}
|
||||
else
|
||||
throw new Exception("Unable to determine reduction paths for CoverageMask of " + value);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
436
Shared/Constants/Dictionaries.cs
Normal file
436
Shared/Constants/Dictionaries.cs
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
public static class Dictionaries
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a dictionary of skill ids vs names
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static readonly Dictionary<int, string> SkillInfo = new Dictionary<int, string>
|
||||
{
|
||||
// This list was taken from the Alinco source
|
||||
{ 0x1, "Axe" },
|
||||
{ 0x2, "Bow" },
|
||||
{ 0x3, "Crossbow" },
|
||||
{ 0x4, "Dagger" },
|
||||
{ 0x5, "Mace" },
|
||||
{ 0x6, "Melee Defense" },
|
||||
{ 0x7, "Missile Defense" },
|
||||
{ 0x8, "Sling" },
|
||||
{ 0x9, "Spear" },
|
||||
{ 0xA, "Staff" },
|
||||
{ 0xB, "Sword" },
|
||||
{ 0xC, "Thrown Weapons" },
|
||||
{ 0xD, "Unarmed Combat" },
|
||||
{ 0xE, "Arcane Lore" },
|
||||
{ 0xF, "Magic Defense" },
|
||||
{ 0x10, "Mana Conversion" },
|
||||
{ 0x12, "Item Tinkering" },
|
||||
{ 0x13, "Assess Person" },
|
||||
{ 0x14, "Deception" },
|
||||
{ 0x15, "Healing" },
|
||||
{ 0x16, "Jump" },
|
||||
{ 0x17, "Lockpick" },
|
||||
{ 0x18, "Run" },
|
||||
{ 0x1B, "Assess Creature" },
|
||||
{ 0x1C, "Weapon Tinkering" },
|
||||
{ 0x1D, "Armor Tinkering" },
|
||||
{ 0x1E, "Magic Item Tinkering" },
|
||||
{ 0x1F, "Creature Enchantment" },
|
||||
{ 0x20, "Item Enchantment" },
|
||||
{ 0x21, "Life Magic" },
|
||||
{ 0x22, "War Magic" },
|
||||
{ 0x23, "Leadership" },
|
||||
{ 0x24, "Loyalty" },
|
||||
{ 0x25, "Fletching" },
|
||||
{ 0x26, "Alchemy" },
|
||||
{ 0x27, "Cooking" },
|
||||
{ 0x28, "Salvaging" },
|
||||
{ 0x29, "Two Handed Combat" },
|
||||
{ 0x2A, "Gearcraft"},
|
||||
{ 0x2B, "Void" },
|
||||
{ 0x2C, "Heavy Weapons" },
|
||||
{ 0x2D, "Light Weapons" },
|
||||
{ 0x2E, "Finesse Weapons" },
|
||||
{ 0x2F, "Missile Weapons" },
|
||||
{ 0x30, "Shield" },
|
||||
{ 0x31, "Dual Wield" },
|
||||
{ 0x32, "Recklessness" },
|
||||
{ 0x33, "Sneak Attack" },
|
||||
{ 0x34, "Dirty Fighting" },
|
||||
{ 0x35, "Challenge" },
|
||||
{ 0x36, "Summoning" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of mastery ids vs names
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<int, string> MasteryInfo = new Dictionary<int, string>
|
||||
{
|
||||
{ 1, "Unarmed Weapon" },
|
||||
{ 2, "Sword" },
|
||||
{ 3, "Axe" },
|
||||
{ 4, "Mace" },
|
||||
{ 5, "Spear" },
|
||||
{ 6, "Dagger" },
|
||||
{ 7, "Staff" },
|
||||
{ 8, "Bow" },
|
||||
{ 9, "Crossbow" },
|
||||
{ 10, "Thrown" },
|
||||
{ 11, "Two Handed Combat" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of attribute set ids vs names
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<int, string> AttributeSetInfo = new Dictionary<int, string>
|
||||
{
|
||||
// This list was taken from Virindi Tank Loot Editor
|
||||
// 01
|
||||
{ 02, "Test"},
|
||||
// 03
|
||||
{ 04, "Carraida's Benediction"},
|
||||
{ 05, "Noble Relic Set" },
|
||||
{ 06, "Ancient Relic Set" },
|
||||
{ 07, "Relic Alduressa Set" },
|
||||
{ 08, "Shou-jen Set" },
|
||||
{ 09, "Empyrean Rings Set" },
|
||||
{ 10, "Arm, Mind, Heart Set" },
|
||||
{ 11, "Coat of the Perfect Light Set" },
|
||||
{ 12, "Leggings of Perfect Light Set" },
|
||||
{ 13, "Soldier's Set" },
|
||||
{ 14, "Adept's Set" },
|
||||
{ 15, "Archer's Set" },
|
||||
{ 16, "Defender's Set" },
|
||||
{ 17, "Tinker's Set" },
|
||||
{ 18, "Crafter's Set" },
|
||||
{ 19, "Hearty Set" },
|
||||
{ 20, "Dexterous Set" },
|
||||
{ 21, "Wise Set" },
|
||||
{ 22, "Swift Set" },
|
||||
{ 23, "Hardenend Set" },
|
||||
{ 24, "Reinforced Set" },
|
||||
{ 25, "Interlocking Set" },
|
||||
{ 26, "Flame Proof Set" },
|
||||
{ 27, "Acid Proof Set" },
|
||||
{ 28, "Cold Proof Set" },
|
||||
{ 29, "Lightning Proof Set" },
|
||||
{ 30, "Dedication Set" },
|
||||
{ 31, "Gladiatorial Clothing Set" },
|
||||
{ 32, "Ceremonial Clothing" },
|
||||
{ 33, "Protective Clothing" },
|
||||
{ 34, "Noobie Armor" },
|
||||
{ 35, "Sigil of Defense" },
|
||||
{ 36, "Sigil of Destruction" },
|
||||
{ 37, "Sigil of Fury" },
|
||||
{ 38, "Sigil of Growth" },
|
||||
{ 39, "Sigil of Vigor" },
|
||||
{ 40, "Heroic Protector Set" },
|
||||
{ 41, "Heroic Destroyer Set" },
|
||||
{ 42, "Olthoi Armor D Red" },
|
||||
{ 43, "Olthoi Armor C Rat" },
|
||||
{ 44, "Olthoi Armor C Red" },
|
||||
{ 45, "Olthoi Armor D Rat" },
|
||||
{ 46, "Upgraded Relic Alduressa Set" },
|
||||
{ 47, "Upgraded Ancient Relic Set" },
|
||||
{ 48, "Upgraded Noble Relic Set" },
|
||||
{ 49, "Weave of Alchemy" },
|
||||
{ 50, "Weave of Arcane Lore" },
|
||||
{ 51, "Weave of Armor Tinkering" },
|
||||
{ 52, "Weave of Assess Person" },
|
||||
{ 53, "Weave of Light Weapons" },
|
||||
{ 54, "Weave of Missile Weapons" },
|
||||
{ 55, "Weave of Cooking" },
|
||||
{ 56, "Weave of Creature Enchantment" },
|
||||
{ 57, "Weave of Missile Weapons" },
|
||||
{ 58, "Weave of Finesse" },
|
||||
{ 59, "Weave of Deception" },
|
||||
{ 60, "Weave of Fletching" },
|
||||
{ 61, "Weave of Healing" },
|
||||
{ 62, "Weave of Item Enchantment" },
|
||||
{ 63, "Weave of Item Tinkering" },
|
||||
{ 64, "Weave of Leadership" },
|
||||
{ 65, "Weave of Life Magic" },
|
||||
{ 66, "Weave of Loyalty" },
|
||||
{ 67, "Weave of Light Weapons" },
|
||||
{ 68, "Weave of Magic Defense" },
|
||||
{ 69, "Weave of Magic Item Tinkering" },
|
||||
{ 70, "Weave of Mana Conversion" },
|
||||
{ 71, "Weave of Melee Defense" },
|
||||
{ 72, "Weave of Missile Defense" },
|
||||
{ 73, "Weave of Salvaging" },
|
||||
{ 74, "Weave of Light Weapons" },
|
||||
{ 75, "Weave of Light Weapons" },
|
||||
{ 76, "Weave of Heavy Weapons" },
|
||||
{ 77, "Weave of Missile Weapons" },
|
||||
{ 78, "Weave of Two Handed Combat" },
|
||||
{ 79, "Weave of Light Weapons" },
|
||||
{ 80, "Weave of Void Magic" },
|
||||
{ 81, "Weave of War Magic" },
|
||||
{ 82, "Weave of Weapon Tinkering" },
|
||||
{ 83, "Weave of Assess Creature " },
|
||||
{ 84, "Weave of Dirty Fighting" },
|
||||
{ 85, "Weave of Dual Wield" },
|
||||
{ 86, "Weave of Recklessness" },
|
||||
{ 87, "Weave of Shield" },
|
||||
{ 88, "Weave of Sneak Attack" },
|
||||
{ 89, "Ninja_New" },
|
||||
{ 90, "Weave of Summoning" },
|
||||
|
||||
{ 91, "Shrouded Soul" },
|
||||
{ 92, "Darkened Mind" },
|
||||
{ 93, "Clouded Spirit" },
|
||||
{ 94, "Minor Stinging Shrouded Soul" },
|
||||
{ 95, "Minor Sparking Shrouded Soul" },
|
||||
{ 96, "Minor Smoldering Shrouded Soul" },
|
||||
{ 97, "Minor Shivering Shrouded Soul" },
|
||||
{ 98, "Minor Stinging Darkened Mind" },
|
||||
{ 99, "Minor Sparking Darkened Mind" },
|
||||
|
||||
{ 100, "Minor Smoldering Darkened Mind" },
|
||||
{ 101, "Minor Shivering Darkened Mind" },
|
||||
{ 102, "Minor Stinging Clouded Spirit" },
|
||||
{ 103, "Minor Sparking Clouded Spirit" },
|
||||
{ 104, "Minor Smoldering Clouded Spirit" },
|
||||
{ 105, "Minor Shivering Clouded Spirit" },
|
||||
{ 106, "Major Stinging Shrouded Soul" },
|
||||
{ 107, "Major Sparking Shrouded Soul" },
|
||||
{ 108, "Major Smoldering Shrouded Soul" },
|
||||
{ 109, "Major Shivering Shrouded Soul" },
|
||||
|
||||
{ 110, "Major Stinging Darkened Mind" },
|
||||
{ 111, "Major Sparking Darkened Mind" },
|
||||
{ 112, "Major Smoldering Darkened Mind" },
|
||||
{ 113, "Major Shivering Darkened Mind" },
|
||||
{ 114, "Major Stinging Clouded Spirit" },
|
||||
{ 115, "Major Sparking Clouded Spirit" },
|
||||
{ 116, "Major Smoldering Clouded Spirit" },
|
||||
{ 117, "Major Shivering Clouded Spirit" },
|
||||
{ 118, "Blackfire Stinging Shrouded Soul" },
|
||||
{ 119, "Blackfire Sparking Shrouded Soul" },
|
||||
|
||||
{ 120, "Blackfire Smoldering Shrouded Soul" },
|
||||
{ 121, "Blackfire Shivering Shrouded Soul" },
|
||||
{ 122, "Blackfire Stinging Darkened Mind" },
|
||||
{ 123, "Blackfire Sparking Darkened Mind" },
|
||||
{ 124, "Blackfire Smoldering Darkened Mind" },
|
||||
{ 125, "Blackfire Shivering Darkened Mind" },
|
||||
{ 126, "Blackfire Stinging Clouded Spirit" },
|
||||
{ 127, "Blackfire Sparking Clouded Spirit" },
|
||||
{ 128, "Blackfire Smoldering Clouded Spirit" },
|
||||
{ 129, "Blackfire Shivering Clouded Spirit" },
|
||||
|
||||
{ 130, "Shimmering Shadows" },
|
||||
|
||||
{ 131, "Brown Society Locket" },
|
||||
{ 132, "Yellow Society Locket" },
|
||||
{ 133, "Red Society Band" },
|
||||
{ 134, "Green Society Band" },
|
||||
{ 135, "Purple Society Band" },
|
||||
{ 136, "Blue Society Band" },
|
||||
|
||||
{ 137, "Gauntlet Garb" },
|
||||
|
||||
{ 138, "UNKNOWN_138" }, // Possibly Paragon Missile Weapons
|
||||
{ 139, "UNKNOWN_139" }, // Possibly Paragon Casters
|
||||
{ 140, "UNKNOWN_140" }, // Possibly Paragon Melee Weapons
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of material ids vs names
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<int, string> MaterialInfo = new Dictionary<int, string>
|
||||
{
|
||||
{ 1, "Ceramic" },
|
||||
{ 2, "Porcelain" },
|
||||
// 3
|
||||
{ 4, "Linen" },
|
||||
{ 5, "Satin" },
|
||||
{ 6, "Silk" },
|
||||
{ 7, "Velvet" },
|
||||
{ 8, "Wool" },
|
||||
// 9
|
||||
{ 10, "Agate" },
|
||||
{ 11, "Amber" },
|
||||
{ 12, "Amethyst" },
|
||||
{ 13, "Aquamarine" },
|
||||
{ 14, "Azurite" },
|
||||
{ 15, "Black Garnet" },
|
||||
{ 16, "Black Opal" },
|
||||
{ 17, "Bloodstone" },
|
||||
{ 18, "Carnelian" },
|
||||
{ 19, "Citrine" },
|
||||
{ 20, "Diamond" },
|
||||
{ 21, "Emerald" },
|
||||
{ 22, "Fire Opal" },
|
||||
{ 23, "Green Garnet" },
|
||||
{ 24, "Green Jade" },
|
||||
{ 25, "Hematite" },
|
||||
{ 26, "Imperial Topaz" },
|
||||
{ 27, "Jet" },
|
||||
{ 28, "Lapis Lazuli" },
|
||||
{ 29, "Lavender Jade" },
|
||||
{ 30, "Malachite" },
|
||||
{ 31, "Moonstone" },
|
||||
{ 32, "Onyx" },
|
||||
{ 33, "Opal" },
|
||||
{ 34, "Peridot" },
|
||||
{ 35, "Red Garnet" },
|
||||
{ 36, "Red Jade" },
|
||||
{ 37, "Rose Quartz" },
|
||||
{ 38, "Ruby" },
|
||||
{ 39, "Sapphire" },
|
||||
{ 40, "Smokey Quartz" },
|
||||
{ 41, "Sunstone" },
|
||||
{ 42, "Tiger Eye" },
|
||||
{ 43, "Tourmaline" },
|
||||
{ 44, "Turquoise" },
|
||||
{ 45, "White Jade" },
|
||||
{ 46, "White Quartz" },
|
||||
{ 47, "White Sapphire" },
|
||||
{ 48, "Yellow Garnet" },
|
||||
{ 49, "Yellow Topaz" },
|
||||
{ 50, "Zircon" },
|
||||
{ 51, "Ivory" },
|
||||
{ 52, "Leather" },
|
||||
{ 53, "Armoredillo Hide" },
|
||||
{ 54, "Gromnie Hide" },
|
||||
{ 55, "Reed Shark Hide" },
|
||||
// 56
|
||||
{ 57, "Brass" },
|
||||
{ 58, "Bronze" },
|
||||
{ 59, "Copper" },
|
||||
{ 60, "Gold" },
|
||||
{ 61, "Iron" },
|
||||
{ 62, "Pyreal" },
|
||||
{ 63, "Silver" },
|
||||
{ 64, "Steel" },
|
||||
// 65
|
||||
{ 66, "Alabaster" },
|
||||
{ 67, "Granite" },
|
||||
{ 68, "Marble" },
|
||||
{ 69, "Obsidian" },
|
||||
{ 70, "Sandstone" },
|
||||
{ 71, "Serpentine" },
|
||||
{ 73, "Ebony" },
|
||||
{ 74, "Mahogany" },
|
||||
{ 75, "Oak" },
|
||||
{ 76, "Pine" },
|
||||
{ 77, "Teak" },
|
||||
};
|
||||
|
||||
public struct SpellInfo<T>
|
||||
{
|
||||
public readonly int Key;
|
||||
public readonly T Change;
|
||||
public readonly T Bonus;
|
||||
|
||||
public SpellInfo(int key, T change, T bonus = default(T))
|
||||
{
|
||||
Key = key;
|
||||
Change = change;
|
||||
Bonus = bonus;
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from Decal.Adapter.Wrappers.LongValueKey
|
||||
const int LongValueKey_MaxDamage = 218103842;
|
||||
const int LongValueKey_ArmorLevel = 28;
|
||||
|
||||
public static readonly Dictionary<int, SpellInfo<int>> LongValueKeySpellEffects = new Dictionary<int, SpellInfo<int>>()
|
||||
{
|
||||
// In 2012 they removed these item spells and converted them to auras that are cast on the player, not on the item.
|
||||
{ 1616, new SpellInfo<int>(LongValueKey_MaxDamage, 20)}, // Blood Drinker VI
|
||||
{ 2096, new SpellInfo<int>(LongValueKey_MaxDamage, 22)}, // Infected Caress
|
||||
//{ 5183, new SpellInfo<LongValueKey>(LongValueKey_MaxDamage, 22)}, // Incantation of Blood Drinker Pre Feb-2013
|
||||
//{ 4395, new SpellInfo<LongValueKey>(LongValueKey_MaxDamage, 24, 2)}, // Incantation of Blood Drinker, this spell on the item adds 2 more points of damage over a user casted 8 Pre Feb-2013
|
||||
{ 5183, new SpellInfo<int>(LongValueKey_MaxDamage, 24)}, // Incantation of Blood Drinker Post Feb-2013
|
||||
{ 4395, new SpellInfo<int>(LongValueKey_MaxDamage, 24)}, // Incantation of Blood Drinker Post Feb-2013
|
||||
|
||||
{ 2598, new SpellInfo<int>(LongValueKey_MaxDamage, 2, 2)}, // Minor Blood Thirst
|
||||
{ 2586, new SpellInfo<int>(LongValueKey_MaxDamage, 4, 4)}, // Major Blood Thirst
|
||||
{ 4661, new SpellInfo<int>(LongValueKey_MaxDamage, 7, 7)}, // Epic Blood Thirst
|
||||
{ 6089, new SpellInfo<int>(LongValueKey_MaxDamage, 10, 10)}, // Legendary Blood Thirst
|
||||
|
||||
{ 3688, new SpellInfo<int>(LongValueKey_MaxDamage, 300)}, // Prodigal Blood Drinker
|
||||
|
||||
|
||||
{ 1486, new SpellInfo<int>(LongValueKey_ArmorLevel, 200)}, // Impenetrability VI
|
||||
{ 2108, new SpellInfo<int>(LongValueKey_ArmorLevel, 220)}, // Brogard's Defiance
|
||||
{ 4407, new SpellInfo<int>(LongValueKey_ArmorLevel, 240)}, // Incantation of Impenetrability
|
||||
|
||||
{ 2604, new SpellInfo<int>(LongValueKey_ArmorLevel, 20, 20)}, // Minor Impenetrability
|
||||
{ 2592, new SpellInfo<int>(LongValueKey_ArmorLevel, 40, 40)}, // Major Impenetrability
|
||||
{ 4667, new SpellInfo<int>(LongValueKey_ArmorLevel, 60, 60)}, // Epic Impenetrability
|
||||
{ 6095, new SpellInfo<int>(LongValueKey_ArmorLevel, 80, 80)}, // Legendary Impenetrability
|
||||
};
|
||||
|
||||
// Taken from Decal.Adapter.Wrappers.DoubleValueKey
|
||||
const int DoubleValueKey_ElementalDamageVersusMonsters = 152;
|
||||
const int DoubleValueKey_AttackBonus = 167772172;
|
||||
const int DoubleValueKey_MeleeDefenseBonus = 29;
|
||||
const int DoubleValueKey_ManaCBonus = 144;
|
||||
|
||||
public static readonly Dictionary<int, SpellInfo<double>> DoubleValueKeySpellEffects = new Dictionary<int, SpellInfo<double>>()
|
||||
{
|
||||
// In 2012 they removed these item spells and converted them to auras that are cast on the player, not on the item.
|
||||
{ 3258, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .06)}, // Spirit Drinker VI
|
||||
{ 3259, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .07)}, // Infected Spirit Caress
|
||||
//{ 5182, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .07)}, // Incantation of Spirit Drinker Pre Feb-2013
|
||||
//{ 4414, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .08, .01)}, // Incantation of Spirit Drinker, this spell on the item adds 1 more % of damage over a user casted 8 Pre Feb-2013
|
||||
{ 5182, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .08)}, // Incantation of Spirit Drinker Post Feb-2013
|
||||
{ 4414, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .08)}, // Incantation of Spirit Drinker, this spell on the item adds 1 more % of damage over a user casted 8 Post Feb-2013
|
||||
|
||||
{ 3251, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .01, .01)}, // Minor Spirit Thirst
|
||||
{ 3250, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .03, .03)}, // Major Spirit Thirst
|
||||
{ 4670, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .05, .05)}, // Epic Spirit Thirst
|
||||
{ 6098, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .07, .07)}, // Legendary Spirit Thirst
|
||||
|
||||
{ 3735, new SpellInfo<double>(DoubleValueKey_ElementalDamageVersusMonsters, .15)}, // Prodigal Spirit Drinker
|
||||
|
||||
|
||||
// In 2012 they removed these item spells and converted them to auras that are cast on the player, not on the item.
|
||||
{ 1592, new SpellInfo<double>(DoubleValueKey_AttackBonus, .15)}, // Heart Seeker VI
|
||||
{ 2106, new SpellInfo<double>(DoubleValueKey_AttackBonus, .17)}, // Elysa's Sight
|
||||
{ 4405, new SpellInfo<double>(DoubleValueKey_AttackBonus, .20)}, // Incantation of Heart Seeker
|
||||
|
||||
{ 2603, new SpellInfo<double>(DoubleValueKey_AttackBonus, .03, .03)}, // Minor Heart Thirst
|
||||
{ 2591, new SpellInfo<double>(DoubleValueKey_AttackBonus, .05, .05)}, // Major Heart Thirst
|
||||
{ 4666, new SpellInfo<double>(DoubleValueKey_AttackBonus, .07, .07)}, // Epic Heart Thirst
|
||||
{ 6094, new SpellInfo<double>(DoubleValueKey_AttackBonus, .09, .09)}, // Legendary Heart Thirst
|
||||
|
||||
|
||||
// In 2012 they removed these item spells and converted them to auras that are cast on the player, not on the item.
|
||||
{ 1605, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .15)}, // Defender VI
|
||||
{ 2101, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .17)}, // Cragstone's Will
|
||||
//{ 4400, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .17)}, // Incantation of Defender Pre Feb-2013
|
||||
{ 4400, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .20)}, // Incantation of Defender Post Feb-2013
|
||||
|
||||
{ 2600, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .03, .03)}, // Minor Defender
|
||||
{ 3985, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .04, .04)}, // Mukkir Sense
|
||||
{ 2588, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .05, .05)}, // Major Defender
|
||||
{ 4663, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .07, .07)}, // Epic Defender
|
||||
{ 6091, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .09, .09)}, // Legendary Defender
|
||||
|
||||
{ 3699, new SpellInfo<double>(DoubleValueKey_MeleeDefenseBonus, .25)}, // Prodigal Defender
|
||||
|
||||
|
||||
// In 2012 they removed these item spells and converted them to auras that are cast on the player, not on the item.
|
||||
{ 1480, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.60)}, // Hermetic Link VI
|
||||
{ 2117, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.70)}, // Mystic's Blessing
|
||||
{ 4418, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.80)}, // Incantation of Hermetic Link
|
||||
|
||||
{ 3201, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.05, 1.05)}, // Feeble Hermetic Link
|
||||
{ 3199, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.10, 1.10)}, // Minor Hermetic Link
|
||||
{ 3202, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.15, 1.15)}, // Moderate Hermetic Link
|
||||
{ 3200, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.20, 1.20)}, // Major Hermetic Link
|
||||
{ 6086, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.25, 1.25)}, // Epic Hermetic Link
|
||||
{ 6087, new SpellInfo<double>(DoubleValueKey_ManaCBonus, 1.30, 1.30)}, // Legendary Hermetic Link
|
||||
};
|
||||
}
|
||||
}
|
||||
266
Shared/Constants/DoubleValueKey.cs
Normal file
266
Shared/Constants/DoubleValueKey.cs
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
// https://github.com/ACEmulator/ACE/blob/master/Source/ACE.Entity/Enum/Properties/PropertyFloat.cs
|
||||
public enum DoubleValueKey
|
||||
{
|
||||
// properties marked as ServerOnly are properties we never saw in PCAPs, from here:
|
||||
// http://ac.yotesfan.com/ace_object/not_used_enums.php
|
||||
// source: @OptimShi
|
||||
// description attributes are used by the weenie editor for a cleaner display name
|
||||
|
||||
Undef = 0,
|
||||
HeartbeatInterval = 1,
|
||||
[Ephemeral]
|
||||
HeartbeatTimestamp = 2,
|
||||
HealthRate = 3,
|
||||
StaminaRate = 4,
|
||||
ManaRate = 5,
|
||||
HealthUponResurrection = 6,
|
||||
StaminaUponResurrection = 7,
|
||||
ManaUponResurrection = 8,
|
||||
StartTime = 9,
|
||||
StopTime = 10,
|
||||
ResetInterval = 11,
|
||||
Shade = 12,
|
||||
ArmorModVsSlash = 13,
|
||||
ArmorModVsPierce = 14,
|
||||
ArmorModVsBludgeon = 15,
|
||||
ArmorModVsCold = 16,
|
||||
ArmorModVsFire = 17,
|
||||
ArmorModVsAcid = 18,
|
||||
ArmorModVsElectric = 19,
|
||||
CombatSpeed = 20,
|
||||
WeaponLength = 21,
|
||||
DamageVariance = 22,
|
||||
CurrentPowerMod = 23,
|
||||
AccuracyMod = 24,
|
||||
StrengthMod = 25,
|
||||
MaximumVelocity = 26,
|
||||
RotationSpeed = 27,
|
||||
MotionTimestamp = 28,
|
||||
WeaponDefense = 29,
|
||||
WimpyLevel = 30,
|
||||
VisualAwarenessRange = 31,
|
||||
AuralAwarenessRange = 32,
|
||||
PerceptionLevel = 33,
|
||||
PowerupTime = 34,
|
||||
MaxChargeDistance = 35,
|
||||
ChargeSpeed = 36,
|
||||
BuyPrice = 37,
|
||||
SellPrice = 38,
|
||||
DefaultScale = 39,
|
||||
LockpickMod = 40,
|
||||
RegenerationInterval = 41,
|
||||
RegenerationTimestamp = 42,
|
||||
GeneratorRadius = 43,
|
||||
TimeToRot = 44,
|
||||
DeathTimestamp = 45,
|
||||
PkTimestamp = 46,
|
||||
VictimTimestamp = 47,
|
||||
LoginTimestamp = 48,
|
||||
CreationTimestamp = 49,
|
||||
MinimumTimeSincePk = 50,
|
||||
DeprecatedHousekeepingPriority = 51,
|
||||
AbuseLoggingTimestamp = 52,
|
||||
LastPortalTeleportTimestamp = 53,
|
||||
UseRadius = 54,
|
||||
HomeRadius = 55,
|
||||
ReleasedTimestamp = 56,
|
||||
MinHomeRadius = 57,
|
||||
Facing = 58,
|
||||
ResetTimestamp = 59,
|
||||
LogoffTimestamp = 60,
|
||||
EconRecoveryInterval = 61,
|
||||
WeaponOffense = 62,
|
||||
DamageMod = 63,
|
||||
ResistSlash = 64,
|
||||
ResistPierce = 65,
|
||||
ResistBludgeon = 66,
|
||||
ResistFire = 67,
|
||||
ResistCold = 68,
|
||||
ResistAcid = 69,
|
||||
ResistElectric = 70,
|
||||
ResistHealthBoost = 71,
|
||||
ResistStaminaDrain = 72,
|
||||
ResistStaminaBoost = 73,
|
||||
ResistManaDrain = 74,
|
||||
ResistManaBoost = 75,
|
||||
[Ephemeral]
|
||||
Translucency = 76,
|
||||
PhysicsScriptIntensity = 77,
|
||||
Friction = 78,
|
||||
Elasticity = 79,
|
||||
AiUseMagicDelay = 80,
|
||||
ItemMinSpellcraftMod = 81,
|
||||
ItemMaxSpellcraftMod = 82,
|
||||
ItemRankProbability = 83,
|
||||
Shade2 = 84,
|
||||
Shade3 = 85,
|
||||
Shade4 = 86,
|
||||
ItemEfficiency = 87,
|
||||
ItemManaUpdateTimestamp = 88,
|
||||
SpellGestureSpeedMod = 89,
|
||||
SpellStanceSpeedMod = 90,
|
||||
AllegianceAppraisalTimestamp = 91,
|
||||
PowerLevel = 92,
|
||||
AccuracyLevel = 93,
|
||||
AttackAngle = 94,
|
||||
AttackTimestamp = 95,
|
||||
CheckpointTimestamp = 96,
|
||||
SoldTimestamp = 97,
|
||||
UseTimestamp = 98,
|
||||
UseLockTimestamp = 99,
|
||||
HealkitMod = 100,
|
||||
FrozenTimestamp = 101,
|
||||
HealthRateMod = 102,
|
||||
AllegianceSwearTimestamp = 103,
|
||||
ObviousRadarRange = 104,
|
||||
HotspotCycleTime = 105,
|
||||
HotspotCycleTimeVariance = 106,
|
||||
SpamTimestamp = 107,
|
||||
SpamRate = 108,
|
||||
BondWieldedTreasure = 109,
|
||||
BulkMod = 110,
|
||||
SizeMod = 111,
|
||||
GagTimestamp = 112,
|
||||
GeneratorUpdateTimestamp = 113,
|
||||
DeathSpamTimestamp = 114,
|
||||
DeathSpamRate = 115,
|
||||
WildAttackProbability = 116,
|
||||
FocusedProbability = 117,
|
||||
CrashAndTurnProbability = 118,
|
||||
CrashAndTurnRadius = 119,
|
||||
CrashAndTurnBias = 120,
|
||||
GeneratorInitialDelay = 121,
|
||||
AiAcquireHealth = 122,
|
||||
AiAcquireStamina = 123,
|
||||
AiAcquireMana = 124,
|
||||
/// <summary>
|
||||
/// this had a default of "1" - leaving comment to investigate potential options for defaulting these things (125)
|
||||
/// </summary>
|
||||
[SendOnLogin]
|
||||
ResistHealthDrain = 125,
|
||||
LifestoneProtectionTimestamp = 126,
|
||||
AiCounteractEnchantment = 127,
|
||||
AiDispelEnchantment = 128,
|
||||
TradeTimestamp = 129,
|
||||
AiTargetedDetectionRadius = 130,
|
||||
EmotePriority = 131,
|
||||
[Ephemeral]
|
||||
LastTeleportStartTimestamp = 132,
|
||||
EventSpamTimestamp = 133,
|
||||
EventSpamRate = 134,
|
||||
InventoryOffset = 135,
|
||||
CriticalMultiplier = 136,
|
||||
ManaStoneDestroyChance = 137,
|
||||
SlayerDamageBonus = 138,
|
||||
AllegianceInfoSpamTimestamp = 139,
|
||||
AllegianceInfoSpamRate = 140,
|
||||
NextSpellcastTimestamp = 141,
|
||||
[Ephemeral]
|
||||
AppraisalRequestedTimestamp = 142,
|
||||
AppraisalHeartbeatDueTimestamp = 143,
|
||||
ManaConversionMod = 144,
|
||||
LastPkAttackTimestamp = 145,
|
||||
FellowshipUpdateTimestamp = 146,
|
||||
CriticalFrequency = 147,
|
||||
LimboStartTimestamp = 148,
|
||||
WeaponMissileDefense = 149,
|
||||
WeaponMagicDefense = 150,
|
||||
IgnoreShield = 151,
|
||||
ElementalDamageMod = 152,
|
||||
StartMissileAttackTimestamp = 153,
|
||||
LastRareUsedTimestamp = 154,
|
||||
IgnoreArmor = 155,
|
||||
ProcSpellRate = 156,
|
||||
ResistanceModifier = 157,
|
||||
AllegianceGagTimestamp = 158,
|
||||
AbsorbMagicDamage = 159,
|
||||
CachedMaxAbsorbMagicDamage = 160,
|
||||
GagDuration = 161,
|
||||
AllegianceGagDuration = 162,
|
||||
[SendOnLogin]
|
||||
GlobalXpMod = 163,
|
||||
HealingModifier = 164,
|
||||
ArmorModVsNether = 165,
|
||||
ResistNether = 166,
|
||||
CooldownDuration = 167,
|
||||
[SendOnLogin]
|
||||
WeaponAuraOffense = 168,
|
||||
[SendOnLogin]
|
||||
WeaponAuraDefense = 169,
|
||||
[SendOnLogin]
|
||||
WeaponAuraElemental = 170,
|
||||
[SendOnLogin]
|
||||
WeaponAuraManaConv = 171,
|
||||
|
||||
|
||||
// ACE Specific
|
||||
[ServerOnly]
|
||||
PCAPRecordedWorkmanship = 8004,
|
||||
[ServerOnly]
|
||||
PCAPRecordedVelocityX = 8010,
|
||||
[ServerOnly]
|
||||
PCAPRecordedVelocityY = 8011,
|
||||
[ServerOnly]
|
||||
PCAPRecordedVelocityZ = 8012,
|
||||
[ServerOnly]
|
||||
PCAPRecordedAccelerationX = 8013,
|
||||
[ServerOnly]
|
||||
PCAPRecordedAccelerationY = 8014,
|
||||
[ServerOnly]
|
||||
PCAPRecordedAccelerationZ = 8015,
|
||||
[ServerOnly]
|
||||
PCAPRecordeOmegaX = 8016,
|
||||
[ServerOnly]
|
||||
PCAPRecordeOmegaY = 8017,
|
||||
[ServerOnly]
|
||||
PCAPRecordeOmegaZ = 8018,
|
||||
|
||||
|
||||
// Decal Specific
|
||||
SlashProt_Decal = 167772160,
|
||||
PierceProt_Decal = 167772161,
|
||||
BludgeonProt_Decal = 167772162,
|
||||
AcidProt_Decal = 167772163,
|
||||
LightningProt_Decal = 167772164,
|
||||
FireProt_Decal = 167772165,
|
||||
ColdProt_Decal = 167772166,
|
||||
Heading_Decal = 167772167,
|
||||
ApproachDistance_Decal = 167772168,
|
||||
SalvageWorkmanship_Decal = 167772169,
|
||||
Scale_Decal = 167772170,
|
||||
Variance_Decal = 167772171,
|
||||
AttackBonus_Decal = 167772172,
|
||||
Range_Decal = 167772173,
|
||||
DamageBonus_Decal = 167772174,
|
||||
}
|
||||
|
||||
public static class DoubleValueKeyTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a decal specific IntValueKey to the actual IntValueKey.
|
||||
/// If this is not an IntValueKey, 0 will be returned.
|
||||
/// </summary>
|
||||
public static uint ConvertToDouble(DoubleValueKey input)
|
||||
{
|
||||
if (input == DoubleValueKey.SlashProt_Decal) return (int)DoubleValueKey.ArmorModVsSlash;
|
||||
if (input == DoubleValueKey.PierceProt_Decal) return (int)DoubleValueKey.ArmorModVsPierce;
|
||||
if (input == DoubleValueKey.BludgeonProt_Decal) return (int)DoubleValueKey.ArmorModVsBludgeon;
|
||||
if (input == DoubleValueKey.AcidProt_Decal) return (int)DoubleValueKey.ArmorModVsAcid;
|
||||
if (input == DoubleValueKey.LightningProt_Decal) return (int)DoubleValueKey.ArmorModVsElectric;
|
||||
if (input == DoubleValueKey.FireProt_Decal) return (int)DoubleValueKey.ArmorModVsFire;
|
||||
if (input == DoubleValueKey.ColdProt_Decal) return (int)DoubleValueKey.ArmorModVsCold;
|
||||
|
||||
if (input == DoubleValueKey.ApproachDistance_Decal) return (int)DoubleValueKey.UseRadius;
|
||||
if (input == DoubleValueKey.Scale_Decal) return (int)DoubleValueKey.DefaultScale;
|
||||
if (input == DoubleValueKey.Variance_Decal) return (int)DoubleValueKey.DamageVariance;
|
||||
if (input == DoubleValueKey.AttackBonus_Decal) return (int)DoubleValueKey.WeaponOffense;;
|
||||
if (input == DoubleValueKey.Range_Decal) return (int)DoubleValueKey.MaximumVelocity;
|
||||
if (input == DoubleValueKey.DamageBonus_Decal) return (int)DoubleValueKey.DamageMod;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Shared/Constants/EphemeralAttribute.cs
Normal file
11
Shared/Constants/EphemeralAttribute.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// These are properties that aren't saved to the shard.
|
||||
/// </summary>
|
||||
public class EphemeralAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
126
Shared/Constants/EquipMask.cs
Normal file
126
Shared/Constants/EquipMask.cs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the mapping for LongValueKey.EquippableSlots.
|
||||
/// It represents where you can drag items to on your paper doll.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum EquipMask : uint
|
||||
{
|
||||
None = 0x00000000,
|
||||
|
||||
HeadWear = 0x00000001,
|
||||
|
||||
ChestWear = 0x00000002,
|
||||
AbdomenWear = 0x00000004,
|
||||
UpperArmWear = 0x00000008,
|
||||
LowerArmWear = 0x00000010,
|
||||
|
||||
HandWear = 0x00000020,
|
||||
|
||||
UpperLegWear = 0x00000040,
|
||||
LowerLegWear = 0x00000080,
|
||||
|
||||
FootWear = 0x00000100,
|
||||
ChestArmor = 0x00000200,
|
||||
AbdomenArmor = 0x00000400,
|
||||
UpperArmArmor = 0x00000800,
|
||||
LowerArmArmor = 0x00001000,
|
||||
UpperLegArmor = 0x00002000,
|
||||
LowerLegArmor = 0x00004000,
|
||||
|
||||
NeckWear = 0x00008000,
|
||||
WristWearLeft = 0x00010000,
|
||||
WristWearRight = 0x00020000,
|
||||
FingerWearLeft = 0x00040000,
|
||||
FingerWearRight = 0x00080000,
|
||||
|
||||
MeleeWeapon = 0x00100000,
|
||||
Shield = 0x00200000,
|
||||
MissileWeapon = 0x00400000,
|
||||
MissileAmmo = 0x00800000,
|
||||
Held = 0x01000000,
|
||||
TwoHanded = 0x02000000,
|
||||
|
||||
TrinketOne = 0x04000000,
|
||||
Cloak = 0x08000000,
|
||||
|
||||
SigilOne = 0x10000000, // Blue
|
||||
SigilTwo = 0x20000000, // Yellow
|
||||
SigilThree = 0x40000000, // Red
|
||||
|
||||
Clothing = 0x80000000 | HeadWear | ChestWear | AbdomenWear | UpperArmWear | LowerArmWear | HandWear | UpperLegWear | LowerLegWear | FootWear,
|
||||
Armor = ChestArmor | AbdomenArmor | UpperArmArmor | LowerArmArmor | UpperLegArmor | LowerLegArmor | FootWear,
|
||||
ArmorExclusive = ChestArmor | AbdomenArmor | UpperArmArmor | LowerArmArmor | UpperLegArmor | LowerLegArmor,
|
||||
Extremity = HeadWear | HandWear | FootWear,
|
||||
Jewelry = NeckWear | WristWearLeft | WristWearRight | FingerWearLeft | FingerWearRight | TrinketOne | Cloak | SigilOne | SigilTwo | SigilThree,
|
||||
WristWear = WristWearLeft | WristWearRight,
|
||||
FingerWear = FingerWearLeft | FingerWearRight,
|
||||
Sigil = SigilOne | SigilTwo | SigilThree,
|
||||
ReadySlot = Held | TwoHanded | TrinketOne | Cloak | SigilOne | SigilTwo,
|
||||
Weapon = SigilTwo | TrinketOne | Held,
|
||||
WeaponReadySlot = SigilOne | SigilTwo | TrinketOne | Held,
|
||||
Selectable = MeleeWeapon | Shield | MissileWeapon | Held | TwoHanded,
|
||||
SelectablePlusAmmo = Selectable | MissileAmmo,
|
||||
All = 0x7FFFFFFF,
|
||||
CanGoInReadySlot = 0x7FFFFFFF
|
||||
}
|
||||
|
||||
public static class EquipMaskExtensions
|
||||
{
|
||||
public static int GetTotalBitsSet(this EquipMask value)
|
||||
{
|
||||
int slotFlags = (int)value;
|
||||
int bitsSet = 0;
|
||||
|
||||
while (slotFlags != 0)
|
||||
{
|
||||
if ((slotFlags & 1) == 1)
|
||||
bitsSet++;
|
||||
slotFlags >>= 1;
|
||||
}
|
||||
|
||||
return bitsSet;
|
||||
}
|
||||
|
||||
// Some feet armor have EquipMask.Feet | EquipMask.PantsLowerLegs
|
||||
|
||||
public static bool IsBodyArmor(this EquipMask value)
|
||||
{
|
||||
return ((int)value & 0x00007F21) != 0;
|
||||
}
|
||||
|
||||
public static bool IsCoreBodyArmor(this EquipMask value)
|
||||
{
|
||||
return (value & (EquipMask.ChestArmor | EquipMask.UpperArmArmor | EquipMask.LowerArmArmor | EquipMask.AbdomenArmor | EquipMask.UpperLegArmor | EquipMask.LowerLegArmor)) != 0;
|
||||
}
|
||||
|
||||
public static bool IsExtremityBodyArmor(this EquipMask value)
|
||||
{
|
||||
return (value & (EquipMask.FootWear | EquipMask.HandWear | EquipMask.HeadWear)) != 0;
|
||||
}
|
||||
|
||||
public static bool IsUnderwear(this EquipMask value)
|
||||
{
|
||||
if (value == (EquipMask.FootWear | EquipMask.LowerLegWear))
|
||||
return false;
|
||||
|
||||
return ((int)value & 0x000000DE) != 0;
|
||||
}
|
||||
|
||||
public static bool IsShirt(this EquipMask value)
|
||||
{
|
||||
return ((int)value & 0x0000001A) != 0;
|
||||
}
|
||||
|
||||
public static bool IsPants(this EquipMask value)
|
||||
{
|
||||
if (value == (EquipMask.FootWear | EquipMask.LowerLegWear))
|
||||
return false;
|
||||
|
||||
return ((int)value & 0x000000C4) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
722
Shared/Constants/IntValueKey.cs
Normal file
722
Shared/Constants/IntValueKey.cs
Normal file
|
|
@ -0,0 +1,722 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
// https://github.com/ACEmulator/ACE/blob/master/Source/ACE.Entity/Enum/Properties/PropertyInt.cs
|
||||
public enum IntValueKey
|
||||
{
|
||||
// properties marked as ServerOnly are properties we never saw in PCAPs, from here:
|
||||
// http://ac.yotesfan.com/ace_object/not_used_enums.php
|
||||
// source: @OptimShi
|
||||
// description attributes are used by the weenie editor for a cleaner display name
|
||||
|
||||
Undef = 0,
|
||||
[ServerOnly]
|
||||
ItemType = 1,
|
||||
CreatureType = 2,
|
||||
[ServerOnly]
|
||||
PaletteTemplate = 3,
|
||||
ClothingPriority = 4,
|
||||
[SendOnLogin]
|
||||
EncumbranceVal = 5, // ENCUMB_VAL_INT,
|
||||
[SendOnLogin]
|
||||
ItemsCapacity = 6,
|
||||
[SendOnLogin]
|
||||
ContainersCapacity = 7,
|
||||
[ServerOnly]
|
||||
Mass = 8,
|
||||
[ServerOnly]
|
||||
ValidLocations = 9, // LOCATIONS_INT
|
||||
[ServerOnly]
|
||||
CurrentWieldedLocation = 10,
|
||||
[ServerOnly]
|
||||
MaxStackSize = 11,
|
||||
[ServerOnly]
|
||||
StackSize = 12,
|
||||
[ServerOnly]
|
||||
StackUnitEncumbrance = 13,
|
||||
[ServerOnly]
|
||||
StackUnitMass = 14,
|
||||
[ServerOnly]
|
||||
StackUnitValue = 15,
|
||||
[ServerOnly]
|
||||
ItemUseable = 16,
|
||||
RareId = 17,
|
||||
[ServerOnly]
|
||||
UiEffects = 18,
|
||||
Value = 19,
|
||||
[Ephemeral][SendOnLogin]
|
||||
CoinValue = 20,
|
||||
TotalExperience = 21,
|
||||
AvailableCharacter = 22,
|
||||
TotalSkillCredits = 23,
|
||||
[SendOnLogin]
|
||||
AvailableSkillCredits = 24,
|
||||
[SendOnLogin]
|
||||
Level = 25,
|
||||
AccountRequirements = 26,
|
||||
ArmorType = 27,
|
||||
ArmorLevel = 28,
|
||||
AllegianceCpPool = 29,
|
||||
[SendOnLogin]
|
||||
AllegianceRank = 30,
|
||||
ChannelsAllowed = 31,
|
||||
ChannelsActive = 32,
|
||||
Bonded = 33,
|
||||
MonarchsRank = 34,
|
||||
AllegianceFollowers = 35,
|
||||
ResistMagic = 36,
|
||||
ResistItemAppraisal = 37,
|
||||
ResistLockpick = 38,
|
||||
DeprecatedResistRepair = 39,
|
||||
[SendOnLogin]
|
||||
CombatMode = 40,
|
||||
CurrentAttackHeight = 41,
|
||||
CombatCollisions = 42,
|
||||
[SendOnLogin]
|
||||
NumDeaths = 43,
|
||||
Damage = 44,
|
||||
DamageType = 45,
|
||||
[ServerOnly]
|
||||
DefaultCombatStyle = 46,
|
||||
[SendOnLogin]
|
||||
AttackType = 47,
|
||||
WeaponSkill = 48,
|
||||
WeaponTime = 49,
|
||||
AmmoType = 50,
|
||||
CombatUse = 51,
|
||||
[ServerOnly]
|
||||
ParentLocation = 52,
|
||||
/// <summary>
|
||||
/// TODO: Migrate inventory order away from this and instead use the new InventoryOrder property
|
||||
/// TODO: PlacementPosition is used (very sparingly) in cache.bin, so it has (or had) a meaning at one point before we hijacked it
|
||||
/// TODO: and used it for our own inventory order
|
||||
/// </summary>
|
||||
[ServerOnly]
|
||||
PlacementPosition = 53,
|
||||
WeaponEncumbrance = 54,
|
||||
WeaponMass = 55,
|
||||
ShieldValue = 56,
|
||||
ShieldEncumbrance = 57,
|
||||
MissileInventoryLocation = 58,
|
||||
FullDamageType = 59,
|
||||
WeaponRange = 60,
|
||||
AttackersSkill = 61,
|
||||
DefendersSkill = 62,
|
||||
AttackersSkillValue = 63,
|
||||
AttackersClass = 64,
|
||||
[ServerOnly]
|
||||
Placement = 65,
|
||||
CheckpointStatus = 66,
|
||||
Tolerance = 67,
|
||||
TargetingTactic = 68,
|
||||
CombatTactic = 69,
|
||||
HomesickTargetingTactic = 70,
|
||||
NumFollowFailures = 71,
|
||||
FriendType = 72,
|
||||
FoeType = 73,
|
||||
MerchandiseItemTypes = 74,
|
||||
MerchandiseMinValue = 75,
|
||||
MerchandiseMaxValue = 76,
|
||||
NumItemsSold = 77,
|
||||
NumItemsBought = 78,
|
||||
MoneyIncome = 79,
|
||||
MoneyOutflow = 80,
|
||||
[Ephemeral]
|
||||
MaxGeneratedObjects = 81,
|
||||
[Ephemeral]
|
||||
InitGeneratedObjects = 82,
|
||||
ActivationResponse = 83,
|
||||
OriginalValue = 84,
|
||||
NumMoveFailures = 85,
|
||||
MinLevel = 86,
|
||||
MaxLevel = 87,
|
||||
LockpickMod = 88,
|
||||
BoosterEnum = 89,
|
||||
BoostValue = 90,
|
||||
MaxStructure = 91,
|
||||
Structure = 92,
|
||||
[ServerOnly]
|
||||
PhysicsState = 93,
|
||||
[ServerOnly]
|
||||
TargetType = 94,
|
||||
RadarBlipColor = 95,
|
||||
EncumbranceCapacity = 96,
|
||||
LoginTimestamp = 97,
|
||||
[SendOnLogin]
|
||||
CreationTimestamp = 98,
|
||||
PkLevelModifier = 99,
|
||||
GeneratorType = 100,
|
||||
AiAllowedCombatStyle = 101,
|
||||
LogoffTimestamp = 102,
|
||||
GeneratorDestructionType = 103,
|
||||
ActivationCreateClass = 104,
|
||||
ItemWorkmanship = 105,
|
||||
ItemSpellcraft = 106,
|
||||
ItemCurMana = 107,
|
||||
ItemMaxMana = 108,
|
||||
ItemDifficulty = 109,
|
||||
ItemAllegianceRankLimit = 110,
|
||||
PortalBitmask = 111,
|
||||
AdvocateLevel = 112,
|
||||
[SendOnLogin]
|
||||
Gender = 113,
|
||||
Attuned = 114,
|
||||
ItemSkillLevelLimit = 115,
|
||||
GateLogic = 116,
|
||||
ItemManaCost = 117,
|
||||
Logoff = 118,
|
||||
Active = 119,
|
||||
AttackHeight = 120,
|
||||
NumAttackFailures = 121,
|
||||
AiCpThreshold = 122,
|
||||
AiAdvancementStrategy = 123,
|
||||
Version = 124,
|
||||
[SendOnLogin]
|
||||
Age = 125,
|
||||
VendorHappyMean = 126,
|
||||
VendorHappyVariance = 127,
|
||||
CloakStatus = 128,
|
||||
[SendOnLogin]
|
||||
VitaeCpPool = 129,
|
||||
NumServicesSold = 130,
|
||||
MaterialType = 131,
|
||||
[SendOnLogin]
|
||||
NumAllegianceBreaks = 132,
|
||||
[Ephemeral]
|
||||
ShowableOnRadar = 133,
|
||||
[SendOnLogin]
|
||||
PlayerKillerStatus = 134,
|
||||
VendorHappyMaxItems = 135,
|
||||
ScorePageNum = 136,
|
||||
ScoreConfigNum = 137,
|
||||
ScoreNumScores = 138,
|
||||
[SendOnLogin]
|
||||
DeathLevel = 139,
|
||||
AiOptions = 140,
|
||||
OpenToEveryone = 141,
|
||||
GeneratorTimeType = 142,
|
||||
GeneratorStartTime = 143,
|
||||
GeneratorEndTime = 144,
|
||||
GeneratorEndDestructionType = 145,
|
||||
XpOverride = 146,
|
||||
NumCrashAndTurns = 147,
|
||||
ComponentWarningThreshold = 148,
|
||||
HouseStatus = 149,
|
||||
[ServerOnly]
|
||||
HookPlacement = 150,
|
||||
[ServerOnly]
|
||||
HookType = 151,
|
||||
[ServerOnly]
|
||||
HookItemType = 152,
|
||||
AiPpThreshold = 153,
|
||||
GeneratorVersion = 154,
|
||||
HouseType = 155,
|
||||
PickupEmoteOffset = 156,
|
||||
WeenieIteration = 157,
|
||||
WieldRequirements = 158,
|
||||
WieldSkillType = 159,
|
||||
WieldDifficulty = 160,
|
||||
HouseMaxHooksUsable = 161,
|
||||
HouseCurrentHooksUsable = 162,
|
||||
AllegianceMinLevel = 163,
|
||||
AllegianceMaxLevel = 164,
|
||||
HouseRelinkHookCount = 165,
|
||||
SlayerCreatureType = 166,
|
||||
ConfirmationInProgress = 167,
|
||||
ConfirmationTypeInProgress = 168,
|
||||
TsysMutationData = 169,
|
||||
NumItemsInMaterial = 170,
|
||||
NumTimesTinkered = 171,
|
||||
AppraisalLongDescDecoration = 172,
|
||||
AppraisalLockpickSuccessPercent = 173,
|
||||
[Ephemeral]
|
||||
AppraisalPages = 174,
|
||||
[Ephemeral]
|
||||
AppraisalMaxPages = 175,
|
||||
AppraisalItemSkill = 176,
|
||||
GemCount = 177,
|
||||
GemType = 178,
|
||||
ImbuedEffect = 179,
|
||||
AttackersRawSkillValue = 180,
|
||||
[SendOnLogin]
|
||||
ChessRank = 181,
|
||||
ChessTotalGames = 182,
|
||||
ChessGamesWon = 183,
|
||||
ChessGamesLost = 184,
|
||||
TypeOfAlteration = 185,
|
||||
SkillToBeAltered = 186,
|
||||
SkillAlterationCount = 187,
|
||||
[SendOnLogin]
|
||||
HeritageGroup = 188,
|
||||
TransferFromAttribute = 189,
|
||||
TransferToAttribute = 190,
|
||||
AttributeTransferCount = 191,
|
||||
[SendOnLogin]
|
||||
FakeFishingSkill = 192,
|
||||
NumKeys = 193,
|
||||
DeathTimestamp = 194,
|
||||
PkTimestamp = 195,
|
||||
VictimTimestamp = 196,
|
||||
HookGroup = 197,
|
||||
AllegianceSwearTimestamp = 198,
|
||||
[SendOnLogin]
|
||||
HousePurchaseTimestamp = 199,
|
||||
RedirectableEquippedArmorCount = 200,
|
||||
MeleeDefenseImbuedEffectTypeCache = 201,
|
||||
MissileDefenseImbuedEffectTypeCache = 202,
|
||||
MagicDefenseImbuedEffectTypeCache = 203,
|
||||
ElementalDamageBonus = 204,
|
||||
ImbueAttempts = 205,
|
||||
ImbueSuccesses = 206,
|
||||
CreatureKills = 207,
|
||||
PlayerKillsPk = 208,
|
||||
PlayerKillsPkl = 209,
|
||||
RaresTierOne = 210,
|
||||
RaresTierTwo = 211,
|
||||
RaresTierThree = 212,
|
||||
RaresTierFour = 213,
|
||||
RaresTierFive = 214,
|
||||
[SendOnLogin]
|
||||
AugmentationStat = 215,
|
||||
[SendOnLogin]
|
||||
AugmentationFamilyStat = 216,
|
||||
[SendOnLogin]
|
||||
AugmentationInnateFamily = 217,
|
||||
[SendOnLogin]
|
||||
AugmentationInnateStrength = 218,
|
||||
[SendOnLogin]
|
||||
AugmentationInnateEndurance = 219,
|
||||
[SendOnLogin]
|
||||
AugmentationInnateCoordination = 220,
|
||||
[SendOnLogin]
|
||||
AugmentationInnateQuickness = 221,
|
||||
[SendOnLogin]
|
||||
AugmentationInnateFocus = 222,
|
||||
[SendOnLogin]
|
||||
AugmentationInnateSelf = 223,
|
||||
[SendOnLogin]
|
||||
AugmentationSpecializeSalvaging = 224,
|
||||
[SendOnLogin]
|
||||
AugmentationSpecializeItemTinkering = 225,
|
||||
[SendOnLogin]
|
||||
AugmentationSpecializeArmorTinkering = 226,
|
||||
[SendOnLogin]
|
||||
AugmentationSpecializeMagicItemTinkering = 227,
|
||||
[SendOnLogin]
|
||||
AugmentationSpecializeWeaponTinkering = 228,
|
||||
[SendOnLogin]
|
||||
AugmentationExtraPackSlot = 229,
|
||||
[SendOnLogin]
|
||||
AugmentationIncreasedCarryingCapacity = 230,
|
||||
[SendOnLogin]
|
||||
AugmentationLessDeathItemLoss = 231,
|
||||
[SendOnLogin]
|
||||
AugmentationSpellsRemainPastDeath = 232,
|
||||
[SendOnLogin]
|
||||
AugmentationCriticalDefense = 233,
|
||||
[SendOnLogin]
|
||||
AugmentationBonusXp = 234,
|
||||
[SendOnLogin]
|
||||
AugmentationBonusSalvage = 235,
|
||||
[SendOnLogin]
|
||||
AugmentationBonusImbueChance = 236,
|
||||
[SendOnLogin]
|
||||
AugmentationFasterRegen = 237,
|
||||
[SendOnLogin]
|
||||
AugmentationIncreasedSpellDuration = 238,
|
||||
[SendOnLogin]
|
||||
AugmentationResistanceFamily = 239,
|
||||
[SendOnLogin]
|
||||
AugmentationResistanceSlash = 240,
|
||||
[SendOnLogin]
|
||||
AugmentationResistancePierce = 241,
|
||||
[SendOnLogin]
|
||||
AugmentationResistanceBlunt = 242,
|
||||
[SendOnLogin]
|
||||
AugmentationResistanceAcid = 243,
|
||||
[SendOnLogin]
|
||||
AugmentationResistanceFire = 244,
|
||||
[SendOnLogin]
|
||||
AugmentationResistanceFrost = 245,
|
||||
[SendOnLogin]
|
||||
AugmentationResistanceLightning = 246,
|
||||
RaresTierOneLogin = 247,
|
||||
RaresTierTwoLogin = 248,
|
||||
RaresTierThreeLogin = 249,
|
||||
RaresTierFourLogin = 250,
|
||||
RaresTierFiveLogin = 251,
|
||||
RaresLoginTimestamp = 252,
|
||||
RaresTierSix = 253,
|
||||
RaresTierSeven = 254,
|
||||
RaresTierSixLogin = 255,
|
||||
RaresTierSevenLogin = 256,
|
||||
ItemAttributeLimit = 257,
|
||||
ItemAttributeLevelLimit = 258,
|
||||
ItemAttribute2ndLimit = 259,
|
||||
ItemAttribute2ndLevelLimit = 260,
|
||||
CharacterTitleId = 261,
|
||||
NumCharacterTitles = 262,
|
||||
ResistanceModifierType = 263,
|
||||
FreeTinkersBitfield = 264,
|
||||
EquipmentSetId = 265,
|
||||
PetClass = 266,
|
||||
Lifespan = 267,
|
||||
[Ephemeral]
|
||||
RemainingLifespan = 268,
|
||||
UseCreateQuantity = 269,
|
||||
WieldRequirements2 = 270,
|
||||
WieldSkillType2 = 271,
|
||||
WieldDifficulty2 = 272,
|
||||
WieldRequirements3 = 273,
|
||||
WieldSkillType3 = 274,
|
||||
WieldDifficulty3 = 275,
|
||||
WieldRequirements4 = 276,
|
||||
WieldSkillType4 = 277,
|
||||
WieldDifficulty4 = 278,
|
||||
Unique = 279,
|
||||
SharedCooldown = 280,
|
||||
Faction1Bits = 281,
|
||||
Faction2Bits = 282,
|
||||
Faction3Bits = 283,
|
||||
Hatred1Bits = 284,
|
||||
Hatred2Bits = 285,
|
||||
Hatred3Bits = 286,
|
||||
SocietyRankCelhan = 287,
|
||||
SocietyRankEldweb = 288,
|
||||
SocietyRankRadblo = 289,
|
||||
HearLocalSignals = 290,
|
||||
HearLocalSignalsRadius = 291,
|
||||
Cleaving = 292,
|
||||
[SendOnLogin]
|
||||
AugmentationSpecializeGearcraft = 293,
|
||||
[SendOnLogin]
|
||||
AugmentationInfusedCreatureMagic = 294,
|
||||
[SendOnLogin]
|
||||
AugmentationInfusedItemMagic = 295,
|
||||
[SendOnLogin]
|
||||
AugmentationInfusedLifeMagic = 296,
|
||||
[SendOnLogin]
|
||||
AugmentationInfusedWarMagic = 297,
|
||||
[SendOnLogin]
|
||||
AugmentationCriticalExpertise = 298,
|
||||
[SendOnLogin]
|
||||
AugmentationCriticalPower = 299,
|
||||
[SendOnLogin]
|
||||
AugmentationSkilledMelee = 300,
|
||||
[SendOnLogin]
|
||||
AugmentationSkilledMissile = 301,
|
||||
[SendOnLogin]
|
||||
AugmentationSkilledMagic = 302,
|
||||
ImbuedEffect2 = 303,
|
||||
ImbuedEffect3 = 304,
|
||||
ImbuedEffect4 = 305,
|
||||
ImbuedEffect5 = 306,
|
||||
[SendOnLogin]
|
||||
DamageRating = 307,
|
||||
[SendOnLogin]
|
||||
DamageResistRating = 308,
|
||||
[SendOnLogin]
|
||||
AugmentationDamageBonus = 309,
|
||||
[SendOnLogin]
|
||||
AugmentationDamageReduction = 310,
|
||||
ImbueStackingBits = 311,
|
||||
[SendOnLogin]
|
||||
HealOverTime = 312,
|
||||
[SendOnLogin]
|
||||
CritRating = 313,
|
||||
[SendOnLogin]
|
||||
CritDamageRating = 314,
|
||||
[SendOnLogin]
|
||||
CritResistRating = 315,
|
||||
[SendOnLogin]
|
||||
CritDamageResistRating = 316,
|
||||
[SendOnLogin]
|
||||
HealingResistRating = 317,
|
||||
[SendOnLogin]
|
||||
DamageOverTime = 318,
|
||||
ItemMaxLevel = 319,
|
||||
ItemXpStyle = 320,
|
||||
EquipmentSetExtra = 321,
|
||||
[SendOnLogin]
|
||||
AetheriaBitfield = 322,
|
||||
[SendOnLogin]
|
||||
HealingBoostRating = 323,
|
||||
HeritageSpecificArmor = 324,
|
||||
AlternateRacialSkills = 325,
|
||||
[SendOnLogin]
|
||||
AugmentationJackOfAllTrades = 326,
|
||||
[SendOnLogin]
|
||||
AugmentationResistanceNether = 327,
|
||||
[SendOnLogin]
|
||||
AugmentationInfusedVoidMagic = 328,
|
||||
[SendOnLogin]
|
||||
WeaknessRating = 329,
|
||||
[SendOnLogin]
|
||||
NetherOverTime = 330,
|
||||
[SendOnLogin]
|
||||
NetherResistRating = 331,
|
||||
LuminanceAward = 332,
|
||||
[SendOnLogin]
|
||||
LumAugDamageRating = 333,
|
||||
[SendOnLogin]
|
||||
LumAugDamageReductionRating = 334,
|
||||
[SendOnLogin]
|
||||
LumAugCritDamageRating = 335,
|
||||
[SendOnLogin]
|
||||
LumAugCritReductionRating = 336,
|
||||
[SendOnLogin]
|
||||
LumAugSurgeEffectRating = 337,
|
||||
[SendOnLogin]
|
||||
LumAugSurgeChanceRating = 338,
|
||||
[SendOnLogin]
|
||||
LumAugItemManaUsage = 339,
|
||||
[SendOnLogin]
|
||||
LumAugItemManaGain = 340,
|
||||
[SendOnLogin]
|
||||
LumAugVitality = 341,
|
||||
[SendOnLogin]
|
||||
LumAugHealingRating = 342,
|
||||
[SendOnLogin]
|
||||
LumAugSkilledCraft = 343,
|
||||
[SendOnLogin]
|
||||
LumAugSkilledSpec = 344,
|
||||
[SendOnLogin]
|
||||
LumAugNoDestroyCraft = 345,
|
||||
RestrictInteraction = 346,
|
||||
OlthoiLootTimestamp = 347,
|
||||
OlthoiLootStep = 348,
|
||||
UseCreatesContractId = 349,
|
||||
[SendOnLogin]
|
||||
DotResistRating = 350,
|
||||
[SendOnLogin]
|
||||
LifeResistRating = 351,
|
||||
CloakWeaveProc = 352,
|
||||
WeaponType = 353,
|
||||
[SendOnLogin]
|
||||
MeleeMastery = 354,
|
||||
[SendOnLogin]
|
||||
RangedMastery = 355,
|
||||
SneakAttackRating = 356,
|
||||
RecklessnessRating = 357,
|
||||
DeceptionRating = 358,
|
||||
CombatPetRange = 359,
|
||||
[SendOnLogin]
|
||||
WeaponAuraDamage = 360,
|
||||
[SendOnLogin]
|
||||
WeaponAuraSpeed = 361,
|
||||
[SendOnLogin]
|
||||
SummoningMastery = 362,
|
||||
HeartbeatLifespan = 363,
|
||||
UseLevelRequirement = 364,
|
||||
[SendOnLogin]
|
||||
LumAugAllSkills = 365,
|
||||
UseRequiresSkill = 366,
|
||||
UseRequiresSkillLevel = 367,
|
||||
UseRequiresSkillSpec = 368,
|
||||
UseRequiresLevel = 369,
|
||||
[SendOnLogin]
|
||||
GearDamage = 370,
|
||||
[SendOnLogin]
|
||||
GearDamageResist = 371,
|
||||
[SendOnLogin]
|
||||
GearCrit = 372,
|
||||
[SendOnLogin]
|
||||
GearCritResist = 373,
|
||||
[SendOnLogin]
|
||||
GearCritDamage = 374,
|
||||
[SendOnLogin]
|
||||
GearCritDamageResist = 375,
|
||||
[SendOnLogin]
|
||||
GearHealingBoost = 376,
|
||||
[SendOnLogin]
|
||||
GearNetherResist = 377,
|
||||
[SendOnLogin]
|
||||
GearLifeResist = 378,
|
||||
[SendOnLogin]
|
||||
GearMaxHealth = 379,
|
||||
Unknown380 = 380,
|
||||
[SendOnLogin]
|
||||
PKDamageRating = 381,
|
||||
[SendOnLogin]
|
||||
PKDamageResistRating = 382,
|
||||
[SendOnLogin]
|
||||
GearPKDamageRating = 383,
|
||||
[SendOnLogin]
|
||||
GearPKDamageResistRating = 384,
|
||||
Unknown385 = 385,
|
||||
/// <summary>
|
||||
/// Overpower chance % for endgame creatures.
|
||||
/// </summary>
|
||||
[SendOnLogin]
|
||||
Overpower = 386,
|
||||
[SendOnLogin]
|
||||
OverpowerResist = 387,
|
||||
// Client does not display accurately
|
||||
[SendOnLogin]
|
||||
GearOverpower = 388,
|
||||
// Client does not display accurately
|
||||
[SendOnLogin]
|
||||
GearOverpowerResist = 389,
|
||||
// Number of times a character has enlightened
|
||||
[SendOnLogin]
|
||||
Enlightenment = 390,
|
||||
|
||||
|
||||
// ACE Specific
|
||||
[ServerOnly]
|
||||
PCAPRecordedAutonomousMovement = 8007,
|
||||
[ServerOnly]
|
||||
PCAPRecordedMaxVelocityEstimated = 8030,
|
||||
[ServerOnly]
|
||||
PCAPRecordedPlacement = 8041,
|
||||
[ServerOnly]
|
||||
PCAPRecordedAppraisalPages = 8042,
|
||||
[ServerOnly]
|
||||
PCAPRecordedAppraisalMaxPages = 8043,
|
||||
|
||||
//[ServerOnly]
|
||||
//TotalLogins = 9001,
|
||||
//[ServerOnly]
|
||||
//DeletionTimestamp = 9002,
|
||||
//[ServerOnly]
|
||||
//CharacterOptions1 = 9003,
|
||||
//[ServerOnly]
|
||||
//CharacterOptions2 = 9004,
|
||||
//[ServerOnly]
|
||||
//LootTier = 9005,
|
||||
//[ServerOnly]
|
||||
//GeneratorProbability = 9006,
|
||||
//[ServerOnly]
|
||||
//WeenieType = 9007 // I don't think this property type is needed anymore. We don't store the weenie type in the property bags, we store it as a separate field in the base objects.
|
||||
[ServerOnly]
|
||||
CurrentLoyaltyAtLastLogoff = 9008,
|
||||
[ServerOnly]
|
||||
CurrentLeadershipAtLastLogoff = 9009,
|
||||
[ServerOnly]
|
||||
AllegianceOfficerRank = 9010,
|
||||
[ServerOnly]
|
||||
HouseRentTimestamp = 9011,
|
||||
/// <summary>
|
||||
/// Stores the player's selected hairstyle at creation or after a barber use. This is used only for Gear Knights and Olthoi characters who have more than a single part/texture for a "hairstyle" (BodyStyle)
|
||||
/// </summary>
|
||||
[ServerOnly]
|
||||
Hairstyle = 9012,
|
||||
/// <summary>
|
||||
/// Used to store the calculated Clothing Priority for use with armor reduced items and items like Over-Robes.
|
||||
/// </summary>
|
||||
[Ephemeral][ServerOnly]
|
||||
VisualClothingPriority = 9013,
|
||||
[ServerOnly]
|
||||
SquelchGlobal = 9014,
|
||||
|
||||
/// <summary>
|
||||
/// TODO: This is a place holder for future use. See PlacementPosition
|
||||
/// This is the sort order for items in a container
|
||||
/// </summary>
|
||||
[ServerOnly]
|
||||
InventoryOrder = 9015,
|
||||
|
||||
// Decal Specific
|
||||
WeenieClassId_Decal = 218103808,
|
||||
Icon_Decal_DID = 218103809,
|
||||
Container_Decal_IID = 218103810,
|
||||
Landblock_Decal = 218103811,
|
||||
ItemSlots_Decal = 218103812,
|
||||
PackSlots_Decal = 218103813,
|
||||
StackCount_Decal = 218103814,
|
||||
StackMax_Decal = 218103815,
|
||||
Spell_Decal_DID = 218103816,
|
||||
SlotLegacy_Decal = 218103817,
|
||||
Wielder_Decal_IID = 218103818,
|
||||
WieldingSlot_Decal = 218103819,
|
||||
Monarch_Decal_IID = 218103820,
|
||||
Coverage_Decal = 218103821,
|
||||
EquipableSlots_Decal = 218103822,
|
||||
EquipType_Decal = 218103823,
|
||||
IconOutline_Decal = 218103824,
|
||||
MissileType_Decal = 218103825,
|
||||
UsageMask_Decal = 218103826,
|
||||
HouseOwner_Decal_IID = 218103827,
|
||||
HookMask_Decal = 218103828,
|
||||
HookType_Decal = 218103829,
|
||||
Setup_Decal_DID = 218103830,
|
||||
ObjectDescriptionFlags_Decal = 218103831,
|
||||
CreateFlags1_Decal = 218103832,
|
||||
CreateFlags2_Decal = 218103833,
|
||||
Category_Decal = 218103834,
|
||||
Behavior_Decal = 218103835,
|
||||
MagicDef_Decal = 218103836,
|
||||
SpecialProps_Decal = 218103837,
|
||||
SpellCount_Decal = 218103838,
|
||||
WeapSpeed_Decal = 218103839,
|
||||
EquipSkill_Decal = 218103840,
|
||||
DamageType_Decal = 218103841,
|
||||
MaxDamage_Decal = 218103842,
|
||||
Unknown10_Decal = 218103843, // CurrentWieldLocation?
|
||||
Unknown100000_Decal = 218103844, // RadarBlipColor ???
|
||||
Unknown800000_Decal = 218103845,
|
||||
Unknown8000000_Decal = 218103846,
|
||||
PhysicsDataFlags_Decal = 218103847,
|
||||
ActiveSpellCount_Decal = 218103848,
|
||||
IconOverlay_Decal_DID = 218103849,
|
||||
IconUnderlay_Decal_DID = 218103850,
|
||||
Slot_Decal = 231735296,
|
||||
}
|
||||
|
||||
public static class IntValueKeyTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a decal specific IntValueKey to the actual IntValueKey.
|
||||
/// If this is not an IntValueKey, 0 will be returned.
|
||||
/// </summary>
|
||||
public static uint ConvertToInt(IntValueKey input)
|
||||
{
|
||||
if (input == IntValueKey.Category_Decal) return (int)IntValueKey.ItemType;
|
||||
if (input == IntValueKey.Coverage_Decal) return (int)IntValueKey.ClothingPriority;
|
||||
if (input == IntValueKey.ItemSlots_Decal) return (int)IntValueKey.ItemsCapacity;
|
||||
if (input == IntValueKey.PackSlots_Decal) return (int)IntValueKey.ContainersCapacity;
|
||||
if (input == IntValueKey.EquipableSlots_Decal) return (int)IntValueKey.ValidLocations;
|
||||
//if (input == IntValueKey.WieldingSlot_Decal) return (int)IntValueKey.CurrentWieldedLocation;
|
||||
if (input == IntValueKey.StackMax_Decal) return (int)IntValueKey.MaxStackSize;
|
||||
if (input == IntValueKey.StackCount_Decal) return (int)IntValueKey.StackSize;
|
||||
if (input == IntValueKey.IconOutline_Decal) return (int)IntValueKey.UiEffects;
|
||||
if (input == IntValueKey.MaxDamage_Decal) return (int)IntValueKey.Damage;
|
||||
if (input == IntValueKey.DamageType_Decal) return (int)IntValueKey.DamageType;
|
||||
if (input == IntValueKey.EquipSkill_Decal) return (int)IntValueKey.WeaponSkill;
|
||||
if (input == IntValueKey.WeapSpeed_Decal) return (int)IntValueKey.WeaponTime;
|
||||
if (input == IntValueKey.MissileType_Decal) return (int)IntValueKey.AmmoType;
|
||||
if (input == IntValueKey.EquipType_Decal) return (int)IntValueKey.CombatUse;
|
||||
if (input == IntValueKey.UsageMask_Decal) return (int)IntValueKey.TargetType;
|
||||
if (input == IntValueKey.HookMask_Decal) return (int)IntValueKey.HookType;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If input is not a IID, 0 will be returned
|
||||
/// </summary>
|
||||
public static uint ConvertToIID(IntValueKey input)
|
||||
{
|
||||
if (input == IntValueKey.Container_Decal_IID) return 2; // CONTAINER_IID
|
||||
if (input == IntValueKey.Wielder_Decal_IID) return 3; // WIELDER_IID
|
||||
if (input == IntValueKey.Monarch_Decal_IID) return 26; // MONARCH_IID
|
||||
if (input == IntValueKey.HouseOwner_Decal_IID) return 32; // HOUSE_OWNER_IID
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If input is not a DID, 0 will be returned
|
||||
/// </summary>
|
||||
public static uint ConvertToDID(IntValueKey input)
|
||||
{
|
||||
if (input == IntValueKey.Setup_Decal_DID) return 1; // SETUP_DID
|
||||
if (input == IntValueKey.Icon_Decal_DID) return 8; // ICON_DID
|
||||
if (input == IntValueKey.Spell_Decal_DID) return 28; // SPELL_DID
|
||||
if (input == IntValueKey.IconOverlay_Decal_DID) return 50; // ICON_OVERLAY_DID
|
||||
if (input == IntValueKey.IconUnderlay_Decal_DID) return 52; // ICON_UNDERLAY_DID
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Shared/Constants/ItemType.cs
Normal file
54
Shared/Constants/ItemType.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
[Flags]
|
||||
public enum ItemType : uint
|
||||
{
|
||||
None = 0x00000000,
|
||||
MeleeWeapon = 0x00000001,
|
||||
Armor = 0x00000002,
|
||||
Clothing = 0x00000004,
|
||||
Jewelry = 0x00000008,
|
||||
Creature = 0x00000010,
|
||||
Food = 0x00000020,
|
||||
Money = 0x00000040,
|
||||
Misc = 0x00000080,
|
||||
MissileWeapon = 0x00000100,
|
||||
Container = 0x00000200,
|
||||
Useless = 0x00000400,
|
||||
Gem = 0x00000800,
|
||||
SpellComponents = 0x00001000,
|
||||
Writable = 0x00002000,
|
||||
Key = 0x00004000,
|
||||
Caster = 0x00008000,
|
||||
Portal = 0x00010000,
|
||||
Lockable = 0x00020000,
|
||||
PromissoryNote = 0x00040000,
|
||||
ManaStone = 0x00080000,
|
||||
Service = 0x00100000,
|
||||
MagicWieldable = 0x00200000,
|
||||
CraftCookingBase = 0x00400000,
|
||||
CraftAlchemyBase = 0x00800000,
|
||||
CraftFletchingBase = 0x02000000,
|
||||
CraftAlchemyIntermediate = 0x04000000,
|
||||
CraftFletchingIntermediate = 0x08000000,
|
||||
LifeStone = 0x10000000,
|
||||
TinkeringTool = 0x20000000,
|
||||
TinkeringMaterial = 0x40000000,
|
||||
Gameboard = 0x80000000,
|
||||
|
||||
PortalMagicTarget = Portal | LifeStone,
|
||||
LockableMagicTarget = Misc | Container,
|
||||
Vestements = Armor | Clothing,
|
||||
Weapon = MeleeWeapon | MissileWeapon,
|
||||
WeaponOrCaster = MeleeWeapon | MissileWeapon | Caster,
|
||||
Item = MeleeWeapon | Armor | Clothing | Jewelry | Food | Money | Misc | MissileWeapon | Container |
|
||||
Gem | SpellComponents | Writable | Key | Caster | Portal | PromissoryNote | ManaStone | MagicWieldable,
|
||||
RedirectableItemEnchantmentTarget = MeleeWeapon | Armor | Clothing | MissileWeapon | Caster,
|
||||
ItemEnchantableTarget = MeleeWeapon | Armor | Clothing | Jewelry | Misc | MissileWeapon | Container | Gem | Caster | ManaStone,
|
||||
VendorShopKeep = MeleeWeapon | Armor | Clothing | Food | Misc | MissileWeapon | Container | Useless | Writable | Key |
|
||||
PromissoryNote | CraftFletchingIntermediate | TinkeringMaterial,
|
||||
VendorGrocer = Food | Container | Writable | Key | PromissoryNote | CraftCookingBase
|
||||
}
|
||||
}
|
||||
80
Shared/Constants/MaterialType.cs
Normal file
80
Shared/Constants/MaterialType.cs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
public enum MaterialType : uint
|
||||
{
|
||||
Unknown = 0x00000000,
|
||||
Ceramic = 0x00000001,
|
||||
Porcelain = 0x00000002,
|
||||
Linen = 0x00000004,
|
||||
Satin = 0x00000005,
|
||||
Silk = 0x00000006,
|
||||
Velvet = 0x00000007,
|
||||
Wool = 0x00000008,
|
||||
Agate = 0x0000000A,
|
||||
Amber = 0x0000000B,
|
||||
Amethyst = 0x0000000C,
|
||||
Aquamarine = 0x0000000D,
|
||||
Azurite = 0x0000000E,
|
||||
BlackGarnet = 0x0000000F,
|
||||
BlackOpal = 0x00000010,
|
||||
Bloodstone = 0x00000011,
|
||||
Carnelian = 0x00000012,
|
||||
Citrine = 0x00000013,
|
||||
Diamond = 0x00000014,
|
||||
Emerald = 0x00000015,
|
||||
FireOpal = 0x00000016,
|
||||
GreenGarnet = 0x00000017,
|
||||
GreenJade = 0x00000018,
|
||||
Hematite = 0x00000019,
|
||||
ImperialTopaz = 0x0000001A,
|
||||
Jet = 0x0000001B,
|
||||
LapisLazuli = 0x0000001C,
|
||||
LavenderJade = 0x0000001D,
|
||||
Malachite = 0x0000001E,
|
||||
Moonstone = 0x0000001F,
|
||||
Onyx = 0x00000020,
|
||||
Opal = 0x00000021,
|
||||
Peridot = 0x00000022,
|
||||
RedGarnet = 0x00000023,
|
||||
RedJade = 0x00000024,
|
||||
RoseQuartz = 0x00000025,
|
||||
Ruby = 0x00000026,
|
||||
Sapphire = 0x00000027,
|
||||
SmokeyQuartz = 0x00000028,
|
||||
Sunstone = 0x00000029,
|
||||
TigerEye = 0x0000002A,
|
||||
Tourmaline = 0x0000002B,
|
||||
Turquoise = 0x0000002C,
|
||||
WhiteJade = 0x0000002D,
|
||||
WhiteQuartz = 0x0000002E,
|
||||
WhiteSapphire = 0x0000002F,
|
||||
YellowGarnet = 0x00000030,
|
||||
YellowTopaz = 0x00000031,
|
||||
Zircon = 0x00000032,
|
||||
Ivory = 0x00000033,
|
||||
Leather = 0x00000034,
|
||||
ArmoredilloHide = 0x00000035,
|
||||
GromnieHide = 0x00000036,
|
||||
ReedSharkHide = 0x00000037,
|
||||
Brass = 0x00000039,
|
||||
Bronze = 0x0000003A,
|
||||
Copper = 0x0000003B,
|
||||
Gold = 0x0000003C,
|
||||
Iron = 0x0000003D,
|
||||
Pyreal = 0x0000003E,
|
||||
Silver = 0x0000003F,
|
||||
Steel = 0x00000040,
|
||||
Alabaster = 0x00000042,
|
||||
Granite = 0x00000043,
|
||||
Marble = 0x00000044,
|
||||
Obsidian = 0x00000045,
|
||||
Sandstone = 0x00000046,
|
||||
Serpentine = 0x00000047,
|
||||
Ebony = 0x00000049,
|
||||
Mahogany = 0x0000004A,
|
||||
Oak = 0x0000004B,
|
||||
Pine = 0x0000004C,
|
||||
Teak = 0x0000004D,
|
||||
}
|
||||
}
|
||||
33
Shared/Constants/QuadValueKey.cs
Normal file
33
Shared/Constants/QuadValueKey.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
// https://github.com/ACEmulator/ACE/blob/master/Source/ACE.Entity/Enum/Properties/PropertyInt64.cs
|
||||
public enum QuadValueKey
|
||||
{
|
||||
Undef = 0,
|
||||
[SendOnLogin]
|
||||
TotalExperience = 1,
|
||||
[SendOnLogin]
|
||||
AvailableExperience = 2,
|
||||
AugmentationCost = 3,
|
||||
ItemTotalXp = 4,
|
||||
ItemBaseXp = 5,
|
||||
[SendOnLogin]
|
||||
AvailableLuminance = 6,
|
||||
[SendOnLogin]
|
||||
MaximumLuminance = 7,
|
||||
InteractionReqs = 8,
|
||||
|
||||
|
||||
// ACE Specific
|
||||
/* custom */
|
||||
[ServerOnly]
|
||||
AllegianceXPCached = 9000,
|
||||
[ServerOnly]
|
||||
AllegianceXPGenerated = 9001,
|
||||
[ServerOnly]
|
||||
AllegianceXPReceived = 9002,
|
||||
[ServerOnly]
|
||||
VerifyXp = 9003
|
||||
}
|
||||
}
|
||||
11
Shared/Constants/SendOnLoginAttribute.cs
Normal file
11
Shared/Constants/SendOnLoginAttribute.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// These are properties that aren't saved to the shard.
|
||||
/// </summary>
|
||||
public class SendOnLoginAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
11
Shared/Constants/ServerOnlyAttribute.cs
Normal file
11
Shared/Constants/ServerOnlyAttribute.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// These are properties that aren't saved to the shard.
|
||||
/// </summary>
|
||||
public class ServerOnlyAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
67
Shared/Constants/Skill.cs
Normal file
67
Shared/Constants/Skill.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// note: even though these are unnumbered, order is very important. values of "none" or commented
|
||||
/// as retired or unused --ABSOLUTELY CANNOT-- be removed. Skills that are none, retired, or not
|
||||
/// implemented have been removed from the SkillHelper.ValidSkills hashset below.
|
||||
/// </summary>
|
||||
public enum Skill
|
||||
{
|
||||
None,
|
||||
Axe, /* Retired */
|
||||
Bow, /* Retired */
|
||||
Crossbow, /* Retired */
|
||||
Dagger, /* Retired */
|
||||
Mace, /* Retired */
|
||||
MeleeDefense,
|
||||
MissileDefense,
|
||||
Sling, /* Retired */
|
||||
Spear, /* Retired */
|
||||
Staff, /* Retired */
|
||||
Sword, /* Retired */
|
||||
ThrownWeapon, /* Retired */
|
||||
UnarmedCombat, /* Retired */
|
||||
ArcaneLore,
|
||||
MagicDefense,
|
||||
ManaConversion,
|
||||
Spellcraft, /* Unimplemented */
|
||||
ItemTinkering,
|
||||
AssessPerson,
|
||||
Deception,
|
||||
Healing,
|
||||
Jump,
|
||||
Lockpick,
|
||||
Run,
|
||||
Awareness, /* Unimplemented */
|
||||
ArmsAndArmorRepair, /* Unimplemented */
|
||||
AssessCreature,
|
||||
WeaponTinkering,
|
||||
ArmorTinkering,
|
||||
MagicItemTinkering,
|
||||
CreatureEnchantment,
|
||||
ItemEnchantment,
|
||||
LifeMagic,
|
||||
WarMagic,
|
||||
Leadership,
|
||||
Loyalty,
|
||||
Fletching,
|
||||
Alchemy,
|
||||
Cooking,
|
||||
Salvaging,
|
||||
TwoHandedCombat,
|
||||
Gearcraft, /* Retired */
|
||||
VoidMagic,
|
||||
HeavyWeapons,
|
||||
LightWeapons,
|
||||
FinesseWeapons,
|
||||
MissileWeapons,
|
||||
Shield,
|
||||
DualWield,
|
||||
Recklessness,
|
||||
SneakAttack,
|
||||
DirtyFighting,
|
||||
Challenge, /* Unimplemented */
|
||||
Summoning
|
||||
}
|
||||
}
|
||||
707
Shared/Constants/SpellCategory.cs
Normal file
707
Shared/Constants/SpellCategory.cs
Normal file
|
|
@ -0,0 +1,707 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
public enum SpellCategory
|
||||
{
|
||||
Undefined,
|
||||
Strength_Raising,
|
||||
Strength_Lowering,
|
||||
Endurance_Raising,
|
||||
Endurance_Lowering,
|
||||
Quickness_Raising,
|
||||
Quickness_Lowering,
|
||||
Coordination_Raising,
|
||||
Coordination_Lowering,
|
||||
Focus_Raising,
|
||||
Focus_Lowering,
|
||||
Self_Raising,
|
||||
Self_Lowering,
|
||||
Focus_Concentration,
|
||||
Focus_Disruption,
|
||||
Focus_Brilliance,
|
||||
Focus_Dullness,
|
||||
Axe_Raising,
|
||||
Axe_Lowering,
|
||||
Bow_Raising,
|
||||
Bow_Lowering,
|
||||
Crossbow_Raising,
|
||||
Crossbow_Lowering,
|
||||
Dagger_Raising,
|
||||
Dagger_Lowering,
|
||||
Mace_Raising,
|
||||
Mace_Lowering,
|
||||
Spear_Raising,
|
||||
Spear_Lowering,
|
||||
Staff_Raising,
|
||||
Staff_Lowering,
|
||||
Sword_Raising,
|
||||
Sword_Lowering,
|
||||
Thrown_Weapons_Raising,
|
||||
Thrown_Weapons_Lowering,
|
||||
Unarmed_Combat_Raising,
|
||||
Unarmed_Combat_Lowering,
|
||||
Melee_Defense_Raising,
|
||||
Melee_Defense_Lowering,
|
||||
Missile_Defense_Raising,
|
||||
Missile_Defense_Lowering,
|
||||
Magic_Defense_Raising,
|
||||
Magic_Defense_Lowering,
|
||||
Creature_Enchantment_Raising,
|
||||
Creature_Enchantment_Lowering,
|
||||
Item_Enchantment_Raising,
|
||||
Item_Enchantment_Lowering,
|
||||
Life_Magic_Raising,
|
||||
Life_Magic_Lowering,
|
||||
War_Magic_Raising,
|
||||
War_Magic_Lowering,
|
||||
Mana_Conversion_Raising,
|
||||
Mana_Conversion_Lowering,
|
||||
Arcane_Lore_Raising,
|
||||
Arcane_Lore_Lowering,
|
||||
Appraise_Armor_Raising,
|
||||
Appraise_Armor_Lowering,
|
||||
Appraise_Item_Raising,
|
||||
Appraise_Item_Lowering,
|
||||
Appraise_Magic_Item_Raising,
|
||||
Appraise_Magic_Item_Lowering,
|
||||
Appraise_Weapon_Raising,
|
||||
Appraise_Weapon_Lowering,
|
||||
Assess_Monster_Raising,
|
||||
Assess_Monster_Lowering,
|
||||
Deception_Raising,
|
||||
Deception_Lowering,
|
||||
Healing_Raising,
|
||||
Healing_Lowering,
|
||||
Jump_Raising,
|
||||
Jump_Lowering,
|
||||
Leadership_Raising,
|
||||
Leadership_Lowering,
|
||||
Lockpick_Raising,
|
||||
Lockpick_Lowering,
|
||||
Loyalty_Raising,
|
||||
Loyalty_Lowering,
|
||||
Run_Raising,
|
||||
Run_Lowering,
|
||||
Health_Raising,
|
||||
Health_Lowering,
|
||||
Stamina_Raising,
|
||||
Stamina_Lowering,
|
||||
Mana_Raising,
|
||||
Mana_Lowering,
|
||||
Mana_Remedy,
|
||||
Mana_Malediction,
|
||||
Health_Transfer_to_caster,
|
||||
Health_Transfer_from_caster,
|
||||
Stamina_Transfer_to_caster,
|
||||
Stamina_Transfer_from_caster,
|
||||
Mana_Transfer_to_caster,
|
||||
Mana_Transfer_from_caster,
|
||||
Health_Accelerating,
|
||||
Health_Decelerating,
|
||||
Stamina_Accelerating,
|
||||
Stamina_Decelerating,
|
||||
Mana_Accelerating,
|
||||
Mana_Decelerating,
|
||||
Vitae_Raising,
|
||||
Vitae_Lowering,
|
||||
Acid_Protection,
|
||||
Acid_Vulnerability,
|
||||
Bludgeon_Protection,
|
||||
Bludgeon_Vulnerability,
|
||||
Cold_Protection,
|
||||
Cold_Vulnerability,
|
||||
Electric_Protection,
|
||||
Electric_Vulnerability,
|
||||
Fire_Protection,
|
||||
Fire_Vulnerability,
|
||||
Pierce_Protection,
|
||||
Pierce_Vulnerability,
|
||||
Slash_Protection,
|
||||
Slash_Vulnerability,
|
||||
Armor_Raising,
|
||||
Armor_Lowering,
|
||||
Acid_Missile,
|
||||
Bludgeoning_Missile,
|
||||
Cold_Missile,
|
||||
Electric_Missile,
|
||||
Fire_Missile,
|
||||
Piercing_Missile,
|
||||
Slashing_Missile,
|
||||
Acid_Seeker,
|
||||
Bludgeoning_Seeker,
|
||||
Cold_Seeker,
|
||||
Electric_Seeker,
|
||||
Fire_Seeker,
|
||||
Piercing_Seeker,
|
||||
Slashing_Seeker,
|
||||
Acid_Burst,
|
||||
Bludgeoning_Burst,
|
||||
Cold_Burst,
|
||||
Electric_Burst,
|
||||
Fire_Burst,
|
||||
Piercing_Burst,
|
||||
Slashing_Burst,
|
||||
Acid_Blast,
|
||||
Bludgeoning_Blast,
|
||||
Cold_Blast,
|
||||
Electric_Blast,
|
||||
Fire_Blast,
|
||||
Piercing_Blast,
|
||||
Slashing_Blast,
|
||||
Acid_Scatter,
|
||||
Bludgeoning_Scatter,
|
||||
Cold_Scatter,
|
||||
Electric_Scatter,
|
||||
Fire_Scatter,
|
||||
Piercing_Scatter,
|
||||
Slashing_Scatter,
|
||||
Attack_Mod_Raising,
|
||||
Attack_Mod_Lowering,
|
||||
Damage_Raising,
|
||||
Damage_Lowering,
|
||||
Defense_Mod_Raising,
|
||||
Defense_Mod_Lowering,
|
||||
Weapon_Time_Raising,
|
||||
Weapon_Time_Lowering,
|
||||
Armor_Value_Raising,
|
||||
Armor_Value_Lowering,
|
||||
Acid_Resistance_Raising,
|
||||
Acid_Resistance_Lowering,
|
||||
Bludgeon_Resistance_Raising,
|
||||
Bludgeon_Resistance_Lowering,
|
||||
Cold_Resistance_Raising,
|
||||
Cold_Resistance_Lowering,
|
||||
Electric_Resistance_Raising,
|
||||
Electric_Resistance_Lowering,
|
||||
Fire_Resistance_Raising,
|
||||
Fire_Resistance_Lowering,
|
||||
Pierce_Resistance_Raising,
|
||||
Pierce_Resistance_Lowering,
|
||||
Slash_Resistance_Raising,
|
||||
Slash_Resistance_Lowering,
|
||||
Bludgeoning_Resistance_Raising,
|
||||
Bludgeoning_Resistance_Lowering,
|
||||
Slashing_Resistance_Raising,
|
||||
Slashing_Resistance_Lowering,
|
||||
Piercing_Resistance_Raising,
|
||||
Piercing_Resistance_Lowering,
|
||||
Electrical_Resistance_Raising,
|
||||
Electrical_Resistance_Lowering,
|
||||
Frost_Resistance_Raising,
|
||||
Frost_Resistance_Lowering,
|
||||
Flame_Resistance_Raising,
|
||||
Flame_Resistance_Lowering,
|
||||
Acidic_Resistance_Raising,
|
||||
Acidic_Resistance_Lowering,
|
||||
Armor_Level_Raising,
|
||||
Armor_Level_Lowering,
|
||||
Lockpick_Resistance_Raising,
|
||||
Lockpick_Resistance_Lowering,
|
||||
Appraisal_Resistance_Raising,
|
||||
Appraisal_Resistance_Lowering,
|
||||
Vision_Raising,
|
||||
Vision_Lowering,
|
||||
Transparency_Raising,
|
||||
Transparency_Lowering,
|
||||
Portal_Tie,
|
||||
Portal_Recall,
|
||||
Portal_Creation,
|
||||
Portal_Item_Creation,
|
||||
Vitae,
|
||||
Assess_Person_Raising,
|
||||
Assess_Person_Lowering,
|
||||
Acid_Volley,
|
||||
Bludgeoning_Volley,
|
||||
Frost_Volley,
|
||||
Lightning_Volley,
|
||||
Flame_Volley,
|
||||
Force_Volley,
|
||||
Blade_Volley,
|
||||
Portal_Sending,
|
||||
Lifestone_Sending,
|
||||
Cooking_Raising,
|
||||
Cooking_Lowering,
|
||||
Fletching_Raising,
|
||||
Fletching_Lowering,
|
||||
Alchemy_Lowering,
|
||||
Alchemy_Raising,
|
||||
Acid_Ring,
|
||||
Bludgeoning_Ring,
|
||||
Cold_Ring,
|
||||
Electric_Ring,
|
||||
Fire_Ring,
|
||||
Piercing_Ring,
|
||||
Slashing_Ring,
|
||||
Acid_Wall,
|
||||
Bludgeoning_Wall,
|
||||
Cold_Wall,
|
||||
Electric_Wall,
|
||||
Fire_Wall,
|
||||
Piercing_Wall,
|
||||
Slashing_Wall,
|
||||
Acid_Strike,
|
||||
Bludgeoning_Strike,
|
||||
Cold_Strike,
|
||||
Electric_Strike,
|
||||
Fire_Strike,
|
||||
Piercing_Strike,
|
||||
Slashing_Strike,
|
||||
Acid_Streak,
|
||||
Bludgeoning_Streak,
|
||||
Cold_Streak,
|
||||
Electric_Streak,
|
||||
Fire_Streak,
|
||||
Piercing_Streak,
|
||||
Slashing_Streak,
|
||||
Dispel,
|
||||
Creature_Mystic_Raising,
|
||||
Creature_Mystic_Lowering,
|
||||
Item_Mystic_Raising,
|
||||
Item_Mystic_Lowering,
|
||||
War_Mystic_Raising,
|
||||
War_Mystic_Lowering,
|
||||
Health_Restoring,
|
||||
Health_Depleting,
|
||||
Mana_Restoring,
|
||||
Mana_Depleting,
|
||||
Strength_Increase,
|
||||
Strength_Decrease,
|
||||
Endurance_Increase,
|
||||
Endurance_Decrease,
|
||||
Quickness_Increase,
|
||||
Quickness_Decrease,
|
||||
Coordination_Increase,
|
||||
Coordination_Decrease,
|
||||
Focus_Increase,
|
||||
Focus_Decrease,
|
||||
Self_Increase,
|
||||
Self_Decrease,
|
||||
GreatVitality_Raising,
|
||||
PoorVitality_Lowering,
|
||||
GreatVigor_Raising,
|
||||
PoorVigor_Lowering,
|
||||
GreaterIntellect_Raising,
|
||||
LessorIntellect_Lowering,
|
||||
LifeGiver_Raising,
|
||||
LifeTaker_Lowering,
|
||||
StaminaGiver_Raising,
|
||||
StaminaTaker_Lowering,
|
||||
ManaGiver_Raising,
|
||||
ManaTaker_Lowering,
|
||||
Acid_Ward_Protection,
|
||||
Acid_Ward_Vulnerability,
|
||||
Fire_Ward_Protection,
|
||||
Fire_Ward_Vulnerability,
|
||||
Cold_Ward_Protection,
|
||||
Cold_Ward_Vulnerability,
|
||||
Electric_Ward_Protection,
|
||||
Electric_Ward_Vulnerability,
|
||||
Leadership_Obedience_Raising,
|
||||
Leadership_Obedience_Lowering,
|
||||
Melee_Defense_Shelter_Raising,
|
||||
Melee_Defense_Shelter_Lowering,
|
||||
Missile_Defense_Shelter_Raising,
|
||||
Missile_Defense_Shelter_Lowering,
|
||||
Magic_Defense_Shelter_Raising,
|
||||
Magic_Defense_Shelter_Lowering,
|
||||
HuntersAcumen_Raising,
|
||||
HuntersAcumen_Lowering,
|
||||
StillWater_Raising,
|
||||
StillWater_Lowering,
|
||||
StrengthofEarth_Raising,
|
||||
StrengthofEarth_Lowering,
|
||||
Torrent_Raising,
|
||||
Torrent_Lowering,
|
||||
Growth_Raising,
|
||||
Growth_Lowering,
|
||||
CascadeAxe_Raising,
|
||||
CascadeAxe_Lowering,
|
||||
CascadeDagger_Raising,
|
||||
CascadeDagger_Lowering,
|
||||
CascadeMace_Raising,
|
||||
CascadeMace_Lowering,
|
||||
CascadeSpear_Raising,
|
||||
CascadeSpear_Lowering,
|
||||
CascadeStaff_Raising,
|
||||
CascadeStaff_Lowering,
|
||||
StoneCliffs_Raising,
|
||||
StoneCliffs_Lowering,
|
||||
MaxDamage_Raising,
|
||||
MaxDamage_Lowering,
|
||||
Bow_Damage_Raising,
|
||||
Bow_Damage_Lowering,
|
||||
Bow_Range_Raising,
|
||||
Bow_Range_Lowering,
|
||||
Extra_Defense_Mod_Raising,
|
||||
Extra_Defense_Mod_Lowering,
|
||||
Extra_Bow_Skill_Raising,
|
||||
Extra_Bow_Skill_Lowering,
|
||||
Extra_Alchemy_Skill_Raising,
|
||||
Extra_Alchemy_Skill_Lowering,
|
||||
Extra_Arcane_Lore_Skill_Raising,
|
||||
Extra_Arcane_Lore_Skill_Lowering,
|
||||
Extra_Appraise_Armor_Skill_Raising,
|
||||
Extra_Appraise_Armor_Skill_Lowering,
|
||||
Extra_Cooking_Skill_Raising,
|
||||
Extra_Cooking_Skill_Lowering,
|
||||
Extra_Crossbow_Skill_Raising,
|
||||
Extra_Crossbow_Skill_Lowering,
|
||||
Extra_Deception_Skill_Raising,
|
||||
Extra_Deception_Skill_Lowering,
|
||||
Extra_Loyalty_Skill_Raising,
|
||||
Extra_Loyalty_Skill_Lowering,
|
||||
Extra_Fletching_Skill_Raising,
|
||||
Extra_Fletching_Skill_Lowering,
|
||||
Extra_Healing_Skill_Raising,
|
||||
Extra_Healing_Skill_Lowering,
|
||||
Extra_Melee_Defense_Skill_Raising,
|
||||
Extra_Melee_Defense_Skill_Lowering,
|
||||
Extra_Appraise_Item_Skill_Raising,
|
||||
Extra_Appraise_Item_Skill_Lowering,
|
||||
Extra_Jumping_Skill_Raising,
|
||||
Extra_Jumping_Skill_Lowering,
|
||||
Extra_Life_Magic_Skill_Raising,
|
||||
Extra_Life_Magic_Skill_Lowering,
|
||||
Extra_Lockpick_Skill_Raising,
|
||||
Extra_Lockpick_Skill_Lowering,
|
||||
Extra_Appraise_Magic_Item_Skill_Raising,
|
||||
Extra_Appraise_Magic_Item_Skill_Lowering,
|
||||
Extra_Mana_Conversion_Skill_Raising,
|
||||
Extra_Mana_Conversion_Skill_Lowering,
|
||||
Extra_Assess_Creature_Skill_Raising,
|
||||
Extra_Assess_Creature_Skill_Lowering,
|
||||
Extra_Assess_Person_Skill_Raising,
|
||||
Extra_Assess_Person_Skill_Lowering,
|
||||
Extra_Run_Skill_Raising,
|
||||
Extra_Run_Skill_Lowering,
|
||||
Extra_Sword_Skill_Raising,
|
||||
Extra_Sword_Skill_Lowering,
|
||||
Extra_Thrown_Weapons_Skill_Raising,
|
||||
Extra_Thrown_Weapons_Skill_Lowering,
|
||||
Extra_Unarmed_Combat_Skill_Raising,
|
||||
Extra_Unarmed_Combat_Skill_Lowering,
|
||||
Extra_Appraise_Weapon_Skill_Raising,
|
||||
Extra_Appraise_Weapon_Skill_Lowering,
|
||||
Armor_Increase,
|
||||
Armor_Decrease,
|
||||
Extra_Acid_Resistance_Raising,
|
||||
Extra_Acid_Resistance_Lowering,
|
||||
Extra_Bludgeon_Resistance_Raising,
|
||||
Extra_Bludgeon_Resistance_Lowering,
|
||||
Extra_Fire_Resistance_Raising,
|
||||
Extra_Fire_Resistance_Lowering,
|
||||
Extra_Cold_Resistance_Raising,
|
||||
Extra_Cold_Resistance_Lowering,
|
||||
Extra_Attack_Mod_Raising,
|
||||
Extra_Attack_Mod_Lowering,
|
||||
Extra_Armor_Value_Raising,
|
||||
Extra_Armor_Value_Lowering,
|
||||
Extra_Pierce_Resistance_Raising,
|
||||
Extra_Pierce_Resistance_Lowering,
|
||||
Extra_Slash_Resistance_Raising,
|
||||
Extra_Slash_Resistance_Lowering,
|
||||
Extra_Electric_Resistance_Raising,
|
||||
Extra_Electric_Resistance_Lowering,
|
||||
Extra_Weapon_Time_Raising,
|
||||
Extra_Weapon_Time_Lowering,
|
||||
Bludgeon_Ward_Protection,
|
||||
Bludgeon_Ward_Vulnerability,
|
||||
Slash_Ward_Protection,
|
||||
Slash_Ward_Vulnerability,
|
||||
Pierce_Ward_Protection,
|
||||
Pierce_Ward_Vulnerability,
|
||||
Stamina_Restoring,
|
||||
Stamina_Depleting,
|
||||
Fireworks,
|
||||
Health_Divide,
|
||||
Stamina_Divide,
|
||||
Mana_Divide,
|
||||
Coordination_Increase2,
|
||||
Strength_Increase2,
|
||||
Focus_Increase2,
|
||||
Endurance_Increase2,
|
||||
Self_Increase2,
|
||||
Melee_Defense_Multiply,
|
||||
Missile_Defense_Multiply,
|
||||
Magic_Defense_Multiply,
|
||||
Attributes_Decrease,
|
||||
LifeGiver_Raising2,
|
||||
Item_Enchantment_Raising2,
|
||||
Skills_Decrease,
|
||||
Extra_Mana_Conversion_Bonus,
|
||||
War_Mystic_Raising2,
|
||||
War_Mystic_Lowering2,
|
||||
Magic_Defense_Shelter_Raising2,
|
||||
Extra_Life_Magic_Skill_Raising2,
|
||||
Creature_Mystic_Raising2,
|
||||
Item_Mystic_Raising2,
|
||||
Mana_Raising2,
|
||||
Self_Raising2,
|
||||
CreatureEnchantment_Raising2,
|
||||
Salvaging_Raising,
|
||||
Extra_Salvaging_Raising,
|
||||
Extra_Salvaging_Raising2,
|
||||
CascadeAxe_Raising2,
|
||||
Extra_Bow_Skill_Raising2,
|
||||
Extra_Thrown_Weapons_Skill_Raising2,
|
||||
Extra_Crossbow_Skill_Raising2,
|
||||
CascadeDagger_Raising2,
|
||||
CascadeMace_Raising2,
|
||||
Extra_Unarmed_Combat_Skill_Raising2,
|
||||
CascadeSpear_Raising2,
|
||||
CascadeStaff_Raising2,
|
||||
Extra_Sword_Skill_Raising2,
|
||||
Acid_Protection_Rare,
|
||||
Acid_Resistance_Raising_Rare,
|
||||
Alchemy_Raising_Rare,
|
||||
Appraisal_Resistance_Lowering_Rare,
|
||||
Appraise_Armor_Raising_Rare,
|
||||
Appraise_Item_Raising_Rare,
|
||||
Appraise_Magic_Item_Raising_Rare,
|
||||
Appraise_Weapon_Raising_Rare,
|
||||
Arcane_Lore_Raising_Rare,
|
||||
Armor_Raising_Rare,
|
||||
Armor_Value_Raising_Rare,
|
||||
Assess_Monster_Raising_Rare,
|
||||
Assess_Person_Raising_Rare,
|
||||
Attack_Mod_Raising_Rare,
|
||||
Axe_Raising_Rare,
|
||||
Bludgeon_Protection_Rare,
|
||||
Bludgeon_Resistance_Raising_Rare,
|
||||
Bow_Raising_Rare,
|
||||
Cold_Protection_Rare,
|
||||
Cold_Resistance_Raising_Rare,
|
||||
Cooking_Raising_Rare,
|
||||
Coordination_Raising_Rare,
|
||||
Creature_Enchantment_Raising_Rare,
|
||||
Crossbow_Raising_Rare,
|
||||
Dagger_Raising_Rare,
|
||||
Damage_Raising_Rare,
|
||||
Deception_Raising_Rare,
|
||||
Defense_Mod_Raising_Rare,
|
||||
Electric_Protection_Rare,
|
||||
Electric_Resistance_Raising_Rare,
|
||||
Endurance_Raising_Rare,
|
||||
Fire_Protection_Rare,
|
||||
Fire_Resistance_Raising_Rare,
|
||||
Fletching_Raising_Rare,
|
||||
Focus_Raising_Rare,
|
||||
Healing_Raising_Rare,
|
||||
Health_Accelerating_Rare,
|
||||
Item_Enchantment_Raising_Rare,
|
||||
Jump_Raising_Rare,
|
||||
Leadership_Raising_Rare,
|
||||
Life_Magic_Raising_Rare,
|
||||
Lockpick_Raising_Rare,
|
||||
Loyalty_Raising_Rare,
|
||||
Mace_Raising_Rare,
|
||||
Magic_Defense_Raising_Rare,
|
||||
Mana_Accelerating_Rare,
|
||||
Mana_Conversion_Raising_Rare,
|
||||
Melee_Defense_Raising_Rare,
|
||||
Missile_Defense_Raising_Rare,
|
||||
Pierce_Protection_Rare,
|
||||
Pierce_Resistance_Raising_Rare,
|
||||
Quickness_Raising_Rare,
|
||||
Run_Raising_Rare,
|
||||
Self_Raising_Rare,
|
||||
Slash_Protection_Rare,
|
||||
Slash_Resistance_Raising_Rare,
|
||||
Spear_Raising_Rare,
|
||||
Staff_Raising_Rare,
|
||||
Stamina_Accelerating_Rare,
|
||||
Strength_Raising_Rare,
|
||||
Sword_Raising_Rare,
|
||||
Thrown_Weapons_Raising_Rare,
|
||||
Unarmed_Combat_Raising_Rare,
|
||||
War_Magic_Raising_Rare,
|
||||
Weapon_Time_Raising_Rare,
|
||||
Armor_Increase_Inky_Armor,
|
||||
Magic_Defense_Shelter_Raising_Fiun,
|
||||
Extra_Run_Skill_Raising_Fiun,
|
||||
Extra_Mana_Conversion_Skill_Raising_Fiun,
|
||||
Attributes_Increase_Cantrip1,
|
||||
Extra_Melee_Defense_Skill_Raising2,
|
||||
ACTDPurchaseRewardSpell,
|
||||
ACTDPurchaseRewardSpellHealth,
|
||||
SaltAsh_Attack_Mod_Raising,
|
||||
Quickness_Increase2,
|
||||
Extra_Alchemy_Skill_Raising2,
|
||||
Extra_Cooking_Skill_Raising2,
|
||||
Extra_Fletching_Skill_Raising2,
|
||||
Extra_Lockpick_Skill_Raising2,
|
||||
MucorManaWell,
|
||||
Stamina_Restoring2,
|
||||
Allegiance_Raising,
|
||||
Health_DoT,
|
||||
Health_DoT_Secondary,
|
||||
Health_DoT_Tertiary,
|
||||
Health_HoT,
|
||||
Health_HoT_Secondary,
|
||||
Health_HoT_Tertiary,
|
||||
Health_Divide_Secondary,
|
||||
Health_Divide_Tertiary,
|
||||
SetSword_Raising,
|
||||
SetAxe_Raising,
|
||||
SetDagger_Raising,
|
||||
SetMace_Raising,
|
||||
SetSpear_Raising,
|
||||
SetStaff_Raising,
|
||||
SetUnarmed_Raising,
|
||||
SetBow_Raising,
|
||||
SetCrossbow_Raising,
|
||||
SetThrown_Raising,
|
||||
SetItemEnchantment_Raising,
|
||||
SetCreatureEnchantment_Raising,
|
||||
SetWarMagic_Raising,
|
||||
SetLifeMagic_Raising,
|
||||
SetMeleeDefense_Raising,
|
||||
SetMissileDefense_Raising,
|
||||
SetMagicDefense_Raising,
|
||||
SetStamina_Accelerating,
|
||||
SetCooking_Raising,
|
||||
SetFletching_Raising,
|
||||
SetLockpick_Raising,
|
||||
SetAlchemy_Raising,
|
||||
SetSalvaging_Raising,
|
||||
SetArmorExpertise_Raising,
|
||||
SetWeaponExpertise_Raising,
|
||||
SetItemTinkering_Raising,
|
||||
SetMagicItemExpertise_Raising,
|
||||
SetLoyalty_Raising,
|
||||
SetStrength_Raising,
|
||||
SetEndurance_Raising,
|
||||
SetCoordination_Raising,
|
||||
SetQuickness_Raising,
|
||||
SetFocus_Raising,
|
||||
SetWillpower_Raising,
|
||||
SetHealth_Raising,
|
||||
SetStamina_Raising,
|
||||
SetMana_Raising,
|
||||
SetSprint_Raising,
|
||||
SetJumping_Raising,
|
||||
SetSlashResistance_Raising,
|
||||
SetBludgeonResistance_Raising,
|
||||
SetPierceResistance_Raising,
|
||||
SetFlameResistance_Raising,
|
||||
SetAcidResistance_Raising,
|
||||
SetFrostResistance_Raising,
|
||||
SetLightningResistance_Raising,
|
||||
Crafting_LockPick_Raising,
|
||||
Crafting_Fletching_Raising,
|
||||
Crafting_Cooking_Raising,
|
||||
Crafting_Alchemy_Raising,
|
||||
Crafting_ArmorTinkering_Raising,
|
||||
Crafting_WeaponTinkering_Raising,
|
||||
Crafting_MagicTinkering_Raising,
|
||||
Crafting_ItemTinkering_Raising,
|
||||
SkillPercent_Alchemy_Raising,
|
||||
TwoHanded_Raising,
|
||||
TwoHanded_Lowering,
|
||||
Extra_TwoHanded_Skill_Raising,
|
||||
Extra_TwoHanded_Skill_Lowering,
|
||||
Extra_TwoHanded_Skill_Raising2,
|
||||
TwoHanded_Raising_Rare,
|
||||
SetTwoHanded_Raising,
|
||||
GearCraft_Raising,
|
||||
GearCraft_Lowering,
|
||||
Extra_GearCraft_Skill_Raising,
|
||||
Extra_GearCraft_Skill_Lowering,
|
||||
Extra_GearCraft_Skill_Raising2,
|
||||
GearCraft_Raising_Rare,
|
||||
SetGearCraft_Raising,
|
||||
LoyaltyMana_Raising,
|
||||
LoyaltyStamina_Raising,
|
||||
LeadershipHealth_Raising,
|
||||
TrinketDamage_Raising,
|
||||
TrinketDamage_Lowering,
|
||||
TrinketHealth_Raising,
|
||||
TrinketStamina_Raising,
|
||||
TrinketMana_Raising,
|
||||
TrinketXP_Raising,
|
||||
DeceptionArcaneLore_Raising,
|
||||
HealOverTime_Raising,
|
||||
DamageOverTime_Raising,
|
||||
HealingResistRating_Raising,
|
||||
AetheriaDamageRating_Raising,
|
||||
AetheriaDamageReduction_Raising,
|
||||
SKIPPED,
|
||||
AetheriaHealth_Raising,
|
||||
AetheriaStamina_Raising,
|
||||
AetheriaMana_Raising,
|
||||
AetheriaCriticalDamage_Raising,
|
||||
AetheriaHealingAmplification_Raising,
|
||||
AetheriaProcDamageRating_Raising,
|
||||
AetheriaProcDamageReduction_Raising,
|
||||
AetheriaProcHealthOverTime_Raising,
|
||||
AetheriaProcDamageOverTime_Raising,
|
||||
AetheriaProcHealingReduction_Raising,
|
||||
RareDamageRating_Raising,
|
||||
RareDamageReductionRating_Raising,
|
||||
AetheriaEndurance_Raising,
|
||||
NetherDamageOverTime_Raising,
|
||||
NetherDamageOverTime_Raising2,
|
||||
NetherDamageOverTime_Raising3,
|
||||
Nether_Streak,
|
||||
Nether_Missile,
|
||||
Nether_Ring,
|
||||
NetherDamageRating_Lowering,
|
||||
NetherDamageHealingReduction_Raising,
|
||||
Void_Magic_Lowering,
|
||||
Void_Magic_Raising,
|
||||
Void_Mystic_Raising,
|
||||
SetVoidMagic_Raising,
|
||||
Void_Magic_Raising_Rare,
|
||||
Void_Mystic_Raising2,
|
||||
LuminanceDamageRating_Raising,
|
||||
LuminanceDamageReduction_Raising,
|
||||
LuminanceHealth_Raising,
|
||||
AetheriaCriticalReduction_Raising,
|
||||
Extra_Missile_Defense_Skill_Raising,
|
||||
Extra_Missile_Defense_Skill_Lowering,
|
||||
Extra_Missile_Defense_Skill_Raising2,
|
||||
AetheriaHealthResistance_Raising,
|
||||
AetheriaDotResistance_Raising,
|
||||
Cloak_Skill_Raising,
|
||||
Cloak_All_Skill_Raising,
|
||||
Cloak_Magic_Defense_Lowering,
|
||||
Cloak_Melee_Defense_Lowering,
|
||||
Cloak_Missile_Defense_Lowering,
|
||||
DirtyFighting_Lowering,
|
||||
DirtyFighting_Raising,
|
||||
Extra_DirtyFighting_Raising,
|
||||
DualWield_Lowering,
|
||||
DualWield_Raising,
|
||||
Extra_DualWield_Raising,
|
||||
Recklessness_Lowering,
|
||||
Recklessness_Raising,
|
||||
Extra_Recklessness_Raising,
|
||||
Shield_Lowering,
|
||||
Shield_Raising,
|
||||
Extra_Shield_Raising,
|
||||
SneakAttack_Lowering,
|
||||
SneakAttack_Raising,
|
||||
Extra_SneakAttack_Raising,
|
||||
Rare_DirtyFighting_Raising,
|
||||
Rare_DualWield_Raising,
|
||||
Rare_Recklessness_Raising,
|
||||
Rare_Shield_Raising,
|
||||
Rare_SneakAttack_Raising,
|
||||
DF_Attack_Skill_Debuff,
|
||||
DF_Bleed_Damage,
|
||||
DF_Defense_Skill_Debuff,
|
||||
DF_Healing_Debuff,
|
||||
SetDirtyFighting_Raising,
|
||||
SetDualWield_Raising,
|
||||
SetRecklessness_Raising,
|
||||
SetShield_Raising,
|
||||
SetSneakAttack_Raising,
|
||||
LifeGiver_Mhoire,
|
||||
RareDamageRating_Raising2,
|
||||
Spell_Damage_Raising,
|
||||
Summoning_Raising,
|
||||
Summoning_Lowering,
|
||||
Extra_Summoning_Skill_Raising,
|
||||
SetSummoning_Raising
|
||||
}
|
||||
}
|
||||
106
Shared/Constants/StringValueKey.cs
Normal file
106
Shared/Constants/StringValueKey.cs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
// https://github.com/ACEmulator/ACE/blob/master/Source/ACE.Entity/Enum/Properties/PropertyString.cs
|
||||
public enum StringValueKey
|
||||
{
|
||||
// properties marked as ServerOnly are properties we never saw in PCAPs, from here:
|
||||
// http://ac.yotesfan.com/ace_object/not_used_enums.php
|
||||
// source: @OptimShi
|
||||
// description attributes are used by the weenie editor for a cleaner display name
|
||||
Undef = 0,
|
||||
[SendOnLogin]
|
||||
Name = 1,
|
||||
/// <summary>
|
||||
/// default "Adventurer"
|
||||
/// </summary>
|
||||
Title = 2,
|
||||
Sex = 3,
|
||||
HeritageGroup = 4,
|
||||
Template = 5,
|
||||
AttackersName = 6,
|
||||
Inscription = 7,
|
||||
[Description("Scribe Name")]
|
||||
ScribeName = 8,
|
||||
VendorsName = 9,
|
||||
Fellowship = 10,
|
||||
MonarchsName = 11,
|
||||
[ServerOnly]
|
||||
LockCode = 12,
|
||||
[ServerOnly]
|
||||
KeyCode = 13,
|
||||
Use = 14,
|
||||
ShortDesc = 15,
|
||||
LongDesc = 16,
|
||||
ActivationTalk = 17,
|
||||
[ServerOnly]
|
||||
UseMessage = 18,
|
||||
ItemHeritageGroupRestriction = 19,
|
||||
PluralName = 20,
|
||||
MonarchsTitle = 21,
|
||||
ActivationFailure = 22,
|
||||
ScribeAccount = 23,
|
||||
TownName = 24,
|
||||
CraftsmanName = 25,
|
||||
UsePkServerError = 26,
|
||||
ScoreCachedText = 27,
|
||||
ScoreDefaultEntryFormat = 28,
|
||||
ScoreFirstEntryFormat = 29,
|
||||
ScoreLastEntryFormat = 30,
|
||||
ScoreOnlyEntryFormat = 31,
|
||||
ScoreNoEntry = 32,
|
||||
[ServerOnly]
|
||||
Quest = 33,
|
||||
GeneratorEvent = 34,
|
||||
PatronsTitle = 35,
|
||||
HouseOwnerName = 36,
|
||||
QuestRestriction = 37,
|
||||
AppraisalPortalDestination = 38,
|
||||
TinkerName = 39,
|
||||
ImbuerName = 40,
|
||||
HouseOwnerAccount = 41,
|
||||
DisplayName = 42,
|
||||
DateOfBirth = 43,
|
||||
ThirdPartyApi = 44,
|
||||
KillQuest = 45,
|
||||
Afk = 46,
|
||||
AllegianceName = 47,
|
||||
AugmentationAddQuest = 48,
|
||||
KillQuest2 = 49,
|
||||
KillQuest3 = 50,
|
||||
UseSendsSignal = 51,
|
||||
|
||||
[Description("Gear Plating Name")]
|
||||
GearPlatingName = 52,
|
||||
|
||||
|
||||
// ACE Specific
|
||||
[ServerOnly]
|
||||
PCAPRecordedCurrentMotionState = 8006,
|
||||
[ServerOnly]
|
||||
PCAPRecordedServerName = 8031,
|
||||
[ServerOnly]
|
||||
PCAPRecordedCharacterName = 8032,
|
||||
|
||||
/* custom */
|
||||
[ServerOnly]
|
||||
AllegianceMotd = 9001,
|
||||
[ServerOnly]
|
||||
AllegianceMotdSetBy = 9002,
|
||||
[ServerOnly]
|
||||
AllegianceSpeakerTitle = 9003,
|
||||
[ServerOnly]
|
||||
AllegianceSeneschalTitle = 9004,
|
||||
[ServerOnly]
|
||||
AllegianceCastellanTitle = 9005,
|
||||
[ServerOnly]
|
||||
GodState = 9006,
|
||||
[ServerOnly]
|
||||
TinkerLog = 9007,
|
||||
|
||||
|
||||
// Decal Specific
|
||||
SecondaryName_Decal = 184549376,
|
||||
}
|
||||
}
|
||||
79
Shared/Constants/WeenieType.cs
Normal file
79
Shared/Constants/WeenieType.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
public enum WeenieType : uint
|
||||
{
|
||||
Undef,
|
||||
Generic,
|
||||
Clothing,
|
||||
MissileLauncher,
|
||||
Missile,
|
||||
Ammunition,
|
||||
MeleeWeapon,
|
||||
Portal,
|
||||
Book,
|
||||
Coin,
|
||||
Creature,
|
||||
Admin,
|
||||
Vendor,
|
||||
HotSpot,
|
||||
Corpse,
|
||||
Cow,
|
||||
AI,
|
||||
Machine,
|
||||
Food,
|
||||
Door,
|
||||
Chest,
|
||||
Container,
|
||||
Key,
|
||||
Lockpick,
|
||||
PressurePlate,
|
||||
LifeStone,
|
||||
Switch,
|
||||
PKModifier,
|
||||
Healer,
|
||||
LightSource,
|
||||
Allegiance,
|
||||
UNKNOWN__GUESSEDNAME32, // NOTE: Missing 1
|
||||
SpellComponent,
|
||||
ProjectileSpell,
|
||||
Scroll,
|
||||
Caster,
|
||||
Channel,
|
||||
ManaStone,
|
||||
Gem,
|
||||
AdvocateFane,
|
||||
AdvocateItem,
|
||||
Sentinel,
|
||||
GSpellEconomy,
|
||||
LSpellEconomy,
|
||||
CraftTool,
|
||||
LScoreKeeper,
|
||||
GScoreKeeper,
|
||||
GScoreGatherer,
|
||||
ScoreBook,
|
||||
EventCoordinator,
|
||||
Entity,
|
||||
Stackable,
|
||||
HUD,
|
||||
House,
|
||||
Deed,
|
||||
SlumLord,
|
||||
Hook,
|
||||
Storage,
|
||||
BootSpot,
|
||||
HousePortal,
|
||||
Game,
|
||||
GamePiece,
|
||||
SkillAlterationDevice,
|
||||
AttributeTransferDevice,
|
||||
Hooker,
|
||||
AllegianceBindstone,
|
||||
InGameStatKeeper,
|
||||
AugmentationDevice,
|
||||
SocialManager,
|
||||
Pet,
|
||||
PetDevice,
|
||||
CombatPet
|
||||
}
|
||||
}
|
||||
20
Shared/Constants/WieldRequirement.cs
Normal file
20
Shared/Constants/WieldRequirement.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
namespace Mag.Shared.Constants
|
||||
{
|
||||
public enum WieldRequirement
|
||||
{
|
||||
Invalid,
|
||||
Skill,
|
||||
RawSkill,
|
||||
Attrib,
|
||||
RawAttrib,
|
||||
SecondaryAttrib,
|
||||
RawSecondaryAttrib,
|
||||
Level,
|
||||
Training,
|
||||
IntStat,
|
||||
BoolStat,
|
||||
CreatureType,
|
||||
HeritageType
|
||||
}
|
||||
}
|
||||
139
Shared/Debug.cs
Normal file
139
Shared/Debug.cs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
using Decal.Adapter;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
static class Debug
|
||||
{
|
||||
static string _debugLogPath;
|
||||
static string _errorLogPath;
|
||||
|
||||
static string _pluginName;
|
||||
|
||||
public static void Init(string debugLogPath, string errorLogPath, string pluginName)
|
||||
{
|
||||
_debugLogPath = debugLogPath;
|
||||
_errorLogPath = errorLogPath;
|
||||
|
||||
_pluginName = pluginName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will write to the debug.txt
|
||||
/// </summary>
|
||||
public static void LogDebug(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(_debugLogPath))
|
||||
return;
|
||||
|
||||
FileInfo fileInfo = new FileInfo(_debugLogPath);
|
||||
|
||||
// Limit the file to 1MB
|
||||
bool append = !(fileInfo.Exists && fileInfo.Length > 1048576);
|
||||
|
||||
using (StreamWriter writer = new StreamWriter(fileInfo.FullName, append))
|
||||
{
|
||||
writer.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + "," + message);
|
||||
writer.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Eat the exception, yumm.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will only write the exception to the errors.txt file if DebugEnabled is true.
|
||||
/// </summary>
|
||||
public static void LogException(Exception ex, string note = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
//if (!Settings.SettingsManager.Misc.DebuggingEnabled.Value)
|
||||
// return;
|
||||
|
||||
if (note != null)
|
||||
MyClasses.VCS_Connector.SendChatTextCategorized("Errors", "<{" + _pluginName + "}>: " + "Exception caught: " + ex.Message + Environment.NewLine + ex.Source + Environment.NewLine + ex.StackTrace + Environment.NewLine + "Note: " + note, 5);
|
||||
else
|
||||
MyClasses.VCS_Connector.SendChatTextCategorized("Errors", "<{" + _pluginName + "}>: " + "Exception caught: " + ex.Message + Environment.NewLine + ex.Source + Environment.NewLine + ex.StackTrace, 5);
|
||||
|
||||
if (String.IsNullOrEmpty(_errorLogPath))
|
||||
return;
|
||||
|
||||
FileInfo fileInfo = new FileInfo(_errorLogPath);
|
||||
|
||||
// Limit the file to 1MB
|
||||
bool append = !(fileInfo.Exists && fileInfo.Length > 1048576);
|
||||
|
||||
using (StreamWriter writer = new StreamWriter(fileInfo.FullName, append))
|
||||
{
|
||||
writer.WriteLine("============================================================================");
|
||||
|
||||
writer.WriteLine(DateTime.Now.ToString(CultureInfo.InvariantCulture));
|
||||
writer.WriteLine(ex);
|
||||
|
||||
if (note != null)
|
||||
writer.WriteLine("Note: " + note);
|
||||
|
||||
writer.WriteLine("============================================================================");
|
||||
writer.WriteLine("");
|
||||
writer.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Eat the exception, yumm.
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogText(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
//if (!Settings.SettingsManager.Misc.DebuggingEnabled.Value)
|
||||
// return;
|
||||
|
||||
MyClasses.VCS_Connector.SendChatTextCategorized("CommandLine", "<{" + _pluginName + "}>: " + "Log Text: " + text, 5);
|
||||
|
||||
if (String.IsNullOrEmpty(_errorLogPath))
|
||||
return;
|
||||
|
||||
FileInfo fileInfo = new FileInfo(_errorLogPath);
|
||||
|
||||
// Limit the file to 1MB
|
||||
bool append = !(fileInfo.Exists && fileInfo.Length > 1048576);
|
||||
|
||||
using (StreamWriter writer = new StreamWriter(fileInfo.FullName, append))
|
||||
{
|
||||
writer.WriteLine(DateTime.Now + ": " + text);
|
||||
writer.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { LogException(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will only write the message to the chat if DebugEnabled is true.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="color"></param>
|
||||
/// <param name="target"></param>
|
||||
public static void WriteToChat(string message, int color = 5, int target = 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
//if (!Settings.SettingsManager.Misc.DebuggingEnabled.Value)
|
||||
// return;
|
||||
|
||||
MyClasses.VCS_Connector.SendChatTextCategorized("CommandLine", "<{" + _pluginName + "}>: " + message, color, target);
|
||||
}
|
||||
catch (Exception ex) { LogException(ex); }
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Shared/DecalProxy.cs
Normal file
178
Shared/DecalProxy.cs
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
//File: DecalProxy.cs
|
||||
//
|
||||
//Description: Contains reflection-based calls of newer Decal methods, so that
|
||||
// they can be easily invoked only if the currently running version contains
|
||||
// the desired feature.
|
||||
//
|
||||
//
|
||||
//This file is Copyright (c) 2013 VirindiPlugins
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//Virindi: yes, or you could just directly resize the window
|
||||
//Virindi: note...
|
||||
//Virindi: right now you have to call move after you call resize
|
||||
//Virindi: so right now what you have to do if you use that resize is grab the location, resize, then move back to the original location
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Reflection;
|
||||
using System.Drawing;
|
||||
|
||||
using Decal.Adapter;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
internal static class DecalProxy
|
||||
{
|
||||
public enum UIElementType : int
|
||||
{
|
||||
Smartbox = 0x1000049A,
|
||||
Chat = 0x10000601,
|
||||
FloatChat1 = 0x10000505,
|
||||
FloatChat2 = 0x1000050E,
|
||||
FloatChat3 = 0x1000050F,
|
||||
FloatChat4 = 0x10000510,
|
||||
Examination = 0x100005F7,
|
||||
Vitals = 0x100005FA,
|
||||
EnvPack = 0x100005FD,
|
||||
Panels = 0x100005FF,
|
||||
TBar = 0x10000603,
|
||||
Indicators = 0x10000611,
|
||||
ProgressBar = 0x10000613,
|
||||
Combat = 0x100006B5,
|
||||
Radar = 0x100006D2,
|
||||
}
|
||||
|
||||
|
||||
static Version iCachedDecalVersion = null;
|
||||
static Assembly iCachedDecalAssembly = null;
|
||||
public static bool TestDecalVersion(Version v)
|
||||
{
|
||||
if (iCachedDecalVersion != null) return iCachedDecalVersion >= v;
|
||||
|
||||
System.Reflection.Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (System.Reflection.Assembly a in asms)
|
||||
{
|
||||
AssemblyName nmm = a.GetName();
|
||||
if (nmm.Name == "Decal.Adapter")
|
||||
{
|
||||
iCachedDecalVersion = nmm.Version;
|
||||
iCachedDecalAssembly = a;
|
||||
return (nmm.Version >= v);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Hooks_UIElementMove(Decal.Adapter.Wrappers.HooksWrapper hooks, UIElementType e, int x, int y)
|
||||
{
|
||||
//This will load the cached assembly. We don't actually care what the version is
|
||||
//since we just check below if the required members are present.
|
||||
TestDecalVersion(new Version());
|
||||
|
||||
int ee = (int)e;
|
||||
|
||||
Type DecalType_UIElementType = iCachedDecalAssembly.GetType("Decal.Adapter.Wrappers.UIElementType");
|
||||
if (DecalType_UIElementType == null) return;
|
||||
object e_decal = Enum.ToObject(DecalType_UIElementType, ee);
|
||||
|
||||
Type DecalType_HooksWrapper = iCachedDecalAssembly.GetType("Decal.Adapter.Wrappers.HooksWrapper");
|
||||
if (DecalType_HooksWrapper == null) return;
|
||||
MethodInfo call = DecalType_HooksWrapper.GetMethod("UIElementMove", new Type[] { DecalType_UIElementType, typeof(int), typeof(int) });
|
||||
if (call == null) return;
|
||||
|
||||
call.Invoke(hooks, new object[] { e_decal, x, y });
|
||||
}
|
||||
|
||||
public static void Hooks_UIElementResize(Decal.Adapter.Wrappers.HooksWrapper hooks, UIElementType e, int width, int height)
|
||||
{
|
||||
//This will load the cached assembly. We don't actually care what the version is
|
||||
//since we just check below if the required members are present.
|
||||
TestDecalVersion(new Version());
|
||||
|
||||
int ee = (int)e;
|
||||
|
||||
Type DecalType_UIElementType = iCachedDecalAssembly.GetType("Decal.Adapter.Wrappers.UIElementType");
|
||||
if (DecalType_UIElementType == null) return;
|
||||
object e_decal = Enum.ToObject(DecalType_UIElementType, ee);
|
||||
|
||||
Type DecalType_HooksWrapper = iCachedDecalAssembly.GetType("Decal.Adapter.Wrappers.HooksWrapper");
|
||||
if (DecalType_HooksWrapper == null) return;
|
||||
MethodInfo call = DecalType_HooksWrapper.GetMethod("UIElementResize", new Type[] { DecalType_UIElementType, typeof(int), typeof(int) });
|
||||
if (call == null) return;
|
||||
|
||||
call.Invoke(hooks, new object[] { e_decal, width, height });
|
||||
}
|
||||
|
||||
public static Rectangle Hooks_UIElementRegion(Decal.Adapter.Wrappers.HooksWrapper hooks, UIElementType e)
|
||||
{
|
||||
//This will load the cached assembly. We don't actually care what the version is
|
||||
//since we just check below if the required members are present.
|
||||
TestDecalVersion(new Version());
|
||||
|
||||
int ee = (int)e;
|
||||
|
||||
Type DecalType_UIElementType = iCachedDecalAssembly.GetType("Decal.Adapter.Wrappers.UIElementType");
|
||||
if (DecalType_UIElementType == null) return Rectangle.Empty;
|
||||
object e_decal = Enum.ToObject(DecalType_UIElementType, ee);
|
||||
|
||||
Type DecalType_HooksWrapper = iCachedDecalAssembly.GetType("Decal.Adapter.Wrappers.HooksWrapper");
|
||||
if (DecalType_HooksWrapper == null) return Rectangle.Empty;
|
||||
MethodInfo call = DecalType_HooksWrapper.GetMethod("UIElementRegion", new Type[] { DecalType_UIElementType });
|
||||
if (call == null) return Rectangle.Empty;
|
||||
|
||||
return (Rectangle)call.Invoke(hooks, new object[] { e_decal });
|
||||
}
|
||||
|
||||
|
||||
[DllImport("Decal.dll")]
|
||||
static extern int DispatchOnChatCommand(ref IntPtr str, [MarshalAs(UnmanagedType.U4)] int target);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will first attempt to send the messages to all plugins. If no plugins set e.Eat to true on the message, it will then simply call InvokeChatParser.
|
||||
/// </summary>
|
||||
/// <param name="cmd"></param>
|
||||
public static void DispatchChatToBoxWithPluginIntercept(string cmd)
|
||||
{
|
||||
if (!Decal_DispatchOnChatCommand(cmd))
|
||||
CoreManager.Current.Actions.InvokeChatParser(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
355
Shared/ItemInfo.cs
Normal file
355
Shared/ItemInfo.cs
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Mag.Shared.Constants;
|
||||
using Mag.Shared.Spells;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiate this object with the item you want info for.
|
||||
/// ToString() this object for the info.
|
||||
/// </summary>
|
||||
public class ItemInfo
|
||||
{
|
||||
private readonly MyWorldObject mwo;
|
||||
|
||||
public ItemInfo(MyWorldObject myWorldObject)
|
||||
{
|
||||
mwo = myWorldObject;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(true, true);
|
||||
}
|
||||
|
||||
public string ToString(bool showBuffedValues, bool showValueAndBurden)
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
|
||||
if (mwo.Values(IntValueKey.MaterialType) > 0)
|
||||
{
|
||||
if (Dictionaries.MaterialInfo.ContainsKey(mwo.Values(IntValueKey.MaterialType)))
|
||||
sb.Append(Dictionaries.MaterialInfo[mwo.Values(IntValueKey.MaterialType)] + " ");
|
||||
else
|
||||
sb.Append("unknown material " + mwo.Values(IntValueKey.MaterialType) + " ");
|
||||
}
|
||||
|
||||
sb.Append(mwo.Name);
|
||||
|
||||
if (mwo.Values((IntValueKey)353) > 0)
|
||||
{
|
||||
if (Dictionaries.MasteryInfo.ContainsKey(mwo.Values((IntValueKey)353)))
|
||||
sb.Append(" (" + Dictionaries.MasteryInfo[mwo.Values((IntValueKey)353)] + ")");
|
||||
else
|
||||
sb.Append(" (Unknown mastery " + mwo.Values((IntValueKey)353) + ")");
|
||||
}
|
||||
|
||||
int set = mwo.Values((IntValueKey)265, 0);
|
||||
if (set != 0)
|
||||
{
|
||||
sb.Append(", ");
|
||||
if (Dictionaries.AttributeSetInfo.ContainsKey(set))
|
||||
sb.Append(Dictionaries.AttributeSetInfo[set]);
|
||||
else
|
||||
sb.Append("Unknown set " + set);
|
||||
}
|
||||
|
||||
if (mwo.Values(IntValueKey.ArmorLevel) > 0)
|
||||
sb.Append(", AL " + mwo.Values(IntValueKey.ArmorLevel));
|
||||
|
||||
if (mwo.Values(IntValueKey.ImbuedEffect) > 0)
|
||||
{
|
||||
sb.Append(",");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 1) == 1) sb.Append(" CS");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 2) == 2) sb.Append(" CB");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 4) == 4) sb.Append(" AR");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 8) == 8) sb.Append(" SlashRend");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 16) == 16) sb.Append(" PierceRend");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 32) == 32) sb.Append(" BludgeRend");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 64) == 64) sb.Append(" AcidRend");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 128) == 128) sb.Append(" FrostRend");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 256) == 256) sb.Append(" LightRend");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 512) == 512) sb.Append(" FireRend");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 1024) == 1024) sb.Append(" MeleeImbue");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 4096) == 4096) sb.Append(" MagicImbue");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 8192) == 8192) sb.Append(" Hematited");
|
||||
if ((mwo.Values(IntValueKey.ImbuedEffect) & 536870912) == 536870912) sb.Append(" MagicAbsorb");
|
||||
}
|
||||
|
||||
if (mwo.Values(IntValueKey.NumTimesTinkered) > 0)
|
||||
sb.Append(", Tinks " + mwo.Values(IntValueKey.NumTimesTinkered));
|
||||
|
||||
if (mwo.Values(IntValueKey.MaxDamage_Decal) != 0 && mwo.Values(DoubleValueKey.Variance_Decal) != 0)
|
||||
sb.Append(", " + (mwo.Values(IntValueKey.MaxDamage_Decal) - (mwo.Values(IntValueKey.MaxDamage_Decal) * mwo.Values(DoubleValueKey.Variance_Decal))).ToString("N2") + "-" + mwo.Values(IntValueKey.MaxDamage_Decal));
|
||||
else if (mwo.Values(IntValueKey.MaxDamage_Decal) != 0 && mwo.Values(DoubleValueKey.Variance_Decal) == 0)
|
||||
sb.Append(", " + mwo.Values(IntValueKey.MaxDamage_Decal));
|
||||
|
||||
if (mwo.Values(IntValueKey.ElementalDamageBonus, 0) != 0)
|
||||
sb.Append(", +" + mwo.Values(IntValueKey.ElementalDamageBonus));
|
||||
|
||||
if (mwo.Values(DoubleValueKey.DamageBonus_Decal, 1) != 1)
|
||||
sb.Append(", +" + Math.Round(((mwo.Values(DoubleValueKey.DamageBonus_Decal) - 1) * 100)) + "%");
|
||||
|
||||
if (mwo.Values(DoubleValueKey.ElementalDamageMod, 1) != 1)
|
||||
sb.Append(", +" + Math.Round(((mwo.Values(DoubleValueKey.ElementalDamageMod) - 1) * 100)) + "%vs. Monsters");
|
||||
|
||||
if (mwo.Values(DoubleValueKey.AttackBonus_Decal, 1) != 1)
|
||||
sb.Append(", +" + Math.Round(((mwo.Values(DoubleValueKey.AttackBonus_Decal) - 1) * 100)) + "%a");
|
||||
|
||||
if (mwo.Values(DoubleValueKey.WeaponDefense, 1) != 1)
|
||||
sb.Append(", " + Math.Round(((mwo.Values(DoubleValueKey.WeaponDefense) - 1) * 100)) + "%md");
|
||||
|
||||
if (mwo.Values(DoubleValueKey.WeaponMagicDefense, 1) != 1)
|
||||
sb.Append(", " + Math.Round(((mwo.Values(DoubleValueKey.WeaponMagicDefense) - 1) * 100), 1) + "%mgc.d");
|
||||
|
||||
if (mwo.Values(DoubleValueKey.WeaponMissileDefense, 1) != 1)
|
||||
sb.Append(", " + Math.Round(((mwo.Values(DoubleValueKey.WeaponMissileDefense) - 1) * 100), 1) + "%msl.d");
|
||||
|
||||
if (mwo.Values(DoubleValueKey.ManaConversionMod) != 0)
|
||||
sb.Append(", " + Math.Round((mwo.Values(DoubleValueKey.ManaConversionMod) * 100)) + "%mc");
|
||||
|
||||
if (showBuffedValues && (mwo.ObjectClass == (int)ObjectClass.MeleeWeapon || mwo.ObjectClass == (int)ObjectClass.MissileWeapon || mwo.ObjectClass == (int)ObjectClass.WandStaffOrb))
|
||||
{
|
||||
sb.Append(", (");
|
||||
|
||||
// (Damage)
|
||||
if (mwo.ObjectClass == (int)ObjectClass.MeleeWeapon)
|
||||
sb.Append(mwo.CalcedBuffedTinkedDoT.ToString("N1") + "/" + mwo.GetBuffedIntValueKey((int)IntValueKey.MaxDamage_Decal));
|
||||
|
||||
if (mwo.ObjectClass == (int)ObjectClass.MissileWeapon)
|
||||
sb.Append(mwo.CalcedBuffedMissileDamage.ToString("N1"));
|
||||
|
||||
if (mwo.ObjectClass == (int)ObjectClass.WandStaffOrb)
|
||||
sb.Append(((mwo.GetBuffedDoubleValueKey((int)DoubleValueKey.ElementalDamageMod) - 1) * 100).ToString("N1"));
|
||||
|
||||
// (AttackBonus/MeleeDefenseBonus/ManaCBonus)
|
||||
sb.Append(" ");
|
||||
|
||||
if (mwo.Values(DoubleValueKey.AttackBonus_Decal, 1) != 1)
|
||||
sb.Append(Math.Round(((mwo.GetBuffedDoubleValueKey((int)DoubleValueKey.AttackBonus_Decal) - 1) * 100)).ToString("N1") + "/");
|
||||
|
||||
if (mwo.Values(DoubleValueKey.WeaponDefense, 1) != 1)
|
||||
sb.Append(Math.Round(((mwo.GetBuffedDoubleValueKey((int)DoubleValueKey.WeaponDefense) - 1) * 100)).ToString("N1"));
|
||||
|
||||
if (mwo.Values(DoubleValueKey.ManaConversionMod) != 0)
|
||||
sb.Append("/" + Math.Round(mwo.GetBuffedDoubleValueKey((int)DoubleValueKey.ManaConversionMod) * 100));
|
||||
|
||||
sb.Append(")");
|
||||
}
|
||||
|
||||
if (mwo.Spells.Count > 0)
|
||||
{
|
||||
List<int> sortedSpellIds = new List<int>();
|
||||
|
||||
foreach (var spell in mwo.Spells)
|
||||
sortedSpellIds.Add(spell);
|
||||
|
||||
sortedSpellIds.Sort();
|
||||
sortedSpellIds.Reverse();
|
||||
|
||||
foreach (int spellId in sortedSpellIds)
|
||||
{
|
||||
Spell spell = SpellTools.GetSpell(spellId);
|
||||
|
||||
if (spell == null)
|
||||
continue;
|
||||
|
||||
// If the item is not loot generated, show all spells
|
||||
if (!mwo.IntValues.ContainsKey((int)IntValueKey.MaterialType))
|
||||
goto ShowSpell;
|
||||
|
||||
// Always show Minor/Major/Epic Impen
|
||||
if (spell.Name.Contains("Minor Impenetrability") || spell.Name.Contains("Major Impenetrability") || spell.Name.Contains("Epic Impenetrability") || spell.Name.Contains("Legendary Impenetrability"))
|
||||
goto ShowSpell;
|
||||
|
||||
// Always show trinket spells
|
||||
if (spell.Name.Contains("Augmented"))
|
||||
goto ShowSpell;
|
||||
|
||||
if (mwo.Values(IntValueKey.ResistMagic, 0) != 0)
|
||||
{
|
||||
// Show banes and impen on unenchantable equipment
|
||||
if (spell.Name.Contains(" Bane") || spell.Name.Contains("Impen") || spell.Name.StartsWith("Brogard"))
|
||||
goto ShowSpell;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hide banes and impen on enchantable equipment
|
||||
if (spell.Name.Contains(" Bane") || spell.Name.Contains("Impen") || spell.Name.StartsWith("Brogard"))
|
||||
continue;
|
||||
}
|
||||
|
||||
//Debug.WriteToChat(spellById.Name + ", Difficulty: " + spellById.Difficulty + ", Family: " + spellById.Family + ", Generation: " + spellById.Generation + ", Type: " + spellById.Type + ", " + spellById.Unknown1 + " " + spellById.Unknown2 + " " + spellById.Unknown3 + " " + spellById.Unknown4 + " " + spellById.Unknown5 + " " + spellById.Unknown6 + " " + spellById.Unknown7 + " " + spellById.Unknown8 + " " + spellById.Unknown9 + " " + spellById.Unknown10);
|
||||
// <{Mag-Tools}>: Major Coordination, Difficulty: 15, Family: 267, Generation: 1, Type: 1, 0 1 1 2572 -2.07525870829232E+20 0 0 0 0 0
|
||||
// <{Mag-Tools}>: Epic Magic Resistance, Difficulty: 20, Family: 299, Generation: 1, Type: 1, 0 1 1 4704 -2.07525870829232E+20 0 0 0 0 0
|
||||
// <{Mag-Tools}>: Epic Life Magic Aptitude, Difficulty: 20, Family: 357, Generation: 1, Type: 1, 0 1 1 4700 -2.07525870829232E+20 0 0 0 0 0
|
||||
// <{Mag-Tools}>: Epic Endurance, Difficulty: 20, Family: 263, Generation: 1, Type: 1, 0 1 1 4226 -2.07525870829232E+20 0 0 0 0 0
|
||||
// <{Mag-Tools}>: Essence Glutton, Difficulty: 30, Family: 279, Generation: 1, Type: 1, 0 0 1 2666 -2.07525870829232E+20 0 0 0 0 0
|
||||
|
||||
// <{Mag-Tools}>: Might of the Lugians, Difficulty: 300, Family: 1, Generation: 1, Type: 1, 0 0 1 2087 -2.07525870829232E+20 0 0 0 0 0
|
||||
// <{Mag-Tools}>: Executor's Blessing, Difficulty: 300, Family: 115, Generation: 1, Type: 1, 0 0 1 2053 -2.07525870829232E+20 0 0 0 0 0
|
||||
// <{Mag-Tools}>: Regeneration Other Incantation, Difficulty: 400, Family: 93, Generation: 1, Type: 1, 5 0.25 1 3982 -2.07525870829232E+20 0 0 0 0 0
|
||||
|
||||
// Focusing stone
|
||||
// <{Mag-Tools}>: Brilliance, Difficulty: 250, Family: 15, Generation: 1, Type: 1, 5 0.25 1 2348 -2.07525870829232E+20 0 0 0 0 0
|
||||
// <{Mag-Tools}>: Concentration, Difficulty: 100, Family: 13, Generation: 1, Type: 1, 0 0 1 2347 -2.07525870829232E+20 0 0 0 0 0
|
||||
// <{Mag-Tools}>: Malediction, Difficulty: 50, Family: 284, Generation: 1, Type: 1, 0 0 1 2346 -2.07525870829232E+20 0 0 0 0 0
|
||||
|
||||
// Weapon buffs
|
||||
// <{Mag-Tools}>: Elysa's Sight, Difficulty: 300, Family: 152, Generation: 1, Type: 1, 25 0 1 2106 -2.07525870829232E+20 0 0 0 0 0 (Attack Skill)
|
||||
// <{Mag-Tools}>: Infected Caress, Difficulty: 300, Family: 154, Generation: 1, Type: 1, 25 0 1 2096 -2.07525870829232E+20 0 0 0 0 0 (Damage)
|
||||
// <{Mag-Tools}>: Infected Spirit Caress, Difficulty: 300, Family: 154, Generation: 1, Type: 1, 25 0 1 3259 -2.07525870829232E+20 0 0 0 0 0 (Damage)
|
||||
// <{Mag-Tools}>: Cragstone's Will, Difficulty: 300, Family: 156, Generation: 1, Type: 1, 25 0 1 2101 -2.07525870829232E+20 0 0 0 0 0 (Defense)
|
||||
// <{Mag-Tools}>: Atlan's Alacrity, Difficulty: 300, Family: 158, Generation: 1, Type: 1, 25 0 1 2116 -2.07525870829232E+20 0 0 0 0 0 (Speed)
|
||||
// <{Mag-Tools}>: Mystic's Blessing, Difficulty: 300, Family: 195, Generation: 1, Type: 1, 25 0 1 2117 -2.07525870829232E+20 0 0 0 0 0 (Mana C)
|
||||
// <{Mag-Tools}>: Vision of the Hunter, Difficulty: 500, Family: 325, Generation: 1, Type: 1, 25 0 1 2968 -2.07525870829232E+20 0 0 0 0 0 (Damage Mod)
|
||||
|
||||
if ((spell.Family >= 152 && spell.Family <= 158) || spell.Family == 195 || spell.Family == 325)
|
||||
{
|
||||
// This is a weapon buff
|
||||
|
||||
// Lvl 6
|
||||
if (spell.Difficulty == 250)
|
||||
continue;
|
||||
|
||||
// Lvl 7
|
||||
if (spell.Difficulty == 300)
|
||||
goto ShowSpell;
|
||||
|
||||
// Lvl 8+
|
||||
if (spell.Difficulty >= 400)
|
||||
goto ShowSpell;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is not a weapon buff.
|
||||
|
||||
// Filter all 1-5 spells
|
||||
if (spell.Name.EndsWith(" I") || spell.Name.EndsWith(" II") || spell.Name.EndsWith(" III") || spell.Name.EndsWith(" IV") || spell.Name.EndsWith(" V"))
|
||||
continue;
|
||||
|
||||
// Filter 6's
|
||||
if (spell.Name.EndsWith(" VI"))
|
||||
continue;
|
||||
|
||||
// Filter 7's
|
||||
if (spell.Difficulty == 300)
|
||||
continue;
|
||||
|
||||
// Filter 8's
|
||||
if (spell.Name.Contains("Incantation"))
|
||||
continue;
|
||||
|
||||
ShowSpell:
|
||||
|
||||
sb.Append(", " + spell.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Wield Lvl 180
|
||||
if (mwo.Values(IntValueKey.WieldDifficulty) > 0)
|
||||
{
|
||||
// I don't quite understand this.
|
||||
if (mwo.Values(IntValueKey.WieldRequirements) == 7 && mwo.Values(IntValueKey.WieldSkillType) == 1)
|
||||
sb.Append(", Wield Lvl " + mwo.Values(IntValueKey.WieldDifficulty));
|
||||
else
|
||||
{
|
||||
if (Dictionaries.SkillInfo.ContainsKey(mwo.Values(IntValueKey.WieldSkillType)))
|
||||
sb.Append(", " + Dictionaries.SkillInfo[mwo.Values(IntValueKey.WieldSkillType)] + " " + mwo.Values(IntValueKey.WieldDifficulty));
|
||||
else
|
||||
sb.Append(", Unknown skill: " + mwo.Values(IntValueKey.WieldSkillType) + " " + mwo.Values(IntValueKey.WieldDifficulty));
|
||||
}
|
||||
}
|
||||
|
||||
// Summoning Gem
|
||||
if (mwo.Values((IntValueKey)369) > 0)
|
||||
sb.Append(", Lvl " + mwo.Values((IntValueKey)369));
|
||||
|
||||
// Melee Defense 300 to Activate
|
||||
// If the activation is lower than the wield requirement, don't show it.
|
||||
if (mwo.Values(IntValueKey.ItemSkillLevelLimit) > 0 && (mwo.Values(IntValueKey.WieldSkillType) != mwo.Values(IntValueKey.AppraisalItemSkill) || mwo.Values(IntValueKey.WieldDifficulty) < mwo.Values(IntValueKey.ItemSkillLevelLimit)))
|
||||
{
|
||||
if (Dictionaries.SkillInfo.ContainsKey(mwo.Values(IntValueKey.AppraisalItemSkill)))
|
||||
sb.Append(", " + Dictionaries.SkillInfo[mwo.Values(IntValueKey.AppraisalItemSkill)] + " " + mwo.Values(IntValueKey.ItemSkillLevelLimit) + " to Activate");
|
||||
else
|
||||
sb.Append(", Unknown skill: " + mwo.Values(IntValueKey.AppraisalItemSkill) + " " + mwo.Values(IntValueKey.ItemSkillLevelLimit) + " to Activate");
|
||||
}
|
||||
|
||||
// Summoning Gem
|
||||
if (mwo.Values((IntValueKey)366) > 0 && mwo.Values((IntValueKey)367) > 0)
|
||||
{
|
||||
if (Dictionaries.SkillInfo.ContainsKey(mwo.Values((IntValueKey)366)))
|
||||
sb.Append(", " + Dictionaries.SkillInfo[mwo.Values((IntValueKey)366)] + " " + mwo.Values((IntValueKey)367));
|
||||
else
|
||||
sb.Append(", Unknown skill: " + mwo.Values((IntValueKey)366) + " " + mwo.Values((IntValueKey)367));
|
||||
}
|
||||
|
||||
// Summoning Gem
|
||||
if (mwo.Values((IntValueKey)368) > 0 && mwo.Values((IntValueKey)367) > 0)
|
||||
{
|
||||
if (Dictionaries.SkillInfo.ContainsKey(mwo.Values((IntValueKey)368)))
|
||||
sb.Append(", Spec " + Dictionaries.SkillInfo[mwo.Values((IntValueKey)368)] + " " + mwo.Values((IntValueKey)367));
|
||||
else
|
||||
sb.Append(", Unknown skill spec: " + mwo.Values((IntValueKey)368) + " " + mwo.Values((IntValueKey)367));
|
||||
}
|
||||
|
||||
if (mwo.Values(IntValueKey.ItemDifficulty) > 0)
|
||||
sb.Append(", Diff " + mwo.Values(IntValueKey.ItemDifficulty));
|
||||
|
||||
if (mwo.ObjectClass == (int)ObjectClass.Salvage)
|
||||
{
|
||||
if (mwo.Values(DoubleValueKey.SalvageWorkmanship_Decal) > 0)
|
||||
sb.Append(", Work " + mwo.Values(DoubleValueKey.SalvageWorkmanship_Decal).ToString("N2"));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mwo.Values(IntValueKey.ItemWorkmanship) > 0 && mwo.Values(IntValueKey.NumTimesTinkered) != 10) // Don't show the work if its already 10 tinked.
|
||||
sb.Append(", Craft " + mwo.Values(IntValueKey.ItemWorkmanship));
|
||||
}
|
||||
|
||||
if (mwo.ObjectClass == (int)ObjectClass.Armor && mwo.Values(IntValueKey.ResistMagic, 0) != 0)
|
||||
{
|
||||
sb.Append(", [" +
|
||||
mwo.Values(DoubleValueKey.SlashProt_Decal).ToString("N1") + "/" +
|
||||
mwo.Values(DoubleValueKey.PierceProt_Decal).ToString("N1") + "/" +
|
||||
mwo.Values(DoubleValueKey.BludgeonProt_Decal).ToString("N1") + "/" +
|
||||
mwo.Values(DoubleValueKey.ColdProt_Decal).ToString("N1") + "/" +
|
||||
mwo.Values(DoubleValueKey.FireProt_Decal).ToString("N1") + "/" +
|
||||
mwo.Values(DoubleValueKey.AcidProt_Decal).ToString("N1") + "/" +
|
||||
mwo.Values(DoubleValueKey.LightningProt_Decal).ToString("N1") + "]");
|
||||
}
|
||||
|
||||
if (showValueAndBurden)
|
||||
{
|
||||
if (mwo.Values(IntValueKey.Value) > 0)
|
||||
sb.Append(", Value " + String.Format("{0:n0}", mwo.Values(IntValueKey.Value)));
|
||||
|
||||
if (mwo.Values(IntValueKey.EncumbranceVal) > 0)
|
||||
sb.Append(", BU " + mwo.Values(IntValueKey.EncumbranceVal));
|
||||
}
|
||||
|
||||
if (mwo.TotalRating > 0)
|
||||
{
|
||||
sb.Append(", [");
|
||||
bool first = true;
|
||||
if (mwo.DamRating > 0) { sb.Append("D " + mwo.DamRating); first = false; }
|
||||
if (mwo.DamResistRating > 0) { if (!first) sb.Append(", "); sb.Append("DR " + mwo.DamResistRating); first = false; }
|
||||
if (mwo.CritRating > 0) { if (!first) sb.Append(", "); sb.Append("C " + mwo.CritRating); first = false; }
|
||||
if (mwo.CritDamRating > 0) { if (!first) sb.Append(", "); sb.Append("CD " + mwo.CritDamRating); first = false; }
|
||||
if (mwo.CritResistRating > 0) { if (!first) sb.Append(", "); sb.Append("CR " + mwo.CritResistRating); first = false; }
|
||||
if (mwo.CritDamResistRating > 0) { if (!first) sb.Append(", "); sb.Append("CDR " + mwo.CritDamResistRating); first = false; }
|
||||
if (mwo.HealBoostRating > 0) { if (!first) sb.Append(", "); sb.Append("HB " + mwo.HealBoostRating); first = false; }
|
||||
if (mwo.VitalityRating > 0) { if (!first) sb.Append(", "); sb.Append("V " + mwo.VitalityRating); first = false; }
|
||||
sb.Append("]");
|
||||
}
|
||||
|
||||
if (mwo.ObjectClass == (int)ObjectClass.Misc && mwo.Name.Contains("Keyring"))
|
||||
sb.Append(", Keys: " + mwo.Values(IntValueKey.NumKeys) + ", Uses: " + mwo.Values(IntValueKey.Structure));
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
388
Shared/MyWorldObject.cs
Normal file
388
Shared/MyWorldObject.cs
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
using Mag.Shared.Constants;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a user defiend object to represent a Decal.Adapter.WorldObject.
|
||||
/// All properties will return -1 if the property isn't supported.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MyWorldObject
|
||||
{
|
||||
public bool HasIdData;
|
||||
public int Id;
|
||||
public int LastIdTime;
|
||||
public int ObjectClass;
|
||||
public int Icon;
|
||||
|
||||
public SerializableDictionary<int, bool> BoolValues = new SerializableDictionary<int, bool>();
|
||||
public SerializableDictionary<int, double> DoubleValues = new SerializableDictionary<int, double>();
|
||||
public SerializableDictionary<int, int> IntValues = new SerializableDictionary<int, int>();
|
||||
public SerializableDictionary<int, string> StringValues = new SerializableDictionary<int, string>();
|
||||
|
||||
public List<int> ActiveSpells = new List<int>();
|
||||
public List<int> Spells = new List<int>();
|
||||
|
||||
public void Init(bool hasIdData, int id, int lastIdTime, int objectClass, int icon, IDictionary<int, bool> boolValues, IDictionary<int, double> doubleValues, IDictionary<int, int> intValues, IDictionary<int, string> stringValues, IList<int> activeSpells, IList<int> spells)
|
||||
{
|
||||
HasIdData = hasIdData;
|
||||
Id = id;
|
||||
LastIdTime = lastIdTime;
|
||||
ObjectClass = objectClass;
|
||||
Icon = icon;
|
||||
|
||||
AddTo(boolValues, doubleValues, intValues, stringValues);
|
||||
|
||||
ActiveSpells.Clear();
|
||||
foreach (var i in activeSpells)
|
||||
ActiveSpells.Add(i);
|
||||
|
||||
Spells.Clear();
|
||||
foreach (var i in spells)
|
||||
Spells.Add(i);
|
||||
}
|
||||
|
||||
public void AddTo(IDictionary<int, bool> boolValues, IDictionary<int, double> doubleValues, IDictionary<int, int> intValues, IDictionary<int, string> stringValues)
|
||||
{
|
||||
foreach (var kvp in boolValues)
|
||||
{
|
||||
if (boolValues.ContainsKey(kvp.Key))
|
||||
BoolValues[kvp.Key] = kvp.Value;
|
||||
else
|
||||
BoolValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
foreach (var kvp in doubleValues)
|
||||
{
|
||||
if (doubleValues.ContainsKey(kvp.Key))
|
||||
DoubleValues[kvp.Key] = kvp.Value;
|
||||
else
|
||||
DoubleValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
foreach (var kvp in intValues)
|
||||
{
|
||||
if (intValues.ContainsKey(kvp.Key))
|
||||
IntValues[kvp.Key] = kvp.Value;
|
||||
else
|
||||
IntValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
foreach (var kvp in stringValues)
|
||||
{
|
||||
if (stringValues.ContainsKey(kvp.Key))
|
||||
StringValues[kvp.Key] = kvp.Value;
|
||||
else
|
||||
StringValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Values(BoolValueKey key, bool defaultValue = false)
|
||||
{
|
||||
if (BoolValues.ContainsKey((int)key))
|
||||
return BoolValues[(int)key];
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public double Values(DoubleValueKey key, double defaultValue = 0)
|
||||
{
|
||||
if (DoubleValues.ContainsKey((int)key))
|
||||
return DoubleValues[(int)key];
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public int Values(IntValueKey key, int defaultValue = 0)
|
||||
{
|
||||
if (IntValues.ContainsKey((int)key))
|
||||
return IntValues[(int)key];
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public string Values(StringValueKey key, string defaultValue = null)
|
||||
{
|
||||
if (StringValues.ContainsKey((int)key))
|
||||
return StringValues[(int)key];
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
||||
public string Material { get { if (IntValues.ContainsKey(131)) return Dictionaries.MaterialInfo.ContainsKey(IntValues[131]) ? Dictionaries.MaterialInfo[IntValues[131]] : IntValues[131].ToString(CultureInfo.InvariantCulture); return null; } }
|
||||
|
||||
public string Name { get { return StringValues.ContainsKey(1) ? StringValues[1] : null; } }
|
||||
|
||||
|
||||
public string EquipSkill { get { if (IntValues.ContainsKey(218103840)) return Dictionaries.SkillInfo.ContainsKey(IntValues[218103840]) ? Dictionaries.SkillInfo[IntValues[218103840]] : IntValues[218103840].ToString(CultureInfo.InvariantCulture); return null; } }
|
||||
|
||||
public string Mastery { get { if (IntValues.ContainsKey(353)) return Dictionaries.MasteryInfo.ContainsKey(IntValues[353]) ? Dictionaries.MasteryInfo[IntValues[353]] : IntValues[353].ToString(CultureInfo.InvariantCulture); return null; } }
|
||||
|
||||
public string ItemSet { get { if (IntValues.ContainsKey(265)) return Dictionaries.AttributeSetInfo.ContainsKey(IntValues[265]) ? Dictionaries.AttributeSetInfo[IntValues[265]] : IntValues[265].ToString(CultureInfo.InvariantCulture); return null; } }
|
||||
|
||||
|
||||
public int ArmorLevel { get { return IntValues.ContainsKey(28) ? IntValues[28] : -1; } }
|
||||
|
||||
public string Imbue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IntValues.ContainsKey(179) || IntValues[179] == 0) return null;
|
||||
|
||||
string retVal = String.Empty;
|
||||
if ((IntValues[179] & 1) == 1) retVal += " CS";
|
||||
if ((IntValues[179] & 2) == 2) retVal += " CB";
|
||||
if ((IntValues[179] & 4) == 4) retVal += " AR";
|
||||
if ((IntValues[179] & 8) == 8) retVal += " SlashRend";
|
||||
if ((IntValues[179] & 16) == 16) retVal += " PierceRend";
|
||||
if ((IntValues[179] & 32) == 32) retVal += " BludgeRend";
|
||||
if ((IntValues[179] & 64) == 64) retVal += " AcidRend";
|
||||
if ((IntValues[179] & 128) == 128) retVal += " FrostRend";
|
||||
if ((IntValues[179] & 256) == 256) retVal += " LightRend";
|
||||
if ((IntValues[179] & 512) == 512) retVal += " FireRend";
|
||||
if ((IntValues[179] & 1024) == 1024) retVal += " MeleeImbue";
|
||||
if ((IntValues[179] & 4096) == 4096) retVal += " MagicImbue";
|
||||
if ((IntValues[179] & 8192) == 8192) retVal += " Hematited";
|
||||
if ((IntValues[179] & 536870912) == 536870912) retVal += " MagicAbsorb";
|
||||
retVal = retVal.Trim();
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
public int Tinks { get { return IntValues.ContainsKey(171) ? IntValues[171] : -1; } }
|
||||
|
||||
|
||||
public int MaxDamage { get { return IntValues.ContainsKey(218103842) ? IntValues[218103842] : -1; } }
|
||||
|
||||
public int ElementalDmgBonus { get { return IntValues.ContainsKey(204) ? IntValues[204] : -1; } }
|
||||
|
||||
public Double Variance { get { return DoubleValues.ContainsKey(167772171) ? DoubleValues[167772171] : -1; } }
|
||||
|
||||
public Double DamageBonus { get { return DoubleValues.ContainsKey(167772174) ? DoubleValues[167772174] : -1; } }
|
||||
|
||||
public Double ElementalDamageVersusMonsters { get { return DoubleValues.ContainsKey(152) ? DoubleValues[152] : -1; } }
|
||||
|
||||
public Double AttackBonus { get { return DoubleValues.ContainsKey(167772172) ? DoubleValues[167772172] : -1; } }
|
||||
|
||||
public Double MeleeDefenseBonus { get { return DoubleValues.ContainsKey(29) ? DoubleValues[29] : -1; } }
|
||||
|
||||
public Double MagicDBonus { get { return DoubleValues.ContainsKey(150) ? DoubleValues[150] : -1; } }
|
||||
|
||||
public Double MissileDBonus { get { return DoubleValues.ContainsKey(149) ? DoubleValues[149] : -1; } }
|
||||
|
||||
public Double ManaCBonus { get { return DoubleValues.ContainsKey(144) ? DoubleValues[144] : -1; } }
|
||||
|
||||
|
||||
public int WieldLevel { get { if (IntValues.ContainsKey(160) && IntValues[160] > 0 && IntValues.ContainsKey(158) && IntValues[158] == 7 && IntValues.ContainsKey(159) && IntValues[159] == 1) return IntValues[160]; return -1; } }
|
||||
|
||||
public int SkillLevel { get { if (IntValues.ContainsKey(160) && IntValues[160] > 0 && (!IntValues.ContainsKey(158) || IntValues[158] != 7) && IntValues.ContainsKey(159)) return IntValues[160]; return -1; } }
|
||||
|
||||
|
||||
public int LoreRequirement { get { return IntValues.ContainsKey(109) ? IntValues[109] : -1; } }
|
||||
|
||||
public Double SalvageWorkmanship { get { return DoubleValues.ContainsKey(167772169) ? DoubleValues[167772169] : -1; } }
|
||||
|
||||
public int Workmanship { get { return IntValues.ContainsKey(105) ? IntValues[105] : -1; } }
|
||||
|
||||
public int Value { get { return IntValues.ContainsKey(19) ? IntValues[19] : -1; } }
|
||||
|
||||
public int Burden { get { return IntValues.ContainsKey(5) ? IntValues[5] : -1; } }
|
||||
|
||||
|
||||
public int DamRating { get { return IntValues.ContainsKey(370) ? IntValues[370] : -1; } }
|
||||
|
||||
public int DamResistRating { get { return IntValues.ContainsKey(371) ? IntValues[371] : -1; } }
|
||||
|
||||
public int CritRating { get { return IntValues.ContainsKey(372) ? IntValues[372] : -1; } }
|
||||
|
||||
public int CritResistRating { get { return IntValues.ContainsKey(373) ? IntValues[373] : -1; } }
|
||||
|
||||
public int CritDamRating { get { return IntValues.ContainsKey(374) ? IntValues[374] : -1; } }
|
||||
|
||||
public int CritDamResistRating { get { return IntValues.ContainsKey(375) ? IntValues[375] : -1; } }
|
||||
|
||||
public int HealBoostRating { get { return IntValues.ContainsKey(376) ? IntValues[376] : -1; } }
|
||||
|
||||
public int VitalityRating { get { return IntValues.ContainsKey(379) ? IntValues[379] : -1; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sum of all the ratings found on this item, or -1 if no ratings exist.
|
||||
/// </summary>
|
||||
public int TotalRating
|
||||
{
|
||||
get
|
||||
{
|
||||
if (DamRating == -1 && DamResistRating == -1 && CritRating == -1 && CritResistRating == -1 && CritDamRating == -1 && CritDamResistRating == -1 && HealBoostRating == -1 && VitalityRating == -1)
|
||||
return -1;
|
||||
|
||||
return Math.Max(DamRating, 0) + Math.Max(DamResistRating, 0) + Math.Max(CritRating, 0) + Math.Max(CritResistRating, 0) + Math.Max(CritDamRating, 0) + Math.Max(CritDamResistRating, 0) + Math.Max(HealBoostRating, 0) + Math.Max(VitalityRating, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This will take the current AmorLevel of the item, subtract any buffs, subtract tinks as 20 AL each (not including imbue), and add any impen cantrips.
|
||||
/// </summary>
|
||||
public int CalcedStartingArmorLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
int armorFromTinks = 0;
|
||||
int armorFromBuffs = 0;
|
||||
|
||||
if (Tinks > 0 && ArmorLevel > 0)
|
||||
armorFromTinks = (Imbue != null) ? (Tinks - 1) * 20 : Tinks * 20; // This assumes each tink adds an amor level of 20
|
||||
|
||||
if ((!IntValues.ContainsKey(131) || IntValues[131] == 0) && ArmorLevel > 0) // If this item has no material, its not a loot gen, assume its a quest item and subtract 200 al
|
||||
armorFromTinks = 200;
|
||||
|
||||
foreach (int spell in ActiveSpells)
|
||||
{
|
||||
foreach (var effect in Dictionaries.LongValueKeySpellEffects)
|
||||
{
|
||||
if (spell == effect.Key && effect.Value.Key == 28)
|
||||
armorFromBuffs += effect.Value.Change;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int spell in Spells)
|
||||
{
|
||||
foreach (var effect in Dictionaries.LongValueKeySpellEffects)
|
||||
{
|
||||
if (spell == effect.Key && effect.Value.Key == 28)
|
||||
armorFromBuffs -= effect.Value.Bonus;
|
||||
}
|
||||
}
|
||||
|
||||
return ArmorLevel - armorFromTinks - armorFromBuffs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will take into account Variance, MaxDamage and Tinks of a melee weapon and determine what its optimal 10 tinked DamageOverTime is.
|
||||
/// </summary>
|
||||
public double CalcedBuffedTinkedDoT
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!DoubleValues.ContainsKey(167772171) || !IntValues.ContainsKey(218103842))
|
||||
return -1;
|
||||
|
||||
double variance = DoubleValues.ContainsKey(167772171) ? DoubleValues[167772171] : 0;
|
||||
int maxDamage = GetBuffedIntValueKey(218103842);
|
||||
|
||||
int numberOfTinksLeft = Math.Max(10 - Math.Max(Tinks, 0), 0);
|
||||
|
||||
if (!IntValues.ContainsKey(179) || IntValues[179] == 0)
|
||||
numberOfTinksLeft--; // Factor in an imbue tink
|
||||
|
||||
// If this is not a loot generated item, it can't be tinked
|
||||
if (!IntValues.ContainsKey(131) || IntValues[131] == 0)
|
||||
numberOfTinksLeft = 0;
|
||||
|
||||
for (int i = 1; i <= numberOfTinksLeft; i++)
|
||||
{
|
||||
double ironTinkDoT = CalculateDamageOverTime(maxDamage + 24 + 1, variance);
|
||||
double graniteTinkDoT = CalculateDamageOverTime(maxDamage + 24, variance * .8);
|
||||
|
||||
if (ironTinkDoT >= graniteTinkDoT)
|
||||
maxDamage++;
|
||||
else
|
||||
variance *= .8;
|
||||
}
|
||||
|
||||
return CalculateDamageOverTime(maxDamage + 24, variance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetBuffedIntValueKey(LongValueKey.MaxDamage) + (((GetBuffedDoubleValueKey(DoubleValueKey.DamageBonus) - 1) * 100) / 3) + GetBuffedIntValueKey(LongValueKey.ElementalDmgBonus);
|
||||
/// </summary>
|
||||
public double CalcedBuffedMissileDamage { get { if (!IntValues.ContainsKey(218103842) || !DoubleValues.ContainsKey(167772174) || !IntValues.ContainsKey(204)) return -1; return GetBuffedIntValueKey(218103842) + (((GetBuffedDoubleValueKey(167772174) - 1) * 100) / 3) + GetBuffedIntValueKey(204); } }
|
||||
|
||||
public double BuffedElementalDamageVersusMonsters { get { return GetBuffedDoubleValueKey(152, -1); } }
|
||||
|
||||
public double BuffedAttackBonus { get { return GetBuffedDoubleValueKey(167772172, -1); } }
|
||||
|
||||
public double BuffedMeleeDefenseBonus { get { return GetBuffedDoubleValueKey(29, -1); } }
|
||||
|
||||
public double BuffedManaCBonus { get { return GetBuffedDoubleValueKey(144, -1); } }
|
||||
|
||||
public int GetBuffedIntValueKey(int key, int defaultValue = 0)
|
||||
{
|
||||
if (!IntValues.ContainsKey(key))
|
||||
return defaultValue;
|
||||
|
||||
int value = IntValues[key];
|
||||
|
||||
foreach (int spell in ActiveSpells)
|
||||
{
|
||||
if (Dictionaries.LongValueKeySpellEffects.ContainsKey(spell) && Dictionaries.LongValueKeySpellEffects[spell].Key == key)
|
||||
value -= Dictionaries.LongValueKeySpellEffects[spell].Change;
|
||||
}
|
||||
|
||||
foreach (int spell in Spells)
|
||||
{
|
||||
if (Dictionaries.LongValueKeySpellEffects.ContainsKey(spell) && Dictionaries.LongValueKeySpellEffects[spell].Key == key)
|
||||
value += Dictionaries.LongValueKeySpellEffects[spell].Bonus;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public double GetBuffedDoubleValueKey(int key, double defaultValue = 0)
|
||||
{
|
||||
if (!DoubleValues.ContainsKey(key))
|
||||
return defaultValue;
|
||||
|
||||
double value = DoubleValues[key];
|
||||
|
||||
foreach (int spell in ActiveSpells)
|
||||
{
|
||||
if (Dictionaries.DoubleValueKeySpellEffects.ContainsKey(spell) && Dictionaries.DoubleValueKeySpellEffects[spell].Key == key)
|
||||
{
|
||||
if (Math.Abs(Dictionaries.DoubleValueKeySpellEffects[spell].Change - 1) < Double.Epsilon)
|
||||
value /= Dictionaries.DoubleValueKeySpellEffects[spell].Change;
|
||||
else
|
||||
value -= Dictionaries.DoubleValueKeySpellEffects[spell].Change;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int spell in Spells)
|
||||
{
|
||||
if (Dictionaries.DoubleValueKeySpellEffects.ContainsKey(spell) && Dictionaries.DoubleValueKeySpellEffects[spell].Key == key && Math.Abs(Dictionaries.DoubleValueKeySpellEffects[spell].Bonus - 0) > Double.Epsilon)
|
||||
{
|
||||
if (Math.Abs(Dictionaries.DoubleValueKeySpellEffects[spell].Change - 1) < Double.Epsilon)
|
||||
value *= Dictionaries.DoubleValueKeySpellEffects[spell].Bonus;
|
||||
else
|
||||
value += Dictionaries.DoubleValueKeySpellEffects[spell].Bonus;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// maxDamage * ((1 - critChance) * (2 - variance) / 2 + (critChance * critMultiplier));
|
||||
/// </summary>
|
||||
/// <param name="maxDamage"></param>
|
||||
/// <param name="variance"></param>
|
||||
/// <param name="critChance"></param>
|
||||
/// <param name="critMultiplier"></param>
|
||||
/// <returns></returns>
|
||||
public static double CalculateDamageOverTime(int maxDamage, double variance, double critChance = .1, double critMultiplier = 2)
|
||||
{
|
||||
return maxDamage * ((1 - critChance) * (2 - variance) / 2 + (critChance * critMultiplier));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Shared/MyWorldObjectCreator.cs
Normal file
61
Shared/MyWorldObjectCreator.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Decal.Adapter.Wrappers;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
public static class MyWorldObjectCreator
|
||||
{
|
||||
public static MyWorldObject Create(WorldObject wo)
|
||||
{
|
||||
MyWorldObject mwo = new MyWorldObject();
|
||||
|
||||
Dictionary<int, bool> boolValues = new Dictionary<int,bool>();
|
||||
Dictionary<int, double> doubleValues = new Dictionary<int,double>();
|
||||
Dictionary<int, int> intValues = new Dictionary<int, int>();
|
||||
Dictionary<int, string> stringValues = new Dictionary<int,string>();
|
||||
List<int> activeSpells = new List<int>();
|
||||
List<int> spells = new List<int>();
|
||||
|
||||
foreach (var key in wo.BoolKeys)
|
||||
boolValues.Add(key, wo.Values((BoolValueKey)key));
|
||||
|
||||
foreach (var key in wo.DoubleKeys)
|
||||
doubleValues.Add(key, wo.Values((DoubleValueKey)key));
|
||||
|
||||
foreach (var key in wo.LongKeys)
|
||||
intValues.Add(key, wo.Values((LongValueKey)key));
|
||||
|
||||
foreach (var key in wo.StringKeys)
|
||||
stringValues.Add(key, wo.Values((StringValueKey)key));
|
||||
|
||||
for (int i = 0 ; i < wo.ActiveSpellCount ; i++)
|
||||
activeSpells.Add(wo.ActiveSpell(i));
|
||||
|
||||
for (int i = 0; i < wo.SpellCount; i++)
|
||||
spells.Add(wo.Spell(i));
|
||||
|
||||
mwo.Init(wo.HasIdData, wo.Id, wo.LastIdTime, (int)wo.ObjectClass, wo.Icon, boolValues, doubleValues, intValues, stringValues, activeSpells, spells);
|
||||
|
||||
return mwo;
|
||||
}
|
||||
|
||||
public static MyWorldObject Combine(MyWorldObject older, WorldObject newer)
|
||||
{
|
||||
// Always repair missing icons from old JSON files (plugin upgrades)
|
||||
if (older.Icon == 0 && newer.Icon != 0)
|
||||
{
|
||||
older.Icon = newer.Icon;
|
||||
}
|
||||
|
||||
if (!older.HasIdData || newer.HasIdData)
|
||||
return Create(newer);
|
||||
|
||||
MyWorldObject mwo = Create(newer);
|
||||
|
||||
older.AddTo(mwo.BoolValues, mwo.DoubleValues, mwo.IntValues, mwo.StringValues);
|
||||
|
||||
return older;
|
||||
}
|
||||
}
|
||||
}
|
||||
165
Shared/ObjectClass.cs
Normal file
165
Shared/ObjectClass.cs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
using System;
|
||||
|
||||
using Mag.Shared.Constants;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
public enum ObjectClass
|
||||
{
|
||||
Unknown = 0,
|
||||
MeleeWeapon = 1,
|
||||
Armor = 2,
|
||||
Clothing = 3,
|
||||
Jewelry = 4,
|
||||
Monster = 5,
|
||||
Food = 6,
|
||||
Money = 7,
|
||||
Misc = 8,
|
||||
MissileWeapon = 9,
|
||||
Container = 10,
|
||||
Gem = 11,
|
||||
SpellComponent = 12,
|
||||
Key = 13,
|
||||
Portal = 14,
|
||||
TradeNote = 15,
|
||||
ManaStone = 16,
|
||||
Plant = 17,
|
||||
BaseCooking = 18,
|
||||
BaseAlchemy = 19,
|
||||
BaseFletching = 20,
|
||||
CraftedCooking = 21,
|
||||
CraftedAlchemy = 22,
|
||||
CraftedFletching = 23,
|
||||
Player = 24,
|
||||
Vendor = 25,
|
||||
Door = 26,
|
||||
Corpse = 27,
|
||||
Lifestone = 28,
|
||||
HealingKit = 29,
|
||||
Lockpick = 30,
|
||||
WandStaffOrb = 31,
|
||||
Bundle = 32,
|
||||
Book = 33,
|
||||
Journal = 34,
|
||||
Sign = 35,
|
||||
Housing = 36,
|
||||
Npc = 37,
|
||||
Foci = 38,
|
||||
Salvage = 39,
|
||||
Ust = 40,
|
||||
Services = 41,
|
||||
Scroll = 42,
|
||||
NumObjectClasses = 43,
|
||||
}
|
||||
|
||||
public static class ObjectClassTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a decal specific IntValueKey to the actual IntValueKey.
|
||||
/// If this is not an IntValueKey, 0 will be returned.
|
||||
/// </summary>
|
||||
public static ObjectClass FromWeenieType(ItemType itemType, WeenieType weenieType)
|
||||
{
|
||||
var result = ObjectClass.Unknown;
|
||||
|
||||
if ((itemType & ItemType.MeleeWeapon) != 0)
|
||||
result = ObjectClass.MeleeWeapon;
|
||||
else if ((itemType & ItemType.Armor) != 0)
|
||||
result = ObjectClass.Armor;
|
||||
else if ((itemType & ItemType.Clothing) != 0)
|
||||
result = ObjectClass.Clothing;
|
||||
else if ((itemType & ItemType.Jewelry) != 0)
|
||||
result = ObjectClass.Jewelry;
|
||||
else if ((itemType & ItemType.Creature) != 0)
|
||||
result = ObjectClass.Monster;
|
||||
else if ((itemType & ItemType.Food) != 0)
|
||||
result = ObjectClass.Food;
|
||||
else if ((itemType & ItemType.Money) != 0)
|
||||
result = ObjectClass.Money;
|
||||
else if ((itemType & ItemType.Misc) != 0)
|
||||
result = ObjectClass.Misc;
|
||||
else if ((itemType & ItemType.MissileWeapon) != 0)
|
||||
result = ObjectClass.MissileWeapon;
|
||||
else if ((itemType & ItemType.Container) != 0)
|
||||
result = ObjectClass.Container;
|
||||
else if ((itemType & ItemType.Useless) != 0)
|
||||
result = ObjectClass.Bundle;
|
||||
else if ((itemType & ItemType.Gem) != 0)
|
||||
result = ObjectClass.Gem;
|
||||
else if ((itemType & ItemType.SpellComponents) != 0)
|
||||
result = ObjectClass.SpellComponent;
|
||||
else if ((itemType & ItemType.Key) != 0)
|
||||
result = ObjectClass.Key;
|
||||
else if ((itemType & ItemType.Caster) != 0)
|
||||
result = ObjectClass.WandStaffOrb;
|
||||
else if ((itemType & ItemType.Portal) != 0)
|
||||
result = ObjectClass.Portal;
|
||||
else if ((itemType & ItemType.PromissoryNote) != 0)
|
||||
result = ObjectClass.TradeNote;
|
||||
else if ((itemType & ItemType.ManaStone) != 0)
|
||||
result = ObjectClass.ManaStone;
|
||||
else if ((itemType & ItemType.Service) != 0)
|
||||
result = ObjectClass.Services;
|
||||
else if ((itemType & ItemType.MagicWieldable) != 0)
|
||||
result = ObjectClass.Plant;
|
||||
else if ((itemType & ItemType.CraftCookingBase) != 0)
|
||||
result = ObjectClass.BaseCooking;
|
||||
else if ((itemType & ItemType.CraftAlchemyBase) != 0)
|
||||
result = ObjectClass.BaseAlchemy;
|
||||
//else if ((itemType & ItemType.01000000)
|
||||
// result = ObjectClass.BaseFletching;
|
||||
else if ((itemType & ItemType.CraftFletchingBase) != 0)
|
||||
result = ObjectClass.CraftedCooking;
|
||||
else if ((itemType & ItemType.CraftAlchemyIntermediate) != 0)
|
||||
result = ObjectClass.CraftedAlchemy;
|
||||
else if ((itemType & ItemType.CraftFletchingIntermediate) != 0)
|
||||
result = ObjectClass.CraftedFletching;
|
||||
else if ((itemType & ItemType.TinkeringTool) != 0)
|
||||
result = ObjectClass.Ust;
|
||||
else if ((itemType & ItemType.TinkeringMaterial) != 0)
|
||||
result = ObjectClass.Salvage;
|
||||
|
||||
/*
|
||||
if (Behavior & 0x00000008)
|
||||
result = ObjectClass.Player;
|
||||
else if (Behavior & 0x00000200)
|
||||
result = ObjectClass.Vendor;
|
||||
else if (Behavior & 0x00001000)
|
||||
result = ObjectClass.Door;
|
||||
else if (Behavior & 0x00002000)
|
||||
result = ObjectClass.Corpse;
|
||||
else if (Behavior & 0x00004000)
|
||||
result = ObjectClass.Lifestone;
|
||||
else if (Behavior & 0x00008000)
|
||||
result = ObjectClass.Food;
|
||||
else if (Behavior & 0x00010000)
|
||||
result = ObjectClass.HealingKit;
|
||||
else if (Behavior & 0x00020000)
|
||||
result = ObjectClass.Lockpick;
|
||||
else if (Behavior & 0x00040000)
|
||||
result = ObjectClass.Portal;
|
||||
else if (Behavior & 0x00800000)
|
||||
result = ObjectClass.Foci;
|
||||
else if (Behavior & 0x00000001)
|
||||
result = ObjectClass.Container;
|
||||
*/
|
||||
|
||||
/*if (((itemType & ItemType.Writable) != 0) && (Behavior & 0x00000100) && result == ObjectClass.Unknown)
|
||||
{
|
||||
if (pCreate->m_Behavior & 0x00000002)
|
||||
result = ObjectClass.Journal;
|
||||
else if (pCreate->m_Behavior & 0x00000004)
|
||||
result = ObjectClass.Sign;
|
||||
else if (!(pCreate->m_Behavior & 0x0000000F))
|
||||
result = ObjectClass.Book;
|
||||
}*/
|
||||
|
||||
/*if (((itemType & ItemType.Writable) != 0) && ((GameDataFlags1 & 0x00400000) != 0))
|
||||
result = ObjectClass.Scroll;*/
|
||||
|
||||
//throw new Exception($"Unable to convert WeenieType {input} to an ObjectClass.");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
293
Shared/PostMessageTools.cs
Normal file
293
Shared/PostMessageTools.cs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using Decal.Adapter;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
public static class PostMessageTools
|
||||
{
|
||||
// http://msdn.microsoft.com/en-us/library/dd375731%28v=vs.85%29.aspx
|
||||
|
||||
private const byte VK_RETURN = 0x0D;
|
||||
private const byte VK_SHIFT = 0x10;
|
||||
private const byte VK_CONTROL = 0x11;
|
||||
private const byte VK_PAUSE = 0x13;
|
||||
private const byte VK_SPACE = 0x20;
|
||||
|
||||
private static byte ScanCode(char Char)
|
||||
{
|
||||
switch (char.ToLower(Char))
|
||||
{
|
||||
case 'a': return 0x1E;
|
||||
case 'b': return 0x30;
|
||||
case 'c': return 0x2E;
|
||||
case 'd': return 0x20;
|
||||
case 'e': return 0x12;
|
||||
case 'f': return 0x21;
|
||||
case 'g': return 0x22;
|
||||
case 'h': return 0x23;
|
||||
case 'i': return 0x17;
|
||||
case 'j': return 0x24;
|
||||
case 'k': return 0x25;
|
||||
case 'l': return 0x26;
|
||||
case 'm': return 0x32;
|
||||
case 'n': return 0x31;
|
||||
case 'o': return 0x18;
|
||||
case 'p': return 0x19;
|
||||
case 'q': return 0x10;
|
||||
case 'r': return 0x13;
|
||||
case 's': return 0x1F;
|
||||
case 't': return 0x14;
|
||||
case 'u': return 0x16;
|
||||
case 'v': return 0x2F;
|
||||
case 'w': return 0x11;
|
||||
case 'x': return 0x2D;
|
||||
case 'y': return 0x15;
|
||||
case 'z': return 0x2C;
|
||||
case '/': return 0x35;
|
||||
case ' ': return 0x39;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static byte CharCode(char Char)
|
||||
{
|
||||
switch (char.ToLower(Char))
|
||||
{
|
||||
case 'a': return 0x41;
|
||||
case 'b': return 0x42;
|
||||
case 'c': return 0x43;
|
||||
case 'd': return 0x44;
|
||||
case 'e': return 0x45;
|
||||
case 'f': return 0x46;
|
||||
case 'g': return 0x47;
|
||||
case 'h': return 0x48;
|
||||
case 'i': return 0x49;
|
||||
case 'j': return 0x4A;
|
||||
case 'k': return 0x4B;
|
||||
case 'l': return 0x4C;
|
||||
case 'm': return 0x4D;
|
||||
case 'n': return 0x4E;
|
||||
case 'o': return 0x4F;
|
||||
case 'p': return 0x50;
|
||||
case 'q': return 0x51;
|
||||
case 'r': return 0x52;
|
||||
case 's': return 0x53;
|
||||
case 't': return 0x54;
|
||||
case 'u': return 0x55;
|
||||
case 'v': return 0x56;
|
||||
case 'w': return 0x57;
|
||||
case 'x': return 0x58;
|
||||
case 'y': return 0x59;
|
||||
case 'z': return 0x5A;
|
||||
case '/': return 0xBF;
|
||||
case ' ': return 0x20;
|
||||
}
|
||||
return 0x20;
|
||||
}
|
||||
|
||||
public static void SendEnter()
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)VK_RETURN, (UIntPtr)0x001C0001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)VK_RETURN, (UIntPtr)0xC01C0001);
|
||||
}
|
||||
|
||||
public static void SendPause()
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)VK_PAUSE, (UIntPtr)0x00450001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)VK_PAUSE, (UIntPtr)0xC0450001);
|
||||
}
|
||||
|
||||
static Timer _spaceReleaseTimer;
|
||||
static DateTime _spaceSendTime;
|
||||
static int _spaceHoldTimeMilliseconds;
|
||||
static bool _spaceAddShift;
|
||||
static bool _spaceAddW;
|
||||
static bool _spaceAddZ;
|
||||
static bool _spaceAddX;
|
||||
static bool _spaceAddC;
|
||||
|
||||
public static void SendSpace(int msToHoldDown = 0, bool addShift = false, bool addW = false, bool addZ = false, bool addX = false, bool addC = false)
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)VK_SPACE, (UIntPtr)0x00390001);
|
||||
if (msToHoldDown == 0)
|
||||
{
|
||||
if (addShift) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)VK_SHIFT, (UIntPtr)0x002A0001);
|
||||
if (addW) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode('w'), (UIntPtr)0x00110001);
|
||||
if (addZ) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode('z'), (UIntPtr)0x002C0001);
|
||||
if (addX) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode('x'), (UIntPtr)0x002D0001);
|
||||
if (addC) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode('c'), (UIntPtr)0x002E0001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)VK_SPACE, (UIntPtr)0xC0390001);
|
||||
if (addW) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode('w'), (UIntPtr)0xC0110001);
|
||||
if (addZ) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode('z'), (UIntPtr)0xC02C0001);
|
||||
if (addX) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode('x'), (UIntPtr)0xC02D0001);
|
||||
if (addC) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode('c'), (UIntPtr)0xC02E0001);
|
||||
if (addShift) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)VK_SHIFT, (UIntPtr)0xC02A0001);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_spaceReleaseTimer == null)
|
||||
{
|
||||
_spaceReleaseTimer = new Timer();
|
||||
_spaceReleaseTimer.Tick += new EventHandler(SpaceReleaseTimer_Tick);
|
||||
_spaceReleaseTimer.Interval = 1;
|
||||
}
|
||||
|
||||
_spaceSendTime = DateTime.UtcNow;
|
||||
_spaceHoldTimeMilliseconds = msToHoldDown;
|
||||
_spaceAddShift = addShift;
|
||||
_spaceAddW = addW;
|
||||
_spaceAddZ = addZ;
|
||||
_spaceAddX = addX;
|
||||
_spaceAddC = addC;
|
||||
_spaceReleaseTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
static void SpaceReleaseTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (_spaceSendTime.AddMilliseconds(_spaceHoldTimeMilliseconds) <= DateTime.UtcNow)
|
||||
{
|
||||
_spaceReleaseTimer.Stop();
|
||||
if (_spaceAddShift) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)VK_SHIFT, (UIntPtr)0x002A0001);
|
||||
if (_spaceAddW) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode('w'), (UIntPtr)0x00110001);
|
||||
if (_spaceAddZ) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode('z'), (UIntPtr)0x002C0001);
|
||||
if (_spaceAddX) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode('x'), (UIntPtr)0x002D0001);
|
||||
if (_spaceAddC) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode('c'), (UIntPtr)0x002E0001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)VK_SPACE, (UIntPtr)0xC0390001);
|
||||
if (_spaceAddW) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode('w'), (UIntPtr)0xC0110001);
|
||||
if (_spaceAddZ) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode('z'), (UIntPtr)0xC02C0001);
|
||||
if (_spaceAddX) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode('x'), (UIntPtr)0xC02D0001);
|
||||
if (_spaceAddC) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode('c'), (UIntPtr)0xC02E0001);
|
||||
if (_spaceAddShift) User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)VK_SHIFT, (UIntPtr)0xC02A0001);
|
||||
}
|
||||
}
|
||||
|
||||
static Timer _movementReleaseTimer;
|
||||
static DateTime _movementSendTime;
|
||||
static int _movementHoldTimeMilliseconds;
|
||||
static char _movementKey;
|
||||
|
||||
public static void SendMovement(char ch, int msToHoldDown = 0)
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode(ch), (UIntPtr)(0x00000001 + ScanCode(ch) * 0x10000));
|
||||
if (msToHoldDown == 0)
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode(ch), (UIntPtr)(0xC0000001 + ScanCode(ch) * 0x10000));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_movementReleaseTimer == null)
|
||||
{
|
||||
_movementReleaseTimer = new Timer();
|
||||
_movementReleaseTimer.Tick += new EventHandler(MovementReleaseTimer_Tick);
|
||||
_movementReleaseTimer.Interval = 1;
|
||||
}
|
||||
|
||||
_movementSendTime = DateTime.Now;
|
||||
_movementHoldTimeMilliseconds = msToHoldDown;
|
||||
_movementKey = ch;
|
||||
_movementReleaseTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
static void MovementReleaseTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (_movementSendTime.AddMilliseconds(_movementHoldTimeMilliseconds) <= DateTime.Now)
|
||||
{
|
||||
_movementReleaseTimer.Stop();
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode(_movementKey), (UIntPtr)(0xC0000001 + ScanCode(_movementKey) * 0x10000));
|
||||
}
|
||||
}
|
||||
|
||||
public static void SendCntrl(char ch)
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)VK_CONTROL, (UIntPtr)0x001D0001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)CharCode(ch), (UIntPtr)0x00100001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)CharCode(ch), (UIntPtr)0xC0100001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)VK_CONTROL, (UIntPtr)0xC01D0001);
|
||||
}
|
||||
|
||||
public static void SendAltF4()
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_DESTROY, new IntPtr(0), new UIntPtr(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens/Closes fellowship view
|
||||
/// </summary>
|
||||
public static void SendF4()
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)0x00000073, (UIntPtr)0x003E0001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)0x00000073, (UIntPtr)0xC03E0001);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens/Closes main pack view
|
||||
/// </summary>
|
||||
public static void SendF12()
|
||||
{
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)0x0000007B, (UIntPtr)0x00580001);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)0x0000007B, (UIntPtr)0xC0580001);
|
||||
}
|
||||
|
||||
public static void SendMsg(string msg)
|
||||
{
|
||||
foreach (char ch in msg)
|
||||
{
|
||||
byte code = CharCode(ch);
|
||||
uint lparam = (uint)((ScanCode(ch) << 0x10) | 1);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYDOWN, (IntPtr)code, (UIntPtr)(lparam));
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_KEYUP, (IntPtr)code, (UIntPtr)(0xC0000000 | lparam));
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClickOK()
|
||||
{
|
||||
User32.RECT rect = new User32.RECT();
|
||||
|
||||
User32.GetWindowRect(CoreManager.Current.Decal.Hwnd, ref rect);
|
||||
|
||||
// The reason why we click at both of these positions is some clients will be running windowed, and some windowless. This will hit both locations
|
||||
SendMouseClick(rect.Width / 2, rect.Height / 2 + 18);
|
||||
SendMouseClick(rect.Width / 2, rect.Height / 2 + 25);
|
||||
SendMouseClick(rect.Width / 2, rect.Height / 2 + 31);
|
||||
}
|
||||
|
||||
public static void ClickYes()
|
||||
{
|
||||
User32.RECT rect = new User32.RECT();
|
||||
|
||||
User32.GetWindowRect(CoreManager.Current.Decal.Hwnd, ref rect);
|
||||
|
||||
// 800x600 +32 works, +33 does not work on single/double/tripple line boxes
|
||||
// 1600x1200 +31 works, +32 does not work on single/double/tripple line boxes
|
||||
// The reason why we click at both of these positions is some clients will be running windowed, and some windowless. This will hit both locations
|
||||
SendMouseClick(rect.Width / 2 - 80, rect.Height / 2 + 18);
|
||||
SendMouseClick(rect.Width / 2 - 80, rect.Height / 2 + 25);
|
||||
SendMouseClick(rect.Width / 2 - 80, rect.Height / 2 + 31);
|
||||
}
|
||||
|
||||
public static void ClickNo()
|
||||
{
|
||||
User32.RECT rect = new User32.RECT();
|
||||
|
||||
User32.GetWindowRect(CoreManager.Current.Decal.Hwnd, ref rect);
|
||||
|
||||
// The reason why we click at both of these positions is some clients will be running windowed, and some windowless. This will hit both locations
|
||||
SendMouseClick(rect.Width / 2 + 80, rect.Height / 2 + 18);
|
||||
SendMouseClick(rect.Width / 2 + 80, rect.Height / 2 + 25);
|
||||
SendMouseClick(rect.Width / 2 + 80, rect.Height / 2 + 31);
|
||||
}
|
||||
|
||||
public static void SendMouseClick(int x, int y)
|
||||
{
|
||||
int loc = (y * 0x10000) + x;
|
||||
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_MOUSEMOVE, (IntPtr)0x00000000, (UIntPtr)loc);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_LBUTTONDOWN, (IntPtr)0x00000001, (UIntPtr)loc);
|
||||
User32.PostMessage(CoreManager.Current.Decal.Hwnd, User32.WM_LBUTTONUP, (IntPtr)0x00000000, (UIntPtr)loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Shared/RateLimiter.cs
Normal file
59
Shared/RateLimiter.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
public class RateLimiter
|
||||
{
|
||||
public readonly int MaxNumberOfEvents;
|
||||
private readonly double overPeriodInSeconds;
|
||||
private readonly double targetEventSpacingInSeconds;
|
||||
|
||||
private readonly Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
|
||||
public RateLimiter(int maxNumberOfEvents, TimeSpan overPeriod)
|
||||
{
|
||||
if (maxNumberOfEvents <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(maxNumberOfEvents), $"{nameof(maxNumberOfEvents)} must be greater than 0");
|
||||
|
||||
if (overPeriod <= TimeSpan.Zero)
|
||||
throw new ArgumentOutOfRangeException(nameof(overPeriod), $"{nameof(overPeriod)} must be greater than TimeSpan.Zero");
|
||||
|
||||
MaxNumberOfEvents = maxNumberOfEvents;
|
||||
overPeriodInSeconds = overPeriod.TotalSeconds;
|
||||
|
||||
targetEventSpacingInSeconds = overPeriodInSeconds / maxNumberOfEvents;
|
||||
}
|
||||
|
||||
|
||||
private int numberOfEventsRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Result > 0 : We're able to meet our target rate and must pause between events.<para />
|
||||
/// Result = 0 : We're running right no time. A new event should be registered without delay.<para />
|
||||
/// Result < 0 : We're running behind. A new event should be registered without delay. We're failing to meet our target rate.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double GetSecondsToWaitBeforeNextEvent()
|
||||
{
|
||||
var elapsedSeconds = stopwatch.Elapsed.TotalSeconds;
|
||||
|
||||
return ((targetEventSpacingInSeconds * numberOfEventsRegistered) - elapsedSeconds);
|
||||
}
|
||||
|
||||
public void RegisterEvent()
|
||||
{
|
||||
numberOfEventsRegistered++;
|
||||
|
||||
var elapsedSeconds = stopwatch.Elapsed.TotalSeconds;
|
||||
|
||||
if (numberOfEventsRegistered > MaxNumberOfEvents || elapsedSeconds > overPeriodInSeconds)
|
||||
{
|
||||
numberOfEventsRegistered = 1;
|
||||
|
||||
stopwatch.Reset();
|
||||
stopwatch.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Shared/SerializableDictionary.cs
Normal file
81
Shared/SerializableDictionary.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
[XmlRoot("dictionary")]
|
||||
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
|
||||
{
|
||||
public System.Xml.Schema.XmlSchema GetSchema()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ReadXml(System.Xml.XmlReader reader)
|
||||
{
|
||||
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
|
||||
|
||||
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
|
||||
|
||||
bool wasEmpty = reader.IsEmptyElement;
|
||||
|
||||
reader.Read();
|
||||
|
||||
if (wasEmpty)
|
||||
return;
|
||||
|
||||
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
|
||||
{
|
||||
reader.ReadStartElement("item");
|
||||
|
||||
reader.ReadStartElement("key");
|
||||
|
||||
TKey key = (TKey)keySerializer.Deserialize(reader);
|
||||
|
||||
reader.ReadEndElement();
|
||||
|
||||
reader.ReadStartElement("value");
|
||||
|
||||
TValue value = (TValue)valueSerializer.Deserialize(reader);
|
||||
|
||||
reader.ReadEndElement();
|
||||
|
||||
this.Add(key, value);
|
||||
|
||||
reader.ReadEndElement();
|
||||
|
||||
reader.MoveToContent();
|
||||
}
|
||||
|
||||
reader.ReadEndElement();
|
||||
}
|
||||
|
||||
public void WriteXml(System.Xml.XmlWriter writer)
|
||||
{
|
||||
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
|
||||
|
||||
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
|
||||
|
||||
foreach (TKey key in this.Keys)
|
||||
{
|
||||
writer.WriteStartElement("item");
|
||||
|
||||
writer.WriteStartElement("key");
|
||||
|
||||
keySerializer.Serialize(writer, key);
|
||||
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("value");
|
||||
|
||||
TValue value = this[key];
|
||||
|
||||
valueSerializer.Serialize(writer, value);
|
||||
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Shared/Settings/Setting.cs
Normal file
59
Shared/Settings/Setting.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
|
||||
namespace Mag.Shared.Settings
|
||||
{
|
||||
class Setting<T>
|
||||
{
|
||||
public readonly string Xpath;
|
||||
|
||||
public readonly string Description;
|
||||
|
||||
public readonly T DefaultValue;
|
||||
|
||||
private T value;
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
// If we're setting it to the value its already at, don't continue with the set.
|
||||
if (Object.Equals(this.value, value))
|
||||
return;
|
||||
|
||||
// The value differs, set it.
|
||||
this.value = value;
|
||||
|
||||
StoreValueInConfigFile();
|
||||
|
||||
if (Changed != null)
|
||||
Changed(this);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<Setting<T>> Changed;
|
||||
|
||||
public Setting(string xpath, string description = null, T defaultValue = default(T))
|
||||
{
|
||||
Xpath = xpath;
|
||||
|
||||
Description = description;
|
||||
|
||||
DefaultValue = defaultValue;
|
||||
|
||||
LoadValueFromConfig(defaultValue);
|
||||
}
|
||||
|
||||
void LoadValueFromConfig(T defaultValue)
|
||||
{
|
||||
value = SettingsFile.GetSetting(Xpath, defaultValue);
|
||||
}
|
||||
|
||||
void StoreValueInConfigFile()
|
||||
{
|
||||
SettingsFile.PutSetting(Xpath, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
210
Shared/Settings/SettingsFile.cs
Normal file
210
Shared/Settings/SettingsFile.cs
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
|
||||
namespace Mag.Shared.Settings
|
||||
{
|
||||
static class SettingsFile
|
||||
{
|
||||
internal static readonly XmlDocument XmlDocument = new XmlDocument();
|
||||
|
||||
static string _documentPath;
|
||||
|
||||
static string _rootNodeName = "Settings";
|
||||
|
||||
static SettingsFile()
|
||||
{
|
||||
ReloadXmlDocument();
|
||||
}
|
||||
|
||||
public static void Init(string filePath, string rootNode = "Settings")
|
||||
{
|
||||
_documentPath = filePath;
|
||||
|
||||
_rootNodeName = rootNode;
|
||||
|
||||
ReloadXmlDocument();
|
||||
}
|
||||
|
||||
public static void ReloadXmlDocument()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!String.IsNullOrEmpty(_documentPath) && File.Exists(_documentPath))
|
||||
XmlDocument.Load(_documentPath);
|
||||
else
|
||||
XmlDocument.LoadXml("<" + _rootNodeName + "></" + _rootNodeName + ">");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
|
||||
XmlDocument.LoadXml("<" + _rootNodeName + "></" + _rootNodeName + ">");
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveXmlDocument()
|
||||
{
|
||||
XmlDocument.Save(_documentPath);
|
||||
}
|
||||
|
||||
public static T GetSetting<T>(string xPath, T defaultValue = default(T))
|
||||
{
|
||||
XmlNode xmlNode = XmlDocument.SelectSingleNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
if (xmlNode != null)
|
||||
{
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
|
||||
if (converter.CanConvertFrom(typeof(string)))
|
||||
return (T)converter.ConvertFromString(xmlNode.InnerText);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static void PutSetting<T>(string xPath, T value)
|
||||
{
|
||||
// Before we save a setting, we reload the document to make sure we don't overwrite settings saved from another session.
|
||||
ReloadXmlDocument();
|
||||
|
||||
XmlNode xmlNode = XmlDocument.SelectSingleNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
if (xmlNode == null)
|
||||
xmlNode = createMissingNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
|
||||
|
||||
if (converter.CanConvertTo(typeof(string)))
|
||||
{
|
||||
string result = converter.ConvertToString(value);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
xmlNode.InnerText = result;
|
||||
|
||||
XmlDocument.Save(_documentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static XmlNode createMissingNode(string xPath)
|
||||
{
|
||||
string[] xPathSections = xPath.Split('/');
|
||||
|
||||
string currentXPath = "";
|
||||
|
||||
XmlNode currentNode = XmlDocument.SelectSingleNode(_rootNodeName);
|
||||
|
||||
foreach (string xPathSection in xPathSections)
|
||||
{
|
||||
currentXPath += xPathSection;
|
||||
|
||||
XmlNode testNode = XmlDocument.SelectSingleNode(currentXPath);
|
||||
|
||||
if (testNode == null)
|
||||
{
|
||||
if (currentNode != null)
|
||||
currentNode.InnerXml += "<" + xPathSection + "></" + xPathSection + ">";
|
||||
}
|
||||
|
||||
currentNode = XmlDocument.SelectSingleNode(currentXPath);
|
||||
|
||||
currentXPath += "/";
|
||||
}
|
||||
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
public static IList<string> GetChilderenInnerTexts(string xPath)
|
||||
{
|
||||
XmlNode xmlNode = XmlDocument.SelectSingleNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
Collection<string> collection = new Collection<string>();
|
||||
|
||||
if (xmlNode != null)
|
||||
{
|
||||
foreach (XmlNode childNode in xmlNode.ChildNodes)
|
||||
collection.Add(childNode.InnerText);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public static void SetNodeChilderen(string xPath, string childNodeName, IList<string> innerTexts)
|
||||
{
|
||||
// Before we save a setting, we reload the document to make sure we don't overwrite settings saved from another session.
|
||||
ReloadXmlDocument();
|
||||
|
||||
XmlNode parentNode = XmlDocument.SelectSingleNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
if (parentNode == null)
|
||||
{
|
||||
if (innerTexts.Count == 0)
|
||||
return;
|
||||
|
||||
parentNode = createMissingNode(_rootNodeName + "/" + xPath);
|
||||
}
|
||||
|
||||
parentNode.RemoveAll();
|
||||
|
||||
if (innerTexts.Count == 0)
|
||||
{
|
||||
XmlDocument.Save(_documentPath);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string innerText in innerTexts)
|
||||
{
|
||||
XmlNode childNode = parentNode.AppendChild(XmlDocument.CreateElement(childNodeName));
|
||||
|
||||
childNode.InnerText = innerText;
|
||||
}
|
||||
|
||||
XmlDocument.Save(_documentPath);
|
||||
}
|
||||
|
||||
public static XmlNode GetNode(string xPath, bool createIfNull = false)
|
||||
{
|
||||
var node = XmlDocument.SelectSingleNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
if (node == null && createIfNull)
|
||||
node = createMissingNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public static void SetNodeChilderen(string xPath, string childNodeName, Collection<Dictionary<string, string>> childNodeAttributes)
|
||||
{
|
||||
// Before we save a setting, we reload the document to make sure we don't overwrite settings saved from another session.
|
||||
ReloadXmlDocument();
|
||||
|
||||
XmlNode parentNode = XmlDocument.SelectSingleNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
if (parentNode == null)
|
||||
parentNode = createMissingNode(_rootNodeName + "/" + xPath);
|
||||
|
||||
if (parentNode.HasChildNodes)
|
||||
parentNode.RemoveAll();
|
||||
|
||||
foreach (Dictionary<string, string> dictionary in childNodeAttributes)
|
||||
{
|
||||
XmlNode childNode = parentNode.AppendChild(XmlDocument.CreateElement(childNodeName));
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in dictionary)
|
||||
{
|
||||
XmlAttribute attribute = XmlDocument.CreateAttribute(pair.Key);
|
||||
attribute.Value = pair.Value;
|
||||
|
||||
if (childNode.Attributes != null)
|
||||
childNode.Attributes.Append(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
XmlDocument.Save(_documentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Shared/SortableBindingList.cs
Normal file
100
Shared/SortableBindingList.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Mag.Shared
|
||||
{
|
||||
class SortableBindingList<T> : BindingList<T> where T : class
|
||||
{
|
||||
protected override bool SupportsSortingCore { get { return true; } }
|
||||
|
||||
private bool isSorted;
|
||||
private ListSortDirection sortDirection;
|
||||
private PropertyDescriptor sortProperty;
|
||||
|
||||
protected override bool IsSortedCore { get { return isSorted; } }
|
||||
|
||||
protected override ListSortDirection SortDirectionCore { get { return sortDirection; } }
|
||||
|
||||
protected override PropertyDescriptor SortPropertyCore { get { return sortProperty; } }
|
||||
|
||||
protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
|
||||
{
|
||||
sortProperty = prop;
|
||||
sortDirection = direction;
|
||||
|
||||
List<T> items = (List<T>)Items;
|
||||
|
||||
items.Sort(delegate(T lhs, T rhs)
|
||||
{
|
||||
isSorted = true;
|
||||
object lhsValue = lhs == null ? null : prop.GetValue(lhs);
|
||||
object rhsValue = rhs == null ? null : prop.GetValue(rhs);
|
||||
int result = Comparer.Default.Compare(lhsValue, rhsValue);
|
||||
|
||||
if (direction == ListSortDirection.Descending)
|
||||
result = -result;
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void RemoveSortCore()
|
||||
{
|
||||
sortDirection = ListSortDirection.Ascending;
|
||||
sortProperty = null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sorts using the default IComparer of T
|
||||
/// </summary>
|
||||
public void Sort()
|
||||
{
|
||||
sort(null, null);
|
||||
}
|
||||
|
||||
public void Sort(IComparer<T> comparer)
|
||||
{
|
||||
sort(comparer, null);
|
||||
}
|
||||
|
||||
public void Sort(Comparison<T> comparison)
|
||||
{
|
||||
sort(null, comparison);
|
||||
}
|
||||
|
||||
private void sort(IComparer<T> comparer, Comparison<T> comparison)
|
||||
{
|
||||
sortProperty = null;
|
||||
sortDirection = ListSortDirection.Ascending;
|
||||
|
||||
//Extract items and sort separately
|
||||
List<T> sortList = new List<T>();
|
||||
foreach (var item in this)
|
||||
sortList.Add(item);
|
||||
|
||||
if (comparison == null)
|
||||
sortList.Sort(comparer);
|
||||
else
|
||||
sortList.Sort(comparison);
|
||||
|
||||
//Disable notifications, rebuild, and re-enable notifications
|
||||
bool oldRaise = RaiseListChangedEvents;
|
||||
RaiseListChangedEvents = false;
|
||||
|
||||
try
|
||||
{
|
||||
ClearItems();
|
||||
sortList.ForEach(item => Add(item));
|
||||
}
|
||||
finally
|
||||
{
|
||||
RaiseListChangedEvents = oldRaise;
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
188
Shared/Spells/Spell.cs
Normal file
188
Shared/Spells/Spell.cs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
|
||||
namespace Mag.Shared.Spells
|
||||
{
|
||||
/// <summary>
|
||||
/// Use GetSpell() to intialize a Spell object
|
||||
/// </summary>
|
||||
public class Spell
|
||||
{
|
||||
public readonly int Id;
|
||||
public readonly string Name;
|
||||
public readonly int Difficulty;
|
||||
public readonly int Duration;
|
||||
public readonly int Family;
|
||||
|
||||
public enum BuffLevels
|
||||
{
|
||||
None,
|
||||
|
||||
I,
|
||||
II,
|
||||
III,
|
||||
IV,
|
||||
V,
|
||||
VI,
|
||||
VII,
|
||||
VIII,
|
||||
}
|
||||
public readonly BuffLevels BuffLevel;
|
||||
|
||||
public enum CantripLevels
|
||||
{
|
||||
None,
|
||||
|
||||
Feeble,
|
||||
Minor,
|
||||
Lesser,
|
||||
Moderate,
|
||||
Inner,
|
||||
Major,
|
||||
Epic,
|
||||
Legendary,
|
||||
}
|
||||
public readonly CantripLevels CantripLevel;
|
||||
|
||||
public Spell(int id, string name, int difficulty, int duration, int family)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Difficulty = difficulty;
|
||||
Duration = duration;
|
||||
Family = family;
|
||||
|
||||
if (name.EndsWith(" I"))
|
||||
BuffLevel = BuffLevels.I;
|
||||
else if (name.EndsWith(" II"))
|
||||
BuffLevel = BuffLevels.II;
|
||||
else if (name.EndsWith(" III"))
|
||||
BuffLevel = BuffLevels.III;
|
||||
else if (name.EndsWith(" IV"))
|
||||
BuffLevel = BuffLevels.IV;
|
||||
else if (name.EndsWith(" V"))
|
||||
BuffLevel = BuffLevels.V;
|
||||
else if (name.EndsWith(" VI"))
|
||||
BuffLevel = BuffLevels.VI;
|
||||
else if (name.EndsWith(" VII")) // This doesn't pick up every lvl 7
|
||||
BuffLevel = BuffLevels.VII;
|
||||
else if (name.StartsWith("Incantation ") || name.StartsWith("Aura of Incantation "))
|
||||
BuffLevel = BuffLevels.VIII;
|
||||
|
||||
if (name.StartsWith("Feeble "))
|
||||
CantripLevel = CantripLevels.Feeble;
|
||||
else if (name.StartsWith("Minor "))
|
||||
CantripLevel = CantripLevels.Minor;
|
||||
else if (name.StartsWith("Lesser "))
|
||||
CantripLevel = CantripLevels.Lesser;
|
||||
else if (name.StartsWith("Moderate "))
|
||||
CantripLevel = CantripLevels.Moderate;
|
||||
else if (name.StartsWith("Inner ") && name != "Inner Calm")
|
||||
CantripLevel = CantripLevels.Inner;
|
||||
else if (name.StartsWith("Major "))
|
||||
CantripLevel = CantripLevels.Major;
|
||||
else if (name.StartsWith("Epic "))
|
||||
CantripLevel = CantripLevels.Epic;
|
||||
else if (name.StartsWith("Legendary "))
|
||||
CantripLevel = CantripLevels.Legendary;
|
||||
|
||||
// Try to determine if this is a lvl x
|
||||
if (BuffLevel == BuffLevels.None && CantripLevel == CantripLevels.None)
|
||||
{
|
||||
// These spells don't have levels
|
||||
if (name.StartsWith("Prodigal ") || // Rares
|
||||
name.StartsWith("Cloaked in ") || name.StartsWith("Shroud of ")) // Cloaks
|
||||
return;
|
||||
|
||||
if (difficulty == 1 && duration == 1800)
|
||||
BuffLevel = BuffLevels.I;
|
||||
if (difficulty == 50 && duration == -1 && name.StartsWith("Evaporate "))
|
||||
BuffLevel = BuffLevels.II;
|
||||
|
||||
if (difficulty == 50 && duration == 1800)
|
||||
BuffLevel = BuffLevels.II;
|
||||
if (difficulty == 100 && duration == -1 && name.StartsWith("Extinguish "))
|
||||
BuffLevel = BuffLevels.II;
|
||||
|
||||
if (difficulty == 100 && duration == 1800)
|
||||
BuffLevel = BuffLevels.III;
|
||||
if (difficulty == 150 && duration == -1 && name.StartsWith("Cleanse "))
|
||||
BuffLevel = BuffLevels.III;
|
||||
|
||||
if (difficulty == 150 && duration == 1800)
|
||||
BuffLevel = BuffLevels.IV;
|
||||
if (difficulty == 200 && duration == -1 && name.StartsWith("Devour "))
|
||||
BuffLevel = BuffLevels.IV;
|
||||
|
||||
if (difficulty == 200 && duration == 1800)
|
||||
BuffLevel = BuffLevels.V;
|
||||
if (difficulty == 250 && duration == -1 && name.StartsWith("Purge "))
|
||||
BuffLevel = BuffLevels.V;
|
||||
|
||||
if (difficulty == 200 && duration == -1 && !name.StartsWith("Devour ")) // Ring spells
|
||||
BuffLevel = BuffLevels.VI;
|
||||
if (difficulty == 250 && duration == 2700)
|
||||
BuffLevel = BuffLevels.VI;
|
||||
if (difficulty == 300 && duration == -1 && name.StartsWith("Nullify "))
|
||||
BuffLevel = BuffLevels.VI;
|
||||
|
||||
if (difficulty == 300 && duration == -1 && !name.StartsWith("Nullify "))
|
||||
BuffLevel = BuffLevels.VII;
|
||||
if (difficulty == 300 && duration == 240)
|
||||
BuffLevel = BuffLevels.VII;
|
||||
if (difficulty == 300 && duration == 3600)
|
||||
BuffLevel = BuffLevels.VII;
|
||||
if (difficulty == 325 && duration == -1)
|
||||
BuffLevel = BuffLevels.VII;
|
||||
if (difficulty == 325 && duration == 240)
|
||||
BuffLevel = BuffLevels.VII;
|
||||
if (difficulty == 350 && duration == -1)
|
||||
BuffLevel = BuffLevels.VII;
|
||||
|
||||
if (difficulty == 400 && duration == 5400)
|
||||
BuffLevel = BuffLevels.VIII;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsOfSameFamilyAndGroup(Spell compareSpell)
|
||||
{
|
||||
if (Family != compareSpell.Family)
|
||||
return false;
|
||||
|
||||
if (BuffLevel != 0 && compareSpell.BuffLevel != 0)
|
||||
return true;
|
||||
|
||||
if (CantripLevel != 0 && compareSpell.CantripLevel != 0)
|
||||
return true;
|
||||
|
||||
// Are both spells are of an unkown group?
|
||||
if (BuffLevel == 0 && compareSpell.BuffLevel == 0 && CantripLevel == 0 && compareSpell.CantripLevel == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsSameOrSurpasses(Spell compareSpell)
|
||||
{
|
||||
return (this == compareSpell || Surpasses(compareSpell));
|
||||
}
|
||||
|
||||
public bool Surpasses(Spell compareSpell)
|
||||
{
|
||||
if (Family == 0 || Family != compareSpell.Family)
|
||||
return false;
|
||||
|
||||
if (BuffLevel > 0 && compareSpell.BuffLevel > 0 && BuffLevel > compareSpell.BuffLevel)
|
||||
return true;
|
||||
|
||||
if (CantripLevel > 0 && compareSpell.CantripLevel > 0 && CantripLevel > compareSpell.CantripLevel)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue