using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text.RegularExpressions; namespace IdleMafiaBot { public sealed class BossStage { public int Stage = -1; public string Boss = ""; public int Health = -1, HealthMax = -1; public OcrLine Attack; public bool Cooldown; public bool Cleared; public bool DefeatedToday; public int Damage = -1; public int Highest = -1; public int ResetSeconds = -1; public int Tokens = -1; public int NeedLevel = -1; public OcrLine StagesTab; public OcrLine DefenseLine; public OcrLine Next; public OcrLine Ready; public int ReadyStage = -1; public List> Readies = new List>(); public int NextStage = -1; public int LockedLevel = -1; public OcrLine Locked; public int ListTop = -1; public int ListFirst = -1, ListLast = -1; public System.Drawing.Point ListAt; public string ListSig = ""; public string Said = ""; public int YourStamina = -1, YourStaminaMax = -1; public int YourHealth = -1, YourHealthMax = -1; public override string ToString() { return "stage " + Stage + (Boss.Length > 0 ? ", " + Boss : "") + (HealthMax > 0 ? " (" + Health + " / " + HealthMax + ")" : ""); } } sealed partial class Game { static readonly Regex StageTitleRx = new Regex(@"^\s*STAGE\s*([0-9IlOoS|]{1,3})\s*$", RegexOptions.IgnoreCase); static readonly Regex StageTitleLooseRx = new Regex(@"^\s*[S5$][^\s\d]{2,6}\s*([0-9]{1,3})\s*$", RegexOptions.IgnoreCase); static readonly Regex StageAttackRx = new Regex(@"^\s*ATTACK\s*\(?\s*[1Il|]\s*STAMINA\s*\)?\s*$", RegexOptions.IgnoreCase); static readonly Regex StageHealthRx = new Regex(@"^\s*([0-9Oo][0-9Oo,.]*)\s*[/|Il]?\s*([0-9Oo][0-9Oo,.]*)\s*$"); static readonly Regex StageDamageRx = new Regex(@"([0-9][0-9,.]*)\s*DAMAGE", RegexOptions.IgnoreCase); static readonly Regex StageHighestRx = new Regex(@"HIGHEST\s*STA[GC]E\s*([0-9IlOo]{1,3})", RegexOptions.IgnoreCase); static readonly Regex StageHeadRx = new Regex(@"HIGHEST\s*STA[GC]E", RegexOptions.IgnoreCase); static readonly Regex StageRowRx = new Regex(@"^\s*ST[A4][GC6]E\s*([0-9IlOoS|]{1,3})\s*$", RegexOptions.IgnoreCase); static readonly Regex StagesWordRx = new Regex(@"^\s*ST[A4][GC6]ES\s*$", RegexOptions.IgnoreCase); static readonly Regex StageResetRx = new Regex(@"RESETS\s*IN\s*([0-9]{1,2})\s*:\s*([0-9]{2})\s*:\s*([0-9]{2})", RegexOptions.IgnoreCase); static readonly Regex StageTokensRx = new Regex(@"([0-9OoIl][0-9OoIl,]*)\s*BOSS\s*TOKENS", RegexOptions.IgnoreCase); static readonly Regex YourStaminaRx = new Regex(@"^\s*([0-9Oo]{1,4})\s*[/|Il]?\s*([0-9Oo]{1,4})\s*STAMINA\s*$", RegexOptions.IgnoreCase); static readonly Regex YourHealthRx = new Regex(@"^\s*([0-9Oo][0-9Oo,.]{0,6})\s*[/|Il]?\s*([0-9Oo][0-9Oo,.]{0,6})\s*HEA[LIl1|]TH\s*$", RegexOptions.IgnoreCase); static readonly Regex StageLevelRx = new Regex(@"LEVEL\s*([0-9IlOo]{2,3})\b", RegexOptions.IgnoreCase); static readonly Regex StageRowLevelRx = new Regex(@"^\s*LEVEL\s*([0-9IlOo]{1,3})\s*$", RegexOptions.IgnoreCase); public BossStage ReadBossStage(Frame f) { var area = PageArea(f); int s = PageScale(f); var lines = Ocr.Read(f, area, s, Prep.None); CheckForPurchasePrompt(lines); var st = ReadBossStage(lines, area); if (st == null || st.Stage < 0) { var spot = Rectangle.FromLTRB(area.Left, area.Top + area.Height / 10, area.Left + (int)(area.Width * 0.70), area.Top + area.Height * 35 / 100); var loose = new Dictionary(); var seen = new List(); OcrLine title = null; var tries = new[] { s + 1, Math.Min(4, s + 2), Math.Max(1, s) }.Distinct() .SelectMany(z => new[] { Tuple.Create(z, Prep.RedText), Tuple.Create(z, Prep.None) }).ToList(); foreach (var t in tries) { var red = Ocr.Read(f, spot, t.Item1, t.Item2); title = red.Where(l => StageTitleRx.IsMatch(l.Text)).OrderByDescending(l => l.Box.Height).FirstOrDefault(); int n; if (title != null && int.TryParse(Parse.Digits(StageTitleRx.Match(title.Text).Groups[1].Value), out n)) break; title = null; var l2 = red.Where(l => StageTitleLooseRx.IsMatch(l.Text)).OrderByDescending(l => l.Box.Height).FirstOrDefault(); if (l2 == null || !int.TryParse(StageTitleLooseRx.Match(l2.Text).Groups[1].Value, out n) || n <= 0) continue; if (seen.Contains(n)) { title = new OcrLine { Text = "STAGE " + n, Box = loose[n].Box }; break; } seen.Add(n); loose[n] = l2; } if (title != null) { lines.Add(title); st = ReadBossStage(lines, area); } } if (st != null && st.Locked != null) { var b = st.Locked.Box; int top = st.ListTop >= 0 ? st.ListTop : area.Top; var spot = Rectangle.Intersect(Rectangle.FromLTRB(b.X - 2 * b.Height, top, b.Right + 2 * b.Height, b.Bottom), area); if (spot.Width > 10 && spot.Height > b.Height + 2) foreach (var t in new[] { Tuple.Create(s + 1, Prep.None), Tuple.Create(s + 1, Prep.WhiteText) }) NoteLocked(st, Ocr.Read(f, spot, t.Item1, t.Item2)); } if (st != null && st.HealthMax < 0 && st.DefenseLine != null) { var d = st.DefenseLine; int dh = Math.Max(8, d.Box.Height); var spot = Rectangle.Intersect(Rectangle.FromLTRB(d.CenterX - 13 * dh, d.Box.Y - 7 * dh, d.CenterX + 13 * dh, d.Box.Y - 4 * dh), area); if (spot.Width > 10 && spot.Height > 5) foreach (var t in new[] { Tuple.Create(s + 1, Prep.None), Tuple.Create(s, Prep.WhiteText), Tuple.Create(s + 1, Prep.WhiteText), Tuple.Create(s + 2, Prep.None) }) { var hit = Ocr.Read(f, spot, t.Item1, t.Item2).FirstOrDefault(l => StageHealthRx.IsMatch(l.Text)); if (hit != null && StageHealth(hit.Text, st)) break; } } if (st != null && st.Stage > 0 && st.YourStamina < 0) { int split = area.Left + (int)(area.Width * 0.70), mid = area.Left + (split - area.Left) / 2; var hp = lines.Where(l => l.CenterX < mid && Parse.Key(l.Text).EndsWith("HEAITH") && Regex.IsMatch(l.Text, "[0-9]")).OrderBy(l => l.Box.Y).FirstOrDefault(); if (hp != null) { int hh = Math.Max(8, hp.Box.Height); var spot = Rectangle.Intersect(Rectangle.FromLTRB(hp.Box.X - 4 * hh, hp.Box.Bottom, hp.Box.Right + 4 * hh, hp.Box.Bottom + 3 * hh), area); if (spot.Width > 10 && spot.Height > 5) foreach (var t in new[] { Tuple.Create(s + 1, Prep.WhiteText), Tuple.Create(s + 1, Prep.None), Tuple.Create(s + 2, Prep.WhiteText) }) { var m = Ocr.Read(f, spot, t.Item1, t.Item2).Select(l => YourStaminaRx.Match(l.Text)).FirstOrDefault(x => x.Success); int ya, yb; if (m != null && int.TryParse(Parse.Digits(m.Groups[1].Value), out ya) && int.TryParse(Parse.Digits(m.Groups[2].Value), out yb) && ya <= yb) { st.YourStamina = ya; st.YourStaminaMax = yb; break; } } } } return st; } static bool StageHealth(string text, BossStage st) { var m = StageHealthRx.Match(text ?? ""); int cur, max; if (!m.Success || !int.TryParse(Parse.Digits(m.Groups[1].Value), out cur) || !int.TryParse(Parse.Digits(m.Groups[2].Value), out max) || max <= 0 || cur > max) return false; st.Health = cur; st.HealthMax = max; return true; } internal static BossStage ReadBossStage(List lines, Rectangle area) { var st = new BossStage(); string all = string.Join(" | ", lines.Select(l => l.Text)); bool list = Parse.Has(all, "HIGHEST STAGE") || Parse.Has(all, "RESETS IN"); int split = area.Left + (int)(area.Width * 0.70); var main = lines.Where(l => l.CenterX < split).ToList(); var topRow = lines.Where(l => l.CenterY < area.Top + area.Height / 10).ToList(); var title = main.Where(l => StageTitleRx.IsMatch(l.Text) && l.CenterY > area.Top + area.Height / 10).OrderByDescending(l => l.Box.Height).FirstOrDefault(); int n; if (title != null && int.TryParse(Parse.Digits(StageTitleRx.Match(title.Text).Groups[1].Value), out n)) st.Stage = n; st.StagesTab = topRow.Where(l => Parse.Key(l.Text) == "STAGES").OrderBy(l => l.Box.Y).FirstOrDefault(); if (st.Stage < 0 && !list) return st.StagesTab != null ? st : null; st.Attack = main.FirstOrDefault(l => StageAttackRx.IsMatch(l.Text)); st.Cooldown = main.Any(l => Parse.Has(l.Text, "COOLDOWN") || Parse.Key(l.Text).StartsWith(Parse.Key("ATTACKING"))); st.Cleared = lines.Any(l => Parse.Has(l.Text, "ALREADY CLEARED")); st.DefeatedToday = main.Any(l => Parse.Has(l.Text, "DEFEATED TODA")); var dm = main.Select(l => StageDamageRx.Match(l.Text)).FirstOrDefault(m => m.Success); if (dm != null && int.TryParse(Parse.Digits(dm.Groups[1].Value), out n)) st.Damage = n; var hm = lines.Select(l => StageHighestRx.Match(l.Text)).FirstOrDefault(m => m.Success); if (hm != null && int.TryParse(Parse.Digits(hm.Groups[1].Value), out n)) st.Highest = n; var rm = lines.Select(l => StageResetRx.Match(l.Text)).FirstOrDefault(m => m.Success); if (rm != null) st.ResetSeconds = int.Parse(rm.Groups[1].Value) * 3600 + int.Parse(rm.Groups[2].Value) * 60 + int.Parse(rm.Groups[3].Value); var tm = topRow.Select(l => StageTokensRx.Match(l.Text)).FirstOrDefault(m => m.Success); if (tm != null && int.TryParse(Parse.Digits(tm.Groups[1].Value), out n)) st.Tokens = n; int mid = title != null ? title.CenterX : area.Left + (split - area.Left) / 2; st.DefenseLine = main.Where(l => l.CenterX > mid && Parse.Key(l.Text).StartsWith("DEFENSE")).OrderBy(l => l.Box.Y).FirstOrDefault(); if (st.DefenseLine != null) { var d = st.DefenseLine; int dh = Math.Max(8, d.Box.Height); var name = main.Where(l => l.Box.Bottom <= d.Box.Y && d.Box.Y - l.Box.Bottom < 14 * dh && Math.Abs(l.CenterX - d.CenterX) < 14 * dh && Regex.Matches(l.Text, "[a-z]").Count >= 3 && BossNameLike(l.Text)) .OrderByDescending(l => l.Box.Bottom).FirstOrDefault(); if (name != null) st.Boss = name.Text.Trim(); var health = main.Where(l => Math.Abs(l.CenterX - d.CenterX) < 14 * dh && l.Box.Bottom < d.Box.Y - 2 * dh && (name == null || l.Box.Y > name.Box.Y) && StageHealthRx.IsMatch(l.Text)).OrderBy(l => l.Box.Y).FirstOrDefault(); if (health != null) StageHealth(health.Text, st); } var lv = main.Where(l => !Parse.Has(l.Text, "FAMILY") && (Parse.Has(l.Text, "REQUIRE") || Parse.Has(l.Text, "REACH") || Parse.Has(l.Text, "UNLOCK"))) .Select(l => StageLevelRx.Match(l.Text)).FirstOrDefault(m => m.Success); if (lv != null && int.TryParse(Parse.Digits(lv.Groups[1].Value), out n)) st.NeedLevel = n; st.Next = lines.FirstOrDefault(l => l.CenterX >= split && Parse.Key(l.Text) == "NEXT"); var head = lines.FirstOrDefault(l => l.CenterX >= split && StageHighestRx.IsMatch(l.Text)) ?? lines.FirstOrDefault(l => l.CenterX >= split && StageHeadRx.IsMatch(l.Text)); if (head != null) st.ListAt = new System.Drawing.Point(head.CenterX + head.Box.Width / 4, area.Top + (int)(area.Height * 0.62)); var rows = lines.Where(l => l.CenterX >= split && StageRowRx.IsMatch(l.Text) && !StagesWordRx.IsMatch(l.Text) && (head == null || l.Box.Y > head.Box.Bottom)).ToList(); var nums = rows.Select(l => { int k; return int.TryParse(Parse.Digits(StageRowRx.Match(l.Text).Groups[1].Value), out k) ? k : -1; }).Where(k => k > 0).ToList(); if (nums.Count > 0) { st.ListFirst = nums.Min(); st.ListLast = nums.Max(); } st.Ready = lines.Where(l => l.CenterX >= split && Parse.Key(l.Text) == "READY" && (head == null || l.Box.Y > head.Box.Bottom + 2)) .OrderBy(l => l.Box.Y).FirstOrDefault(); Func rowStage = r => { var row = rows.Where(l => l.CenterX < r.Box.X && l.Box.Y <= r.CenterY && r.CenterY - l.CenterY < 4 * Math.Max(8, r.Box.Height)) .OrderByDescending(l => l.Box.Y).FirstOrDefault(); int k; return row != null && int.TryParse(Parse.Digits(StageRowRx.Match(row.Text).Groups[1].Value), out k) ? k : -1; }; if (st.Ready != null) st.ReadyStage = rowStage(st.Ready); st.Readies = lines.Where(l => l.CenterX >= split && Parse.Key(l.Text) == "READY" && (head == null || l.Box.Y > head.Box.Bottom + 2)) .OrderBy(l => l.Box.Y).Select(l => new KeyValuePair(rowStage(l), l)).ToList(); if (st.Next != null) st.NextStage = rowStage(st.Next); if (head != null) st.ListTop = head.Box.Bottom + 2; st.ListSig = string.Join("|", lines.Where(l => l.CenterX >= split && (head == null || l.Box.Y > head.Box.Bottom)).OrderBy(l => l.Box.Y).Select(l => l.Text.Trim() + "@" + l.Box.Y / 8)); NoteLocked(st, lines.Where(l => l.CenterX >= split)); var ys = main.Where(l => l.CenterX < mid).Select(l => YourStaminaRx.Match(l.Text)).FirstOrDefault(m => m.Success); int ya, yb; if (ys != null && int.TryParse(Parse.Digits(ys.Groups[1].Value), out ya) && int.TryParse(Parse.Digits(ys.Groups[2].Value), out yb) && ya <= yb) { st.YourStamina = ya; st.YourStaminaMax = yb; } var yh = main.Where(l => l.CenterX < mid).Select(l => YourHealthRx.Match(l.Text)).FirstOrDefault(m => m.Success); if (yh != null && int.TryParse(Parse.Digits(yh.Groups[1].Value), out ya) && int.TryParse(Parse.Digits(yh.Groups[2].Value), out yb) && yb > 0 && ya <= yb) { st.YourHealth = ya; st.YourHealthMax = yb; } st.Said = string.Join(" | ", main.Where(l => l.CenterY > area.Top + area.Height / 10).Select(l => l.Text.Trim())); return st; } static void NoteLocked(BossStage st, IEnumerable lines) { int n; var top = lines.Where(l => StageRowLevelRx.IsMatch(l.Text) && l.Box.Y > st.ListTop).OrderBy(l => l.Box.Y).FirstOrDefault(); if (top == null || (st.Locked != null && top.Box.Y >= st.Locked.Box.Y)) return; if (int.TryParse(Parse.Digits(StageRowLevelRx.Match(top.Text).Groups[1].Value), out n) && n > 0) { st.Locked = top; st.LockedLevel = n; } } internal static bool BossNameLike(string text) { text = text ?? ""; return !Regex.IsMatch(text, @"\bLEVEL\b", RegexOptions.IgnoreCase) && Regex.Matches(text, "[A-Z]").Count <= Regex.Matches(text, "[a-z]").Count; } public void StageAttack(BossStage st) { if (st != null && st.Attack != null && StageAttackRx.IsMatch(st.Attack.Text)) Press(st.Attack, 300); } public void StageNext(BossStage st) { if (st != null && st.Next != null && Parse.Key(st.Next.Text) == "NEXT") Press(st.Next, 1200); } public void StageReady(BossStage st) { if (st != null && st.Ready != null && Parse.Key(st.Ready.Text) == "READY") Press(st.Ready, 1200); } public void StageReady(OcrLine ready) { if (ready != null && Parse.Key(ready.Text) == "READY") Press(ready, 1200); } public void StagesListScroll(BossStage st, int notches) { if (st != null && !st.ListAt.IsEmpty) Scroll(st.ListAt.X, st.ListAt.Y, notches); } public void StagesTab(BossStage st) { if (st != null && st.StagesTab != null) Press(st.StagesTab, 900); } } }