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) {