using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text.RegularExpressions; namespace IdleMafiaBot { sealed class DistrictWindow { public string Title = "", TitleAgain = "", HeldBy = ""; public bool Is(string name) { string k = Parse.Key(name); foreach (var t in new[] { Parse.Key(Title), Parse.Key(TitleAgain) }) { if (t.Length == 0) continue; if (View.Distance(t, k) <= 2) return true; if (t.Length >= 7 && t.Length * 10 >= k.Length * 7 && k.Contains(t)) return true; if (t.Length >= 5 && t.Length * 2 >= k.Length && k.EndsWith(t)) return true; } return false; } public bool IsLastWord(string name) { string last = Parse.Key(Regex.Match(name ?? "", @"\S+\s*$").Value); return last.Length >= 3 && (Parse.Key(Title) == last || Parse.Key(TitleAgain) == last); } public int Garrison = -1, GarrisonMax = -1, Available = -1; public OcrLine StationCrew, StationAll; public Point Close = Point.Empty; } sealed partial class Game { static readonly Regex ToastWhat = new Regex(@"^(.*?)\s*[—–\-:]*\s*(defen[cs]es\s*fa[lI1|]{2}en|attacked|garrison\s*[lI1|]ost)", RegexOptions.IgnoreCase); static readonly Regex DistrictRow = new Regex(@"^\s*([0-9OoDIl|]{3})\s+(.+?)\s*/\s*(.+?)\s*$"); internal static bool ListedDistrict(string text, string city, out int number, out string name) { number = -1; name = null; var m = Regex.Match(text ?? "", @"^\s*(\S{2,4})\s+(.+?)\s*/\s*(.+?)\s*$"); if (!m.Success) return CityDistrict(text, city, out number, out name); if (View.Distance(Parse.Key(m.Groups[3].Value), Parse.Key(city)) > 2) return false; string c2; if (MatchDistrict(m.Groups[2].Value, out c2, out number, out name) && c2 == city) return true; string digits = Regex.Replace(m.Groups[1].Value, "[OoD]", "0"); digits = Regex.Replace(digits, "[Il|]", "1"); if (!Regex.IsMatch(digits, @"^\d{3}$")) return false; number = int.Parse(digits); name = m.Groups[2].Value.Trim(); return true; } internal static bool CityDistrict(string text, string city, out int number, out string name) { number = -1; name = null; string[] names; if (city == null || !Districts.ByCity.TryGetValue(city, out names)) return false; string k = Parse.Key(Regex.Replace((text ?? "").Replace("æ", "as").Replace("Æ", "As"), @"^\W*\d{1,3}\W*\s+(?=[A-Za-z])", "")); if (k.Length < 4) return false; int best = int.MaxValue, at = -1; bool tie = false; for (int i = 0; i < names.Length; i++) { int d = View.Distance(Parse.Key(names[i]), k); if (d < best) { best = d; at = i; tie = false; } else if (d == best) tie = true; } if (at < 0 || tie || best > Math.Min(2, k.Length / 5)) return false; number = at + 1; name = names[at]; return true; } internal static int DistrictRowNumber(string text) { var m = DistrictRow.Match(text ?? ""); return m.Success ? int.Parse(Parse.Digits(m.Groups[1].Value)) : -1; } static readonly Regex GarrisonRx =new Regex(@"GARRISON\s*([0-9OoIl]{1,3})\s*/\s*([0-9OoIl]{1,3})", RegexOptions.IgnoreCase); static readonly Regex GarrisonFreeRx = new Regex(@"([0-9]{1,3})\s*available", RegexOptions.IgnoreCase); internal string TerritoryToastDistrict(Frame f, out bool garrisonLost, out Point x) { string by; return TerritoryToastDistrict(f, out garrisonLost, out x, out by); } internal string TerritoryToastDistrict(Frame f, out bool garrisonLost, out Point x, out string by) { string text; return TerritoryToastDistrict(f, out garrisonLost, out x, out by, out text); } internal string TerritoryToastDistrict(Frame f, out bool garrisonLost, out Point x, out string by, out string text) { garrisonLost = false; by = ""; x = TerritoryToastX(f, out text); if (x.IsEmpty || text == null) return null; var m = ToastWhat.Match(text.Trim()); if (!m.Success) return null; garrisonLost = Parse.Key(m.Groups[2].Value).StartsWith("GARRISON") || Parse.Key(m.Groups[2].Value).StartsWith("DEFEN"); var from = Regex.Match(text, @"\bfrom\s+(.+?)\s*$", RegexOptions.IgnoreCase); if (from.Success) by = Regex.Replace(from.Groups[1].Value.Trim(), @"\s+[xX×]$", ""); return m.Groups[1].Value.Trim(); } internal static bool MatchDistrict(string read, out string city, out int number, out string name) { city = name = null; number = -1; read = Regex.Replace(read ?? "", @"^\W*\d{1,3}[.):]?\s+(?=[A-Za-z])", ""); read = Regex.Replace(read, @"^[xX×]\s+(?=[A-Za-z])", ""); if (MatchDistrictWhole(read, out city, out number, out name)) return true; var words = read.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); for (int n = words.Length - 1; n >= 2; n--) if (MatchDistrictWhole(string.Join(" ", words.Take(n)), out city, out number, out name)) return true; words = words.Where(w => !Regex.IsMatch(w, @"^[xX×]$")).ToArray(); for (int start = 1; start <= 2 && words.Length - start >= 2; start++) for (int n = words.Length - start; n >= 2; n--) if (MatchDistrictWhole(string.Join(" ", words.Skip(start).Take(n)), out city, out number, out name)) return true; return false; } static bool MatchDistrictWhole(string read, out string city, out int number, out string name) { city = name = null; number = -1; string k = Parse.Key((read ?? "").Replace("æ", "as").Replace("Æ", "As")); if (k.Length < 4) return false; var hits = new List>(); foreach (var kv in Districts.ByCity) for (int i = 0; i < kv.Value.Length; i++) { int d = View.Distance(Parse.Key(kv.Value[i]), k); if (d <= Math.Min(2, k.Length / 6)) hits.Add(Tuple.Create(d, kv.Key, i + 1, kv.Value[i])); } if (hits.Count == 0) return false; int best = hits.Min(h => h.Item1); var top = hits.Where(h => h.Item1 == best).ToList(); if (top.Count != 1) return false; city = top[0].Item2; number = top[0].Item3; name = top[0].Item4; return true; } internal static string CleanHolder(string read) { var words = (read ?? "").Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); if (words.Count > 1 && Regex.IsMatch(words[words.Count - 1], @"^(TURF|FORT|STRONGHOLD|CAPITAL|HQ)$", RegexOptions.IgnoreCase)) words.RemoveAt(words.Count - 1); return string.Join(" ", words).Trim(); } internal static bool HeldByUs(string heldBy, string ours) { string h = Parse.Key(heldBy), o = Parse.Key(ours); if (o.Length == 0 || h.Length <= 2) return true; if (View.Distance(h, o) <= 2 || h.Contains(o)) return true; if (h.Length >= o.Length && o.Length >= 3 && View.Distance(h.Substring(0, o.Length), o) <= 1) return true; return o.Length >= 3 && h.Length > o.Length && Enumerable.Range(0, h.Length - o.Length + 1).Any(i => View.Distance(h.Substring(i, o.Length), o) <= 1) && h.Length <= o.Length + 4; } internal bool TerritoryBar(Frame f, out OcrLine cityBox, out OcrLine districtBox) { cityBox = districtBox = null; var area = PageArea(f); var lines = Ocr.Read(f, area, PageScale(f), Prep.None); var search = lines.FirstOrDefault(l => Parse.Has(l.Text, "Search districts")); if (search == null) return TerritoryBarNew(lines, area, out cityBox, out districtBox) || TerritoryBarTyped(lines, area, out cityBox, out districtBox); int h = Math.Max(8, search.Box.Height); var row = lines.Where(l => Math.Abs(l.CenterY - search.CenterY) < 2 * h).ToList(); cityBox = row.Where(l => l.Box.Right < search.Box.X && Districts.Cities.Any(c => Parse.Key(l.Text).StartsWith(Parse.Key(c)))) .OrderByDescending(l => l.Box.X).FirstOrDefault(); string[] buttons = { "MYFAMIIY", "AIIBONUSES", "SETTINGS" }; districtBox = row.Where(l => l.Box.X > search.Box.Right && l.Box.X - search.Box.Right < Math.Max(12 * h, area.Width / 7) && !buttons.Contains(Parse.Key(l.Text))) .OrderBy(l => l.Box.X).FirstOrDefault(); if (districtBox != null) districtBox = Stacked(row, districtBox, h); if (cityBox != null) cityBox = Stacked(row, cityBox, h); return cityBox != null && districtBox != null; } static bool TerritoryBarNew(List lines, Rectangle area, out OcrLine cityBox, out OcrLine districtBox) { cityBox = districtBox = null; var mine = lines.FirstOrDefault(l => Parse.Key(l.Text) == "MYFAMIIY"); if (mine == null) return false; int h = Math.Max(8, mine.Box.Height); var row = lines.Where(l => Math.Abs(l.CenterY - mine.CenterY) < 2 * h).ToList(); var first = row.Where(l => l.Box.Right < mine.Box.X).OrderBy(l => l.Box.X).FirstOrDefault(); if (first == null || Districts.Cities.Any(c => Parse.Key(first.Text).StartsWith(Parse.Key(c)))) return false; cityBox = lines.Where(l => l.CenterY > area.Y + area.Height / 2 && Districts.Cities.Any(c => View.Distance(Parse.Key(l.Text), Parse.Key(c)) <= 1) && lines.Any(t => t != l && TierWord(t.Text) && t.Box.Y >= l.CenterY && t.Box.Y - l.Box.Bottom < 2 * h && Math.Abs(t.CenterX - l.CenterX) < 3 * h)) .OrderByDescending(l => l.CenterY).FirstOrDefault(); if (cityBox == null) cityBox = lines.Where(l => l.CenterY > area.Y + area.Height * 3 / 4 && l.CenterX < area.X + area.Width / 2 && Districts.Cities.Any(c => View.Distance(Parse.Key(l.Text), Parse.Key(c)) <= 1)) .OrderByDescending(l => l.CenterY).FirstOrDefault(); if (cityBox == null) return false; districtBox = Stacked(row, first, h); return true; } internal static bool TierWord(string text) { return Regex.IsMatch(Parse.Key(text), "^TIE[RQP]"); } static bool TerritoryBarTyped(List lines, Rectangle area, out OcrLine cityBox, out OcrLine districtBox) { cityBox = districtBox = null; var mine = lines.FirstOrDefault(l => Parse.Key(l.Text) == "MYFAMIIY"); if (mine == null) return false; int h = Math.Max(8, mine.Box.Height); var row = lines.Where(l => Math.Abs(l.CenterY - mine.CenterY) < 2 * h).ToList(); cityBox = row.Where(l => l.Box.Right < mine.Box.X && Districts.Cities.Any(c => Parse.Key(l.Text).StartsWith(Parse.Key(c)))) .OrderBy(l => l.Box.X).FirstOrDefault(); if (cityBox == null) return false; var city = cityBox; var last = row.Where(l => l.Box.X > city.Box.Right + 4 * h && l.Box.Right < mine.Box.X).OrderByDescending(l => l.Box.X).FirstOrDefault(); if (last == null) return false; districtBox = Stacked(row, last, h); cityBox = Stacked(row, cityBox, h); return true; } static OcrLine Stacked(List row, OcrLine first, int h) { var stack = row.Where(l => Math.Abs(l.Box.X - first.Box.X) < 2 * h && !(l.Box.Right < first.Box.X || l.Box.X > first.Box.Right)).OrderBy(l => l.Box.Y).ToList(); if (stack.Count < 2) return first; var box = stack[0].Box; foreach (var l in stack) box = Rectangle.Union(box, l.Box); return new OcrLine { Text = string.Join(" ", stack.Select(l => l.Text.Trim())), Box = box }; } bool SelectCity(string city, out OcrLine cityBox, out OcrLine districtBox) { using (var f = Capture()) if (!TerritoryBar(f, out cityBox, out districtBox)) { log("Territory: the page's city and district drop-downs didn't read"); return false; } if (Parse.Key(cityBox.Text).StartsWith(Parse.Key(city))) return true; int h = Math.Max(8, cityBox.Box.Height); var box = cityBox; Click(box.CenterX, box.CenterY, 900); OcrLine row; using (var f = Capture()) row = Ocr.Read(f, PageArea(f), PageScale(f), Prep.None) .FirstOrDefault(l => (l.Box.Y > box.Box.Bottom && Math.Abs(l.Box.X - box.Box.X) < 6 * h || l.Box.Bottom < box.Box.Y) && Parse.Key(l.Text).StartsWith(Parse.Key(city)) && Parse.Has(l.Text, "TIER")); if (row == null) { log("Territory: " + city + " not in the city list"); Click(box.CenterX, box.CenterY, 600); return false; } Click(row.CenterX, row.CenterY, 2000); using (var f = Capture()) return TerritoryBar(f, out cityBox, out districtBox) && Parse.Key(cityBox.Text).StartsWith(Parse.Key(city)); } bool MyFamilyOn() { for (int tries = 0; tries < 3; tries++) { OcrLine mine; using (var f = Capture()) { var lines = Ocr.Read(f, PageArea(f), PageScale(f), Prep.None); if (lines.Any(l => Parse.Has(l.Text, "MATCHING"))) return true; mine = lines.FirstOrDefault(l => Parse.Key(l.Text) == "MYFAMIIY"); if (mine == null) break; if (MatchingUnder(f, mine)) return true; } if (tries == 2) break; Click(mine.CenterX, mine.CenterY, 1500); } log("Territory: the MY FAMILY filter didn't turn on - no district opened"); return false; } bool MatchingUnder(Frame f, OcrLine mine) { int h = Math.Max(8, mine.Box.Height); var area = PageArea(f); var band = Rectangle.Intersect(Rectangle.FromLTRB(area.Left, mine.Box.Bottom, mine.Box.Right, mine.Box.Bottom + 6 * h), area); if (band.Width < 10 || band.Height < 5) return false; foreach (var p in new[] { Prep.None, Prep.Contrast }) if (Ocr.Read(f, band, PageScale(f) + 1, p).Any(l => Parse.Has(l.Text, "MATCHING") || Parse.Key(l.Text).Contains("CIEARFIITERS"))) return true; return false; } internal List FlaggedCities(Frame f) { var got = new List(); var lines = Ocr.Read(f, PageArea(f), PageScale(f), Prep.None); var search = lines.FirstOrDefault(l => Parse.Has(l.Text, "Search districts")); foreach (var row in lines.Where(l => Parse.Has(l.Text, "TIER"))) { string city = Districts.Cities.FirstOrDefault(c => Parse.Key(row.Text).StartsWith(Parse.Key(c))); if (city == null || got.Contains(city)) continue; if (RowFlagged(f, row.Box, search != null ? search.Box.X : row.Box.X + 45 * Math.Max(8, row.Box.Height))) got.Add(city); } return got; } static bool FlagGold(Color c) { return c.R > 170 && c.G > 130 && c.G < 200 && c.B < 90 && c.R - c.B > 110 || EventOrange(c); } internal static bool RowFlagged(Frame f, Rectangle row, int limit) { int h = Math.Max(8, row.Height); int y0 = Math.Max(0, row.Y + row.Height / 2 - h), y1 = Math.Min(f.Height - 1, row.Y + row.Height / 2 + h); int gold = 0; for (int x = row.Right + h; x < Math.Min(f.Width, limit); x++) { int col = 0; for (int y = y0; y <= y1; y++) if (FlagGold(f.Pixel(x, y))) col++; if (col * 10 >= (y1 - y0 + 1) * 9) break; gold += col; } return gold * 3 >= h * h; } internal static List> ListColumn(List> rows) { if (rows.Count == 0) return rows; int tol = Math.Max(6, rows.Select(t => t.Item2.Box.Height).OrderBy(x => x).ElementAt(rows.Count / 2)); var byY = rows.OrderBy(t => t.Item2.Box.Y).ToList(); int left = rows.Select(t => t.Item2.Box.X).OrderBy(x => x).ElementAt(rows.Count / 2); var col = byY.Where(t => Math.Abs(t.Item2.Box.X - left) <= tol).ToList(); var kept = new List>(); foreach (var t in col) { if (kept.Count > 0 && t.Item2.Box.Y - kept[kept.Count - 1].Item2.Box.Y > 4 * Math.Max(tol, t.Item2.Box.Height)) break; kept.Add(t); } return kept; } public bool OpenDistrict(string city, int number, string name) { LastDistrictGone = false; if (!OpenTab(Tab.Territory)) return false; OcrLine cityBox, districtBox; if (!SelectCity(city, out cityBox, out districtBox)) return false; if (!MyFamilyOn()) return false; int h = Math.Max(8, cityBox.Box.Height); Click(districtBox.CenterX, districtBox.CenterY, 900); OcrLine pick = null; for (int look = 0; look < 25 && pick == null; look++) { var rows = new List>(); using (var f = Capture()) foreach (var l in Ocr.Read(f, PageArea(f), PageScale(f) + 1, Prep.None).Where(l => l.Box.Y > districtBox.Box.Bottom && l.Box.X < districtBox.Box.X + 8 * h)) { int no; string nm; if (ListedDistrict(l.Text, city, out no, out nm)) rows.Add(Tuple.Create(no, l, nm)); } rows = ListColumn(rows); Func, int> off = t => View.Distance(Parse.Key(t.Item3), Parse.Key(name)); pick = rows.Where(t => off(t) <= 2 || (t.Item1 == number && off(t) <= 4)).OrderBy(off).Select(t => t.Item2).FirstOrDefault(); if (pick != null || rows.Count == 0) break; int notches = number < rows.Min(t => t.Item1) ? 3 : -3; Scroll(rows[0].Item2.CenterX, rows[rows.Count / 2].Item2.CenterY, notches); Wait(400); } if (pick == null) { Click(districtBox.CenterX, districtBox.CenterY, 600); bool filtered; using (var f = Capture()) filtered = Ocr.Read(f, PageArea(f), PageScale(f), Prep.None).Any(l => Parse.Has(l.Text, "MATCHING")); LastDistrictGone = filtered; log("Territory: " + name + " not found in " + city + "'s district list" + (filtered ? " of your family's districts" : "")); return false; } Click(pick.CenterX, pick.CenterY, 2500); OcrLine nowCity, nowDistrict; bool took; using (var f = Capture()) took = TerritoryBar(f, out nowCity, out nowDistrict) && View.Distance(Parse.Key(NoNumber(Regex.Replace(nowDistrict.Text, @"\s*[Vv]$", ""))), Parse.Key(name)) <= 2; if (!took) { log("Territory: picking " + name + " in the district list didn't take"); CloseStrayDistrict(); return false; } OcrLine label = null; for (int look = 0; look < 6 && label == null; look++) { if (look > 0) Wait(900); using (var f = Capture()) { var area = PageArea(f); int s = PageScale(f); var middle = Rectangle.Intersect(Rectangle.FromLTRB(area.Left + area.Width / 4, districtBox.Box.Bottom + 2 * h, area.Right - area.Width / 4, area.Bottom - area.Height / 6), area); var tries = new[] { Tuple.Create(area, s, Prep.None), Tuple.Create(area, s, Prep.WhiteText) }.ToList(); if (middle.Width > 20 && middle.Height > 10) tries.AddRange(new[] { Tuple.Create(middle, s + 1, Prep.WhiteText), Tuple.Create(middle, s + 1, Prep.WhiteBright), Tuple.Create(middle, s + 2, Prep.None), Tuple.Create(middle, s + 2, Prep.WhiteBright) }); if (look >= 3) tries.AddRange(new[] { Tuple.Create(area, s + 1, Prep.WhiteText), Tuple.Create(area, s + 1, Prep.Contrast), Tuple.Create(area, s + 2, Prep.WhiteBright) }); foreach (var t in tries) { label = Ocr.Read(f, t.Item1, t.Item2, t.Item3) .Where(l => l.Box.Y > districtBox.Box.Bottom + 2 * h && View.Distance(Parse.Key(NoNumber(l.Text)), Parse.Key(name)) <= 2) .OrderBy(l => View.Distance(Parse.Key(NoNumber(l.Text)), Parse.Key(name))).FirstOrDefault(); if (label != null) break; } } } if (label == null) { log("Territory: " + name + " not seen on the map"); CloseStrayDistrict(); return false; } Click(label.CenterX, label.CenterY + 2 * label.Box.Height, 2000); DistrictWindow w; using (var f = Capture()) w = ReadDistrictWindow(f); if (w == null || !(w.Is(name) || w.IsLastWord(name))) { log("Territory: " + name + "'s window didn't open" + (w != null ? " (" + w.Title + " did)" : "")); if (w != null) CloseDistrict(w); return false; } return true; } static string NoNumber(string text) { return Regex.Replace(text ?? "", @"^\W*\d{1,3}\W*\s+(?=[A-Za-z])", ""); } public bool LastDistrictGone; void CloseStrayDistrict() { DistrictWindow w; using (var f = Capture()) w = ReadDistrictWindow(f); if (w != null) CloseDistrict(w); } public DistrictWindow ReadDistrictWindow(Frame f) { var lines = Ocr.Read(f, BigPopupArea(f), PopupScale(f), Prep.None); var held = lines.FirstOrDefault(l => Parse.Has(l.Text, "HELD BY")); var gar = lines.Select(l => Tuple.Create(l, GarrisonRx.Match(l.Text))).FirstOrDefault(t => t.Item2.Success); if (held == null && gar == null) return null; var w = new DistrictWindow(); int h = Math.Max(8, (held ?? gar.Item1).Box.Height); if (held != null) { int reach = Math.Max(6 * h, (int)(60 * Math.Min(f.Height / 1009.0, f.Width / 1790.0))); var title = lines.Where(l => l.Box.Bottom < held.Box.Y && held.Box.Y - l.Box.Bottom < reach && Regex.IsMatch(l.Text, "[A-Za-z]{3}")).OrderByDescending(l => l.Box.Height).FirstOrDefault(); if (title != null) { var row = lines.Where(l => Math.Abs(l.CenterY - title.CenterY) < title.Box.Height / 2 && l.Box.Height * 3 >= title.Box.Height * 2 && Regex.IsMatch(l.Text, "[A-Za-z]{2}")).OrderBy(l => l.Box.X).ToList(); if (row.Count > 1) { var u = row.Aggregate(title.Box, (r, l) => Rectangle.Union(r, l.Box)); title = new OcrLine { Text = string.Join(" ", row.Select(l => l.Text.Trim())), Box = u }; } w.Title = title.Text.Trim(); w.Close = CloseMark(f, title); int th = title.Box.Height, more = title.Box.X <= BigPopupArea(f).X + 2 ? 2 * th : th / 2; var box = Rectangle.FromLTRB(title.Box.X - more, title.Box.Y - th / 4 - 2, title.Box.Right + th / 2, title.Box.Bottom + th / 4 + 2); w.TitleAgain = Ocr.ReadText(f, Rectangle.Intersect(box, new Rectangle(0, 0, f.Width, f.Height)), PopupScale(f) + 1, Prep.Contrast).Trim(); w.TitleAgain = Regex.Replace(w.TitleAgain, @"^\S\s+(?=\S{2})", ""); } string after = Regex.Replace(held.Text, @"^.*HELD\s*BY\s*", "", RegexOptions.IgnoreCase).Trim(); if (after.Length < 2) after = string.Join(" ", lines.Where(l => Math.Abs(l.CenterY - held.CenterY) < h && l.Box.X > held.Box.Right).OrderBy(l => l.Box.X).Select(l => l.Text.Trim())); w.HeldBy = CleanHolder(after); } if (gar != null) { int a, b; if (int.TryParse(Parse.Digits(gar.Item2.Groups[1].Value), out a)) w.Garrison = a; if (int.TryParse(Parse.Digits(gar.Item2.Groups[2].Value), out b)) w.GarrisonMax = b; } w.StationCrew = lines.FirstOrDefault(l => Parse.Key(l.Text) == "STATIONCREW"); w.StationAll = lines.FirstOrDefault(l => Parse.Key(l.Text).StartsWith("STATIONAII")); var av = lines.Select(l => GarrisonFreeRx.Match(l.Text)).FirstOrDefault(m => m.Success); if (av != null) w.Available = int.Parse(av.Groups[1].Value); return w; } static Point CloseMark(Frame f, OcrLine title) { int th = Math.Max(8, title.Box.Height), x0 = title.Box.Right + 4 * th, y0 = Math.Max(0, title.CenterY - th * 3 / 4), y1 = Math.Min(f.Height, title.CenterY + th * 3 / 4); Func light = c => c.R >= 150 && c.G >= 150 && c.B >= 150 && Math.Max(c.R, Math.Max(c.G, c.B)) - Math.Min(c.R, Math.Min(c.G, c.B)) <= 30 || c.G >= 140 && c.R >= 0.8 * c.G && c.R < c.G && c.B >= 0.7 * c.G && c.B < c.G; int minX = -1, maxX = -1; for (int x = Math.Max(x0, f.Width / 2); x < f.Width; x++) { bool any = false; for (int y = y0; y < y1 && !any; y++) any = light(f.Pixel(x, y)); if (any) { if (minX < 0) minX = x; maxX = x; } else if (minX >= 0 && x - maxX > th / 2) break; } if (maxX < 0 || maxX - minX > 4 * th || maxX - minX < th / 3) return Point.Empty; int minY = int.MaxValue, maxY = -1; for (int y = y0; y < y1; y++) for (int x = minX; x <= maxX; x++) if (light(f.Pixel(x, y))) { minY = Math.Min(minY, y); maxY = Math.Max(maxY, y); } return new Point((minX + maxX) / 2, (minY + maxY) / 2); } public int StationAllHere() { DistrictWindow w; w = null; for (int look = 0; look < 3 && (w == null || w.StationAll == null && w.StationCrew == null); look++) { if (look > 0) Wait(600); using (var f = Capture()) w = ReadDistrictWindow(f) ?? w; } if (w == null) return -1; if (w.StationAll == null) { if (w.StationCrew == null) return -1; Click(w.StationCrew.CenterX, w.StationCrew.CenterY, 1200); for (int look = 0; look < 4; look++) { if (look > 0) Wait(700); DistrictWindow again; using (var f = Capture()) again = ReadDistrictWindow(f); if (again != null) w = again; if (w.StationAll != null || w.Available >= 0) break; } if (w.StationAll == null && w.Available < 0 && w.StationCrew != null) return -2; } if (w.Available == 0) return 0; if (w.StationAll == null) return w.Available > 0 ? -1 : 0; int before = w.Garrison; NotDisconnected(); if (OnlyNavigate) RefuseInCheck(w.StationAll.Text); Click(w.StationAll.CenterX, w.StationAll.CenterY, 1500); using (var f = Capture()) w = ReadDistrictWindow(f); if (w != null && before >= 0 && w.Garrison > before) return w.Garrison - before; return w != null && w.Available == 0 ? Math.Max(1, w.Garrison - Math.Max(0, before)) : 0; } public void CloseDistrict(DistrictWindow w = null) { for (int go = 0; go < 3; go++) { if (w == null) using (var f = Capture()) w = ReadDistrictWindow(f); if (w == null) return; if (w.Close.IsEmpty) { if (go > 0 || !CloseTerritoryToasts()) return; w = null; continue; } bool covered; using (var f = Capture()) covered = ToastOver(f, w.Close); if (covered && go == 0 && CloseTerritoryToasts()) { w = null; continue; } Click(w.Close.X, w.Close.Y, 900); w = null; } } } }