public BuildResult GenBuilds(string prefix = "") { if (Type == BuildType.Lock) { Best = new Monster(Mon, true); return BuildResult.Success; } else if (Type == BuildType.Link) { if (LinkBuild == null) { for (int i = 0; i < 6; i++) runes[i] = new Rune[0]; return BuildResult.Failure; } else { CopyFrom(LinkBuild); } } if (runes.Any(r => r == null)) { BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Null rune")); return BuildResult.BadRune; } if (!BuildSaveStats) BuildGoodRunes = false; if (!BuildGoodRunes) { RuneUsage = new RuneUsage(); BuildUsage = new BuildUsage(); } try { // if to get awakened if (DownloadAwake && !Mon.downloaded) { BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Downloading Awake def")); var mref = MonsterStat.FindMon(Mon); if (mref != null) { // download the current (unawakened monster) var mstat = mref.Download(); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Reading stats")); // if the retrieved mon is unawakened, get the awakened if (!mstat.Awakened && mstat.AwakenTo != null) { BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Awakening")); Mon = mstat.AwakenTo.Download().GetMon(Mon); } } BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Downloaded")); } // getting awakened also gets level 40, so... // only get lvl 40 stats if the monster isn't 40, wants to download AND isn't already downloaded (first and last are about the same) else if (Mon.level < 40 && DownloadStats && !Mon.downloaded) { BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Downloading 40 def")); var mref = MonsterStat.FindMon(Mon); if (mref != null) Mon = mref.Download().GetMon(Mon); } } catch (Exception e) { BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Failed downloading def: " + e.Message + Environment.NewLine + e.StackTrace)); } if (!getRunningHandle()) throw new InvalidOperationException("The build is locked with another action."); Loads.Clear(); if (!Sort[Attr.Speed].EqualTo(0) && Sort[Attr.Speed] <= 1 // 1 SPD is too good to pass || Mon.Current.Runes.Any(r => r == null) || !Mon.Current.Runes.All(r => runes[r.Slot - 1].Contains(r)) // only IgnoreLess5 if I have my own runes || Sort.NonZeroStats.HasCount(1)) // if there is only 1 sorting, must be too important to drop??? IgnoreLess5 = false; Thread timeThread = null; if (!string.IsNullOrWhiteSpace(BuildStrategy)) { var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(asm => asm.GetTypes().Where(t => typeof(IBuildStrategyDefinition).IsAssignableFrom(t))); var type = types.FirstOrDefault(t => t.AssemblyQualifiedName.Contains(BuildStrategy)); if (type != null) { RunStrategy(); } } tcs.TrySetResult(null); try { Best = null; Stats bstats = null; count = 0; actual = 0; total = runes[0].Length; total *= runes[1].Length; total *= runes[2].Length; total *= runes[3].Length; total *= runes[4].Length; total *= runes[5].Length; complete = total; Mon.ExtraCritRate = extraCritRate; Mon.GetStats(); Mon.DamageFormula?.Invoke(Mon); int?[] slotFakesTemp = new int?[6]; bool[] slotPred = new bool[6]; getPrediction(slotFakesTemp, slotPred); int[] slotFakes = slotFakesTemp.Select(i => i ?? 0).ToArray(); var currentLoad = new Monster(Mon, true); currentLoad.Current.TempLoad = true; currentLoad.Current.Buffs = Buffs; currentLoad.Current.Shrines = Shrines; currentLoad.Current.Leader = Leader; currentLoad.Current.FakeLevel = slotFakes; currentLoad.Current.PredictSubs = slotPred; double currentScore = CalcScore(currentLoad.GetStats(true)); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "cooking")); if (total == 0) { BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "0 perms")); RuneLog.Info("Zero permuations"); return BuildResult.NoPermutations; } bool hasSort = Sort.IsNonZero; if (BuildTake == 0 && !hasSort) { BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "No sort")); RuneLog.Info("No method of determining best"); return BuildResult.NoSorting; } DateTime begin = DateTime.Now; RuneLog.Debug(count + "/" + total + " " + string.Format("{0:P2}", (count + complete - total) / (double)complete)); // set to running IsRunning = true; #if BUILD_PRECHECK_BUILDS_DEBUG SynchronizedCollection<string> outstrs = new SynchronizedCollection<string>(); #endif BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "...")); List<Monster> tests = new List<Monster>(); timeThread = new Thread(() => { while (IsRunning) { if (RunesOnlyFillEmpty) Thread.Sleep(30 / ((Mon?.Current?.RuneCount ?? 1) + 1)); else Thread.Sleep(400); // every second, give a bit of feedback to those watching RuneLog.Debug(count + "/" + total + " " + string.Format("{0:P2}", (count + complete - total) / (double)complete)); if (tests != null) BuildProgTo?.Invoke(this, ProgToEventArgs.GetEvent(this, (count + complete - total) / (double)complete, tests.Count)); if (BuildTimeout > 0 && DateTime.Now > begin.AddSeconds(BuildTimeout)) { RuneLog.Info("Timeout"); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "Timeout")); BuildProgTo?.Invoke(this, ProgToEventArgs.GetEvent(this, 1, tests.Count)); IsRunning = false; } } }); timeThread.Start(); double bestScore = double.MinValue; var opts = new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount - 1 }; var mmm = Maximum.NonZeroCached; // Parallel the outer loop // TODO: setup the begin/finish Actions with syncList. void body (Rune r0, ParallelLoopState loopState) { var tempReq = RequiredSets.ToList(); var tempMax = Maximum == null || !Maximum.IsNonZero ? null : new Stats(Maximum, true); int tempCheck = 0; Monster myBest = null; List<Monster> syncList = new List<Monster>(); void syncMyList() { lock (bestLock) { #if DEBUG_SYNC_BUILDS foreach (var s in syncList) { if (s.Current.Runes.All(r => r.Assigned == mon)) { Console.WriteLine("!"); } } #endif tests.AddRange(syncList); } //syncList.ForEach(_ => tests.Add(_)); syncList.Clear(); if (tests.Count > Math.Max(BuildGenerate, 250000)) { #if DEBUG_SYNC_BUILDS var rems = tests.OrderByDescending(b => b.score).Skip(75000).ToList(); foreach (var bbb in rems) { if (bbb.Current.Runes.All(r => r.Assigned == mon)) { Console.WriteLine("!"); } } #endif lock (bestLock) { tests = tests.OrderByDescending(b => b.score).Take(75000).ToList(); } } if (tests.Count > MaxBuilds32) IsRunning = false; } if (!IsRunning_Unsafe) { syncMyList(); loopState.Break(); } // number of builds ruled out since last sync int kill = 0; // number of builds added since last sync int plus = 0; // number of builds skipped int skip = 0; bool isBad; double myBestScore = double.MinValue, curScore, lastBest = double.MinValue; Stats cstats, myStats; Monster test = new Monster(Mon); test.Current.TempLoad = true; test.Current.Buffs = Buffs; test.Current.Shrines = Shrines; test.Current.Leader = Leader; test.Current.FakeLevel = slotFakes; test.Current.PredictSubs = slotPred; test.ApplyRune(r0, 7); RuneSet set4 = r0.SetIs4 ? r0.Set : RuneSet.Null; RuneSet set2 = r0.SetIs4 ? RuneSet.Null : r0.Set; int pop4 = 0; int pop2 = 0; foreach (Rune r1 in runes[1]) { // TODO: refactor to local method if (!IsRunning_Unsafe) // Can't break to a label, don't want to goto break; // TODO: break into multiple implementations that have less branching #if BUILD_PRECHECK_BUILDS if (!AllowBroken && !RunesOnlyFillEmpty) { if (r1.SetIs4) { if (pop2 == 2) pop2 = 7; if (set4 == RuneSet.Null || pop4 >= 2) { set4 = r1.Set; pop4 = 2; } else if (set4 != r1.Set) { #if BUILD_PRECHECK_BUILDS_DEBUG outstrs.Add($"bad4@2 {set4} {set2} | {r0.Set} {r1.Set}"); #endif skip += runes[2].Length * runes[3].Length * runes[4].Length * runes[5].Length; continue; } } else { if (pop4 == 2) pop4 = 7; if (set2 == RuneSet.Null || pop2 >= 2) { set2 = r1.Set; pop2 = 2; } } } #endif test.ApplyRune(r1, 7); foreach (Rune r2 in runes[2]) { if (!IsRunning_Unsafe) break; #if BUILD_PRECHECK_BUILDS if (!AllowBroken && !RunesOnlyFillEmpty) { if (r2.SetIs4) { if (pop2 == 3) pop2 = 7; if (set4 == RuneSet.Null || pop4 >= 3) { set4 = r2.Set; pop4 = 3; } else if (set4 != r2.Set) { #if BUILD_PRECHECK_BUILDS_DEBUG outstrs.Add($"bad4@3 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set}"); #endif skip += runes[3].Length * runes[4].Length * runes[5].Length; continue; } } else { if (pop4 == 3) pop4 = 7; if (set2 == RuneSet.Null || pop2 >= 3) { set2 = r2.Set; pop2 = 3; } else if (set4 != RuneSet.Null && set2 != r2.Set) { #if BUILD_PRECHECK_BUILDS_DEBUG outstrs.Add($"bad2@3 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set}"); #endif skip += runes[3].Length * runes[4].Length * runes[5].Length; continue; } } } #endif test.ApplyRune(r2, 7); foreach (Rune r3 in runes[3]) { if (!IsRunning_Unsafe) break; #if BUILD_PRECHECK_BUILDS if (!AllowBroken && !RunesOnlyFillEmpty) { if (r3.SetIs4) { if (pop2 == 4) pop2 = 7; if (set4 == RuneSet.Null || pop4 >= 4) { set4 = r3.Set; pop4 = 4; } else if (set4 != r3.Set) { #if BUILD_PRECHECK_BUILDS_DEBUG outstrs.Add($"bad4@4 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set}"); #endif skip += runes[4].Length * runes[5].Length; continue; } } else { if (pop4 == 4) pop4 = 7; if (set2 == RuneSet.Null || pop2 >= 4) { set2 = r3.Set; pop2 = 4; } else if (set4 != RuneSet.Null && set2 != r3.Set) { #if BUILD_PRECHECK_BUILDS_DEBUG outstrs.Add($"bad2@4 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set}"); #endif skip += runes[4].Length * runes[5].Length; continue; } } } #endif test.ApplyRune(r3, 7); foreach (Rune r4 in runes[4]) { if (!IsRunning_Unsafe) { break; } #if BUILD_PRECHECK_BUILDS if (!AllowBroken && !RunesOnlyFillEmpty) { if (r4.SetIs4) { if (pop2 == 5) pop2 = 7; if (set4 == RuneSet.Null || pop4 >= 5) { set4 = r4.Set; pop4 = 5; } else if (set4 != r4.Set) { #if BUILD_PRECHECK_BUILDS_DEBUG outstrs.Add($"bad4@5 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set} {r4.Set}"); #endif skip += runes[5].Length; continue; } } else { if (pop4 == 5) pop4 = 7; if (set2 == RuneSet.Null || pop2 >= 5) { set2 = r4.Set; pop2 = 5; } else if (set4 != RuneSet.Null && set2 != r4.Set) { #if BUILD_PRECHECK_BUILDS_DEBUG outstrs.Add($"bad2@5 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set} {r4.Set}"); #endif skip += runes[5].Length; continue; } } } #endif test.ApplyRune(r4, 7); foreach (Rune r5 in runes[5]) { if (!IsRunning_Unsafe) break; test.ApplyRune(r5, 7); test.Current.CheckSets(); #if BUILD_PRECHECK_BUILDS_DEBUG outstrs.Add($"fine {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set} {r4.Set} {r5.Set}"); #endif isBad = false; cstats = test.GetStats(); // check if build meets minimum isBad |= !RunesOnlyFillEmpty && !AllowBroken && !test.Current.SetsFull; isBad |= tempMax != null && cstats.AnyExceedCached(tempMax); if (!isBad && GrindLoads) { var mahGrinds = grinds.ToList(); for (int rg = 0; rg < 6; rg++) { var lgrinds = test.Runes[rg].FilterGrinds(mahGrinds); foreach (var g in lgrinds) { var tr = test.Runes[rg].Grind(g); } // TODO: } } isBad |= !RunesOnlyFillEmpty && Minimum != null && !cstats.GreaterEqual(Minimum, true); // if no broken sets, check for broken sets // if there are required sets, ensure we have them /*isBad |= (tempReq != null && tempReq.Count > 0 // this Linq adds no overhead compared to GetStats() and ApplyRune() //&& !tempReq.All(s => test.Current.Sets.Count(q => q == s) >= tempReq.Count(q => q == s)) //&& !tempReq.GroupBy(s => s).All(s => test.Current.Sets.Count(q => q == s.Key) >= s.Count()) );*/ // TODO: recheck this code is correct if (tempReq != null && tempReq.Count > 0) { tempCheck = 0; foreach (var r in tempReq) { int i; for (i = 0; i < 3; i++) { if (test.Current.Sets[i] == r && (tempCheck & 1 << i) != 1 << i) { tempCheck |= 1 << i; break; } } if (i >= 3) { isBad |= true; break; } } } if (isBad) { kill++; curScore = 0; } else { // try to reduce CalcScore hits curScore = CalcScore(cstats); isBad |= IgnoreLess5 && curScore < currentScore * 1.05; if (isBad) kill++; } if (!isBad) { // we found an okay build! plus++; test.score = curScore; if (BuildSaveStats) { foreach (Rune r in test.Current.Runes) { if (!BuildGoodRunes) { r.manageStats.AddOrUpdate("LoadFilt", 1, (s, d) => { return d + 1; }); RuneUsage.runesGood.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot); r.manageStats.AddOrUpdate("currentBuildPoints", curScore, (k, v) => Math.Max(v, curScore)); r.manageStats.AddOrUpdate("cbp" + ID, curScore, (k, v) => Math.Max(v, curScore)); } else { r.manageStats.AddOrUpdate("LoadFilt", 0.001, (s, d) => { return d + 0.001; }); RuneUsage.runesOkay.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot); r.manageStats.AddOrUpdate("cbp" + ID, curScore, (k, v) => Math.Max(v, curScore * 0.9)); } } } if (syncList.Count >= 500) { syncMyList(); } // if we are to track all good builds, keep it if (!BuildDumpBads) { syncList.Add(new Monster(test, true)); // locally track my best if (myBest == null || curScore > myBestScore) { myBest = new Monster(test, true); myStats = myBest.GetStats(); myBestScore = CalcScore(myStats); myBest.score = myBestScore; } // if mine is better than what I last saw if (myBestScore > lastBest) { lock (bestLock) { if (Best == null) { Best = new Monster(myBest, true); bstats = Best.GetStats(); bestScore = CalcScore(bstats); Best.score = bestScore; } else { // sync best score lastBest = bestScore; // double check if (myBestScore > lastBest) { Best = new Monster(myBest, true); bestScore = CalcScore(bstats); Best.score = bestScore; bstats = Best.GetStats(); } } } } } // if we only want to track really good builds else { // if there are currently no good builds, keep it // or if this build is better than the best, keep it // locally track my best if (myBest == null || curScore > myBestScore) { myBest = new Monster(test, true); myStats = myBest.GetStats(); myBestScore = CalcScore(myStats); myBest.score = myBestScore; syncList.Add(myBest); } else if (BuildSaveStats) { // keep it for spreadsheeting syncList.Add(new Monster(test, true) { score = curScore }); } } } } // sum up what work we've done Interlocked.Add(ref count, kill); Interlocked.Add(ref count, skip); Interlocked.Add(ref skipped, skip); Interlocked.Add(ref actual, kill); Interlocked.Add(ref BuildUsage.failed, kill); kill = 0; skip = 0; Interlocked.Add(ref count, plus); Interlocked.Add(ref actual, plus); Interlocked.Add(ref BuildUsage.passed, plus); plus = 0; // if we've got enough, stop if (BuildGenerate > 0 && BuildUsage.passed >= BuildGenerate) { IsRunning = false; break; } } } } } // just before dying syncMyList(); } Parallel.ForEach(runes[0], opts, body); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "finalizing...")); BuildProgTo?.Invoke(this, ProgToEventArgs.GetEvent(this, 0.99, -1)); #if BUILD_PRECHECK_BUILDS_DEBUG System.IO.File.WriteAllLines("_into_the_bridge.txt", outstrs.ToArray()); #endif if (BuildSaveStats) { foreach (var ra in runes) { foreach (var r in ra) { if (!BuildGoodRunes) { r.manageStats.AddOrUpdate("buildScoreTotal", CalcScore(Best), (k, v) => v + CalcScore(Best)); RuneUsage.runesUsed.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot); r.manageStats.AddOrUpdate("LoadGen", total, (s, d) => { return d + total; }); } else { RuneUsage.runesBetter.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot); r.manageStats.AddOrUpdate("LoadGen", total * 0.001, (s, d) => { return d + total * 0.001; }); } } } } // write out completion RuneLog.Debug(IsRunning + " " + count + "/" + total + " " + string.Format("{0:P2}", (count + complete - total) / (double)complete)); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + " completed")); BuildProgTo?.Invoke(this, ProgToEventArgs.GetEvent(this, 1, tests.Count)); // sort *all* the builds int takeAmount = 1; if (BuildSaveStats) takeAmount = 10; if (BuildTake > 0) takeAmount = BuildTake; if (IgnoreLess5) tests.Add(new Monster(Mon, true)); foreach (var ll in tests.Where(t => t != null).OrderByDescending(r => CalcScore(r.GetStats())).Take(takeAmount)) Loads.Add(ll); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Found a load " + Loads.Count())); if (!BuildGoodRunes) BuildUsage.loads = tests.ToList(); // dump everything to console, if nothing to print to if (BuildPrintTo == null) foreach (var l in Loads) { RuneLog.Debug(l.GetStats().Health + " " + l.GetStats().Attack + " " + l.GetStats().Defense + " " + l.GetStats().Speed + " " + l.GetStats().CritRate + "%" + " " + l.GetStats().CritDamage + "%" + " " + l.GetStats().Resistance + "%" + " " + l.GetStats().Accuracy + "%"); } // sadface if no builds if (!Loads.Any()) { RuneLog.Info("No builds :("); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "Zero :(")); } else { // remember the good one Best = Loads.First(); Best.Current.TempLoad = false; Best.score = CalcScore(Best.GetStats()); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "best " + (Best?.score ?? -1))); Best.Current.ActualTests = actual; foreach (var bb in Loads) { foreach (Rune r in bb.Current.Runes) { double val = Best.score; if (BuildGoodRunes) { val *= 0.25; if (bb == Best) RuneUsage.runesSecond.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot); } if (bb != Best) val *= 0.1; else r.manageStats.AddOrUpdate("In", BuildGoodRunes ? 2 : 1, (s, e) => BuildGoodRunes ? 2 : 1); r.manageStats.AddOrUpdate("buildScoreIn", val, (k, v) => v + val); } } for (int i = 0; i < 6; i++) { if (!BuildGoodRunes && Mon.Current.Runes[i] != null && Mon.Current.Runes[i].Id != Best.Current.Runes[i].Id) Mon.Current.Runes[i].Swapped = true; } foreach (var ra in runes) { foreach (var r in ra) { var cbp = r.manageStats.GetOrAdd("currentBuildPoints", 0); if (cbp / Best.score < 1) r.manageStats.AddOrUpdate("bestBuildPercent", cbp / Best.score, (k, v) => Math.Max(v, cbp / Best.score)); } } } tests.Clear(); tests = null; BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "Test cleared")); return BuildResult.Success; } catch (Exception e) { RuneLog.Error("Error " + e); BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + e.ToString())); return BuildResult.Failure; } finally { tcs = new TaskCompletionSource<IBuildRunner>(); IsRunning = false; if (timeThread != null) timeThread.Join(); } }
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; } }
public Monster GenBuild(params Rune[] runes) { if (runes.Length != 6) return null; // if to get awakened if (DownloadAwake && !Mon.downloaded) { var mref = MonsterStat.FindMon(Mon); if (mref != null) { // download the current (unawakened monster) var mstat = mref.Download(); // if the retrieved mon is unawakened, get the awakened if (!mstat.Awakened && mstat.AwakenTo != null) Mon = mstat.AwakenTo.Download().GetMon(Mon); } } // getting awakened also gets level 40, so... // only get lvl 40 stats if the monster isn't 40, wants to download AND isn't already downloaded (first and last are about the same) else if (Mon.level < 40 && DownloadStats && !Mon.downloaded) { var mref = MonsterStat.FindMon(Mon); if (mref != null) Mon = mref.Download().GetMon(Mon); } int?[] slotFakes = new int?[6]; bool[] slotPred = new bool[6]; getPrediction(slotFakes, slotPred); Mon.ExtraCritRate = extraCritRate; Monster test = new Monster(Mon); test.Current.Shrines = Shrines; test.Current.Leader = Leader; test.Current.FakeLevel = slotFakes.Select(i => i ?? 0).ToArray(); test.Current.PredictSubs = slotPred; test.ApplyRune(runes[0], 7); test.ApplyRune(runes[1], 7); test.ApplyRune(runes[2], 7); test.ApplyRune(runes[3], 7); test.ApplyRune(runes[4], 7); test.ApplyRune(runes[5], 7); test.Current.CheckSets(); // TODO: Outsource to whoever wants it bool isBad = false; var cstats = test.GetStats(); // check if build meets minimum isBad |= Minimum != null && (cstats <= Minimum); // if no broken sets, check for broken sets isBad |= !AllowBroken && !test.Current.SetsFull; // if there are required sets, ensure we have them isBad |= RequiredSets != null && RequiredSets.Count > 0 // this Linq adds no overhead compared to GetStats() and ApplyRune() && !RequiredSets.All(s => test.Current.Sets.Count(q => q == s) >= RequiredSets.Count(q => q == s)); if (isBad) return null; return test; }