Compare commits
105 commits
9de0d03474
...
cac8e96656
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cac8e96656 | ||
|
|
7610ad9029 | ||
|
|
ac691d3140 | ||
|
|
97ace3375a | ||
|
|
afa85ef80d | ||
|
|
ce0fae7d10 | ||
|
|
725bbf473f | ||
|
|
c702263770 | ||
|
|
a13d30f0b2 | ||
|
|
57a6946e6b | ||
|
|
aed74984c6 | ||
|
|
1ffa163501 | ||
|
|
361c2012da | ||
|
|
1fff36e3f7 | ||
|
|
a446158f63 | ||
|
|
a4d2108b3a | ||
|
|
c30704aaa7 | ||
|
|
64e690f625 | ||
|
|
0713e96a99 | ||
|
|
f9264f2767 | ||
|
|
c90e888d32 | ||
|
|
366cca8cb6 | ||
|
|
4845a67c1f | ||
|
|
9e9a94f159 | ||
|
|
c53aa4b31b | ||
|
|
ad8fb3a4ba | ||
|
|
88600db779 | ||
|
|
1fdae96262 | ||
|
|
655bfd5163 | ||
|
|
84462d7c92 | ||
|
|
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 |
372 changed files with 46220 additions and 8220 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -360,4 +360,14 @@ MigrationBackup/
|
|||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
FodyWeavers.xsd
|
||||
/UI_Creation_Manual.md
|
||||
/UI_Creation_VirindiViewService_Manual.md
|
||||
/MosswartMassacre/Unused
|
||||
/MosswartMassacre/Decal.Adapter.csproj
|
||||
/MosswartMassacre/Decal.Interop.Core.csproj
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
CLAUDE.md
|
||||
**/CLAUDE.md
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
376
MosswartMassacre/CharacterStats.cs
Normal file
376
MosswartMassacre/CharacterStats.cs
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
public struct AllegianceInfoRecord
|
||||
{
|
||||
public string name;
|
||||
public int rank;
|
||||
public int race;
|
||||
public int gender;
|
||||
|
||||
public AllegianceInfoRecord(string _name, int _rank, int _race, int _gender)
|
||||
{
|
||||
name = _name;
|
||||
rank = _rank;
|
||||
race = _race;
|
||||
gender = _gender;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CharacterStats
|
||||
{
|
||||
private static IPluginLogger _logger;
|
||||
|
||||
// Cached allegiance data (populated from network messages)
|
||||
private static string allegianceName;
|
||||
private static int allegianceSize;
|
||||
private static int followers;
|
||||
private static AllegianceInfoRecord monarch;
|
||||
private static AllegianceInfoRecord patron;
|
||||
private static int allegianceRank;
|
||||
|
||||
// Cached luminance data (populated from network messages)
|
||||
private static long luminanceEarned = -1;
|
||||
private static long luminanceTotal = -1;
|
||||
|
||||
// Cached title data (populated from network messages)
|
||||
private static int currentTitle = -1;
|
||||
private static List<string> titlesList = new List<string>();
|
||||
|
||||
// Cached DWORD properties (populated from 0x0013 message)
|
||||
private static Dictionary<int, int> characterProperties = new Dictionary<int, int>();
|
||||
|
||||
// DWORD blacklist — exclude cosmetic/internal properties (same as TreeStats)
|
||||
private static readonly HashSet<int> dwordBlacklist = new HashSet<int> {
|
||||
2, 5, 7, 10, 17, 19, 20, 24, 25, 26, 28, 30, 33, 35, 36, 38, 43, 45,
|
||||
86, 87, 88, 89, 90, 91, 92, 98,
|
||||
105, 106, 107, 108, 109, 110, 111, 113, 114, 115, 117, 125, 129, 131, 134,
|
||||
158, 159, 160, 166, 170, 171, 172, 174, 175, 176, 177, 178, 179, 188, 193,
|
||||
270, 271, 272, 293
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Reset all cached data. Call on plugin init.
|
||||
/// </summary>
|
||||
internal static void Init(IPluginLogger logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
allegianceName = null;
|
||||
allegianceSize = 0;
|
||||
followers = 0;
|
||||
monarch = new AllegianceInfoRecord();
|
||||
patron = new AllegianceInfoRecord();
|
||||
allegianceRank = 0;
|
||||
luminanceEarned = -1;
|
||||
luminanceTotal = -1;
|
||||
currentTitle = -1;
|
||||
titlesList.Clear();
|
||||
characterProperties.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process game event 0x0020 - Allegiance info.
|
||||
/// Extracts monarch, patron, rank, followers from the allegiance tree.
|
||||
/// Reference: TreeStats Character.cs:642-745
|
||||
/// </summary>
|
||||
internal static void ProcessAllegianceInfoMessage(NetworkMessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
allegianceName = e.Message.Value<string>("allegianceName");
|
||||
allegianceSize = e.Message.Value<Int32>("allegianceSize");
|
||||
followers = e.Message.Value<Int32>("followers");
|
||||
|
||||
monarch = new AllegianceInfoRecord();
|
||||
patron = new AllegianceInfoRecord();
|
||||
|
||||
MessageStruct records = e.Message.Struct("records");
|
||||
int currentId = CoreManager.Current.CharacterFilter.Id;
|
||||
var parentMap = new Dictionary<int, int>();
|
||||
var recordMap = new Dictionary<int, AllegianceInfoRecord>();
|
||||
|
||||
for (int i = 0; i < records.Count; i++)
|
||||
{
|
||||
var record = records.Struct(i);
|
||||
int charId = record.Value<int>("character");
|
||||
int treeParent = record.Value<int>("treeParent");
|
||||
|
||||
parentMap[charId] = treeParent;
|
||||
recordMap[charId] = new AllegianceInfoRecord(
|
||||
record.Value<string>("name"),
|
||||
record.Value<int>("rank"),
|
||||
record.Value<int>("race"),
|
||||
record.Value<int>("gender"));
|
||||
|
||||
// Monarch: treeParent <= 1
|
||||
if (treeParent <= 1)
|
||||
{
|
||||
monarch = recordMap[charId];
|
||||
}
|
||||
}
|
||||
|
||||
// Patron: parent of current character
|
||||
if (parentMap.ContainsKey(currentId) && recordMap.ContainsKey(parentMap[currentId]))
|
||||
{
|
||||
patron = recordMap[parentMap[currentId]];
|
||||
}
|
||||
|
||||
// Our rank from the record
|
||||
if (recordMap.ContainsKey(currentId))
|
||||
{
|
||||
allegianceRank = recordMap[currentId].rank;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[CharStats] Allegiance processing error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process game event 0x0013 - Character property data.
|
||||
/// Extracts luminance from QWORD keys 6 and 7.
|
||||
/// Reference: TreeStats Character.cs:582-640
|
||||
/// </summary>
|
||||
internal static void ProcessCharacterPropertyData(NetworkMessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
MessageStruct props = e.Message.Struct("properties");
|
||||
MessageStruct qwords = props.Struct("qwords");
|
||||
|
||||
for (int i = 0; i < qwords.Count; i++)
|
||||
{
|
||||
var tmpStruct = qwords.Struct(i);
|
||||
long key = tmpStruct.Value<Int64>("key");
|
||||
long value = tmpStruct.Value<Int64>("value");
|
||||
|
||||
if (key == Constants.AvailableLuminanceKey)
|
||||
luminanceEarned = value;
|
||||
else if (key == Constants.MaximumLuminanceKey)
|
||||
luminanceTotal = value;
|
||||
}
|
||||
|
||||
// Parse DWORD properties (augmentations, ratings, masteries, society, etc.)
|
||||
MessageStruct dwords = props.Struct("dwords");
|
||||
characterProperties.Clear();
|
||||
for (int i = 0; i < dwords.Count; i++)
|
||||
{
|
||||
var tmpStruct = dwords.Struct(i);
|
||||
int key = tmpStruct.Value<Int32>("key");
|
||||
int value = tmpStruct.Value<Int32>("value");
|
||||
if (!dwordBlacklist.Contains(key))
|
||||
characterProperties[key] = value;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[CharStats] Property processing error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process message 0x02CF - PrivateUpdatePropertyInt64.
|
||||
/// Sent during gameplay when an Int64 property changes (e.g., luminance earned/spent).
|
||||
/// Wire format after 4-byte type header: sequence(1) + key(4) + value(8).
|
||||
/// Uses RawData since Decal's messages.xml may not define this message type.
|
||||
/// </summary>
|
||||
internal static void ProcessPropertyInt64Update(NetworkMessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] raw = e.Message.RawData;
|
||||
if (raw.Length < 17) return; // 4 type + 1 seq + 4 key + 8 value
|
||||
|
||||
int key = BitConverter.ToInt32(raw, 5);
|
||||
long value = BitConverter.ToInt64(raw, 9);
|
||||
|
||||
if (key == Constants.AvailableLuminanceKey)
|
||||
luminanceEarned = value;
|
||||
else if (key == Constants.MaximumLuminanceKey)
|
||||
luminanceTotal = value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[CharStats] Int64 property update error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process game event 0x0029 - Titles list.
|
||||
/// Extracts current title ID.
|
||||
/// Reference: TreeStats Character.cs:551-580
|
||||
/// </summary>
|
||||
internal static void ProcessTitlesMessage(NetworkMessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Capture full titles list
|
||||
MessageStruct titles = e.Message.Struct("titles");
|
||||
titlesList.Clear();
|
||||
for (int i = 0; i < titles.Count; i++)
|
||||
{
|
||||
titlesList.Add(titles.Struct(i).Value<string>("value"));
|
||||
}
|
||||
|
||||
currentTitle = e.Message.Value<Int32>("current");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[CharStats] Title processing error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process game event 0x002b - Set title (when player changes title).
|
||||
/// </summary>
|
||||
internal static void ProcessSetTitleMessage(NetworkMessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentTitle = e.Message.Value<Int32>("title");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[CharStats] Set title error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collect all character data and send via WebSocket.
|
||||
/// Called on login (after delay) and every 10 minutes.
|
||||
/// </summary>
|
||||
internal static void CollectAndSend()
|
||||
{
|
||||
if (!PluginCore.WebSocketEnabled)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var cf = CoreManager.Current.CharacterFilter;
|
||||
var culture = new CultureInfo("en-US");
|
||||
|
||||
// --- Attributes ---
|
||||
var attributes = new Dictionary<string, object>();
|
||||
foreach (var attr in cf.Attributes)
|
||||
{
|
||||
attributes[attr.Name.ToLower()] = new
|
||||
{
|
||||
@base = attr.Base,
|
||||
creation = attr.Creation
|
||||
};
|
||||
}
|
||||
|
||||
// --- Vitals (base values) ---
|
||||
var vitals = new Dictionary<string, object>();
|
||||
foreach (var vital in cf.Vitals)
|
||||
{
|
||||
vitals[vital.Name.ToLower()] = new
|
||||
{
|
||||
@base = vital.Base
|
||||
};
|
||||
}
|
||||
|
||||
// --- Skills ---
|
||||
var skills = new Dictionary<string, object>();
|
||||
Decal.Filters.FileService fs = CoreManager.Current.FileService as Decal.Filters.FileService;
|
||||
if (fs != null)
|
||||
{
|
||||
for (int i = 0; i < fs.SkillTable.Length; i++)
|
||||
{
|
||||
Decal.Interop.Filters.SkillInfo skillinfo = null;
|
||||
try
|
||||
{
|
||||
skillinfo = cf.Underlying.get_Skill(
|
||||
(Decal.Interop.Filters.eSkillID)fs.SkillTable[i].Id);
|
||||
|
||||
string name = skillinfo.Name.ToLower().Replace(" ", "_");
|
||||
string training = skillinfo.Training.ToString();
|
||||
// Training enum returns "eTrainSpecialized" etc, strip "eTrain" prefix
|
||||
if (training.Length > 6)
|
||||
training = training.Substring(6);
|
||||
|
||||
skills[name] = new
|
||||
{
|
||||
@base = skillinfo.Base,
|
||||
training = training
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (skillinfo != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(skillinfo);
|
||||
skillinfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Allegiance ---
|
||||
object allegiance = null;
|
||||
if (allegianceName != null)
|
||||
{
|
||||
allegiance = new
|
||||
{
|
||||
name = allegianceName,
|
||||
monarch = monarch.name != null ? new
|
||||
{
|
||||
name = monarch.name,
|
||||
race = monarch.race,
|
||||
rank = monarch.rank,
|
||||
gender = monarch.gender
|
||||
} : null,
|
||||
patron = patron.name != null ? new
|
||||
{
|
||||
name = patron.name,
|
||||
race = patron.race,
|
||||
rank = patron.rank,
|
||||
gender = patron.gender
|
||||
} : null,
|
||||
rank = allegianceRank,
|
||||
followers = followers
|
||||
};
|
||||
}
|
||||
|
||||
// --- Build payload ---
|
||||
var payload = new
|
||||
{
|
||||
type = "character_stats",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = cf.Name,
|
||||
level = cf.Level,
|
||||
race = cf.Race,
|
||||
gender = cf.Gender,
|
||||
birth = cf.Birth.ToString(culture),
|
||||
total_xp = cf.TotalXP,
|
||||
unassigned_xp = cf.UnassignedXP,
|
||||
skill_credits = cf.SkillPoints,
|
||||
deaths = cf.Deaths,
|
||||
luminance_earned = luminanceEarned >= 0 ? (long?)luminanceEarned : null,
|
||||
luminance_total = luminanceTotal >= 0 ? (long?)luminanceTotal : null,
|
||||
current_title = currentTitle >= 0 ? (int?)currentTitle : null,
|
||||
attributes = attributes,
|
||||
vitals = vitals,
|
||||
skills = skills,
|
||||
allegiance = allegiance,
|
||||
properties = characterProperties.Count > 0 ? new Dictionary<int, int>(characterProperties) : null,
|
||||
titles = titlesList.Count > 0 ? new List<string>(titlesList) : null
|
||||
};
|
||||
|
||||
_ = WebSocket.SendCharacterStatsAsync(payload);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[CharStats] Error collecting stats: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
MosswartMassacre/ChatEventRouter.cs
Normal file
90
MosswartMassacre/ChatEventRouter.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Routes chat events to the appropriate handler (KillTracker, RareTracker, etc.)
|
||||
/// Replaces the big if/else chain in PluginCore.OnChatText.
|
||||
/// </summary>
|
||||
internal class ChatEventRouter
|
||||
{
|
||||
private readonly IPluginLogger _logger;
|
||||
private readonly KillTracker _killTracker;
|
||||
private RareTracker _rareTracker;
|
||||
private readonly Action<int> _onRareCountChanged;
|
||||
private readonly Action<string> _onAllegianceReport;
|
||||
|
||||
internal void SetRareTracker(RareTracker rareTracker) => _rareTracker = rareTracker;
|
||||
|
||||
internal ChatEventRouter(
|
||||
IPluginLogger logger,
|
||||
KillTracker killTracker,
|
||||
RareTracker rareTracker,
|
||||
Action<int> onRareCountChanged,
|
||||
Action<string> onAllegianceReport)
|
||||
{
|
||||
_logger = logger;
|
||||
_killTracker = killTracker;
|
||||
_rareTracker = rareTracker;
|
||||
_onRareCountChanged = onRareCountChanged;
|
||||
_onAllegianceReport = onAllegianceReport;
|
||||
}
|
||||
|
||||
internal void OnChatText(object sender, ChatTextInterceptEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_killTracker.CheckForKill(e.Text);
|
||||
|
||||
if (_rareTracker != null && _rareTracker.CheckForRare(e.Text, out string rareText))
|
||||
{
|
||||
_killTracker.RareCount = _rareTracker.RareCount;
|
||||
_onRareCountChanged?.Invoke(_rareTracker.RareCount);
|
||||
}
|
||||
|
||||
if (e.Color == 18 && e.Text.EndsWith("!report\""))
|
||||
{
|
||||
TimeSpan elapsed = DateTime.Now - _killTracker.StatsStartTime;
|
||||
string reportMessage = $"Total Kills: {_killTracker.TotalKills}, Kills per Hour: {_killTracker.KillsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {_rareTracker?.RareCount ?? 0}";
|
||||
_logger?.Log($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}");
|
||||
_onAllegianceReport?.Invoke(reportMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log("Error processing chat message: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Streams all chat text to WebSocket (separate handler from the filtered one above).
|
||||
/// </summary>
|
||||
internal static async void AllChatText(object sender, ChatTextInterceptEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
string cleaned = NormalizeChatLine(e.Text);
|
||||
await WebSocket.SendChatTextAsync(e.Color, cleaned);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[WS] Chat send failed: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeChatLine(string raw)
|
||||
{
|
||||
if (string.IsNullOrEmpty(raw))
|
||||
return raw;
|
||||
|
||||
var noTags = Regex.Replace(raw, "<[^>]+>", "");
|
||||
var trimmed = noTags.TrimEnd('\r', '\n');
|
||||
var collapsed = Regex.Replace(trimmed, @"[ ]{2,}", " ");
|
||||
|
||||
return collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
67
MosswartMassacre/CommandRouter.cs
Normal file
67
MosswartMassacre/CommandRouter.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary-based /mm command dispatcher. Commands are registered with descriptions
|
||||
/// and routed by name lookup instead of a giant switch statement.
|
||||
/// </summary>
|
||||
internal class CommandRouter
|
||||
{
|
||||
private readonly Dictionary<string, (Action<string[]> handler, string description)> _commands
|
||||
= new Dictionary<string, (Action<string[]>, string)>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Register a command with its handler and help description.
|
||||
/// </summary>
|
||||
internal void Register(string name, Action<string[]> handler, string description)
|
||||
{
|
||||
_commands[name] = (handler, description);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispatch a raw /mm command string. Returns false if the command was not found.
|
||||
/// </summary>
|
||||
internal bool Dispatch(string rawText)
|
||||
{
|
||||
string[] args = rawText.Substring(3).Trim().Split(' ');
|
||||
|
||||
if (args.Length == 0 || string.IsNullOrEmpty(args[0]))
|
||||
{
|
||||
PluginCore.WriteToChat("Usage: /mm <command>. Try /mm help");
|
||||
return true;
|
||||
}
|
||||
|
||||
string subCommand = args[0].ToLower();
|
||||
|
||||
if (subCommand == "help")
|
||||
{
|
||||
PrintHelp();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_commands.TryGetValue(subCommand, out var entry))
|
||||
{
|
||||
entry.handler(args);
|
||||
return true;
|
||||
}
|
||||
|
||||
PluginCore.WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void PrintHelp()
|
||||
{
|
||||
PluginCore.WriteToChat("Mosswart Massacre Commands:");
|
||||
foreach (var kvp in _commands)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(kvp.Value.description))
|
||||
{
|
||||
PluginCore.WriteToChat($"/mm {kvp.Key,-18} - {kvp.Value.description}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
MosswartMassacre/Constants.cs
Normal file
36
MosswartMassacre/Constants.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Centralized constants for timer intervals, message type IDs, and property keys.
|
||||
/// </summary>
|
||||
internal static class Constants
|
||||
{
|
||||
// Timer intervals (milliseconds)
|
||||
internal const int StatsUpdateIntervalMs = 1000;
|
||||
internal const int VitalsUpdateIntervalMs = 5000;
|
||||
internal const int CommandProcessIntervalMs = 10;
|
||||
internal const int QuestStreamingIntervalMs = 30000;
|
||||
internal const int CharacterStatsIntervalMs = 600000; // 10 minutes
|
||||
internal const int LoginDelayMs = 5000;
|
||||
|
||||
// Network message types
|
||||
internal const int GameEventMessageType = 0xF7B0;
|
||||
internal const int PrivateUpdatePropertyInt64 = 0x02CF;
|
||||
|
||||
// Game event IDs (sub-events within 0xF7B0)
|
||||
internal const int AllegianceInfoEvent = 0x0020;
|
||||
internal const int LoginCharacterEvent = 0x0013;
|
||||
internal const int TitlesListEvent = 0x0029;
|
||||
internal const int SetTitleEvent = 0x002b;
|
||||
|
||||
// Int64 property keys
|
||||
internal const int AvailableLuminanceKey = 6;
|
||||
internal const int MaximumLuminanceKey = 7;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin version derived from assembly version (CalVer: YYYY.M.D.HHmm)
|
||||
/// </summary>
|
||||
public static string PluginVersion =>
|
||||
typeof(Constants).Assembly.GetName().Version.ToString();
|
||||
}
|
||||
}
|
||||
559
MosswartMassacre/DecalHarmonyClean.cs
Normal file
559
MosswartMassacre/DecalHarmonyClean.cs
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
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 (Exception ex)
|
||||
{
|
||||
AddDebugLog($"ApplyDecalPatches failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Exception ex)
|
||||
{
|
||||
AddDebugLog($"PatchHooksWrapper single patch failed ({prefixMethodName}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddDebugLog($"PatchHooksWrapper failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Exception ex)
|
||||
{
|
||||
AddDebugLog($"PatchHostActions single patch failed ({prefixMethodName}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// PATHWAY 3: Try to patch at PluginHost level
|
||||
PatchPluginHost();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddDebugLog($"PatchHostActions failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try a different approach - patch the actual chat system or find global instances
|
||||
/// </summary>
|
||||
private static void PatchPluginHost()
|
||||
{
|
||||
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 (Exception ex)
|
||||
{
|
||||
AddDebugLog($"PatchPluginHost single patch failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddDebugLog($"PatchPluginHost failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Exception ex)
|
||||
{
|
||||
AddDebugLog($"ApplySinglePatch failed ({prefixMethodName}): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Exception ex)
|
||||
{
|
||||
AddDebugLog($"Cleanup failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (Exception ex)
|
||||
{
|
||||
DecalHarmonyClean.AddDebugLog($"ProcessInterceptedMessage failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
|
|
|
|||
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>
|
||||
55
MosswartMassacre/GameEventRouter.cs
Normal file
55
MosswartMassacre/GameEventRouter.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using Decal.Adapter;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Routes EchoFilter.ServerDispatch network messages to the appropriate handlers.
|
||||
/// Owns the routing of 0xF7B0 sub-events and 0x02CF to CharacterStats.
|
||||
/// </summary>
|
||||
internal class GameEventRouter
|
||||
{
|
||||
private readonly IPluginLogger _logger;
|
||||
|
||||
internal GameEventRouter(IPluginLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
internal void OnServerDispatch(object sender, NetworkMessageEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.Message.Type == Constants.GameEventMessageType)
|
||||
{
|
||||
int eventId = (int)e.Message["event"];
|
||||
|
||||
if (eventId == Constants.AllegianceInfoEvent)
|
||||
{
|
||||
CharacterStats.ProcessAllegianceInfoMessage(e);
|
||||
}
|
||||
else if (eventId == Constants.LoginCharacterEvent)
|
||||
{
|
||||
CharacterStats.ProcessCharacterPropertyData(e);
|
||||
}
|
||||
else if (eventId == Constants.TitlesListEvent)
|
||||
{
|
||||
CharacterStats.ProcessTitlesMessage(e);
|
||||
}
|
||||
else if (eventId == Constants.SetTitleEvent)
|
||||
{
|
||||
CharacterStats.ProcessSetTitleMessage(e);
|
||||
}
|
||||
}
|
||||
else if (e.Message.Type == Constants.PrivateUpdatePropertyInt64)
|
||||
{
|
||||
CharacterStats.ProcessPropertyInt64Update(e);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[CharStats] ServerDispatch error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Decal.Adapter;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
public static class HttpCommandServer
|
||||
{
|
||||
private static HttpListener listener;
|
||||
private static CancellationTokenSource cts;
|
||||
private static bool isRunning = false;
|
||||
|
||||
public static bool IsRunning => isRunning;
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
if (isRunning) return;
|
||||
|
||||
try
|
||||
{
|
||||
listener = new HttpListener();
|
||||
listener.Prefixes.Add("http://localhost:8085/");
|
||||
listener.Start();
|
||||
cts = new CancellationTokenSource();
|
||||
Task.Run(() => ListenLoop(cts.Token));
|
||||
|
||||
isRunning = true;
|
||||
PluginCore.WriteToChat("[HTTP] Server started on port 8085.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat("[HTTP] Error starting server: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
if (!isRunning) return;
|
||||
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
listener.Stop();
|
||||
listener.Close();
|
||||
listener = null;
|
||||
isRunning = false;
|
||||
PluginCore.WriteToChat("[HTTP] Server stopped.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat("[HTTP] Error stopping server: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ListenLoop(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
HttpListenerContext context = null;
|
||||
|
||||
try
|
||||
{
|
||||
context = await listener.GetContextAsync();
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
break; // Listener was stopped
|
||||
}
|
||||
|
||||
if (context == null) continue;
|
||||
|
||||
string requestBody = new System.IO.StreamReader(context.Request.InputStream).ReadToEnd();
|
||||
|
||||
PluginCore.WriteToChat("[HTTP] Received request: " + requestBody);
|
||||
|
||||
// Parse simple format: target=Name&command=/say hello
|
||||
string target = "";
|
||||
string command = "";
|
||||
foreach (var pair in requestBody.Split('&'))
|
||||
{
|
||||
var parts = pair.Split('=');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
if (parts[0] == "target") target = WebUtility.UrlDecode(parts[1]);
|
||||
else if (parts[0] == "command") command = WebUtility.UrlDecode(parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(target) && !string.IsNullOrWhiteSpace(command))
|
||||
{
|
||||
string tellCmd = $"/a {target} {command}";
|
||||
CoreManager.Current.Actions.InvokeChatParser(tellCmd);
|
||||
}
|
||||
|
||||
byte[] buffer = Encoding.UTF8.GetBytes("Command received.");
|
||||
context.Response.ContentLength64 = buffer.Length;
|
||||
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
context.Response.OutputStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
MosswartMassacre/IGameStats.cs
Normal file
19
MosswartMassacre/IGameStats.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides game statistics for WebSocket telemetry payloads.
|
||||
/// Replaces direct static field access on PluginCore.
|
||||
/// </summary>
|
||||
public interface IGameStats
|
||||
{
|
||||
int TotalKills { get; }
|
||||
double KillsPerHour { get; }
|
||||
int SessionDeaths { get; }
|
||||
int TotalDeaths { get; }
|
||||
int CachedPrismaticCount { get; }
|
||||
string CharTag { get; }
|
||||
DateTime StatsStartTime { get; }
|
||||
}
|
||||
}
|
||||
11
MosswartMassacre/IPluginLogger.cs
Normal file
11
MosswartMassacre/IPluginLogger.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for writing messages to the game chat window.
|
||||
/// Eliminates direct PluginCore.WriteToChat() dependencies from manager classes.
|
||||
/// </summary>
|
||||
public interface IPluginLogger
|
||||
{
|
||||
void Log(string message);
|
||||
}
|
||||
}
|
||||
184
MosswartMassacre/InventoryMonitor.cs
Normal file
184
MosswartMassacre/InventoryMonitor.cs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks Prismatic Taper inventory counts using event-driven delta math.
|
||||
/// Avoids expensive inventory scans during gameplay.
|
||||
/// </summary>
|
||||
internal class InventoryMonitor
|
||||
{
|
||||
private readonly IPluginLogger _logger;
|
||||
private readonly Dictionary<int, int> _trackedTaperContainers = new Dictionary<int, int>();
|
||||
private readonly Dictionary<int, int> _lastKnownStackSizes = new Dictionary<int, int>();
|
||||
|
||||
internal int CachedPrismaticCount { get; private set; }
|
||||
internal int LastPrismaticCount { get; private set; }
|
||||
|
||||
internal InventoryMonitor(IPluginLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
LastPrismaticCount = CachedPrismaticCount;
|
||||
CachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper");
|
||||
|
||||
_trackedTaperContainers.Clear();
|
||||
_lastKnownStackSizes.Clear();
|
||||
|
||||
foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
|
||||
{
|
||||
if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase) &&
|
||||
IsPlayerOwnedContainer(wo.Container))
|
||||
{
|
||||
int stackCount = wo.Values(LongValueKey.StackCount, 1);
|
||||
_trackedTaperContainers[wo.Id] = wo.Container;
|
||||
_lastKnownStackSizes[wo.Id] = stackCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[TAPER] Error initializing count: {ex.Message}");
|
||||
CachedPrismaticCount = 0;
|
||||
LastPrismaticCount = 0;
|
||||
_trackedTaperContainers.Clear();
|
||||
_lastKnownStackSizes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnInventoryCreate(object sender, CreateObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.New;
|
||||
if (IsPlayerOwnedContainer(item.Container) &&
|
||||
item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
LastPrismaticCount = CachedPrismaticCount;
|
||||
int stackCount = item.Values(LongValueKey.StackCount, 1);
|
||||
CachedPrismaticCount += stackCount;
|
||||
|
||||
_trackedTaperContainers[item.Id] = item.Container;
|
||||
_lastKnownStackSizes[item.Id] = stackCount;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[TAPER] Error in OnInventoryCreate: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnInventoryRelease(object sender, ReleaseObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.Released;
|
||||
if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (_trackedTaperContainers.TryGetValue(item.Id, out int previousContainer))
|
||||
{
|
||||
if (IsPlayerOwnedContainer(previousContainer))
|
||||
{
|
||||
LastPrismaticCount = CachedPrismaticCount;
|
||||
int stackCount = item.Values(LongValueKey.StackCount, 1);
|
||||
CachedPrismaticCount -= stackCount;
|
||||
}
|
||||
|
||||
_trackedTaperContainers.Remove(item.Id);
|
||||
_lastKnownStackSizes.Remove(item.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
LastPrismaticCount = CachedPrismaticCount;
|
||||
CachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[TAPER] Error in OnInventoryRelease: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnInventoryChange(object sender, ChangeObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.Changed;
|
||||
if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
bool isInPlayerContainer = IsPlayerOwnedContainer(item.Container);
|
||||
|
||||
if (isInPlayerContainer)
|
||||
{
|
||||
bool wasAlreadyTracked = _trackedTaperContainers.ContainsKey(item.Id);
|
||||
_trackedTaperContainers[item.Id] = item.Container;
|
||||
|
||||
int currentStack = item.Values(LongValueKey.StackCount, 1);
|
||||
|
||||
if (!wasAlreadyTracked)
|
||||
{
|
||||
LastPrismaticCount = CachedPrismaticCount;
|
||||
CachedPrismaticCount += currentStack;
|
||||
}
|
||||
else if (_lastKnownStackSizes.TryGetValue(item.Id, out int previousStack))
|
||||
{
|
||||
int stackDelta = currentStack - previousStack;
|
||||
if (stackDelta != 0)
|
||||
{
|
||||
LastPrismaticCount = CachedPrismaticCount;
|
||||
CachedPrismaticCount += stackDelta;
|
||||
}
|
||||
}
|
||||
|
||||
_lastKnownStackSizes[item.Id] = currentStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[TAPER] Error in OnInventoryChange: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
_trackedTaperContainers.Clear();
|
||||
_lastKnownStackSizes.Clear();
|
||||
}
|
||||
|
||||
internal int TrackedTaperCount => _trackedTaperContainers.Count;
|
||||
internal int KnownStackSizesCount => _lastKnownStackSizes.Count;
|
||||
|
||||
private static bool IsPlayerOwnedContainer(int containerId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (containerId == CoreManager.Current.CharacterFilter.Id)
|
||||
return true;
|
||||
|
||||
WorldObject container = CoreManager.Current.WorldFilter[containerId];
|
||||
if (container != null &&
|
||||
container.ObjectClass == ObjectClass.Container &&
|
||||
container.Container == CoreManager.Current.CharacterFilter.Id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
176
MosswartMassacre/KillTracker.cs
Normal file
176
MosswartMassacre/KillTracker.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Timers;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks kills, deaths, and kill rate calculations.
|
||||
/// Owns the 1-second stats update timer.
|
||||
/// </summary>
|
||||
internal class KillTracker
|
||||
{
|
||||
private readonly IPluginLogger _logger;
|
||||
private readonly Action<int, double, double> _onStatsUpdated;
|
||||
private readonly Action<TimeSpan> _onElapsedUpdated;
|
||||
|
||||
private int _totalKills;
|
||||
private int _sessionDeaths;
|
||||
private int _totalDeaths;
|
||||
private double _killsPer5Min;
|
||||
private double _killsPerHour;
|
||||
private DateTime _lastKillTime = DateTime.Now;
|
||||
private DateTime _statsStartTime = DateTime.Now;
|
||||
private Timer _updateTimer;
|
||||
|
||||
// Kill message patterns — all 35+ patterns preserved exactly
|
||||
private static readonly string[] KillPatterns = new string[]
|
||||
{
|
||||
@"^You flatten (?<targetname>.+)'s body with the force of your assault!$",
|
||||
@"^You bring (?<targetname>.+) to a fiery end!$",
|
||||
@"^You beat (?<targetname>.+) to a lifeless pulp!$",
|
||||
@"^You smite (?<targetname>.+) mightily!$",
|
||||
@"^You obliterate (?<targetname>.+)!$",
|
||||
@"^You run (?<targetname>.+) through!$",
|
||||
@"^You reduce (?<targetname>.+) to a sizzling, oozing mass!$",
|
||||
@"^You knock (?<targetname>.+) into next Morningthaw!$",
|
||||
@"^You split (?<targetname>.+) apart!$",
|
||||
@"^You cleave (?<targetname>.+) in twain!$",
|
||||
@"^You slay (?<targetname>.+) viciously enough to impart death several times over!$",
|
||||
@"^You reduce (?<targetname>.+) to a drained, twisted corpse!$",
|
||||
@"^Your killing blow nearly turns (?<targetname>.+) inside-out!$",
|
||||
@"^Your attack stops (?<targetname>.+) cold!$",
|
||||
@"^Your lightning coruscates over (?<targetname>.+)'s mortal remains!$",
|
||||
@"^Your assault sends (?<targetname>.+) to an icy death!$",
|
||||
@"^You killed (?<targetname>.+)!$",
|
||||
@"^The thunder of crushing (?<targetname>.+) is followed by the deafening silence of death!$",
|
||||
@"^The deadly force of your attack is so strong that (?<targetname>.+)'s ancestors feel it!$",
|
||||
@"^(?<targetname>.+)'s seared corpse smolders before you!$",
|
||||
@"^(?<targetname>.+) is reduced to cinders!$",
|
||||
@"^(?<targetname>.+) is shattered by your assault!$",
|
||||
@"^(?<targetname>.+) catches your attack, with dire consequences!$",
|
||||
@"^(?<targetname>.+) is utterly destroyed by your attack!$",
|
||||
@"^(?<targetname>.+) suffers a frozen fate!$",
|
||||
@"^(?<targetname>.+)'s perforated corpse falls before you!$",
|
||||
@"^(?<targetname>.+) is fatally punctured!$",
|
||||
@"^(?<targetname>.+)'s death is preceded by a sharp, stabbing pain!$",
|
||||
@"^(?<targetname>.+) is torn to ribbons by your assault!$",
|
||||
@"^(?<targetname>.+) is liquified by your attack!$",
|
||||
@"^(?<targetname>.+)'s last strength dissolves before you!$",
|
||||
@"^Electricity tears (?<targetname>.+) apart!$",
|
||||
@"^Blistered by lightning, (?<targetname>.+) falls!$",
|
||||
@"^(?<targetname>.+)'s last strength withers before you!$",
|
||||
@"^(?<targetname>.+) is dessicated by your attack!$",
|
||||
@"^(?<targetname>.+) is incinerated by your assault!$"
|
||||
};
|
||||
|
||||
internal int TotalKills => _totalKills;
|
||||
internal double KillsPerHour => _killsPerHour;
|
||||
internal double KillsPer5Min => _killsPer5Min;
|
||||
internal int SessionDeaths => _sessionDeaths;
|
||||
internal int TotalDeaths => _totalDeaths;
|
||||
internal DateTime StatsStartTime => _statsStartTime;
|
||||
internal DateTime LastKillTime => _lastKillTime;
|
||||
internal int RareCount { get; set; }
|
||||
|
||||
/// <param name="logger">Logger for chat output</param>
|
||||
/// <param name="onStatsUpdated">Callback(totalKills, killsPer5Min, killsPerHour) for UI updates</param>
|
||||
/// <param name="onElapsedUpdated">Callback(elapsed) for UI elapsed time updates</param>
|
||||
internal KillTracker(IPluginLogger logger, Action<int, double, double> onStatsUpdated, Action<TimeSpan> onElapsedUpdated)
|
||||
{
|
||||
_logger = logger;
|
||||
_onStatsUpdated = onStatsUpdated;
|
||||
_onElapsedUpdated = onElapsedUpdated;
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
_updateTimer = new Timer(Constants.StatsUpdateIntervalMs);
|
||||
_updateTimer.Elapsed += UpdateStats;
|
||||
_updateTimer.Start();
|
||||
}
|
||||
|
||||
internal void Stop()
|
||||
{
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
_updateTimer.Stop();
|
||||
_updateTimer.Dispose();
|
||||
_updateTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool CheckForKill(string text)
|
||||
{
|
||||
if (IsKilledByMeMessage(text))
|
||||
{
|
||||
_totalKills++;
|
||||
_lastKillTime = DateTime.Now;
|
||||
CalculateKillsPerInterval();
|
||||
_onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void OnDeath()
|
||||
{
|
||||
_sessionDeaths++;
|
||||
}
|
||||
|
||||
internal void SetTotalDeaths(int totalDeaths)
|
||||
{
|
||||
_totalDeaths = totalDeaths;
|
||||
}
|
||||
|
||||
internal void RestartStats()
|
||||
{
|
||||
_totalKills = 0;
|
||||
RareCount = 0;
|
||||
_sessionDeaths = 0;
|
||||
_statsStartTime = DateTime.Now;
|
||||
_killsPer5Min = 0;
|
||||
_killsPerHour = 0;
|
||||
|
||||
_logger?.Log($"Stats have been reset. Session deaths: {_sessionDeaths}, Total deaths: {_totalDeaths}");
|
||||
_onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour);
|
||||
}
|
||||
|
||||
private void UpdateStats(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
TimeSpan elapsed = DateTime.Now - _statsStartTime;
|
||||
_onElapsedUpdated?.Invoke(elapsed);
|
||||
|
||||
CalculateKillsPerInterval();
|
||||
_onStatsUpdated?.Invoke(_totalKills, _killsPer5Min, _killsPerHour);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log("Error updating stats: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateKillsPerInterval()
|
||||
{
|
||||
double minutesElapsed = (DateTime.Now - _statsStartTime).TotalMinutes;
|
||||
|
||||
if (minutesElapsed > 0)
|
||||
{
|
||||
_killsPer5Min = (_totalKills / minutesElapsed) * 5;
|
||||
_killsPerHour = (_totalKills / minutesElapsed) * 60;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsKilledByMeMessage(string text)
|
||||
{
|
||||
foreach (string pattern in KillPatterns)
|
||||
{
|
||||
if (Regex.IsMatch(text, pattern))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
169
MosswartMassacre/LiveInventoryTracker.cs
Normal file
169
MosswartMassacre/LiveInventoryTracker.cs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Decal.Adapter;
|
||||
using Decal.Adapter.Wrappers;
|
||||
using Mag.Shared;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends inventory delta events (add/remove/update) via WebSocket
|
||||
/// whenever items change in the player's inventory.
|
||||
/// </summary>
|
||||
internal class LiveInventoryTracker
|
||||
{
|
||||
private readonly IPluginLogger _logger;
|
||||
private readonly HashSet<int> _trackedItemIds = new HashSet<int>();
|
||||
|
||||
internal LiveInventoryTracker(IPluginLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize tracking for all current inventory items.
|
||||
/// Called after login or hot reload, after the full inventory dump.
|
||||
/// </summary>
|
||||
internal void Initialize()
|
||||
{
|
||||
_trackedItemIds.Clear();
|
||||
try
|
||||
{
|
||||
foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
|
||||
{
|
||||
_trackedItemIds.Add(wo.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[LiveInv] Error initializing: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnCreateObject(object sender, CreateObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.New;
|
||||
if (!IsPlayerInventory(item)) return;
|
||||
if (_trackedItemIds.Contains(item.Id)) return;
|
||||
|
||||
_trackedItemIds.Add(item.Id);
|
||||
var mwo = MyWorldObjectCreator.Create(item);
|
||||
_ = WebSocket.SendInventoryDeltaAsync("add", mwo);
|
||||
|
||||
// Request appraisal if item needs full ID data (spells, combat stats, etc.)
|
||||
if (!item.HasIdData && ObjectClassNeedsIdent(item.ObjectClass, item.Name))
|
||||
{
|
||||
CoreManager.Current.Actions.RequestId(item.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[LiveInv] Error in OnCreate: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnReleaseObject(object sender, ReleaseObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.Released;
|
||||
if (!_trackedItemIds.Contains(item.Id)) return;
|
||||
|
||||
_trackedItemIds.Remove(item.Id);
|
||||
_ = WebSocket.SendInventoryRemoveAsync(item.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[LiveInv] Error in OnRelease: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnChangeObject(object sender, ChangeObjectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = e.Changed;
|
||||
if (!IsPlayerInventory(item))
|
||||
{
|
||||
// Item left our inventory
|
||||
if (_trackedItemIds.Contains(item.Id))
|
||||
{
|
||||
_trackedItemIds.Remove(item.Id);
|
||||
_ = WebSocket.SendInventoryRemoveAsync(item.Id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_trackedItemIds.Contains(item.Id))
|
||||
{
|
||||
// New item appeared via ChangeObject
|
||||
_trackedItemIds.Add(item.Id);
|
||||
var mwo = MyWorldObjectCreator.Create(item);
|
||||
_ = WebSocket.SendInventoryDeltaAsync("add", mwo);
|
||||
|
||||
// Request appraisal if item needs full ID data
|
||||
if (!item.HasIdData && ObjectClassNeedsIdent(item.ObjectClass, item.Name))
|
||||
{
|
||||
CoreManager.Current.Actions.RequestId(item.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Existing item changed (equip/unequip, stack change, container move)
|
||||
var mwo = MyWorldObjectCreator.Create(item);
|
||||
_ = WebSocket.SendInventoryDeltaAsync("update", mwo);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[LiveInv] Error in OnChange: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
_trackedItemIds.Clear();
|
||||
}
|
||||
|
||||
private static 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"));
|
||||
}
|
||||
|
||||
private static bool IsPlayerInventory(WorldObject item)
|
||||
{
|
||||
try
|
||||
{
|
||||
int containerId = item.Container;
|
||||
int charId = CoreManager.Current.CharacterFilter.Id;
|
||||
|
||||
// Directly in character's inventory
|
||||
if (containerId == charId) return true;
|
||||
|
||||
// In a side pack owned by the character
|
||||
WorldObject container = CoreManager.Current.WorldFilter[containerId];
|
||||
if (container != null &&
|
||||
container.ObjectClass == ObjectClass.Container &&
|
||||
container.Container == charId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,42 +32,206 @@
|
|||
<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>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>lib\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.Interop.Core, Version=2.9.8.3, Culture=neutral, PublicKeyToken=481f17d392f1fb65, processorArchitecture=MSIL">
|
||||
<Reference Include="Decal.FileService">
|
||||
<HintPath>lib\Decal.FileService.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Core">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>lib\Decal.Interop.Core.DLL</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Filters">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>lib\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">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>lib\Decal.Interop.D3DService.DLL</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Decal.Interop.Input">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>lib\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="utank2-i, Version=1.0.0.0, Culture=neutral, processorArchitecture=x86">
|
||||
<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">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>bin\Debug\utank2-i.dll</HintPath>
|
||||
<HintPath>lib\utank2-i.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="VCS5">
|
||||
<HintPath>lib\VCS5.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="VirindiViewService">
|
||||
<HintPath>lib\VirindiViewService.dll</HintPath>
|
||||
|
|
@ -74,28 +241,111 @@
|
|||
</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="ChatEventRouter.cs" />
|
||||
<Compile Include="CommandRouter.cs" />
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="GameEventRouter.cs" />
|
||||
<Compile Include="IGameStats.cs" />
|
||||
<Compile Include="IPluginLogger.cs" />
|
||||
<Compile Include="QuestStreamingService.cs" />
|
||||
<Compile Include="InventoryMonitor.cs" />
|
||||
<Compile Include="LiveInventoryTracker.cs" />
|
||||
<Compile Include="KillTracker.cs" />
|
||||
<Compile Include="RareTracker.cs" />
|
||||
<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" />
|
||||
<Compile Include="Coordinates.cs" />
|
||||
<Compile Include="Geometry.cs" />
|
||||
<Compile Include="Utils.cs" />
|
||||
<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="CharacterStats.cs" />
|
||||
<Compile Include="WebSocket.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
|
|
@ -104,11 +354,51 @@
|
|||
</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>
|
||||
<Reference Include="Decal">
|
||||
<HintPath>lib\Decal.dll</HintPath>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="DecalNet">
|
||||
<HintPath>lib\decalnet.dll</HintPath>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />
|
||||
</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')" />
|
||||
<!-- Auto-generate CalVer version: YYYY.M.D.HHmm -->
|
||||
<Target Name="SetCalVer" BeforeTargets="CoreCompile">
|
||||
<PropertyGroup>
|
||||
<CalVerVersion>$([System.DateTime]::UtcNow.ToString("yyyy.M.d.HHmm"))</CalVerVersion>
|
||||
<CalVerFile>$(IntermediateOutputPath)CalVer.cs</CalVerFile>
|
||||
</PropertyGroup>
|
||||
<WriteLinesToFile File="$(CalVerFile)" Overwrite="true"
|
||||
Lines="using System.Reflection%3B;[assembly: AssemblyVersion("$(CalVerVersion)")];[assembly: AssemblyFileVersion("$(CalVerVersion)")]" />
|
||||
<ItemGroup>
|
||||
<Compile Include="$(CalVerFile)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -13,25 +13,60 @@ namespace MosswartMassacre
|
|||
private static readonly object _sync = new object();
|
||||
|
||||
// backing fields
|
||||
private bool _remoteCommandsEnabled = false;
|
||||
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()
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance);
|
||||
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||
.IgnoreUnmatchedProperties();
|
||||
var deserializer = builder.Build();
|
||||
|
||||
PluginSettings loaded = null;
|
||||
|
|
@ -100,34 +135,73 @@ namespace MosswartMassacre
|
|||
}
|
||||
|
||||
// public properties
|
||||
public bool RemoteCommandsEnabled
|
||||
{
|
||||
get => _remoteCommandsEnabled;
|
||||
set { _remoteCommandsEnabled = value; Save(); }
|
||||
}
|
||||
|
||||
public bool RareMetaEnabled
|
||||
{
|
||||
get => _rareMetaEnabled;
|
||||
set { _rareMetaEnabled = value; Save(); }
|
||||
}
|
||||
|
||||
public bool HttpServerEnabled
|
||||
public bool WebSocketEnabled
|
||||
{
|
||||
get => _httpServerEnabled;
|
||||
set { _httpServerEnabled = value; Save(); }
|
||||
get => _webSocketEnabled;
|
||||
set { _webSocketEnabled = value; Save(); }
|
||||
}
|
||||
|
||||
public bool TelemetryEnabled
|
||||
{
|
||||
get => _telemetryEnabled;
|
||||
set { _telemetryEnabled = 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,4 @@ using System.Runtime.InteropServices;
|
|||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("9b6a07e1-ae78-47f4-b09c-174f6a27d7a3")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
[assembly: AssemblyVersion("2.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.0.0.0")]
|
||||
// Version is auto-generated at build time (CalVer: YYYY.M.D.HHmm)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
133
MosswartMassacre/QuestStreamingService.cs
Normal file
133
MosswartMassacre/QuestStreamingService.cs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Timers;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Streams high-priority quest timer data via WebSocket on a 30-second interval.
|
||||
/// </summary>
|
||||
internal class QuestStreamingService
|
||||
{
|
||||
private readonly IPluginLogger _logger;
|
||||
private Timer _timer;
|
||||
|
||||
internal QuestStreamingService(IPluginLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
_timer = new Timer(Constants.QuestStreamingIntervalMs);
|
||||
_timer.Elapsed += OnTimerElapsed;
|
||||
_timer.AutoReset = true;
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
internal void Stop()
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Elapsed -= OnTimerElapsed;
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsRunning => _timer != null && _timer.Enabled;
|
||||
|
||||
private void OnTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PluginSettings.Instance?.VerboseLogging == true)
|
||||
{
|
||||
_logger?.Log("[QUEST-STREAM] Timer fired, checking conditions...");
|
||||
}
|
||||
|
||||
if (!PluginCore.WebSocketEnabled)
|
||||
{
|
||||
if (PluginSettings.Instance?.VerboseLogging == true)
|
||||
{
|
||||
_logger?.Log("[QUEST-STREAM] WebSocket not enabled, skipping");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var questManager = PluginCore.questManager;
|
||||
if (questManager?.QuestList == null || questManager.QuestList.Count == 0)
|
||||
{
|
||||
if (PluginSettings.Instance?.VerboseLogging == true)
|
||||
{
|
||||
_logger?.Log($"[QUEST-STREAM] No quest data available (null: {questManager?.QuestList == null}, count: {questManager?.QuestList?.Count ?? 0})");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
var priorityQuests = questManager.QuestList
|
||||
.Where(q => IsHighPriorityQuest(q.Id))
|
||||
.GroupBy(q => q.Id)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
if (PluginSettings.Instance?.VerboseLogging == true)
|
||||
{
|
||||
_logger?.Log($"[QUEST-STREAM] Found {priorityQuests.Count} priority quests to stream");
|
||||
}
|
||||
|
||||
foreach (var quest in priorityQuests)
|
||||
{
|
||||
try
|
||||
{
|
||||
string questName = questManager.GetFriendlyQuestName(quest.Id);
|
||||
long timeRemaining = quest.ExpireTime - currentTime;
|
||||
string countdown = FormatCountdown(timeRemaining);
|
||||
|
||||
if (PluginSettings.Instance?.VerboseLogging == true)
|
||||
{
|
||||
_logger?.Log($"[QUEST-STREAM] Sending: {questName} - {countdown}");
|
||||
}
|
||||
|
||||
System.Threading.Tasks.Task.Run(() => WebSocket.SendQuestDataAsync(questName, countdown));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[QUEST-STREAM] Error streaming quest {quest.Id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[QUEST-STREAM] Error in timer handler: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsHighPriorityQuest(string questId)
|
||||
{
|
||||
return questId == "stipendtimer_0812" ||
|
||||
questId == "augmentationblankgemacquired" ||
|
||||
questId == "insatiableeaterjaw";
|
||||
}
|
||||
|
||||
internal static string FormatCountdown(long seconds)
|
||||
{
|
||||
if (seconds <= 0)
|
||||
return "READY";
|
||||
|
||||
var timeSpan = TimeSpan.FromSeconds(seconds);
|
||||
|
||||
if (timeSpan.TotalDays >= 1)
|
||||
return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours:D2}h";
|
||||
else if (timeSpan.TotalHours >= 1)
|
||||
return $"{timeSpan.Hours}h {timeSpan.Minutes:D2}m";
|
||||
else if (timeSpan.TotalMinutes >= 1)
|
||||
return $"{timeSpan.Minutes}m {timeSpan.Seconds:D2}s";
|
||||
else
|
||||
return $"{timeSpan.Seconds}s";
|
||||
}
|
||||
}
|
||||
}
|
||||
71
MosswartMassacre/RareTracker.cs
Normal file
71
MosswartMassacre/RareTracker.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Decal.Adapter;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracks rare item discoveries, handles rare meta state toggles,
|
||||
/// and sends rare notifications via WebSocket.
|
||||
/// </summary>
|
||||
internal class RareTracker
|
||||
{
|
||||
private readonly IPluginLogger _logger;
|
||||
private readonly string _characterName;
|
||||
|
||||
internal int RareCount { get; set; }
|
||||
internal bool RareMetaEnabled { get; set; } = true;
|
||||
|
||||
internal RareTracker(IPluginLogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_characterName = CoreManager.Current.CharacterFilter.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the chat text is a rare discovery by this character.
|
||||
/// If so, increments count, triggers meta switch, allegiance announce, and WebSocket notification.
|
||||
/// Returns true if a rare was found.
|
||||
/// </summary>
|
||||
internal bool CheckForRare(string text, out string rareText)
|
||||
{
|
||||
if (IsRareDiscoveryMessage(text, out rareText))
|
||||
{
|
||||
RareCount++;
|
||||
|
||||
if (RareMetaEnabled)
|
||||
{
|
||||
PluginCore.Decal_DispatchOnChatCommand("/vt setmetastate loot_rare");
|
||||
}
|
||||
|
||||
DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000);
|
||||
_ = WebSocket.SendRareAsync(rareText);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void ToggleRareMeta()
|
||||
{
|
||||
PluginSettings.Instance.RareMetaEnabled = !PluginSettings.Instance.RareMetaEnabled;
|
||||
RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
|
||||
}
|
||||
|
||||
private bool IsRareDiscoveryMessage(string text, out string rareTextOnly)
|
||||
{
|
||||
rareTextOnly = null;
|
||||
|
||||
string pattern = @"^(?<name>['A-Za-z ]+)\shas discovered the (?<item>.*?)!$";
|
||||
Match match = Regex.Match(text, pattern);
|
||||
|
||||
if (match.Success && match.Groups["name"].Value == _characterName)
|
||||
{
|
||||
rareTextOnly = match.Groups["item"].Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
// Telemetry.cs ───────────────────────────────────────────────────────────────
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Decal.Adapter;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MosswartMassacre
|
||||
{
|
||||
public static class Telemetry
|
||||
{
|
||||
/* ───────────── configuration ───────────── */
|
||||
private const string Endpoint = "https://mosswart.snakedesert.se/position/"; // <- trailing slash!
|
||||
private const string SharedSecret = "your_shared_secret"; // <- keep in sync
|
||||
private const int IntervalSec = 5; // seconds between posts
|
||||
|
||||
/* ───────────── runtime state ───────────── */
|
||||
private static readonly HttpClient _http = new HttpClient();
|
||||
private static string _sessionId;
|
||||
private static CancellationTokenSource _cts;
|
||||
private static bool _enabled;
|
||||
|
||||
/* ───────────── public API ───────────── */
|
||||
public static void Start()
|
||||
{
|
||||
if (_enabled) return;
|
||||
|
||||
_enabled = true;
|
||||
_sessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
PluginCore.WriteToChat("[Telemetry] HTTP streaming ENABLED");
|
||||
|
||||
_ = Task.Run(() => LoopAsync(_cts.Token)); // fire-and-forget
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
if (!_enabled) return;
|
||||
_cts.Cancel();
|
||||
_enabled = false;
|
||||
PluginCore.WriteToChat("[Telemetry] HTTP streaming DISABLED");
|
||||
}
|
||||
|
||||
/* ───────────── async loop ───────────── */
|
||||
private static async Task LoopAsync(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SendSnapshotAsync(token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Telemetry] send failed: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(IntervalSec), token);
|
||||
}
|
||||
catch (TaskCanceledException) { } // expected on Stop()
|
||||
}
|
||||
}
|
||||
|
||||
/* ───────────── single POST ───────────── */
|
||||
private static async Task SendSnapshotAsync(CancellationToken token)
|
||||
{
|
||||
var coords = Coordinates.Me;
|
||||
|
||||
var payload = new
|
||||
{
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
char_tag = PluginCore.CharTag,
|
||||
session_id = _sessionId,
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
|
||||
ew = coords.EW,
|
||||
ns = coords.NS,
|
||||
z = coords.Z,
|
||||
|
||||
kills = PluginCore.totalKills,
|
||||
onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"),
|
||||
kills_per_hour = PluginCore.killsPerHour.ToString("F0"),
|
||||
deaths = 0,
|
||||
rares_found = PluginCore.rareCount,
|
||||
prismatic_taper_count = 0,
|
||||
vt_state = VtankControl.VtGetMetaState(),
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(payload);
|
||||
var req = new HttpRequestMessage(HttpMethod.Post, Endpoint)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
req.Headers.Add("X-Plugin-Secret", SharedSecret);
|
||||
|
||||
using var resp = await _http.SendAsync(req, token);
|
||||
|
||||
if (!resp.IsSuccessStatusCode) // stay quiet on success
|
||||
{
|
||||
PluginCore.WriteToChat($"[Telemetry] server replied {resp.StatusCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
253
MosswartMassacre/UpdateManager.cs
Normal file
253
MosswartMassacre/UpdateManager.cs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for update and auto-install if available. Used by startup auto-update.
|
||||
/// </summary>
|
||||
public static async Task CheckAndInstallAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool checkOk = await CheckForUpdateAsync();
|
||||
if (checkOk && updateAvailable)
|
||||
{
|
||||
PluginCore.WriteToChat("[Update] Auto-installing update...");
|
||||
await DownloadAndInstallUpdateAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginCore.WriteToChat($"[Update] Auto-update failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
150
MosswartMassacre/ViewXML/mainViewTabbed.xml
Normal file
150
MosswartMassacre/ViewXML/mainViewTabbed.xml
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?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"/>
|
||||
|
||||
<!-- WebSocket setting -->
|
||||
<control progid="DecalControls.Checkbox" name="chkWebSocketEnabled" left="20" top="60" width="300" height="20" text="WebSocket streaming" checked="false"/>
|
||||
|
||||
<!-- Character tag setting -->
|
||||
<control progid="DecalControls.StaticText" name="lblCharTag" left="20" top="90" width="100" height="16" text="Character Tag:"/>
|
||||
<control progid="DecalControls.Edit" name="txtCharTag" left="125" top="88" 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";
|
||||
}
|
||||
|
||||
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 v{Constants.PluginVersion}";
|
||||
}
|
||||
|
||||
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 v{Constants.PluginVersion}";
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
1237
MosswartMassacre/Views/VVSTabbedMainView.cs
Normal file
1237
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,9 +72,9 @@ namespace MosswartMassacre
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Swallow any errors and signal failure
|
||||
PluginCore.WriteToChat($"[VTank] SetSetting error ({setting}): {ex.Message}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
412
MosswartMassacre/WebSocket.cs
Normal file
412
MosswartMassacre/WebSocket.cs
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
// 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 = "";
|
||||
private static IPluginLogger _logger;
|
||||
private static IGameStats _gameStats;
|
||||
|
||||
// ─── 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 SetLogger(IPluginLogger logger) => _logger = logger;
|
||||
public static void SetGameStats(IGameStats gameStats) => _gameStats = gameStats;
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
if (_enabled) return;
|
||||
_enabled = true;
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
_logger?.Log("[WebSocket] connecting…");
|
||||
_ = Task.Run(ConnectAndLoopAsync);
|
||||
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
if (!_enabled) return;
|
||||
_enabled = false;
|
||||
|
||||
_cts.Cancel();
|
||||
_ws?.Abort();
|
||||
_ws?.Dispose();
|
||||
_ws = null;
|
||||
|
||||
_logger?.Log("[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);
|
||||
_logger?.Log("[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);
|
||||
_logger?.Log("[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)
|
||||
{
|
||||
_logger?.Log($"[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
|
||||
_logger?.Log("[WebSocket] Starting telemetry loop");
|
||||
while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = BuildPayloadJson();
|
||||
await SendEncodedAsync(json, _cts.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[WebSocket] Telemetry failed: {ex.Message}");
|
||||
break; // Exit telemetry loop on failure
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(IntervalSec), _cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger?.Log("[WebSocket] Telemetry loop cancelled");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Log why telemetry loop exited
|
||||
_logger?.Log($"[WebSocket] Telemetry loop ended - State: {_ws?.State}, Cancelled: {_cts.Token.IsCancellationRequested}");
|
||||
|
||||
// Wait for receive loop to finish
|
||||
await receiveTask;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger?.Log("[WebSocket] Connection cancelled");
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Log($"[WebSocket] Connection error: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
var finalState = _ws?.State.ToString() ?? "null";
|
||||
_logger?.Log($"[WebSocket] Cleaning up connection - Final state: {finalState}");
|
||||
_ws?.Abort();
|
||||
_ws?.Dispose();
|
||||
_ws = null;
|
||||
}
|
||||
|
||||
// Pause before reconnecting
|
||||
if (_enabled)
|
||||
{
|
||||
_logger?.Log("[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 SendInventoryDeltaAsync(string action, Mag.Shared.MyWorldObject item)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "inventory_delta",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
action = action,
|
||||
item = item
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(envelope);
|
||||
await SendEncodedAsync(json, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task SendInventoryRemoveAsync(int itemId)
|
||||
{
|
||||
var envelope = new
|
||||
{
|
||||
type = "inventory_delta",
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
action = "remove",
|
||||
item_id = itemId
|
||||
};
|
||||
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 SendCharacterStatsAsync(object statsData)
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(statsData);
|
||||
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)
|
||||
{
|
||||
_logger?.Log($"[WebSocket] Send error: {ex.Message}");
|
||||
_ws?.Abort();
|
||||
_ws?.Dispose();
|
||||
_ws = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sendLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── payload builder ──────────────────────────────
|
||||
|
||||
private static string BuildPayloadJson()
|
||||
{
|
||||
var tele = new ClientTelemetry();
|
||||
var coords = Coordinates.Me;
|
||||
var stats = _gameStats;
|
||||
var payload = new
|
||||
{
|
||||
type = "telemetry",
|
||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||
char_tag = stats?.CharTag ?? "",
|
||||
session_id = SessionInfo.GuidString,
|
||||
timestamp = DateTime.UtcNow.ToString("o"),
|
||||
ew = coords.EW,
|
||||
ns = coords.NS,
|
||||
z = coords.Z,
|
||||
kills = stats?.TotalKills ?? 0,
|
||||
kills_per_hour = (stats?.KillsPerHour ?? 0).ToString("F0"),
|
||||
onlinetime = (DateTime.Now - (stats?.StatsStartTime ?? DateTime.Now)).ToString(@"dd\.hh\:mm\:ss"),
|
||||
deaths = (stats?.SessionDeaths ?? 0).ToString(),
|
||||
total_deaths = (stats?.TotalDeaths ?? 0).ToString(),
|
||||
prismatic_taper_count = (stats?.CachedPrismaticCount ?? 0).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/Decal.FileService.dll
Normal file
BIN
MosswartMassacre/lib/Decal.FileService.dll
Normal file
Binary file not shown.
BIN
MosswartMassacre/lib/Decal.Interop.D3DService.DLL
Normal file
BIN
MosswartMassacre/lib/Decal.Interop.D3DService.DLL
Normal file
Binary file not shown.
BIN
MosswartMassacre/lib/Decal.Interop.Filters.DLL
Normal file
BIN
MosswartMassacre/lib/Decal.Interop.Filters.DLL
Normal file
Binary file not shown.
BIN
MosswartMassacre/lib/Decal.Interop.Input.DLL
Normal file
BIN
MosswartMassacre/lib/Decal.Interop.Input.DLL
Normal file
Binary file not shown.
BIN
MosswartMassacre/lib/Decal.dll
Normal file
BIN
MosswartMassacre/lib/Decal.dll
Normal file
Binary file not shown.
BIN
MosswartMassacre/lib/VCS5.dll
Normal file
BIN
MosswartMassacre/lib/VCS5.dll
Normal file
Binary file not shown.
BIN
MosswartMassacre/lib/decalnet.dll
Normal file
BIN
MosswartMassacre/lib/decalnet.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"
|
||||
}
|
||||
274
README.md
274
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
|
||||
- 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)
|
||||
## 🚀 Features
|
||||
|
||||
## 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.
|
||||
### 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
|
||||
|
||||
## 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.
|
||||
### 🗺️ 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
|
||||
|
||||
## MosswartMassacre
|
||||
Tracks monster kills and rare drops, with multiple utility features.
|
||||
### 🎛️ 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
|
||||
|
||||
### 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.
|
||||
## 📥 Installation
|
||||
|
||||
### HTTP Command Server
|
||||
- Listens on `http://localhost:8085/`.
|
||||
- Accepts POST data: `target=<player>&command=<text>`, then sends a /tell and executes the command.
|
||||
### Prerequisites
|
||||
- Windows with .NET Framework 4.8
|
||||
- Asheron's Call with DECAL Adapter installed
|
||||
- VirindiViewService (included in lib/ folder)
|
||||
|
||||
### 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.
|
||||
### 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
|
||||
|
||||
### Telemetry
|
||||
- Periodically posts JSON snapshots of position and stats to a configurable endpoint.
|
||||
- Configure `Endpoint`, `SharedSecret`, and `IntervalSec` in `Telemetry.cs`.
|
||||
### Building from Source
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone [repository-url]
|
||||
cd MosswartMassacre
|
||||
|
||||
## Dependencies
|
||||
- Decal.Adapter (v2.9.8.3)
|
||||
- Decal.Interop.Core & Decal.Interop.Inject
|
||||
- VirindiViewService
|
||||
- Newtonsoft.Json (v13.0.3)
|
||||
- YamlDotNet (v16.3.0)
|
||||
# Restore packages and build
|
||||
nuget restore packages.config
|
||||
msbuild MosswartMassacre.csproj /p:Configuration=Release /p:Platform=AnyCPU
|
||||
```
|
||||
|
||||
## 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.
|
||||
## 🎮 Usage
|
||||
|
||||
--
|
||||
_This README provides a high-level overview to get up and running quickly._
|
||||
### 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
|
||||
Settings are stored per-character in YAML format at `<PluginDir>/<CharacterName>.yaml`:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
### 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
|
||||
|
||||
### 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
|
||||
}
|
||||
}
|
||||
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