From 7eec19eccfdc973e19f8bc5f952155b9cbfb8d11 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 21 May 2017 11:19:47 +0200 Subject: [PATCH 01/21] Fixed rename duplicating profiles, closes #362 Fixed delete leaving the profile select empty Added a G810-specific keymap --- Artemis/Artemis/Artemis.csproj | 1 + Artemis/Artemis/DAL/ProfileProvider.cs | 8 +- .../DeviceProviders/Corsair/CorsairHeadset.cs | 15 +- .../DeviceProviders/Corsair/CorsairMouse.cs | 13 +- .../Corsair/CorsairMousemat.cs | 13 +- .../Artemis/DeviceProviders/Logitech/G810.cs | 58 ++- .../Artemis/DeviceProviders/Logitech/G910.cs | 150 ++++++- .../Logitech/LogitechGeneric.cs | 110 ++--- .../Logitech/LogitechKeyboard.cs | 26 +- .../Logitech/Utilities/KeyMap.cs | 8 +- .../Logitech/Utilities/KeyMapG810.cs | 392 ++++++++++++++++++ .../Logitech/Utilities/OrionUtilities.cs | 158 +------ Artemis/Artemis/Models/ProfileEditorModel.cs | 9 +- .../Artemis/Modules/Abstract/ModuleModel.cs | 4 +- 14 files changed, 712 insertions(+), 253 deletions(-) create mode 100644 Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMapG810.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 5f0aea86c..4a43c96f0 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -340,6 +340,7 @@ + MarkdownDialog.xaml diff --git a/Artemis/Artemis/DAL/ProfileProvider.cs b/Artemis/Artemis/DAL/ProfileProvider.cs index 9a64c6446..9fea4dfc2 100644 --- a/Artemis/Artemis/DAL/ProfileProvider.cs +++ b/Artemis/Artemis/DAL/ProfileProvider.cs @@ -102,12 +102,16 @@ namespace Artemis.DAL if (string.IsNullOrEmpty(name)) return; - // Remove the old profile - DeleteProfile(profile); + // Store the profile path before it is renamed + var oldPath = ProfileFolder + $@"\{profile.KeyboardSlug}\{profile.GameName}\{profile.Slug}.json"; // Update the profile, creating a new file profile.Name = name; AddOrUpdate(profile); + + // Remove the file with the old name + if (File.Exists(oldPath)) + File.Delete(oldPath); } public static void DeleteProfile(ProfileModel prof) diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs index ca842fd0e..1bf18b973 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs @@ -23,9 +23,16 @@ namespace Artemis.DeviceProviders.Corsair public override bool TryEnable() { - CanUse = CanInitializeSdk(); - if (CanUse && !CueSDK.IsInitialized) - CueSDK.Initialize(); + try + { + CanUse = CanInitializeSdk(); + if (CanUse && !CueSDK.IsInitialized) + CueSDK.Initialize(); + } + catch (Exception) + { + CanUse = false; + } Logger.Debug("Attempted to enable Corsair headset. CanUse: {0}", CanUse); @@ -69,4 +76,4 @@ namespace Artemis.DeviceProviders.Corsair return false; } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs index 301f07781..8baade687 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs @@ -23,9 +23,16 @@ namespace Artemis.DeviceProviders.Corsair public override bool TryEnable() { - CanUse = CanInitializeSdk(); - if (CanUse && !CueSDK.IsInitialized) - CueSDK.Initialize(); + try + { + CanUse = CanInitializeSdk(); + if (CanUse && !CueSDK.IsInitialized) + CueSDK.Initialize(); + } + catch (Exception) + { + CanUse = false; + } Logger.Debug("Attempted to enable Corsair mice. CanUse: {0}", CanUse); diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs index 0a3ae1283..d8c1ccec3 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs @@ -23,9 +23,16 @@ namespace Artemis.DeviceProviders.Corsair public override bool TryEnable() { - CanUse = CanInitializeSdk(); - if (CanUse && !CueSDK.IsInitialized) - CueSDK.Initialize(); + try + { + CanUse = CanInitializeSdk(); + if (CanUse && !CueSDK.IsInitialized) + CueSDK.Initialize(); + } + catch (Exception) + { + CanUse = false; + } Logger.Debug("Attempted to enable Corsair mousemat. CanUse: {0}", CanUse); diff --git a/Artemis/Artemis/DeviceProviders/Logitech/G810.cs b/Artemis/Artemis/DeviceProviders/Logitech/G810.cs index 75b82713d..d6d7538c8 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/G810.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/G810.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Drawing; +using System.Linq; using System.Windows; using System.Windows.Forms; using Artemis.DAL; @@ -20,23 +21,68 @@ namespace Artemis.DeviceProviders.Logitech "Please check your cables and updating the Logitech Gaming Software\n" + "A minimum version of 8.81.15 is required.\n\n" + "If needed, you can select a different keyboard in Artemis under settings."; - Height = 6; + Height = 7; Width = 21; - PreviewSettings = new PreviewSettings(new Rect(19, 70, 1010, 269), Resources.g810); + PreviewSettings = new PreviewSettings(new Rect(19, 36, 1010, 304), Resources.g810); _generalSettings = SettingsProvider.Load(); } + /// + /// The G910 also updates the G-logo, G-badge and G-keys + /// + /// + public override void DrawBitmap(Bitmap bitmap) + { + using (var croppedBitmap = new Bitmap(21 * 4, 6 * 4)) + { + // Deal with non-standard DPI + croppedBitmap.SetResolution(96, 96); + // Don't forget that the image is upscaled 4 times + using (var g = Graphics.FromImage(croppedBitmap)) + { + g.DrawImage(bitmap, new Rectangle(0, 0, 84, 24), new Rectangle(4, 4, 84, 24), GraphicsUnit.Pixel); + } + + LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_PERKEY_RGB); + // TODO: Remapping + LogitechGSDK.LogiLedSetLightingFromBitmap(OrionUtilities.BitmapToByteArray(bitmap)); + } + + using (var resized = OrionUtilities.ResizeImage(bitmap, 21, 7)) + { + // Color G-logo, lets also try some other values to see what happens + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_LOGO, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_BADGE, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_1, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_2, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_3, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_4, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_5, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_6, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_7, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_8, 0, 0); + SetLogitechColorFromCoordinates(resized, KeyboardNames.G_9, 0, 0); + } + } + public override KeyMatch? GetKeyPosition(Keys keyCode) { + KeyMatch value; switch (_generalSettings.Layout) { case "Qwerty": - return KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + value = KeyMap.QwertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + break; case "Qwertz": - return KeyMap.QwertzLayout.FirstOrDefault(k => k.KeyCode == keyCode); + value = KeyMap.QwertzLayout.FirstOrDefault(k => k.KeyCode == keyCode); + break; default: - return KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + value = KeyMap.AzertyLayout.FirstOrDefault(k => k.KeyCode == keyCode); + break; } + + // Adjust the distance by 1 on y for the G810 + return new KeyMatch(value.KeyCode, value.X, value.Y + 1); } } } \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/Logitech/G910.cs b/Artemis/Artemis/DeviceProviders/Logitech/G910.cs index 7339d8fac..295c5be3c 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/G910.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/G910.cs @@ -64,7 +64,8 @@ namespace Artemis.DeviceProviders.Logitech g.DrawImage(bitmap, new Rectangle(0, 0, 84, 24), new Rectangle(4, 4, 84, 24), GraphicsUnit.Pixel); } - base.DrawBitmap(croppedBitmap); + LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_PERKEY_RGB); + LogitechGSDK.LogiLedSetLightingFromBitmap(OrionUtilities.BitmapToByteArray(bitmap, G910Keymappings)); } using (var resized = OrionUtilities.ResizeImage(bitmap, 22, 7)) @@ -88,14 +89,147 @@ namespace Artemis.DeviceProviders.Logitech } } - private void SetLogitechColorFromCoordinates(Bitmap bitmap, KeyboardNames key, int x, int y) + // These mappings are used by the G910 to fix alignments + public static OrionUtilities.KeyMapping[] G910Keymappings = { - var color = bitmap.GetPixel(x, y); - var rPer = (int) Math.Round(color.R / 2.55); - var gPer = (int) Math.Round(color.G / 2.55); - var bPer = (int) Math.Round(color.B / 2.55); + // First row + new OrionUtilities.KeyMapping(0, 0), + new OrionUtilities.KeyMapping(1, 1), + new OrionUtilities.KeyMapping(2, 1), + new OrionUtilities.KeyMapping(3, 2), + new OrionUtilities.KeyMapping(4, 3), + new OrionUtilities.KeyMapping(5, 4), + new OrionUtilities.KeyMapping(6, 5), + new OrionUtilities.KeyMapping(7, 6), + new OrionUtilities.KeyMapping(8, 7), + new OrionUtilities.KeyMapping(9, 8), + new OrionUtilities.KeyMapping(10, 9), + new OrionUtilities.KeyMapping(11, 9), + new OrionUtilities.KeyMapping(12, 10), + new OrionUtilities.KeyMapping(13, 11), + new OrionUtilities.KeyMapping(13, 12), + new OrionUtilities.KeyMapping(14, 13), + new OrionUtilities.KeyMapping(15, 14), + new OrionUtilities.KeyMapping(16, 15), + new OrionUtilities.KeyMapping(17, 16), + new OrionUtilities.KeyMapping(18, 17), + new OrionUtilities.KeyMapping(19, 18), - LogitechGSDK.LogiLedSetLightingForKeyWithKeyName(key, rPer, gPer, bPer); - } + // Second row + new OrionUtilities.KeyMapping(21, 21), + new OrionUtilities.KeyMapping(22, 22), + new OrionUtilities.KeyMapping(23, 23), + new OrionUtilities.KeyMapping(24, 24), + new OrionUtilities.KeyMapping(25, 25), + new OrionUtilities.KeyMapping(26, 26), + new OrionUtilities.KeyMapping(27, 27), + new OrionUtilities.KeyMapping(28, 28), + new OrionUtilities.KeyMapping(29, 29), + new OrionUtilities.KeyMapping(30, 30), + new OrionUtilities.KeyMapping(31, 31), + new OrionUtilities.KeyMapping(32, 32), + new OrionUtilities.KeyMapping(33, 33), + new OrionUtilities.KeyMapping(34, 34), + new OrionUtilities.KeyMapping(35, 35), + new OrionUtilities.KeyMapping(36, 36), + new OrionUtilities.KeyMapping(37, 37), + new OrionUtilities.KeyMapping(38, 38), + new OrionUtilities.KeyMapping(39, 39), + new OrionUtilities.KeyMapping(40, 40), + new OrionUtilities.KeyMapping(41, 41), + + // Third row + new OrionUtilities.KeyMapping(42, 42), + new OrionUtilities.KeyMapping(43, 43), + new OrionUtilities.KeyMapping(44, 44), + new OrionUtilities.KeyMapping(45, 45), + new OrionUtilities.KeyMapping(46, 46), + new OrionUtilities.KeyMapping(47, 46), + new OrionUtilities.KeyMapping(48, 47), + new OrionUtilities.KeyMapping(49, 48), + new OrionUtilities.KeyMapping(50, 49), + new OrionUtilities.KeyMapping(51, 50), + new OrionUtilities.KeyMapping(52, 51), + new OrionUtilities.KeyMapping(53, 52), + new OrionUtilities.KeyMapping(54, 53), + new OrionUtilities.KeyMapping(54, 54), + new OrionUtilities.KeyMapping(55, 55), + new OrionUtilities.KeyMapping(56, 56), + new OrionUtilities.KeyMapping(57, 57), + new OrionUtilities.KeyMapping(58, 58), + new OrionUtilities.KeyMapping(59, 59), + new OrionUtilities.KeyMapping(60, 60), + new OrionUtilities.KeyMapping(61, 61), + new OrionUtilities.KeyMapping(62, 62), + + // Fourth row + new OrionUtilities.KeyMapping(63, 63), + new OrionUtilities.KeyMapping(64, 64), + new OrionUtilities.KeyMapping(65, 65), + new OrionUtilities.KeyMapping(66, 65), + new OrionUtilities.KeyMapping(67, 66), + new OrionUtilities.KeyMapping(68, 67), + new OrionUtilities.KeyMapping(69, 68), + new OrionUtilities.KeyMapping(70, 69), + new OrionUtilities.KeyMapping(71, 70), + new OrionUtilities.KeyMapping(72, 71), + new OrionUtilities.KeyMapping(73, 72), + new OrionUtilities.KeyMapping(74, 73), + new OrionUtilities.KeyMapping(75, 74), + new OrionUtilities.KeyMapping(76, 75), + new OrionUtilities.KeyMapping(76, 76), + new OrionUtilities.KeyMapping(78, 77), + new OrionUtilities.KeyMapping(79, 78), + new OrionUtilities.KeyMapping(79, 79), + new OrionUtilities.KeyMapping(80, 80), + new OrionUtilities.KeyMapping(81, 81), + new OrionUtilities.KeyMapping(82, 82), + + // Fifth row + new OrionUtilities.KeyMapping(84, 84), + new OrionUtilities.KeyMapping(85, 85), + new OrionUtilities.KeyMapping(86, 86), + new OrionUtilities.KeyMapping(87, 87), + new OrionUtilities.KeyMapping(88, 88), + new OrionUtilities.KeyMapping(89, 89), + new OrionUtilities.KeyMapping(90, 90), + new OrionUtilities.KeyMapping(91, 91), + new OrionUtilities.KeyMapping(92, 92), + new OrionUtilities.KeyMapping(93, 93), + new OrionUtilities.KeyMapping(94, 94), + new OrionUtilities.KeyMapping(95, 95), + new OrionUtilities.KeyMapping(96, 96), + new OrionUtilities.KeyMapping(97, 97), + new OrionUtilities.KeyMapping(98, 98), + new OrionUtilities.KeyMapping(99, 99), + new OrionUtilities.KeyMapping(100, 100), + new OrionUtilities.KeyMapping(101, 101), + new OrionUtilities.KeyMapping(102, 102), + new OrionUtilities.KeyMapping(103, 103), + new OrionUtilities.KeyMapping(104, 104), + + // Sixth row + new OrionUtilities.KeyMapping(105, 105), + new OrionUtilities.KeyMapping(106, 106), + new OrionUtilities.KeyMapping(107, 107), + new OrionUtilities.KeyMapping(108, 107), + new OrionUtilities.KeyMapping(109, 109), + new OrionUtilities.KeyMapping(110, 110), + new OrionUtilities.KeyMapping(111, 110), + new OrionUtilities.KeyMapping(112, 111), + new OrionUtilities.KeyMapping(113, 112), + new OrionUtilities.KeyMapping(114, 113), + new OrionUtilities.KeyMapping(115, 114), + new OrionUtilities.KeyMapping(116, 115), + new OrionUtilities.KeyMapping(115, 116), // ALTGR + new OrionUtilities.KeyMapping(116, 117), + new OrionUtilities.KeyMapping(117, 118), + new OrionUtilities.KeyMapping(118, 119), + new OrionUtilities.KeyMapping(119, 120), + new OrionUtilities.KeyMapping(120, 121), + new OrionUtilities.KeyMapping(121, 122), + new OrionUtilities.KeyMapping(122, 123), + new OrionUtilities.KeyMapping(124, 124) + }; } } diff --git a/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs b/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs index 9c978a791..c0fc32556 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs @@ -1,55 +1,55 @@ -using System; -using System.Drawing; -using Artemis.DeviceProviders.Logitech.Utilities; -using Ninject.Extensions.Logging; - -namespace Artemis.DeviceProviders.Logitech -{ - // TODO: Handle shutdown, maybe implement Disable() afterall? - public class LogitechGeneric : DeviceProvider - { - /// - /// A generic Logitech DeviceProvider. Because the Logitech SDK currently doesn't allow specific - /// device targeting (only very broad per-key-RGB and full RGB etc..) - /// - public LogitechGeneric(ILogger logger) - { - Logger = logger; - Type = DeviceType.Generic; - } - - public ILogger Logger { get; set; } - - public override void UpdateDevice(Bitmap bitmap) - { - if (!CanUse || bitmap == null) - return; - - var col = bitmap.GetPixel(bitmap.Width/2, bitmap.Height/2); - LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_RGB); - LogitechGSDK.LogiLedSetLighting((int) (col.R/2.55), (int) (col.G/2.55), (int) (col.B/2.55)); - } - - public override bool TryEnable() - { - var majorNum = 0; - var minorNum = 0; - var buildNum = 0; - - LogitechGSDK.LogiLedInit(); - LogitechGSDK.LogiLedGetSdkVersion(ref majorNum, ref minorNum, ref buildNum); - - // Turn it into one long number... - var version = int.Parse($"{majorNum}{minorNum}{buildNum}"); - CanUse = version >= 88115; - Logger.Debug("Attempted to enable Logitech generic device. CanUse: {0}", CanUse); - - return CanUse; - } - - public override void Disable() - { - throw new NotSupportedException("Can only disable a keyboard"); - } - } -} \ No newline at end of file +//using System; +//using System.Drawing; +//using Artemis.DeviceProviders.Logitech.Utilities; +//using Ninject.Extensions.Logging; +// +//namespace Artemis.DeviceProviders.Logitech +//{ +// // TODO: Handle shutdown, maybe implement Disable() afterall? +// public class LogitechGeneric : DeviceProvider +// { +// /// +// /// A generic Logitech DeviceProvider. Because the Logitech SDK currently doesn't allow specific +// /// device targeting (only very broad per-key-RGB and full RGB etc..) +// /// +// public LogitechGeneric(ILogger logger) +// { +// Logger = logger; +// Type = DeviceType.Generic; +// } +// +// public ILogger Logger { get; set; } +// +// public override void UpdateDevice(Bitmap bitmap) +// { +// if (!CanUse || bitmap == null) +// return; +// +// var col = bitmap.GetPixel(bitmap.Width/2, bitmap.Height/2); +// LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_RGB); +// LogitechGSDK.LogiLedSetLighting((int) (col.R/2.55), (int) (col.G/2.55), (int) (col.B/2.55)); +// } +// +// public override bool TryEnable() +// { +// var majorNum = 0; +// var minorNum = 0; +// var buildNum = 0; +// +// LogitechGSDK.LogiLedInit(); +// LogitechGSDK.LogiLedGetSdkVersion(ref majorNum, ref minorNum, ref buildNum); +// +// // Turn it into one long number... +// var version = int.Parse($"{majorNum}{minorNum}{buildNum}"); +// CanUse = version >= 88115; +// Logger.Debug("Attempted to enable Logitech generic device. CanUse: {0}", CanUse); +// +// return CanUse; +// } +// +// public override void Disable() +// { +// throw new NotSupportedException("Can only disable a keyboard"); +// } +// } +//} \ No newline at end of file diff --git a/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs b/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs index ec8ecd391..72ffe9926 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs @@ -1,4 +1,5 @@ -using System.Drawing; +using System; +using System.Drawing; using System.Threading; using Artemis.DeviceProviders.Logitech.Utilities; using Artemis.Utilities.DataReaders; @@ -12,18 +13,7 @@ namespace Artemis.DeviceProviders.Logitech { // Just to be sure, restore the Logitech DLL registry key DllManager.RestoreLogitechDll(); - - // Check to see if VC++ 2012 x64 is installed. - if (Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Classes\Installer\Dependencies\{ca67548a-5ebe-413a-b50c-4b9ceb6d66c6}") == null) - { - CantEnableText = "Couldn't connect to your Logitech keyboard.\n" + - "The Visual C++ 2012 Redistributable v11.0.61030.0 could not be found, which is required.\n" + - "Please download it by going to the following URL (link also in wiki):\n\n" + - "https://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe"; - - return false; - } - + int majorNum = 0, minorNum = 0, buildNum = 0; LogitechGSDK.LogiLedInit(); @@ -58,10 +48,14 @@ namespace Artemis.DeviceProviders.Logitech LogitechGSDK.LogiLedShutdown(); } - public override void DrawBitmap(Bitmap bitmap) + protected void SetLogitechColorFromCoordinates(Bitmap bitmap, KeyboardNames key, int x, int y) { - LogitechGSDK.LogiLedSetTargetDevice(LogitechGSDK.LOGI_DEVICETYPE_PERKEY_RGB); - LogitechGSDK.LogiLedSetLightingFromBitmap(OrionUtilities.BitmapToByteArray(bitmap)); + var color = bitmap.GetPixel(x, y); + var rPer = (int)Math.Round(color.R / 2.55); + var gPer = (int)Math.Round(color.G / 2.55); + var bPer = (int)Math.Round(color.B / 2.55); + + LogitechGSDK.LogiLedSetLightingForKeyWithKeyName(key, rPer, gPer, bPer); } } } diff --git a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMap.cs b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMap.cs index 140b16380..eb31a9669 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMap.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMap.cs @@ -132,7 +132,7 @@ namespace Artemis.DeviceProviders.Logitech.Utilities new KeyMatch(Keys.Decimal, 19, 5) }; - #endregion + #endregion #region Qwertz @@ -257,7 +257,7 @@ namespace Artemis.DeviceProviders.Logitech.Utilities new KeyMatch(Keys.Decimal, 19, 5) }; - #endregion + #endregion #region Azerty @@ -384,9 +384,9 @@ namespace Artemis.DeviceProviders.Logitech.Utilities #endregion } - + public static List QwertyLayout { get; set; } public static List QwertzLayout { get; set; } public static List AzertyLayout { get; set; } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMapG810.cs b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMapG810.cs new file mode 100644 index 000000000..35b38389c --- /dev/null +++ b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/KeyMapG810.cs @@ -0,0 +1,392 @@ +using System.Collections.Generic; +using System.Windows.Forms; + +namespace Artemis.DeviceProviders.Logitech.Utilities +{ + public static class KeyMapG810 + { + static KeyMapG810() + { + // There are several keyboard layouts + + #region Qwerty + + QwertyLayout = new List + { + // Row 1 + new KeyMatch(Keys.Escape, 0, 0), + new KeyMatch(Keys.F1, 2, 0), + new KeyMatch(Keys.F2, 3, 0), + new KeyMatch(Keys.F3, 4, 0), + new KeyMatch(Keys.F4, 5, 0), + new KeyMatch(Keys.F5, 6, 0), + new KeyMatch(Keys.F6, 7, 0), + new KeyMatch(Keys.F7, 8, 0), + new KeyMatch(Keys.F8, 9, 0), + new KeyMatch(Keys.F9, 11, 0), + new KeyMatch(Keys.F10, 12, 0), + new KeyMatch(Keys.F11, 13, 0), + new KeyMatch(Keys.F12, 14, 0), + new KeyMatch(Keys.PrintScreen, 15, 0), + new KeyMatch(Keys.Scroll, 16, 0), + new KeyMatch(Keys.Pause, 17, 0), + + // Row 2 + new KeyMatch(Keys.Oemtilde, 0, 1), + new KeyMatch(Keys.D1, 1, 1), + new KeyMatch(Keys.D2, 2, 1), + new KeyMatch(Keys.D3, 3, 1), + new KeyMatch(Keys.D4, 4, 1), + new KeyMatch(Keys.D5, 5, 1), + new KeyMatch(Keys.D6, 6, 1), + new KeyMatch(Keys.D7, 7, 1), + new KeyMatch(Keys.D8, 8, 1), + new KeyMatch(Keys.D9, 9, 1), + new KeyMatch(Keys.D0, 10, 1), + new KeyMatch(Keys.OemMinus, 11, 1), + new KeyMatch(Keys.Oemplus, 12, 1), + new KeyMatch(Keys.Back, 13, 1), + new KeyMatch(Keys.Insert, 14, 1), + new KeyMatch(Keys.Home, 15, 1), + new KeyMatch(Keys.PageUp, 16, 1), + new KeyMatch(Keys.NumLock, 17, 1), + new KeyMatch(Keys.Divide, 18, 1), + new KeyMatch(Keys.Multiply, 19, 1), + new KeyMatch(Keys.Subtract, 20, 1), + + // Row 3 + new KeyMatch(Keys.Tab, 0, 2), + new KeyMatch(Keys.Q, 1, 2), + new KeyMatch(Keys.W, 2, 2), + new KeyMatch(Keys.E, 3, 2), + new KeyMatch(Keys.R, 5, 2), + new KeyMatch(Keys.T, 6, 2), + new KeyMatch(Keys.Y, 7, 2), + new KeyMatch(Keys.U, 8, 2), + new KeyMatch(Keys.I, 9, 2), + new KeyMatch(Keys.O, 10, 2), + new KeyMatch(Keys.P, 11, 2), + new KeyMatch(Keys.OemOpenBrackets, 12, 2), + new KeyMatch(Keys.Oem6, 13, 2), + new KeyMatch(Keys.Delete, 14, 2), + new KeyMatch(Keys.End, 15, 2), + new KeyMatch(Keys.Next, 16, 2), + new KeyMatch(Keys.NumPad7, 17, 2), + new KeyMatch(Keys.NumPad8, 18, 2), + new KeyMatch(Keys.NumPad9, 19, 2), + new KeyMatch(Keys.Add, 20, 2), + + // Row 4 + new KeyMatch(Keys.Capital, 0, 3), + new KeyMatch(Keys.A, 1, 3), + new KeyMatch(Keys.S, 3, 3), + new KeyMatch(Keys.D, 4, 3), + new KeyMatch(Keys.F, 5, 3), + new KeyMatch(Keys.G, 6, 3), + new KeyMatch(Keys.H, 7, 3), + new KeyMatch(Keys.J, 8, 3), + new KeyMatch(Keys.K, 9, 3), + new KeyMatch(Keys.L, 10, 3), + new KeyMatch(Keys.Oem1, 11, 3), + new KeyMatch(Keys.Oem7, 12, 3), + new KeyMatch(Keys.Oem5, 13, 3), + new KeyMatch(Keys.Return, 14, 3), + new KeyMatch(Keys.NumPad4, 17, 3), + new KeyMatch(Keys.NumPad5, 18, 3), + new KeyMatch(Keys.NumPad6, 19, 3), + + // Row 5 + new KeyMatch(Keys.LShiftKey, 1, 4), + new KeyMatch(Keys.OemBackslash, 2, 4), + new KeyMatch(Keys.Z, 2, 4), + new KeyMatch(Keys.X, 3, 4), + new KeyMatch(Keys.C, 4, 4), + new KeyMatch(Keys.V, 5, 4), + new KeyMatch(Keys.B, 6, 4), + new KeyMatch(Keys.N, 7, 4), + new KeyMatch(Keys.M, 8, 4), + new KeyMatch(Keys.Oemcomma, 9, 4), + new KeyMatch(Keys.OemPeriod, 10, 4), + new KeyMatch(Keys.OemQuestion, 11, 4), + new KeyMatch(Keys.RShiftKey, 13, 4), + new KeyMatch(Keys.Up, 15, 4), + new KeyMatch(Keys.NumPad1, 17, 4), + new KeyMatch(Keys.NumPad2, 18, 4), + new KeyMatch(Keys.NumPad3, 19, 4), + // Both returns return "Return" (Yes...) + // new OrionKey(System.Windows.Forms.Keys.Return, 20, 4), + + // Row 6 + new KeyMatch(Keys.LControlKey, 0, 5), + new KeyMatch(Keys.LWin, 1, 5), + new KeyMatch(Keys.LMenu, 3, 5), + new KeyMatch(Keys.Space, 6, 5), + new KeyMatch(Keys.RMenu, 11, 5), + new KeyMatch(Keys.RWin, 12, 5), + new KeyMatch(Keys.Apps, 13, 5), + new KeyMatch(Keys.RControlKey, 14, 5), + new KeyMatch(Keys.Left, 15, 5), + new KeyMatch(Keys.Down, 16, 5), + new KeyMatch(Keys.Right, 17, 5), + new KeyMatch(Keys.NumPad0, 18, 5), + new KeyMatch(Keys.Decimal, 19, 5) + }; + + #endregion + + #region Qwertz + + QwertzLayout = new List + { + // Row 1 + new KeyMatch(Keys.Escape, 0, 0), + new KeyMatch(Keys.F1, 2, 0), + new KeyMatch(Keys.F2, 3, 0), + new KeyMatch(Keys.F3, 4, 0), + new KeyMatch(Keys.F4, 5, 0), + new KeyMatch(Keys.F5, 6, 0), + new KeyMatch(Keys.F6, 7, 0), + new KeyMatch(Keys.F7, 8, 0), + new KeyMatch(Keys.F8, 9, 0), + new KeyMatch(Keys.F9, 11, 0), + new KeyMatch(Keys.F10, 12, 0), // returns 'None' + new KeyMatch(Keys.F11, 13, 0), + new KeyMatch(Keys.F12, 14, 0), + new KeyMatch(Keys.PrintScreen, 15, 0), + new KeyMatch(Keys.Scroll, 16, 0), + new KeyMatch(Keys.Pause, 17, 0), + + // Row 2 + new KeyMatch(Keys.Oem5, 0, 1), + new KeyMatch(Keys.D1, 1, 1), + new KeyMatch(Keys.D2, 2, 1), + new KeyMatch(Keys.D3, 3, 1), + new KeyMatch(Keys.D4, 4, 1), + new KeyMatch(Keys.D5, 5, 1), + new KeyMatch(Keys.D6, 6, 1), + new KeyMatch(Keys.D7, 7, 1), + new KeyMatch(Keys.D8, 8, 1), + new KeyMatch(Keys.D9, 9, 1), + new KeyMatch(Keys.D0, 10, 1), + new KeyMatch(Keys.OemOpenBrackets, 11, 1), + new KeyMatch(Keys.Oem6, 12, 1), + new KeyMatch(Keys.Back, 13, 1), + new KeyMatch(Keys.Insert, 14, 1), + new KeyMatch(Keys.Home, 15, 1), + new KeyMatch(Keys.PageUp, 16, 1), + new KeyMatch(Keys.NumLock, 17, 1), + new KeyMatch(Keys.Divide, 18, 1), + new KeyMatch(Keys.Multiply, 19, 1), + new KeyMatch(Keys.Subtract, 20, 1), + + // Row 3 + new KeyMatch(Keys.Tab, 0, 2), + new KeyMatch(Keys.Q, 1, 2), + new KeyMatch(Keys.W, 2, 2), + new KeyMatch(Keys.E, 3, 2), + new KeyMatch(Keys.R, 5, 2), + new KeyMatch(Keys.T, 6, 2), + new KeyMatch(Keys.Z, 7, 2), + new KeyMatch(Keys.U, 8, 2), + new KeyMatch(Keys.I, 9, 2), + new KeyMatch(Keys.O, 10, 2), + new KeyMatch(Keys.P, 11, 2), + new KeyMatch(Keys.Oem1, 12, 2), + new KeyMatch(Keys.Oemplus, 13, 2), + new KeyMatch(Keys.Delete, 14, 2), + new KeyMatch(Keys.End, 15, 2), + new KeyMatch(Keys.Next, 16, 2), + new KeyMatch(Keys.NumPad7, 17, 2), + new KeyMatch(Keys.NumPad8, 18, 2), + new KeyMatch(Keys.NumPad9, 19, 2), + new KeyMatch(Keys.Add, 20, 2), + + // Row 4 + new KeyMatch(Keys.Capital, 0, 3), + new KeyMatch(Keys.A, 1, 3), + new KeyMatch(Keys.S, 3, 3), + new KeyMatch(Keys.D, 4, 3), + new KeyMatch(Keys.F, 5, 3), + new KeyMatch(Keys.G, 6, 3), + new KeyMatch(Keys.H, 7, 3), + new KeyMatch(Keys.J, 8, 3), + new KeyMatch(Keys.K, 9, 3), + new KeyMatch(Keys.L, 10, 3), + new KeyMatch(Keys.Oemtilde, 11, 3), + new KeyMatch(Keys.Oem7, 12, 3), + new KeyMatch(Keys.OemQuestion, 13, 3), + new KeyMatch(Keys.Return, 14, 3), + new KeyMatch(Keys.NumPad4, 17, 3), + new KeyMatch(Keys.NumPad5, 18, 3), + new KeyMatch(Keys.NumPad6, 19, 3), + + // Row 5 + new KeyMatch(Keys.LShiftKey, 1, 4), + new KeyMatch(Keys.OemBackslash, 2, 4), + new KeyMatch(Keys.Y, 2, 4), + new KeyMatch(Keys.X, 3, 4), + new KeyMatch(Keys.C, 4, 4), + new KeyMatch(Keys.V, 5, 4), + new KeyMatch(Keys.B, 6, 4), + new KeyMatch(Keys.N, 7, 4), + new KeyMatch(Keys.M, 8, 4), + new KeyMatch(Keys.Oemcomma, 9, 4), + new KeyMatch(Keys.OemPeriod, 10, 4), + new KeyMatch(Keys.OemMinus, 11, 4), + new KeyMatch(Keys.RShiftKey, 13, 4), + new KeyMatch(Keys.Up, 15, 4), + new KeyMatch(Keys.NumPad1, 17, 4), + new KeyMatch(Keys.NumPad2, 18, 4), + new KeyMatch(Keys.NumPad3, 19, 4), + // Both returns return "Return" (Yes...) + // new OrionKey(System.Windows.Forms.Keys.Return, 20, 4), + + // Row 6 + new KeyMatch(Keys.LControlKey, 0, 5), + new KeyMatch(Keys.LWin, 1, 5), + new KeyMatch(Keys.Menu, 3, 5), // returns 'None' + new KeyMatch(Keys.Space, 6, 5), + new KeyMatch(Keys.RMenu, 11, 5), + new KeyMatch(Keys.RWin, 12, 5), + new KeyMatch(Keys.Apps, 13, 5), + new KeyMatch(Keys.RControlKey, 14, 5), + new KeyMatch(Keys.Left, 15, 5), + new KeyMatch(Keys.Down, 16, 5), + new KeyMatch(Keys.Right, 17, 5), + new KeyMatch(Keys.NumPad0, 18, 5), + new KeyMatch(Keys.Decimal, 19, 5) + }; + + #endregion + + #region Azerty + + AzertyLayout = new List + { + // Row 1 + new KeyMatch(Keys.Escape, 0, 0), + new KeyMatch(Keys.F1, 2, 0), + new KeyMatch(Keys.F2, 3, 0), + new KeyMatch(Keys.F3, 4, 0), + new KeyMatch(Keys.F4, 5, 0), + new KeyMatch(Keys.F5, 6, 0), + new KeyMatch(Keys.F6, 7, 0), + new KeyMatch(Keys.F7, 8, 0), + new KeyMatch(Keys.F8, 9, 0), + new KeyMatch(Keys.F9, 11, 0), + new KeyMatch(Keys.F10, 12, 0), + new KeyMatch(Keys.F11, 13, 0), + new KeyMatch(Keys.F12, 14, 0), + new KeyMatch(Keys.PrintScreen, 15, 0), + new KeyMatch(Keys.Scroll, 16, 0), + new KeyMatch(Keys.Pause, 17, 0), + + // Row 2 + new KeyMatch(Keys.Oemtilde, 0, 1), + new KeyMatch(Keys.D1, 1, 1), + new KeyMatch(Keys.D2, 2, 1), + new KeyMatch(Keys.D3, 3, 1), + new KeyMatch(Keys.D4, 4, 1), + new KeyMatch(Keys.D5, 5, 1), + new KeyMatch(Keys.D6, 6, 1), + new KeyMatch(Keys.D7, 7, 1), + new KeyMatch(Keys.D8, 8, 1), + new KeyMatch(Keys.D9, 9, 1), + new KeyMatch(Keys.D0, 10, 1), + new KeyMatch(Keys.OemMinus, 11, 1), + new KeyMatch(Keys.Oemplus, 12, 1), + new KeyMatch(Keys.Back, 13, 1), + new KeyMatch(Keys.Insert, 14, 1), + new KeyMatch(Keys.Home, 15, 1), + new KeyMatch(Keys.PageUp, 16, 1), + new KeyMatch(Keys.NumLock, 17, 1), + new KeyMatch(Keys.Divide, 18, 1), + new KeyMatch(Keys.Multiply, 19, 1), + new KeyMatch(Keys.Subtract, 20, 1), + + // Row 3 + new KeyMatch(Keys.Tab, 0, 2), + new KeyMatch(Keys.A, 1, 2), + new KeyMatch(Keys.Z, 2, 2), + new KeyMatch(Keys.E, 3, 2), + new KeyMatch(Keys.R, 5, 2), + new KeyMatch(Keys.T, 6, 2), + new KeyMatch(Keys.Y, 7, 2), + new KeyMatch(Keys.U, 8, 2), + new KeyMatch(Keys.I, 9, 2), + new KeyMatch(Keys.O, 10, 2), + new KeyMatch(Keys.P, 11, 2), + new KeyMatch(Keys.OemQuotes, 12, 2), + new KeyMatch(Keys.Oem6, 13, 2), + new KeyMatch(Keys.Delete, 14, 2), + new KeyMatch(Keys.End, 15, 2), + new KeyMatch(Keys.Next, 16, 2), + new KeyMatch(Keys.NumPad7, 17, 2), + new KeyMatch(Keys.NumPad8, 18, 2), + new KeyMatch(Keys.NumPad9, 19, 2), + new KeyMatch(Keys.Add, 20, 2), + + // Row 4 + new KeyMatch(Keys.Capital, 0, 3), + new KeyMatch(Keys.Q, 1, 3), + new KeyMatch(Keys.S, 3, 3), + new KeyMatch(Keys.D, 4, 3), + new KeyMatch(Keys.F, 5, 3), + new KeyMatch(Keys.G, 6, 3), + new KeyMatch(Keys.H, 7, 3), + new KeyMatch(Keys.J, 8, 3), + new KeyMatch(Keys.K, 9, 3), + new KeyMatch(Keys.L, 10, 3), + new KeyMatch(Keys.M, 11, 3), + new KeyMatch(Keys.Oem7, 12, 3), + new KeyMatch(Keys.Oem5, 13, 3), + new KeyMatch(Keys.Return, 14, 3), + new KeyMatch(Keys.NumPad4, 17, 3), + new KeyMatch(Keys.NumPad5, 18, 3), + new KeyMatch(Keys.NumPad6, 19, 3), + + // Row 5 + new KeyMatch(Keys.LShiftKey, 1, 4), + new KeyMatch(Keys.OemBackslash, 2, 4), + new KeyMatch(Keys.W, 2, 4), + new KeyMatch(Keys.X, 3, 4), + new KeyMatch(Keys.C, 4, 4), + new KeyMatch(Keys.V, 5, 4), + new KeyMatch(Keys.B, 6, 4), + new KeyMatch(Keys.N, 7, 4), + new KeyMatch(Keys.OemQuestion, 8, 4), + new KeyMatch(Keys.Oemcomma, 9, 4), + new KeyMatch(Keys.OemPeriod, 10, 4), + new KeyMatch(Keys.OemQuestion, 11, 4), + new KeyMatch(Keys.RShiftKey, 13, 4), + new KeyMatch(Keys.Up, 15, 4), + new KeyMatch(Keys.NumPad1, 17, 4), + new KeyMatch(Keys.NumPad2, 18, 4), + new KeyMatch(Keys.NumPad3, 19, 4), + // Both returns return "Return" (Yes...) + // new OrionKey(System.Windows.Forms.Keys.Return, 20, 4), + + // Row 6 + new KeyMatch(Keys.LControlKey, 0, 5), + new KeyMatch(Keys.LWin, 1, 5), + new KeyMatch(Keys.LMenu, 3, 5), + new KeyMatch(Keys.Space, 6, 5), + new KeyMatch(Keys.RMenu, 11, 5), + new KeyMatch(Keys.RWin, 12, 5), + new KeyMatch(Keys.Apps, 13, 5), + new KeyMatch(Keys.RControlKey, 14, 5), + new KeyMatch(Keys.Left, 15, 5), + new KeyMatch(Keys.Down, 16, 5), + new KeyMatch(Keys.Right, 17, 5), + new KeyMatch(Keys.NumPad0, 18, 5), + new KeyMatch(Keys.Decimal, 19, 5) + }; + + #endregion + } + + public static List QwertyLayout { get; set; } + public static List QwertzLayout { get; set; } + public static List AzertyLayout { get; set; } + } +} diff --git a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/OrionUtilities.cs b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/OrionUtilities.cs index 736794cca..8057695f0 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/Utilities/OrionUtilities.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/Utilities/OrionUtilities.cs @@ -7,149 +7,7 @@ namespace Artemis.DeviceProviders.Logitech.Utilities { public static class OrionUtilities { - public static KeyMapping[] Keymappings = - { - // First row - new KeyMapping(0, 0), - new KeyMapping(1, 1), - new KeyMapping(2, 1), - new KeyMapping(3, 2), - new KeyMapping(4, 3), - new KeyMapping(5, 4), - new KeyMapping(6, 5), - new KeyMapping(7, 6), - new KeyMapping(8, 7), - new KeyMapping(9, 8), - new KeyMapping(10, 9), - new KeyMapping(11, 9), - new KeyMapping(12, 10), - new KeyMapping(13, 11), - new KeyMapping(13, 12), - new KeyMapping(14, 13), - new KeyMapping(15, 14), - new KeyMapping(16, 15), - new KeyMapping(17, 16), - new KeyMapping(18, 17), - new KeyMapping(19, 18), - - // Second row - new KeyMapping(21, 21), - new KeyMapping(22, 22), - new KeyMapping(23, 23), - new KeyMapping(24, 24), - new KeyMapping(25, 25), - new KeyMapping(26, 26), - new KeyMapping(27, 27), - new KeyMapping(28, 28), - new KeyMapping(29, 29), - new KeyMapping(30, 30), - new KeyMapping(31, 31), - new KeyMapping(32, 32), - new KeyMapping(33, 33), - new KeyMapping(34, 34), - new KeyMapping(35, 35), - new KeyMapping(36, 36), - new KeyMapping(37, 37), - new KeyMapping(38, 38), - new KeyMapping(39, 39), - new KeyMapping(40, 40), - new KeyMapping(41, 41), - - // Third row - new KeyMapping(42, 42), - new KeyMapping(43, 43), - new KeyMapping(44, 44), - new KeyMapping(45, 45), - new KeyMapping(46, 46), - new KeyMapping(47, 46), - new KeyMapping(48, 47), - new KeyMapping(49, 48), - new KeyMapping(50, 49), - new KeyMapping(51, 50), - new KeyMapping(52, 51), - new KeyMapping(53, 52), - new KeyMapping(54, 53), - new KeyMapping(54, 54), - new KeyMapping(55, 55), - new KeyMapping(56, 56), - new KeyMapping(57, 57), - new KeyMapping(58, 58), - new KeyMapping(59, 59), - new KeyMapping(60, 60), - new KeyMapping(61, 61), - new KeyMapping(62, 62), - - // Fourth row - new KeyMapping(63, 63), - new KeyMapping(64, 64), - new KeyMapping(65, 65), - new KeyMapping(66, 65), - new KeyMapping(67, 66), - new KeyMapping(68, 67), - new KeyMapping(69, 68), - new KeyMapping(70, 69), - new KeyMapping(71, 70), - new KeyMapping(72, 71), - new KeyMapping(73, 72), - new KeyMapping(74, 73), - new KeyMapping(75, 74), - new KeyMapping(76, 75), - new KeyMapping(76, 76), - new KeyMapping(78, 77), - new KeyMapping(79, 78), - new KeyMapping(79, 79), - new KeyMapping(80, 80), - new KeyMapping(81, 81), - new KeyMapping(82, 82), - - // Fifth row - new KeyMapping(84, 84), - new KeyMapping(85, 85), - new KeyMapping(86, 86), - new KeyMapping(87, 87), - new KeyMapping(88, 88), - new KeyMapping(89, 89), - new KeyMapping(90, 90), - new KeyMapping(91, 91), - new KeyMapping(92, 92), - new KeyMapping(93, 93), - new KeyMapping(94, 94), - new KeyMapping(95, 95), - new KeyMapping(96, 96), - new KeyMapping(97, 97), - new KeyMapping(98, 98), - new KeyMapping(99, 99), - new KeyMapping(100, 100), - new KeyMapping(101, 101), - new KeyMapping(102, 102), - new KeyMapping(103, 103), - new KeyMapping(104, 104), - - // Sixth row - new KeyMapping(105, 105), - new KeyMapping(106, 106), - new KeyMapping(107, 107), - new KeyMapping(108, 107), - new KeyMapping(109, 109), - new KeyMapping(110, 110), - new KeyMapping(111, 110), - new KeyMapping(112, 111), - new KeyMapping(113, 112), - new KeyMapping(114, 113), - new KeyMapping(115, 114), - new KeyMapping(116, 115), - new KeyMapping(115, 116), // ALTGR - new KeyMapping(116, 117), - new KeyMapping(117, 118), - new KeyMapping(118, 119), - new KeyMapping(119, 120), - new KeyMapping(120, 121), - new KeyMapping(121, 122), - new KeyMapping(122, 123), - new KeyMapping(124, 124) - }; - - public static byte[] BitmapToByteArray(Bitmap b, bool remap = true) + public static byte[] BitmapToByteArray(Bitmap b, KeyMapping[] keymappings = null) { if (b.Width > 21 || b.Height > 6) b = ResizeImage(b, 21, 6); @@ -158,23 +16,23 @@ namespace Artemis.DeviceProviders.Logitech.Utilities var bitmapData = b.LockBits(rect, ImageLockMode.ReadWrite, b.PixelFormat); var depth = Image.GetPixelFormatSize(b.PixelFormat); - var step = depth/8; - var pixels = new byte[21*6*step]; + var step = depth / 8; + var pixels = new byte[21 * 6 * step]; var iptr = bitmapData.Scan0; // Copy data from pointer to array Marshal.Copy(iptr, pixels, 0, pixels.Length); - if (!remap) + if (keymappings == null) return pixels; var remapped = new byte[pixels.Length]; // Every key is 4 bytes - for (var i = 0; i <= pixels.Length/4; i++) + for (var i = 0; i <= pixels.Length / 4; i++) { - var firstSByte = Keymappings[i].Source*4; - var firstTByte = Keymappings[i].Target*4; + var firstSByte = keymappings[i].Source * 4; + var firstTByte = keymappings[i].Target * 4; for (var j = 0; j < 4; j++) remapped[firstTByte + j] = pixels[firstSByte + j]; @@ -234,4 +92,4 @@ namespace Artemis.DeviceProviders.Logitech.Utilities public int Target { get; set; } } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/Models/ProfileEditorModel.cs b/Artemis/Artemis/Models/ProfileEditorModel.cs index 389d5ecf7..0e818be43 100644 --- a/Artemis/Artemis/Models/ProfileEditorModel.cs +++ b/Artemis/Artemis/Models/ProfileEditorModel.cs @@ -141,15 +141,22 @@ namespace Artemis.Models public async Task RenameProfile(ProfileModel profileModel) { + // Store the old name + var oldName = profileModel.Name; var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); // User cancelled if (name == null) return; + + // MakeProfileUnique does a check but also modifies the profile, set the old name back var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); + var newName = profileModel.Name; + profileModel.Name = oldName; + if (!doRename) return; - ProfileProvider.RenameProfile(profileModel, profileModel.Name); + ProfileProvider.RenameProfile(profileModel, newName); } public async Task DuplicateProfile(ProfileModel selectedProfile) diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index 376ce59df..c540fd3de 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -96,10 +96,12 @@ namespace Artemis.Modules.Abstract public void ChangeProfile(ProfileModel profileModel) { - if (!IsInitialized || Equals(profileModel, ProfileModel)) + if (!IsInitialized) return; + ProfileModel?.Deactivate(_luaManager); ProfileModel = profileModel; + if (!IsOverlay) ProfileModel?.Activate(_luaManager); if (ProfileModel != null) From d737716e12258169a2d99368a8b522ccc3cddf19 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 21 May 2017 11:56:58 +0200 Subject: [PATCH 02/21] Corsair keymap fixes --- Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboard.cs | 2 ++ Artemis/Artemis/DeviceProviders/Corsair/Utilities/KeyMap.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboard.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboard.cs index 4468016b9..abb4e026b 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboard.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairKeyboard.cs @@ -128,6 +128,8 @@ namespace Artemis.DeviceProviders.Corsair { cueLed = _keyboard.Leds.FirstOrDefault(k => k.Id.ToString() == keyCode.ToString()) ?? _keyboard.Leds.FirstOrDefault(k => k.Id == KeyMap.FormsKeys[keyCode]); + + Logger.Trace("Keycode: {0} resolved to CUE LED: {1}", keyCode, cueLed); } catch (Exception) { diff --git a/Artemis/Artemis/DeviceProviders/Corsair/Utilities/KeyMap.cs b/Artemis/Artemis/DeviceProviders/Corsair/Utilities/KeyMap.cs index 19395ca61..2c107060b 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/Utilities/KeyMap.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/Utilities/KeyMap.cs @@ -23,9 +23,9 @@ namespace Artemis.DeviceProviders.Corsair.Utilities {Keys.Capital, CorsairLedId.CapsLock}, {Keys.Oem1, CorsairLedId.SemicolonAndColon}, {Keys.Oem7, CorsairLedId.ApostropheAndDoubleQuote}, - {Keys.OemBackslash, CorsairLedId.Backslash}, + {Keys.OemBackslash, CorsairLedId.NonUsBackslash}, {Keys.LShiftKey, CorsairLedId.LeftShift}, - {Keys.Oem5, CorsairLedId.NonUsBackslash}, + {Keys.Oem5, CorsairLedId.NonUsTilde}, {Keys.Oemcomma, CorsairLedId.CommaAndLessThan}, {Keys.OemPeriod, CorsairLedId.PeriodAndBiggerThan}, {Keys.OemQuestion, CorsairLedId.SlashAndQuestionMark}, From 7791b64a0f7e3605032e4bf1cf6e645e54958dba Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 22 May 2017 14:28:52 +0200 Subject: [PATCH 03/21] Fixed all cases of the profile selection going empty I could find --- Artemis/Artemis/Models/ProfileEditorModel.cs | 12 ++--------- .../Artemis/Modules/Abstract/ModuleModel.cs | 8 +++----- .../ViewModels/ProfileEditorViewModel.cs | 20 +++++++++++-------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/Artemis/Artemis/Models/ProfileEditorModel.cs b/Artemis/Artemis/Models/ProfileEditorModel.cs index 0e818be43..f3f5b9a0a 100644 --- a/Artemis/Artemis/Models/ProfileEditorModel.cs +++ b/Artemis/Artemis/Models/ProfileEditorModel.cs @@ -177,21 +177,13 @@ namespace Artemis.Models return newProfile; } - public async Task DeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel) + public async Task ConfirmDeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel) { var confirm = await _dialogService.ShowQuestionMessageBox("Delete profile", $"Are you sure you want to delete the profile named: {selectedProfile.Name}?\n\n" + "This cannot be undone."); - if (!confirm.Value) - return false; - var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, moduleModel, "Default"); - var deleteProfile = selectedProfile; - - moduleModel.ChangeProfile(defaultProfile); - ProfileProvider.DeleteProfile(deleteProfile); - - return true; + return confirm.Value; } public async Task ImportProfile(ModuleModel moduleModel) diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index c540fd3de..bc107abba 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.Windows; using Artemis.DAL; using Artemis.Events; using Artemis.Managers; using Artemis.Models; using Artemis.Profiles; -using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Newtonsoft.Json; using Ninject; @@ -96,7 +94,7 @@ namespace Artemis.Modules.Abstract public void ChangeProfile(ProfileModel profileModel) { - if (!IsInitialized) + if (!IsInitialized || Equals(ProfileModel, profileModel)) return; ProfileModel?.Deactivate(_luaManager); @@ -133,7 +131,7 @@ namespace Artemis.Modules.Abstract ChangeToLastProfile(); } - private void ChangeToLastProfile() + public void ChangeToLastProfile() { var profileName = !string.IsNullOrEmpty(Settings?.LastProfile) ? Settings.LastProfile : "Default"; @@ -205,4 +203,4 @@ namespace Artemis.Modules.Abstract #endregion } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index b0d3b1daf..219f2073b 100644 --- a/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -406,8 +406,6 @@ namespace Artemis.ViewModels ProfileNames.Clear(); if (_moduleModel != null && _deviceManager.ActiveKeyboard != null) ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel)); - - NotifyOfPropertyChange(() => SelectedProfile); }); } @@ -444,7 +442,7 @@ namespace Artemis.ViewModels return; LoadProfiles(); - _moduleModel.ChangeProfile(profile); + SelectedProfileName = profile.Name; } public async void RenameProfile() @@ -456,7 +454,8 @@ namespace Artemis.ViewModels await ProfileEditorModel.RenameProfile(SelectedProfile); LoadProfiles(); - _moduleModel.ChangeProfile(renameProfile); + SelectedProfileName = "Default"; + SelectedProfileName = renameProfile.Name; } public async void DuplicateProfile() @@ -469,7 +468,7 @@ namespace Artemis.ViewModels return; LoadProfiles(); - _moduleModel.ChangeProfile(newProfle); + SelectedProfileName = newProfle.Name; } public async void DeleteProfile() @@ -477,12 +476,17 @@ namespace Artemis.ViewModels if (SelectedProfile == null) return; - var confirmed = await ProfileEditorModel.DeleteProfile(SelectedProfile, _moduleModel); + var confirmed = await ProfileEditorModel.ConfirmDeleteProfile(SelectedProfile, _moduleModel); if (!confirmed) return; + var deleteProfile = SelectedProfile; + + _moduleModel.ChangeProfile(null); + ProfileProvider.DeleteProfile(deleteProfile); + LoadProfiles(); - ProfileEditorModel.ChangeProfileByName(_moduleModel, null); + SelectedProfileName = "Default"; } public async void ImportProfile() @@ -499,7 +503,7 @@ namespace Artemis.ViewModels return; LoadProfiles(); - _moduleModel.ChangeProfile(importProfile); + SelectedProfileName = importProfile.Name; } public void ExportProfile() From f1bf40cb12362788704837d824c75eabcd5afd60 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Tue, 23 May 2017 01:10:47 +0200 Subject: [PATCH 04/21] Updated UT detection code Updated UT plugin --- Artemis/Artemis/DAL/ProfileProvider.cs | 3 +- .../UnrealTournament/Resources/ut-plugin.zip | Bin 84528 -> 93567 bytes .../UnrealTournament/UnrealTournamentModel.cs | 6 +- .../UnrealTournamentViewModel.cs | 100 +++++++++--------- 4 files changed, 55 insertions(+), 54 deletions(-) diff --git a/Artemis/Artemis/DAL/ProfileProvider.cs b/Artemis/Artemis/DAL/ProfileProvider.cs index 9fea4dfc2..4b0e28181 100644 --- a/Artemis/Artemis/DAL/ProfileProvider.cs +++ b/Artemis/Artemis/DAL/ProfileProvider.cs @@ -167,7 +167,8 @@ namespace Artemis.DAL var gifDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Artemis\gifs"; Directory.CreateDirectory(gifDir); var gifPath = gifDir + $"\\{fileName}.gif"; - gifFile.Save(gifPath); + if (!File.Exists(gifPath)) + gifFile.Save(gifPath); foreach (var profile in profiles) { diff --git a/Artemis/Artemis/Modules/Games/UnrealTournament/Resources/ut-plugin.zip b/Artemis/Artemis/Modules/Games/UnrealTournament/Resources/ut-plugin.zip index 80d8f3fbcfb9093dbb0c338147c0444600692e8f..9973ee615a369a1b3d89edb099d037f124f4aee7 100644 GIT binary patch literal 93567 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 true; - - public void BrowseDirectory() - { - var dialog = new FolderBrowserDialog {SelectedPath = ((UnrealTournamentSettings) Settings).GameDirectory}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - ((UnrealTournamentSettings) Settings).GameDirectory = dialog.SelectedPath; - ((UnrealTournamentModel) ModuleModel).PlaceFiles(); - Settings.Save(); - NotifyOfPropertyChange(() => Settings); - } - - public void PlaceFiles() +using System.Windows.Forms; +using Artemis.DAL; +using Artemis.Managers; +using Artemis.Modules.Abstract; +using Artemis.Properties; +using Ninject; + +namespace Artemis.Modules.Games.UnrealTournament +{ + public sealed class UnrealTournamentViewModel : ModuleViewModel + { + public UnrealTournamentViewModel(MainManager mainManager, + [Named(nameof(UnrealTournamentModel))] ModuleModel moduleModel, IKernel kernel) + : base(mainManager, moduleModel, kernel) { - ((UnrealTournamentModel)ModuleModel).PlaceFiles(); - Settings.Save(); - NotifyOfPropertyChange(() => Settings); - } - - // Installing GIF on editor open to make sure the proper profiles are loaded - private void InstallGif() - { - var gif = Resources.redeemer; - if (gif == null) - return; - - ProfileProvider.InsertGif("UnrealTournament", "Default", "Redeemer GIF", gif, "redeemer"); - } - } + DisplayName = "Unreal Tournament"; + InstallGif(); + } + + public override bool UsesProfileEditor => true; + + public void BrowseDirectory() + { + var dialog = new FolderBrowserDialog {SelectedPath = ((UnrealTournamentSettings) Settings).GameDirectory}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + ((UnrealTournamentSettings) Settings).GameDirectory = dialog.SelectedPath; + ((UnrealTournamentModel) ModuleModel).PlaceFiles(); + Settings.Save(); + NotifyOfPropertyChange(() => Settings); + } + + public void PlaceFiles() + { + ((UnrealTournamentModel)ModuleModel).PlaceFiles(); + Settings.Save(); + NotifyOfPropertyChange(() => Settings); + } + + // Installing GIF on editor open to make sure the proper profiles are loaded + private void InstallGif() + { + var gif = Resources.redeemer; + if (gif == null) + return; + + ProfileProvider.InsertGif("UnrealTournament", "Default", "Redeemer GIF", gif, "redeemer"); + } + } } \ No newline at end of file From 1d47a9f77d6a2b983d4b77ae37655b2eeab8e473 Mon Sep 17 00:00:00 2001 From: Tyler Jaacks Date: Wed, 26 Jul 2017 13:58:11 -0500 Subject: [PATCH 05/21] Added Orisa and Doomfist to the OverwatchCharacter, and added Orisa and Doomfist to CharacterColor in OverwatchModel. --- Artemis/Artemis/Modules/Games/Overwatch/OverwatchDataModel.cs | 4 +++- Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchDataModel.cs b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchDataModel.cs index d88a3a28e..72895b27d 100644 --- a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchDataModel.cs +++ b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchDataModel.cs @@ -48,6 +48,8 @@ namespace Artemis.Modules.Games.Overwatch Symmetra, Zenyatta, Ana, - Sombra + Sombra, + Orisa, + Doomfist } } \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs index 819611cb0..3454a954b 100644 --- a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs +++ b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs @@ -76,7 +76,9 @@ namespace Artemis.Modules.Games.Overwatch new CharacterColor {Character = OverwatchCharacter.Symmetra, Color = Color.FromRgb(46, 116, 148)}, new CharacterColor {Character = OverwatchCharacter.Zenyatta, Color = Color.FromRgb(248, 218, 26)}, new CharacterColor {Character = OverwatchCharacter.Ana, Color = Color.FromRgb(16, 36, 87)}, - new CharacterColor {Character = OverwatchCharacter.Sombra, Color = Color.FromRgb(20, 5, 101)} + new CharacterColor {Character = OverwatchCharacter.Sombra, Color = Color.FromRgb(20, 5, 101)}, + new CharacterColor {Character = OverwatchCharacter.Orisa, Color = Color.FromRgb(194,233,78)}, + new CharacterColor {Character = OverwatchCharacter.Doomfist, Color = Color.FromRgb(207, 137, 77)} }; } From a09649e9d08f4002284ddc032f4413b2f20f61b6 Mon Sep 17 00:00:00 2001 From: Tyler Jaacks Date: Wed, 26 Jul 2017 14:59:24 -0500 Subject: [PATCH 06/21] Added initial imlementation of LuaSlider, still needs event system. --- .../Profiles/Lua/Modules/Gui/LuaSlider.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaSlider.cs diff --git a/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaSlider.cs b/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaSlider.cs new file mode 100644 index 000000000..0dcb8b958 --- /dev/null +++ b/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaSlider.cs @@ -0,0 +1,42 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using Artemis.Managers; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; + +namespace Artemis.Profiles.Lua.Modules.Gui +{ + [MoonSharpUserData] + class LuaSlider + { + private readonly LuaManager _luaManager; + + public LuaSlider(LuaManager luaManager, int interval, double intialValue, double x, double y, double? width, double? height) + { + _luaManager = luaManager; + + Slider = new Slider { Value = intialValue, Interval = interval }; + + if (width != null) + Slider.Width = width.Value; + if (height != null) + Slider.Height = height.Value; + } + + [MoonSharpVisible(false)] + public Slider Slider { get; } + + public double Value + { + get => Slider.Dispatcher.Invoke(() => (double) Slider.Value); + set => Slider.Dispatcher.Invoke(() => Slider.Value = value); + } + + public int Interval + { + get => Slider.Dispatcher.Invoke(() => (int) Slider.Interval); + set => Slider.Dispatcher.Invoke(() => Slider.Interval = value); + } + } +} \ No newline at end of file From 75a556f07d9828e423f4e779fee3cf63324901c6 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 28 Jul 2017 19:33:02 +0200 Subject: [PATCH 07/21] Adjust Overwatch colors Add slider creation method Add slider changed event Add minimum and maximum to slider Show slider ticks Snap to slider ticks --- Artemis/Artemis/Artemis.csproj | 1 + .../Modules/Games/Overwatch/OverwatchModel.cs | 4 ++-- .../Profiles/Lua/Modules/Gui/LuaSlider.cs | 21 ++++++++++++++++--- .../Lua/Modules/Gui/LuaWindowViewModel.cs | 12 +++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 5f0aea86c..1f6bdac10 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -573,6 +573,7 @@ + LuaWindowView.xaml diff --git a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs index 3454a954b..4a216cd99 100644 --- a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs +++ b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs @@ -77,8 +77,8 @@ namespace Artemis.Modules.Games.Overwatch new CharacterColor {Character = OverwatchCharacter.Zenyatta, Color = Color.FromRgb(248, 218, 26)}, new CharacterColor {Character = OverwatchCharacter.Ana, Color = Color.FromRgb(16, 36, 87)}, new CharacterColor {Character = OverwatchCharacter.Sombra, Color = Color.FromRgb(20, 5, 101)}, - new CharacterColor {Character = OverwatchCharacter.Orisa, Color = Color.FromRgb(194,233,78)}, - new CharacterColor {Character = OverwatchCharacter.Doomfist, Color = Color.FromRgb(207, 137, 77)} + new CharacterColor {Character = OverwatchCharacter.Orisa, Color = Color.FromRgb(1,40,0)}, + new CharacterColor {Character = OverwatchCharacter.Doomfist, Color = Color.FromRgb(33, 3, 1)} }; } diff --git a/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaSlider.cs b/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaSlider.cs index 0dcb8b958..0a14af4f8 100644 --- a/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaSlider.cs +++ b/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaSlider.cs @@ -1,6 +1,7 @@ using System; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; using Artemis.Managers; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; @@ -8,20 +9,22 @@ using MoonSharp.Interpreter.Interop; namespace Artemis.Profiles.Lua.Modules.Gui { [MoonSharpUserData] - class LuaSlider + public class LuaSlider { private readonly LuaManager _luaManager; - public LuaSlider(LuaManager luaManager, int interval, double intialValue, double x, double y, double? width, double? height) + public LuaSlider(LuaManager luaManager, int interval, double intialValue, double minimum, double maximum, double x, double y, double? width, double? height) { _luaManager = luaManager; - Slider = new Slider { Value = intialValue, Interval = interval }; + Slider = new Slider { Value = intialValue, TickFrequency = interval, Minimum = minimum, Maximum = maximum, TickPlacement = TickPlacement.BottomRight, IsSnapToTickEnabled = true}; if (width != null) Slider.Width = width.Value; if (height != null) Slider.Height = height.Value; + + Slider.ValueChanged += SliderOnValueChanged; } [MoonSharpVisible(false)] @@ -38,5 +41,17 @@ namespace Artemis.Profiles.Lua.Modules.Gui get => Slider.Dispatcher.Invoke(() => (int) Slider.Interval); set => Slider.Dispatcher.Invoke(() => Slider.Interval = value); } + + private void SliderOnValueChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + _luaManager.EventsModule.LuaInvoke(_luaManager.ProfileModel, () => OnValueChanged(this)); + } + + public event EventHandler ValueChanged; + + protected virtual void OnValueChanged(LuaSlider slider) + { + ValueChanged?.Invoke(slider, null); + } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaWindowViewModel.cs b/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaWindowViewModel.cs index 5c927cdb1..f7201069e 100644 --- a/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaWindowViewModel.cs +++ b/Artemis/Artemis/Profiles/Lua/Modules/Gui/LuaWindowViewModel.cs @@ -83,6 +83,18 @@ namespace Artemis.Profiles.Lua.Modules.Gui return element; } + public LuaSlider CreateSlider(int interval, double initialValue, double minimum, double maximum, double x, double y, double? width = 200.0, double? height = 20.0) + { + LuaSlider element = null; + Execute.OnUIThread(() => + { + element = new LuaSlider(_luaManager, interval, initialValue, minimum, maximum, x, y, width, height); + AddControl(element.Slider, x, y); + }); + + return element; + } + private void AddControl(UIElement uiElement, double x, double y) { Canvas.Children.Add(uiElement); From 04e594399670aa387f3df0d36fbb7b9d62a2ac66 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 4 Sep 2017 21:46:40 +0200 Subject: [PATCH 08/21] Started WoW rework --- Artemis/Artemis/Artemis.csproj | 23 +- Artemis/Artemis/Managers/ModuleManager.cs | 5 +- .../Artemis/Modules/Games/WoW/Data/Int128.cs | 2256 ----------------- .../Modules/Games/WoW/Data/WoWEnums.cs | 157 -- .../Modules/Games/WoW/Data/WoWNameCache.cs | 63 - .../Modules/Games/WoW/Data/WoWObject.cs | 73 - .../Games/WoW/Data/WoWObjectManager.cs | 92 - .../Modules/Games/WoW/Data/WoWOffsets.cs | 368 --- .../Modules/Games/WoW/Data/WoWPlayer.cs | 23 - .../Modules/Games/WoW/Data/WoWStructs.cs | 181 -- .../Artemis/Modules/Games/WoW/Data/WoWUnit.cs | 31 - .../Artemis/Modules/Games/WoW/WoWAddresses.cs | 256 -- .../Artemis/Modules/Games/WoW/WoWDataModel.cs | 154 +- Artemis/Artemis/Modules/Games/WoW/WoWModel.cs | 194 +- Artemis/Artemis/ViewModels/GamesViewModel.cs | 18 +- Artemis/Artemis/lib/SDKDLL.dll | Bin 538624 -> 538624 bytes Artemis/Artemis/packages.config | 1 + 17 files changed, 278 insertions(+), 3617 deletions(-) delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/Int128.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/WoWEnums.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/WoWNameCache.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/WoWObject.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/WoWObjectManager.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/WoWOffsets.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/WoWPlayer.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/WoWStructs.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/Data/WoWUnit.cs delete mode 100644 Artemis/Artemis/Modules/Games/WoW/WoWAddresses.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 381d9d8d9..884520177 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -255,6 +255,18 @@ ..\packages\squirrel.windows.1.4.4\lib\Net45\NuGet.Squirrel.dll True + + ..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Base.dll + + + ..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Core.dll + + + ..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Core.Extensions.dll + + + ..\packages\Pcap.Net.x64.1.0.4.1\lib\net45\PcapDotNet.Packets.dll + ..\packages\Process.NET.1.0.8\lib\Process.NET.dll True @@ -471,15 +483,6 @@ UnrealTournamentView.xaml - - - - - - - - - WoWView.xaml @@ -487,7 +490,6 @@ - @@ -1098,6 +1100,7 @@ + diff --git a/Artemis/Artemis/Managers/ModuleManager.cs b/Artemis/Artemis/Managers/ModuleManager.cs index 59088bd1c..d6e5cf1ec 100644 --- a/Artemis/Artemis/Managers/ModuleManager.cs +++ b/Artemis/Artemis/Managers/ModuleManager.cs @@ -24,10 +24,7 @@ namespace Artemis.Managers Modules = new List(moduleModels.Where(m => !m.IsOverlay && !m.IsBoundToProcess)); OverlayModules = new List(moduleModels.Where(m => m.IsOverlay)); - // Exclude WoW if needed - ProcessModules = _generalSettings.GamestatePort == 62575 - ? new List(moduleModels.Where(m => m.IsBoundToProcess)) - : new List(moduleModels.Where(m => m.IsBoundToProcess && m.Name != "WoW")); + ProcessModules = new List(moduleModels.Where(m => m.IsBoundToProcess)); _logger.Info("Intialized ModuleManager"); } diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/Int128.cs b/Artemis/Artemis/Modules/Games/WoW/Data/Int128.cs deleted file mode 100644 index 4a66ecc0f..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/Int128.cs +++ /dev/null @@ -1,2256 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.SqlServer.Server; - -namespace Artemis.Modules.Games.WoW.Data -{ - namespace WowSharp.Client.Patchables - { - /// - /// Represents a 128-bit signed integer. - /// -#if !WINDOWS_PHONE && !SILVERLIGHT - [Serializable] -#endif - [StructLayout(LayoutKind.Sequential)] - [TypeConverter(typeof(Int128Converter))] - public struct Int128 : IComparable, IComparable, IEquatable, IConvertible, IFormattable -#if !WINDOWS_PHONE && !SILVERLIGHT - , IBinarySerialize -#endif - { - private ulong _hi; - private ulong _lo; - - private const ulong HiNeg = 0x8000000000000000; - - /// - /// Gets a value that represents the number 0 (zero). - /// - public static Int128 Zero = GetZero(); - - /// - /// Represents the largest possible value of an Int128. - /// - public static Int128 MaxValue = GetMaxValue(); - - /// - /// Represents the smallest possible value of an Int128. - /// - public static Int128 MinValue = GetMinValue(); - - private static Int128 GetMaxValue() - { - return new Int128(long.MaxValue, ulong.MaxValue); - } - - private static Int128 GetMinValue() - { - return new Int128(HiNeg, 0); - } - - private static Int128 GetZero() - { - return new Int128(); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(byte value) - { - _hi = 0; - _lo = value; - } - - /// - /// Initializes a new instance of the struct. - /// - /// if set to true [value]. - public Int128(bool value) - { - _hi = 0; - _lo = (ulong) (value ? 1 : 0); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(char value) - { - _hi = 0; - _lo = value; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(decimal value) - { - if (value < 0) - { - var n = -new Int128(-value); - _hi = n._hi; - _lo = n._lo; - return; - } - - var bits = decimal.GetBits(value); - _hi = (uint) bits[2]; - _lo = (uint) bits[0] | ((ulong) bits[1] << 32); - - var scale = (bits[3] >> 16) & 31; - if (scale > 0) - { - var i = new Int128(_hi, _lo); - for (var s = 0; s < scale; s++) - i = i/10; - _hi = i._hi; - _lo = i._lo; - } - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(double value) - : this((decimal) value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(float value) - : this((decimal) value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(short value) - : this((int) value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(int value) - : this((long) value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(long value) - { - if (value < 0) - { - // long.MinValue = -long.MinValue - if (value == long.MinValue) - { - _hi = HiNeg; - _lo = HiNeg; - return; - } - - var n = -new Int128(-value); - _hi = n._hi; - _lo = n._lo; - return; - } - - _hi = 0; - _lo = (ulong) value; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(sbyte value) - : this((long) value) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(ushort value) - { - _hi = 0; - _lo = value; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(uint value) - { - _hi = 0; - _lo = value; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(ulong value) - { - _hi = 0; - _lo = value; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(Guid value) - : this(value.ToByteArray()) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The value. - public Int128(byte[] value) - { - if (value == null) - throw new ArgumentNullException("value"); - - if (value.Length != 16) - throw new ArgumentException(null, "value"); - - _hi = BitConverter.ToUInt64(value, 8); - _lo = BitConverter.ToUInt64(value, 0); - } - - public Int128(ulong hi, ulong lo) - { - _hi = hi; - _lo = lo; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The sign. - /// The ints. - public Int128(int sign, uint[] ints) - { - if (ints == null) - throw new ArgumentNullException("ints"); - - var lo = new byte[8]; - var hi = new byte[8]; - - if (ints.Length > 0) - { - Array.Copy(BitConverter.GetBytes(ints[0]), 0, lo, 0, 4); - if (ints.Length > 1) - { - Array.Copy(BitConverter.GetBytes(ints[1]), 0, lo, 4, 4); - if (ints.Length > 2) - { - Array.Copy(BitConverter.GetBytes(ints[2]), 0, hi, 0, 4); - if (ints.Length > 3) - Array.Copy(BitConverter.GetBytes(ints[3]), 0, hi, 4, 4); - } - } - } - - _lo = BitConverter.ToUInt64(lo, 0); - _hi = BitConverter.ToUInt64(hi, 0); - - if (sign < 0) - _hi |= HiNeg; - else - _hi &= ~HiNeg; - } - - /// - /// Gets a number that indicates the sign (negative, positive, or zero) of the current Int128 object. - /// - /// A number that indicates the sign of the Int128 object - public int Sign - { - get - { - if ((_hi == 0) && (_lo == 0)) - return 0; - - return (_hi & HiNeg) == 0 ? 1 : -1; - } - } - - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() - { - return _hi.GetHashCode() ^ _lo.GetHashCode(); - } - - /// - /// Returns a value indicating whether this instance is equal to a specified object. - /// - /// An object to compare with this instance. - /// - /// true if obj has the same value as this instance; otherwise, false. - /// - public override bool Equals(object obj) - { - return base.Equals(obj); - } - - /// - /// Returns a value indicating whether this instance is equal to a specified Int64 value. - /// - /// The obj. - /// - /// true if obj has the same value as this instance; otherwise, false. - /// - public bool Equals(Int128 obj) - { - return (_hi == obj._hi) && (_lo == obj._lo); - } - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() - { - return ToString(null, null); - } - - /// - /// Returns a that represents this instance. - /// - /// The format. Only x, X, g, G, d, D are supported. - /// - /// A that represents this instance. - /// - public string ToString(string format) - { - return ToString(format, null); - } - - /// - /// Returns a that represents this instance. - /// - /// The format. Only x, X, g, G, d, D are supported. - /// An object that supplies culture-specific formatting information about this instance. - /// - /// A that represents this instance. - /// - public string ToString(string format, IFormatProvider formatProvider) - { - if (formatProvider == null) - formatProvider = CultureInfo.CurrentCulture; - - if (!string.IsNullOrEmpty(format)) - { - var ch = format[0]; - if ((ch == 'x') || (ch == 'X')) - { - int min; - int.TryParse(format.Substring(1).Trim(), out min); - return ToHexaString(ch == 'X', min); - } - - if ((ch != 'G') && (ch != 'g') && (ch != 'D') && (ch != 'd')) - throw new NotSupportedException("Not supported format: " + format); - } - - return ToString((NumberFormatInfo) formatProvider.GetFormat(typeof(NumberFormatInfo))); - } - - private string ToHexaString(bool caps, int min) - { - var sb = new StringBuilder(); - var x = caps ? "X" : "x"; - if ((min < 0) || (min > 16) || (_hi != 0)) - { - sb.Append(min > 16 ? _hi.ToString(x + (min - 16)) : _hi.ToString(x)); - sb.Append(_lo.ToString(x + "16")); - } - else - { - sb.Append(_lo.ToString(x + min)); - } - return sb.ToString(); - } - - private string ToString(NumberFormatInfo info) - { - if (Sign == 0) - return "0"; - - var sb = new StringBuilder(); - var ten = new Int128(10); - var current = this; - current._hi &= ~HiNeg; - Int128 r; - while (true) - { - current = DivRem(current, ten, out r); - if ((r._lo > 0) || (current.Sign != 0) || (sb.Length == 0)) - { -#if !WINDOWS_PHONE && !SILVERLIGHT - sb.Insert(0, (char) ('0' + r._lo)); -#else - sb.Insert(0, new[] { (char)('0' + r._lo) }); -#endif - } - if (current.Sign == 0) - break; - } - - var s = sb.ToString(); - if ((Sign < 0) && (s != "0")) - return info.NegativeSign + s; - - return s; - } - - /// - /// Returns the for this instance. - /// - /// - /// The enumerated constant that is the of the class or value type that implements - /// this interface. - /// - TypeCode IConvertible.GetTypeCode() - { - return TypeCode.Object; - } - - /// - /// Converts the value of this instance to an equivalent Boolean value using the specified culture-specific formatting - /// information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// A Boolean value equivalent to the value of this instance. - /// - bool IConvertible.ToBoolean(IFormatProvider provider) - { - return (bool) this; - } - - /// - /// Converts the value of this instance to an equivalent 8-bit unsigned integer using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An 8-bit unsigned integer equivalent to the value of this instance. - /// - byte IConvertible.ToByte(IFormatProvider provider) - { - return (byte) this; - } - - /// - /// Converts the value of this instance to an equivalent Unicode character using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// A Unicode character equivalent to the value of this instance. - /// - char IConvertible.ToChar(IFormatProvider provider) - { - return (char) this; - } - - /// - /// Converts the value of this instance to an equivalent using the specified - /// culture-specific formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// A instance equivalent to the value of this instance. - /// - DateTime IConvertible.ToDateTime(IFormatProvider provider) - { - throw new InvalidCastException(); - } - - /// - /// Converts the value of this instance to an equivalent number using the specified - /// culture-specific formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// A number equivalent to the value of this instance. - /// - decimal IConvertible.ToDecimal(IFormatProvider provider) - { - return (decimal) this; - } - - /// - /// Converts the value of this instance to an equivalent double-precision floating-point number using the specified - /// culture-specific formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// A double-precision floating-point number equivalent to the value of this instance. - /// - double IConvertible.ToDouble(IFormatProvider provider) - { - return (double) this; - } - - /// - /// Converts the value of this instance to an equivalent 16-bit signed integer using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An 16-bit signed integer equivalent to the value of this instance. - /// - short IConvertible.ToInt16(IFormatProvider provider) - { - return (short) this; - } - - /// - /// Converts the value of this instance to an equivalent 32-bit signed integer using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An 32-bit signed integer equivalent to the value of this instance. - /// - int IConvertible.ToInt32(IFormatProvider provider) - { - return (int) this; - } - - /// - /// Converts the value of this instance to an equivalent 64-bit signed integer using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An 64-bit signed integer equivalent to the value of this instance. - /// - long IConvertible.ToInt64(IFormatProvider provider) - { - return (int) this; - } - - /// - /// Converts the value of this instance to an equivalent 8-bit signed integer using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An 8-bit signed integer equivalent to the value of this instance. - /// - sbyte IConvertible.ToSByte(IFormatProvider provider) - { - return (sbyte) this; - } - - /// - /// Converts the value of this instance to an equivalent single-precision floating-point number using the specified - /// culture-specific formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// A single-precision floating-point number equivalent to the value of this instance. - /// - float IConvertible.ToSingle(IFormatProvider provider) - { - return (float) this; - } - - /// - /// Returns a that represents this instance. - /// - /// The provider. - /// - /// A that represents this instance. - /// - string IConvertible.ToString(IFormatProvider provider) - { - return ToString(null, provider); - } - - /// - /// Converts the numeric value to an equivalent object. The return value indicates whether the conversion succeeded. - /// - /// The target conversion type. - /// An object that supplies culture-specific information about the conversion. - /// - /// When this method returns, contains the value that is equivalent to the numeric value, if the - /// conversion succeeded, or is null if the conversion failed. This parameter is passed uninitialized. - /// - /// true if this value was converted successfully; otherwise, false. - public bool TryConvert(Type conversionType, IFormatProvider provider, out object value) - { - if (conversionType == typeof(bool)) - { - value = (bool) this; - return true; - } - - if (conversionType == typeof(byte)) - { - value = (byte) this; - return true; - } - - if (conversionType == typeof(char)) - { - value = (char) this; - return true; - } - - if (conversionType == typeof(decimal)) - { - value = (decimal) this; - return true; - } - - if (conversionType == typeof(double)) - { - value = (double) this; - return true; - } - - if (conversionType == typeof(short)) - { - value = (short) this; - return true; - } - - if (conversionType == typeof(int)) - { - value = (int) this; - return true; - } - - if (conversionType == typeof(long)) - { - value = (long) this; - return true; - } - - if (conversionType == typeof(sbyte)) - { - value = (sbyte) this; - return true; - } - - if (conversionType == typeof(float)) - { - value = (float) this; - return true; - } - - if (conversionType == typeof(string)) - { - value = ToString(null, provider); - return true; - } - - if (conversionType == typeof(ushort)) - { - value = (ushort) this; - return true; - } - - if (conversionType == typeof(uint)) - { - value = (uint) this; - return true; - } - - if (conversionType == typeof(ulong)) - { - value = (ulong) this; - return true; - } - - if (conversionType == typeof(byte[])) - { - value = ToByteArray(); - return true; - } - - if (conversionType == typeof(Guid)) - { - value = new Guid(ToByteArray()); - return true; - } - - value = null; - return false; - } - - /// - /// Converts the string representation of a number to its Int128 equivalent. - /// - /// A string that contains a number to convert. - /// - /// A value that is equivalent to the number specified in the value parameter. - /// - public static Int128 Parse(string value) - { - return Parse(value, NumberStyles.Integer, NumberFormatInfo.CurrentInfo); - } - - /// - /// Converts the string representation of a number in a specified style format to its Int128 equivalent. - /// - /// A string that contains a number to convert. - /// A bitwise combination of the enumeration values that specify the permitted format of value. - /// - /// A value that is equivalent to the number specified in the value parameter. - /// - public static Int128 Parse(string value, NumberStyles style) - { - return Parse(value, style, NumberFormatInfo.CurrentInfo); - } - - /// - /// Converts the string representation of a number in a culture-specific format to its Int128 equivalent. - /// - /// A string that contains a number to convert. - /// An object that provides culture-specific formatting information about value. - /// - /// A value that is equivalent to the number specified in the value parameter. - /// - public static Int128 Parse(string value, IFormatProvider provider) - { - return Parse(value, NumberStyles.Integer, NumberFormatInfo.GetInstance(provider)); - } - - /// - /// Converts the string representation of a number in a specified style and culture-specific format to its Int128 - /// equivalent. - /// - /// A string that contains a number to convert. - /// A bitwise combination of the enumeration values that specify the permitted format of value. - /// An object that provides culture-specific formatting information about value. - /// A value that is equivalent to the number specified in the value parameter. - public static Int128 Parse(string value, NumberStyles style, IFormatProvider provider) - { - Int128 result; - if (!TryParse(value, style, provider, out result)) - throw new ArgumentException(null, "value"); - - return result; - } - - /// - /// Tries to convert the string representation of a number to its Int128 equivalent, and returns a value that indicates - /// whether the conversion succeeded.. - /// - /// The string representation of a number. - /// - /// When this method returns, contains the Int128 equivalent to the number that is contained in value, - /// or Int128.Zero if the conversion failed. This parameter is passed uninitialized. - /// - /// - /// true if the value parameter was converted successfully; otherwise, false. - /// - public static bool TryParse(string value, out Int128 result) - { - return TryParse(value, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result); - } - - /// - /// Tries to convert the string representation of a number in a specified style and culture-specific format to its - /// Int128 equivalent, and returns a value that indicates whether the conversion succeeded.. - /// - /// - /// The string representation of a number. The string is interpreted using the style specified by - /// style. - /// - /// - /// A bitwise combination of enumeration values that indicates the style elements that can be present - /// in value. A typical value to specify is NumberStyles.Integer. - /// - /// An object that supplies culture-specific formatting information about value. - /// - /// When this method returns, contains the Int128 equivalent to the number that is contained in value, - /// or Int128.Zero if the conversion failed. This parameter is passed uninitialized. - /// - /// true if the value parameter was converted successfully; otherwise, false. - public static bool TryParse(string value, NumberStyles style, IFormatProvider provider, out Int128 result) - { - result = Zero; - if (string.IsNullOrEmpty(value)) - return false; - - if (value.StartsWith("x", StringComparison.OrdinalIgnoreCase)) - { - style |= NumberStyles.AllowHexSpecifier; - value = value.Substring(1); - } - else if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) - { - style |= NumberStyles.AllowHexSpecifier; - value = value.Substring(2); - } - - if ((style & NumberStyles.AllowHexSpecifier) == NumberStyles.AllowHexSpecifier) - return TryParseHex(value, out result); - - return TryParseNum(value, out result); - } - - private static bool TryParseHex(string value, out Int128 result) - { - if (value.Length > 32) - throw new OverflowException(); - - result = Zero; - var hi = false; - var pos = 0; - for (var i = value.Length - 1; i >= 0; i--) - { - var ch = value[i]; - ulong b; - if ((ch >= '0') && (ch <= '9')) - b = (ulong) (ch - '0'); - else if ((ch >= 'A') && (ch <= 'F')) - b = (ulong) (ch - 'A' + 10); - else if ((ch >= 'a') && (ch <= 'f')) - b = (ulong) (ch - 'a' + 10); - else - return false; - - if (hi) - { - result._hi |= b << pos; - pos += 4; - } - else - { - result._lo |= b << pos; - pos += 4; - if (pos == 64) - { - pos = 0; - hi = true; - } - } - } - return true; - } - - private static bool TryParseNum(string value, out Int128 result) - { - result = Zero; - foreach (var ch in value) - { - byte b; - if ((ch >= '0') && (ch <= '9')) - b = (byte) (ch - '0'); - else - return false; - - result = 10*result; - result += b; - } - return true; - } - - /// - /// Converts the value of this instance to an of the specified - /// that has an equivalent value, using the specified culture-specific formatting - /// information. - /// - /// The to which the value of this instance is converted. - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An instance of type whose value is equivalent to - /// the value of this instance. - /// - public object ToType(Type conversionType, IFormatProvider provider) - { - object value; - if (TryConvert(conversionType, provider, out value)) - return value; - - throw new InvalidCastException(); - } - - /// - /// Converts the value of this instance to an equivalent 16-bit unsigned integer using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An 16-bit unsigned integer equivalent to the value of this instance. - /// - ushort IConvertible.ToUInt16(IFormatProvider provider) - { - if (_hi != 0) - throw new OverflowException(); - - return Convert.ToUInt16(_lo); - } - - /// - /// Converts the value of this instance to an equivalent 32-bit unsigned integer using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An 32-bit unsigned integer equivalent to the value of this instance. - /// - uint IConvertible.ToUInt32(IFormatProvider provider) - { - if (_hi != 0) - throw new OverflowException(); - - return Convert.ToUInt32(_lo); - } - - /// - /// Converts the value of this instance to an equivalent 64-bit unsigned integer using the specified culture-specific - /// formatting information. - /// - /// - /// An interface implementation that supplies - /// culture-specific formatting information. - /// - /// - /// An 64-bit unsigned integer equivalent to the value of this instance. - /// - ulong IConvertible.ToUInt64(IFormatProvider provider) - { - if (_hi != 0) - throw new OverflowException(); - - return _lo; - } - - /// - /// Compares the current instance with another object of the same type and returns an integer that indicates whether - /// the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - /// - /// An object to compare with this instance. - /// - /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value - /// Meaning Less than zero This instance is less than . Zero This instance is equal to - /// . Greater than zero This instance is greater than . - /// - /// - /// is not the same type as this instance. - /// - int IComparable.CompareTo(object obj) - { - return Compare(this, obj); - } - - /// - /// Compares two Int128 values and returns an integer that indicates whether the first value is less than, equal to, or - /// greater than the second value. - /// - /// The first value to compare. - /// The second value to compare. - /// A signed integer that indicates the relative values of left and right, as shown in the following table. - public static int Compare(Int128 left, object right) - { - if (right is Int128) - return Compare(left, (Int128) right); - - // NOTE: this could be optimized type per type - if (right is bool) - return Compare(left, new Int128((bool) right)); - - if (right is byte) - return Compare(left, new Int128((byte) right)); - - if (right is char) - return Compare(left, new Int128((char) right)); - - if (right is decimal) - return Compare(left, new Int128((decimal) right)); - - if (right is double) - return Compare(left, new Int128((double) right)); - - if (right is short) - return Compare(left, new Int128((short) right)); - - if (right is int) - return Compare(left, new Int128((int) right)); - - if (right is long) - return Compare(left, new Int128((long) right)); - - if (right is sbyte) - return Compare(left, new Int128((sbyte) right)); - - if (right is float) - return Compare(left, new Int128((float) right)); - - if (right is ushort) - return Compare(left, new Int128((ushort) right)); - - if (right is uint) - return Compare(left, new Int128((uint) right)); - - if (right is ulong) - return Compare(left, new Int128((ulong) right)); - - var bytes = right as byte[]; - if ((bytes != null) && (bytes.Length != 16)) - return Compare(left, new Int128(bytes)); - - if (right is Guid) - return Compare(left, new Int128((Guid) right)); - - throw new ArgumentException(); - } - - /// - /// Converts an Int128 value to a byte array. - /// - /// The value of the current Int128 object converted to an array of bytes. - public byte[] ToByteArray() - { - var bytes = new byte[16]; - Buffer.BlockCopy(BitConverter.GetBytes(_lo), 0, bytes, 0, 8); - Buffer.BlockCopy(BitConverter.GetBytes(_hi), 0, bytes, 8, 8); - return bytes; - } - - /// - /// Compares two 128-bit signed integer values and returns an integer that indicates whether the first value is less - /// than, equal to, or greater than the second value. - /// - /// The first value to compare. - /// The second value to compare. - /// - /// A signed number indicating the relative values of this instance and value. - /// - public static int Compare(Int128 left, Int128 right) - { - if (left.Sign < 0) - { - if (right.Sign >= 0) - return -1; - - var xhi = left._hi & ~HiNeg; - var yhi = right._hi & ~HiNeg; - if (xhi != yhi) - return -xhi.CompareTo(yhi); - - return -left._lo.CompareTo(right._lo); - } - - if (right.Sign < 0) - return 1; - - if (left._hi != right._hi) - return left._hi.CompareTo(right._hi); - - return left._lo.CompareTo(right._lo); - } - - /// - /// Compares this instance to a specified 128-bit signed integer and returns an indication of their relative values. - /// - /// An integer to compare. - /// A signed number indicating the relative values of this instance and value. - public int CompareTo(Int128 value) - { - return Compare(this, value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// if set to true [value]. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(bool value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(byte value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(char value) - { - return new Int128(value); - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator Int128(decimal value) - { - return new Int128(value); - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator Int128(double value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(short value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(int value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(long value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(sbyte value) - { - return new Int128(value); - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator Int128(float value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(ushort value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(uint value) - { - return new Int128(value); - } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static implicit operator Int128(ulong value) - { - return new Int128(value); - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator bool(Int128 value) - { - return value.Sign != 0; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator byte(Int128 value) - { - if (value.Sign == 0) - return 0; - - if ((value.Sign < 0) || (value._lo > 0xFF)) - throw new OverflowException(); - - return (byte) value._lo; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator char(Int128 value) - { - if (value.Sign == 0) - return (char) 0; - - if ((value.Sign < 0) || (value._lo > 0xFFFF)) - throw new OverflowException(); - - return (char) (ushort) value._lo; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator decimal(Int128 value) - { - if (value.Sign == 0) - return 0; - - return new decimal((int) (value._lo & 0xFFFFFFFF), (int) (value._lo >> 32), - (int) (value._hi & 0xFFFFFFFF), - value.Sign < 0, 0); - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator double(Int128 value) - { - if (value.Sign == 0) - return 0; - - double d; - var nfi = CultureInfo.InvariantCulture.NumberFormat; - if (!double.TryParse(value.ToString(nfi), NumberStyles.Number, nfi, out d)) - throw new OverflowException(); - - return d; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator float(Int128 value) - { - if (value.Sign == 0) - return 0; - - float f; - var nfi = CultureInfo.InvariantCulture.NumberFormat; - if (!float.TryParse(value.ToString(nfi), NumberStyles.Number, nfi, out f)) - throw new OverflowException(); - - return f; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator short(Int128 value) - { - if (value.Sign == 0) - return 0; - - if (value._lo > 0x8000) - throw new OverflowException(); - - if ((value._lo == 0x8000) && (value.Sign > 0)) - throw new OverflowException(); - - return (short) ((int) value._lo*value.Sign); - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator int(Int128 value) - { - if (value.Sign == 0) - return 0; - - if (value._lo > 0x80000000) - throw new OverflowException(); - - if ((value._lo == 0x80000000) && (value.Sign > 0)) - throw new OverflowException(); - - return (int) value._lo*value.Sign; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator long(Int128 value) - { - if (value.Sign == 0) - return 0; - - if (value._lo > long.MaxValue) - throw new OverflowException(); - - return (long) value._lo*value.Sign; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator uint(Int128 value) - { - if (value.Sign == 0) - return 0; - - if ((value.Sign < 0) || (value._lo > uint.MaxValue)) - throw new OverflowException(); - - return (uint) value._lo; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator ushort(Int128 value) - { - if (value.Sign == 0) - return 0; - - if ((value.Sign < 0) || (value._lo > ushort.MaxValue)) - throw new OverflowException(); - - return (ushort) value._lo; - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// - /// The result of the conversion. - /// - public static explicit operator ulong(Int128 value) - { - if ((value.Sign < 0) || (value._hi != 0)) - throw new OverflowException(); - - return value._lo; - } - - /// - /// Implements the operator >. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static bool operator >(Int128 left, Int128 right) - { - return Compare(left, right) > 0; - } - - /// - /// Implements the operator <. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static bool operator <(Int128 left, Int128 right) - { - return Compare(left, right) < 0; - } - - /// - /// Implements the operator >=. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static bool operator >=(Int128 left, Int128 right) - { - return Compare(left, right) >= 0; - } - - /// - /// Implements the operator <=. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static bool operator <=(Int128 left, Int128 right) - { - return Compare(left, right) <= 0; - } - - /// - /// Implements the operator !=. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static bool operator !=(Int128 left, Int128 right) - { - return Compare(left, right) != 0; - } - - /// - /// Implements the operator ==. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static bool operator ==(Int128 left, Int128 right) - { - return Compare(left, right) == 0; - } - - /// - /// Implements the operator +. - /// - /// The value. - /// - /// The result of the operator. - /// - public static Int128 operator +(Int128 value) - { - return value; - } - - /// - /// Implements the operator -. - /// - /// The value. - /// - /// The result of the operator. - /// - public static Int128 operator -(Int128 value) - { - return Negate(value); - } - - /// - /// Negates a specified Int128 value. - /// - /// The value to negate. - /// The result of the value parameter multiplied by negative one (-1). - public static Int128 Negate(Int128 value) - { - var neg = value; - var sign = value.Sign; - if (sign < 0) - neg._hi &= ~HiNeg; - else - neg._hi |= HiNeg; - return neg; - } - - /// - /// Gets the absolute value this object. - /// - /// The absolute value. - public Int128 ToAbs() - { - return Abs(this); - } - - /// - /// Gets the absolute value of an Int128 object. - /// - /// A number. - /// - /// The absolute value. - /// - public static Int128 Abs(Int128 value) - { - if (value.Sign < 0) - return -value; - - return value; - } - - /// - /// Implements the operator +. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static Int128 operator +(Int128 left, Int128 right) - { - var add = left; - add._hi += right._hi; - add._lo += right._lo; - if (add._lo < left._lo) - add._hi++; - return add; - } - - /// - /// Implements the operator -. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static Int128 operator -(Int128 left, Int128 right) - { - return left + -right; - } - - /// - /// Adds two Int128 values and returns the result. - /// - /// The first value to add. - /// The second value to add. - /// The sum of left and right. - public static Int128 Add(Int128 left, Int128 right) - { - return left + right; - } - - /// - /// Subtracts one Int128 value from another and returns the result. - /// - /// The value to subtract from (the minuend). - /// The value to subtract (the subtrahend). - /// The result of subtracting right from left. - public static Int128 Subtract(Int128 left, Int128 right) - { - return left - right; - } - - /// - /// Divides one Int128 value by another and returns the result. - /// - /// The value to be divided. - /// The value to divide by. - /// The quotient of the division. - public static Int128 Divide(Int128 dividend, Int128 divisor) - { - Int128 integer; - return DivRem(dividend, divisor, out integer); - } - - /// - /// Divides one Int128 value by another, returns the result, and returns the remainder in an output parameter. - /// - /// The value to be divided. - /// The value to divide by. - /// - /// When this method returns, contains an Int128 value that represents the remainder from the - /// division. This parameter is passed uninitialized. - /// - /// - /// The quotient of the division. - /// - public static Int128 DivRem(Int128 dividend, Int128 divisor, out Int128 remainder) - { - if (divisor == 0) - throw new DivideByZeroException(); - - uint[] quotient; - uint[] rem; - DivRem(dividend.ToUIn32Array(), divisor.ToUIn32Array(), out quotient, out rem); - remainder = new Int128(1, rem); - return new Int128(dividend.Sign*divisor.Sign, quotient); - } - - private static void DivRem(uint[] dividend, uint[] divisor, out uint[] quotient, out uint[] remainder) - { - const ulong hiBit = 0x100000000; - var divisorLen = GetLength(divisor); - var dividendLen = GetLength(dividend); - if (divisorLen <= 1) - { - ulong rem = 0; - var div = divisor[0]; - quotient = new uint[dividendLen]; - remainder = new uint[1]; - for (var i = dividendLen - 1; i >= 0; i--) - { - rem *= hiBit; - rem += dividend[i]; - var q = rem/div; - rem -= q*div; - quotient[i] = (uint) q; - } - remainder[0] = (uint) rem; - return; - } - - if (dividendLen >= divisorLen) - { - var shift = GetNormalizeShift(divisor[divisorLen - 1]); - var normDividend = new uint[dividendLen + 1]; - var normDivisor = new uint[divisorLen]; - Normalize(dividend, dividendLen, normDividend, shift); - Normalize(divisor, divisorLen, normDivisor, shift); - quotient = new uint[dividendLen - divisorLen + 1]; - for (var j = dividendLen - divisorLen; j >= 0; j--) - { - var dx = hiBit*normDividend[j + divisorLen] + normDividend[j + divisorLen - 1]; - var qj = dx/normDivisor[divisorLen - 1]; - dx -= qj*normDivisor[divisorLen - 1]; - do - { - if ((qj < hiBit) && - (qj*normDivisor[divisorLen - 2] <= dx*hiBit + normDividend[j + divisorLen - 2])) - break; - - qj -= 1L; - dx += normDivisor[divisorLen - 1]; - } while (dx < hiBit); - - long di = 0; - long dj; - var index = 0; - while (index < divisorLen) - { - var dqj = normDivisor[index]*qj; - dj = normDividend[index + j] - (uint) dqj - di; - normDividend[index + j] = (uint) dj; - dqj = dqj >> 32; - dj = dj >> 32; - di = (long) dqj - dj; - index++; - } - - dj = normDividend[j + divisorLen] - di; - normDividend[j + divisorLen] = (uint) dj; - quotient[j] = (uint) qj; - - if (dj < 0) - { - quotient[j]--; - ulong sum = 0; - for (index = 0; index < divisorLen; index++) - { - sum = normDivisor[index] + normDividend[j + index] + sum; - normDividend[j + index] = (uint) sum; - sum = sum >> 32; - } - sum += normDividend[j + divisorLen]; - normDividend[j + divisorLen] = (uint) sum; - } - } - remainder = Unnormalize(normDividend, shift); - return; - } - - quotient = new uint[0]; - remainder = dividend; - } - - private static int GetLength(uint[] uints) - { - var index = uints.Length - 1; - while ((index >= 0) && (uints[index] == 0)) - index--; - return index + 1; - } - - private static int GetNormalizeShift(uint ui) - { - var shift = 0; - if ((ui & 0xffff0000) == 0) - { - ui = ui << 16; - shift += 16; - } - - if ((ui & 0xff000000) == 0) - { - ui = ui << 8; - shift += 8; - } - - if ((ui & 0xf0000000) == 0) - { - ui = ui << 4; - shift += 4; - } - - if ((ui & 0xc0000000) == 0) - { - ui = ui << 2; - shift += 2; - } - - if ((ui & 0x80000000) == 0) - shift++; - return shift; - } - - private static uint[] Unnormalize(uint[] normalized, int shift) - { - var len = GetLength(normalized); - var unormalized = new uint[len]; - if (shift > 0) - { - var rshift = 32 - shift; - uint r = 0; - for (var i = len - 1; i >= 0; i--) - { - unormalized[i] = (normalized[i] >> shift) | r; - r = normalized[i] << rshift; - } - } - else - { - for (var j = 0; j < len; j++) - unormalized[j] = normalized[j]; - } - return unormalized; - } - - private static void Normalize(uint[] unormalized, int len, uint[] normalized, int shift) - { - int i; - uint n = 0; - if (shift > 0) - { - var rshift = 32 - shift; - for (i = 0; i < len; i++) - { - normalized[i] = (unormalized[i] << shift) | n; - n = unormalized[i] >> rshift; - } - } - else - { - i = 0; - while (i < len) - { - normalized[i] = unormalized[i]; - i++; - } - } - - while (i < normalized.Length) - normalized[i++] = 0; - - if (n != 0) - normalized[len] = n; - } - - /// - /// Performs integer division on two Int128 values and returns the remainder. - /// - /// The value to be divided. - /// The value to divide by. - /// The remainder after dividing dividend by divisor. - public static Int128 Remainder(Int128 dividend, Int128 divisor) - { - Int128 remainder; - DivRem(dividend, divisor, out remainder); - return remainder; - } - - /// - /// Implements the operator %. - /// - /// The dividend. - /// The divisor. - /// - /// The result of the operator. - /// - public static Int128 operator %(Int128 dividend, Int128 divisor) - { - return Remainder(dividend, divisor); - } - - /// - /// Implements the operator /. - /// - /// The dividend. - /// The divisor. - /// - /// The result of the operator. - /// - public static Int128 operator /(Int128 dividend, Int128 divisor) - { - return Divide(dividend, divisor); - } - - /// - /// Converts an int128 value to an unsigned long array. - /// - /// - /// The value of the current Int128 object converted to an array of unsigned integers. - /// - public ulong[] ToUIn64Array() - { - return new[] {_hi, _lo}; - } - - /// - /// Converts an int128 value to an unsigned integer array. - /// - /// The value of the current Int128 object converted to an array of unsigned integers. - public uint[] ToUIn32Array() - { - var ints = new uint[4]; - var lob = BitConverter.GetBytes(_lo); - var hib = BitConverter.GetBytes(_hi); - - Buffer.BlockCopy(lob, 0, ints, 0, 4); - Buffer.BlockCopy(lob, 4, ints, 4, 4); - Buffer.BlockCopy(hib, 0, ints, 8, 4); - Buffer.BlockCopy(hib, 4, ints, 12, 4); - return ints; - } - - /// - /// Returns the product of two Int128 values. - /// - /// The first number to multiply. - /// The second number to multiply. - /// The product of the left and right parameters. - public static Int128 Multiply(Int128 left, Int128 right) - { - var xInts = left.ToUIn32Array(); - var yInts = right.ToUIn32Array(); - var mulInts = new uint[8]; - - for (var i = 0; i < xInts.Length; i++) - { - var index = i; - ulong remainder = 0; - foreach (var yi in yInts) - { - remainder = remainder + (ulong) xInts[i]*yi + mulInts[index]; - mulInts[index++] = (uint) remainder; - remainder = remainder >> 32; - } - - while (remainder != 0) - { - remainder += mulInts[index]; - mulInts[index++] = (uint) remainder; - remainder = remainder >> 32; - } - } - return new Int128(left.Sign*right.Sign, mulInts); - } - - /// - /// Implements the operator *. - /// - /// The x. - /// The y. - /// - /// The result of the operator. - /// - public static Int128 operator *(Int128 left, Int128 right) - { - return Multiply(left, right); - } - - /// - /// Implements the operator >>. - /// - /// The value. - /// The shift. - /// The result of the operator. - public static Int128 operator >>(Int128 value, int shift) - { - if (shift == 0) - return value; - - if (shift < 0) - return value << -shift; - - shift = shift%128; - - var shifted = new Int128(); - - if (shift > 63) - { - shifted._lo = value._hi >> (shift - 64); - shifted._hi = 0; - } - else - { - shifted._hi = value._hi >> shift; - shifted._lo = (value._hi << (64 - shift)) | (value._lo >> shift); - } - return shifted; - } - - /// - /// Implements the operator <<. - /// - /// The value. - /// The shift. - /// The result of the operator. - public static Int128 operator <<(Int128 value, int shift) - { - if (shift == 0) - return value; - - if (shift < 0) - return value >> -shift; - - shift = shift%128; - - var shifted = new Int128(); - - if (shift > 63) - { - shifted._hi = value._lo << (shift - 64); - shifted._lo = 0; - } - else - { - var ul = value._lo >> (64 - shift); - shifted._hi = ul | (value._hi << shift); - shifted._lo = value._lo << shift; - } - return shifted; - } - - /// - /// Implements the operator |. - /// - /// The left. - /// The right. - /// The result of the operator. - public static Int128 operator |(Int128 left, Int128 right) - { - if (left == 0) - return right; - - if (right == 0) - return left; - - var result = left; - result._hi |= right._hi; - result._lo |= right._lo; - return result; - } - - /// - /// Implements the operator ~. - /// - /// The left. - /// The right. - /// The result of the operator. - public static Int128 operator ~(Int128 value) - { - Int128 result = 0; - result._hi = ~value._hi; - result._lo = ~value._lo; - return result; - } - - /// - /// Implements the operator &. - /// - /// The left. - /// The right. - /// The result of the operator. - public static Int128 operator &(Int128 left, Int128 right) - { - if ((left == 0) || (right == 0)) - return Zero; - - var result = left; - result._hi &= right._hi; - result._lo &= right._lo; - return result; - } - -#if !WINDOWS_PHONE && !SILVERLIGHT - void IBinarySerialize.Read(BinaryReader reader) - { - if (reader == null) - throw new ArgumentNullException("reader"); - - _hi = reader.ReadUInt64(); - _lo = reader.ReadUInt64(); - } - - void IBinarySerialize.Write(BinaryWriter writer) - { - if (writer == null) - throw new ArgumentNullException("writer"); - - writer.Write(_hi); - writer.Write(_lo); - } -#endif - - /// - /// Converts a String type to a Int128 type, and vice versa. - /// - public class Int128Converter : TypeConverter - { - /// - /// Returns whether this converter can convert an object of the given type to the type of this converter, using the - /// specified context. - /// - /// An that provides a format context. - /// A that represents the type you want to convert from. - /// - /// true if this converter can perform the conversion; otherwise, false. - /// - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - if (sourceType == typeof(string)) - return true; - - return base.CanConvertFrom(context, sourceType); - } - - /// - /// Converts the given object to the type of this converter, using the specified context and culture information. - /// - /// An that provides a format context. - /// The to use as the current culture. - /// The to convert. - /// - /// An that represents the converted value. - /// - /// - /// The conversion cannot be performed. - /// - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value != null) - { - Int128 i; - if (TryParse(string.Format("{0}", value), out i)) - return i; - } - return new Int128(); - } - - /// - /// Returns whether this converter can convert the object to the specified type, using the specified context. - /// - /// An that provides a format context. - /// A that represents the type you want to convert to. - /// - /// true if this converter can perform the conversion; otherwise, false. - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(string)) - return true; - - return base.CanConvertTo(context, destinationType); - } - - /// - /// Converts the given value object to the specified type, using the specified context and culture information. - /// - /// An that provides a format context. - /// - /// A . If null is passed, the current culture is - /// assumed. - /// - /// The to convert. - /// The to convert the parameter to. - /// - /// An that represents the converted value. - /// - /// - /// The parameter is null. - /// - /// - /// The conversion cannot be performed. - /// - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, - Type destinationType) - { - if (destinationType == typeof(string)) - return string.Format("{0}", value); - - return base.ConvertTo(context, culture, value, destinationType); - } - } - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/WoWEnums.cs b/Artemis/Artemis/Modules/Games/WoW/Data/WoWEnums.cs deleted file mode 100644 index 24fa676fa..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/WoWEnums.cs +++ /dev/null @@ -1,157 +0,0 @@ -namespace Artemis.Modules.Games.WoW.Data -{ - public static class WoWEnums - { - public enum GuidType : byte - { - Null = 0, - Uniq = 1, - Player = 2, - Item = 3, - StaticDoor = 4, - Transport = 5, - Conversation = 6, - Creature = 7, - Vehicle = 8, - Pet = 9, - GameObject = 10, - DynamicObject = 11, - AreaTrigger = 12, - Corpse = 13, - LootObject = 14, - SceneObject = 15, - Scenario = 16, - AiGroup = 17, - DynamicDoor = 18, - ClientActor = 19, - Vignette = 20, - CallForHelp = 21, - AiResource = 22, - AiLock = 23, - AiLockTicket = 24, - ChatChannel = 25, - Party = 26, - Guild = 27, - WowAccount = 28, - BNetAccount = 29, - GmTask = 30, - MobileSession = 31, - RaidGroup = 32, - Spell = 33, - Mail = 34, - WebObj = 35, - LfgObject = 36, - LfgList = 37, - UserRouter = 38, - PvpQueueGroup = 39, - UserClient = 40, - PetBattle = 41, - UniqueUserClient = 42, - BattlePet = 43 - } - - public enum ObjectType - { - Object = 0, - Item = 1, - Container = 2, - Unit = 3, - Player = 4, - GameObject = 5, - DynamicObject = 6, - Corpse = 7, - AreaTrigger = 8, - SceneObject = 9, - Conversation = 10 - } - - public enum PowerType - { - Mana = 0, - Rage = 1, - Focus = 2, - Energy = 3, - Happiness = 4, - RunicPower = 5, - Runes = 6, - Health = 7, - Maelstrom = 11, - Insanity = 13, - Fury = 17, - Pain = 18, - UNKNOWN - } - - public enum Reaction - { - Hostile = 1, - Neutral = 3, - Friendly = 4 - } - - public enum ShapeshiftForm - { - Normal = 0, - Cat = 1, - TreeOfLife = 2, - Travel = 3, - Aqua = 4, - Bear = 5, - Ambient = 6, - Ghoul = 7, - DireBear = 8, - CreatureBear = 14, - CreatureCat = 15, - GhostWolf = 16, - BattleStance = 17, - DefensiveStance = 18, - BerserkerStance = 19, - EpicFlightForm = 27, - Shadow = 28, - Stealth = 30, - Moonkin = 31, - SpiritOfRedemption = 32 - } - - public enum WoWClass - { - None = 0, - Warrior = 1, - Paladin = 2, - Hunter = 3, - Rogue = 4, - Priest = 5, - DeathKnight = 6, - Shaman = 7, - Mage = 8, - Warlock = 9, - Druid = 11 - } - - public enum WoWRace - { - Human = 1, - Orc = 2, - Dwarf = 3, - NightElf = 4, - Undead = 5, - Tauren = 6, - Gnome = 7, - Troll = 8, - Goblin = 9, - BloodElf = 10, - Draenei = 11, - FelOrc = 12, - Naga = 13, - Broken = 14, - Skeleton = 15, - Worgen = 22 - } - - public enum WoWType - { - Player, - Npc - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/WoWNameCache.cs b/Artemis/Artemis/Modules/Games/WoW/Data/WoWNameCache.cs deleted file mode 100644 index 78677f117..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/WoWNameCache.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Text; -using Process.NET; - -namespace Artemis.Modules.Games.WoW.Data -{ - public class WoWNameCache - { - public WoWNameCache(ProcessSharp process, IntPtr baseAddress) - { - Process = process; - CurrentCacheAddress = process.Native.MainModule.BaseAddress + baseAddress.ToInt32(); - } - - public ProcessSharp Process { get; set; } - - public IntPtr CurrentCacheAddress { get; set; } - - public WoWDetails GetNameByGuid(Guid searchGuid) - { - var current = Process.Memory.Read(CurrentCacheAddress); - var index = 0; - while (current != IntPtr.Zero) - { - var guid = Process.Memory.Read(current + 0x20); - if (guid.Equals(searchGuid)) - { - var pRace = Process.Memory.Read(current + 0x88); - var pClass = Process.Memory.Read(current + 0x90); - var pName = Process.Memory.Read(current + 0x31, Encoding.ASCII, 48); - - var name = new WoWDetails(guid, pRace, pClass, WoWEnums.WoWType.Player, pName); - return name; - } - - if (index > 20000) - return null; - - index++; - current = Process.Memory.Read(current); - } - return null; - } - } - - public class WoWDetails - { - public WoWDetails(Guid guid, int race, int @class, WoWEnums.WoWType type, string name) - { - Guid = guid; - Race = (WoWEnums.WoWRace) race; - Class = (WoWEnums.WoWClass) @class; - Type = type; - Name = name; - } - - public Guid Guid { get; set; } - public WoWEnums.WoWRace Race { get; set; } - public WoWEnums.WoWClass Class { get; set; } - public WoWEnums.WoWType Type { get; set; } - public string Name { get; set; } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/WoWObject.cs b/Artemis/Artemis/Modules/Games/WoW/Data/WoWObject.cs deleted file mode 100644 index a8db9114c..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/WoWObject.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Text; -using Newtonsoft.Json; -using Process.NET; - -namespace Artemis.Modules.Games.WoW.Data -{ - public class WoWObject - { - private readonly bool _readPointer; - - public WoWObject(IProcess process, IntPtr baseAddress, bool readPointer = false) - { - Process = process; - BaseAddress = baseAddress; - _readPointer = readPointer; - - Guid = ReadField(0x00); - } - - [JsonIgnore] - public IntPtr BaseAddress { get; set; } - - [JsonIgnore] - public IProcess Process { get; set; } - - public Guid Guid { get; set; } - - [JsonIgnore] - public WoWStructs.ObjectData Data { get; set; } - - public T ReadField(int offset) - { - var address = GetAddress(); - if (address == IntPtr.Zero) - return default(T); - - var ptr = Process.Memory.Read(address + 0x10); - return Process.Memory.Read(ptr + offset); - } - - public T ReadField(Enum offset) - { - var address = GetAddress(); - if (address == IntPtr.Zero) - return default(T); - - var ptr = Process.Memory.Read(address + 0x10); - return Process.Memory.Read(ptr + Convert.ToInt32(offset)); - } - - private IntPtr GetAddress() - { - return _readPointer - ? Process.Memory.Read(Process.Native.MainModule.BaseAddress + BaseAddress.ToInt32()) - : BaseAddress; - } - - public WoWDetails GetNpcDetails() - { - var address = GetAddress(); - if (address == IntPtr.Zero) - return null; - - var npcCachePtr = Process.Memory.Read(address + 0x1760); - if (npcCachePtr == IntPtr.Zero) - return null; - - var npcName = Process.Memory.Read(Process.Memory.Read(npcCachePtr + 0x00A0), Encoding.ASCII, 48); - return new WoWDetails(Guid, 0, 0, WoWEnums.WoWType.Npc, npcName); - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/WoWObjectManager.cs b/Artemis/Artemis/Modules/Games/WoW/Data/WoWObjectManager.cs deleted file mode 100644 index e47ff61ec..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/WoWObjectManager.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Process.NET; - -namespace Artemis.Modules.Games.WoW.Data -{ - public class WoWObjectManager - { - public WoWObjectManager(IProcess process, IntPtr baseAddress) - { - Process = process; - CurrentManagerAddress = process.Native.MainModule.BaseAddress + baseAddress.ToInt32(); - } - - public IProcess Process { get; set; } - - public IntPtr CurrentManagerAddress { get; set; } - - public Dictionary WoWObjects { get; set; } - - public IntPtr GetFirstObject() - { - var mgr = GetCurrentManager(); - return mgr.VisibleObjects.m_fulllist.baseClass.m_terminator.m_next; - } - - public WoWStructs.CurrentManager GetCurrentManager() - { - return Process.Memory.Read(Process.Memory.Read(CurrentManagerAddress)); - } - - public IntPtr GetNextObjectFromCurrent(IntPtr current) - { - var mgr = GetCurrentManager(); - - return Process.Memory.Read( - current + mgr.VisibleObjects.m_fulllist.baseClass.m_linkoffset + IntPtr.Size); - } - - public void Update() - { - WoWObjects.Clear(); - var wowObjects = EnumVisibleObjects(); - foreach (var wowObject in wowObjects) - WoWObjects[wowObject.Data.Guid] = wowObject; - - OnObjectsUpdated(WoWObjects); - } - - public event EventHandler> ObjectsUpdated; - - // Loop through the games object list. - public IEnumerable EnumVisibleObjects() - { - var first = GetFirstObject(); - var typeOffset = Marshal.OffsetOf(typeof(WoWStructs.ObjectData), "ObjectType").ToInt32(); - - while (((first.ToInt64() & 1) == 0) && (first != IntPtr.Zero)) - { - var type = (WoWEnums.ObjectType) Process.Memory.Read(first + typeOffset); - - // Fix below with other object types as added. - // ReSharper disable once SwitchStatementMissingSomeCases - switch (type) - { - case WoWEnums.ObjectType.Object: - yield return new WoWObject(Process, first); - break; - case WoWEnums.ObjectType.Container: - break; - case WoWEnums.ObjectType.Unit: - yield return new WoWUnit(Process, first); - break; - case WoWEnums.ObjectType.Player: - yield return new WoWPlayer(Process, first, new IntPtr(0x179A6E0)); - break; - default: - yield return new WoWObject(Process, first); - break; - } - - first = GetNextObjectFromCurrent(first); - } - } - - protected virtual void OnObjectsUpdated(Dictionary e) - { - ObjectsUpdated?.Invoke(this, e); - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/WoWOffsets.cs b/Artemis/Artemis/Modules/Games/WoW/Data/WoWOffsets.cs deleted file mode 100644 index b4037a419..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/WoWOffsets.cs +++ /dev/null @@ -1,368 +0,0 @@ -namespace Artemis.Modules.Games.WoW.Data -{ - internal static class WoWOffsets - { - internal enum AreaTriggerData - { - OverrideScaleCurve = 0x30, // Size: 0x7, Flags: 0x201 - ExtraScaleCurve = 0x4C, // Size: 0x7, Flags: 0x201 - Caster = 0x68, // Size: 0x4, Flags: 0x1 - Duration = 0x78, // Size: 0x1, Flags: 0x1 - TimeToTarget = 0x7C, // Size: 0x1, Flags: 0x201 - TimeToTargetScale = 0x80, // Size: 0x1, Flags: 0x201 - TimeToTargetExtraScale = 0x84, // Size: 0x1, Flags: 0x201 - SpellId = 0x88, // Size: 0x1, Flags: 0x1 - SpellVisualId = 0x8C, // Size: 0x1, Flags: 0x80 - BoundsRadius2D = 0x90, // Size: 0x1, Flags: 0x280 - DecalPropertiesId = 0x94 // Size: 0x1, Flags: 0x1 - } - - internal enum ContainerData - { - Slots = 0x150, // Size: 0x90, Flags: 0x1 - NumSlots = 0x390 // Size: 0x1, Flags: 0x1 - } - - internal enum ConversationData - { - LastLineDuration = 0x30 // Size: 0x1, Flags: 0x80 - } - - internal enum CorpseData - { - Owner = 0x30, // Size: 0x4, Flags: 0x1 - PartyGuid = 0x40, // Size: 0x4, Flags: 0x1 - DisplayId = 0x50, // Size: 0x1, Flags: 0x1 - Items = 0x54, // Size: 0x13, Flags: 0x1 - SkinId = 0xA0, // Size: 0x1, Flags: 0x1 - FacialHairStyleId = 0xA4, // Size: 0x1, Flags: 0x1 - Flags = 0xA8, // Size: 0x1, Flags: 0x1 - DynamicFlags = 0xAC, // Size: 0x1, Flags: 0x80 - FactionTemplate = 0xB0, // Size: 0x1, Flags: 0x1 - CustomDisplayOption = 0xB4 // Size: 0x1, Flags: 0x1 - } - - internal enum DynamicObjectData - { - Caster = 0x30, // Size: 0x4, Flags: 0x1 - TypeAndVisualId = 0x40, // Size: 0x1, Flags: 0x80 - SpellId = 0x44, // Size: 0x1, Flags: 0x1 - Radius = 0x48, // Size: 0x1, Flags: 0x1 - CastTime = 0x4C // Size: 0x1, Flags: 0x1 - } - - internal enum GameObjectData - { - CreatedBy = 0x30, // Size: 0x4, Flags: 0x1 - DisplayId = 0x40, // Size: 0x1, Flags: 0x280 - Flags = 0x44, // Size: 0x1, Flags: 0x201 - ParentRotation = 0x48, // Size: 0x4, Flags: 0x1 - FactionTemplate = 0x58, // Size: 0x1, Flags: 0x1 - Level = 0x5C, // Size: 0x1, Flags: 0x1 - PercentHealth = 0x60, // Size: 0x1, Flags: 0x201 - SpellVisualId = 0x64, // Size: 0x1, Flags: 0x281 - StateSpellVisualId = 0x68, // Size: 0x1, Flags: 0x280 - StateAnimId = 0x6C, // Size: 0x1, Flags: 0x280 - StateAnimKitId = 0x70, // Size: 0x1, Flags: 0x280 - StateWorldEffectId = 0x74 // Size: 0x4, Flags: 0x280 - } - - internal enum ItemData - { - Owner = 0x30, // Size: 0x4, Flags: 0x1 - ContainedIn = 0x40, // Size: 0x4, Flags: 0x1 - Creator = 0x50, // Size: 0x4, Flags: 0x1 - GiftCreator = 0x60, // Size: 0x4, Flags: 0x1 - StackCount = 0x70, // Size: 0x1, Flags: 0x4 - Expiration = 0x74, // Size: 0x1, Flags: 0x4 - SpellCharges = 0x78, // Size: 0x5, Flags: 0x4 - DynamicFlags = 0x8C, // Size: 0x1, Flags: 0x1 - Enchantment = 0x90, // Size: 0x27, Flags: 0x1 - PropertySeed = 0x12C, // Size: 0x1, Flags: 0x1 - RandomPropertiesId = 0x130, // Size: 0x1, Flags: 0x1 - Durability = 0x134, // Size: 0x1, Flags: 0x4 - MaxDurability = 0x138, // Size: 0x1, Flags: 0x4 - CreatePlayedTime = 0x13C, // Size: 0x1, Flags: 0x1 - ModifiersMask = 0x140, // Size: 0x1, Flags: 0x4 - Context = 0x144, // Size: 0x1, Flags: 0x1 - ArtifactXp = 0x148, // Size: 0x1, Flags: 0x4 - ItemAppearanceModId = 0x14C // Size: 0x1, Flags: 0x4 - } - - internal enum KeyBinding - { - NumKeyBindings = 0x1700030, // -0x17C0 - First = 0xC8, - Next = 0xB8, - Key = 0x30, - Command = 0x58 - } - - internal enum ObjectData - { - Guid = 0x0, // Size: 0x4, Flags: 0x1 - Data = 0x10, // Size: 0x4, Flags: 0x1 - Type = 0x20, // Size: 0x1, Flags: 0x1 - EntryId = 0x24, // Size: 0x1, Flags: 0x80 - DynamicFlags = 0x28, // Size: 0x1, Flags: 0x280 - Scale = 0x2C // Size: 0x1, Flags: 0x1 - } - - internal enum PlayerData - { - DuelArbiter = 0x360, // Size: 0x4, Flags: 0x1 - WowAccount = 0x370, // Size: 0x4, Flags: 0x1 - LootTargetGuid = 0x380, // Size: 0x4, Flags: 0x1 - PlayerFlags = 0x390, // Size: 0x1, Flags: 0x1 - PlayerFlagsEx = 0x394, // Size: 0x1, Flags: 0x1 - GuildRankId = 0x398, // Size: 0x1, Flags: 0x1 - GuildDeleteDate = 0x39C, // Size: 0x1, Flags: 0x1 - GuildLevel = 0x3A0, // Size: 0x1, Flags: 0x1 - HairColorId = 0x3A4, // Size: 0x1, Flags: 0x1 - CustomDisplayOption = 0x3A8, // Size: 0x1, Flags: 0x1 - Inebriation = 0x3AC, // Size: 0x1, Flags: 0x1 - ArenaFaction = 0x3B0, // Size: 0x1, Flags: 0x1 - DuelTeam = 0x3B4, // Size: 0x1, Flags: 0x1 - GuildTimeStamp = 0x3B8, // Size: 0x1, Flags: 0x1 - QuestLog = 0x3BC, // Size: 0x320, Flags: 0x20 - VisibleItems = 0x103C, // Size: 0x26, Flags: 0x1 - PlayerTitle = 0x10D4, // Size: 0x1, Flags: 0x1 - FakeInebriation = 0x10D8, // Size: 0x1, Flags: 0x1 - VirtualPlayerRealm = 0x10DC, // Size: 0x1, Flags: 0x1 - CurrentSpecId = 0x10E0, // Size: 0x1, Flags: 0x1 - TaxiMountAnimKitId = 0x10E4, // Size: 0x1, Flags: 0x1 - AvgItemLevel = 0x10E8, // Size: 0x4, Flags: 0x1 - CurrentBattlePetBreedQuality = 0x10F8, // Size: 0x1, Flags: 0x1 - Prestige = 0x10FC, // Size: 0x1, Flags: 0x1 - HonorLevel = 0x1100, // Size: 0x1, Flags: 0x1 - InvSlots = 0x1104, // Size: 0x2EC, Flags: 0x2 - FarsightObject = 0x1CB4, // Size: 0x4, Flags: 0x2 - SummonedBattlePetGuid = 0x1CC4, // Size: 0x4, Flags: 0x2 - KnownTitles = 0x1CD4, // Size: 0xC, Flags: 0x2 - Coinage = 0x1D04, // Size: 0x2, Flags: 0x2 - Xp = 0x1D0C, // Size: 0x1, Flags: 0x2 - NextLevelXp = 0x1D10, // Size: 0x1, Flags: 0x2 - Skill = 0x1D14, // Size: 0x1C0, Flags: 0x2 - CharacterPoints = 0x2414, // Size: 0x1, Flags: 0x2 - MaxTalentTiers = 0x2418, // Size: 0x1, Flags: 0x2 - TrackCreatureMask = 0x241C, // Size: 0x1, Flags: 0x2 - TrackResourceMask = 0x2420, // Size: 0x1, Flags: 0x2 - MainhandExpertise = 0x2424, // Size: 0x1, Flags: 0x2 - OffhandExpertise = 0x2428, // Size: 0x1, Flags: 0x2 - RangedExpertise = 0x242C, // Size: 0x1, Flags: 0x2 - CombatRatingExpertise = 0x2430, // Size: 0x1, Flags: 0x2 - BlockPercentage = 0x2434, // Size: 0x1, Flags: 0x2 - DodgePercentage = 0x2438, // Size: 0x1, Flags: 0x2 - ParryPercentage = 0x243C, // Size: 0x1, Flags: 0x2 - CritPercentage = 0x2440, // Size: 0x1, Flags: 0x2 - RangedCritPercentage = 0x2444, // Size: 0x1, Flags: 0x2 - OffhandCritPercentage = 0x2448, // Size: 0x1, Flags: 0x2 - SpellCritPercentage = 0x244C, // Size: 0x1, Flags: 0x2 - ShieldBlock = 0x2450, // Size: 0x1, Flags: 0x2 - ShieldBlockCritPercentage = 0x2454, // Size: 0x1, Flags: 0x2 - Mastery = 0x2458, // Size: 0x1, Flags: 0x2 - Speed = 0x245C, // Size: 0x1, Flags: 0x2 - Lifesteal = 0x2460, // Size: 0x1, Flags: 0x2 - Avoidance = 0x2464, // Size: 0x1, Flags: 0x2 - Sturdiness = 0x2468, // Size: 0x1, Flags: 0x2 - Versatility = 0x246C, // Size: 0x1, Flags: 0x2 - VersatilityBonus = 0x2470, // Size: 0x1, Flags: 0x2 - PvpPowerDamage = 0x2474, // Size: 0x1, Flags: 0x2 - PvpPowerHealing = 0x2478, // Size: 0x1, Flags: 0x2 - ExploredZones = 0x247C, // Size: 0x100, Flags: 0x2 - RestInfo = 0x287C, // Size: 0x4, Flags: 0x2 - ModDamageDonePos = 0x288C, // Size: 0x7, Flags: 0x2 - ModDamageDoneNeg = 0x28A8, // Size: 0x7, Flags: 0x2 - ModDamageDonePercent = 0x28C4, // Size: 0x7, Flags: 0x2 - ModHealingDonePos = 0x28E0, // Size: 0x1, Flags: 0x2 - ModHealingPercent = 0x28E4, // Size: 0x1, Flags: 0x2 - ModHealingDonePercent = 0x28E8, // Size: 0x1, Flags: 0x2 - ModPeriodicHealingDonePercent = 0x28EC, // Size: 0x1, Flags: 0x2 - WeaponDmgMultipliers = 0x28F0, // Size: 0x3, Flags: 0x2 - WeaponAtkSpeedMultipliers = 0x28FC, // Size: 0x3, Flags: 0x2 - ModSpellPowerPercent = 0x2908, // Size: 0x1, Flags: 0x2 - ModResiliencePercent = 0x290C, // Size: 0x1, Flags: 0x2 - OverrideSpellPowerByApPercent = 0x2910, // Size: 0x1, Flags: 0x2 - OverrideApBySpellPowerPercent = 0x2914, // Size: 0x1, Flags: 0x2 - ModTargetResistance = 0x2918, // Size: 0x1, Flags: 0x2 - ModTargetPhysicalResistance = 0x291C, // Size: 0x1, Flags: 0x2 - LocalFlags = 0x2920, // Size: 0x1, Flags: 0x2 - NumRespecs = 0x2924, // Size: 0x1, Flags: 0x2 - SelfResSpell = 0x2928, // Size: 0x1, Flags: 0x2 - PvpMedals = 0x292C, // Size: 0x1, Flags: 0x2 - BuybackPrice = 0x2930, // Size: 0xC, Flags: 0x2 - BuybackTimestamp = 0x2960, // Size: 0xC, Flags: 0x2 - YesterdayHonorableKills = 0x2990, // Size: 0x1, Flags: 0x2 - LifetimeHonorableKills = 0x2994, // Size: 0x1, Flags: 0x2 - WatchedFactionIndex = 0x2998, // Size: 0x1, Flags: 0x2 - CombatRatings = 0x299C, // Size: 0x20, Flags: 0x2 - PvpInfo = 0x2A1C, // Size: 0x24, Flags: 0x2 - MaxLevel = 0x2AAC, // Size: 0x1, Flags: 0x2 - ScalingPlayerLevelDelta = 0x2AB0, // Size: 0x1, Flags: 0x2 - MaxCreatureScalingLevel = 0x2AB4, // Size: 0x1, Flags: 0x2 - NoReagentCostMask = 0x2AB8, // Size: 0x4, Flags: 0x2 - PetSpellPower = 0x2AC8, // Size: 0x1, Flags: 0x2 - Researching = 0x2ACC, // Size: 0xA, Flags: 0x2 - ProfessionSkillLine = 0x2AF4, // Size: 0x2, Flags: 0x2 - UiHitModifier = 0x2AFC, // Size: 0x1, Flags: 0x2 - UiSpellHitModifier = 0x2B00, // Size: 0x1, Flags: 0x2 - HomeRealmTimeOffset = 0x2B04, // Size: 0x1, Flags: 0x2 - ModPetHaste = 0x2B08, // Size: 0x1, Flags: 0x2 - OverrideSpellsId = 0x2B0C, // Size: 0x1, Flags: 0x402 - LfgBonusFactionId = 0x2B10, // Size: 0x1, Flags: 0x2 - LootSpecId = 0x2B14, // Size: 0x1, Flags: 0x2 - OverrideZonePvpType = 0x2B18, // Size: 0x1, Flags: 0x402 - BagSlotFlags = 0x2B1C, // Size: 0x4, Flags: 0x2 - BankBagSlotFlags = 0x2B2C, // Size: 0x7, Flags: 0x2 - InsertItemsLeftToRight = 0x2B48, // Size: 0x1, Flags: 0x2 - QuestCompleted = 0x2B4C, // Size: 0x36B, Flags: 0x2 - Honor = 0x38F8, // Size: 0x1, Flags: 0x2 - HonorNextLevel = 0x38FC // Size: 0x1, Flags: 0x2 - } - - internal enum SceneObjectData - { - ScriptPackageId = 0x30, // Size: 0x1, Flags: 0x1 - RndSeedVal = 0x34, // Size: 0x1, Flags: 0x1 - CreatedBy = 0x38, // Size: 0x4, Flags: 0x1 - SceneType = 0x48 // Size: 0x1, Flags: 0x1 - } - - internal enum Unit - { - CurrentCastId = 0x1B98, - CurrentChanneledId = 0x1BB8, - AuraTable = 0x1D10, - AuraCount = 0x2390, - AuraSize = 0x68, - ClientRace = 0x2670, - DisplayData = 0x1718 - } - - // Note: Invalid possibly! - internal enum UnitAuras : uint - { - AuraCount1 = 0x2390, - AuraCount2 = 0x1D10, - AuraTable1 = 0x1D14, - AuraTable2 = 0x1D18, - AuraSize = 0x68, - - OwnerGuid = 0x40, - AuraSpellId = 0x50, - //AuraFlags = 0x54, //Not exactly sure here. - //AuraLevel = 0x58, //Not exactly sure here. - AuraStack = 0x59, - TimeLeft = 0x60, - //In case I need it: - DruidEclipse = 0x2694 - } - - // Below is all of the World of Warcraft in-game object field offsets. - // Commenting is not used on purpose and enums below should remain internal. - internal enum UnitData - { - Charm = 0x30, // Size: 0x4, Flags: 0x1 - Summon = 0x40, // Size: 0x4, Flags: 0x1 - Critter = 0x50, // Size: 0x4, Flags: 0x2 - CharmedBy = 0x60, // Size: 0x4, Flags: 0x1 - SummonedBy = 0x70, // Size: 0x4, Flags: 0x1 - CreatedBy = 0x80, // Size: 0x4, Flags: 0x1 - DemonCreator = 0x90, // Size: 0x4, Flags: 0x1 - Target = 0xA0, // Size: 0x4, Flags: 0x1 - BattlePetCompanionGuid = 0xB0, // Size: 0x4, Flags: 0x1 - BattlePetDbid = 0xC0, // Size: 0x2, Flags: 0x1 - ChannelObject = 0xC8, // Size: 0x4, Flags: 0x201 - ChannelSpell = 0xD8, // Size: 0x1, Flags: 0x201 - ChannelSpellXSpellVisual = 0xDC, // Size: 0x1, Flags: 0x201 - SummonedByHomeRealm = 0xE0, // Size: 0x1, Flags: 0x1 - Sex = 0xE4, // Size: 0x1, Flags: 0x1 - DisplayPower = 0xE8, // Size: 0x1, Flags: 0x1 - OverrideDisplayPowerId = 0xEC, // Size: 0x1, Flags: 0x1 - Health = 0xF0, // Size: 0x2, Flags: 0x1 - Power = 0xF8, // Size: 0x6, Flags: 0x401 - TertiaryPower = 0xFC, - SecondaryPower = 0x100, - MaxHealth = 0x110, // Size: 0x2, Flags: 0x1 - MaxPower = 0x118, // Size: 0x6, Flags: 0x1 - PowerRegenFlatModifier = 0x130, // Size: 0x6, Flags: 0x46 - PowerRegenInterruptedFlatModifier = 0x148, // Size: 0x6, Flags: 0x46 - Level = 0x160, // Size: 0x1, Flags: 0x1 - EffectiveLevel = 0x164, // Size: 0x1, Flags: 0x1 - ScalingLevelMin = 0x168, // Size: 0x1, Flags: 0x1 - ScalingLevelMax = 0x16C, // Size: 0x1, Flags: 0x1 - ScalingLevelDelta = 0x170, // Size: 0x1, Flags: 0x1 - FactionTemplate = 0x174, // Size: 0x1, Flags: 0x1 - VirtualItems = 0x178, // Size: 0x6, Flags: 0x1 - Flags = 0x190, // Size: 0x1, Flags: 0x201 - Flags2 = 0x194, // Size: 0x1, Flags: 0x201 - Flags3 = 0x198, // Size: 0x1, Flags: 0x201 - AuraState = 0x19C, // Size: 0x1, Flags: 0x1 - AttackRoundBaseTime = 0x1A0, // Size: 0x2, Flags: 0x1 - RangedAttackRoundBaseTime = 0x1A8, // Size: 0x1, Flags: 0x2 - BoundingRadius = 0x1AC, // Size: 0x1, Flags: 0x1 - CombatReach = 0x1B0, // Size: 0x1, Flags: 0x1 - DisplayId = 0x1B4, // Size: 0x1, Flags: 0x280 - NativeDisplayId = 0x1B8, // Size: 0x1, Flags: 0x201 - MountDisplayId = 0x1BC, // Size: 0x1, Flags: 0x201 - MinDamage = 0x1C0, // Size: 0x1, Flags: 0x16 - MaxDamage = 0x1C4, // Size: 0x1, Flags: 0x16 - MinOffHandDamage = 0x1C8, // Size: 0x1, Flags: 0x16 - MaxOffHandDamage = 0x1CC, // Size: 0x1, Flags: 0x16 - AnimTier = 0x1D0, // Size: 0x1, Flags: 0x1 - PetNumber = 0x1D4, // Size: 0x1, Flags: 0x1 - PetNameTimestamp = 0x1D8, // Size: 0x1, Flags: 0x1 - PetExperience = 0x1DC, // Size: 0x1, Flags: 0x4 - PetNextLevelExperience = 0x1E0, // Size: 0x1, Flags: 0x4 - ModCastingSpeed = 0x1E4, // Size: 0x1, Flags: 0x1 - ModSpellHaste = 0x1E8, // Size: 0x1, Flags: 0x1 - ModHaste = 0x1EC, // Size: 0x1, Flags: 0x1 - ModRangedHaste = 0x1F0, // Size: 0x1, Flags: 0x1 - ModHasteRegen = 0x1F4, // Size: 0x1, Flags: 0x1 - ModTimeRate = 0x1F8, // Size: 0x1, Flags: 0x1 - CreatedBySpell = 0x1FC, // Size: 0x1, Flags: 0x1 - NpcFlags = 0x200, // Size: 0x2, Flags: 0x81 - EmoteState = 0x208, // Size: 0x1, Flags: 0x1 - Stats = 0x20C, // Size: 0x4, Flags: 0x6 - StatPosBuff = 0x21C, // Size: 0x4, Flags: 0x6 - StatNegBuff = 0x22C, // Size: 0x4, Flags: 0x6 - Resistances = 0x23C, // Size: 0x7, Flags: 0x16 - ResistanceBuffModsPositive = 0x258, // Size: 0x7, Flags: 0x6 - ResistanceBuffModsNegative = 0x274, // Size: 0x7, Flags: 0x6 - ModBonusArmor = 0x290, // Size: 0x1, Flags: 0x6 - BaseMana = 0x294, // Size: 0x1, Flags: 0x1 - BaseHealth = 0x298, // Size: 0x1, Flags: 0x6 - ShapeshiftForm = 0x29C, // Size: 0x1, Flags: 0x1 - AttackPower = 0x2A0, // Size: 0x1, Flags: 0x6 - AttackPowerModPos = 0x2A4, // Size: 0x1, Flags: 0x6 - AttackPowerModNeg = 0x2A8, // Size: 0x1, Flags: 0x6 - AttackPowerMultiplier = 0x2AC, // Size: 0x1, Flags: 0x6 - RangedAttackPower = 0x2B0, // Size: 0x1, Flags: 0x6 - RangedAttackPowerModPos = 0x2B4, // Size: 0x1, Flags: 0x6 - RangedAttackPowerModNeg = 0x2B8, // Size: 0x1, Flags: 0x6 - RangedAttackPowerMultiplier = 0x2BC, // Size: 0x1, Flags: 0x6 - SetAttackSpeedAura = 0x2C0, // Size: 0x1, Flags: 0x6 - MinRangedDamage = 0x2C4, // Size: 0x1, Flags: 0x6 - MaxRangedDamage = 0x2C8, // Size: 0x1, Flags: 0x6 - PowerCostModifier = 0x2CC, // Size: 0x7, Flags: 0x6 - PowerCostMultiplier = 0x2E8, // Size: 0x7, Flags: 0x6 - MaxHealthModifier = 0x304, // Size: 0x1, Flags: 0x6 - HoverHeight = 0x308, // Size: 0x1, Flags: 0x1 - MinItemLevelCutoff = 0x30C, // Size: 0x1, Flags: 0x1 - MinItemLevel = 0x310, // Size: 0x1, Flags: 0x1 - MaxItemLevel = 0x314, // Size: 0x1, Flags: 0x1 - WildBattlePetLevel = 0x318, // Size: 0x1, Flags: 0x1 - BattlePetCompanionNameTimestamp = 0x31C, // Size: 0x1, Flags: 0x1 - InteractSpellId = 0x320, // Size: 0x1, Flags: 0x1 - StateSpellVisualId = 0x324, // Size: 0x1, Flags: 0x280 - StateAnimId = 0x328, // Size: 0x1, Flags: 0x280 - StateAnimKitId = 0x32C, // Size: 0x1, Flags: 0x280 - StateWorldEffectId = 0x330, // Size: 0x4, Flags: 0x280 - ScaleDuration = 0x340, // Size: 0x1, Flags: 0x1 - LooksLikeMountId = 0x344, // Size: 0x1, Flags: 0x1 - LooksLikeCreatureId = 0x348, // Size: 0x1, Flags: 0x1 - LookAtControllerId = 0x34C, // Size: 0x1, Flags: 0x1 - LookAtControllerTarget = 0x350 // Size: 0x4, Flags: 0x1 - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/WoWPlayer.cs b/Artemis/Artemis/Modules/Games/WoW/Data/WoWPlayer.cs deleted file mode 100644 index b7eba53bb..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/WoWPlayer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Linq; -using Process.NET; - -namespace Artemis.Modules.Games.WoW.Data -{ - public class WoWPlayer : WoWUnit - { - private readonly IntPtr _targetIntPtr; - - public WoWPlayer(IProcess process, IntPtr baseAddress, IntPtr targetIntPtr, bool readPointer = false) - : base(process, baseAddress, readPointer) - { - _targetIntPtr = targetIntPtr; - } - - public WoWObject GetTarget(WoWObjectManager manager) - { - var targetGuid = Process.Memory.Read(Process.Native.MainModule.BaseAddress + _targetIntPtr.ToInt32()); - return manager.EnumVisibleObjects().FirstOrDefault(o => o.Guid == targetGuid); - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/WoWStructs.cs b/Artemis/Artemis/Modules/Games/WoW/Data/WoWStructs.cs deleted file mode 100644 index b16179265..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/WoWStructs.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Artemis.Modules.Games.WoW.Data.WowSharp.Client.Patchables; - -namespace Artemis.Modules.Games.WoW.Data -{ - public static class WoWStructs - { - [StructLayout(LayoutKind.Explicit)] - public struct ObjectData - { - // x32 : x64 - [FieldOffset(0)] private readonly IntPtr vtable; // 0x00 0x00 - [FieldOffset(10)] public IntPtr Descriptors; // 0x04 0x10 - [FieldOffset(18)] private readonly IntPtr unk1; // 0x08 0x18 - [FieldOffset(20)] public int ObjectType; // 0x0C 0x20 - [FieldOffset(24)] private readonly IntPtr unk3; // 0x10 0x24 - [FieldOffset(28)] private readonly IntPtr unk4; // 0x14 0x28 - [FieldOffset(30)] private readonly IntPtr unk5; // 0x18 0x30 - [FieldOffset(38)] private readonly IntPtr unk6; // 0x1C 0x38 - [FieldOffset(40)] private readonly IntPtr unk7; // 0x20 0x40 - [FieldOffset(48)] private readonly IntPtr unk8; // 0x24 0x48 - [FieldOffset(50)] public Guid Guid; // 0x28 0x50 - } - - public struct Guid - { - private readonly Int128 _mGuid; - - public static readonly Guid Zero = new Guid(0); - - public Guid(Int128 guid) - { - _mGuid = guid; - } - - public override string ToString() - { - // ReSharper disable once SwitchStatementMissingSomeCases - switch (Type) - { - case WoWEnums.GuidType.Creature: - case WoWEnums.GuidType.Vehicle: - case WoWEnums.GuidType.Pet: - case WoWEnums.GuidType.GameObject: - case WoWEnums.GuidType.AreaTrigger: - case WoWEnums.GuidType.DynamicObject: - case WoWEnums.GuidType.Corpse: - case WoWEnums.GuidType.LootObject: - case WoWEnums.GuidType.SceneObject: - case WoWEnums.GuidType.Scenario: - case WoWEnums.GuidType.AiGroup: - case WoWEnums.GuidType.DynamicDoor: - case WoWEnums.GuidType.Vignette: - case WoWEnums.GuidType.Conversation: - case WoWEnums.GuidType.CallForHelp: - case WoWEnums.GuidType.AiResource: - case WoWEnums.GuidType.AiLock: - case WoWEnums.GuidType.AiLockTicket: - return $"{Type}-{SubType}-{RealmId}-{MapId}-{ServerId}-{Id}-{CreationBits:X10}"; - case WoWEnums.GuidType.Player: - return $"{Type}-{RealmId}-{(ulong) (_mGuid >> 64):X8}"; - case WoWEnums.GuidType.Item: - return $"{Type}-{RealmId}-{(uint) ((_mGuid >> 18) & 0xFFFFFF)}-{(ulong) (_mGuid >> 64):X10}"; - //case GuidType.ClientActor: - // return String.Format("{0}-{1}-{2}", Type, RealmId, CreationBits); - //case GuidType.Transport: - //case GuidType.StaticDoor: - // return String.Format("{0}-{1}-{2}", Type, RealmId, CreationBits); - default: - return $"{Type}-{_mGuid:X32}"; - } - } - - public override bool Equals(object obj) - { - if (obj is Guid) - return _mGuid == ((Guid) obj)._mGuid; - return false; - } - - public override int GetHashCode() - { - return _mGuid.GetHashCode(); - } - - public WoWEnums.GuidType Type => (WoWEnums.GuidType) (byte) ((_mGuid >> 58) & 0x3F); - - public byte SubType => (byte) ((_mGuid >> 120) & 0x3F); - - public ushort RealmId => (ushort) ((_mGuid >> 42) & 0x1FFF); - - public ushort ServerId => (ushort) ((_mGuid >> 104) & 0x1FFF); - - public ushort MapId => (ushort) ((_mGuid >> 29) & 0x1FFF); - - // Creature, Pet, Vehicle - public uint Id => (uint) ((_mGuid >> 6) & 0x7FFFFF); - - public ulong CreationBits => (ulong) ((_mGuid >> 64) & 0xFFFFFFFFFF); - } - - #region Manager - - // Region is here due to the large amount of structs- - // the CurremtManager struct depends on. - [StructLayout(LayoutKind.Sequential)] - public struct CurrentManager - { - public TsHashTable VisibleObjects; // m_objects - public TsHashTable LazyCleanupObjects; // m_lazyCleanupObjects - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] - // m_lazyCleanupFifo, m_freeObjects, m_visibleObjects, m_reenabledObjects, whateverObjects... - public TsExplicitList[] Links; // Links[13] has all objects stored in VisibleObjects it seems - -#if !X64 - public int Unknown1; // wtf is that and why x86 only? -#endif - public Int128 ActivePlayer; - public int MapId; - public IntPtr ClientConnection; - public IntPtr MovementGlobals; - public int Unk1; - } - - [StructLayout(LayoutKind.Sequential)] - public struct Ts - { - public IntPtr vtable; - public uint m_alloc; - public uint m_count; - public IntPtr m_data; //TSExplicitList* m_data; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TsExplicitList - { - public TsList baseClass; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TsFixedArray - { - public Ts baseClass; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TsGrowableArray - { - public TsFixedArray baseclass; - public uint m_chunk; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TsHashTable - { - public IntPtr vtable; - public TsExplicitList m_fulllist; - public int m_fullnessIndicator; - public TsGrowableArray m_slotlistarray; - public int m_slotmask; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TsLink - { - public IntPtr m_prevlink; //TSLink *m_prevlink - public IntPtr m_next; // C_OBJECTHASH *m_next - } - - [StructLayout(LayoutKind.Sequential)] - public struct TsList - { - public int m_linkoffset; - public TsLink m_terminator; - } - - #endregion; - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Data/WoWUnit.cs b/Artemis/Artemis/Modules/Games/WoW/Data/WoWUnit.cs deleted file mode 100644 index c0c0f45bd..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/Data/WoWUnit.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Process.NET; -using static Artemis.Modules.Games.WoW.Data.WoWEnums; -using static Artemis.Modules.Games.WoW.Data.WoWOffsets; - -namespace Artemis.Modules.Games.WoW.Data -{ - public class WoWUnit : WoWObject - { - public WoWUnit(IProcess process, IntPtr baseAddress, bool readPointer = false) - : base(process, baseAddress, readPointer) - { - } - - public int Health => ReadField(UnitData.Health); - public int MaxHealth => ReadField(UnitData.MaxHealth); - public int Power => ReadField(UnitData.Power); - public int SecondaryPower => ReadField(UnitData.SecondaryPower); - public int TertiaryPower => ReadField(UnitData.TertiaryPower); - public int MaxPower => ReadField(UnitData.MaxPower); - public PowerType PowerType => (PowerType) ReadField(UnitData.DisplayPower); - public int Level => ReadField(UnitData.Level); - - public WoWDetails Details { get; set; } - - public void UpdateDetails(WoWNameCache nameCache) - { - Details = GetNpcDetails() ?? nameCache.GetNameByGuid(Guid); - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWAddresses.cs b/Artemis/Artemis/Modules/Games/WoW/WoWAddresses.cs deleted file mode 100644 index 487eeab04..000000000 --- a/Artemis/Artemis/Modules/Games/WoW/WoWAddresses.cs +++ /dev/null @@ -1,256 +0,0 @@ -namespace Artemis.Modules.Games.WoW -{ - public static class WoWAddresses - { - public enum ActivateSettings - { - Activate_Offset = 0x34, - AutoDismount_Activate_Pointer = 0xe56850, - AutoInteract_Activate_Pointer = 0xe56848, - AutoLoot_Activate_Pointer = 0xe56868, - AutoSelfCast_Activate_Pointer = 0xe56874 - } - - public enum Battleground - { - MaxBattlegroundId = 0xec3fdc, - PvpExitWindow = 0xec4198, - StatPvp = 0xc3c03c - } - - public enum Chat - { - chatBufferPos = 0xeb1bf0, - chatBufferStart = 0xe58190, - msgFormatedChat = 0x65, - NextMessage = 0x17e8 - } - - public enum ClickToMove - { - CTM = 0xddf8f0, - CTM_PUSH = 0xddf8ac, - CTM_X = 0xddf918, - CTM_Y = 0xddf91c, - CTM_Z = 0xddf920 - } - - public enum CorpsePlayer - { - X = 0xe57894, - Y = 0xe57898, - Z = 0xe5789c - } - - public enum DBC - { - FactionTemplate = 0, - ItemClass = 0xd173c0, - ItemSubClass = 0, - Lock = 0, - Map = 0xd291a0, - QuestPOIPoint = 0xd1e950, - ResearchSite = 0xd1d2d0, - SpellCategories = 0, - Unknown = 0xf35428 - } - - public enum EventsListener - { - BaseEvents = 0xcb2474, - EventOffsetCount = 0x48, - EventOffsetName = 0x18, - EventsCount = 0xcb2470 - } - - public enum Fishing - { - BobberHasMoved = 0xf8 - } - - public enum FunctionWow - { - CGUnit_C__InitializeTrackingState = 0x30623b, - CGUnit_C__Interact = 0x524ff, - CGWorldFrame__Intersect = 0x5e46ab, - ClntObjMgrGetActivePlayerObj = 0x816d7, - FrameScript__GetLocalizedText = 0x300b48, - FrameScript_ExecuteBuffer = 0xa6772, - IsOutdoors = 0, - Spell_C_HandleTerrainClick = 0x2b76ff, - strlen = 0x74fcb0, - UnitCanAttack = 0, - WowClientDB2__GetRowPointer = 0x20c775 - } - - public enum GameInfo - { - AreaId = 0xc32c2c, - buildWoWVersionString = 0xd002a8, - gameState = 0xe56a49, - GetTime = 0xcb2150, - isLoading = 0xca59b0, - LastHardwareAction = 0xd0e090, - MapTextureId = 0xc3bd28, - SubAreaId = 0xc32c24, - subZoneMap = 0xe56a68, - TextBoxActivated = 0xbbe9ac, - zoneMap = 0xe56a64 - } - - public enum GameObject - { - CachedCastBarCaption = 12, - CachedData0 = 20, - CachedIconName = 8, - CachedName = 180, - CachedQuestItem1 = 0x9c, - CachedSize = 0x98, - DBCacheRow = 620, - GAMEOBJECT_FIELD_X = 0x138, - GAMEOBJECT_FIELD_Y = 0x13c, - GAMEOBJECT_FIELD_Z = 320, - PackedRotationQuaternion = 0x150, - TransformationMatrice = 0x278 - } - - public enum Hooking - { - DX_DEVICE = 0xcc523c, - DX_DEVICE_IDX = 0x2508, - ENDSCENE_IDX = 0xa8 - } - - public enum Login - { - realmName = 0xf35e16 - } - - public enum MovementFlagsOffsets - { - Offset1 = 0x124, - Offset2 = 0x40 - } - - public enum ObjectManager - { - continentId = 0x108, - firstObject = 0xd8, - localGuid = 0xf8, - nextObject = 0x44, - objectGUID = 0x30, - objectTYPE = 0x10 - } - - public enum Party - { - NumOfPlayers = 200, - NumOfPlayersSuBGroup = 0xcc, - PartyOffset = 0xeb5458, - PlayerGuid = 0x10 - } - - public enum PetBattle - { - IsInBattle = 0xba8a10 - } - - public enum Player - { - LocalPlayerSpellsOnCooldown = 0xd372b8, - petGUID = 0xec7158, - playerName = 0xf35e20, - RetrieveCorpseWindow = 0xe576f4, - RuneStartCooldown = 0xf18aa8, - SkillMaxValue = 0x400, - SkillValue = 0x200 - } - - public enum PlayerNameStore - { - PlayerNameNextOffset = 20, - PlayerNameStorePtr = 0xd0b4e0, - PlayerNameStringOffset = 0x11 - } - - public enum PowerIndex - { - Multiplicator = 0x10, - PowerIndexArrays = 0xddf914 - } - - public enum Quests - { - QuestGiverStatus = 0xf4 - } - - public enum SpellBook - { - FirstTalentBookPtr = 0xeb52ec, - KnownAllSpells = 0xeb5130, - MountBookMountsPtr = 0xeb5194, - MountBookNumMounts = 0xeb5190, - NextTalentBookPtr = 0xeb52e4, - SpellBookNumSpells = 0xeb5134, - SpellBookSpellsPtr = 0xeb5138, - SpellDBCMaxIndex = 0x30d40, - TalentBookOverrideSpellId = 0x1c, - TalentBookSpellId = 20 - } - - public enum UnitBaseGetUnitAura - { - AuraSize = 0x58, - AuraStructCasterLevel = 0x3a, - AuraStructCount = 0x39, - AuraStructCreatorGuid = 0x20, - AuraStructDuration = 60, - AuraStructFlag = 0x34, - AuraStructMask = 0x35, - AuraStructSpellEndTime = 0x40, - AuraStructSpellId = 0x30, - AuraStructUnk1 = 0x3b, - AuraStructUnk2 = 0x44, - AuraTable1 = 0x1150, - AuraTable2 = 0x580 - } - - public enum UnitField - { - CachedIsBoss = 0x60, - CachedModelId1 = 0x6c, - CachedName = 0x80, - CachedQuestItem1 = 60, - CachedSubName = 0, - CachedTypeFlag = 0x24, - CachedUnitClassification = 0x2c, - CanInterrupt = 0xfc4, - CanInterruptOffset = 0xe02ea0, - CanInterruptOffset2 = 0xe02ea4, - CanInterruptOffset3 = 0xe02ea8, - CastingSpellEndTime = 0x108c, - CastingSpellID = 0x1064, - CastingSpellStartTime = 0x1088, - ChannelSpellEndTime = 0x1098, - ChannelSpellID = 0x1090, - ChannelSpellStartTime = 0x1094, - DBCacheRow = 0xc80, - TransportGUID = 0xae8, - UNIT_FIELD_R = 0xb08, - UNIT_FIELD_X = 0xaf8, - UNIT_FIELD_Y = 0xafc, - UNIT_FIELD_Z = 0xb00 - } - - public enum VMT - { - CGUnit_C__GetFacing = 0x35 - } - - public class ObjectManagerClass - { - public static uint clientConnection; - public static uint sCurMgr; - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs index 3edf73ecc..e64e8b39f 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs @@ -1,11 +1,159 @@ -using Artemis.Modules.Abstract; -using Artemis.Modules.Games.WoW.Data; +using System; +using System.Collections.Generic; +using Artemis.Modules.Abstract; +using Castle.Components.DictionaryAdapter; +using Newtonsoft.Json.Linq; namespace Artemis.Modules.Games.WoW { public class WoWDataModel : ModuleDataModel { + public WoWDataModel() + { + Player = new WoWUnit(); + Target = new WoWUnit(); + } + public WoWUnit Player { get; set; } public WoWUnit Target { get; set; } + public string Realm { get; set; } + public string Zone { get; set; } + public string SubZone { get; set; } } -} \ No newline at end of file + + public class WoWUnit + { + public WoWUnit() + { + Buffs = new List(); + Debuffs = new EditableList(); + } + + public string Name { get; set; } + public int Level { get; set; } + public int Health { get; set; } + public int MaxHealth { get; set; } + public int Power { get; set; } + public int MaxPower { get; set; } + public WoWPowerType PowerType { get; set; } + public WoWClass Class { get; set; } + public WoWRace Race { get; set; } + public WoWGender Gender { get; set; } + public List Buffs { get; set; } + public List Debuffs { get; set; } + + public void ApplyJson(JObject json) + { + if (json["name"] == null) + return; + + Name = json["name"].Value(); + Level = json["level"].Value(); + Class = (WoWClass) Enum.Parse(typeof(WoWClass), json["class"].Value().Replace(" ", "")); + Race = (WoWRace) Enum.Parse(typeof(WoWRace), json["race"].Value().Replace(" ", "")); + Gender = json["gender"].Value() == 3 ? WoWGender.Female : WoWGender.Male; + } + + public void ApplyStateJson(JObject json) + { + Health = json["health"].Value(); + MaxHealth = json["maxHealth"].Value(); + PowerType = (WoWPowerType) Enum.Parse(typeof(WoWPowerType), json["powerType"].Value().ToString(), true); + Power = json["power"].Value(); + MaxPower = json["maxPower"].Value(); + + Buffs.Clear(); + foreach (var auraJson in json["buffs"].Children()) + { + var aura = new WoWAura(); + aura.ApplyJson(auraJson); + Buffs.Add(aura); + } + Debuffs.Clear(); + foreach (var auraJson in json["debuffs"].Children()) + { + var aura = new WoWAura(); + aura.ApplyJson(auraJson); + Debuffs.Add(aura); + } + } + } + + public class WoWAura + { + public string Name { get; set; } + public int Id { get; set; } + public string Caster { get; set; } + public int Stacks { get; set; } + public TimeSpan Duration { get; set; } + public TimeSpan Expires { get; set; } + + public void ApplyJson(JToken buffJson) + { + Name = buffJson["name"].Value(); + Id = buffJson["spellID"].Value(); + Caster = buffJson["caster"].Value(); + Stacks = buffJson["count"].Value(); + + // TODO: Duration + } + } + + public enum WoWPowerType + { + Mana = 0, + Rage = 1, + Focus = 2, + Energy = 3, + ComboPoints = 4, + Runes = 5, + RunicPower = 6, + SoulShards = 7, + LunarPower = 8, + HolyPower = 9, + AlternatePower = 10, + Maelstrom = 11, + Chi = 12, + Insanity = 13, + ArcaneCharges = 16 + } + + public enum WoWClass + { + Warrior, + Paladin, + Hunter, + Rogue, + Priest, + DeathKnight, + Shaman, + Mage, + Warlock, + Druid, + Monk, + DemonHunter + } + + public enum WoWRace + { + Human, + Orc, + Dwarf, + NightElf, + Undead, + Tauren, + Gnome, + Troll, + BloodElf, + Draenei, + Goblin, + Worgen, + Pandaren + } + + public enum WoWGender + { + Male, + Female + } +} diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs index c50926dfa..3c31d23a0 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs @@ -1,21 +1,22 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; using Artemis.DAL; using Artemis.Managers; using Artemis.Modules.Abstract; -using Artemis.Modules.Games.WoW.Data; -using Artemis.Settings; -using Artemis.Utilities.Memory; -using Process.NET; -using Process.NET.Memory; +using Newtonsoft.Json.Linq; +using PcapDotNet.Core; +using PcapDotNet.Packets; namespace Artemis.Modules.Games.WoW { public class WoWModel : ModuleModel { - private readonly GamePointersCollection _pointer; - private ProcessSharp _process; - + private readonly Regex _rgx; + private PacketCommunicator _communicator; public WoWModel(DeviceManager deviceManager, LuaManager luaManager) : base(deviceManager, luaManager) { @@ -23,94 +24,119 @@ namespace Artemis.Modules.Games.WoW DataModel = new WoWDataModel(); ProcessNames.Add("Wow-64"); - // Currently WoW is locked behind a hidden trigger (obviously not that hidden since you're reading this) - // It is using memory reading and lets first try to contact Blizzard - var settings = SettingsProvider.Load(); - Settings.IsEnabled = settings.GamestatePort == 62575 && Settings.IsEnabled; - - _pointer = SettingsProvider.Load().WorldOfWarcraft; - //_pointer = new GamePointersCollection - //{ - // Game = "WorldOfWarcraft", - // GameVersion = "7.0.3.22810", - // GameAddresses = new List - // { - // new GamePointer - // { - // Description = "ObjectManager", - // BasePointer = new IntPtr(0x1578070) - // }, - // new GamePointer - // { - // Description = "LocalPlayer", - // BasePointer = new IntPtr(0x169DF10) - // }, - // new GamePointer - // { - // Description = "NameCache", - // BasePointer = new IntPtr(0x151DCE8) - // }, - // new GamePointer - // { - // Description = "TargetGuid", - // BasePointer = new IntPtr(0x179C940) - // } - // } - //}; - //var res = JsonConvert.SerializeObject(_pointer, Formatting.Indented); + _rgx = new Regex("(artemis)\\((.*?)\\)", RegexOptions.Compiled); } public override string Name => "WoW"; public override bool IsOverlay => false; public override bool IsBoundToProcess => true; + + public override void Enable() + { + // Start scanning WoW packets + // Retrieve the device list from the local machine + IList allDevices = LivePacketDevice.AllLocalMachine; + + if (allDevices.Count == 0) + { + Logger.Warn("No interfaces found! Can't scan WoW packets."); + return; + } + + // Take the selected adapter + PacketDevice selectedDevice = allDevices.First(); + + // Open the device + _communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 100); + Logger.Debug("Listening on " + selectedDevice.Description + " for WoW packets"); + + // Compile the filter + using (var filter = _communicator.CreateFilter("tcp")) + { + // Set the filter + _communicator.SetFilter(filter); + } + + Task.Run(() => ReceivePackets()); + base.Enable(); + } + + private void ReceivePackets() + { + // start the capture + try + { + _communicator.ReceivePackets(0, PacketHandler); + } + catch (InvalidOperationException) + { + // ignored, happens on shutdown + } + } + + private void PacketHandler(Packet packet) + { + // Ignore duplicates + if (packet.Ethernet.IpV4.Udp.SourcePort == 3724) + return; + + var str = Encoding.Default.GetString(packet.Buffer); + if (str.ToLower().Contains("artemis")) + { + var match = _rgx.Match(str); + if (match.Groups.Count != 3) + return; + + Logger.Debug("[{0}] {1}", packet.Ethernet.IpV4.Udp.SourcePort, match.Groups[2].Value); + // Get the command and argument + var parts = match.Groups[2].Value.Split('|'); + HandleGameData(parts[0], parts[1]); + } + } + + private void HandleGameData(string command, string data) + { + var json = JObject.Parse(data); + var dataModel = (WoWDataModel) DataModel; + switch (command) + { + case "player": + ParsePlayer(json, dataModel); + break; + case "target": + ParseTarget(json, dataModel); + break; + case "playerState": + ParsePlayerState(json, dataModel); + break; + } + } + + private void ParsePlayer(JObject json, WoWDataModel dataModel) + { + dataModel.Player.ApplyJson(json); + } + + private void ParseTarget(JObject json, WoWDataModel dataModel) + { + dataModel.Target.ApplyJson(json); + } + + private void ParsePlayerState(JObject json, WoWDataModel dataModel) + { + dataModel.Player.ApplyStateJson(json); + } + public override void Dispose() { + _communicator.Break(); + _communicator.Dispose(); base.Dispose(); - - _process?.Dispose(); - _process = null; } public override void Update() { - if (_process == null) - { - var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessNames[0]); - if (tempProcess == null) - return; - - _process = new ProcessSharp(tempProcess, MemoryType.Remote); - } - - if (ProfileModel == null || DataModel == null || _process == null) - return; - - var dataModel = (WoWDataModel) DataModel; - - var objectManager = new WoWObjectManager(_process, - _pointer.GameAddresses.First(a => a.Description == "ObjectManager").BasePointer); - var nameCache = new WoWNameCache(_process, - _pointer.GameAddresses.First(a => a.Description == "NameCache").BasePointer); - var player = new WoWPlayer(_process, - _pointer.GameAddresses.First(a => a.Description == "LocalPlayer").BasePointer, - _pointer.GameAddresses.First(a => a.Description == "TargetGuid").BasePointer, true); - - dataModel.Player = player; - if (dataModel.Player != null && dataModel.Player.Guid != Guid.Empty) - { - dataModel.Player.UpdateDetails(nameCache); - var target = player.GetTarget(objectManager); - if (target == null) - return; - - dataModel.Target = new WoWUnit(target.Process, target.BaseAddress); - dataModel.Target.UpdateDetails(nameCache); - } - else - { - dataModel.Target = null; - } } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/ViewModels/GamesViewModel.cs b/Artemis/Artemis/ViewModels/GamesViewModel.cs index 68a5d6041..21914d534 100644 --- a/Artemis/Artemis/ViewModels/GamesViewModel.cs +++ b/Artemis/Artemis/ViewModels/GamesViewModel.cs @@ -1,9 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Artemis.DAL; -using Artemis.Managers; using Artemis.Modules.Abstract; -using Artemis.Settings; using Artemis.ViewModels.Abstract; namespace Artemis.ViewModels @@ -16,18 +13,7 @@ namespace Artemis.ViewModels { DisplayName = "Games"; - // Currently WoW is locked behind a hidden trigger (obviously not that hidden since you're reading this) - // It is using memory reading and lets first try to contact Blizzard - if (SettingsProvider.Load().GamestatePort == 62575) - { - _vms = moduleViewModels.Where(m => m.ModuleModel.IsBoundToProcess) - .OrderBy(g => g.DisplayName).ToList(); - } - else - { - _vms = moduleViewModels.Where(m => m.ModuleModel.IsBoundToProcess && m.DisplayName != "WoW") - .OrderBy(g => g.DisplayName).ToList(); - } + _vms = moduleViewModels.Where(m => m.ModuleModel.IsBoundToProcess).OrderBy(g => g.DisplayName).ToList(); } protected override void OnActivate() @@ -37,4 +23,4 @@ namespace Artemis.ViewModels Items.AddRange(_vms); } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/lib/SDKDLL.dll b/Artemis/Artemis/lib/SDKDLL.dll index a716191988c237399a068f25e572da886124a5a5..057316385ae7127829daf2abc97e9ef36200f4e3 100644 GIT binary patch delta 115 zcmZqppwRF^VS@xC%g6<&W`R2lYITvPzq!u;@WSg%=EVowedJg)x>$Yyjcl)2!2-mr NK+LwiVg-9gKL8(8FH8Uc delta 115 zcmZqppwRF^VS@xC%T=z@h{-aHK1{P;Zq8zyCB;}bnN>!Ov1YT5jHnwUU$awEyHgS) z5HoFeN@8x}0BhKOB8$0Xvw)UG)oI0m6|Pfu?eeqMwbIz`BgdlA#c~B`WP8O579eH? MVz%uSE7&{w0r*lW2mk;8 diff --git a/Artemis/Artemis/packages.config b/Artemis/Artemis/packages.config index d4e1ed0f9..cf23b3b03 100644 --- a/Artemis/Artemis/packages.config +++ b/Artemis/Artemis/packages.config @@ -24,6 +24,7 @@ + From 681ce58c1e541c1a2e9db2aa762da8b491c83479 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Tue, 5 Sep 2017 11:49:40 +0200 Subject: [PATCH 09/21] Added list support to conditions --- .../Artemis/Modules/Games/WoW/WoWDataModel.cs | 97 ++++++++++++++++--- Artemis/Artemis/Modules/Games/WoW/WoWModel.cs | 80 ++++++++++++--- .../Layers/Models/LayerConditionModel.cs | 81 +++++++++++++--- Artemis/Artemis/Utilities/GeneralHelpers.cs | 74 ++++++++++---- .../Profiles/LayerConditionViewModel.cs | 22 ++++- 5 files changed, 290 insertions(+), 64 deletions(-) diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs index e64e8b39f..4d04e2bcc 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Artemis.Modules.Abstract; -using Castle.Components.DictionaryAdapter; +using Artemis.Utilities; using Newtonsoft.Json.Linq; namespace Artemis.Modules.Games.WoW @@ -16,6 +17,7 @@ namespace Artemis.Modules.Games.WoW public WoWUnit Player { get; set; } public WoWUnit Target { get; set; } + public string Realm { get; set; } public string Zone { get; set; } public string SubZone { get; set; } @@ -26,7 +28,8 @@ namespace Artemis.Modules.Games.WoW public WoWUnit() { Buffs = new List(); - Debuffs = new EditableList(); + Debuffs = new List(); + CastBar = new WoWCastBar(); } public string Name { get; set; } @@ -36,11 +39,12 @@ namespace Artemis.Modules.Games.WoW public int Power { get; set; } public int MaxPower { get; set; } public WoWPowerType PowerType { get; set; } - public WoWClass Class { get; set; } + public string Class { get; set; } public WoWRace Race { get; set; } public WoWGender Gender { get; set; } public List Buffs { get; set; } public List Debuffs { get; set; } + public WoWCastBar CastBar { get; set; } public void ApplyJson(JObject json) { @@ -49,32 +53,40 @@ namespace Artemis.Modules.Games.WoW Name = json["name"].Value(); Level = json["level"].Value(); - Class = (WoWClass) Enum.Parse(typeof(WoWClass), json["class"].Value().Replace(" ", "")); - Race = (WoWRace) Enum.Parse(typeof(WoWRace), json["race"].Value().Replace(" ", "")); + Class = json["class"].Value(); Gender = json["gender"].Value() == 3 ? WoWGender.Female : WoWGender.Male; + + if (json["race"] != null) + Race = GeneralHelpers.ParseEnum(json["race"].Value()); } public void ApplyStateJson(JObject json) { Health = json["health"].Value(); MaxHealth = json["maxHealth"].Value(); - PowerType = (WoWPowerType) Enum.Parse(typeof(WoWPowerType), json["powerType"].Value().ToString(), true); + PowerType = GeneralHelpers.ParseEnum(json["powerType"].Value().ToString()); Power = json["power"].Value(); MaxPower = json["maxPower"].Value(); Buffs.Clear(); - foreach (var auraJson in json["buffs"].Children()) + if (json["buffs"] != null) { - var aura = new WoWAura(); - aura.ApplyJson(auraJson); - Buffs.Add(aura); + foreach (var auraJson in json["buffs"].Children()) + { + var aura = new WoWAura(); + aura.ApplyJson(auraJson); + Buffs.Add(aura); + } } Debuffs.Clear(); - foreach (var auraJson in json["debuffs"].Children()) + if (json["debuffs"] != null) { - var aura = new WoWAura(); - aura.ApplyJson(auraJson); - Debuffs.Add(aura); + foreach (var auraJson in json["debuffs"].Children()) + { + var aura = new WoWAura(); + aura.ApplyJson(auraJson); + Debuffs.Add(aura); + } } } } @@ -85,8 +97,8 @@ namespace Artemis.Modules.Games.WoW public int Id { get; set; } public string Caster { get; set; } public int Stacks { get; set; } - public TimeSpan Duration { get; set; } - public TimeSpan Expires { get; set; } + public DateTime StartTime { set; get; } + public DateTime EndTime { get; set; } public void ApplyJson(JToken buffJson) { @@ -99,6 +111,59 @@ namespace Artemis.Modules.Games.WoW } } + public class WoWCastBar + { + public void ApplyJson(JToken spellJson) + { + var castMs = spellJson["endTime"].Value() - spellJson["startTime"].Value(); + var tickCount = Environment.TickCount; + var difference = tickCount - spellJson["startTime"].Value(); + + SpellName = spellJson["name"].Value(); + SpellId = spellJson["spellID"].Value(); + StartTime = new DateTime(DateTime.Now.Ticks + difference); + EndTime = StartTime.AddMilliseconds(castMs); + NonInterruptible = spellJson["notInterruptible"].Value(); + + +// SpellName = spellJson["name"].Value(); +// SpellId = spellJson["spellID"].Value(); +// StartTime = DateTime.Now.AddMilliseconds(spellJson["startTime"].Value()/1000.0); +// EndTime = StartTime.AddMilliseconds(spellJson["endTime"].Value()/1000.0); +// NonInterruptible = spellJson["notInterruptible"].Value(); + } + + public void UpdateProgress() + { + if (SpellName == null) + return; + + var elapsed = DateTime.Now - StartTime; + var total = EndTime - StartTime; + Progress = (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds); + Debug.WriteLine(Progress); + if (Progress > 1) + Clear(); + } + + public void Clear() + { + SpellName = null; + SpellId = 0; + StartTime = DateTime.MinValue; + EndTime = DateTime.MinValue; + NonInterruptible = false; + Progress = 0; + } + + public string SpellName { get; set; } + public int SpellId { get; set; } + public DateTime StartTime { set; get; } + public DateTime EndTime { get; set; } + public bool NonInterruptible { get; set; } + public float Progress { get; set; } + } + public enum WoWPowerType { Mana = 0, diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs index 3c31d23a0..e00838cc0 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs @@ -97,19 +97,37 @@ namespace Artemis.Modules.Games.WoW private void HandleGameData(string command, string data) { - var json = JObject.Parse(data); - var dataModel = (WoWDataModel) DataModel; - switch (command) + JObject json = null; + if (!data.StartsWith("\"") && !data.EndsWith("\"")) + json = JObject.Parse(data); + + lock (DataModel) { - case "player": - ParsePlayer(json, dataModel); - break; - case "target": - ParseTarget(json, dataModel); - break; - case "playerState": - ParsePlayerState(json, dataModel); - break; + var dataModel = (WoWDataModel) DataModel; + switch (command) + { + case "player": + ParsePlayer(json, dataModel); + break; + case "target": + ParseTarget(json, dataModel); + break; + case "playerState": + ParsePlayerState(json, dataModel); + break; + case "targetState": + ParseTargetState(json, dataModel); + break; + case "spellCast": + ParseSpellCast(json, dataModel); + break; + case "spellCastFailed": + ParseSpellCastFailed(data, dataModel); + break; + case "spellCastInterrupted": + ParseSpellCastInterrupted(data, dataModel); + break; + } } } @@ -128,6 +146,40 @@ namespace Artemis.Modules.Games.WoW dataModel.Player.ApplyStateJson(json); } + private void ParseTargetState(JObject json, WoWDataModel dataModel) + { + dataModel.Target.ApplyStateJson(json); + } + + private void ParseSpellCast(JObject json, WoWDataModel dataModel) + { + if (json["unitID"].Value() == "player") + dataModel.Player.CastBar.ApplyJson(json); + else if (json["unitID"].Value() == "target") + dataModel.Target.CastBar.ApplyJson(json); + } + + private void ParseInstantSpellCast(JObject json, WoWDataModel dataModel) + { + + } + + private void ParseSpellCastFailed(string data, WoWDataModel dataModel) + { + if (data == "\"player\"") + dataModel.Player.CastBar.Clear(); + else if (data == "\"target\"") + dataModel.Target.CastBar.Clear(); + } + + private void ParseSpellCastInterrupted(string data, WoWDataModel dataModel) + { + if (data == "\"player\"") + dataModel.Player.CastBar.Clear(); + else if (data == "\"target\"") + dataModel.Target.CastBar.Clear(); + } + public override void Dispose() { _communicator.Break(); @@ -137,6 +189,10 @@ namespace Artemis.Modules.Games.WoW public override void Update() { + var dataModel = (WoWDataModel)DataModel; + + dataModel.Player.CastBar.UpdateProgress(); + dataModel.Target.CastBar.UpdateProgress(); } } } diff --git a/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs b/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs index 560d74408..9d7136081 100644 --- a/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs @@ -1,5 +1,10 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using System.Windows.Documents; using Artemis.Modules.Abstract; using Artemis.Utilities; using DynamicExpresso; @@ -11,10 +16,12 @@ namespace Artemis.Profiles.Layers.Models { private readonly Interpreter _interpreter; private object _lastValue; + private Regex _rgx; public LayerConditionModel() { _interpreter = new Interpreter(); + _rgx = new Regex("\\((.*?)\\)"); } public string Field { get; set; } @@ -30,6 +37,53 @@ namespace Artemis.Profiles.Layers.Models if (string.IsNullOrEmpty(Field) || string.IsNullOrEmpty(Type)) return false; + // If the path points to a collection, look inside this collection + if (Field.Contains("(")) + { + // Find the collection in the field path + var collectionField = _rgx.Match(Field).Groups[1].Value; + var collectionInspect = (IEnumerable) GeneralHelpers.GetPropertyValue(subject, collectionField); + var operatorParts = Operator.Split('|'); + _lastValue = collectionInspect; + + if (operatorParts[0] == "any") + { + var anyMatch = false; + foreach (var collectionValue in collectionInspect) + { + var field = Field.Split(')').Last().Substring(1); + anyMatch = EvaluateOperator(collectionValue, field, operatorParts[1]); + if (anyMatch) + break; + } + return anyMatch; + } + if (operatorParts[0] == "all") + { + var allMatch = true; + foreach (var collectionValue in collectionInspect) + { + var field = Field.Split(')').Last().Substring(1); + allMatch = EvaluateOperator(collectionValue, field, operatorParts[1]); + if (!allMatch) + break; + } + return allMatch; + } + if (operatorParts[0] == "none") + { + var noneMatch = true; + foreach (var collectionValue in collectionInspect) + { + var field = Field.Split(')').Last().Substring(1); + noneMatch = !EvaluateOperator(collectionValue, field, operatorParts[1]); + if (!noneMatch) + break; + } + return noneMatch; + } + } + var inspect = GeneralHelpers.GetPropertyValue(subject, Field); if (inspect == null) { @@ -41,7 +95,7 @@ namespace Artemis.Profiles.Layers.Models if (Operator == "changed" || Operator == "decreased" || Operator == "increased") returnValue = EvaluateEventOperator(subject, inspect); else - returnValue = EvaluateOperator(subject); + returnValue = EvaluateOperator(subject, Field); _lastValue = inspect; return returnValue; @@ -68,8 +122,7 @@ namespace Artemis.Profiles.Layers.Models changeOperator = ">"; // Evaluate the result and store it - var returnValue = _interpreter.Eval($"subject.{Field} {changeOperator} value", - new Parameter("subject", subject.GetType(), subject), rightParam); + var returnValue = _interpreter.Eval($"subject.{Field} {changeOperator} value", new Parameter("subject", subject.GetType(), subject), rightParam); // Set the last value to the new value _lastValue = inspect; @@ -77,25 +130,26 @@ namespace Artemis.Profiles.Layers.Models return returnValue; } - private bool EvaluateOperator(ModuleDataModel subject) + private bool EvaluateOperator(object subject, string field, string operatorOverwrite = null) { // Since _lastValue won't be used, rely on Value to not be null if (string.IsNullOrEmpty(Value)) return false; - // Put the subject in a list, allowing Dynamic Linq to be used. if (Type == "String") { - return _interpreter.Eval($"subject.{Field}.ToLower(){Operator}(value)", - new Parameter("subject", subject.GetType(), subject), - new Parameter("value", Value.ToLower())); + var stringExpressionText = operatorOverwrite == null + ? $"subject.{field}.ToLower(){Operator}(value)" + : $"subject.{field}.ToLower(){operatorOverwrite}(value)"; + + return _interpreter.Eval(stringExpressionText, new Parameter("subject", subject.GetType(), subject), new Parameter("value", Value.ToLower())); } Parameter rightParam = null; switch (Type) { case "Enum": - var enumType = GeneralHelpers.GetPropertyValue(subject, Field).GetType(); + var enumType = GeneralHelpers.GetPropertyValue(subject, field).GetType(); rightParam = new Parameter("value", Enum.Parse(enumType, Value)); break; case "Boolean": @@ -111,8 +165,11 @@ namespace Artemis.Profiles.Layers.Models break; } - return _interpreter.Eval($"subject.{Field} {Operator} value", - new Parameter("subject", subject.GetType(), subject), rightParam); + var expressionText = operatorOverwrite == null + ? $"subject.{field} {Operator} value" + : $"subject.{field} {operatorOverwrite} value"; + + return _interpreter.Eval(expressionText, new Parameter("subject", subject.GetType(), subject), rightParam); } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/Utilities/GeneralHelpers.cs b/Artemis/Artemis/Utilities/GeneralHelpers.cs index afc5be9d5..526ed7a65 100644 --- a/Artemis/Artemis/Utilities/GeneralHelpers.cs +++ b/Artemis/Artemis/Utilities/GeneralHelpers.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Threading; -using System.Windows; using Microsoft.Win32; using Newtonsoft.Json; using static System.String; @@ -42,10 +41,12 @@ namespace Artemis.Utilities return GetPropertyValue(value, path.Replace(propertyNames[0] + ".", "")); } - public static List GenerateTypeMap(object o) => GenerateTypeMap(o.GetType().GetProperties()); + public static List GenerateTypeMap(object o) + { + return GenerateTypeMap(o.GetType().GetProperties()); + } - private static List GenerateTypeMap(IEnumerable getProperties, - string path = "") + private static List GenerateTypeMap(IEnumerable getProperties, string path = "", bool inList = false) { var list = new List(); foreach (var propInfo in getProperties) @@ -62,13 +63,28 @@ namespace Artemis.Utilities if (propInfo.PropertyType.BaseType?.Name == "Enum") friendlyName = "(Choice)"; - var parent = new PropertyCollection + // At this point the loop is in the item type contained in the list + PropertyCollection parent; + if (path.Contains("Item") && inList) { - Type = propInfo.PropertyType.Name, - DisplayType = friendlyName, - Display = $"{path.Replace(".", " → ")}{propInfo.Name}", - Path = $"{path}{propInfo.Name}" - }; + parent = new PropertyCollection + { + Type = propInfo.PropertyType.Name, + DisplayType = friendlyName, + Display = $"{path.Replace("Item.", "").Replace(".", " → ")}{propInfo.Name}", + Path = $"{path.Replace("Item.", "")}{propInfo.Name}" + }; + } + else + { + parent = new PropertyCollection + { + Type = propInfo.PropertyType.Name, + DisplayType = friendlyName, + Display = $"{path.Replace(".", " → ")}{propInfo.Name}", + Path = $"{path}{propInfo.Name}" + }; + } if (propInfo.PropertyType.BaseType?.Name == "Enum") { @@ -80,10 +96,19 @@ namespace Artemis.Utilities list.Add(parent); // Don't go into Strings, DateTimes or anything with JsonIgnore on it - if (propInfo.PropertyType.Name != "String" && + if (propInfo.PropertyType.Name != "String" && propInfo.PropertyType.Name != "DateTime" && propInfo.CustomAttributes.All(a => a.AttributeType != typeof(JsonIgnoreAttribute))) - list.AddRange(GenerateTypeMap(propInfo.PropertyType.GetProperties(), path + $"{propInfo.Name}.")); + { + var newPath = $"{path}{propInfo.Name}."; + var toInList = propInfo.PropertyType.Name == "List`1"; + if (toInList) + { + inList = true; + newPath = $"({path}{propInfo.Name})."; + } + list.AddRange(GenerateTypeMap(propInfo.PropertyType.GetProperties(), newPath, inList)); + } } return list; } @@ -114,6 +139,21 @@ namespace Artemis.Utilities return null; } + public static void ExecuteSta(Action action) + { + var thread = new Thread(action.Invoke); + thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA + thread.Start(); + thread.Join(); + } + + public static T ParseEnum(string value, bool ignoreCase = true, bool stripWhitespaces = true) + { + if (stripWhitespaces) + value = value.Replace(" ", ""); + return (T) Enum.Parse(typeof(T), value, true); + } + public struct PropertyCollection { public string Display { get; set; } @@ -128,13 +168,5 @@ namespace Artemis.Utilities public List Children { get; set; } public string DisplayType { get; set; } } - - public static void ExecuteSta(Action action) - { - var thread = new Thread(action.Invoke); - thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA - thread.Start(); - thread.Join(); - } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs index 596b6b1ea..ef71e7ed3 100644 --- a/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/LayerConditionViewModel.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using Artemis.Profiles.Layers.Models; using Artemis.Utilities; @@ -41,6 +42,13 @@ namespace Artemis.ViewModels.Profiles new NamedOperator("Ends with", ".EndsWith") }; + private readonly NamedOperator[] _listOperatorsPrefixes = + { + new NamedOperator("Any", "any"), + new NamedOperator("All", "all"), + new NamedOperator("None", "none") + }; + private HotKey _hotKey; private bool _keybindIsVisible; private GeneralHelpers.PropertyCollection _selectedDataModelProp; @@ -205,7 +213,6 @@ namespace Artemis.ViewModels.Profiles { Operators.Clear(); DropdownValues.Clear(); - switch (SelectedDataModelProp.Type) { case "Int32": @@ -228,7 +235,16 @@ namespace Artemis.ViewModels.Profiles UserValueIsVisible = true; break; } - + // If the selected value is in a list, prefix all choices with list choices + if (SelectedDataModelProp.Path != null && SelectedDataModelProp.Path.Contains("(")) + { + var listOperators = new List(); + foreach (var o in Operators) + listOperators.AddRange(_listOperatorsPrefixes.Select(p => new NamedOperator(p.Display + " " + o.Display.ToLower(), p.Value + "|" + o.Value))); + + Operators.Clear(); + Operators.AddRange(listOperators); + } // Add Changed operator is the type is event if (_editorViewModel.ProposedLayer.IsEvent) { From 53dce2c4a5d1685aa6ef394e2cee7de22c55c4ad Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 6 Sep 2017 15:41:23 +0200 Subject: [PATCH 10/21] Rewrote most of the WoW module --- Artemis/Artemis/Artemis.csproj | 6 + Artemis/Artemis/Managers/LoopManager.cs | 9 +- Artemis/Artemis/Models/DeviceVisualModel.cs | 5 +- .../Modules/Games/WoW/Models/WoWAura.cs | 27 ++ .../Modules/Games/WoW/Models/WoWCastBar.cs | 57 +++ .../Modules/Games/WoW/Models/WoWEnums.cs | 44 ++ .../Games/WoW/Models/WoWSpecialization.cs | 20 + .../Modules/Games/WoW/Models/WoWSpell.cs | 11 + .../Modules/Games/WoW/Models/WoWUnit.cs | 118 +++++ .../Artemis/Modules/Games/WoW/WoWDataModel.cs | 209 +-------- Artemis/Artemis/Modules/Games/WoW/WoWModel.cs | 420 +++++++++--------- .../Artemis/Modules/Games/WoW/WoWSettings.cs | 2 +- .../Artemis/Modules/Games/WoW/WoWView.xaml | 13 +- .../Artemis/Modules/Games/WoW/WoWView.xaml.cs | 2 +- .../Artemis/Modules/Games/WoW/WoWViewModel.cs | 2 +- .../Layers/Conditions/DataModelCondition.cs | 2 + .../Layers/Models/LayerConditionModel.cs | 5 +- 17 files changed, 526 insertions(+), 426 deletions(-) create mode 100644 Artemis/Artemis/Modules/Games/WoW/Models/WoWAura.cs create mode 100644 Artemis/Artemis/Modules/Games/WoW/Models/WoWCastBar.cs create mode 100644 Artemis/Artemis/Modules/Games/WoW/Models/WoWEnums.cs create mode 100644 Artemis/Artemis/Modules/Games/WoW/Models/WoWSpecialization.cs create mode 100644 Artemis/Artemis/Modules/Games/WoW/Models/WoWSpell.cs create mode 100644 Artemis/Artemis/Modules/Games/WoW/Models/WoWUnit.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 884520177..9f1be00a2 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -404,6 +404,12 @@ TerrariaView.xaml + + + + + + diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs index 598bfa3b1..0e198c4e0 100644 --- a/Artemis/Artemis/Managers/LoopManager.cs +++ b/Artemis/Artemis/Managers/LoopManager.cs @@ -16,9 +16,12 @@ namespace Artemis.Managers { private readonly DebugViewModel _debugViewModel; private readonly DeviceManager _deviceManager; + private readonly ILogger _logger; + //private readonly Timer _loopTimer; private readonly Task _loopTask; + private readonly ModuleManager _moduleManager; public LoopManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, @@ -163,9 +166,7 @@ namespace Artemis.Managers var keyboardOnly = !mice.Any() && !headsets.Any() && !generics.Any() && !mousemats.Any(); // Setup the frame for this tick - using ( - var frame = new FrameModel(_deviceManager.ActiveKeyboard, mice.Any(), headsets.Any(), generics.Any(), - mousemats.Any())) + using (var frame = new FrameModel(_deviceManager.ActiveKeyboard, mice.Any(), headsets.Any(), generics.Any(), mousemats.Any())) { if (renderModule.IsInitialized) renderModule.Render(frame, keyboardOnly); @@ -207,4 +208,4 @@ namespace Artemis.Managers RenderCompleted?.Invoke(this, EventArgs.Empty); } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/Models/DeviceVisualModel.cs b/Artemis/Artemis/Models/DeviceVisualModel.cs index 43d59c3c4..cf64bdef0 100644 --- a/Artemis/Artemis/Models/DeviceVisualModel.cs +++ b/Artemis/Artemis/Models/DeviceVisualModel.cs @@ -54,8 +54,7 @@ namespace Artemis.Models using (var g = Graphics.FromImage(bitmap)) { g.Clear(Color.Black); - g.DrawImage(frame, new Rectangle(0, 0, bitmap.Width, bitmap.Height), RelativeRectangle, - GraphicsUnit.Pixel); + g.DrawImage(frame, new Rectangle(0, 0, bitmap.Width, bitmap.Height), RelativeRectangle, GraphicsUnit.Pixel); } return bitmap; @@ -72,4 +71,4 @@ namespace Artemis.Models _ctx = null; } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWAura.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWAura.cs new file mode 100644 index 000000000..3511e7db7 --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWAura.cs @@ -0,0 +1,27 @@ +using System; +using MoonSharp.Interpreter; +using Newtonsoft.Json.Linq; + +namespace Artemis.Modules.Games.WoW.Models +{ + [MoonSharpUserData] + public class WoWAura + { + public string Name { get; set; } + public int Id { get; set; } + public string Caster { get; set; } + public int Stacks { get; set; } + public DateTime StartTime { set; get; } + public DateTime EndTime { get; set; } + + public void ApplyJson(JToken buffJson) + { + Name = buffJson["name"].Value(); + Id = buffJson["spellID"].Value(); + Stacks = buffJson["count"].Value(); + Caster = buffJson["caster"]?.Value(); + + // TODO: Duration + } + } +} diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWCastBar.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWCastBar.cs new file mode 100644 index 000000000..37cc83c79 --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWCastBar.cs @@ -0,0 +1,57 @@ +using System; +using MoonSharp.Interpreter; +using Newtonsoft.Json.Linq; + +namespace Artemis.Modules.Games.WoW.Models +{ + [MoonSharpUserData] + public class WoWCastBar + { + public WoWCastBar() + { + Spell = new WoWSpell(); + } + + public WoWSpell Spell { get; set; } + public DateTime StartTime { set; get; } + public DateTime EndTime { get; set; } + public bool NonInterruptible { get; set; } + public float Progress { get; set; } + + public void ApplyJson(JToken spellJson) + { + var castMs = spellJson["endTime"].Value() - spellJson["startTime"].Value(); + var tickCount = Environment.TickCount; + var difference = tickCount - spellJson["startTime"].Value(); + + Spell.Name = spellJson["name"].Value(); + Spell.Id = spellJson["spellID"].Value(); + StartTime = new DateTime(DateTime.Now.Ticks + difference); + EndTime = StartTime.AddMilliseconds(castMs); + NonInterruptible = spellJson["notInterruptible"].Value(); + } + + public void UpdateProgress() + { + if (Spell.Name == null) + return; + + var elapsed = DateTime.Now - StartTime; + var total = EndTime - StartTime; + + Progress = (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds); + if (Progress > 1) + Clear(); + } + + public void Clear() + { + Spell.Name = null; + Spell.Id = 0; + StartTime = DateTime.MinValue; + EndTime = DateTime.MinValue; + NonInterruptible = false; + Progress = 0; + } + } +} diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWEnums.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWEnums.cs new file mode 100644 index 000000000..9a484d508 --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWEnums.cs @@ -0,0 +1,44 @@ +namespace Artemis.Modules.Games.WoW.Models +{ + public enum WoWRace + { + Human, + Orc, + Dwarf, + NightElf, + Undead, + Tauren, + Gnome, + Troll, + BloodElf, + Draenei, + Goblin, + Worgen, + Pandaren + } + + public enum WoWPowerType + { + Mana = 0, + Rage = 1, + Focus = 2, + Energy = 3, + ComboPoints = 4, + Runes = 5, + RunicPower = 6, + SoulShards = 7, + LunarPower = 8, + HolyPower = 9, + AlternatePower = 10, + Maelstrom = 11, + Chi = 12, + Insanity = 13, + ArcaneCharges = 16 + } + + public enum WoWGender + { + Male, + Female + } +} diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpecialization.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpecialization.cs new file mode 100644 index 000000000..63e66fd09 --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpecialization.cs @@ -0,0 +1,20 @@ +using MoonSharp.Interpreter; +using Newtonsoft.Json.Linq; + +namespace Artemis.Modules.Games.WoW.Models +{ + [MoonSharpUserData] + public class WoWSpecialization + { + public string Name { get; set; } + public int Id { get; set; } + public string Role { get; set; } + + public void ApplyJson(JToken specJson) + { + Name = specJson["name"].Value(); + Id = specJson["id"].Value(); + Role = specJson["role"].Value(); + } + } +} diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpell.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpell.cs new file mode 100644 index 000000000..8210bf2cb --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpell.cs @@ -0,0 +1,11 @@ +using MoonSharp.Interpreter; + +namespace Artemis.Modules.Games.WoW.Models +{ + [MoonSharpUserData] + public class WoWSpell + { + public string Name { get; set; } + public int Id { get; set; } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWUnit.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWUnit.cs new file mode 100644 index 000000000..de82392e5 --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWUnit.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Linq; +using Artemis.Utilities; +using MoonSharp.Interpreter; +using Newtonsoft.Json.Linq; + +namespace Artemis.Modules.Games.WoW.Models +{ + [MoonSharpUserData] + public class WoWUnit + { + private readonly List _currentFrameCasts = new List(); + + public WoWUnit() + { + CastBar = new WoWCastBar(); + Specialization = new WoWSpecialization(); + + Buffs = new List(); + Debuffs = new List(); + RecentIntantCasts = new List(); + } + + public string Name { get; set; } + public int Level { get; set; } + public int Health { get; set; } + public int MaxHealth { get; set; } + public int Power { get; set; } + public int MaxPower { get; set; } + public WoWPowerType PowerType { get; set; } + public string Class { get; set; } + public WoWRace Race { get; set; } + public WoWGender Gender { get; set; } + + public WoWCastBar CastBar { get; set; } + public WoWSpecialization Specialization { get; } + + public List Buffs { get; } + public List Debuffs { get; } + public List RecentIntantCasts { get; private set; } + + public void ApplyJson(JObject json) + { + if (json["name"] == null) + return; + + Name = json["name"].Value(); + Level = json["level"].Value(); + Class = json["class"].Value(); + Gender = json["gender"].Value() == 3 ? WoWGender.Female : WoWGender.Male; + + if (json["race"] != null) + Race = GeneralHelpers.ParseEnum(json["race"].Value()); + if (json["specialization"] != null) + Specialization.ApplyJson(json["specialization"]); + } + + public void ApplyStateJson(JObject json) + { + Health = json["health"].Value(); + MaxHealth = json["maxHealth"].Value(); + PowerType = GeneralHelpers.ParseEnum(json["powerType"].Value().ToString()); + Power = json["power"].Value(); + MaxPower = json["maxPower"].Value(); + } + + public void ApplyAuraJson(JObject json) + { + Buffs.Clear(); + if (json["buffs"] != null) + { + foreach (var auraJson in json["buffs"].Children()) + { + var aura = new WoWAura(); + aura.ApplyJson(auraJson); + Buffs.Add(aura); + } + } + Debuffs.Clear(); + if (json["debuffs"] != null) + { + foreach (var auraJson in json["debuffs"].Children()) + { + var aura = new WoWAura(); + aura.ApplyJson(auraJson); + Debuffs.Add(aura); + } + } + } + + public void AddInstantCast(WoWSpell spell) + { + lock (_currentFrameCasts) + { + _currentFrameCasts.Add(spell); + } + } + + public void ClearInstantCasts() + { + lock (_currentFrameCasts) + { + // Remove all casts that weren't cast in the after the last frame + RecentIntantCasts.Clear(); + RecentIntantCasts.AddRange(_currentFrameCasts); + + // Clear the that were after the last frame so that they are removed next frame when this method is called again + _currentFrameCasts.Clear(); + } + } + + public void Update() + { + CastBar.UpdateProgress(); + ClearInstantCasts(); + } + } +} diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs index 4d04e2bcc..3e2365362 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWDataModel.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Artemis.Modules.Abstract; -using Artemis.Utilities; -using Newtonsoft.Json.Linq; +using Artemis.Modules.Abstract; +using Artemis.Modules.Games.WoW.Models; +using MoonSharp.Interpreter; namespace Artemis.Modules.Games.WoW { + [MoonSharpUserData] public class WoWDataModel : ModuleDataModel { public WoWDataModel() @@ -22,203 +20,4 @@ namespace Artemis.Modules.Games.WoW public string Zone { get; set; } public string SubZone { get; set; } } - - public class WoWUnit - { - public WoWUnit() - { - Buffs = new List(); - Debuffs = new List(); - CastBar = new WoWCastBar(); - } - - public string Name { get; set; } - public int Level { get; set; } - public int Health { get; set; } - public int MaxHealth { get; set; } - public int Power { get; set; } - public int MaxPower { get; set; } - public WoWPowerType PowerType { get; set; } - public string Class { get; set; } - public WoWRace Race { get; set; } - public WoWGender Gender { get; set; } - public List Buffs { get; set; } - public List Debuffs { get; set; } - public WoWCastBar CastBar { get; set; } - - public void ApplyJson(JObject json) - { - if (json["name"] == null) - return; - - Name = json["name"].Value(); - Level = json["level"].Value(); - Class = json["class"].Value(); - Gender = json["gender"].Value() == 3 ? WoWGender.Female : WoWGender.Male; - - if (json["race"] != null) - Race = GeneralHelpers.ParseEnum(json["race"].Value()); - } - - public void ApplyStateJson(JObject json) - { - Health = json["health"].Value(); - MaxHealth = json["maxHealth"].Value(); - PowerType = GeneralHelpers.ParseEnum(json["powerType"].Value().ToString()); - Power = json["power"].Value(); - MaxPower = json["maxPower"].Value(); - - Buffs.Clear(); - if (json["buffs"] != null) - { - foreach (var auraJson in json["buffs"].Children()) - { - var aura = new WoWAura(); - aura.ApplyJson(auraJson); - Buffs.Add(aura); - } - } - Debuffs.Clear(); - if (json["debuffs"] != null) - { - foreach (var auraJson in json["debuffs"].Children()) - { - var aura = new WoWAura(); - aura.ApplyJson(auraJson); - Debuffs.Add(aura); - } - } - } - } - - public class WoWAura - { - public string Name { get; set; } - public int Id { get; set; } - public string Caster { get; set; } - public int Stacks { get; set; } - public DateTime StartTime { set; get; } - public DateTime EndTime { get; set; } - - public void ApplyJson(JToken buffJson) - { - Name = buffJson["name"].Value(); - Id = buffJson["spellID"].Value(); - Caster = buffJson["caster"].Value(); - Stacks = buffJson["count"].Value(); - - // TODO: Duration - } - } - - public class WoWCastBar - { - public void ApplyJson(JToken spellJson) - { - var castMs = spellJson["endTime"].Value() - spellJson["startTime"].Value(); - var tickCount = Environment.TickCount; - var difference = tickCount - spellJson["startTime"].Value(); - - SpellName = spellJson["name"].Value(); - SpellId = spellJson["spellID"].Value(); - StartTime = new DateTime(DateTime.Now.Ticks + difference); - EndTime = StartTime.AddMilliseconds(castMs); - NonInterruptible = spellJson["notInterruptible"].Value(); - - -// SpellName = spellJson["name"].Value(); -// SpellId = spellJson["spellID"].Value(); -// StartTime = DateTime.Now.AddMilliseconds(spellJson["startTime"].Value()/1000.0); -// EndTime = StartTime.AddMilliseconds(spellJson["endTime"].Value()/1000.0); -// NonInterruptible = spellJson["notInterruptible"].Value(); - } - - public void UpdateProgress() - { - if (SpellName == null) - return; - - var elapsed = DateTime.Now - StartTime; - var total = EndTime - StartTime; - Progress = (float) (elapsed.TotalMilliseconds / total.TotalMilliseconds); - Debug.WriteLine(Progress); - if (Progress > 1) - Clear(); - } - - public void Clear() - { - SpellName = null; - SpellId = 0; - StartTime = DateTime.MinValue; - EndTime = DateTime.MinValue; - NonInterruptible = false; - Progress = 0; - } - - public string SpellName { get; set; } - public int SpellId { get; set; } - public DateTime StartTime { set; get; } - public DateTime EndTime { get; set; } - public bool NonInterruptible { get; set; } - public float Progress { get; set; } - } - - public enum WoWPowerType - { - Mana = 0, - Rage = 1, - Focus = 2, - Energy = 3, - ComboPoints = 4, - Runes = 5, - RunicPower = 6, - SoulShards = 7, - LunarPower = 8, - HolyPower = 9, - AlternatePower = 10, - Maelstrom = 11, - Chi = 12, - Insanity = 13, - ArcaneCharges = 16 - } - - public enum WoWClass - { - Warrior, - Paladin, - Hunter, - Rogue, - Priest, - DeathKnight, - Shaman, - Mage, - Warlock, - Druid, - Monk, - DemonHunter - } - - public enum WoWRace - { - Human, - Orc, - Dwarf, - NightElf, - Undead, - Tauren, - Gnome, - Troll, - BloodElf, - Draenei, - Goblin, - Worgen, - Pandaren - } - - public enum WoWGender - { - Male, - Female - } } diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs index e00838cc0..b3df1a6a5 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs @@ -1,198 +1,222 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Artemis.DAL; -using Artemis.Managers; -using Artemis.Modules.Abstract; -using Newtonsoft.Json.Linq; -using PcapDotNet.Core; -using PcapDotNet.Packets; - -namespace Artemis.Modules.Games.WoW -{ - public class WoWModel : ModuleModel - { - private readonly Regex _rgx; - private PacketCommunicator _communicator; - - public WoWModel(DeviceManager deviceManager, LuaManager luaManager) : base(deviceManager, luaManager) - { - Settings = SettingsProvider.Load(); - DataModel = new WoWDataModel(); - ProcessNames.Add("Wow-64"); - - _rgx = new Regex("(artemis)\\((.*?)\\)", RegexOptions.Compiled); - } - - public override string Name => "WoW"; - public override bool IsOverlay => false; - public override bool IsBoundToProcess => true; - - - public override void Enable() - { - // Start scanning WoW packets - // Retrieve the device list from the local machine - IList allDevices = LivePacketDevice.AllLocalMachine; - - if (allDevices.Count == 0) - { - Logger.Warn("No interfaces found! Can't scan WoW packets."); - return; - } - - // Take the selected adapter - PacketDevice selectedDevice = allDevices.First(); - - // Open the device - _communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 100); - Logger.Debug("Listening on " + selectedDevice.Description + " for WoW packets"); - - // Compile the filter - using (var filter = _communicator.CreateFilter("tcp")) - { - // Set the filter - _communicator.SetFilter(filter); - } - - Task.Run(() => ReceivePackets()); - base.Enable(); - } - - private void ReceivePackets() - { - // start the capture - try - { - _communicator.ReceivePackets(0, PacketHandler); - } - catch (InvalidOperationException) - { - // ignored, happens on shutdown - } - } - - private void PacketHandler(Packet packet) - { - // Ignore duplicates - if (packet.Ethernet.IpV4.Udp.SourcePort == 3724) - return; - - var str = Encoding.Default.GetString(packet.Buffer); - if (str.ToLower().Contains("artemis")) - { - var match = _rgx.Match(str); - if (match.Groups.Count != 3) - return; - - Logger.Debug("[{0}] {1}", packet.Ethernet.IpV4.Udp.SourcePort, match.Groups[2].Value); - // Get the command and argument - var parts = match.Groups[2].Value.Split('|'); - HandleGameData(parts[0], parts[1]); - } - } - - private void HandleGameData(string command, string data) - { - JObject json = null; - if (!data.StartsWith("\"") && !data.EndsWith("\"")) - json = JObject.Parse(data); - - lock (DataModel) - { - var dataModel = (WoWDataModel) DataModel; - switch (command) - { - case "player": - ParsePlayer(json, dataModel); - break; - case "target": - ParseTarget(json, dataModel); - break; - case "playerState": - ParsePlayerState(json, dataModel); - break; - case "targetState": - ParseTargetState(json, dataModel); - break; - case "spellCast": - ParseSpellCast(json, dataModel); - break; - case "spellCastFailed": - ParseSpellCastFailed(data, dataModel); - break; - case "spellCastInterrupted": - ParseSpellCastInterrupted(data, dataModel); - break; - } - } - } - - private void ParsePlayer(JObject json, WoWDataModel dataModel) - { - dataModel.Player.ApplyJson(json); - } - - private void ParseTarget(JObject json, WoWDataModel dataModel) - { - dataModel.Target.ApplyJson(json); - } - - private void ParsePlayerState(JObject json, WoWDataModel dataModel) - { - dataModel.Player.ApplyStateJson(json); - } - - private void ParseTargetState(JObject json, WoWDataModel dataModel) - { - dataModel.Target.ApplyStateJson(json); - } - - private void ParseSpellCast(JObject json, WoWDataModel dataModel) - { - if (json["unitID"].Value() == "player") - dataModel.Player.CastBar.ApplyJson(json); - else if (json["unitID"].Value() == "target") - dataModel.Target.CastBar.ApplyJson(json); - } - - private void ParseInstantSpellCast(JObject json, WoWDataModel dataModel) - { - - } - - private void ParseSpellCastFailed(string data, WoWDataModel dataModel) - { - if (data == "\"player\"") - dataModel.Player.CastBar.Clear(); - else if (data == "\"target\"") - dataModel.Target.CastBar.Clear(); - } - - private void ParseSpellCastInterrupted(string data, WoWDataModel dataModel) - { - if (data == "\"player\"") - dataModel.Player.CastBar.Clear(); - else if (data == "\"target\"") - dataModel.Target.CastBar.Clear(); - } - - public override void Dispose() - { - _communicator.Break(); - _communicator.Dispose(); - base.Dispose(); - } - - public override void Update() - { - var dataModel = (WoWDataModel)DataModel; - - dataModel.Player.CastBar.UpdateProgress(); - dataModel.Target.CastBar.UpdateProgress(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Artemis.DAL; +using Artemis.Managers; +using Artemis.Modules.Abstract; +using Artemis.Modules.Games.WoW.Models; +using Newtonsoft.Json.Linq; +using PcapDotNet.Core; +using PcapDotNet.Packets; + +namespace Artemis.Modules.Games.WoW +{ + public class WoWModel : ModuleModel + { + private readonly Regex _rgx; + private PacketCommunicator _communicator; + + public WoWModel(DeviceManager deviceManager, LuaManager luaManager) : base(deviceManager, luaManager) + { + Settings = SettingsProvider.Load(); + DataModel = new WoWDataModel(); + ProcessNames.Add("Wow-64"); + + _rgx = new Regex("(artemis)\\((.*?)\\)", RegexOptions.Compiled); + } + + public override string Name => "WoW"; + public override bool IsOverlay => false; + public override bool IsBoundToProcess => true; + + + public override void Enable() + { + // Start scanning WoW packets + // Retrieve the device list from the local machine + IList allDevices = LivePacketDevice.AllLocalMachine; + + if (allDevices.Count == 0) + { + Logger.Warn("No interfaces found! Can't scan WoW packets."); + return; + } + + // Take the selected adapter + PacketDevice selectedDevice = allDevices.First(); + + // Open the device + _communicator = selectedDevice.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 40); + Logger.Debug("Listening on " + selectedDevice.Description + " for WoW packets"); + + // Compile the filter + using (var filter = _communicator.CreateFilter("tcp")) + { + // Set the filter + _communicator.SetFilter(filter); + } + + Task.Run(() => ReceivePackets()); + base.Enable(); + } + + private void ReceivePackets() + { + // start the capture + try + { + _communicator.ReceivePackets(0, PacketHandler); + } + catch (InvalidOperationException) + { + // ignored, happens on shutdown + } + } + + private void PacketHandler(Packet packet) + { + // Ignore duplicates + if (packet.Ethernet.IpV4.Udp.SourcePort == 3724) + return; + + var str = Encoding.Default.GetString(packet.Buffer); + if (str.ToLower().Contains("artemis")) + { + var match = _rgx.Match(str); + if (match.Groups.Count != 3) + return; + + Logger.Trace("[{0}] {1}", packet.Ethernet.IpV4.Udp.SourcePort, match.Groups[2].Value); + // Get the command and argument + var parts = match.Groups[2].Value.Split('|'); + HandleGameData(parts[0], parts[1]); + } + } + + private void HandleGameData(string command, string data) + { + JObject json = null; + if (!data.StartsWith("\"") && !data.EndsWith("\"")) + json = JObject.Parse(data); + + lock (DataModel) + { + var dataModel = (WoWDataModel) DataModel; + switch (command) + { + case "player": + ParsePlayer(json, dataModel); + break; + case "target": + ParseTarget(json, dataModel); + break; + case "playerState": + ParsePlayerState(json, dataModel); + break; + case "targetState": + ParseTargetState(json, dataModel); + break; + case "auras": + ParseAuras(json, dataModel); + break; + case "spellCast": + ParseSpellCast(json, dataModel); + break; + case "instantSpellCast": + ParseInstantSpellCast(json, dataModel); + break; + case "spellCastFailed": + ParseSpellCastFailed(data, dataModel); + break; + case "spellCastInterrupted": + ParseSpellCastInterrupted(data, dataModel); + break; + default: + Logger.Warn("The WoW addon sent an unknown command: {0}", command); + break; + } + } + } + + private void ParsePlayer(JObject json, WoWDataModel dataModel) + { + dataModel.Player.ApplyJson(json); + } + + private void ParseTarget(JObject json, WoWDataModel dataModel) + { + dataModel.Target.ApplyJson(json); + } + + private void ParsePlayerState(JObject json, WoWDataModel dataModel) + { + dataModel.Player.ApplyStateJson(json); + } + + private void ParseTargetState(JObject json, WoWDataModel dataModel) + { + dataModel.Target.ApplyStateJson(json); + } + + private void ParseAuras(JObject json, WoWDataModel dataModel) + { + dataModel.Player.ApplyAuraJson(json); + } + + private void ParseSpellCast(JObject json, WoWDataModel dataModel) + { + if (json["unitID"].Value() == "player") + dataModel.Player.CastBar.ApplyJson(json); + else if (json["unitID"].Value() == "target") + dataModel.Target.CastBar.ApplyJson(json); + } + + private void ParseInstantSpellCast(JObject json, WoWDataModel dataModel) + { + var spell = new WoWSpell + { + Name = json["name"].Value(), + Id = json["spellID"].Value() + }; + + if (json["unitID"].Value() == "player") + dataModel.Player.AddInstantCast(spell); + else if (json["unitID"].Value() == "target") + dataModel.Target.AddInstantCast(spell); + } + + private void ParseSpellCastFailed(string data, WoWDataModel dataModel) + { + if (data == "\"player\"") + dataModel.Player.CastBar.Clear(); + else if (data == "\"target\"") + dataModel.Target.CastBar.Clear(); + } + + private void ParseSpellCastInterrupted(string data, WoWDataModel dataModel) + { + if (data == "\"player\"") + dataModel.Player.CastBar.Clear(); + else if (data == "\"target\"") + dataModel.Target.CastBar.Clear(); + } + + public override void Dispose() + { + _communicator.Break(); + _communicator.Dispose(); + base.Dispose(); + } + + public override void Update() + { + var dataModel = (WoWDataModel) DataModel; + + dataModel.Player.Update(); + dataModel.Target.Update(); + } + } +} diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWSettings.cs b/Artemis/Artemis/Modules/Games/WoW/WoWSettings.cs index 2b2797c93..c4a3f3b5d 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWSettings.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWSettings.cs @@ -5,4 +5,4 @@ namespace Artemis.Modules.Games.WoW public class WoWSettings : ModuleSettings { } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWView.xaml b/Artemis/Artemis/Modules/Games/WoW/WoWView.xaml index 9537d78fd..032a99375 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWView.xaml +++ b/Artemis/Artemis/Modules/Games/WoW/WoWView.xaml @@ -1,13 +1,6 @@ - + diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWView.xaml.cs b/Artemis/Artemis/Modules/Games/WoW/WoWView.xaml.cs index 2e0bac541..33c9f8eb0 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWView.xaml.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWView.xaml.cs @@ -12,4 +12,4 @@ namespace Artemis.Modules.Games.WoW InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWViewModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWViewModel.cs index eed8a1228..5978f88c2 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWViewModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWViewModel.cs @@ -14,4 +14,4 @@ namespace Artemis.Modules.Games.WoW public override bool UsesProfileEditor => true; } -} \ No newline at end of file +} diff --git a/Artemis/Artemis/Profiles/Layers/Conditions/DataModelCondition.cs b/Artemis/Artemis/Profiles/Layers/Conditions/DataModelCondition.cs index 31aafa17d..265eb7ef3 100644 --- a/Artemis/Artemis/Profiles/Layers/Conditions/DataModelCondition.cs +++ b/Artemis/Artemis/Profiles/Layers/Conditions/DataModelCondition.cs @@ -12,6 +12,8 @@ namespace Artemis.Profiles.Layers.Conditions lock (layerModel.Properties.Conditions) { var checkConditions = layerModel.Properties.Conditions.Where(c => c.Field != null).ToList(); + if (!checkConditions.Any()) + return true; switch (layerModel.Properties.ConditionType) { case ConditionType.AnyMet: diff --git a/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs b/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs index 9d7136081..b05d15b13 100644 --- a/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Models/LayerConditionModel.cs @@ -44,6 +44,8 @@ namespace Artemis.Profiles.Layers.Models var collectionField = _rgx.Match(Field).Groups[1].Value; var collectionInspect = (IEnumerable) GeneralHelpers.GetPropertyValue(subject, collectionField); var operatorParts = Operator.Split('|'); + var field = Field.Split(')').Last().Substring(1); + _lastValue = collectionInspect; if (operatorParts[0] == "any") @@ -51,7 +53,6 @@ namespace Artemis.Profiles.Layers.Models var anyMatch = false; foreach (var collectionValue in collectionInspect) { - var field = Field.Split(')').Last().Substring(1); anyMatch = EvaluateOperator(collectionValue, field, operatorParts[1]); if (anyMatch) break; @@ -63,7 +64,6 @@ namespace Artemis.Profiles.Layers.Models var allMatch = true; foreach (var collectionValue in collectionInspect) { - var field = Field.Split(')').Last().Substring(1); allMatch = EvaluateOperator(collectionValue, field, operatorParts[1]); if (!allMatch) break; @@ -75,7 +75,6 @@ namespace Artemis.Profiles.Layers.Models var noneMatch = true; foreach (var collectionValue in collectionInspect) { - var field = Field.Split(')').Last().Substring(1); noneMatch = !EvaluateOperator(collectionValue, field, operatorParts[1]); if (!noneMatch) break; From 46a25fd5978ffe7fc430fe1de77c94ccc96b62e1 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 11 Sep 2017 00:01:25 +0200 Subject: [PATCH 11/21] Added WoW addon source Moved to using the addon channel and splitting up >255 char messages --- Artemis/Artemis/Artemis.csproj | 17 + .../Artemis/InjectionModules/BaseModules.cs | 4 +- .../Modules/Games/WoW/Models/WoWAura.cs | 9 +- .../Modules/Games/WoW/Models/WoWCastBar.cs | 10 +- .../Games/WoW/Models/WoWSpecialization.cs | 4 +- .../Modules/Games/WoW/Models/WoWUnit.cs | 48 +- .../Games/WoW/Resources/Addon/Artemis.toc | 9 + .../Games/WoW/Resources/Addon/Core.lua | 297 ++++++++ .../Addon/Libs/AceAddon-3.0/AceAddon-3.0.lua | 674 ++++++++++++++++++ .../Addon/Libs/AceAddon-3.0/AceAddon-3.0.xml | 4 + .../Addon/Libs/AceComm-3.0/AceComm-3.0.lua | 301 ++++++++ .../Addon/Libs/AceComm-3.0/AceComm-3.0.xml | 5 + .../Libs/AceComm-3.0/ChatThrottleLib.lua | 524 ++++++++++++++ .../Libs/AceConsole-3.0/AceConsole-3.0.lua | 250 +++++++ .../Libs/AceConsole-3.0/AceConsole-3.0.xml | 4 + .../Addon/Libs/AceEvent-3.0/AceEvent-3.0.lua | 126 ++++ .../Addon/Libs/AceEvent-3.0/AceEvent-3.0.xml | 4 + .../Addon/Libs/AceTimer-3.0/AceTimer-3.0.lua | 276 +++++++ .../Addon/Libs/AceTimer-3.0/AceTimer-3.0.xml | 4 + .../Resources/Addon/Libs/LibStub/LibStub.lua | 30 + .../Games/WoW/Resources/Addon/Libs/json.lua | 382 ++++++++++ .../Games/WoW/Resources/Addon/embeds.xml | 9 + Artemis/Artemis/Modules/Games/WoW/WoWModel.cs | 133 +--- .../Modules/Games/WoW/WoWPacketScanner.cs | 125 ++++ .../GeneralProfile/GeneralProfileModel.cs | 3 +- .../Profiles/Layers/Models/LayerModel.cs | 68 +- .../Profiles/Layers/Models/TweenModel.cs | 2 +- Artemis/Artemis/Views/LayerEditorView.xaml | 9 + 28 files changed, 3190 insertions(+), 141 deletions(-) create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Artemis.toc create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Core.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceAddon-3.0/AceAddon-3.0.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceAddon-3.0/AceAddon-3.0.xml create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceComm-3.0/AceComm-3.0.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceComm-3.0/AceComm-3.0.xml create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceComm-3.0/ChatThrottleLib.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceConsole-3.0/AceConsole-3.0.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceConsole-3.0/AceConsole-3.0.xml create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceEvent-3.0/AceEvent-3.0.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceEvent-3.0/AceEvent-3.0.xml create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceTimer-3.0/AceTimer-3.0.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceTimer-3.0/AceTimer-3.0.xml create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/LibStub/LibStub.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/json.lua create mode 100644 Artemis/Artemis/Modules/Games/WoW/Resources/Addon/embeds.xml create mode 100644 Artemis/Artemis/Modules/Games/WoW/WoWPacketScanner.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 9f1be00a2..461ac63df 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -410,6 +410,7 @@ + @@ -811,6 +812,16 @@ Code + + + + + + + + + + Designer @@ -1085,6 +1096,12 @@ + + + + + + diff --git a/Artemis/Artemis/InjectionModules/BaseModules.cs b/Artemis/Artemis/InjectionModules/BaseModules.cs index 3cf6d05be..5013b4d2a 100644 --- a/Artemis/Artemis/InjectionModules/BaseModules.cs +++ b/Artemis/Artemis/InjectionModules/BaseModules.cs @@ -2,6 +2,7 @@ using Artemis.Managers; using Artemis.Models; using Artemis.Modules.Abstract; +using Artemis.Modules.Games.WoW; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; @@ -65,7 +66,7 @@ namespace Artemis.InjectionModules #endregion - #region Effects + #region Modules Kernel.Bind(x => x.FromThisAssembly() @@ -81,6 +82,7 @@ namespace Artemis.InjectionModules .BindAllBaseClasses() .Configure(b => b.InSingletonScope()) ); + Bind().ToSelf(); #endregion diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWAura.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWAura.cs index 3511e7db7..583283e07 100644 --- a/Artemis/Artemis/Modules/Games/WoW/Models/WoWAura.cs +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWAura.cs @@ -9,17 +9,16 @@ namespace Artemis.Modules.Games.WoW.Models { public string Name { get; set; } public int Id { get; set; } - public string Caster { get; set; } public int Stacks { get; set; } public DateTime StartTime { set; get; } public DateTime EndTime { get; set; } public void ApplyJson(JToken buffJson) { - Name = buffJson["name"].Value(); - Id = buffJson["spellID"].Value(); - Stacks = buffJson["count"].Value(); - Caster = buffJson["caster"]?.Value(); + Name = buffJson["n"].Value(); + Id = buffJson["id"].Value(); + if (buffJson["c"] != null) + Stacks = buffJson["c"].Value(); // TODO: Duration } diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWCastBar.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWCastBar.cs index 37cc83c79..bad744da5 100644 --- a/Artemis/Artemis/Modules/Games/WoW/Models/WoWCastBar.cs +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWCastBar.cs @@ -20,15 +20,15 @@ namespace Artemis.Modules.Games.WoW.Models public void ApplyJson(JToken spellJson) { - var castMs = spellJson["endTime"].Value() - spellJson["startTime"].Value(); + var castMs = spellJson["e"].Value() - spellJson["s"].Value(); var tickCount = Environment.TickCount; - var difference = tickCount - spellJson["startTime"].Value(); + var difference = tickCount - spellJson["s"].Value(); - Spell.Name = spellJson["name"].Value(); - Spell.Id = spellJson["spellID"].Value(); + Spell.Name = spellJson["n"].Value(); + Spell.Id = spellJson["sid"].Value(); StartTime = new DateTime(DateTime.Now.Ticks + difference); EndTime = StartTime.AddMilliseconds(castMs); - NonInterruptible = spellJson["notInterruptible"].Value(); + NonInterruptible = spellJson["ni"].Value(); } public void UpdateProgress() diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpecialization.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpecialization.cs index 63e66fd09..3582359c8 100644 --- a/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpecialization.cs +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWSpecialization.cs @@ -12,9 +12,9 @@ namespace Artemis.Modules.Games.WoW.Models public void ApplyJson(JToken specJson) { - Name = specJson["name"].Value(); + Name = specJson["n"].Value(); Id = specJson["id"].Value(); - Role = specJson["role"].Value(); + Role = specJson["r"].Value(); } } } diff --git a/Artemis/Artemis/Modules/Games/WoW/Models/WoWUnit.cs b/Artemis/Artemis/Modules/Games/WoW/Models/WoWUnit.cs index de82392e5..537864486 100644 --- a/Artemis/Artemis/Modules/Games/WoW/Models/WoWUnit.cs +++ b/Artemis/Artemis/Modules/Games/WoW/Models/WoWUnit.cs @@ -39,47 +39,47 @@ namespace Artemis.Modules.Games.WoW.Models public List Debuffs { get; } public List RecentIntantCasts { get; private set; } - public void ApplyJson(JObject json) + public void ApplyJson(JToken json) { - if (json["name"] == null) + if (json["n"] == null) return; - Name = json["name"].Value(); - Level = json["level"].Value(); - Class = json["class"].Value(); - Gender = json["gender"].Value() == 3 ? WoWGender.Female : WoWGender.Male; + Name = json["n"].Value(); + Level = json["l"].Value(); + Class = json["c"].Value(); + Gender = json["g"].Value() == 3 ? WoWGender.Female : WoWGender.Male; - if (json["race"] != null) - Race = GeneralHelpers.ParseEnum(json["race"].Value()); - if (json["specialization"] != null) - Specialization.ApplyJson(json["specialization"]); + if (json["r"] != null) + Race = GeneralHelpers.ParseEnum(json["r"].Value()); + if (json["s"] != null) + Specialization.ApplyJson(json["s"]); } - public void ApplyStateJson(JObject json) + public void ApplyStateJson(JToken json) { - Health = json["health"].Value(); - MaxHealth = json["maxHealth"].Value(); - PowerType = GeneralHelpers.ParseEnum(json["powerType"].Value().ToString()); - Power = json["power"].Value(); - MaxPower = json["maxPower"].Value(); + Health = json["h"].Value(); + MaxHealth = json["mh"].Value(); + PowerType = GeneralHelpers.ParseEnum(json["t"].Value().ToString()); + Power = json["p"].Value(); + MaxPower = json["mp"].Value(); } - public void ApplyAuraJson(JObject json) + public void ApplyAuraJson(JToken json, bool buffs) { - Buffs.Clear(); - if (json["buffs"] != null) + if (buffs) { - foreach (var auraJson in json["buffs"].Children()) + Buffs.Clear(); + foreach (var auraJson in json.Children()) { var aura = new WoWAura(); aura.ApplyJson(auraJson); Buffs.Add(aura); } } - Debuffs.Clear(); - if (json["debuffs"] != null) + else { - foreach (var auraJson in json["debuffs"].Children()) + Debuffs.Clear(); + foreach (var auraJson in json.Children()) { var aura = new WoWAura(); aura.ApplyJson(auraJson); @@ -103,7 +103,7 @@ namespace Artemis.Modules.Games.WoW.Models // Remove all casts that weren't cast in the after the last frame RecentIntantCasts.Clear(); RecentIntantCasts.AddRange(_currentFrameCasts); - + // Clear the that were after the last frame so that they are removed next frame when this method is called again _currentFrameCasts.Clear(); } diff --git a/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Artemis.toc b/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Artemis.toc new file mode 100644 index 000000000..824da3279 --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Artemis.toc @@ -0,0 +1,9 @@ +## Interface: 70300 +## Title: Artemis +## Notes: Transmits ingame data to Artemis +## Author: SpoinkyNL +## Version: 1.0.0 + +embeds.xml + +Core.lua diff --git a/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Core.lua b/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Core.lua new file mode 100644 index 000000000..e1b562ff9 --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Core.lua @@ -0,0 +1,297 @@ +Artemis = LibStub("AceAddon-3.0"):NewAddon("Artemis", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0", "AceComm-3.0") +json = LibStub("json") +local debugging = false +local lastLine = {} +local unitUpdates = {} +local lastTransmitMessage +local lastTransmitTime +local lastBuffs = ""; +local lastDebuffs = ""; +local prefixCounts = {} + +function Artemis:OnEnable() + Artemis:RegisterEvent("PLAYER_ENTERING_WORLD") + Artemis:RegisterEvent("PLAYER_LEVEL_UP") + Artemis:RegisterEvent("ACHIEVEMENT_EARNED") + Artemis:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED") + Artemis:RegisterEvent("UNIT_TARGET") + Artemis:RegisterEvent("UNIT_HEALTH") + Artemis:RegisterEvent("UNIT_POWER") + Artemis:RegisterEvent("UNIT_AURA") + Artemis:RegisterEvent("UNIT_SPELLCAST_START") + Artemis:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED") + Artemis:RegisterEvent("UNIT_SPELLCAST_FAILED") + Artemis:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") + Artemis:RegisterEvent("ZONE_CHANGED") + Artemis:RegisterEvent("ZONE_CHANGED_NEW_AREA") + + Artemis:RegisterChatCommand("artemis", "HandleChatCommand") +end + +function Artemis:HandleChatCommand(input) + if input == "debug" then + debugging = not (debugging) + if debugging then + Artemis:Print("Debugging enabled.") + else + Artemis:Print("Debugging disabled.") + end + end + if input == "rc" then + prefixCounts = {} + Artemis:Print("Reset the send counters.") + end + if input == nill or input == "" or input == "help" then + Artemis:Print("Available chat commands:") + Artemis:Printf("|cffb7b7b7/artemis debug|r: Toggle debugging") + Artemis:Printf("|cffb7b7b7/artemis rc|r: Reset the debug counters") + end +end + +function Artemis:Transmit(prefix, data, prio) + local msg = "artemis(".. prefix .. "|" .. json.encode(data) ..")" + -- If the message is the same as the previous, make sure it wasn't sent less than 250ms ago + if msg == lastTransmitMessage then + if not (lastTransmitTime == nil) then + local diff = GetTime() - lastTransmitTime; + if (diff < 0.25) then + return + end + end + end + + lastTransmitTime = GetTime() + + if debugging == true then + if prefixCounts[prefix] == nill then + prefixCounts[prefix] = 0 + end + prefixCounts[prefix] = prefixCounts[prefix] + 1 + end + + if debugging == true then + Artemis:Printf("Transmitting with prefix |cfffdff71" .. prefix .. "|r (" .. prefixCounts[prefix] .. ").") + Artemis:Print(msg) + end + Artemis:SendCommMessage("(artemis)", msg, "WHISPER", UnitName("player"), prio) +end + +function Artemis:TransmitUnitState(unit, ignoreThrottle) + if not ignoreThrottle then + if not (unitUpdates[unit] == nil) then + local diff = GetTime() - unitUpdates[unit] + if (diff < 0.5) then + return + end + end + end + + local table = { + h = UnitHealth(unit), + mh = UnitHealthMax(unit), + p = UnitPower(unit), + mp = UnitPowerMax(unit), + t = UnitPowerType(unit) + }; + + unitUpdates[unit] = GetTime() + Artemis:Transmit(unit .. "State", table) +end + +function Artemis:GetUnitDetails(unit) + return { + n = UnitName(unit), + c = UnitClass(unit), + l = UnitLevel(unit), + r = UnitRace(unit), + g = UnitSex(unit), + f = UnitFactionGroup(unit) + }; +end + +function Artemis:GetPlayerDetails() + local details = Artemis:GetUnitDetails("player") + local id, name, _, _, role = GetSpecializationInfo(GetSpecialization()) + + details.realm = GetRealmName() + details.achievementPoints = GetTotalAchievementPoints(false) + details.s = {id = id, n = name, r = role} + + return details +end + +function Artemis:GetUnitAuras(unit, filter) + local auras = {}; + for index = 1, 40 do + local name, _, _, count, _, duration, expires, caster, _, _, spellID = UnitAura(unit, index, filter); + if not (name == nil) then + local buffTable = {n = name, id = spellID} + -- Leave these values out if they are 0 to save some space + if count > 0 then + buffTable["c"] = count + end + if duration > 0 then + buffTable["d"] = duration + end + if expires > 0 then + buffTable["e"] = expires + end + table.insert(auras, buffTable) + end + end + return auras +end + +function Artemis:PLAYER_ENTERING_WORLD(...) + Artemis:Transmit("player", Artemis:GetPlayerDetails()) + Artemis:TransmitUnitState("player", true); +end + +function Artemis:PLAYER_LEVEL_UP(...) + Artemis:Transmit("player", Artemis:GetPlayerDetails()) +end + +function Artemis:ACHIEVEMENT_EARNED(...) + Artemis:Transmit("player", Artemis:GetPlayerDetails()) +end + +function Artemis:ACTIVE_TALENT_GROUP_CHANGED(...) + Artemis:Transmit("player", Artemis:GetPlayerDetails()) +end + +function Artemis:UNIT_TARGET(...) + local _, source = ... + if not (source == "player") then + return + end + + local details = Artemis:GetUnitDetails("target") + + Artemis:Transmit("target", details) + Artemis:TransmitUnitState("target", true); +end + +function Artemis:UNIT_HEALTH(...) + local _, source = ... + if not (source == "player") and not (source == "target") then + return + end + + Artemis:TransmitUnitState(source, false); +end + +function Artemis:UNIT_POWER(...) + local _, source = ... + if not (source == "player") and not (source == "target") then + return + end + + Artemis:TransmitUnitState(source, false); +end + +function Artemis:UNIT_AURA(...) + local _, source = ... + if not (source == "player") then + return + end + + local buffs = Artemis:GetUnitAuras(source, "PLAYER|HELPFUL") + local debuffs = Artemis:GetUnitAuras(source, "PLAYER|HARMFUL") + + local newBuffs = json.encode(buffs) + local newDebuffs = json.encode(debuffs) + + if not (lastBuffs == newBuffs) then + Artemis:Transmit("buffs", buffs) + end + if not (lastDebuffs == newDebuffs) then + Artemis:Transmit("debuffs", debuffs) + end + + lastBuffs = newBuffs + lastDebuffs = newDebuffs +end + +-- Detect non-instant spell casts +function Artemis:UNIT_SPELLCAST_START(...) + local _, unitID, spell, rank, lineID, spellID = ... + if not (unitID == "player") and not (unitID == "target") then + return + end + + local name, _, _, _, startTime, endTime, _, _, notInterruptible = UnitCastingInfo(unitID) + local table = {uid = unitID, n = name, sid = spellID, s = startTime, e = endTime, ni = notInterruptible} + lastLine[unitID] = lineID + + Artemis:Transmit("spellCast", table, "ALERT") +end + +-- Detect instant spell casts +function Artemis:UNIT_SPELLCAST_SUCCEEDED (...) + local _, unitID, spell, rank, lineID, spellID = ... + if not (unitID == "player") and not (unitID == "target") then + return + end + -- Many spells are irrelevant system spells, don't transmit these + if unitID == "player" and not (IsPlayerSpell(spellID)) then + return + end + + local name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unitID) + -- Don't trigger on the success of a non instant cast + if not (lastLine[unitID] == nil) and lastLine[unitID] == lineID then + return + end + + -- Set back the last line to what is currently being cast (Fireblast during Fireball per example) + if not (name == nil) then + lastLine[unitID] = castID + else + lastLine[unitID] = nil + end + + local table = {uid = unitID, n = spell, sid = spellID} + + Artemis:Transmit("instantSpellCast", table, "ALERT") +end + +-- Detect falure of non instant casts +function Artemis:UNIT_SPELLCAST_FAILED (...) + local source, unitID, _, _, lineID = ... + if not (unitID == "player") and not (unitID == "target") then + return + end + if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then + return + end + + lastLine[unitID] = nil + + Artemis:Transmit("spellCastFailed", unitID, "ALERT") +end + +-- Detect cancellation of non instant casts +function Artemis:UNIT_SPELLCAST_INTERRUPTED (...) + local source, unitID, _, _, lineID = ... + if not (unitID == "player") and not (unitID == "target") then + return + end + if lastLine[unitID] == nil or not (lastLine[unitID] == lineID) then + return + end + + lastLine[unitID] = nil + + Artemis:Transmit("spellCastInterrupted", unitID, "ALERT") +end + +function Artemis:ZONE_CHANGED_NEW_AREA (...) + local pvpType, isSubZonePVP, factionName = GetZonePVPInfo() + + Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName}) +end +function Artemis:ZONE_CHANGED (...) + local pvpType, isSubZonePVP, factionName = GetZonePVPInfo() + + Artemis:Transmit("zone", {z = GetRealZoneText(), s = GetSubZoneText(), t = pvpType, p = isSubZonePVP, f = factionName}) +end \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceAddon-3.0/AceAddon-3.0.lua b/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceAddon-3.0/AceAddon-3.0.lua new file mode 100644 index 000000000..a7f7279cc --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceAddon-3.0/AceAddon-3.0.lua @@ -0,0 +1,674 @@ +--- **AceAddon-3.0** provides a template for creating addon objects. +-- It'll provide you with a set of callback functions that allow you to simplify the loading +-- process of your addon.\\ +-- Callbacks provided are:\\ +-- * **OnInitialize**, which is called directly after the addon is fully loaded. +-- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present. +-- * **OnDisable**, which is only called when your addon is manually being disabled. +-- @usage +-- -- A small (but complete) addon, that doesn't do anything, +-- -- but shows usage of the callbacks. +-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- +-- function MyAddon:OnInitialize() +-- -- do init tasks here, like loading the Saved Variables, +-- -- or setting up slash commands. +-- end +-- +-- function MyAddon:OnEnable() +-- -- Do more initialization here, that really enables the use of your addon. +-- -- Register Events, Hook functions, Create Frames, Get information from +-- -- the game that wasn't available in OnInitialize +-- end +-- +-- function MyAddon:OnDisable() +-- -- Unhook, Unregister Events, Hide frames that you created. +-- -- You would probably only use an OnDisable if you want to +-- -- build a "standby" mode, or be able to toggle modules on/off. +-- end +-- @class file +-- @name AceAddon-3.0.lua +-- @release $Id: AceAddon-3.0.lua 1084 2013-04-27 20:14:11Z nevcairiel $ + +local MAJOR, MINOR = "AceAddon-3.0", 12 +local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceAddon then return end -- No Upgrade needed. + +AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame +AceAddon.addons = AceAddon.addons or {} -- addons in general +AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon. +AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized +AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled +AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon + +-- Lua APIs +local tinsert, tconcat, tremove = table.insert, table.concat, table.remove +local fmt, tostring = string.format, tostring +local select, pairs, next, type, unpack = select, pairs, next, type, unpack +local loadstring, assert, error = loadstring, assert, error +local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler + +--[[ + xpcall safecall implementation +]] +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local xpcall, eh = ... + local method, ARGS + local function call() return method(ARGS) end + + local function dispatch(func, ...) + method = func + if not method then return end + ARGS = ... + return xpcall(call, eh) + end + + return dispatch + ]] + + local ARGS = {} + for i = 1, argCount do ARGS[i] = "arg"..i end + code = code:gsub("ARGS", tconcat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) +Dispatchers[0] = function(func) + return xpcall(func, errorhandler) +end + +local function safecall(func, ...) + -- we check to see if the func is passed is actually a function here and don't error when it isn't + -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not + -- present execution should continue without hinderance + if type(func) == "function" then + return Dispatchers[select('#', ...)](func, ...) + end +end + +-- local functions that will be implemented further down +local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype + +-- used in the addon metatable +local function addontostring( self ) return self.name end + +-- Check if the addon is queued for initialization +local function queuedForInitialization(addon) + for i = 1, #AceAddon.initializequeue do + if AceAddon.initializequeue[i] == addon then + return true + end + end + return false +end + +--- Create a new AceAddon-3.0 addon. +-- Any libraries you specified will be embeded, and the addon will be scheduled for +-- its OnInitialize and OnEnable callbacks. +-- The final addon object, with all libraries embeded, will be returned. +-- @paramsig [object ,]name[, lib, ...] +-- @param object Table to use as a base for the addon (optional) +-- @param name Name of the addon object to create +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create a simple addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0") +-- +-- -- Create a Addon object based on the table of a frame +-- local MyFrame = CreateFrame("Frame") +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0") +function AceAddon:NewAddon(objectorname, ...) + local object,name + local i=1 + if type(objectorname)=="table" then + object=objectorname + name=... + i=2 + else + name=objectorname + end + if type(name)~="string" then + error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) + end + if self.addons[name] then + error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) + end + + object = object or {} + object.name = name + + local addonmeta = {} + local oldmeta = getmetatable(object) + if oldmeta then + for k, v in pairs(oldmeta) do addonmeta[k] = v end + end + addonmeta.__tostring = addontostring + + setmetatable( object, addonmeta ) + self.addons[name] = object + object.modules = {} + object.orderedModules = {} + object.defaultModuleLibraries = {} + Embed( object ) -- embed NewModule, GetModule methods + self:EmbedLibraries(object, select(i,...)) + + -- add to queue of addons to be initialized upon ADDON_LOADED + tinsert(self.initializequeue, object) + return object +end + + +--- Get the addon object by its name from the internal AceAddon registry. +-- Throws an error if the addon object cannot be found (except if silent is set). +-- @param name unique name of the addon object +-- @param silent if true, the addon is optional, silently return nil if its not found +-- @usage +-- -- Get the Addon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +function AceAddon:GetAddon(name, silent) + if not silent and not self.addons[name] then + error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2) + end + return self.addons[name] +end + +-- - Embed a list of libraries into the specified addon. +-- This function will try to embed all of the listed libraries into the addon +-- and error if a single one fails. +-- +-- **Note:** This function is for internal use by :NewAddon/:NewModule +-- @paramsig addon, [lib, ...] +-- @param addon addon object to embed the libs in +-- @param lib List of libraries to embed into the addon +function AceAddon:EmbedLibraries(addon, ...) + for i=1,select("#", ... ) do + local libname = select(i, ...) + self:EmbedLibrary(addon, libname, false, 4) + end +end + +-- - Embed a library into the addon object. +-- This function will check if the specified library is registered with LibStub +-- and if it has a :Embed function to call. It'll error if any of those conditions +-- fails. +-- +-- **Note:** This function is for internal use by :EmbedLibraries +-- @paramsig addon, libname[, silent[, offset]] +-- @param addon addon object to embed the library in +-- @param libname name of the library to embed +-- @param silent marks an embed to fail silently if the library doesn't exist (optional) +-- @param offset will push the error messages back to said offset, defaults to 2 (optional) +function AceAddon:EmbedLibrary(addon, libname, silent, offset) + local lib = LibStub:GetLibrary(libname, true) + if not lib and not silent then + error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2) + elseif lib and type(lib.Embed) == "function" then + lib:Embed(addon) + tinsert(self.embeds[addon], libname) + return true + elseif lib then + error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2) + end +end + +--- Return the specified module from an addon object. +-- Throws an error if the addon object cannot be found (except if silent is set) +-- @name //addon//:GetModule +-- @paramsig name[, silent] +-- @param name unique name of the module +-- @param silent if true, the module is optional, silently return nil if its not found (optional) +-- @usage +-- -- Get the Addon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- -- Get the Module +-- MyModule = MyAddon:GetModule("MyModule") +function GetModule(self, name, silent) + if not self.modules[name] and not silent then + error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2) + end + return self.modules[name] +end + +local function IsModuleTrue(self) return true end + +--- Create a new module for the addon. +-- The new module can have its own embeded libraries and/or use a module prototype to be mixed into the module.\\ +-- A module has the same functionality as a real addon, it can have modules of its own, and has the same API as +-- an addon object. +-- @name //addon//:NewModule +-- @paramsig name[, prototype|lib[, lib, ...]] +-- @param name unique name of the module +-- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional) +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create a module with some embeded libraries +-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0") +-- +-- -- Create a module with a prototype +-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } +-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0") +function NewModule(self, name, prototype, ...) + if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end + if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end + + if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end + + -- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well. + -- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is. + local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name)) + + module.IsModule = IsModuleTrue + module:SetEnabledState(self.defaultModuleState) + module.moduleName = name + + if type(prototype) == "string" then + AceAddon:EmbedLibraries(module, prototype, ...) + else + AceAddon:EmbedLibraries(module, ...) + end + AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries)) + + if not prototype or type(prototype) == "string" then + prototype = self.defaultModulePrototype or nil + end + + if type(prototype) == "table" then + local mt = getmetatable(module) + mt.__index = prototype + setmetatable(module, mt) -- More of a Base class type feel. + end + + safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy. + self.modules[name] = module + tinsert(self.orderedModules, module) + + return module +end + +--- Returns the real name of the addon or module, without any prefix. +-- @name //addon//:GetName +-- @paramsig +-- @usage +-- print(MyAddon:GetName()) +-- -- prints "MyAddon" +function GetName(self) + return self.moduleName or self.name +end + +--- Enables the Addon, if possible, return true or false depending on success. +-- This internally calls AceAddon:EnableAddon(), thus dispatching a OnEnable callback +-- and enabling all modules of the addon (unless explicitly disabled).\\ +-- :Enable() also sets the internal `enableState` variable to true +-- @name //addon//:Enable +-- @paramsig +-- @usage +-- -- Enable MyModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Enable() +function Enable(self) + self:SetEnabledState(true) + + -- nevcairiel 2013-04-27: don't enable an addon/module if its queued for init still + -- it'll be enabled after the init process + if not queuedForInitialization(self) then + return AceAddon:EnableAddon(self) + end +end + +--- Disables the Addon, if possible, return true or false depending on success. +-- This internally calls AceAddon:DisableAddon(), thus dispatching a OnDisable callback +-- and disabling all modules of the addon.\\ +-- :Disable() also sets the internal `enableState` variable to false +-- @name //addon//:Disable +-- @paramsig +-- @usage +-- -- Disable MyAddon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:Disable() +function Disable(self) + self:SetEnabledState(false) + return AceAddon:DisableAddon(self) +end + +--- Enables the Module, if possible, return true or false depending on success. +-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object. +-- @name //addon//:EnableModule +-- @paramsig name +-- @usage +-- -- Enable MyModule using :GetModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Enable() +-- +-- -- Enable MyModule using the short-hand +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:EnableModule("MyModule") +function EnableModule(self, name) + local module = self:GetModule( name ) + return module:Enable() +end + +--- Disables the Module, if possible, return true or false depending on success. +-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object. +-- @name //addon//:DisableModule +-- @paramsig name +-- @usage +-- -- Disable MyModule using :GetModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Disable() +-- +-- -- Disable MyModule using the short-hand +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:DisableModule("MyModule") +function DisableModule(self, name) + local module = self:GetModule( name ) + return module:Disable() +end + +--- Set the default libraries to be mixed into all modules created by this object. +-- Note that you can only change the default module libraries before any module is created. +-- @name //addon//:SetDefaultModuleLibraries +-- @paramsig lib[, lib, ...] +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create the addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- -- Configure default libraries for modules (all modules need AceEvent-3.0) +-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0") +-- -- Create a module +-- MyModule = MyAddon:NewModule("MyModule") +function SetDefaultModuleLibraries(self, ...) + if next(self.modules) then + error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2) + end + self.defaultModuleLibraries = {...} +end + +--- Set the default state in which new modules are being created. +-- Note that you can only change the default state before any module is created. +-- @name //addon//:SetDefaultModuleState +-- @paramsig state +-- @param state Default state for new modules, true for enabled, false for disabled +-- @usage +-- -- Create the addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- -- Set the default state to "disabled" +-- MyAddon:SetDefaultModuleState(false) +-- -- Create a module and explicilty enable it +-- MyModule = MyAddon:NewModule("MyModule") +-- MyModule:Enable() +function SetDefaultModuleState(self, state) + if next(self.modules) then + error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2) + end + self.defaultModuleState = state +end + +--- Set the default prototype to use for new modules on creation. +-- Note that you can only change the default prototype before any module is created. +-- @name //addon//:SetDefaultModulePrototype +-- @paramsig prototype +-- @param prototype Default prototype for the new modules (table) +-- @usage +-- -- Define a prototype +-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } +-- -- Set the default prototype +-- MyAddon:SetDefaultModulePrototype(prototype) +-- -- Create a module and explicitly Enable it +-- MyModule = MyAddon:NewModule("MyModule") +-- MyModule:Enable() +-- -- should print "OnEnable called!" now +-- @see NewModule +function SetDefaultModulePrototype(self, prototype) + if next(self.modules) then + error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2) + end + if type(prototype) ~= "table" then + error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2) + end + self.defaultModulePrototype = prototype +end + +--- Set the state of an addon or module +-- This should only be called before any enabling actually happend, e.g. in/before OnInitialize. +-- @name //addon//:SetEnabledState +-- @paramsig state +-- @param state the state of an addon or module (enabled=true, disabled=false) +function SetEnabledState(self, state) + self.enabledState = state +end + + +--- Return an iterator of all modules associated to the addon. +-- @name //addon//:IterateModules +-- @paramsig +-- @usage +-- -- Enable all modules +-- for name, module in MyAddon:IterateModules() do +-- module:Enable() +-- end +local function IterateModules(self) return pairs(self.modules) end + +-- Returns an iterator of all embeds in the addon +-- @name //addon//:IterateEmbeds +-- @paramsig +local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end + +--- Query the enabledState of an addon. +-- @name //addon//:IsEnabled +-- @paramsig +-- @usage +-- if MyAddon:IsEnabled() then +-- MyAddon:Disable() +-- end +local function IsEnabled(self) return self.enabledState end +local mixins = { + NewModule = NewModule, + GetModule = GetModule, + Enable = Enable, + Disable = Disable, + EnableModule = EnableModule, + DisableModule = DisableModule, + IsEnabled = IsEnabled, + SetDefaultModuleLibraries = SetDefaultModuleLibraries, + SetDefaultModuleState = SetDefaultModuleState, + SetDefaultModulePrototype = SetDefaultModulePrototype, + SetEnabledState = SetEnabledState, + IterateModules = IterateModules, + IterateEmbeds = IterateEmbeds, + GetName = GetName, +} +local function IsModule(self) return false end +local pmixins = { + defaultModuleState = true, + enabledState = true, + IsModule = IsModule, +} +-- Embed( target ) +-- target (object) - target object to embed aceaddon in +-- +-- this is a local function specifically since it's meant to be only called internally +function Embed(target, skipPMixins) + for k, v in pairs(mixins) do + target[k] = v + end + if not skipPMixins then + for k, v in pairs(pmixins) do + target[k] = target[k] or v + end + end +end + + +-- - Initialize the addon after creation. +-- This function is only used internally during the ADDON_LOADED event +-- It will call the **OnInitialize** function on the addon object (if present), +-- and the **OnEmbedInitialize** function on all embeded libraries. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- @param addon addon object to intialize +function AceAddon:InitializeAddon(addon) + safecall(addon.OnInitialize, addon) + + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedInitialize, lib, addon) end + end + + -- we don't call InitializeAddon on modules specifically, this is handled + -- from the event handler and only done _once_ +end + +-- - Enable the addon after creation. +-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED, +-- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons. +-- It will call the **OnEnable** function on the addon object (if present), +-- and the **OnEmbedEnable** function on all embeded libraries.\\ +-- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- Use :Enable on the addon itself instead. +-- @param addon addon object to enable +function AceAddon:EnableAddon(addon) + if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end + if self.statuses[addon.name] or not addon.enabledState then return false end + + -- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable. + self.statuses[addon.name] = true + + safecall(addon.OnEnable, addon) + + -- make sure we're still enabled before continueing + if self.statuses[addon.name] then + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedEnable, lib, addon) end + end + + -- enable possible modules. + local modules = addon.orderedModules + for i = 1, #modules do + self:EnableAddon(modules[i]) + end + end + return self.statuses[addon.name] -- return true if we're disabled +end + +-- - Disable the addon +-- Note: This function is only used internally. +-- It will call the **OnDisable** function on the addon object (if present), +-- and the **OnEmbedDisable** function on all embeded libraries.\\ +-- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- Use :Disable on the addon itself instead. +-- @param addon addon object to enable +function AceAddon:DisableAddon(addon) + if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end + if not self.statuses[addon.name] then return false end + + -- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable. + self.statuses[addon.name] = false + + safecall( addon.OnDisable, addon ) + + -- make sure we're still disabling... + if not self.statuses[addon.name] then + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedDisable, lib, addon) end + end + -- disable possible modules. + local modules = addon.orderedModules + for i = 1, #modules do + self:DisableAddon(modules[i]) + end + end + + return not self.statuses[addon.name] -- return true if we're disabled +end + +--- Get an iterator over all registered addons. +-- @usage +-- -- Print a list of all installed AceAddon's +-- for name, addon in AceAddon:IterateAddons() do +-- print("Addon: " .. name) +-- end +function AceAddon:IterateAddons() return pairs(self.addons) end + +--- Get an iterator over the internal status registry. +-- @usage +-- -- Print a list of all enabled addons +-- for name, status in AceAddon:IterateAddonStatus() do +-- if status then +-- print("EnabledAddon: " .. name) +-- end +-- end +function AceAddon:IterateAddonStatus() return pairs(self.statuses) end + +-- Following Iterators are deprecated, and their addon specific versions should be used +-- e.g. addon:IterateEmbeds() instead of :IterateEmbedsOnAddon(addon) +function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end +function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end + +-- Event Handling +local function onEvent(this, event, arg1) + -- 2011-08-17 nevcairiel - ignore the load event of Blizzard_DebugTools, so a potential startup error isn't swallowed up + if (event == "ADDON_LOADED" and arg1 ~= "Blizzard_DebugTools") or event == "PLAYER_LOGIN" then + -- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration + while(#AceAddon.initializequeue > 0) do + local addon = tremove(AceAddon.initializequeue, 1) + -- this might be an issue with recursion - TODO: validate + if event == "ADDON_LOADED" then addon.baseName = arg1 end + AceAddon:InitializeAddon(addon) + tinsert(AceAddon.enablequeue, addon) + end + + if IsLoggedIn() then + while(#AceAddon.enablequeue > 0) do + local addon = tremove(AceAddon.enablequeue, 1) + AceAddon:EnableAddon(addon) + end + end + end +end + +AceAddon.frame:RegisterEvent("ADDON_LOADED") +AceAddon.frame:RegisterEvent("PLAYER_LOGIN") +AceAddon.frame:SetScript("OnEvent", onEvent) + +-- upgrade embeded +for name, addon in pairs(AceAddon.addons) do + Embed(addon, true) +end + +-- 2010-10-27 nevcairiel - add new "orderedModules" table +if oldminor and oldminor < 10 then + for name, addon in pairs(AceAddon.addons) do + addon.orderedModules = {} + for module_name, module in pairs(addon.modules) do + tinsert(addon.orderedModules, module) + end + end +end diff --git a/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceAddon-3.0/AceAddon-3.0.xml b/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceAddon-3.0/AceAddon-3.0.xml new file mode 100644 index 000000000..dcf24c707 --- /dev/null +++ b/Artemis/Artemis/Modules/Games/WoW/Resources/Addon/Libs/AceAddon-3.0/AceAddon-3.0.xml @@ -0,0 +1,4 @@ + +