using System.Collections.Generic;using System.Text.RegularExpressions; sealed partial class Game public sealed class AttackEntry public int WaitSeconds = -1; public override string ToString() return Name + (Won ? " won" : Lost ? " lost" : " ?") + (WaitSeconds >= 0 ? " wait " + WaitSeconds + "s" : "") + (Again != null ? " AGAIN" : "") + (Limit ? " LIMIT" : ""); static readonly Regex AttackedRx = new Regex(@"^\W{0,3}You\s*attac\s?ked\s*(.*)$", RegexOptions.IgnoreCase); static readonly Regex AttackedYouRx = new Regex(@"attac\s?ked\s*you\s*$", RegexOptions.IgnoreCase); static readonly Regex HistoryWaitRx = new Regex(@"WA\s?[I1l]T\s*\(?\s*([0-9OoIlS]{1,3})\s*s", RegexOptions.IgnoreCase); public List<AttackEntry> ReadAttackHistory(Frame f) int left = p.Left + (int)(p.Width * 0.6); var area = new Rectangle(left, p.Top, p.Right - left, p.Height); var entries = new List<AttackEntry>(); var lines = Ocr.Read(f, area, Math.Min(5, PageScale(f) + 1), Prep.None); var heads = lines.Where(l => AttackedRx.IsMatch(l.Text) || AttackedYouRx.IsMatch(l.Text)).OrderBy(l => l.Box.Y).ToList(); for (int i = 0; i < heads.Count; i++) if (!AttackedRx.IsMatch(head.Text)) continue; int h = Math.Max(8, head.Box.Height); var e = new AttackEntry { Top = head.Box.Y - h, Bottom = i + 1 < heads.Count ? heads[i + 1].Box.Y - h / 2 : head.Box.Bottom + 6 * h }; e.Name = AttackedRx.Match(head.Text).Groups[1].Value.Trim(); var name = lines.Where(l => l != head && l.Box.X > head.Box.Right && l.Box.X - head.Box.Right < 3 * h && Math.Abs(l.CenterY - head.CenterY) <= h / 2) .OrderBy(l => l.Box.X).FirstOrDefault(); if (name != null) e.Name = name.Text.Trim(); foreach (var l in lines.Where(l => l != head && l.CenterY > head.Box.Y && l.CenterY < e.Bottom)) string k = Parse.Key(l.Text); if (k.Contains("WONTHEATTACK") || k.StartsWith("YOUWON")) e.Won = true; else if (k.Contains("FOUGHTYOUOFF")) e.Lost = true; var w = HistoryWaitRx.Match(l.Text); if (w.Success && int.TryParse(Parse.Digits(w.Groups[1].Value), out s)) e.WaitSeconds = s; if (AgainButton(l.Text)) e.Again = l; if (DailyLimit(l.Text)) e.Limit = true; Func<Rectangle, string> nameAt = r => foreach (int sc in new[] { Math.Min(5, PageScale(f) + 2), 5 }.Distinct()) foreach (int grow in new[] { 0, -2, 2 }) var s = Rectangle.Intersect(Rectangle.FromLTRB(r.Left, r.Top - grow, r.Right, r.Bottom + grow), area); if (s.Width < 4 || s.Height < 4) continue; string t = string.Join(" ", Ocr.Read(f, s, sc, Prep.Contrast).Select(l => l.Text)).Trim(); if (Parse.Key(t).Length >= 3) return t; if (heads.Count == 0) entries = ReadCompactHistory(lines, nameAt); if (entries.Count == 0 && heads.Count == 0 && lines.Any(l => Parse.Key(l.Text).Contains("ATTACKHISTORY") || Parse.Key(l.Text) == "ATTACKBACK")) foreach (var t in new[] { Tuple.Create(Math.Min(5, PageScale(f) + 1), Prep.Contrast), Tuple.Create(PageScale(f), Prep.None) }) entries = ReadCompactHistory(Ocr.Read(f, area, t.Item1, t.Item2), nameAt); if (entries.Count > 0) break; if (heads.Count == 0 && entries.Count > 0) int firstTop = entries[0].Top; bool above = lines.Any(l => l.Box.Bottom < firstTop && (AgainButton(l.Text) || HistoryWaitRx.IsMatch(l.Text) || DailyLimit(l.Text))); var more = ReadCompactHistory(Ocr.Read(f, area, Math.Min(5, PageScale(f) + 1), Prep.Contrast), nameAt) .Where(x => x.Top < firstTop - (entries[0].Bottom - entries[0].Top) / 2).ToList(); if (more.Count > 0) entries = more.Concat(entries).ToList(); static readonly Regex CompactResultRx = new Regex(@"^\W{0,3}([^\sA-Za-z0-9]*([A-Za-z0-9][^\sA-Za-z0-9]*){0,2}\s+){0,2}\W{0,2}(V[I1l]C\S{0,2}?[O0]RY\b|DEFEAT\b|V\S{1,4}?(\.{2,}|…)|DEF\S{0,2}?(\.{2,}|…))", RegexOptions.IgnoreCase); static bool AgainButton(string text) return Regex.IsMatch(Parse.Key(text), "^ATTACKA[GC6]A[A-Z0-9]?N$"); static readonly Regex AgoRx = new Regex(@"(\bago|\bnow)\s*$", RegexOptions.IgnoreCase); static List<AttackEntry> ReadCompactHistory(List<OcrLine> lines, Func<Rectangle, string> nameAt = null) var entries = new List<AttackEntry>(); var results = lines.Where(l => CompactResultRx.IsMatch(l.Text)).OrderBy(l => l.Box.Y).ToList(); foreach (var r in results) int h = Math.Max(8, r.Box.Height); var row = lines.Where(l => l != r && l.Box.X > r.Box.X + 8 * h && Math.Abs(l.CenterY - r.CenterY) <= h).ToList(); bool back = row.Any(l => Parse.Key(l.Text) == "ATTACKBACK"); var again = row.FirstOrDefault(l => AgainButton(l.Text)); var w = HistoryWaitRx.Match(l.Text); if (w.Success && int.TryParse(Parse.Digits(w.Groups[1].Value.Replace(" ", "")), out s)) wait = s; bool limit = row.Any(l => DailyLimit(l.Text)); if (back || (again == null && wait < 0 && !limit)) continue; var name = lines.Where(l => l != r && !AgoRx.IsMatch(l.Text) && Math.Abs(l.Box.X - r.Box.X) <= 2 * h && l.CenterY < r.Box.Y && r.Box.Y - l.CenterY <= 2 * h) .OrderByDescending(l => l.Box.Y).FirstOrDefault(); if (name != null && nameAt != null) string t = name.Text.Trim(); if (sp > 0 && t.Substring(0, sp).Count(char.IsLetterOrDigit) < 2 && Parse.Key(t.Substring(sp)).Length >= 3) int famX = name.Box.X + name.Box.Width * (sp + 1) / t.Length; string text = famX - h > r.Box.X ? nameAt(Rectangle.FromLTRB(r.Box.X - h / 2, name.CenterY - h * 7 / 10, famX - h, name.CenterY + h * 7 / 10)) : ""; if (Parse.Key(text).Length >= 3) name = new OcrLine { Text = text, Box = Rectangle.FromLTRB(r.Box.X, name.Box.Y, famX - h, name.Box.Bottom) }; if (name == null && nameAt != null) var fam = lines.Where(l => l != r && !AgoRx.IsMatch(l.Text) && l.Box.X > r.Box.X + 2 * h && l.Box.X < r.Box.X + 20 * h && l.CenterY < r.Box.Y && r.Box.Y - l.CenterY <= 2 * h) .OrderBy(l => l.Box.X).FirstOrDefault(); string text = nameAt(Rectangle.FromLTRB(r.Box.X - h / 2, fam.CenterY - h * 7 / 10, fam.Box.X - h, fam.CenterY + h * 7 / 10)); if (Parse.Key(text).Length >= 3) name = new OcrLine { Text = text, Box = Rectangle.FromLTRB(r.Box.X, fam.Box.Y, fam.Box.X - h, fam.Box.Bottom) }; if (name == null) continue; var e = new AttackEntry { Name = name.Text.Trim(),Again = again, WaitSeconds = wait, Limit = limit, Top = name.Box.Y - h / 2, Bottom = r.Box.Bottom + h }; string k = Parse.Key(CompactResultRx.Match(r.Text).Groups[3].Value).Replace('0', 'O'); e.Won = k.StartsWith("V"); e.Lost = k.StartsWith("DEF"); public static bool SamePlayer(string a, string b) string ka = Parse.Key(a), kb = Parse.Key(b); if (ka.Length < 3 || kb.Length < 3) return false; if (ka == kb) return true; if (Math.Min(ka.Length, kb.Length) >= 5 && (ka.StartsWith(kb) || kb.StartsWith(ka))) return true; int n = Math.Min(ka.Length, kb.Length); return n >= 6 && View.Distance(ka, kb) <= (n >= 10 ? 2 : 1); public static bool CloseName(string a, string b) if (SamePlayer(a, b)) return true; Func<string, string> noBadge = s => Regex.Replace((s ?? "").Trim(), @"\s+[O0o]$", ""); string ka = Parse.Key(noBadge(a)), kb = Parse.Key(noBadge(b)); string la = Regex.Replace(ka, "[^A-Z0-9]", ""), lb = Regex.Replace(kb, "[^A-Z0-9]", ""); if (la.Length >= 4 && lb.Length >= 4 && View.Distance(la, lb) <= 1) return true; if (ka.Length < 4 || kb.Length < 4) return false; if (ka == kb || ka.Substring(0, 4) == kb.Substring(0, 4)) return true; if (ka.Substring(0, 2) == kb.Substring(0, 2) && View.Distance(ka, kb) <= 2) return true; return Math.Min(ka.Length, kb.Length) >= 6 && View.Distance(ka, kb) <= 3; public void AttackHistoryToTop() using (var f = Capture()) int left = p.Left + (int)(p.Width * 0.6); at = new Point((left + p.Right) / 2, p.Top + p.Height * 2 / 3); public void AttackAgain(AttackEntry e) { Press(e.Again, 400); }