using System.Collections.Generic;using System.Globalization;using System.Text.RegularExpressions; sealed partial class Game static readonly Regex RespectChip = new Regex(@"Respect\s*[:.]?\s*([0-9OoIlS][0-9OoIlS,. ]{0,9})\s*$", RegexOptions.IgnoreCase); internal static int FightRespectIn(List<OcrLine> lines, Rectangle area) foreach (var l in lines.Where(x => x.CenterX > area.Left + area.Width / 2 && x.CenterY < area.Top + area.Height / 3)) var m = RespectChip.Match(l.Text); if (m.Success && int.TryParse(Parse.Digits(m.Groups[1].Value), out v) && v >= 0) return v; public int FightRespect(Frame f, Prep prep) return FightRespectIn(Ocr.Read(f, area, PageScale(f), prep), area); internal OcrLine CollectionWithdrawBeside(Frame f, OcrLine deposit) int h = Math.Max(8, deposit.Box.Height); foreach (var prep in new[] { Prep.None, Prep.DarkText, Prep.WhiteText }) var hit = Ocr.Read(f, area, PageScale(f), prep).FirstOrDefault(l => Parse.Key(l.Text) == "WITHDRAWAII" && l.Box.X >= deposit.Box.Right && l.Box.X - deposit.Box.Right < Math.Max(12 * h, 5 * deposit.Box.Width) && Math.Abs(l.CenterY - deposit.CenterY) < 2 * h); if (hit != null) return hit; public sealed class WithdrawResult { public CollectionState Before, After; public bool Out; } internal static OcrLine CollectionWithdrawConfirm(List<OcrLine> lines) if (!lines.Any(l => Parse.Has(l.Text, "RETURN ALL") || Parse.Has(l.Text, "COLLECTION TO"))) return null; var cancel = lines.FirstOrDefault(l => Parse.Key(l.Text) == "CANCEI"); if (cancel == null) return null; int h = Math.Max(8, cancel.Box.Height); return lines.FirstOrDefault(l => Parse.Key(l.Text) == "WITHDRAWAII" && l.Box.Right < cancel.Box.X && Math.Abs(l.CenterY - cancel.CenterY) <= h); public WithdrawResult WithdrawCollection() if (!OpenTab(Tab.Collection)) return null; var r = new WithdrawResult(); for (int look = 0; look < 3 && button == null; look++) if (look > 0) Wait(1200); using (var f = Capture()) var deposit = FindCollectionDeposit(f, out r.Before); if (deposit != null) button = CollectionWithdrawBeside(f, deposit); if (r.Before != null && r.Before.Deposited == 0 && r.Before.Attack <= 0) log("Collection: nothing in it to withdraw (" + r.Before + ")"); log("Collection: WITHDRAW ALL not found - nothing pressed (" + collectionSeen + ")"); Snapshot("collection withdraw not found", 60); CheckForPurchaseWindow(); if (OnlyNavigate) RefuseInCheck(button.Text); Click(button.CenterX, button.CenterY, 1200); Snapshot("collection withdraw pressed", 0, true); for (int look = 0; look < 4; look++) using (var f = Capture()) var popup = Ocr.Read(f, PopupArea(f), PopupScale(f), Prep.None); CheckForPurchasePrompt(popup); var yes = CollectionWithdrawConfirm(popup); if (yes != null) { Snapshot("collection withdraw window", 0, true); Click(yes.CenterX, yes.CenterY, 1500); continue; } FindCollectionDeposit(f, out r.After); if (r.After != null && (r.After.Attack >= 0 && r.Before.Attack > r.After.Attack || r.After.Deposited >= 0 && r.Before.Deposited > r.After.Deposited)) { r.Out = true; break; } internal string LowestFlaggedCity(Frame f) var flagged = FlaggedCities(f); return Districts.Cities.FirstOrDefault(flagged.Contains); public bool LowestFamilyDistrict(out string city, out int number, out string name) city = name = null; number = -1; if (!OpenTab(Tab.Territory)) return false; OcrLine cityBox, districtBox; cityBox = districtBox = null; for (int look = 0; look < 6 && cityBox == null; look++) if (look > 0) Wait(1000); using (var f = Capture()) if (!TerritoryBar(f, out cityBox, out districtBox)) cityBox = null; if (cityBox == null) { log("Territory: the page's city and district drop-downs didn't read"); Snapshot("respect cycle territory not read", 60); return false; } Click(cityBox.CenterX, cityBox.CenterY, 900); using (var f = Capture()) city = LowestFlaggedCity(f); Click(cityBox.CenterX, cityBox.CenterY, 600); if (city == null) return false; if (!SelectCity(city, out cityBox, out districtBox) || !MyFamilyOn()) return false; Click(districtBox.CenterX, districtBox.CenterY, 900); int h = Math.Max(8, cityBox.Box.Height), bottom = districtBox.Box.Bottom, left = districtBox.Box.X; var rows = new List<Tuple<int, OcrLine, string>>(); using (var f = Capture()) foreach (var l in Ocr.Read(f, PageArea(f), PageScale(f) + 1, Prep.None).Where(l => l.Box.Y > bottom && l.Box.X < left + 8 * h)) if (ListedDistrict(l.Text, city, out no, out nm)) rows.Add(Tuple.Create(no, l, nm)); Click(districtBox.CenterX, districtBox.CenterY, 600); var first = ListColumn(rows).FirstOrDefault(); if (first == null) return false; number = first.Item1; name = first.Item3; bool Lowering { get { return S.RespectState.Length > 0; } } internal static bool RespectUnfinished(string state) { return state == "0|0" || state.EndsWith("|s"); } int lastRespect = -1, respectOnce = -1, depositTries; DateTime respectWait = DateTime.MinValue, stationAgain = DateTime.MinValue; using (var f = game.Capture()) { a = game.FightRespect(f, Prep.None); b = game.FightRespect(f, Prep.WhiteText); } int v = a >= 0 && a == b ? a : -1; int one = a >= 0 && b < 0 ? a : b >= 0 && a < 0 ? b : -1; if (one >= 0 && (one == respectOnce || (lastRespect >= 0 && Math.Abs(one - lastRespect) <= 300))) v = one; if (v >= 0) lastRespect = v; if (DateTime.UtcNow < respectWait) return false; if (r < 0) { SayOnce("respect unread " + DateTime.UtcNow.ToString("yyyyMMddHH"), "Respect cycle: couldn't read your respect on the Fight page"); return false; } SayOnce("respect read " + DateTime.UtcNow.ToString("yyyyMMddHH"), "Respect cycle: respect is " + r.ToString("N0", CultureInfo.InvariantCulture) + ", limits " + S.RespectLow.ToString("N0", CultureInfo.InvariantCulture) + " to " + S.RespectHighNow.ToString("N0", CultureInfo.InvariantCulture) + (Lowering ? " - lowering" : "")); if (!Lowering) return r >= S.RespectHighNow && StartLowering(r); if (r > S.RespectLow && RespectUnfinished(S.RespectState)) return StartLowering(r); if (r > S.RespectLow && S.RespectState.Split('|').Length > 1 && S.RespectState.Split('|')[1] != "1" && DateTime.UtcNow >= stationAgain) stationAgain = DateTime.UtcNow.AddMinutes(15); Log("Respect cycle: the crew isn't garrisoned yet - trying again"); if (StationCrewLowest(false)) S.RespectState = S.RespectState.Split('|')[0] + "|1"; if (r > S.RespectLow) return false; RespectBack("respect is " + r.ToString("N0", CultureInfo.InvariantCulture)); bool StartLowering(int respect) var p = S.RespectState.Split('|'); bool taken = p[0] == "1", stationed = p.Length > 1 && p[1] == "1"; Log("Respect cycle: respect is " + respect.ToString("N0", CultureInfo.InvariantCulture) + (again ? " - finishing the start that was cut off, " : " - ") + "garrisoning the crew and taking out the collection, down to " + S.RespectLow.ToString("N0", CultureInfo.InvariantCulture)); S.RespectState = "0|0|s"; if (S.Bank && Can(Tab.Bank)) DoBank(); stationed = StationCrewLowest(again); S.RespectState = "0|" + (stationed ? "1" : "0") + "|s"; var w = game.WithdrawCollection(); taken = w != null || again; if (w != null && !w.Out) { Log("Respect cycle: WITHDRAW ALL pressed, but the collection's totals didn't drop (" + w.Before + ")"); game.Snapshot("collection withdraw no change", 60); } S.RespectState = (taken ? "1" : "0") + "|" + (stationed ? "1" : "0"); if (!taken && !stationed) respectWait = DateTime.UtcNow.AddMinutes(30); Log("Respect cycle: couldn't garrison or take out the collection - tried again in 30 minutes"); internal static List<Tuple<string, int, string>> GarrisonPicks(string list, List<string> unknown = null) var got = new List<Tuple<string, int, string>>(); foreach (var e in NeverAttackEntries(list)) if (Game.MatchDistrict(e, out c, out no, out n)) { if (!got.Any(t => t.Item3 == n)) got.Add(Tuple.Create(c, no, n)); } else if (unknown != null) unknown.Add(e); if (got.Count == 5) break; bool StationCrewLowest(bool again) string city = null, name = null; var picks = GarrisonPicks(S.GarrisonPicks); if (game.OpenDistrict(p.Item1, p.Item2, p.Item3)) { city = p.Item1; no = p.Item2; name = p.Item3; opened = true; break; } if (!game.LastDistrictGone) { game.Snapshot("respect cycle district not opened", 60); return false; } SayOnce("garrison pick gone " + p.Item3 + " " + DateTime.UtcNow.ToString("yyyyMMddHH"), "Respect cycle: " + p.Item3 + " isn't your family's any more - the next pick"); SayOnce("garrison picks gone " + DateTime.UtcNow.ToString("yyyyMMddHH"), "Respect cycle: none of your picked districts is your family's any more - the crew stays with you until you pick a new one"); if (!game.LowestFamilyDistrict(out city, out no, out name)) Log("Respect cycle: no district of your family found - the crew stays with you"); if (!game.OpenDistrict(city, no, name)) { game.Snapshot("respect cycle district not opened", 60); return false; } int n = game.StationAllHere(); Log("Respect cycle: " + (n > 0 ? "stationed " + n + " henchmen at " + name + " (" + city + ")" : "no henchmen stationed at " + name)); SnapshotOnce("respect cycle crew stationed"); void RespectBack(string why) Log("Respect cycle: " + why + " - putting the collection back and recalling the crew"); var p = S.RespectState.Split('|'); bool taken = p[0] == "1", stationed = p.Length > 1 && p[1] == "1"; var d = game.DepositCollection(); bool seen = d != null && (d.Toast != null || (d.After != null && d.Before != null && d.After.Attack > d.Before.Attack)); if (d != null) Log("Respect cycle: DEPOSIT ALL pressed" + (d.Toast != null ? " - \"" + d.Toast.Trim() + "\"" : seen ? "" : ", nothing showed it took")); if (seen || (d != null && ++depositTries >= 3)) { taken = false; depositTries = 0; } int recalled = game.OpenTab(Tab.Crew) ? game.RecallEveryone() : -1; if (recalled > 0) Log("Respect cycle: recalled " + recalled + " henchmen"); stationed = recalled < 0; S.RespectState = taken || stationed ? (taken ? "1" : "0") + "|" + (stationed ? "1" : "0") : ""; if (Lowering) { respectWait = DateTime.UtcNow.AddMinutes(2); Log("Respect cycle: not everything went back - tried again in 2 minutes"); } else { nextCrew = nextEquip = DateTime.UtcNow; Log("Respect cycle: done, back to normal fights"); }