private async Task InitializeAsync(string treestring, string opsstring, [CanBeNull] ProgressDialogController controller, AssetLoader assetLoader) { if (!_initialized) { var jss = new JsonSerializerSettings { Error = (sender, args) => { // This one is known: "515":{"x":_,"y":_,"oo":[],"n":[]}} has an Array in "oo". if (args.ErrorContext.Path != "groups.515.oo") Log.Error("Exception while deserializing Json tree", args.ErrorContext.Error); args.ErrorContext.Handled = true; } }; var inTree = JsonConvert.DeserializeObject<PoESkillTree>(treestring, jss); var inOpts = JsonConvert.DeserializeObject<Opts>(opsstring, jss); controller?.SetProgress(0.25); await assetLoader.DownloadSkillNodeSpritesAsync(inTree, d => controller?.SetProgress(0.25 + d * 0.30)); IconInActiveSkills = new SkillIcons(); IconActiveSkills = new SkillIcons(); foreach (var obj in inTree.skillSprites) { SkillIcons icons; string prefix; if (obj.Key.EndsWith("Active")) { // Adds active nodes to IconActiveSkills icons = IconActiveSkills; prefix = obj.Key.Substring(0, obj.Key.Length - "Active".Length); } else if (obj.Key.EndsWith("Inactive")) { // Adds inactive nodes to IconInActiveSkills icons = IconInActiveSkills; prefix = obj.Key.Substring(0, obj.Key.Length - "Inactive".Length); } else { // Adds masteries to IconInActiveSkills icons = IconInActiveSkills; prefix = obj.Key; } var sprite = obj.Value[AssetZoomLevel]; var path = _assetsFolderPath + sprite.filename; icons.Images[sprite.filename] = ImageHelper.OnLoadBitmapImage(new Uri(path, UriKind.Absolute)); foreach (var o in sprite.coords) { var iconKey = prefix + "_" + o.Key; icons.SkillPositions[iconKey] = new Rect(o.Value.x, o.Value.y, o.Value.w, o.Value.h); icons.SkillImages[iconKey] = sprite.filename; } } controller?.SetProgress(0.55); // The last percent progress is reserved for rounding errors as progress must not get > 1. await assetLoader.DownloadAssetsAsync(inTree, d => controller?.SetProgress(0.55 + d * 0.44)); foreach (var ass in inTree.assets) { var path = _assetsFolderPath + ass.Key + ".png"; Assets[ass.Key] = ImageHelper.OnLoadBitmapImage(new Uri(path, UriKind.Absolute)); } RootNodeList = new List<int>(); if (inTree.root != null) { foreach (int i in inTree.root.ot) { RootNodeList.Add(i); } } else if (inTree.main != null) { foreach (int i in inTree.main.ot) { RootNodeList.Add(i); } } _ascClasses = new AscendancyClasses(); if (inOpts != null) { foreach (KeyValuePair<int, baseToAscClass> ascClass in inOpts.ascClasses) { var classes = new List<AscendancyClasses.Class>(); foreach (KeyValuePair<int, classes> asc in ascClass.Value.classes) { var newClass = new AscendancyClasses.Class { Order = asc.Key, DisplayName = asc.Value.displayName, Name = asc.Value.name, FlavourText = asc.Value.flavourText, FlavourTextColour = asc.Value.flavourTextColour.Split(',').Select(int.Parse).ToArray() }; int[] tempPointList = asc.Value.flavourTextRect.Split(',').Select(int.Parse).ToArray(); newClass.FlavourTextRect = new Vector2D(tempPointList[0], tempPointList[1]); classes.Add(newClass); } AscClasses.Classes.Add(ascClass.Value.name, classes); } } CharBaseAttributes = new Dictionary<string, float>[7]; foreach (var c in inTree.characterData) { CharBaseAttributes[c.Key] = new Dictionary<string, float> { {"+# to Strength", c.Value.base_str}, {"+# to Dexterity", c.Value.base_dex}, {"+# to Intelligence", c.Value.base_int} }; } Skillnodes = new Dictionary<ushort, SkillNode>(); RootNodeClassDictionary = new Dictionary<string, int>(); StartNodeDictionary = new Dictionary<int, int>(); AscRootNodeList = new HashSet<SkillNode>(); foreach (var nd in inTree.nodes) { var skillNode = new SkillNode { Id = nd.id, Name = nd.dn, //this value should not be split on '\n' as it causes the attribute list to seperate nodes attributes = nd.dn.Contains("Jewel Socket") ? new[] { "+1 Jewel Socket" } : nd.sd, Orbit = nd.o, OrbitIndex = nd.oidx, Icon = nd.icon, LinkId = nd.ot, G = nd.g, Da = nd.da, Ia = nd.ia, Sa = nd.sa, Spc = nd.spc.Length > 0 ? (int?)nd.spc[0] : null, IsMultipleChoice = nd.isMultipleChoice, IsMultipleChoiceOption = nd.isMultipleChoiceOption, passivePointsGranted = nd.passivePointsGranted, ascendancyName = nd.ascendancyName, IsAscendancyStart = nd.isAscendancyStart, reminderText = nd.reminderText }; if (nd.ks && !nd.not && !nd.isJewelSocket && !nd.m) { skillNode.Type = NodeType.Keystone; } else if (!nd.ks && nd.not && !nd.isJewelSocket && !nd.m) { skillNode.Type = NodeType.Notable; } else if (!nd.ks && !nd.not && nd.isJewelSocket && !nd.m) { skillNode.Type = NodeType.JewelSocket; } else if (!nd.ks && !nd.not && !nd.isJewelSocket && nd.m) { skillNode.Type = NodeType.Mastery; } else if (!nd.ks && !nd.not && !nd.isJewelSocket && !nd.m) { skillNode.Type = NodeType.Normal; } else { throw new InvalidOperationException($"Invalid node type for node {skillNode.Name}"); } Skillnodes.Add(nd.id, skillNode); if(skillNode.IsAscendancyStart) if(!AscRootNodeList.Contains(skillNode)) AscRootNodeList.Add(skillNode); if (RootNodeList.Contains(nd.id)) { if (!RootNodeClassDictionary.ContainsKey(nd.dn.ToUpperInvariant())) { RootNodeClassDictionary.Add(nd.dn.ToUpperInvariant(), nd.id); } foreach (var linkedNode in nd.ot) { if (!StartNodeDictionary.ContainsKey(nd.id) && !nd.isAscendancyStart) { StartNodeDictionary.Add(linkedNode, nd.id); } } } foreach (var node in nd.ot) { if (!StartNodeDictionary.ContainsKey(nd.id) && RootNodeList.Contains(node)) { StartNodeDictionary.Add(nd.id, node); } } } foreach (var skillNode in Skillnodes) { foreach (var i in skillNode.Value.LinkId) { if (Links.Count(nd => (nd[0] == i && nd[1] == skillNode.Key) || nd[0] == skillNode.Key && nd[1] == i) != 1) Links.Add(new[] { skillNode.Key, i }); } } foreach (var ints in Links) { Regex regexString = new Regex(@"Can Allocate Passives from the .* starting point"); bool isScionAscendancyNotable = false; foreach (var attibute in Skillnodes[ints[0]].attributes) { if (regexString.IsMatch(attibute)) isScionAscendancyNotable = true; } foreach (var attibute in Skillnodes[ints[1]].attributes) { if (regexString.IsMatch(attibute)) isScionAscendancyNotable = true; } if (isScionAscendancyNotable && StartNodeDictionary.Keys.Contains(ints[0])) { if (!Skillnodes[ints[1]].Neighbor.Contains(Skillnodes[ints[0]])) Skillnodes[ints[1]].Neighbor.Add(Skillnodes[ints[0]]); } else if (isScionAscendancyNotable && StartNodeDictionary.Keys.Contains(ints[1])) { if (!Skillnodes[ints[0]].Neighbor.Contains(Skillnodes[ints[1]])) Skillnodes[ints[0]].Neighbor.Add(Skillnodes[ints[1]]); } else { if (!Skillnodes[ints[0]].Neighbor.Contains(Skillnodes[ints[1]])) Skillnodes[ints[0]].Neighbor.Add(Skillnodes[ints[1]]); if (!Skillnodes[ints[1]].Neighbor.Contains(Skillnodes[ints[0]])) Skillnodes[ints[1]].Neighbor.Add(Skillnodes[ints[0]]); } } var regexAttrib = new Regex("[0-9]*\\.?[0-9]+"); foreach (var skillnode in Skillnodes) { //add each other as visible neighbors foreach (var snn in skillnode.Value.Neighbor) { if (snn.IsAscendancyStart && skillnode.Value.LinkId.Contains(snn.Id)) continue; skillnode.Value.VisibleNeighbors.Add(snn); } //populate the Attributes fields with parsed attributes skillnode.Value.Attributes = new Dictionary<string, List<float>>(); foreach (string s in skillnode.Value.attributes) { var values = new List<float>(); foreach (Match m in regexAttrib.Matches(s)) { if (!AttributeTypes.Contains(regexAttrib.Replace(s, "#"))) AttributeTypes.Add(regexAttrib.Replace(s, "#")); if (m.Value == "") values.Add(float.NaN); else values.Add(float.Parse(m.Value, CultureInfo.InvariantCulture)); } string cs = (regexAttrib.Replace(s, "#")); skillnode.Value.Attributes[cs] = values; } } NodeGroups = new List<SkillNodeGroup>(); foreach (var gp in inTree.groups) { var ng = new SkillNodeGroup(); ng.OcpOrb = gp.Value.oo; ng.Position = new Vector2D(gp.Value.x, gp.Value.y); foreach (var node in gp.Value.n) { ng.Nodes.Add(Skillnodes[node]); } NodeGroups.Add(ng); } foreach (SkillNodeGroup group in NodeGroups) { foreach (SkillNode node in group.Nodes) { node.SkillNodeGroup = group; } } const int padding = 500; //This is to account for jewel range circles. Might need to find a better way to do it. SkillTreeRect = new Rect2D(new Vector2D(inTree.min_x * 1.1 - padding, inTree.min_y * 1.1 - padding), new Vector2D(inTree.max_x * 1.1 + padding, inTree.max_y * 1.1 + padding)); } if (_persistentData.Options.ShowAllAscendancyClasses) DrawAscendancy = true; InitialSkillTreeDrawing(); controller?.SetProgress(1); _initialized = true; }