using System.Collections.Generic;using System.Text.RegularExpressions; sealed partial class Game internal static bool AutoRollWindowWord(string k) return k == "HIRETIER" || k.StartsWith("STOPATTHISRARITY") || k.StartsWith("THISSIOTONIY") || k.StartsWith("EVERYEMPTYSIOT") || k == "CHANCEPERHIRE"; internal static bool RollingWord(string k, bool left) return k.StartsWith("ROIIINGFOR") || (left && k.StartsWith("AU") && k.EndsWith("ROIIING") && k.Length >= 9 && k.Length <= 13); internal static bool LightInk(Frame f, Rectangle box) box.Intersect(new Rectangle(0, 0, f.Width, f.Height)); for (int y = box.Top; y < box.Bottom; y++) for (int x = box.Left; x < box.Right; x++) int mx = Math.Max(c.R, Math.Max(c.G, c.B)), mn = Math.Min(c.R, Math.Min(c.G, c.B)); if (mx >= 150 && mx - mn <= 30) n++; return n >= Math.Max(4, box.Height); static bool BrightInk(Frame f, Rectangle box) box.Intersect(new Rectangle(0, 0, f.Width, f.Height)); for (int y = box.Top; y < box.Bottom; y++) for (int x = box.Left; x < box.Right; x++) if (Math.Max(c.R, Math.Max(c.G, c.B)) >= 150) n++; return n >= Math.Max(4, box.Height); static OcrLine AutoIcon(Frame f, Rectangle area, OcrLine train, OcrLine trash, OcrLine anchor, int h, out bool active) if (trash == null) return null; int tw = Math.Max(h, trash.Box.Width); int x1 = trash.Box.Left - 2; int x0 = train != null && train.Box.Right < x1 - tw ? Math.Max(train.Box.Right + 3, x1 - 6 * tw) : x1 - 9 * tw / 2; int y0 = Math.Max(area.Y, anchor.CenterY - 3 * h), y1 = Math.Min(area.Bottom - 1, anchor.CenterY + 3 * h); int[] l = { int.MaxValue, int.MaxValue }, t = { int.MaxValue, int.MaxValue }, r = { -1, -1 }, b = { -1, -1 }, n = { 0, 0 }; for (int y = y0; y <= y1; y++) for (int x = Math.Max(0, x0); x <= x1 && x < f.Width; x++) int mx = Math.Max(c.R, Math.Max(c.G, c.B)), mn = Math.Min(c.R, Math.Min(c.G, c.B)); if (mx - mn > 25 || mx < 80) continue; int i = mx >= 150 ? 0 : 1; l[i] = Math.Min(l[i], x); r[i] = Math.Max(r[i], x); t[i] = Math.Min(t[i], y); b[i] = Math.Max(b[i], y); if (r[k] < 0 || n[k] < h) return null; int w = r[k] - l[k] + 1, hh = b[k] - t[k] + 1; if (w < h / 2 || w > 4 * tw || hh < h / 2 || hh > 7 * h) { active = false; return null; } return new OcrLine { Text = "AUTO", Box = Rectangle.FromLTRB(l[k], t[k], r[k] + 1, b[k] + 1) }; static readonly Regex RollForRx = new Regex(@"R\s*O\s*[LI1]\s*[LI1]\s*[IL1]\s*N\s*G\s*F\s*O\s*R\s*([A-Za-z]{3,12})\s*O\s*R\s*B\s*E", RegexOptions.IgnoreCase); static readonly Regex RollSpentRx = new Regex(@"(?:(STREET|PROFESSIONAL|ELITE)\b.*?)?([0-9OoIlS]{1,6})\s*h\s*[iIl1]\s*r\s*e\s*s?\b.*?(\$\s?[0-9OoIlS]{1,3}(?:[.,][0-9OoIlS]{1,3})*\s?[KMBT]?)\s*s\s*p\s*e\s*n\s*t", RegexOptions.IgnoreCase); static void ReadRoll(Frame f, Rectangle area, List<OcrLine> lines, CrewRow row, Func<OcrLine, bool> inBand) foreach (var l in lines.Where(inBand)) var m = RollForRx.Match(l.Text); if (m.Success) { row.RollLine = l; row.RollFor = RarityNear(Parse.Key(m.Groups[1].Value)); } var s = RollSpentRx.Match(l.Text); if (!s.Success) continue; if (int.TryParse(Parse.Digits(s.Groups[2].Value), out n)) row.RollHires = n; if (Parse.Money(s.Groups[3].Value.Replace(" ", ""), out spent)) row.RollSpent = spent; row.RollTier = (CrewRoll)Array.IndexOf(new[] { "STREET", "PROFESSIONAL", "ELITE" }, s.Groups[1].Value.ToUpperInvariant()); var at = row.RollLine ?? row.Anchor; int h = Math.Max(8, at.Box.Height); row.RollStop = lines.FirstOrDefault(l => inBand(l) && Parse.Key(l.Text) == "STOP" && l.CenterX > area.X + area.Width * 0.6 && Math.Abs(l.CenterY - at.CenterY) <= 3 * h && (row.RollLine == null || l.Box.X > row.RollLine.Box.Right)); row.RollStopGold = row.RollStop != null && GoldFace(f, row.RollStop.Box); public sealed class AutoRollWindow public OcrLine[] Tiers = new OcrLine[3]; public bool[] TierLight = new bool[3]; public OcrLine[] Rarities = new OcrLine[8]; public bool[] RarityLight = new bool[8]; public OcrLine ThisSlot, EverySlot; public bool ThisSlotPicked, EverySlotPicked; public Point X = Point.Empty; public string Words = ""; public bool Usable { get { return !Robux && Start != null && Tiers.Count(t => t != null) >= 2 && Rarities.Count(r => r != null) >= 6; } } public bool Names(string rowName) { return Name != null && rowName != null && WindowNames(Name, rowName); } public override string ToString() return (Name ?? (Empty ? "an empty slot" : "no one named")) + ", " + (Tier >= 0 ? ((CrewRoll)Tier).ToString() : "no tier") + ", " + (Rarity >= 0 ? RarityTitle(Rarity) : "no rarity") + ", " + (ThisSlotPicked ? "this slot only" : EverySlotPicked ? "every empty slot" : "no slots picked") + (Start == null ? ", no START" : StartGold ? "" : ", START not gold") + (X.IsEmpty ? ", no X" : "") + (Robux ? ", ROBUX" : ""); static readonly string[] TierKeys = { "STREET", "PROFESSIONAI", "EIITE" }; internal static bool RobuxWords(string text) string k = Parse.Key(text); return Regex.IsMatch(text ?? "", @"R\s?\$") || k.Contains("ROBUX") || k.Contains("GAMEPASS") || k.Contains("PURCHASE") || Regex.IsMatch(" " + (text ?? "").ToUpperInvariant() + " ", @"[^A-Z](PASS|PASSES)[^A-Z]") || k == "BUY" || k == "BUYNOW" || k == "GET" || k == "UNIOCK"; using (var f = Capture()) if (Ocr.Read(f, PopupArea(f), PopupScale(f), Prep.None).Any(l => RobuxWords(l.Text))) return false; return p.Page && !p.AutoRollWindow; public AutoRollWindow ReadAutoRollWindow(Frame f) var pop = Ocr.Read(f, PopupArea(f), PopupScale(f), Prep.None); var title = pop.Where(l => Parse.Key(l.Text) == "AUTOROII").OrderBy(l => l.Box.Y).FirstOrDefault(); if (title == null) return null; var box = WindowFrame(f, title.Box); if (box.IsEmpty && !pop.Any(l => AutoRollWindowWord(Parse.Key(l.Text)))) return null; var w = new AutoRollWindow { Title = title, Box = box.IsEmpty ? PopupArea(f) : box }; var lines = Ocr.Read(f, w.Box, PopupScale(f) + 1, Prep.None); if (lines.Count == 0) lines = pop; w.Words = string.Join(" | ", lines.OrderBy(l => l.Box.Y).ThenBy(l => l.Box.X).Select(l => l.Text.Trim())); int th = Math.Max(8, title.Box.Height); OcrLine hireTier = null, stopAt = null, slots = null; string k = Parse.Key(l.Text); if (k == "HIRETIER") hireTier = l; else if (k.StartsWith("STOPATTHISRARITY")) stopAt = l; else if (k == "SIOTS") slots = l; if (RobuxWords(l.Text)) w.Robux = true; int top = hireTier != null ? hireTier.Box.Bottom : title.Box.Bottom + 4 * th; int mid = stopAt != null ? stopAt.Box.Y : int.MaxValue; string k = Parse.Key(l.Text); if (l.Box.Y >= top - 2 && l.Box.Bottom <= mid + 2) for (int t = 0; t < 3; t++) if (w.Tiers[t] == null && (k == TierKeys[t] || (k.Length >= 4 && View.Distance(k, TierKeys[t]) <= (t == 2 ? 2 : 1)))) w.Tiers[t] = l; w.TierLight[t] = BrightInk(f, l.Box); if (Picked(f, l.Box)) w.Tier = t; if (stopAt != null && l.Box.Y > stopAt.Box.Bottom - 2 && (slots == null || l.Box.Bottom < slots.Box.Y + 2)) if (r >= 0 && w.Rarities[r] == null) w.Rarities[r] = l; w.RarityLight[r] = BrightInk(f, l.Box); if (Picked(f, l.Box)) w.Rarity = r; if (k.StartsWith("THISSIOT")) { w.ThisSlot = l; w.ThisSlotPicked = Picked(f, l.Box); } if (k.StartsWith("EVERYEMPTYSIOT")) { w.EverySlot = l; w.EverySlotPicked = Picked(f, l.Box); } if (k == "START" && (slots == null || l.Box.Y > slots.Box.Bottom)) { w.Start = l; w.StartGold = GoldFace(f, l.Box); } var panel = lines.Where(l => l.Box.Y > title.Box.Bottom && l.Box.Bottom < top - (hireTier != null ? hireTier.Box.Height : 0) && Regex.IsMatch(l.Text, "[A-Za-z]{2}")) .OrderBy(l => l.Box.Y).ToList(); var named = panel.FirstOrDefault(l => !Regex.IsMatch(l.Text, @"(?i)(rep\S*ed|\bs\S{0,2}t\s*\d)")); if (Parse.Key(named.Text).Contains("EMPTY")) w.Empty = true; else w.Name = TidyName(Regex.Replace(named.Text, @"\s+", " ").Trim()); else if (panel.Count == 0) w.Empty = true; w.X = WindowCross(f, w.Box, title); static Rectangle WindowFrame(Frame f, Rectangle title) int th = Math.Max(8, title.Height), y = title.Y + title.Height / 2; Func<int, int, bool> gold = (x, yy) => x >= 0 && yy >= 0 && x < f.Width && yy < f.Height && GoldIsh(f, x, yy); int left = -1, right = -1; for (int x = title.Left - 3; x >= Math.Max(0, title.Left - 8 * th); x--) if (gold(x, y)) { left = x; break; } for (int x = title.Right + 3; x < f.Width; x++) if (gold(x, y)) { right = x; break; } if (left < 0 || right < 0 || right - left < 10 * th) return Rectangle.Empty; for (int yy = title.Y - 2; yy >= Math.Max(0, title.Y - 8 * th); yy--) if (gold(title.Left + title.Width / 2, yy)) { topY = yy; break; } if (topY < 0) return Rectangle.Empty; int bottom = y, miss = 0; for (int yy = y; yy < f.Height && miss < 6; yy++) if (gold(left, yy) || gold(left + 1, yy) || gold(left - 1, yy)) { bottom = yy; miss = 0; } if (bottom - topY < 10 * th) return Rectangle.Empty; return Rectangle.FromLTRB(left + 2, topY + 2, right - 1, bottom - 1); static Point WindowCross(Frame f, Rectangle box, OcrLine title) int th = Math.Max(8, title.Box.Height); int x0 = Math.Max(title.Box.Right + th, box.Right - 8 * th), x1 = Math.Min(f.Width - 1, box.Right - 2); int y0 = Math.Max(0, title.Box.Y - th), y1 = Math.Min(f.Height - 1, title.Box.Bottom + th); int l = int.MaxValue, t = int.MaxValue, r = -1, b = -1, n = 0; for (int y = y0; y <= y1; y++) for (int x = x0; x <= x1; x++) if (Math.Min(c.R, Math.Min(c.G, c.B)) < 120 || Math.Max(c.R, Math.Max(c.G, c.B)) - Math.Min(c.R, Math.Min(c.G, c.B)) > 25) continue; l = Math.Min(l, x); r = Math.Max(r, x); t = Math.Min(t, y); b = Math.Max(b, y); n++; if (r < 0) return Point.Empty; int w = r - l + 1, hh = b - t + 1; if (w < th / 2 || hh < th / 2 || w > 3 * th || hh > 3 * th || n < w * hh / 6) return Point.Empty; return new Point(l + w / 2, t + hh / 2); static bool Picked(Frame f, Rectangle word) int h = Math.Max(6, word.Height), tinted = 0, n = 0; foreach (int side in new[] { -1, 1 }) int x0 = side < 0 ? word.X - 2 * h : word.Right + h, x1 = x0 + h; for (int x = x0; x <= x1; x++) for (int y = word.Y + h / 4; y <= word.Bottom - h / 4; y++) if (x < 0 || y < 0 || x >= f.Width || y >= f.Height) continue; int mx = Math.Max(c.R, Math.Max(c.G, c.B)), mn = Math.Min(c.R, Math.Min(c.G, c.B)); if (mx - mn >= 12) tinted++; return n > 0 && tinted * 10 >= n * 7; public string AutoRollWhy; public bool AutoRollNoPass; public AutoRollWindow AutoRollSeen; public bool StartAutoRoll(CrewRow row, string replaces, CrewRoll tier, int rarity) AutoRollWhy = null; AutoRollNoPass = false; AutoRollSeen = null; if (row == null || row.Auto == null) { AutoRollWhy = "no AUTO button on its row"; return false; } AutoRollNoPass = true; AutoRollWhy = "its AUTO button is greyed (the game's Auto Roll pass isn't on this account)"; Snapshot("crew auto roll greyed", 60); PressCrew(row.Auto, row.AutoActive && (row.Empty ? Parse.Key(row.Auto.Text) == "AUTOROII" : row.Auto.Text == "AUTO"), "not a row's own AUTO"); for (int look = 0; look < 4 && w == null; look++) Wait(look == 0 ? 700 : 500); using (var f = Capture()) try { CheckForPurchasePrompt(Ocr.Read(f, PopupArea(f), PopupScale(f), Prep.None)); } catch (NeedUserException) { AutoRollNoPass = true; throw; } w = ReadAutoRollWindow(f); using (var f = Capture()) pop = Ocr.Read(f, PopupArea(f), PopupScale(f), Prep.None); AutoRollNoPass = pop.Any(l => RobuxWords(l.Text)); AutoRollWhy = AutoRollNoPass ? "AUTO pressed: the game offered something for Robux or a pass instead (" + string.Join(" | ", pop.Select(l => l.Text.Trim()).Take(8)) + ")" : "AUTO pressed, but no AUTO ROLL window showed"; Snapshot(AutoRollNoPass ? "crew auto roll pass offer" : "crew auto roll no window", 60, false, !AutoRollNoPass); for (int step = 0; step < 4; step++) string wrong = WindowWrong(w, replaces); if (w.Robux || !w.Usable) { AutoRollNoPass = w.Robux; Snapshot("crew auto roll window " + (w.Robux ? "robux" : "not read"), 60, false, !w.Robux); } else Snapshot("crew auto roll window wrong", 60); press = w.Tiers[(int)tier]; what = tier.ToString().ToUpperInvariant(); if (press == null || !w.TierLight[(int)tier]) { AutoRollWhy = "its " + what + " tile " + (press == null ? "didn't read" : "is greyed (the cash on hand doesn't cover it)"); CloseAutoRoll(w); return false; } else if (w.Rarity != rarity) press = w.Rarities[rarity]; what = RarityNames[rarity]; if (press == null || !w.RarityLight[rarity]) { AutoRollWhy = "its " + what + " tile " + (press == null ? "didn't read" : "is greyed"); CloseAutoRoll(w); return false; } else if (!w.ThisSlotPicked || w.EverySlotPicked) press = w.ThisSlot; what = "THIS SLOT ONLY"; if (press == null) { AutoRollWhy = "its THIS SLOT ONLY didn't read"; CloseAutoRoll(w); return false; } PressCrew(press, w.Box.Contains(press.CenterX, press.CenterY) && Parse.Key(press.Text).Length >= 4, "not the AUTO ROLL window's " + what); AutoRollWindow again = null; for (int look = 0; look < 3 && again == null; look++) Wait(look == 0 ? 400 : 500); using (var f = Capture()) again = ReadAutoRollWindow(f); if (again == null) { AutoRollWhy = "the AUTO ROLL window went away after " + what; Snapshot("crew auto roll window gone", 60); return false; } if (step == 3) { AutoRollWhy = "the window's picks didn't take (" + w + ")"; Snapshot("crew auto roll picks", 60); CloseAutoRoll(w); return false; } AutoRollWindow last = null; using (var f = Capture()) last = ReadAutoRollWindow(f); if (last == null) { AutoRollWhy = "the AUTO ROLL window went away before START"; return false; } string bad = WindowWrong(last, replaces); if (bad == null && (last.Tier != (int)tier || last.Rarity != rarity || !last.ThisSlotPicked || last.EverySlotPicked)) bad = "its picks read back as " + last; if (bad == null && !last.StartGold) bad = "its START isn't gold"; if (bad != null) { AutoRollWhy = bad; Snapshot("crew auto roll read back", 60); CloseAutoRoll(last); return false; } PressCrew(last.Start, Parse.Key(last.Start.Text) == "START" && last.StartGold && last.Box.Contains(last.Start.CenterX, last.Start.CenterY) && last.ThisSlot != null && last.Start.Box.Y > last.ThisSlot.Box.Bottom, "not the AUTO ROLL window's START"); for (int look = 0; look < 4; look++) Wait(look == 0 ? 900 : 600); using (var f = Capture()) if (look == 0) CheckForPurchasePrompt(Ocr.Read(f, PopupArea(f), PopupScale(f), Prep.None)); if (p.Page && p.AutoRolls) return true; if (p.AutoRollWindow && look == 3) var open = ReadAutoRollWindow(f); if (open != null) CloseAutoRoll(open); AutoRollWhy = "START pressed, but no slot rolls"; Snapshot("crew auto roll not rolling", 60); internal static string WindowWrong(AutoRollWindow w, string replaces) if (w.Robux) return "the AUTO ROLL window offers Robux or a pass (" + w.Words + ")"; if (!w.Usable) return "the AUTO ROLL window didn't read (" + w.Words + ")"; if (replaces == null && w.Name != null) return "the AUTO ROLL window would replace " + w.Name + " - an empty slot was picked"; if (replaces != null && !w.Names(replaces)) return "the AUTO ROLL window names " + (w.Name ?? "nobody") + ", not " + replaces; public bool CloseAutoRoll(AutoRollWindow w) if (w == null || w.X.IsEmpty) { log("Crew: the AUTO ROLL window's X wasn't found - left for you to close"); Snapshot("crew auto roll no x", 60); return false; } PressCrew(new OcrLine { Text = "X", Box = new Rectangle(w.X.X - 2, w.X.Y - 2, 5, 5) }, w.Box.Contains(w.X), "not the AUTO ROLL window's X"); for (int look = 0; look < 3; look++) using (var f = Capture()) if (ReadAutoRollWindow(f) == null) return true; public bool CloseStrayAutoRoll() using (var f = Capture()) w = ReadAutoRollWindow(f); if (w == null) return false; log("Crew: an AUTO ROLL window was open (" + w + ") - closed with its X, nothing pressed in it"); public CrewRow FindRollingRow(out CrewPage page) return FindCrewRow(r => r.AutoRolling, out page, true, false, true); public CrewRow FindRollingRow(out CrewPage page, Func<CrewRow, bool> which) return FindCrewRow(r => r.AutoRolling && which(r), out page, true, false, true); public bool StopAutoRoll(CrewRow row) if (row == null || !row.AutoRolling || row.RollStop == null || row.RollLine == null) return false; int h = Math.Max(8, row.RollLine.Box.Height); PressCrew(row.RollStop, Parse.Key(row.RollStop.Text) == "STOP" && row.RollStopGold && row.RollStop.Box.X > row.RollLine.Box.Right && Math.Abs(row.RollStop.CenterY - row.RollLine.CenterY) <= 3 * h, "not a rolling slot's own STOP"); int y = row.Anchor.CenterY; for (int look = 0; look < 4; look++) Wait(look == 0 ? 800 : 600); using (var f = Capture()) if (look == 0) CheckForPurchasePrompt(Ocr.Read(f, PopupArea(f), PopupScale(f), Prep.None)); if (!p.Rolling.Any(r => r.Top <= y && r.Bottom > y)) return true;