AFK Macro

Idle Mafia Macro/Source/src/AttackHistory.cs

AttackHistory.cs

Reads the fights' attack history · 212 lines · 11 KB

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
namespace IdleMafiaBot
{
sealed partial class Game
{
public sealed class AttackEntry
{
public string Name = "";
public bool Won, Lost;
public int WaitSeconds = -1;
public OcrLine Again;
public bool Limit;
public int Top, Bottom;
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)
{
var p = PageArea(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++)
{
var head = heads[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();
if (e.Name.Length == 0)
{
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);
int s;
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;
}
entries.Add(e);
}
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;
}
return "";
};
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)));
if (above)
{
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();
}
}
return entries;
}
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));
int wait = -1;
foreach (var l in row)
{
var w = HistoryWaitRx.Match(l.Text);
int s;
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();
int sp = t.IndexOf(' ');
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();
if (fam != null)
{
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");
entries.Add(e);
}
return entries;
}
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()
{
Point at;
using (var f = Capture())
{
var p = PageArea(f);
int left = p.Left + (int)(p.Width * 0.6);
at = new Point((left + p.Right) / 2, p.Top + p.Height * 2 / 3);
}
Scroll(at.X, at.Y, 10);
Wait(400);
}
public void AttackAgain(AttackEntry e) { Press(e.Again, 400); }
}
}