using System.Collections.Generic;using System.Text.RegularExpressions; sealed class AnimalsWindow public OcrLine Title, Owned, Shop, Training; public List<OcrLine> Lines = new List<OcrLine>(); public string Name = "", Kind = ""; public int Level = -1, Hunger = -1, Food = -1, Premium = -1; public OcrLine FeedPremium; public override string ToString() return (Name.Length > 0 ? Name : "the animal") + (Kind.Length > 0 ? " (" + Kind + ")" : "") + (Level > 0 ? " level " + Level : "") + ", hunger " + (Hunger >= 0 ? Hunger + "%" : "?") + ", food " + (Food >= 0 ? Food.ToString() : "?"); public bool Crate, SoldOut; public double Price = -1; public OcrLine Buy, Open; public bool BuyLit, OpenLit; public override string ToString() { return Name + " " + (SoldOut ? "sold out" : Price > 0 ? SafehouseInfo.Money(Price) : "price not read") + (Left.Length > 0 ? " (" + Left.ToLowerInvariant() + ")" : ""); } sealed partial class Game static readonly string AnimalsKey = Parse.Key("ANIMALS"); static int SmallNum(string s) return Parse.FirstInt(Regex.Replace(s ?? "", "[OoIl]", m => m.Value == "O" || m.Value == "o" ? "0" : "1")); static readonly string OwnedKey = Parse.Key("OWNED"), ShopTabKey = Parse.Key("ANIMAL SHOP"), TrainingTabKey = Parse.Key("ANIMAL TRAINING"); public AnimalsWindow ReadAnimalsWindow(Frame f) var area = BigPopupArea(f); int wide = f.Width * 46 / 100; if (wide > area.Width / 2) area = new Rectangle(f.Width / 2 - wide, area.Y, 2 * wide, area.Height); int scale = PopupScale(f); var lines = Ocr.Read(f, area, scale, Prep.None); var titles = lines.Where(l => Parse.Key(l.Text) == AnimalsKey).OrderBy(l => l.Box.Y).ToList(); var tabs = lines.Where(l => { string k = Parse.Key(l.Text); return k == OwnedKey || k == ShopTabKey || k == TrainingTabKey; }).OrderBy(l => l.Box.X).ToList(); var one = tabs.FirstOrDefault(t => tabs.Any(o => Parse.Key(o.Text) != Parse.Key(t.Text) && Math.Abs(o.CenterY - t.CenterY) <= t.Box.Height)); titles.Add(new OcrLine { Text = "ANIMALS", Box = new Rectangle(one.Box.X, one.Box.Y - 5 * one.Box.Height, 10 * one.Box.Height, 3 * one.Box.Height) }); foreach (var title in titles) int h = Math.Max(8, title.Box.Height); Func<string, OcrLine> tab = key => lines.FirstOrDefault(l => Parse.Key(l.Text) == key && l.Box.Y > title.Box.Bottom && l.Box.Y < title.Box.Bottom + 4 * h); var w = new AnimalsWindow { Title = title, Owned = tab(OwnedKey), Shop = tab(ShopTabKey), Training = tab(TrainingTabKey), Lines = lines, Area = area }; if ((w.Owned != null ? 1 : 0) + (w.Shop != null ? 1 : 0) + (w.Training != null ? 1 : 0) < 2) continue; w.H = Math.Max(8, (w.Owned ?? w.Shop ?? w.Training).Box.Height); var x = lines.Where(l => Regex.IsMatch(l.Text.Trim(), "^[xX×]$") && l.Box.X > title.Box.Right && Math.Abs(l.CenterY - title.CenterY) <= 1.5 * h) .OrderByDescending(l => l.Box.X).FirstOrDefault(); w.Close = x != null ? new Point(x.CenterX, x.CenterY) : SkillWindowX(f, title, area); public bool OpenAnimals() using (var f = Capture()) if (ReadAnimalsWindow(f) != null) return true; if (!OpenTab(Tab.Safehouse)) return false; for (int attempt = 0; attempt < 2; attempt++) using (var f = Capture()) button = FindAnimalsButton(f); if (button.IsEmpty) { Wait(1200); continue; } Press("ANIMALS", button.X, button.Y, 1200); for (int look = 0; look < 4; look++) using (var f = Capture()) if (ReadAnimalsWindow(f) != null) return true; NoteFailure(pressed ? "the Animals window didn't open" : "the ANIMALS button didn't read"); Snapshot("animals not opened", 60); Point FindAnimalsButton(Frame f) int left = v != null && v.Problem == null ? v.PagePoint(0, 0).X : f.Width * 15 / 100; var page = new Rectangle(left, 0, f.Width - left, f.Height); int top = f.Height * 35 / 100; var line = Ocr.Read(f, page, 2, Prep.None).FirstOrDefault(l => Parse.Key(l.Text).EndsWith(AnimalsKey) && Parse.Key(l.Text).Length <= AnimalsKey.Length + 4 && l.CenterY > top); if (line != null) return new Point(line.CenterX, line.CenterY); var lower = Rectangle.FromLTRB(left, top, f.Width, f.Height * 80 / 100); foreach (var t in new[] { Tuple.Create(2, Prep.None), Tuple.Create(1, Prep.None), Tuple.Create(1, Prep.Contrast), Tuple.Create(3, Prep.None), Tuple.Create(2, Prep.Contrast) }) var word = Ocr.ReadWords(f, lower, t.Item1, t.Item2).FirstOrDefault(w => { string k = Parse.Key(w.Text); return k.Length >= 6 && k.Length <= AnimalsKey.Length + 2 && (k.EndsWith(AnimalsKey) || View.Distance(k, AnimalsKey) <= 1); }); if (word != null) return new Point(word.Box.X + word.Box.Width / 2, word.Box.Y + word.Box.Height / 2); public bool CloseAnimals() for (int attempt = 0; attempt < 6; attempt++) using (var f = Capture()) w = ReadAnimalsWindow(f); if (w == null) return true; if (!CloseTerritoryToasts()) Wait(1500); Click(w.Close.X, w.Close.Y, 900); using (var f = Capture()) if (ReadAnimalsWindow(f) == null) return true; Snapshot("animals not closed", 60); public bool AnimalsTab(string which) string key = which == "shop" ? ShopTabKey : which == "training" ? TrainingTabKey : OwnedKey; for (int attempt = 0; attempt < 3; attempt++) using (var f = Capture()) w = ReadAnimalsWindow(f); if (w == null) return false; var tab = key == ShopTabKey ? w.Shop : key == TrainingTabKey ? w.Training : w.Owned; if (tab == null || Parse.Key(tab.Text) != key) { Wait(800); continue; } if (OnlyNavigate) RefuseInCheck(tab.Text); Click(tab.CenterX, tab.CenterY, 1100); using (var f = Capture()) var now = ReadAnimalsWindow(f); if (now == null) return false; bool shows = key == ShopTabKey ? now.Lines.Any(l => Parse.Has(l.Text, "ANIMAL CRATES") || Parse.Has(l.Text, "ANIMAL FOOD")) : key == TrainingTabKey ? now.Lines.Any(l => Parse.Has(l.Text, "TRAINING SESSION") || Parse.Has(l.Text, "ENERGY COST")) : now.Lines.Any(l => Parse.Has(l.Text, "HUNGER")); public AnimalCard ReadAnimalCard(Frame f, AnimalsWindow w) var c = new AnimalCard(); c.None = lines.Any(l => Parse.Has(l.Text, "NO ACTIVE ANIMAL") || Parse.Has(l.Text, "NO ANIMALS YET")); var hunger = lines.FirstOrDefault(l => Parse.Key(l.Text).StartsWith(Parse.Key("HUNGER")) && !Parse.Has(l.Text, "HUNGER USED") && l.Box.Y > w.Title.Box.Bottom + 4 * w.H); if (hunger != null) c.None = false; if (c.None && !lines.Any(l => Parse.Has(l.Text, "NO ACTIVE ANIMAL")) && lines.Any(l => { string k = Parse.Key(l.Text); return k.StartsWith(Parse.Key("FEED")) || k == Parse.Key("RENAME") || k == Parse.Key("REMOVE") || k.Contains(Parse.Key("PREMIUM FOOD")) || k.Contains(Parse.Key("ACTIVE ANIMAL")); })) var kind = lines.FirstOrDefault(l => Regex.IsMatch(l.Text, @"\b(COMMON|UNCOMMON|RARE|EPIC|LEGENDARY|MYTHIC|SECRET|FORBIDDEN|EXOTIC)\b", RegexOptions.IgnoreCase) && hunger != null && l.Box.Bottom < hunger.Box.Y && l.Box.Y > hunger.Box.Y - 12 * w.H); c.Kind = kind.Text.Trim(); var name = lines.Where(l => l.Box.Bottom <= kind.Box.Y + 2 && l.Box.Y > kind.Box.Y - 4 * w.H && Math.Abs(l.CenterX - kind.CenterX) < kind.Box.Width) .OrderByDescending(l => l.Box.Y).FirstOrDefault(); if (name != null) c.Name = name.Text.Trim().Trim('"', '\'', '“', '”'); var level = lines.FirstOrDefault(l => Regex.IsMatch(l.Text.Trim(), @"^LEVEL\s*[0-9OIl]{1,2}$", RegexOptions.IgnoreCase)); if (level != null) c.Level = SmallNum(Regex.Replace(level.Text, @"(?i)level", "")); c.Hunger = HungerPercent(f, w, hunger, kind); foreach (var l in lines.Where(l => l.Box.Y > hunger.Box.Bottom && l.Box.Y < hunger.Box.Bottom + 5 * w.H)) var t = l.Text.ToUpperInvariant(); var premium = Regex.Match(t, @"PREMIUM\s*F[O0]{2}D\s*([0-9OIl]{1,4})"); if (premium.Success) c.Premium = SmallNum(premium.Groups[1].Value); var food = Regex.Match(Regex.Replace(t, @"PREMIUM\s*F[O0]{2}D\s*[0-9OIl]{0,4}", ""), @"F[O0]{2}D\s*([0-9OIl]{1,4})"); if (food.Success) c.Food = SmallNum(food.Groups[1].Value); c.Feed = lines.Where(l => Parse.Key(l.Text).StartsWith(Parse.Key("FEED")) && l.Box.Y > hunger.Box.Bottom && l.Box.Y < hunger.Box.Bottom + 8 * w.H && !Parse.Key(l.Text).Contains(Parse.Key("50"))).OrderBy(l => l.Box.X).FirstOrDefault(); c.FeedPremium = lines.Where(l => Parse.Key(l.Text).StartsWith(Parse.Key("FEED")) && l.Box.X > c.Feed.Box.Right && Math.Abs(l.CenterY - c.Feed.CenterY) <= c.Feed.Box.Height) .OrderBy(l => l.Box.X).FirstOrDefault(); int HungerPercent(Frame f, AnimalsWindow w, OcrLine hunger, OcrLine centre) int h = Math.Max(8, hunger.Box.Height); int right = centre != null ? 2 * centre.CenterX - hunger.Box.X : hunger.Box.X + 22 * h; Func<OcrLine, int> pct = l => var m = Regex.Match(l.Text.Trim(), @"^([0-9OIl]{1,3})\s*(%|o/o|0/0)$", RegexOptions.IgnoreCase); return m.Success ? SmallNum(m.Groups[1].Value) : -1; var own = w.Lines.FirstOrDefault(l => l != hunger && Math.Abs(l.CenterY - hunger.CenterY) <= 0.7 * h && l.Box.X > hunger.Box.Right && pct(l) >= 0); if (own != null && pct(own) <= 100) return pct(own); var row = Rectangle.FromLTRB(hunger.Box.Right + h, hunger.Box.Y - h / 2, Math.Min(f.Width, right + h), hunger.Box.Bottom + h / 2); foreach (int s in new[] { 3, 4 }) foreach (var prep in new[] { Prep.None, Prep.DarkText }) var hit = Ocr.Read(f, row, s, prep).Select(pct).FirstOrDefault(v => v >= 0 && v <= 100); if (hit > 0 || Ocr.Read(f, row, s, prep).Any(l => pct(l) == 0)) return hit; int y0 = hunger.Box.Bottom + h / 3, y1 = hunger.Box.Bottom + 2 * h; for (int y = y0; y <= y1 && y < f.Height; y++) int green = 0, x = hunger.Box.X; for (; x < right && x < f.Width; x++) if (c.G >= 120 && c.G - c.R >= 40 && c.G - c.B >= 40 || EventOrange(c) || c.R >= 150 && c.R - c.G >= 60 && c.R - c.B >= 60) green++; else if (green > 0) break; if (green >= h) return Math.Max(0, Math.Min(100, (int)Math.Round(100.0 * green / Math.Max(1, right - hunger.Box.X)))); return w.Lines.Any(l => Parse.Has(l.Text, "HUNGRY")) ? 0 : -1; public void PressFeed(AnimalCard c) if (c.Feed == null || !Parse.Key(c.Feed.Text).StartsWith(Parse.Key("FEED")) || Parse.Key(c.Feed.Text).Contains(Parse.Key("50"))) throw new NeverPressException(c.Feed == null ? "" : c.Feed.Text, "not the animal's FEED 10%"); public void PressFeedPremium(AnimalCard c) if (c.FeedPremium == null || c.Premium <= 0 || !Parse.Key(c.FeedPremium.Text).StartsWith(Parse.Key("FEED"))) throw new NeverPressException(c.FeedPremium == null ? "" : c.FeedPremium.Text, "not the animal's FEED 50% with premium food owned"); Press(c.FeedPremium, 1200); public bool ReadTraining(AnimalsWindow w, out OcrLine train, out int energy, out int hungerUsed, out string rest) train = null; energy = hungerUsed = -1; rest = ""; var session = lines.FirstOrDefault(l => Parse.Has(l.Text, "TRAINING SESSION")); Func<string, int> under = label => var lab = lines.FirstOrDefault(l => Parse.Has(l.Text, label)); if (lab == null) return -1; int lh = Math.Max(8, lab.Box.Height); var v = lines.Where(l => l != lab && l.Box.Y > lab.Box.Bottom && l.Box.Y < lab.Box.Bottom + 6 * lh && Math.Abs(l.CenterX - lab.CenterX) < 3 * lh).OrderBy(l => l.Box.Y).FirstOrDefault(); if (v == null) return -1; var m = Regex.Match(v.Text.Trim(), @"^\+?([0-9OIl]{1,3})\s*(%|o/o|0/0)?$", RegexOptions.IgnoreCase); return m.Success ? SmallNum(m.Groups[1].Value) : -1; energy = under("ENERGY COST"); hungerUsed = under("HUNGER USED"); bool ready = lines.Any(l => Parse.Has(l.Text, "READY TO TRAIN")); var timer = lines.FirstOrDefault(l => l.Box.Y > (session != null ? session.Box.Bottom : w.Title.Box.Bottom) && Regex.IsMatch(l.Text, @"\b\d{1,2}\s?[:.]\s?\d{2}\b") && !Parse.Has(l.Text, "ENERGY")); if (timer != null) rest = timer.Text.Trim(); train = lines.FirstOrDefault(l => IsTrainButton(l.Text) && l.Box.Y > session.Box.Bottom && l.Box.X > session.Box.X - 2 * w.H); static bool IsTrainButton(string text) string k = Parse.Key(text), train = Parse.Key("TRAIN"); return k == train || k.EndsWith(train) && k.Length <= train.Length + 2; public OcrLine FindAnimalTrain(Frame f, AnimalsWindow w) var ready = w.Lines.FirstOrDefault(l => Parse.Has(l.Text, "READY TO TRAIN")); if (ready == null) return null; var box = Rectangle.FromLTRB(ready.Box.X - 3 * w.H, ready.Box.Bottom, ready.Box.Right + 3 * w.H, ready.Box.Bottom + 12 * w.H); foreach (var t in new[] { Tuple.Create(1, Prep.None), Tuple.Create(2, Prep.None), Tuple.Create(1, Prep.Contrast), Tuple.Create(2, Prep.Contrast), Tuple.Create(1, Prep.DarkText), Tuple.Create(2, Prep.DarkText), Tuple.Create(3, Prep.None) }) var word = Ocr.ReadWords(f, box, t.Item1, t.Item2).FirstOrDefault(l => IsTrainButton(l.Text)); if (word != null) return word; public void PressAnimalTrain(OcrLine train, AnimalsWindow w) var session = w.Lines.FirstOrDefault(l => Parse.Has(l.Text, "TRAINING SESSION")); if (train == null || !IsTrainButton(train.Text) || session == null || train.Box.Y <= session.Box.Bottom || !w.Area.Contains(train.Box)) throw new NeverPressException(train == null ? "" : train.Text, "not the animal's TRAIN"); if (OnlyNavigate) RefuseInCheck(train.Text); Click(train.CenterX, train.CenterY, 1500); static readonly string BuyKey = Parse.Key("BUY"), OpenKey = Parse.Key("OPEN"); public List<AnimalBuy> ReadAnimalShop(Frame f, AnimalsWindow w) var list = AnimalShopFrom(f, w.Lines, w.H); if (AnimalShopWhole(list, w.Lines)) return list; int scale = PopupScale(f); var merged = w.Lines.ToList(); foreach (var t in new[] { Tuple.Create(scale + 1, Prep.None), Tuple.Create(scale, Prep.Contrast), Tuple.Create(scale + 1, Prep.Contrast), Tuple.Create(Math.Max(1, scale - 1), Prep.None) }) var more = Ocr.Read(f, w.Area, t.Item1, t.Item2); merged.AddRange(more.Where(m => !merged.Any(l => l.Box.IntersectsWith(m.Box)))); foreach (var b in AnimalShopFrom(f, merged, w.H)) int i = list.FindIndex(x => Parse.Key(x.Name) == Parse.Key(b.Name)); else if (list[i].Buy == null && b.Buy != null) list[i] = b; if (AnimalShopWhole(list, merged)) break; static bool AnimalShopWhole(List<AnimalBuy> list, List<OcrLine> lines) int crates = lines.Count(l => Parse.Key(l.Text).EndsWith(Parse.Key("ANIMAL CRATE")) && !Parse.Has(l.Text, "EXOTIC")); return list.Count(b => b.Crate && b.Buy != null) >= Math.Max(2, crates) && list.Any(b => !b.Crate && b.Buy != null); static List<AnimalBuy> AnimalShopFrom(Frame f, List<OcrLine> lines, int h) var list = new List<AnimalBuy>(); foreach (var name in lines.Where(l => Parse.Key(l.Text).EndsWith(Parse.Key("ANIMAL CRATE")) && !Parse.Has(l.Text, "EXOTIC"))) Func<OcrLine, bool> inCard = l => Math.Abs(l.CenterX - name.CenterX) <= half && l.Box.Y > name.Box.Bottom; var b = new AnimalBuy { Name = name.Text.Trim(), Crate = true }; var priceLine = lines.Where(l => inCard(l) && l.Box.Y < name.Box.Bottom + 6 * h && (Parse.Has(l.Text, "SOLD OUT") || l.Text.Contains("$"))) .OrderBy(l => l.Box.Y).FirstOrDefault() ?? PriceAgain(f, new Rectangle(name.CenterX - 5 * h, name.Box.Bottom + 3 * h, 10 * h, 3 * h)); if (priceLine == null) continue; if (Regex.IsMatch(priceLine.Text, @"R\s?\$")) continue; b.SoldOut = Parse.Has(priceLine.Text, "SOLD OUT"); if (!b.SoldOut) b.Price = LastMoney(priceLine.Text); var left = lines.FirstOrDefault(l => inCard(l) && l.Box.Y > priceLine.Box.Bottom && l.Box.Y < priceLine.Box.Bottom + 2 * h && Parse.Has(l.Text, "LEFT TODAY")); if (left != null) b.Left = left.Text.Trim(); var foot = lines.Where(l => inCard(l) && l.Box.Y > priceLine.Box.Bottom && l.Box.Y < priceLine.Box.Bottom + 6 * h).ToList(); b.Buy = foot.FirstOrDefault(l => Parse.Key(l.Text) == BuyKey && l.CenterX > name.CenterX); b.Open = foot.FirstOrDefault(l => Parse.Key(l.Text) == OpenKey && l.CenterX < name.CenterX); if (b.Buy != null) b.BuyLit = Lit(f, b.Buy); if (b.Open != null) b.OpenLit = Lit(f, b.Open); foreach (var name in lines.Where(l => Parse.Key(l.Text) == Parse.Key("ANIMAL FOOD"))) if (!lines.Any(l => Parse.Has(l.Text, "FILLS") && l.Box.Y > name.Box.Bottom && l.Box.Y < name.Box.Bottom + 2 * h && Math.Abs(l.Box.X - name.Box.X) < 2 * h)) continue; var priceLine = lines.Where(l => l.Box.X > name.Box.Right + 4 * h && Math.Abs(l.CenterY - name.CenterY) <= h && (Parse.Has(l.Text, "SOLD OUT") || l.Text.Contains("$"))) .OrderBy(l => l.Box.X).FirstOrDefault(); var under = lines.Where(l => Parse.Key(l.Text) == BuyKey && l.Box.X > name.Box.Right + 4 * h && l.Box.Y > name.Box.Bottom && l.Box.Y < name.Box.Bottom + 4 * h) .OrderBy(l => l.Box.Y).FirstOrDefault(); if (under != null) priceLine = PriceAgain(f, new Rectangle(under.CenterX - 6 * h, name.Box.Y - h, 12 * h, name.Box.Height + 2 * h)); if (priceLine == null || Regex.IsMatch(priceLine.Text, @"R\s?\$")) continue; var b = new AnimalBuy { Name = "Animal Food", SoldOut = Parse.Has(priceLine.Text, "SOLD OUT") }; if (!b.SoldOut) b.Price = LastMoney(priceLine.Text); b.Buy = lines.FirstOrDefault(l => Parse.Key(l.Text) == BuyKey && l.Box.Y > priceLine.Box.Bottom && l.Box.Y < priceLine.Box.Bottom + 4 * h && Math.Abs(l.CenterX - priceLine.CenterX) < 6 * h); if (b.Buy != null) b.BuyLit = Lit(f, b.Buy); static OcrLine PriceAgain(Frame f, Rectangle box) box.Intersect(new Rectangle(0, 0, f.Width, f.Height)); if (box.Width < 8 || box.Height < 4) return null; foreach (var t in new[] { Tuple.Create(3, Prep.None), Tuple.Create(4, Prep.None), Tuple.Create(3, Prep.Contrast), Tuple.Create(2, Prep.None), Tuple.Create(4, Prep.Contrast) }) string s = Ocr.ReadText(f, box, t.Item1, t.Item2) ?? ""; if (Parse.Has(s, "SOLD OUT") || Regex.IsMatch(s, @"\$\s*[0-9][0-9,]*(\.[0-9]+)?\s*[KMBT](?![A-Za-z])|\$\s*[0-9][0-9,]*(?![0-9.,A-Za-z])") && LastMoney(s) > 0) return new OcrLine { Text = s.Trim(), Box = box }; static bool Lit(Frame f, OcrLine button) int h = Math.Max(6, button.Box.Height); var box = Rectangle.FromLTRB(button.Box.X - h, button.Box.Y - h, button.Box.Right + h, button.Box.Bottom + h); box.Intersect(new Rectangle(0, 0, f.Width, f.Height)); for (int y = box.Top; y < box.Bottom; y += 2) for (int x = box.Left; x < box.Right; x += 2) if (GoldIsh(f, x, y)) gold++; return gold * 100 >= 3 * Math.Max(1, all); public void PressAnimalBuy(AnimalBuy b, AnimalsWindow w) if (b == null || b.Buy == null || Parse.Key(b.Buy.Text) != BuyKey || b.SoldOut || b.Price <= 0 || !w.Area.Contains(b.Buy.Box)) throw new NeverPressException(b == null || b.Buy == null ? "" : b.Buy.Text, "not an animal shop cash BUY"); if (OnlyNavigate) RefuseInCheck(b.Buy.Text); Click(b.Buy.CenterX, b.Buy.CenterY, 1500); public void PressAnimalOpen(AnimalBuy b, AnimalsWindow w) if (b == null || !b.Crate || b.Open == null || Parse.Key(b.Open.Text) != OpenKey || !w.Area.Contains(b.Open.Box)) throw new NeverPressException(b == null || b.Open == null ? "" : b.Open.Text, "not an animal crate's OPEN"); if (OnlyNavigate) RefuseInCheck(b.Open.Text); Click(b.Open.CenterX, b.Open.CenterY, 2500); public bool AfterAnimalOpen() var calm = new[] { "CLOSE", "OK", "OKAY", "CONTINUE", "COLLECT", "CLAIM", "NICE", "AWESOME", "TAP TO CONTINUE", "TAP TO CLOSE", "TAP ANYWHERE" }.Select(Parse.Key).ToList(); for (int look = 0; look < 6; look++) using (var f = Capture()) var w = ReadAnimalsWindow(f); var lines = Ocr.Read(f, PopupArea(f), PopupScale(f), Prep.None); if (w != null && lines.Any(l => Parse.Has(l.Text, "ANIMAL CRATES"))) return true; if (look == 0) Snapshot("animal crate opened", 0, true, false); var button = lines.FirstOrDefault(l => calm.Contains(Parse.Key(l.Text))); if (button != null && NeverPress.Why(button.Text) == null) { Press(button, 1200); continue; } using (var f = Capture()) if (ReadAnimalsWindow(f) != null) return true; Snapshot("animal crate window not closed", 60);