using System.Collections.Generic;using System.Text.RegularExpressions; sealed partial class Game public bool OnlyNavigate; static readonly HashSet<string> CloseKeys = Keys("DONE", "OK", "OKAY", "CONTINUE", "CLOSE", "NICE", "AWESOME", "COOL", "GOT IT"); static bool IsMenuEntry(string text) var one = new List<OcrLine> { new OcrLine { Text = text ?? "" } }; return View.TabLabels.Values.Any(l => View.Label(one, l) != null) || Parse.Key(text ?? "") == "EVENTS"; static bool IsSubTab(string text) string k = Parse.Key(text); return FamilyTabKeys.Contains(k) || k == "AUDITIOG" || k == "BROWSEFAMIIIES" || IsBackToMyFamily(new OcrLine { Text = text ?? "" }); internal bool NavigationPress(string text, int x, int y) bool known = v != null && v.Problem == null; if (IsMenuEntry(text) && known && x < v.Menu.Right) return true; if (IsSubTab(text) && known && y < v.Page.Top + v.Page.Height / 4) return true; return CloseKeys.Contains(Parse.Key(text)); readonly HashSet<string> refusedInCheck = new HashSet<string>(); void RefuseInCheck(string text) if (refusedInCheck.Add(Parse.Key(text))) log("The check didn't press \"" + (text ?? "").Trim() + "\": it only opens tabs and reads"); throw new NeverPressException(text, "the check only opens tabs"); public sealed class MenuRead { public List<Tab> Seen = new List<Tab>(); public bool Whole; public int Unread; } public MenuRead ReadWholeMenu() var seen = new HashSet<Tab>(); using (var f = Capture()) if (HideChat(f)) ForgetView(); Dictionary<Tab, int> last = null; for (int step = 0; step < 16; step++) Dictionary<Tab, int> now; using (var f = Capture()) if (v.Problem != null || !v.ReadMenu(f)) return null; int unread = MenuInSight(f, v); foreach (var kv in v.Tabs) if (!IsLockedEntry(f, v, kv.Value)) seen.Add(kv.Key); r.Unread = Math.Max(r.Unread, unread); bool moved = last == null || !View.SameMenu(last, now); if (!moved) { atTop = true; step--; last = null; continue; } Scroll(v.Menu.X + v.Menu.Width / 2, v.Menu.Y + v.Menu.Height / 2, 5); if (!moved) { r.Whole = true; break; } Scroll(v.Menu.X + v.Menu.Width / 2, v.Menu.Y + v.Menu.Height / 2, -5); r.Seen = seen.OrderBy(t => (int)t).ToList(); internal static int MenuInSight(Frame f, View v) for (int i = 0; i < 3 && v.UnreadButtons(f) > 0 && v.ReadAgain(f, v.Strip); i++) { } return v.UnreadButtons(f); public int ReadBankFee(Frame f, out string line) var lines = Ocr.Read(f, PageArea(f), PageScale(f), Prep.None); var card = BankCard(lines); if (card == null) return -1; var words = lines.Where(l => l.Box.Bottom > card.Box.Y).OrderBy(l => l.Box.Y).ThenBy(l => l.Box.X).Select(l => l.Text.Trim()).ToList(); int from = words.FindIndex(w => Parse.Has(w, "FEE") || w.Contains("%")); if (from >= 0) line = string.Join(" ", words.Skip(from)); if (line.Length > 220) line = line.Substring(0, 220); return FeeIn(string.Join(" ", words)); internal static int FeeIn(string text) if (string.IsNullOrEmpty(text)) return -1; foreach (Match m in Regex.Matches(text, @"(?<![0-9A-Za-z.,$])([0-9][0-9Oo]?)\s*%")) if (int.TryParse(Parse.Digits(m.Groups[1].Value), out n) && n > 0) return n; if (NearIn(Parse.Key(text), "NODEPOSITFEENOBANKFEESPASS", 2)) return 0; if (Regex.IsMatch(text, @"\(\s*no\s+fee\s*\)", RegexOptions.IgnoreCase)) return 0; static bool NearIn(string key, string want, int off) if (key.Contains(want)) return true; for (int start = 0; start + want.Length - off <= key.Length; start++) for (int len = want.Length - off; len <= want.Length + off && start + len <= key.Length; len++) if (View.Distance(key.Substring(start, len), want) <= off) return true; public sealed class JobsLook { public List<int> Cities = new List<int>(), Folded = new List<int>(); public int Cash = -1; } public JobsLook ReadJobsList(List<JobInfo> catalog) for (int page = 0; page < 40; page++) using (var f = Capture()) sig = JobsPage(f, catalog, r); ScrollPage(0.54, -ListNotches(6)); public string JobsPage(Frame f, List<JobInfo> catalog, JobsLook r) var lines = Ocr.Read(f, JobsArea(f), 2, Prep.None); foreach (var h in ReadCityHeaders(f, lines)) if (!r.Cities.Contains(h.Index)) r.Cities.Add(h.Index); if (h.Collapsed && !r.Folded.Contains(h.Index)) r.Folded.Add(h.Index); if (r.Cash < 0 && CardsFromLines(f, lines, catalog, false).Count > 0) r.Cash = lines.Any(l => Regex.IsMatch(l.Text, @"\$\s?[0-9OoIlS]") && (Parse.Has(l.Text, "XP") || Parse.Has(l.Text, "ITEM"))) ? 1 : 0; return string.Join(",", lines.Select(l => l.Text + "@" + l.Box.Y)); public sealed class StageLook { public int Stage = -1, Highest = -1, NeedLevel = -1, ResetSeconds = -1; public bool Ready, Cleared; } public StageLook ReadStageLook(int level) for (int look = 0; look < 3; look++) using (var f = Capture()) r = StageLookIn(f, level); internal StageLook StageLookIn(Frame f, int level) var lines = Ocr.Read(f, area, PageScale(f), Prep.None); var st = ReadBossStage(lines, area); if (st == null || (st.Stage < 0 && st.Highest < 0)) return null; var r = new StageLook { Stage = st.Stage, Highest = st.Highest, NeedLevel = st.NeedLevel, ResetSeconds = st.ResetSeconds, Ready = st.Attack != null, Cleared = st.Cleared || st.DefeatedToday }; int split = area.Left + (int)(area.Width * 0.70); bool fromList = r.NeedLevel < 0; foreach (var l in lines.Where(l => fromList && l.CenterX >= split)) var m = StageLevelRx.Match(l.Text); if (m.Success && int.TryParse(Parse.Digits(m.Groups[1].Value), out n) && n > level && (r.NeedLevel < 0 || n < r.NeedLevel)) r.NeedLevel = n; public bool OffersOfflineOps(Frame f) return Ocr.Read(f, PageArea(f), PageScale(f), Prep.None).Any(l => Parse.Key(l.Text).StartsWith("OFFIINEOPERATION"));