public void GenRunes(Save save) { if (save?.Runes == null) return; if (!getRunningHandle()) return; try { if (Type == BuildType.Lock) { foreach (var r in Mon.Current.Runes) { if (r != null) { if (r.Locked) runes[r.Slot - 1] = new Rune[0]; else runes[r.Slot - 1] = new Rune[] { r }; } } return; } if (Type == BuildType.Link && LinkBuild == null) { for (int i = 0; i < 6; i++) runes[i] = new Rune[0]; return; } // todo: less .ToArray-ing ParallelQuery<Rune> rsGlobal = save.Runes.AsParallel(); // if not saving stats, cull unusable here if (!BuildSaveStats) { // Only using 'inventory' or runes on mon // also, include runes which have been unequipped (should only look above) if (!RunesUseEquipped || RunesOnlyFillEmpty) rsGlobal = rsGlobal.Where(r => r.IsUnassigned || r.AssignedId == Mon.Id || r.Swapped); // only if the rune isn't currently locked for another purpose if (!RunesUseLocked) rsGlobal = rsGlobal.Where(r => !r.Locked); rsGlobal = rsGlobal.Where(r => !BannedRuneId.Any(b => b == r.Id) && !BannedRunesTemp.Any(b => b == r.Id)); } if ((BuildSets.Any() || RequiredSets.Any()) && BuildSets.All(s => Rune.SetRequired(s) == 4) && RequiredSets.All(s => Rune.SetRequired(s) == 4)) { // if only include/req 4 sets, include all 2 sets autoRuneSelect && () rsGlobal = rsGlobal.Where(r => BuildSets.Contains(r.Set) || RequiredSets.Contains(r.Set) || Rune.SetRequired(r.Set) == 2); } else if (BuildSets.Any() || RequiredSets.Any()) { rsGlobal = rsGlobal.Where(r => BuildSets.Contains(r.Set) || RequiredSets.Contains(r.Set)); // Only runes which we've included } if (BuildSaveStats) { foreach (Rune r in rsGlobal) { r.manageStats.AddOrUpdate("currentBuildPoints", 0, (k, v) => 0); if (!BuildGoodRunes) r.manageStats.AddOrUpdate("Set", 1, (s, d) => { return d + 1; }); else r.manageStats.AddOrUpdate("Set", 0.001, (s, d) => { return d + 0.001; }); } } int?[] slotFakes = new int?[6]; bool[] slotPred = new bool[6]; getPrediction(slotFakes, slotPred); // Set up each runeslot for (int i = 0; i < 6; i++) { // put the right ones in runes[i] = rsGlobal.Where(r => r.Slot == i + 1).ToArray(); // makes sure that the primary stat type is in the selection if (i % 2 == 1 && SlotStats[i].Count > 0) // actually evens because off by 1 { runes[i] = runes[i].AsParallel().Where(r => SlotStats[i].Contains(r.Main.Type.ToForms())).ToArray(); } if (BuildSaveStats) { foreach (Rune r in runes[i]) { if (!BuildGoodRunes) r.manageStats.AddOrUpdate("TypeFilt", 1, (s, d) => { return d + 1; }); else r.manageStats.AddOrUpdate("TypeFilt", 0.001, (s, d) => { return d + 0.001; }); } // cull here instead if (!RunesUseEquipped || RunesOnlyFillEmpty) runes[i] = runes[i].AsParallel().Where(r => r.IsUnassigned || r.AssignedId == Mon.Id || r.Swapped).ToArray(); if (!RunesUseLocked) runes[i] = runes[i].AsParallel().Where(r => !r.Locked).ToArray(); } } // clean out runes which won't make complete sets cleanBroken(); // clean out runes which won't pass the minimum cleanMinimum(); if (AutoRuneSelect) { // TODO: triple pass: start at needed for min, but each pass reduce the requirements by the average of the chosen runes for that pass, increase it by build scoring var needed = NeededForMin(slotFakes, slotPred); if (needed == null) AutoRuneSelect = false; if (AutoRuneSelect) { var needRune = new Stats(needed) / 6; // Auto-Rune select picking N per RuneSet should be fine to pick more because early-out should keep times low. // reduce number of runes to 10-15 // odds first, then evens foreach (int i in new int[] { 0, 2, 4, 5, 3, 1 }) { Rune[] rr = new Rune[0]; foreach (var rs in RequiredSets) { rr = rr.Concat(runes[i].AsParallel().Where(r => r.Set == rs).OrderByDescending(r => runeVsStats(r, needRune) * 10 + runeVsStats(r, Sort)).Take(AutoRuneAmount / 2).ToArray()).ToArray(); } if (rr.Length < AutoRuneAmount) rr = rr.Concat(runes[i].AsParallel().Where(r => !rr.Contains(r)).OrderByDescending(r => runeVsStats(r, needRune) * 10 + runeVsStats(r, Sort)).Take(AutoRuneAmount - rr.Length).ToArray()).Distinct().ToArray(); runes[i] = rr; } cleanBroken(); } } if (!AutoRuneSelect) { // TODO: Remove #if BUILD_RUNE_LOGGING //var tmp = RuneLog.logTo; //using (var fs = new System.IO.FileStream("sampleselect.log", System.IO.FileMode.Create)) //using (var sw = new System.IO.StreamWriter(fs)) { RuneLog.logTo = sw; #else { #endif // Filter each runeslot for (int i = 0; i < 6; i++) { // default fail OR Predicate<Rune> slotTest = MakeRuneScoring(i + 1, slotFakes[i] ?? 0, slotPred[i]); runes[i] = runes[i].AsParallel().Where(r => slotTest.Invoke(r)).OrderByDescending(r => r.manageStats.GetOrAdd("testScore", 0)).ToArray(); var filt = LoadFilters(i + 1); if (filt.Count != null) { var tSets = RequiredSets.Count + BuildSets.Except(RequiredSets).Count(); var perc = RequiredSets.Count / (float)tSets; var reqLoad = Math.Max(2,(int)((filt.Count ?? AutoRuneAmount ) * perc)); var rr = runes[i].AsParallel().Where(r => RequiredSets.Contains(r.Set)).GroupBy(r => r.Set).SelectMany(r => r).Take(reqLoad).ToArray(); var incLoad = (filt.Count ?? AutoRuneAmount) - rr.Count(); runes[i] = rr.Concat(runes[i].AsParallel().Where(r => !RequiredSets.Contains(r.Set)).Take(incLoad)).ToArray(); // TODO: pick 20% per required set // Then fill remaining with the best from included // Go around checking if there are enough runes from each set to complete it (if NonBroken) // Check if removing N other runes of SCORE will permit finishing set // Remove rune add next best in slot } if (BuildSaveStats) { foreach (Rune r in runes[i]) { if (!BuildGoodRunes) r.manageStats.AddOrUpdate("RuneFilt", 1, (s, d) => d + 1); else r.manageStats.AddOrUpdate("RuneFilt", 0.001, (s, d) => d + 0.001); } } } } } if (RunesDropHalfSetStat) { for (int i = 0; i < 6; i++) { double rmm = 0; var runesForSlot = runes[i]; var outRunes = new List<Rune>(); var runesBySet = runesForSlot.GroupBy(r => r.Set); foreach (var rsg in runesBySet) { var runesByMain = rsg.GroupBy(r => r.Main.Type); foreach (var rmg in runesByMain) { rmm = rmg.Max(r => r.manageStats.GetOrAdd("testScore", 0)) * 0.6; if (rmm > 0) { outRunes.AddRange(rmg.Where(r => r.manageStats.GetOrAdd("testScore", 0) > rmm)); } } } if (rmm > 0) runes[i] = outRunes.ToArray(); } } // if we are only to fill empty slots if (RunesOnlyFillEmpty) { for (int i = 0; i < 6; i++) { if (Mon.Current.Runes[i] != null && (!Mon.Current.Runes[i]?.Locked ?? false)) { runes[i] = new Rune[0]; } } } // always try to put the current rune back in for (int i = 0; i < 6; i++) { var r = Mon.Current.Runes[i]; if (r == null) continue; bool isGoodType = true; if (i % 2 == 1 && SlotStats[i].Count > 0) { isGoodType = SlotStats[i].Contains(r.Main.Type.ToForms()); } if (!runes[i].Contains(r) && !r.Locked && isGoodType) { var tl = runes[i].ToList(); tl.Add(r); runes[i] = tl.ToArray(); } } grinds = runes.SelectMany(rg => rg.SelectMany(r => r.FilterGrinds(save.Crafts).Concat(r.FilterEnchants(save.Crafts)))).Distinct().ToArray(); } finally { IsRunning = false; } }