using System.Collections.Generic;using System.Collections.ObjectModel;using System.Globalization;using System.Text.RegularExpressions;using System.Windows.Controls;using System.Windows.Controls.Primitives;using System.Windows.Input;using System.Windows.Interop;using System.Windows.Markup;using System.Windows.Media;using System.Windows.Media.Animation;using System.Windows.Shapes;using System.Windows.Shell; public sealed class FeedItem public string Who { get; set; } public Brush Color { get; set; } public Brush Stripe { get; set; } public Geometry Icon { get; set; } public string Time { get; set; } public string Text { get; set; } public sealed class OpsRow public string Name { get; set; } public string Left { get; set; } public GridLength A { get; set; } public GridLength B { get; set; } public sealed class PerkView public string Name { get; set; } public string Effect { get; set; } public string Level { get; set; } public string Progress { get; set; } public Brush NameBrush { get; set; } public FontWeight NameWeight { get; set; } public Brush RowBg { get; set; } public GridLength A { get; set; } public GridLength B { get; set; } public sealed class FoundRow public string Title { get; set; } public string Text { get; set; } public sealed class ChangeRow public string Name { get; set; } public string Old { get; set; } public string New { get; set; } public string Reason { get; set; } public string Note { get; set; } public bool Use { get; set; } public sealed class JobRow public string Header { get; set; } public bool IsHeader { get; set; } public string Summary { get; set; } public string Sign { get; set; } public ICommand Toggle { get; set; } public string Name { get; set; } public string Cost { get; set; } public string Xp { get; set; } public string Rank { get; set; } public Brush RankBrush { get; set; } public Brush NameBrush { get; set; } public FontWeight NameWeight { get; set; } public Brush RowBg { get; set; } public GridLength A { get; set; } public GridLength B { get; set; } public ICommand Pick { get; set; } public bool Pickable { get; set; } public bool Hover { get; set; } public sealed class BigPayerView public string Name { get; set; } public string Biggest { get; set; } public string Last { get; set; } public string When { get; set; } public string Record { get; set; } public string RecordTip { get; set; } public Brush RowBg { get; set; } public ICommand Forget { get; set; } public sealed class RelayCommand : ICommand public RelayCommand(Action run) { this.run = run; } public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { run(); } public event EventHandler CanExecuteChanged { add { } remove { } } public string Name, Color, Icon; public Crew(string name, string color, string icon) { Name = name; Color = color; Icon = icon; } static readonly Crew Runner = new Crew("Jobs", "#E08C32", "IcoJobs"); static readonly Crew Fixer = new Crew("Heists", "#8B6ABB", "IcoHeist"); static readonly Crew Enforcer = new Crew("Fights", "#C84B3F", "IcoSwords"); static readonly Crew Capo = new Crew("Bosses", "#D4AF37", "IcoCrown"); static readonly Crew Accountant = new Crew("Bank", "#7EBE5A", "IcoBank"); static readonly Crew Consigliere = new Crew("Operations", "#5B8FB9", "IcoHourglass"); static readonly Crew Collector = new Crew("Rewards", "#C9956A", "IcoGift"); static readonly Crew Items = new Crew("Items", "#C9956A", "IcoGift"); static readonly Crew Landlord = new Crew("Properties", "#7EBE5A", "IcoBank"); static readonly Crew Underboss = new Crew("Family", "#4FB3A5", "IcoFamily"); static readonly Crew Henchmen = new Crew("Crew", "#E0709A", "IcoCrew"); static readonly Crew Don = new Crew("Bot", "#E8E2D0", "IcoFedora"); static readonly string[] Pages = { "Overview", "Jobs", "Settings" }; static readonly string[] Titles = { "Home", "Tasks", "Settings" }; static readonly string[] RarityBrushes = { "RarityCommon", "RarityUncommon", "RarityRare", "RarityEpic", "RarityLegendary", "RarityMythic", "RaritySecret", "RarityForbidden" }; const string ClosestPerk = "Closest to its next level"; public readonly Window Window; readonly ObservableCollection<FeedItem> feed = new ObservableCollection<FeedItem>(); readonly List<FeedItem> feedAll = new List<FeedItem>(); readonly ObservableCollection<OpsRow> ops = new ObservableCollection<OpsRow>(); readonly ObservableCollection<JobRow> jobRows = new ObservableCollection<JobRow>(); readonly ObservableCollection<PerkView> perkRows = new ObservableCollection<PerkView>(); readonly ObservableCollection<BigPayerView> payerRows = new ObservableCollection<BigPayerView>(); readonly ObservableCollection<FoundRow> foundRows = new ObservableCollection<FoundRow>(); readonly ObservableCollection<ChangeRow> changeRows = new ObservableCollection<ChangeRow>(); readonly ObservableCollection<ChangeRow> keptRows = new ObservableCollection<ChangeRow>(); List<string> perkChoices; readonly DateTime started = DateTime.Now; public UiWindow(Settings settings, Bot bot, string dir, string[] args) Window = (Window)LoadXaml("MainWindow.xaml"); var work = SystemParameters.WorkArea; if (work.Width > 0 && work.Height > 0) Window.MinWidth = Math.Min(Window.MinWidth, work.Width); Window.MinHeight = Math.Min(Window.MinHeight, work.Height); Window.Width = Math.Min(Window.Width, work.Width); Window.Height = Math.Min(Window.Height, work.Height); var asm = Assembly.GetExecutingAssembly(); var ico = asm.GetManifestResourceStream("IdleMafiaBot.ui.app.ico"); if (ico != null) Window.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(ico, System.Windows.Media.Imaging.BitmapCreateOptions.None, System.Windows.Media.Imaging.BitmapCacheOption.OnLoad); WindowChrome.SetWindowChrome(Window, new WindowChrome ResizeBorderThickness = new Thickness(6), GlassFrameThickness = new Thickness(0, 0, 0, 1), CornerRadius = new CornerRadius(0), UseAeroCaptionButtons = false, El<ItemsControl>("Feed").ItemsSource = feed; El<ItemsControl>("OpsList").ItemsSource = ops; El<ItemsControl>("JobList").ItemsSource = jobRows; El<ItemsControl>("PerkList").ItemsSource = perkRows; El<ItemsControl>("BigPayerList").ItemsSource = payerRows; El<ItemsControl>("FoundList").ItemsSource = foundRows; El<ItemsControl>("ChangeList").ItemsSource = changeRows; El<ItemsControl>("KeptList").ItemsSource = keptRows; bot.Logged += line => Ui(() => AddFeed(line)); bot.StateChanged += st => Ui(() => ShowState(st)); bot.HeaderRead += h => Ui(() => ShowHeader(h)); bot.InfoChanged += (k, v) => Ui(() => ShowInfo(k, v)); bot.OpsRead += slots => Ui(() => ShowOps(slots)); bot.CatalogChanged += () => Ui(() => { jobsDirty = true; RefreshJobs(); }); bot.AccountChanged += name => Ui(() => if (accountShown != null && !string.Equals(Profile.Key(accountShown), Profile.Key(name), StringComparison.OrdinalIgnoreCase)) { ForgetAccount(); ClearHeader(name); } RefreshControls(); UpdateDerived(); jobsDirty = true; RefreshJobs(); AccountSeen(name); ShowCrew(null); bot.InstancesChanged += list => Ui(() => ShowInstances(list)); bot.InfoChanged += (k, v) => { if (k == "ocr") Ui(() => ShowOcr(v)); }; bot.FamilyRead += fi => Ui(() => ShowFamily(fi)); bot.CrewRead += ci => Ui(() => ShowCrew(ci)); bot.SafehouseRead += info => Ui(() => ShowSafehouse(info)); bot.Discord.Moment += (k, title, text) => Ui(() => ShowMoment(k, title, text)); bot.ProfileRead += p => Ui(() => CheckDone(p)); bot.ProfileChanged += p => Ui(() => { if (profileOf == Profile.Key(p.Name)) { profile = p; UpdateCheckWhen(); if (ReviewShowing) RefreshReview(); } }); Window.Title = Program.Name + " " + Program.Version; El<TextBlock>("VersionText").Text = "version " + Program.Version; El<TextBlock>("SupportVersion").Text = "version " + Program.Version; El<TextBlock>("WelcomeVersion").Text = "Version " + Program.Version; ShowWelcome(!s.WelcomeSeen); ShowRules(!args.Contains("--demo") || args.Contains("--rules")); Window.SourceInitialized += (a, b) => var hwnd = new WindowInteropHelper(Window).Handle; HwndSource.FromHwnd(hwnd).AddHook(WndProc); if (!Native.RegisterHotKey(hwnd, 1, 0, 0x75)) AddFeed(DateTime.Now.ToString("HH:mm:ss") + " F6 is taken by another program. Use the Start button."); if (Application.Current != null) Application.Current.SessionEnding += (a, b) => closedBy = "Windows (" + (b.ReasonSessionEnding == ReasonSessionEnding.Logoff ? "signing out" : "shutting down, or closing the program that started the bot") + ")"; Window.Closing += (a, b) => if (!Closing && !closeAfterStop && CloseWaitsForBot(closedBy, bot.Running && !bot.CheckOrScan, bot.OwnRollRuns)) if (!bot.StopPressed) bot.Stop(); var until = DateTime.UtcNow.AddSeconds(25); var wait = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) }; wait.Tick += (c, d) => { if (bot.Running && DateTime.UtcNow < until) return; wait.Stop(); if (!Closing) Window.Close(); }; bot.Log("Window closed " + (closedBy != null ? "by " + closedBy : "by another program or Windows (not its Close button)")); bool wasRunning = bot.Running; if (ClosedMessage(wasRunning, closedBy)) bot.Discord.Problem("closed", "\U0001F6D1 The bot was closed", "By " + (closedBy ?? "another program or Windows") + ".", Discord.ShotOfRoblox, false, 0); bot.Discord.Flush((closedBy ?? "").StartsWith("you") ? 800 : 4000); bot.Stats.Closing(closedBy); bool scan = args.Contains("--scan"), check = args.Contains("--check"), start = args.Contains("--start"), exit = args.Contains("--exit"); if (args.Contains("--once")) { bot.Once = true; start = true; } if (args.Contains("--fightshots")) { bot.CollectFights = true; bot.Log("Fights: a picture of every fight window is kept (problems\\fights)"); } var page = args.FirstOrDefault(a => a.StartsWith("--page=")); if (page != null) { var rail = Window.FindName("Rail" + page.Substring(7)) as RadioButton; if (rail != null) rail.IsChecked = true; } bool demo = args.Contains("--demo"); if (!demo && Program.InTempFolder(dir)) ShowWarning("Unzip the bot first", "The bot runs from a temporary folder, so its settings and log will be lost. Close it, right-click the zip, " + "pick Extract All, and start IdleMafiaBot.exe from the folder that makes.", false); bot.Log("The bot runs from a temporary folder (inside the zip?) - unzip the download (right-click > Extract All) and start it from there, or its settings get lost"); System.Threading.ThreadPool.QueueUserWorkItem(_ => string problem = Ocr.Check(); if (problem != null) { bot.Log("Problem: " + problem); return; } if (Ocr.Warning != null) { ShowWarning("Text is read in another language", Ocr.Warning, true); bot.Log(Ocr.Warning); } if (!demo) bot.StartWatching(); bool rerollOnly = args.Contains("--rerollonly"); bool turnsAgain = start && args.Contains("--restarted") && args.Contains("--turns"); if ((start || rerollOnly) && args.Contains("--restarted")) Window.Loaded += (a, b) => { if (!bot.Running) { if (rerollOnly) bot.StartRerollOnly(); else if (turnsAgain) bot.StartAllWhenSeen(); else bot.Start(); } }; Window.ContentRendered += (a, b) => shownAt = DateTime.UtcNow; if (scan) bot.RequestScan(); else if (check) bot.RequestCheck(); else if (rerollOnly && !bot.Running) bot.StartRerollOnly(); else if (start && !turnsAgain && !bot.Running) bot.Start(); ShowState(bot.Running ? (check ? "Checking" : "Starting") : "Stopped"); if (exit) bot.StateChanged += st => { if (st == "Stopped") Ui(() => { closedBy = "--exit (the run is over)"; Window.Close(); }); }; El<RadioButton>("TotSpanSession").Checked += (a, b) => UpdateTotals(); El<RadioButton>("TotSpanAll").Checked += (a, b) => UpdateTotals(); Window.Activated += (a, b) => UpdateProblems(); public static object LoadXaml(string name) using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("IdleMafiaBot.ui." + name)) if (stream == null) throw new InvalidOperationException("Missing UI file " + name); return XamlReader.Load(stream); T El<T>(string name) where T : class var o = Window.FindName(name) as T; if (o == null) throw new InvalidOperationException("UI element missing: " + name); Brush Res(string key) { return (Brush)Window.FindResource(key); } static Brush Hex(string hex) { var b = new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex)); b.Freeze(); return b; } void Ui(Action a) { if (!Window.Dispatcher.HasShutdownStarted) Window.Dispatcher.BeginInvoke(a); } var anim = new DoubleAnimation(0.08, 0.35, TimeSpan.FromSeconds(1.1)) { AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever }; El<Ellipse>(name).BeginAnimation(UIElement.OpacityProperty, anim); for (int i = 0; i < Pages.Length; i++) El<RadioButton>("Rail" + Pages[i]).Checked += (a, b) => ShowPage(page); const double ColsMinWidth = 840, FillMinList = 150; readonly List<Grid> fills = new List<Grid>(); var all = new List<FrameworkElement>(); var fold = e as ToggleButton; if (fold != null && fold.Name.StartsWith("Fold_")) var part = El<FrameworkElement>(fold.Name.Substring(5)); RoutedEventHandler show = (a, b) => { part.Visibility = fold.IsChecked == true ? Visibility.Visible : Visibility.Collapsed; FitFillsSoon(); }; if (grid == null) continue; if ("Cols".Equals(grid.Tag)) var right = g.Children.OfType<UIElement>().Where(c => Grid.GetColumn(c) == 2).ToList(); var widths = g.ColumnDefinitions.Select(c => c.Width).ToList(); g.SizeChanged += (a, b) => FitCols(g, right, widths); if ("Fill".Equals(grid.Tag)) g.IsVisibleChanged += (a, b) => FitFillsSoon(); foreach (FrameworkElement c in g.Children) if (Grid.GetRow(c) < g.RowDefinitions.Count - 1) c.SizeChanged += (a, b) => FitFillsSoon(); if (page != null) page.SizeChanged += (a, b) => FitFillsSoon(); static void EvenColumns(Grid g) foreach (var stack in g.Children.OfType<StackPanel>().Where(p => p.Orientation == Orientation.Vertical).ToList()) var dock = new DockPanel { LastChildFill = true, Margin = stack.Margin, Visibility = stack.Visibility }; Grid.SetColumn(dock, Grid.GetColumn(stack)); Grid.SetRow(dock, Grid.GetRow(stack)); Grid.SetColumnSpan(dock, Grid.GetColumnSpan(stack)); var kids = stack.Children.OfType<UIElement>().ToList(); DockPanel.SetDock(k, Dock.Top); var fe = k as FrameworkElement; if (fe != null && k == kids.Last()) fe.VerticalAlignment = VerticalAlignment.Stretch; int at = g.Children.IndexOf(stack); g.Children.Insert(at, dock); static void Collect(DependencyObject d, List<FrameworkElement> all) var fe = d as FrameworkElement; if (fe != null) all.Add(fe); foreach (var child in LogicalTreeHelper.GetChildren(d)) var c = child as DependencyObject; if (c != null) Collect(c, all); static ScrollViewer Page(DependencyObject d) while (d != null && !(d is ScrollViewer && ((ScrollViewer)d).Name.StartsWith("Page"))) d = LogicalTreeHelper.GetParent(d); return d as ScrollViewer; static bool Shown(FrameworkElement e, FrameworkElement page) for (DependencyObject d = e; d != null; d = LogicalTreeHelper.GetParent(d)) if (u != null && u.Visibility != Visibility.Visible) return false; if (d == page) return true; static void FitCols(Grid g, List<UIElement> right, List<GridLength> widths) bool one = g.ActualWidth > 0 && g.ActualWidth < ColsMinWidth; for (int i = 0; i < g.ColumnDefinitions.Count; i++) var w = one && i > 0 ? new GridLength(0) : widths[i]; if (g.ColumnDefinitions[i].Width != w) g.ColumnDefinitions[i].Width = w; if (Grid.GetColumn(r) != (one ? 0 : 2)) Grid.SetColumn(r, one ? 0 : 2); if (Grid.GetRow(r) != (one ? 1 : 0)) Grid.SetRow(r, one ? 1 : 0); Window.Dispatcher.BeginInvoke(new Action(() => { fitting = false; FitFills(); }), System.Windows.Threading.DispatcherPriority.Loaded); if (page == null || !Shown(g, page) || page.ViewportHeight <= 0) continue; var content = page.Content as FrameworkElement; double top = g.TranslatePoint(new Point(0, 0), page).Y + page.VerticalOffset; for (int i = 0; i < g.RowDefinitions.Count - 1; i++) rows += g.RowDefinitions[i].ActualHeight; double h = Math.Max(page.ViewportHeight - top - (content != null ? content.Margin.Bottom : 0), rows + FillMinList); if (double.IsNaN(g.Height) || Math.Abs(g.Height - h) > 0.5) g.Height = h; for (int i = 0; i < Pages.Length; i++) El<ScrollViewer>("Page" + Pages[i]).Visibility = i == page ? Visibility.Visible : Visibility.Collapsed; El<ScrollViewer>("Page" + Pages[page]).BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160))); El<TextBlock>("PageTitle").Text = Titles[page].ToUpperInvariant(); El<Button>("BtnStart").Visibility = page == 0 ? Visibility.Collapsed : Visibility.Visible; El<Border>("StatePill").Visibility = page == 0 ? Visibility.Collapsed : Visibility.Visible; if (Pages[page] == "Settings") UpdateProblems(); readonly List<CheckBox> tiles = new List<CheckBox>(); readonly List<Tuple<HeaderedContentControl, List<CheckBox>>> groups = new List<Tuple<HeaderedContentControl, List<CheckBox>>>(); var all = new List<FrameworkElement>(); var tileStyle = Window.FindResource("TaskTile"); foreach (var t in all.OfType<CheckBox>().Where(c => c.Style == tileStyle && Page(c) != null && Page(c).Name == "PageJobs")) tiles.Add(t); var groupStyle = Window.FindResource("TaskGroup"); foreach (var g in all.OfType<HeaderedContentControl>().Where(c => c.Style == groupStyle)) var inside = new List<FrameworkElement>(); if (inside.OfType<CheckBox>().Any()) groups.Add(Tuple.Create(g, inside.OfType<CheckBox>().ToList())); foreach (var tile in all.OfType<CheckBox>().Where(c => c.Uid == "1")) if (Window.FindName("Opt_" + key) == null) continue; var more = t.Template == null ? null : t.Template.FindName("MoreBtn", t) as Button; if (more != null) more.Click += (a, b) => { OpenOptions(key, (string)t.Content, t.Tag as Geometry); b.Handled = true; }; var layer = El<Grid>("OptionsLayer"); El<Button>("BtnOptionsClose").Click += (a, b) => CloseOptions(); layer.MouseLeftButtonDown += (a, b) => { if (b.OriginalSource == layer) CloseOptions(); }; Window.PreviewKeyDown += (a, b) => { if (b.Key == Key.Escape && optionsOpen != null) { CloseOptions(); b.Handled = true; } }; El<Button>("BtnStartBig").Click += (a, b) => Toggle(); var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; timer.Tick += (a, b) => UpdateTileStatus(); Window.Loaded += (a, b) => UpdateTileStatus(); static void UseDecoFont() if (Application.Current == null) return; string folder = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "IdleMafiaBot-fonts"); string file = System.IO.Path.Combine(folder, "AbrilFatface-Regular.ttf"); using (var s = Assembly.GetExecutingAssembly().GetManifestResourceStream("IdleMafiaBot.ui.AbrilFatface-Regular.ttf")) if (!System.IO.File.Exists(file) || new System.IO.FileInfo(file).Length != s.Length) System.IO.Directory.CreateDirectory(folder); using (var f = System.IO.File.Create(file)) s.CopyTo(f); Application.Current.Resources["Deco"] = new FontFamily(new Uri(folder + System.IO.Path.DirectorySeparatorChar), "./#Abril Fatface"); internal void ShowMoment(Ping k, string title, string text) var box = El<StackPanel>("Toasts"); string head = Regex.Replace(title ?? "", @"^[^\p{L}\p{N}]+", "").Trim(); string rarity = new[] { "Forbidden", "Secret", "Mythic", "Legendary", "Epic", "Rare" }.FirstOrDefault(r => head.Contains(r)); Brush accent = k == Ping.Rarity && rarity != null ? Res("Rarity" + rarity) : k == Ping.Level ? Res("Xp") : Res("Gold"); string icon = k == Ping.Boss || k == Ping.GlobalBoss ? "IcoCrown" : k == Ping.Property ? "IcoHome" : k == Ping.Level ? "IcoStar" : "IcoCrew"; grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); var badge = new Border { Width = 40, Height = 40, CornerRadius = new CornerRadius(20), Background = accent, VerticalAlignment = VerticalAlignment.Top }; badge.Child = new Path { Data = (Geometry)Window.FindResource(icon), Stroke = Res("OnGold"), StrokeThickness = 2, Width = 20, Height = 20, Stretch = Stretch.Uniform, StrokeLineJoin = PenLineJoin.Round, StrokeStartLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round }; var words = new StackPanel { Margin = new Thickness(14, 0, 0, 0) }; words.Children.Add(new TextBlock { Text = head, FontFamily = (FontFamily)Window.FindResource("Deco"), FontSize = 19, Foreground = accent, TextTrimming = TextTrimming.CharacterEllipsis }); words.Children.Add(new TextBlock { Text = text ?? "", FontSize = 13, Foreground = Res("Text"), TextWrapping = TextWrapping.Wrap, TextTrimming = TextTrimming.None, Margin = new Thickness(0, 2, 0, 0), MaxHeight = 54 }); Grid.SetColumn(words, 1); grid.Children.Add(badge); grid.Children.Add(words); var bg = new LinearGradientBrush { StartPoint = new Point(0, 0), EndPoint = new Point(1, 1) }; bg.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#33291A"), 0)); bg.GradientStops.Add(new GradientStop((Color)ColorConverter.ConvertFromString("#18160F"), 1)); var slide = new TranslateTransform(380, 0); var card = new Border { Background = bg, BorderBrush = accent, BorderThickness = new Thickness(1.2), CornerRadius = new CornerRadius(12), Padding = new Thickness(14, 12, 16, 12), Margin = new Thickness(0, 0, 0, 10), Child = grid, RenderTransform = slide, Opacity = 0 }; box.Children.Insert(0, card); while (box.Children.Count > 3) box.Children.RemoveAt(box.Children.Count - 1); var ease = new CubicEase { EasingMode = EasingMode.EaseOut }; slide.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation(380, 0, TimeSpan.FromMilliseconds(380)) { EasingFunction = ease }); card.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(250))); var stay = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(6) }; var fade = new DoubleAnimation(0, TimeSpan.FromMilliseconds(450)); fade.Completed += (c, d) => box.Children.Remove(card); card.BeginAnimation(UIElement.OpacityProperty, fade); slide.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation(60, TimeSpan.FromMilliseconds(450))); static string TileKey(FrameworkElement t) { return t.Name.StartsWith("sw_") ? t.Name.Substring(3) : t.Name.StartsWith("tile_") ? t.Name.Substring(5) : t.Name; } static T Part<T>(Control c, string name) where T : class if (c.Template == null) c.ApplyTemplate(); return c.Template == null ? null : c.Template.FindName(name, c) as T; string Shown(string name) { var t = Window.FindName(name) as TextBlock; return t == null ? "" : (t.Text ?? "").Trim(); } var line = Part<TextBlock>(t, "Status"); if (line == null) continue; string text = t.IsChecked == true ? TileLine(TileKey(t)) : ""; if (line.Text != text) line.Text = text; foreach (var g in groups) var count = Part<TextBlock>(g.Item1, "Count"); if (count == null) continue; var seen = g.Item2.Where(t => Shown(t, g.Item1)).ToList(); string text = seen.Count == 0 ? "" : seen.Count(t => t.IsChecked == true) + " of " + seen.Count + " on"; if (count.Text != text) count.Text = text; string TileLine(string key) Func<string, bool> known = v => v.Length > 0 && v != "-" && v != "?"; case "Jobs": return known(Shown("JobName")) && !Shown("JobName").EndsWith(" off") ? Shown("JobName") : ""; case "Heists": return known(Shown("FavorsText")) ? Shown("FavorsText").Replace(" left", "") + " favors" + (Shown("FavorsText").EndsWith(" left") ? " left" : "") : ""; case "Bosses": return known(Shown("BossName")) ? Shown("BossName") + (Shown("BossWhen").Length > 0 ? ", " + Shown("BossWhen") : "") : ""; case "Fights": return bot.Count.Fights + " this session"; case "CrewFill": return known(Shown("CrewCount")) ? Shown("CrewCount") + " henchmen" : ""; case "Reroll": return RollMode(); case "TrainCrew": return s.TrainOrder == TrainOrder.Evenly ? "Everyone evenly" : "The best first"; case "Animals": return animalLine; case "FamilyWatch": return Shown("FamName") != "Your family" ? Shown("FamName") : ""; case "Takedown": return known(Shown("TdFree")) ? Shown("TdFree") + " free attacks" : ""; case "GiveStamina": return s.StaminaPerk.Trim().Length == 0 ? "Closest perk" : s.StaminaPerk.Trim(); case "DefendTerritory": return known(Shown("FamTerritories")) ? Shown("FamTerritories") + " districts" : ""; case "Properties": return known(Shown("SafeIncome")) ? Shown("SafeIncome") + " an hour" : ""; case "Bank": return Shown("BankText"); case "SafehouseUpgrade": return safeNext; case "GameStats": return "Every 3 hours"; case "MarketRelist": return "Every " + s.MarketMinutes + " min"; case "SpendPoints": return Shown("PointsText").Length > 0 ? Shown("PointsText") : "On " + Game.StatNames[Math.Max(0, Math.Min(Game.StatNames.Length - 1, s.PointsStat))]; case "ShopBuy": return (s.ShopCashMax > 0 ? "Up to " + SafehouseInfo.Money(s.ShopCashMax) : "Any price") + (s.ShopGoldMax > 0 ? ", gold too" : ""); case "Operations": return ops.Count > 0 ? ops[0].Left : ""; case "Rewards": return OnCount(RewardSwitches); default: return key.StartsWith("grp_") ? GroupLine(GroupSwitches(key.Substring(4))) : ""; static readonly Dictionary<string, string[]> TaskTiles = new Dictionary<string, string[]> { "jobs", new[] { "sw_Jobs" } }, { "learn jobs", new[] { "sw_Jobs" } }, { "heists", new[] { "sw_Heists" } }, { "bosses", new[] { "sw_Bosses" } }, { "fights", new[] { "sw_Fights" } }, { "only fighting players", new[] { "sw_Fights" } }, { "crew", new[] { "sw_CrewFill", "tile_Reroll", "sw_TrainCrew" } }, { "rerolls", new[] { "tile_Reroll" } }, { "family", new[] { "sw_FamilyWatch" } }, { "game stats", new[] { "sw_GameStats" } }, { "takedown", new[] { "sw_Takedown" } }, { "give stamina", new[] { "sw_GiveStamina" } }, { "properties", new[] { "sw_Properties" } }, { "bank", new[] { "sw_Bank" } }, { "safehouse upgrade", new[] { "sw_SafehouseUpgrade" } }, { "skill points", new[] { "sw_SpendPoints" } }, { "playtime rewards", new[] { "sw_Playtime" } }, { "contracts", new[] { "sw_Contracts" } }, { "operations", new[] { "sw_Operations" } }, { "briefcases", new[] { "sw_Briefcases" } }, { "trick or treat", new[] { "sw_TrickOrTreat" } }, { "global boss", new[] { "sw_GlobalBoss" } }, { "events", new[] { "sw_Events" } }, { "animals", new[] { "sw_Animals" } }, { "relist", new[] { "sw_MarketRelist" } }, { "gear", new[] { "sw_EquipBest", "sw_Crates", "sw_ShopBuy" } }, { "gear back", new[] { "sw_EquipBest" } }, if (doingTask == null || !TaskTiles.TryGetValue(doingTask, out switches)) switches = new string[0]; var lit = switches.Select(n => RewardSwitches.Contains(n) ? "tile_Rewards" : FoldedInto.ContainsKey(n) ? FoldedInto[n] : n) .Concat(TaskGroups.Where(g => g.Value.Any(switches.Contains)).Select(g => "grp_" + g.Key)).ToList(); var glow = Part<Border>(t, "Glow"); if (glow == null) continue; bool on = lit.Contains(t.Name) && t.IsChecked == true; if (on == (glow.Visibility == Visibility.Visible)) continue; glow.Visibility = on ? Visibility.Visible : Visibility.Collapsed; glow.BeginAnimation(UIElement.OpacityProperty, on ? new DoubleAnimation(0.35, 1, TimeSpan.FromSeconds(0.9)) { AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever } internal void OpenOptions(string key, string title, Geometry icon) foreach (var p in El<StackPanel>("OptionsBody").Children.OfType<FrameworkElement>()) p.Visibility = p.Name == "Opt_" + key ? Visibility.Visible : Visibility.Collapsed; if (key == "Properties") PropertyChoices(); El<TextBlock>("OptionsTitle").Text = title; El<Path>("OptionsIcon").Data = icon; El<ScrollViewer>("OptionsScroll").ScrollToTop(); var layer = El<Grid>("OptionsLayer"); var group = groups.FirstOrDefault(g => g.Item2.Any(t => TileKey(t) == key)); if (group != null && group.Item1.Resources.Contains("TileAccent")) layer.Resources["TileAccent"] = group.Item1.Resources["TileAccent"]; else layer.Resources.Remove("TileAccent"); layer.Visibility = Visibility.Visible; layer.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160))); var slide = new TranslateTransform(0, 18); El<Border>("OptionsPanel").RenderTransform = slide; slide.BeginAnimation(TranslateTransform.YProperty, new DoubleAnimation(18, 0, TimeSpan.FromMilliseconds(240)) { EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } }); if (key == "Jobs") { jobsDirty = true; RefreshJobs(); } if (key == "Fights") RefreshBigPayers(); void ShowOptionsAccount() string who = (s.Account ?? "").Trim(); string text = optionsOpen == "Rejoin" ? "Every account" : who.Length > 0 ? "Account: " + who : ""; var t = El<TextBlock>("OptionsAccount"); t.Visibility = text.Length > 0 ? Visibility.Visible : Visibility.Collapsed; internal void CloseOptions() var layer = El<Grid>("OptionsLayer"); var fade = new DoubleAnimation(0, TimeSpan.FromMilliseconds(130)); fade.Completed += (a, b) => { if (optionsOpen == null) layer.Visibility = Visibility.Collapsed; }; layer.BeginAnimation(UIElement.OpacityProperty, fade); static readonly KeyValuePair<string, string[]>[] TaskGroups = new KeyValuePair<string, string[]>("Energy", new[] { "sw_Jobs", "sw_Heists" }), new KeyValuePair<string, string[]>("Stamina", new[] { "sw_Bosses", "sw_Fights", "sw_WhaleHunt", "sw_GlobalBoss" }), new KeyValuePair<string, string[]>("Crew", new[] { "sw_CrewFill", "tile_Reroll", "sw_TrainCrew", "sw_EquipBest", "sw_Animals" }), new KeyValuePair<string, string[]>("Family", new[] { "sw_FamilyWatch", "sw_Takedown", "sw_GiveStamina", "sw_DefendTerritory", "sw_GameStats" }), new KeyValuePair<string, string[]>("Cash", new[] { "sw_Properties", "sw_SafehouseUpgrade", "sw_Bank", "sw_SpendPoints", "sw_ShopBuy", "sw_MarketRelist" }), new KeyValuePair<string, string[]>("Rewards", new[] { "sw_Playtime", "sw_Contracts", "sw_Briefcases", "sw_Crates", "sw_Operations" }), static readonly string[] AllTasks = TaskGroups.SelectMany(g => g.Value).Concat(new[] { "sw_Events" }).ToArray(); static readonly string[] RewardSwitches = { "sw_Playtime", "sw_Contracts", "sw_Briefcases", "sw_Crates" }; static readonly Dictionary<string, string> FoldedInto = new Dictionary<string, string> { "sw_WhaleHunt", "sw_Fights" }, { "sw_Events", "tile_Rewards" }, static readonly string[] Starter = { "sw_Jobs", "sw_Heists", "sw_Bosses", "sw_GlobalBoss", "sw_CrewFill", "sw_FamilyWatch", "sw_Takedown", "sw_GiveStamina", "sw_Properties", "sw_Bank", "sw_SafehouseUpgrade", "sw_SpendPoints", "sw_Playtime", "sw_Contracts", "sw_Briefcases", "sw_Crates", "sw_EquipBest", "sw_Events", "sw_Operations", "sw_Animals" }; static readonly Tuple<string, string, string[]>[] QuickStarts = Tuple.Create("New account", "Jobs, heists, bosses and rewards. Spends money on properties, the safehouse, hires, crew slots, animals and operations. No training, shop, rerolls or player fights (a new bot has training and the shop on).", Starter), Tuple.Create("Level up", "The most XP. Spends money on properties, the safehouse, hires, crew slots, animals, operations, training and the shop. No rerolls or player fights. Stamina stays for bosses instead of perks.", Starter.Where(n => n != "sw_GiveStamina").Concat(new[] { "sw_TrainCrew", "sw_ShopBuy" }).ToArray()), Tuple.Create("Make money", "The most cash: attacks players and hits big wins again. Spends money on properties, the safehouse, hires, crew slots, animals and operations. No training, shop or rerolls.", Starter.Where(n => n != "sw_GiveStamina").Concat(new[] { "sw_Fights", "sw_WhaleHunt" }).ToArray()), Tuple.Create("Everything", "Every task on, also rerolls, player fights and defending districts. Spends money on properties, the safehouse, hires, crew slots, rerolls, training, the shop, animals and operations.", foreach (var g in TaskGroups) var tile = El<CheckBox>("grp_" + key); tile.Checked += (a, b) => { if (!syncingBundles) SwitchAll(key, members, true); }; tile.Unchecked += (a, b) => { if (!syncingBundles) SwitchAll(key, members, false); }; var more = tile.Template.FindName("MoreBtn", tile) as Button; if (more != null) more.ToolTip = "Its tasks one by one"; if (more != null) more.Click += (a, b) => { b.Handled = true; ShowTasksView(false); ShowCard("grpcard_" + key); }; var rewards = El<CheckBox>("tile_Rewards"); rewards.Checked += (a, b) => { if (!syncingBundles) SwitchAll(FreeRewards, RewardSwitches, true); }; rewards.Unchecked += (a, b) => { if (!syncingBundles) SwitchAll(FreeRewards, RewardSwitches, false); }; El<RadioButton>("TasksView_Simple").Checked += (a, b) => ShowTasksView(true); El<RadioButton>("TasksView_Full").Checked += (a, b) => ShowTasksView(false); ShowTasksView(s.TasksSimple); if (!Program.Whale) return; var keep = new[] { "sw_Fights", "sw_Bank", "sw_Animals" }; var row = new UniformGrid { Columns = 3, Margin = new Thickness(0, 0, 2, 0) }; foreach (var t in tiles.Where(c => keep.Contains(c.Name)).ToList()) t.Resources["TileAccent"] = t.TryFindResource("TileAccent"); ((Panel)t.Parent).Children.Remove(t); var full = El<StackPanel>("TasksFullView"); foreach (UIElement c in full.Children) c.Visibility = Visibility.Collapsed; full.Children.Insert(0, row); El<TextBlock>("LogoText").Text = Program.Name; var logo = El<Grid>("LogoIcon"); logo.Children.Add(new System.Windows.Shapes.Path { Stretch = Stretch.Uniform, Fill = new SolidColorBrush(Color.FromRgb(0x3D, 0xA5, 0xF4)), Data = Geometry.Parse("M8,60 C8,41 26,33 45,33 C60,33 69,44 78,42 C83,41 84,35 81,29 C78,27 74,25 70,25 C76,21 83,22 88,27 C90,22 95,20 100,21 C98,27 95,32 91,36 C92,49 84,63 70,70 C56,77 33,77 21,73 C13,70 8,66 8,60 Z") }); El<FrameworkElement>("WhaleOnlyOpts").Visibility = Visibility.Visible; El<FrameworkElement>("sw_AnimalPremium").Visibility = Visibility.Visible; foreach (var n in new[] { "BtnUpdate", "UpdateStatus", "sw_WarTargetsOnly" }) El<FrameworkElement>(n).Visibility = Visibility.Collapsed; ((FrameworkElement)((FrameworkElement)El<FrameworkElement>("QuickPresets").Parent).Parent).Visibility = Visibility.Collapsed; if (!Program.Reroller) return; var full = El<StackPanel>("TasksFullView"); foreach (UIElement c in full.Children) c.Visibility = Visibility.Collapsed; var card = new Border { Margin = new Thickness(0, 0, 2, 12) }; card.SetResourceReference(FrameworkElement.StyleProperty, "Card"); var p = new StackPanel(); var title = new TextBlock { Text = "Reroll to Forbidden", Margin = new Thickness(0, 0, 0, 8) }; title.SetResourceReference(FrameworkElement.StyleProperty, "CardTitle"); var hint = new TextBlock { Margin = new Thickness(0, 0, 0, 12), Text = "Elite rerolls until your whole crew is Forbidden." }; hint.SetResourceReference(FrameworkElement.StyleProperty, "Hint"); var values = Settings.RerollerAmounts.ToList(); if (!values.Contains(s.RerollerLimit)) { values.Add(s.RerollerLimit); values.Sort(); } var combo = new ComboBox { Height = 36, Width = 300, HorizontalAlignment = HorizontalAlignment.Left, ToolTip = "Taken out of the bank each time the cash runs out" }; foreach (var v in values) combo.Items.Add("Take " + SafehouseInfo.Money(v) + " out of the bank"); combo.SelectedIndex = values.IndexOf(s.RerollerLimit); combo.SelectionChanged += (a, b) => { if (combo.SelectedIndex >= 0) { s.RerollerLimit = values[combo.SelectedIndex]; Save(); } }; foreach (var sw in new[] { Tuple.Create("Full energy: do jobs", "RerollerJobs"), Tuple.Create("Full stamina: give stamina gifts", "RerollerGifts") }) var f = typeof(Settings).GetField(sw.Item2); var box = new CheckBox { Content = sw.Item1, Margin = new Thickness(0, 12, 0, 0), IsChecked = (bool)f.GetValue(s), HorizontalAlignment = HorizontalAlignment.Left, Width = 300 }; box.SetResourceReference(FrameworkElement.StyleProperty, "Switch"); box.Checked += (a, b) => { f.SetValue(s, true); Save(); }; box.Unchecked += (a, b) => { f.SetValue(s, false); Save(); }; var counts = new TextBlock { Margin = new Thickness(0, 14, 0, 0), FontSize = 15 }; counts.SetResourceReference(TextBlock.ForegroundProperty, "Text"); full.Children.Insert(0, card); El<TextBlock>("LogoText").Text = Program.Name; var logo = El<Grid>("LogoIcon"); logo.Width = logo.Height = 26; logo.Margin = new Thickness(0, 6, 10, 0); logo.Children.Add(new System.Windows.Shapes.Path { Stretch = Stretch.Uniform, Stroke = new SolidColorBrush(Color.FromRgb(0xE3, 0xB5, 0x3C)), StrokeThickness = 2.6, StrokeStartLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round, StrokeLineJoin = PenLineJoin.Round, Data = (Geometry)Window.FindResource("IcoReroll") }); foreach (var n in new[] { "BtnUpdate", "UpdateStatus" }) El<FrameworkElement>(n).Visibility = Visibility.Collapsed; ((FrameworkElement)((FrameworkElement)El<FrameworkElement>("QuickPresets").Parent).Parent).Visibility = Visibility.Collapsed; bot.CrewRead += i => crew = i; int startRolls = bot.Count.Rerolls; var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; if (bot.RerollOnly && !was) startRolls = bot.Count.Rerolls; string forb = c != null && c.Henchmen > 0 ? " · Forbidden: " + c.ByRarity[7] + " of " + Math.Max(0, c.Henchmen - 1) : ""; counts.Text = "This Start: " + (bot.Count.Rerolls - startRolls) + " rerolls · spent " + SafehouseInfo.Money((long)Math.Max(0, bot.RerollerSpent)) + forb; internal void ShowTasksView(bool simple) Show("TasksSimpleView", simple); Show("TasksFullView", !simple); var rb = El<RadioButton>(simple ? "TasksView_Simple" : "TasksView_Full"); if (rb.IsChecked != true) rb.IsChecked = true; if (s.TasksSimple != simple) { s.TasksSimple = simple; Save(); } Window.Dispatcher.BeginInvoke(new Action(UpdateTileStatus), System.Windows.Threading.DispatcherPriority.Loaded); void ShowCard(string name) var card = El<FrameworkElement>(name); Window.Dispatcher.BeginInvoke(new Action(() => card.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0.35, 1, TimeSpan.FromMilliseconds(380)) { RepeatBehavior = new RepeatBehavior(2) }); }), System.Windows.Threading.DispatcherPriority.Loaded); const string FreeRewards = "FreeRewards"; void SwitchAll(string key, string[] members, bool on) if (!on) s.GroupsOff = Settings.GroupsOffWith(s.GroupsOff, key, members.Where(IsOn).Select(SwitchField)); var was = on ? Settings.GroupOff(s.GroupsOff, key) : new List<string>(); var pick = !on ? members.ToList() : members.Where(n => was.Contains(SwitchField(n))).ToList(); if (pick.Count == 0) pick = members.Where(n => (bool)typeof(Settings).GetField(SwitchField(n)).GetValue(fresh)).ToList(); if (pick.Count == 0) pick = members.ToList(); foreach (var n in pick) El<CheckBox>(n).IsChecked = on; bot.Log("Tasks: " + string.Join(", ", pick.Select(n => El<CheckBox>(n).Content)) + (on ? " on" : " off")); if (fresh == null) return; foreach (var g in TaskGroups) var tile = El<CheckBox>("grp_" + g.Key); var on = g.Value.Where(IsOn).Select(n => Convert.ToString(El<CheckBox>(n).Content)).ToList(); string tip = on.Count == 0 ? "All off. A click turns them back on." : "On: " + string.Join(", ", on) + ". A click turns them all off."; if (!Equals(tile.ToolTip, tip)) tile.ToolTip = tip; Lit(El<CheckBox>("tile_Rewards"), RewardSwitches); finally { syncingBundles = false; } void Lit(CheckBox tile, string[] members) { bool any = members.Any(IsOn); if (tile.IsChecked != any) tile.IsChecked = any; } bool IsOn(string name) { return El<CheckBox>(name).IsChecked == true; } string OnCount(string[] members) { return members.Count(IsOn) + " of " + members.Length + " on"; } string GroupLine(string[] members) { return OnCount(members) + ": " + string.Join(", ", members.Where(IsOn).Select(n => El<CheckBox>(n).Content)); } static string[] GroupSwitches(string key) { return TaskGroups.First(g => g.Key == key).Value; } static string SwitchField(string name) { return name == "tile_Reroll" ? "CrewReroll" : name.Substring(name.IndexOf('_') + 1); } static Preset QuickPreset(Tuple<string, string, string[]> q) var p = new Preset { Name = q.Item1 }; foreach (var n in AllTasks) p.Values.Add(new KeyValuePair<string, string>(SwitchField(n), q.Item3.Contains(n) ? "True" : "False")); var now = QuickStarts.FirstOrDefault(q => QuickPreset(q).Matches(s)); string shown = now == null ? "" : now.Item1; if (shown == quickShown) return; var box = El<WrapPanel>("QuickPresets"); foreach (var q in QuickStarts) var b = new Button { Style = (Style)Window.FindResource("Btn"), Content = (on ? "✓ " : "") + q.Item1, Padding = new Thickness(14, 7, 14, 7), Margin = new Thickness(0, 0, 8, 0), ToolTip = q.Item2 }; if (on) b.Foreground = Res("Gold"); b.Click += (a, e) => UseQuickStart(p); void UseQuickStart(Preset p) bot.Log("Quick start: " + p.Name); presets = Preset.Load(dir); El<Button>("BtnPresets").Click += (a, b) => { ShowPresets(); OpenOptions("Presets", "Presets", (Geometry)Window.FindResource("IcoSliders")); }; var name = El<TextBox>("PresetNameBox"); name.TextChanged += (a, b) => El<TextBlock>("PresetNameHint").Visibility = name.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; name.KeyDown += (a, b) => { if (b.Key == Key.Enter) SavePreset(); }; El<Button>("BtnPresetSave").Click += (a, b) => SavePreset(); var code = El<TextBox>("PresetCodeBox"); code.TextChanged += (a, b) => { El<TextBlock>("PresetCodeHint").Visibility = code.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; CheckPastedCode(); }; El<Button>("BtnPresetPaste").Click += (a, b) => try { if (Clipboard.ContainsText()) code.Text = Clipboard.GetText(); } catch (Exception) { } El<Button>("BtnPresetAdd").Click += (a, b) => AddPasted(false); El<Button>("BtnPresetAddUse").Click += (a, b) => AddPasted(true); if (presets == null) return; var now = presets.FirstOrDefault(p => p.Matches(s)); string text = now == null ? "Presets" : now.Name; var t = El<TextBlock>("PresetNow"); if (t.Text != text) t.Text = text; void PresetSays(string text) { El<TextBlock>("PresetStatus").Text = text; } var list = El<StackPanel>("PresetList"); El<TextBlock>("PresetEmpty").Visibility = presets.Count == 0 ? Visibility.Visible : Visibility.Collapsed; foreach (var p in presets) bool changed = !on && Preset.SameName(p.Name, s.PresetInUse); row.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); var words = new StackPanel { VerticalAlignment = VerticalAlignment.Center }; words.Children.Add(new TextBlock { Text = p.Name, FontSize = 14.5, FontWeight = FontWeights.SemiBold, TextTrimming = TextTrimming.CharacterEllipsis }); words.Children.Add(new TextBlock { Text = on ? "On now" : changed ? "Changed since you used it" : PresetTasks(p, true).Count + " tasks on", FontSize = 12.5, Foreground = on || changed ? Res("Gold") : Res("Text3"), Margin = new Thickness(0, 2, 0, 0) }); var buttons = new StackPanel { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; Grid.SetColumn(buttons, 1); var use = new Button { Style = (Style)Window.FindResource(on ? "Btn" : "PrimaryBtn"), Content = "Use", Padding = new Thickness(16, 8, 16, 8), IsEnabled = !p.Matches(s, true) }; use.Click += (a, b) => UsePreset(preset); var copy = new Button { Style = (Style)Window.FindResource("Btn"), Content = "Copy code", Padding = new Thickness(14, 8, 14, 8), Margin = new Thickness(8, 0, 0, 0), ToolTip = "Copies this preset's code: in our Discord, /preset share and paste it" }; try { Clipboard.SetText(preset.Code()); PresetSays("\"" + preset.Name + "\" copied. In our Discord type /preset share and paste it: others press Get code, then \"Add a shared code\"."); } catch (Exception) { PresetSays("Couldn't copy it. Try again."); } var share = new Button { Style = (Style)Window.FindResource("Btn"), Content = "Share", Padding = new Thickness(14, 8, 14, 8), Margin = new Thickness(8, 0, 0, 0), ToolTip = "Posts this preset as a card in our Discord, with Add to my bot for others" }; share.Click += (a, b) => SharePreset(preset); share.Visibility = bot.Remote.On ? Visibility.Visible : Visibility.Collapsed; bool asked = deleteAsked == p.Name; var del = new Button { Style = (Style)Window.FindResource("GhostBtn"), Padding = new Thickness(12, 8, 12, 8), Margin = new Thickness(6, 0, 0, 0), Content = new TextBlock { Text = asked ? "Sure? Delete" : "Delete", FontWeight = FontWeights.SemiBold, Foreground = asked ? Res("BloodRed") : Res("Text2") } }; if (deleteAsked != preset.Name) { deleteAsked = preset.Name; ShowPresets(); return; } if (SavePresets()) PresetSays("\"" + preset.Name + "\" deleted."); var update = new Button { Style = (Style)Window.FindResource("PrimaryBtn"), Content = "Update", Padding = new Thickness(16, 8, 16, 8), Margin = new Thickness(0, 0, 8, 0), ToolTip = "Saves what's on now into this preset" }; update.Click += (a, b) => SavePresetAs(preset.Name); buttons.Children.Add(update); use.Style = (Style)Window.FindResource("Btn"); buttons.Children.Add(use); buttons.Children.Add(share); buttons.Children.Add(copy); buttons.Children.Add(del); row.Children.Add(buttons); list.Children.Add(new Border { Style = (Style)Window.FindResource("OptLine"), Child = row }); void SharePreset(Preset p) string why = bot.Remote.Share(p.Code()); if (why == "off") PresetSays("To share with one click, switch on Stats in our Discord and Remote control from Discord (Settings). Or press Copy code and type /preset share in our Discord."); else if (why != null) PresetSays("\"" + p.Name + "\" is too big to share this way. Press Copy code and type /preset share in our Discord."); else { PresetSays("Sharing \"" + p.Name + "\" in our Discord... (up to 15 seconds)"); bot.Log("Preset: sharing " + p.Name + " in our Discord"); } try { Preset.Save(dir, presets); bot.Remote.Wake(); return true; } catch (Exception e) { PresetSays("Couldn't save the presets: " + e.Message); bot.Log("Couldn't save the presets: " + e.Message); return false; } var box = El<TextBox>("PresetNameBox"); string name = Preset.CleanName(box.Text); if (name.Length == 0) { PresetSays("Type a name first."); box.Focus(); return; } if (SavePresetAs(name)) box.Text = ""; bool SavePresetAs(string name) var p = Preset.From(name, s); int old = presets.FindIndex(o => Preset.SameName(o.Name, name)); if (old >= 0) presets[old] = p; else presets.Add(p); if (!SavePresets()) return false; PresetSays(old >= 0 ? "\"" + p.Name + "\" now holds what's on." : "Saved \"" + p.Name + "\"."); bot.Log("Preset saved: " + p.Name); if (s.PresetInUse == name) return; int skipped = p.ApplyTo(s); string left = LeavesLine(p); PresetSays("Using \"" + p.Name + "\"." + (skipped > 0 ? " " + skipped + " of its settings didn't fit this version and stay as they were." : "") + (left.Length > 0 ? " " + left : "")); bot.Log("Preset: using " + p.Name + (skipped > 0 ? " (" + skipped + " settings left as they were)" : "")); List<string> PresetTasks(Preset p, bool on) var names = new List<string>(); foreach (var n in AllTasks) string v = p.Value(SwitchField(n)); bool isOn = v == "1" || v.Equals("true", StringComparison.OrdinalIgnoreCase); if (isOn == on) names.Add(Convert.ToString(El<CheckBox>(n).Content)); string text = El<TextBox>("PresetCodeBox").Text; var preview = El<Border>("PresetPreview"); if (text.Trim().Length == 0) { preview.Visibility = Visibility.Collapsed; PresetSays(""); return; } var p = Preset.Parse(text, out why); if (p == null) { preview.Visibility = Visibility.Collapsed; PresetSays(why); return; } bool taken = presets.Any(o => Preset.SameName(o.Name, p.Name)); El<TextBlock>("PresetPreviewTitle").Text = "Add \"" + p.Name + "\"?" + (taken ? " It replaces your preset of that name." : ""); var on = PresetTasks(p, true); var off = PresetTasks(p, false); El<TextBlock>("PresetPreviewOn").Text = "On: " + (on.Count == 0 ? "nothing" : string.Join(", ", on)); El<TextBlock>("PresetPreviewOff").Text = "Off: " + (off.Count == 0 ? "nothing" : string.Join(", ", off)); var money = PresetMoney(p); El<TextBlock>("PresetPreviewMoney").Text = string.Join("\n", money); Show("PresetPreviewMoney", money.Count > 0); El<TextBlock>("PresetPreviewLeft").Text = LeavesLine(p); Show("PresetPreviewLeft", LeavesLine(p).Length > 0); preview.Visibility = Visibility.Visible; PresetSays(p.Newer > 0 ? "This code is from a newer version of the bot: " + p.Newer + (p.Newer == 1 ? " setting" : " settings") + " in it won't work here. Update the bot (Settings → Check for updates) to get the whole preset." : "Your Discord links and your account stay as they are."); static readonly Tuple<string, Func<Settings, string>>[] MoneySettings = Tuple.Create<string, Func<Settings, string>>("Shop cash", x => ShopCashName(x.ShopCashMax)), Tuple.Create<string, Func<Settings, string>>("Shop gold", x => ShopGoldName(x.ShopGoldMax)), Tuple.Create<string, Func<Settings, string>>("Shop bank", x => !x.ShopWithdraw ? "Don't use the bank" : x.ShopBankFrom == (int)Rarity.Mythic ? "Mythic and up" : "Secret and up"), Tuple.Create<string, Func<Settings, string>>("Hire with", x => x.CrewRollType == CrewRoll.Street ? "Street $10K" : x.CrewRollType == CrewRoll.Professional ? "Pro $10M" : "Elite $10B"), Tuple.Create<string, Func<Settings, string>>("During Recruitment Luck", x => x.CrewLuckRoll == LuckRoll.Same ? "Same" : x.CrewLuckRoll == LuckRoll.Professional ? "Pro $10M" : "Elite $10B"), Tuple.Create<string, Func<Settings, string>>("After the best rarity", x => x.CrewRerollStats > 0 ? "Until every one is perfect" : "Stop at the best rarity"), Tuple.Create<string, Func<Settings, string>>("Buy new crew slots", x => x.CrewSlots ? "On" : "Off"), Tuple.Create<string, Func<Settings, string>>("Auto Roll", x => x.CrewAutoRoll ? RollLimitName(x.AutoRollLimit) : "Off"), Tuple.Create<string, Func<Settings, string>>("Start my own heist", x => x.StartHeists ? x.StartHeist : "Off"), Tuple.Create<string, Func<Settings, string>>("Buy animal food", x => x.AnimalBuyFood ? "On" : "Off"), Tuple.Create<string, Func<Settings, string>>("Buy animal crates", x => x.AnimalBuyCrates ? "On" : "Off"), Tuple.Create<string, Func<Settings, string>>("Animal shop", x => AnimalPriceName(x.AnimalMaxPrice)), Tuple.Create<string, Func<Settings, string>>("Which property to build", x => (x.PropertyOnly ?? "").Trim().Length == 0 ? "Best one" : x.PropertyOnly.Trim()), Tuple.Create<string, Func<Settings, string>>("Lots for gold properties", x => x.GoldLots.ToString(CultureInfo.InvariantCulture)), List<string> PresetMoney(Preset p) var then = new Settings(); foreach (var f in typeof(Settings).GetFields(BindingFlags.Public | BindingFlags.Instance)) f.SetValue(then, f.GetValue(s)); return MoneySettings.Where(m => m.Item2(then) != m.Item2(s)).Select(m => m.Item1 + ": " + m.Item2(then)).ToList(); string LeavesLine(Preset p) var whole = new List<string>(); var parts = new List<string>(); foreach (var f in Preset.Fields()) if (p.Value(f.Name) != null) continue; string name = TaskOf(f, out task); var list = task ? whole : parts; if (name != null && !list.Contains(name)) list.Add(name); parts.RemoveAll(whole.Contains); if (whole.Count + parts.Count == 0) return ""; return "Leaves as they are: " + AndList(whole) + (whole.Count > 0 && parts.Count > 0 ? ", and " : "") + (parts.Count > 0 ? "some options of " + AndList(parts) : "") + "."; static string AndList(List<string> l) return l.Count <= 1 ? string.Join("", l) : string.Join(", ", l.Take(l.Count - 1)) + " and " + l[l.Count - 1]; string TaskOf(FieldInfo f, out bool task) var own = AllTasks.FirstOrDefault(n => SwitchField(n) == f.Name); if (own != null) return Convert.ToString(El<CheckBox>(own).Content); if (f.Name == "RerollsFirst") return Convert.ToString(El<CheckBox>("tile_Reroll").Content); string box = f.Name.StartsWith("PointsStop") ? "txt_PointsStop" : f.Name == "ShopWithdraw" ? "cmb_ShopBankFrom" : f.Name == "StartHeists" ? "cmb_StartHeist" : null; var names = box != null ? new[] { box } : new[] { "sw_", "num_", "cmb_", "txt_" }.Select(pre => pre + f.Name) .Concat(f.FieldType.IsEnum ? Enum.GetNames(f.FieldType).Select(v => "seg_" + f.Name + "_" + v) : Enumerable.Empty<string>()); var c = names.Select(n => Window.FindName(n) as FrameworkElement).FirstOrDefault(e => e != null); if (c == null || c.Visibility == Visibility.Collapsed) return null; for (DependencyObject d = c; d != null; d = LogicalTreeHelper.GetParent(d)) var e = d as FrameworkElement; if (e.Name == "WhaleOnlyOpts" && e.Visibility == Visibility.Collapsed) return null; if (e.Name.StartsWith("Opt_")) var tile = tiles.FirstOrDefault(t => TileKey(t) == e.Name.Substring(4)); return tile == null ? null : Convert.ToString(tile.Content); if (pasted == null) return; int old = presets.FindIndex(o => Preset.SameName(o.Name, p.Name)); if (old >= 0) presets[old] = p; else presets.Add(p); if (!SavePresets()) return; El<TextBox>("PresetCodeBox").Text = ""; bot.Log("Preset added from a code: " + p.Name); else { PresetSays("Added \"" + p.Name + "\"."); ShowPresets(); } s.MarketMinutes = Math.Max(Settings.MarketMinutesLeast, Math.Min(60, s.MarketMinutes)); s.FightWinChance = Settings.WinChanceStep(s.FightWinChance); foreach (var f in typeof(Settings).GetFields(BindingFlags.Public | BindingFlags.Instance)) var sw = Window.FindName("sw_" + f.Name) as CheckBox; if (sw != null && f.FieldType == typeof(bool)) sw.IsChecked = (bool)f.GetValue(s); sw.Checked += (a, b) => Set(field, true); sw.Unchecked += (a, b) => Set(field, false); var holder = Window.FindName("num_" + f.Name) as ContentControl; if (holder != null && f.FieldType == typeof(int)) holder.Content = Stepper(field, (string)holder.Tag); var values = f.FieldType.IsEnum ? Enum.GetNames(f.FieldType).Select(n => (object)Enum.Parse(f.FieldType, n)) : f.FieldType == typeof(int) ? Enumerable.Range(0, 10).Concat(Settings.WinChances).Distinct().Select(n => (object)n) : Enumerable.Empty<object>(); foreach (var v in values) var seg = Window.FindName("seg_" + f.Name + "_" + v) as RadioButton; if (seg == null) continue; seg.IsChecked = Equals(f.GetValue(s), v); seg.Checked += (a, b) => Set(field, value); Combo("cmb_PointsStat", Game.StatNames, s.PointsStat, i => { s.PointsStat = i; ShowPointsStop(); }); perkChoices = new List<string> { ClosestPerk }; perkChoices.AddRange(Game.StaminaPerkNames); if (s.StaminaPerk.Trim().Length > 0 && !perkChoices.Any(n => Game.SamePerk(n, s.StaminaPerk))) perkChoices.Add(s.StaminaPerk.Trim()); int perk = s.StaminaPerk.Trim().Length == 0 ? 0 : perkChoices.FindIndex(n => n != ClosestPerk && Game.SamePerk(n, s.StaminaPerk)); Combo("cmb_StaminaPerk", perkChoices, Math.Max(0, perk), i => s.StaminaPerk = i == 0 ? "" : perkChoices[i]); s.CrewRerollStats = s.CrewRerollStats > 0 ? Settings.Perfect : 0; goalValues = Settings.StatsGoals.ToList(); var goalNames = Settings.StatsGoalNames.ToList(); Combo("cmb_CrewRerollStats", goalNames, goalValues.IndexOf(s.CrewRerollStats), i => s.CrewRerollStats = goalValues[i]); whaleValues = Settings.WhaleAmounts.ToList(); if (!whaleValues.Contains(s.WhaleCash)) { whaleValues.Add(s.WhaleCash); whaleValues.Sort(); } Combo("cmb_WhaleCash", whaleValues.Select(WhaleName).ToList(), whaleValues.IndexOf(s.WhaleCash), i => s.WhaleCash = whaleValues[i]); var picksBox = El<TextBox>("GarrisonPicksBox"); picksBox.Text = string.Join(Environment.NewLine, Bot.NeverAttackEntries(s.GarrisonPicks)); El<TextBlock>("GarrisonPicksHint").Visibility = picksBox.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; var unknown = new List<string>(); var got = Bot.GarrisonPicks(picksBox.Text, unknown); string said = got.Count > 0 ? string.Join(", ", got.Select((t, i) => (i + 1) + ". " + t.Item3 + " (" + t.Item1 + ")")) : ""; if (unknown.Count > 0) said += (said.Length > 0 ? " - " : "") + "not a district: " + string.Join(", ", unknown); if (Bot.NeverAttackEntries(picksBox.Text).Count - unknown.Count > 5) said += " - only the first 5 count"; El<TextBlock>("GarrisonPicksCheck").Text = said; picksBox.TextChanged += (a, b) => string v = string.Join(";", Bot.NeverAttackEntries(picksBox.Text)); if (v != (s.GarrisonPicks ?? "")) { s.GarrisonPicks = v; Save(); } var never = El<TextBox>("NeverAttackBox"); never.Text = string.Join(Environment.NewLine, Bot.NeverAttackEntries(s.NeverAttack)); El<TextBlock>("NeverAttackHint").Visibility = never.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; never.TextChanged += (a, b) => El<TextBlock>("NeverAttackHint").Visibility = never.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; string v = string.Join(";", Bot.NeverAttackEntries(never.Text)); if (v != (s.NeverAttack ?? "")) { s.NeverAttack = v; Save(); } var first = El<TextBox>("AttackFirstBox"); first.Text = string.Join(Environment.NewLine, Bot.NeverAttackEntries(s.AttackFirst)); El<TextBlock>("AttackFirstHint").Visibility = first.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; first.TextChanged += (a, b) => El<TextBlock>("AttackFirstHint").Visibility = first.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; string v = string.Join(";", Bot.NeverAttackEntries(first.Text)); if (v != (s.AttackFirst ?? "")) { s.AttackFirst = v; Save(); } var spare = El<TextBox>("NeverDefendBox"); spare.Text = string.Join(Environment.NewLine, Bot.NeverAttackEntries(s.NeverDefend)); El<TextBlock>("NeverDefendHint").Visibility = spare.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; spare.TextChanged += (a, b) => El<TextBlock>("NeverDefendHint").Visibility = spare.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; string v = string.Join(";", Bot.NeverAttackEntries(spare.Text)); if (v != (s.NeverDefend ?? "")) { s.NeverDefend = v; Save(); } payerValues = Settings.WhaleAmounts.ToList(); if (!payerValues.Contains(s.BigPayerCash)) { payerValues.Add(s.BigPayerCash); payerValues.Sort(); } Combo("cmb_BigPayerCash", payerValues.Select(PayerName).ToList(), payerValues.IndexOf(s.BigPayerCash), i => s.BigPayerCash = payerValues[i]); El<Button>("BtnBigPayersSave").Click += (a, b) => SaveBigPayers(); rollValues = Settings.AutoRollLimits.ToList(); if (!rollValues.Contains(s.AutoRollLimit)) { rollValues.Add(s.AutoRollLimit); rollValues.Sort(); } Combo("cmb_AutoRollLimit", rollValues.Select(RollLimitName).ToList(), rollValues.IndexOf(s.AutoRollLimit), i => s.AutoRollLimit = rollValues[i]); animalValues = Settings.AnimalMaxPrices.ToList(); if (!animalValues.Contains(s.AnimalMaxPrice)) { animalValues.Add(s.AnimalMaxPrice); animalValues.Sort(); } Combo("cmb_AnimalMaxPrice", animalValues.Select(AnimalPriceName).ToList(), animalValues.IndexOf(s.AnimalMaxPrice), i => s.AnimalMaxPrice = animalValues[i]); ShopPick("cmb_ShopCashMax", Settings.ShopCashMaxes, () => s.ShopCashMax, v => s.ShopCashMax = v, ShopCashName); ShopPick("cmb_ShopGoldMax", Settings.ShopGoldMaxes, () => s.ShopGoldMax, v => s.ShopGoldMax = (int)v, ShopGoldName); ShopPick("cmb_ShopBankFrom", new long[] { 0, (int)Rarity.Mythic, (int)Rarity.Secret }, () => s.ShopWithdraw ? s.ShopBankFrom : 0, v => { s.ShopWithdraw = v > 0; if (v > 0) s.ShopBankFrom = (int)v; }, v => v <= 0 ? "Don't use the bank" : v == (int)Rarity.Mythic ? "Mythic and up" : "Secret and up"); ShopPick("cmb_ShopPing", new long[] { 0, (int)Rarity.Mythic, (int)Rarity.Secret }, () => s.ShopPing ? s.ShopPingFrom : 0, v => { s.ShopPing = v > 0; if (v > 0) s.ShopPingFrom = (int)v; }, v => v <= 0 ? "No messages" : v == (int)Rarity.Mythic ? "Mythic and up" : "Secret and up"); bankAtValues = Settings.BankAts.Where(v => v > 0).ToList(); if (s.BankAt > 0 && !bankAtValues.Contains(s.BankAt)) { bankAtValues.Add(s.BankAt); bankAtValues.Sort(); } bankAtValues.Add(BankAtCustom); Combo("cmb_BankAt", bankAtValues.Select(BankAtName).ToList(), bankAtValues.IndexOf(s.BankAt > 0 ? s.BankAt : BankAtCustom), i => BankAtPick(bankAtValues[i])); var bankTxt = El<TextBox>("txt_BankAt"); bankTxt.LostKeyboardFocus += (a, b) => CommitBankAt(bankTxt.Text); bankTxt.KeyDown += (a, b) => { if (b.Key == Key.Enter) { b.Handled = true; CommitBankAt(bankTxt.Text); } }; El<RadioButton>("seg_BankMode_Round").Checked += (a, b) => { if (!bankModeSync) { s.BankAt = 0; ShowBankMode(); Save(); UpdateDerived(); } }; El<RadioButton>("seg_BankMode_Amount").Checked += (a, b) => if (bankModeSync) return; if (s.BankAt <= 0) s.BankAt = bankAtValues.First(v => v > 0); ShowBankMode(); Save(); UpdateDerived(); Combo("cmb_StartHeist", new[] { "Off" }.Concat(Game.HeistNames.Select((n, i) => n + " (" + Money(Game.HeistStakes[i]) + " stake)")).ToList(), HeistChoice(), i => { s.StartHeists = i > 0; if (i > 0) s.StartHeist = Game.HeistNames[i - 1]; }); Combo("cmb_PropertyOnly", new List<string>(), 0, i => { if (!fillingProperties && i >= 0 && i < propertyChoices.Count && propertyChoices[i] != null) s.PropertyOnly = i == 0 ? "" : propertyChoices[i]; }); Combo("cmb_OperationOnly", new[] { BestOperation }.Concat(Game.OpNames.Select(Game.OpLabel)).ToList(), OperationChoice(), i => s.OperationOnly = i == 0 ? "" : Game.OpNames[i - 1]); El<FrameworkElement>("sw_MarketRelist").Visibility = Settings.MarketRelistShown ? Visibility.Visible : Visibility.Collapsed; var whens = new[] { "Problems", "Rarity", "Property", "Summary", "Level", "Boss", "GlobalBoss" }.Select(n => El<CheckBox>("sw_Discord" + n)).ToList(); Action count = () => El<TextBlock>("DiscordWhenCount").Text = whens.Count(c => c.IsChecked == true) + " of " + whens.Count + " on"; foreach (var c in whens) { c.Checked += (a, b) => count(); c.Unchecked += (a, b) => count(); } const string BestOperation = "Best XP per hour (automatic)"; return 1 + Array.FindIndex(Game.OpNames, n => Game.SameOp(n, s.OperationOnly ?? "")); const string BestProperty = "Best one (automatic)"; List<string> propertyChoices = new List<string> { BestProperty }; static DataTemplate PropertyTag(bool gold) var row = new FrameworkElementFactory(typeof(StackPanel)); row.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal); var name = new FrameworkElementFactory(typeof(TextBlock)); name.SetBinding(TextBlock.TextProperty, new System.Windows.Data.Binding()); name.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center); var chip = new FrameworkElementFactory(typeof(Border)); var ink = gold ? Color.FromRgb(0xE0, 0xB8, 0x4A) : Color.FromRgb(0x7C, 0xC4, 0x6A); chip.SetValue(Border.BackgroundProperty, new SolidColorBrush(Color.FromArgb(0x33, ink.R, ink.G, ink.B))); chip.SetValue(Border.CornerRadiusProperty, new CornerRadius(4)); chip.SetValue(Border.PaddingProperty, new Thickness(5, 0, 5, 1)); chip.SetValue(FrameworkElement.MarginProperty, new Thickness(8, 0, 0, 0)); chip.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center); var word = new FrameworkElementFactory(typeof(TextBlock)); word.SetValue(TextBlock.TextProperty, gold ? "GOLD" : "CASH"); word.SetValue(TextBlock.FontSizeProperty, 9.5); word.SetValue(TextBlock.FontWeightProperty, FontWeights.Bold); word.SetValue(TextBlock.ForegroundProperty, new SolidColorBrush(ink)); return new DataTemplate { VisualTree = row }; DataTemplate cashTag, goldTag; var c = El<ComboBox>("cmb_PropertyOnly"); if (cashTag == null) { cashTag = PropertyTag(false); goldTag = PropertyTag(true); } var names = new List<string> { BestProperty }; var gold = new HashSet<string>(GameData.GoldProperties); Func<string, bool> listed = n => names.Any(m => m != null && m != BestProperty && Bot.SameProperty(m, n)); foreach (var city in GameData.Properties) foreach (var n in city.Value) names.Add(n); var extra = new List<string>(); foreach (var t in (s.PropertySeen ?? "").Split('|').Select(t => t.Trim())) bool g = t.StartsWith("g:"); string n = g ? t.Substring(2).Trim() : t; if (n.Length == 0 || listed(n) || extra.Any(m => Bot.SameProperty(m, n))) continue; string only = (s.PropertyOnly ?? "").Trim(); if (only.Length > 0 && !listed(only) && !extra.Any(n => Bot.SameProperty(n, only))) extra.Add(only); names.Add("Read in your game"); fillingProperties = true; var choices = new List<string>(); for (int i = 0; i < names.Count; i++) c.Items.Add(new ComboBoxItem { Content = names[++i].ToUpperInvariant(), IsEnabled = false, FontSize = 11.5, FontWeight = FontWeights.Bold, Foreground = new SolidColorBrush(Color.FromRgb(0xC9, 0xA2, 0x4A)), Margin = new Thickness(0, 6, 0, 0) }); if (at == 0 && i > 0 && only.Length > 0 && Bot.SameProperty(names[i], only)) at = c.Items.Count; var item = new ComboBoxItem { Content = names[i], Padding = new Thickness(i == 0 ? 4 : 16, 3, 4, 3) }; if (i > 0) item.ContentTemplate = gold.Contains(names[i]) || gold.Any(g => Bot.SameProperty(g, names[i])) ? goldTag : cashTag; propertyChoices = choices; finally { fillingProperties = false; } static string ShopCashName(long v) string m = "Up to " + SafehouseInfo.Money(v); return v <= 0 ? "Any price" : m + (v <= 1000000 ? " (cheap items)" : v <= 100000000 ? " (Rare and below)" : v <= 1000000000 ? " (Legendary and below)" : v <= 50000000000 ? " (Mythic and below)" : v <= 1000000000000 ? " (the cheapest Secrets)" : v <= 5000000000000 ? " (cheap Secrets)" : v <= 20000000000000 ? " (most Secrets)" : " (all Secrets)"); static string ShopGoldName(long v) return v <= 0 ? "Don't buy with gold" : "Up to " + v + " gold" + (v <= 10 ? " (Military Crates)" : v <= 20 ? " (Epic and below)" : v <= 40 ? " (Legendary and below)" : v <= 500 ? " (Mythic and below)" : v <= 1000 ? " (Secret and below)" : " (everything)"); sealed class ShopChoice { public string Box; public List<long> Values; public Func<long> Get; public Func<long, string> Name; } readonly List<ShopChoice> shopChoices = new List<ShopChoice>(); void ShopPick(string box, IEnumerable<long> values, Func<long> get, Action<long> set, Func<long, string> name) var list = values.ToList(); if (!list.Contains(get())) { list.Add(get()); list.Sort(); } shopChoices.Add(new ShopChoice { Box = box, Values = list, Get = get, Name = name }); Combo(box, list.Select(name).ToList(), list.IndexOf(get()), i => set(list[i])); const long BankAtCustom = -1; static string BankAtName(long v) { return v < 0 ? "My own amount\u2026" : SafehouseInfo.Money(v); } El<RadioButton>("seg_BankMode_Round").IsChecked = s.BankAt <= 0; El<RadioButton>("seg_BankMode_Amount").IsChecked = s.BankAt > 0; El<Border>("BankAtRow").Visibility = s.BankAt > 0 ? Visibility.Visible : Visibility.Collapsed; if (!bankAtValues.Contains(s.BankAt)) InsertBankAt(s.BankAt); El<ComboBox>("cmb_BankAt").SelectedIndex = bankAtValues.IndexOf(s.BankAt); void InsertBankAt(long v) int pos = bankAtValues.Count(x => x > 0 && x < v); bankAtValues.Insert(pos, v); El<ComboBox>("cmb_BankAt").Items.Insert(pos, BankAtName(v)); El<Border>("txt_BankAtBox").Visibility = custom ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("BankAtHint").Visibility = custom ? Visibility.Visible : Visibility.Collapsed; if (custom) El<TextBox>("txt_BankAt").Focus(); void CommitBankAt(string text) text = (text ?? "").Trim(); if (text.Length == 0) return; var m = Regex.Match(text, @"^\$?\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s?([KMBT])?$", RegexOptions.IgnoreCase); if (!double.TryParse(m.Groups[1].Value.Replace(",", ""), NumberStyles.Float, CultureInfo.InvariantCulture, out amt)) amt = -1; switch (m.Groups[2].Value.ToUpperInvariant()) case "K": amt *= 1e3; break; case "M": amt *= 1e6; break; case "B": amt *= 1e9; break; case "T": amt *= 1e12; break; El<TextBlock>("BankAtHint").Text = "Couldn't read that amount - use something like 25B, 750M or 2500000000."; long bank = (long)Math.Min(amt, 1e15); if (!bankAtValues.Contains(bank)) InsertBankAt(bank); El<TextBlock>("BankAtHint").Text = ""; El<ComboBox>("cmb_BankAt").SelectedIndex = bankAtValues.IndexOf(bank); static string RollLimitName(long v) { return "Stop a slot at " + SafehouseInfo.Money(v) + " spent"; } static string AnimalPriceName(long v) { return "Up to " + SafehouseInfo.Money(v); } static string WhaleName(long v) { return "A win of " + SafehouseInfo.Money(v) + " or more"; } internal static string WhaleHintFor(Settings s) int hits = s.WhaleHitsNow; string hunt = hits == 1 ? "One hit only: nobody is hit again after a big win." : "Hits a player again after a big win, up to " + hits + " times."; const string remember = "Remember players attacks a big win's player first for a day."; return s.RememberPlayers && !s.WhaleHunt ? remember : s.RememberPlayers ? hunt + " " + remember : hunt; static string PayerName(long v) { return "Paid " + SafehouseInfo.Money(v) + " or more in a win"; } void UsePayers(FightMemory m) if (payers != null) payers.Changed -= PayersChanged; if (payers != null) payers.Changed += PayersChanged; void PayersChanged() { Ui(RefreshBigPayers); } if (payers == null) return; El<TextBlock>("FightRespect").Text = "Player fights written down: " + payers.RespectSummary(DateTime.UtcNow); var now = DateTime.UtcNow; var list = payers.BigPayers(s.BigPayerCash, now); payerRows.Add(new BigPayerView Name = name, Biggest = Money(p.Biggest), Last = p.Last >= 0 ? Money(p.Last) : "-", When = Ago(p.LastBig, now), Record = p.Won + "W " + p.Lost + "L", RecordTip = p.Fights + (p.Fights == 1 ? " fight" : " fights") + " in the last 3 days (the last 10 are kept): " + p.Won + " won, " + p.Lost + " lost", RowBg = odd ? Res("Panel2") : Brushes.Transparent, Forget = new RelayCommand(() => ForgetPayer(name)), bool none = payerRows.Count == 0; El<TextBlock>("BigPayersEmpty").Visibility = none ? Visibility.Visible : Visibility.Collapsed; El<Grid>("BigPayersHead").Visibility = none ? Visibility.Collapsed : Visibility.Visible; El<TextBlock>("BigPayersRead").Text = n == 0 ? "" : n + (n == 1 ? " player" : " players") + " remembered"; if (payers == null) return; var now = DateTime.UtcNow; var list = payers.BigPayers(s.BigPayerCash, now); var d = new Microsoft.Win32.SaveFileDialog Title = "Save the Big payers list", Filter = "Text file (*.txt)|*.txt", FileName = "Big payers " + DateTime.Now.ToString("dd-MM-yyyy HH.mm", CultureInfo.InvariantCulture) + ".txt", InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), if (d.ShowDialog(Window) != true) return; var t = new System.Text.StringBuilder(); t.AppendLine("Big payers: paid " + SafehouseInfo.Money(s.BigPayerCash) + " or more in a win, the last 3 days"); t.AppendLine("Saved " + DateTime.Now.ToString("dd-MM-yyyy HH:mm", CultureInfo.InvariantCulture) + " by Idle Mafia Bot"); if (list.Count == 0) t.AppendLine("No player has paid that much in the last 3 days."); int w = Math.Max(6, list.Max(p => p.Name.Length)) + 2; t.AppendLine("PLAYER".PadRight(w) + "BIGGEST".PadLeft(10) + "LAST".PadLeft(10) + "LAST BIG ONE".PadLeft(16) + "FIGHTS".PadLeft(10)); t.AppendLine(p.Name.PadRight(w) + Money(p.Biggest).PadLeft(10) + (p.Last >= 0 ? Money(p.Last) : "-").PadLeft(10) + Ago(p.LastBig, now).PadLeft(16) + (p.Won + "W " + p.Lost + "L").PadLeft(10)); System.IO.File.WriteAllText(d.FileName, t.ToString(), new System.Text.UTF8Encoding(false)); if (payers == bot.Memory) bot.Log("Fights: Big payers list saved (" + list.Count + " players)"); try { System.Diagnostics.Process.Start("explorer.exe", "/select,\"" + d.FileName + "\""); } catch { } catch (Exception e) { MessageBox.Show(Window, "Couldn't save the list: " + e.Message, "Big payers"); } void ForgetPayer(string name) if (payers == null || !payers.Forget(name)) { RefreshBigPayers(); return; } if (payers == bot.Memory) bot.Log("Fights: forgot " + name + " (Big payers list)"); internal static string Ago(DateTime utc, DateTime nowUtc) if (d.TotalMinutes < 1) return "just now"; if (d.TotalMinutes < 60) return (int)d.TotalMinutes + " min ago"; if (d.TotalHours < 24) return (int)d.TotalHours + " h ago"; return utc.ToLocalTime().ToString("dd-MM HH:mm", CultureInfo.InvariantCulture); var seg = Window.FindName("seg_CrewRollType_" + s.CrewRollType) as RadioButton; if (seg != null && seg.IsChecked != true) seg.IsChecked = true; void Combo(string name, IList<string> items, int index, Action<int> set) var c = El<ComboBox>(name); foreach (var it in items) c.Items.Add(it); if (items.Count > 0) c.SelectedIndex = Math.Max(0, Math.Min(items.Count - 1, index)); c.SelectionChanged += (a, b) => { if (c.SelectedIndex >= 0) { set(c.SelectedIndex); Save(); UpdateDerived(); } }; static readonly string[] PointsStopFields = { "PointsStopEnergy", "PointsStopStamina", "PointsStopHealth", "PointsStopAttack", "PointsStopDefense", "PointsStopCapacity" }; var box = El<TextBox>("txt_PointsStop"); box.PreviewTextInput += (a, b) => b.Handled = !b.Text.All(char.IsDigit); DataObject.AddPastingHandler(box, (a, b) => { var t = b.SourceDataObject.GetData(DataFormats.UnicodeText) as string; if (t == null || !t.All(char.IsDigit)) b.CancelCommand(); }); box.TextChanged += (a, b) => if (!int.TryParse(box.Text, out v)) v = 0; var field = typeof(Settings).GetField(PointsStopFields[Math.Max(0, Math.Min(PointsStopFields.Length - 1, s.PointsStat))]); if ((int)field.GetValue(s) != v) Set(field, v); int stop = s.PointsStop(s.PointsStat); El<TextBox>("txt_PointsStop").Text = stop > 0 ? stop.ToString(CultureInfo.InvariantCulture) : ""; FrameworkElement Stepper(FieldInfo f, string tag) var p = (tag ?? "0,100,1,").Split(','); int min = int.Parse(p[0]), max = int.Parse(p[1]), step = int.Parse(p[2]); string suffix = p.Length > 3 && p[3].Length > 0 ? " " + p[3] : ""; f.SetValue(s, Math.Max(min, Math.Min(max, (int)f.GetValue(s)))); var value = new TextBlock { FontWeight = FontWeights.Bold, FontSize = 14.5, MinWidth = 70, TextAlignment = TextAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; Action show = () => value.Text = ((int)f.GetValue(s)).ToString(CultureInfo.InvariantCulture) + suffix; Action<int> change = d => { Set(f, Math.Max(min, Math.Min(max, (int)f.GetValue(s) + d))); show(); }; var minus = new RepeatButton { Content = "−", Style = (Style)Window.FindResource("StepBtn") }; var plus = new RepeatButton { Content = "+", Style = (Style)Window.FindResource("StepBtn") }; minus.Click += (a, b) => change(-step); plus.Click += (a, b) => change(step); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); Grid.SetColumn(value, 1); grid.Children.Add(minus); grid.Children.Add(value); Background = Res("Field"), BorderBrush = Res("Line"), BorderThickness = new Thickness(1.5), CornerRadius = new CornerRadius(10), Height = 40, Padding = new Thickness(3), Child = grid, Margin = new Thickness(16, 0, 0, 0), ToolTip = "Click or hold. The mouse wheel works after a click, or with Ctrl held.", box.MouseWheel += (a, b) => if (!box.IsKeyboardFocusWithin && (Keyboard.Modifiers & ModifierKeys.Control) == 0) return; change(b.Delta > 0 ? step : -step); void Set(FieldInfo f, object value) void ShowInstances(List<Instance> list) var tabs = El<StackPanel>("AccountTabs"); El<Border>("AccountBar").Visibility = list != null && list.Count > 1 ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("StatsTurnsHint").Visibility = El<Border>("AccountBar").Visibility; if (list == null || list.Count <= 1) return; var tab = new RadioButton Style = (Style)Window.FindResource("Segment"), GroupName = "account", Content = i.Label, Tag = i, IsChecked = i.Handle == bot.Pinned, ToolTip = i.Name != null ? "Show " + i.Name + "'s settings - Start plays this account" : "Press to bring this Roblox window to the front and read its account", tab.Checked += (a, b) => { var which = (Instance)tab.Tag; if (which.Name == null) ReadAccount(which); else PickAccount(which); }; void ReadAccount(Instance which) if (bot.Running) { ShowInstances(bot.InstancesSeen); return; } new System.Threading.Thread(() => bool ok = bot.ReadWindow(which); if (ok) PickAccount(which); ShowInstances(bot.InstancesSeen); }) { IsBackground = true }.Start(); void PickAccount(Instance which) if (which.Handle == bot.Pinned) return; if (bot.Running) { Ui(() => ShowInstances(bot.InstancesSeen)); return; } if (!bot.PlayOn(which)) Ui(() => ShowInstances(bot.InstancesSeen)); try { s.Save(System.IO.Path.Combine(dir, "IdleMafiaBot.ini")); } catch (Exception e) { SaveFailed(e); } if (s.Account.Length > 0) try { System.IO.Directory.CreateDirectory(Accounts.Folder(dir, s.Account)); s.Save(Accounts.Ini(dir, s.Account), true); } catch (Exception e) { SaveFailed(e); } void SaveFailed(Exception e) if (e.Message == saveFailed) return; bot.Log("Couldn't save the settings: " + e.Message); if (optionsOpen == "Jobs") RefreshJobs(); El<TextBlock>("PerkHint").Text = s.StaminaPerk.Trim().Length == 0 ? "The perk closest to its next level gets it." : "Always " + s.StaminaPerk.Trim() + ", until it's maxed."; El<TextBlock>("WhaleHint").Text = WhaleHintFor(s); if (crewSeen != null) ShowCrew(crewSeen); if (ReviewShowing) RefreshReview(); var ic = CultureInfo.InvariantCulture; var odds = El<WrapPanel>("CrewOdds"); int roll = (int)s.CrewRollType; for (int r = 7; r >= 0; r--) if (CrewInfo.Odds[roll, r] > 0) odds.Children.Add(RarityChip(r, Game.RarityTitle(r) + " " + CrewInfo.Odds[roll, r].ToString("0.##", ic) + "%")); int best = Game.BestRoll(s.CrewRollType); El<TextBlock>("CrewRollHint").Text = "Until everyone is " + Game.RarityTitle(best) + ", the best " + s.CrewRollType + " hires can reach." + (best < 7 ? " Only Elite can reach Forbidden." : ""); foreach (var mode in new[] { "Off", "On", "First", "Only" }) El<RadioButton>("RollMode_" + m).Checked += (a, b) => RollModePicked(m); var tile = El<CheckBox>("tile_Reroll"); tile.Checked += (a, b) => { if (!syncingRoll && RollMode() == "Off") El<RadioButton>("RollMode_On").IsChecked = true; }; tile.Unchecked += (a, b) => { if (!syncingRoll) El<RadioButton>("RollMode_Off").IsChecked = true; }; void RollModePicked(string m) if (Covered) { SyncRollMode(); return; } ShowState("Working: only rerolling the crew"); if (bot.RerollOnly) { bot.StopRerollOnly(); ShowState("Stopping"); } s.CrewReroll = m != "Off"; s.RerollsFirst = m == "First"; string RollMode() { return bot.RerollOnly ? "Only" : !s.CrewReroll ? "Off" : s.RerollsFirst ? "First" : "On"; } var rb = El<RadioButton>("RollMode_" + m); var tile = El<CheckBox>("tile_Reroll"); if (rb.IsChecked != true || tile.IsChecked != (m != "Off")) try { rb.IsChecked = true; tile.IsChecked = m != "Off"; } finally { syncingRoll = false; } string then = s.CrewRerollStats > 0 ? ", then their own attack and defense" : ""; El<TextBlock>("RollModeHint").Text = m == "Off" ? "No rerolls." : m == "On" ? "In between the other tasks." : m == "First" ? "Before the other tasks. Full bars, the bank and the shop still get their turn. Back to On once the crew is done." : "Nothing else: rerolls with the cash on hand until the crew is done" + then + ", then the bot stops."; foreach (var mode in new[] { "Off", "On", "Only" }) El<RadioButton>("FightMode_" + m).Checked += (a, b) => FightModePicked(m); var tile = El<CheckBox>("sw_Fights"); tile.Checked += (a, b) => { if (!syncingFight && FightMode() == "Off") El<RadioButton>("FightMode_On").IsChecked = true; }; tile.Unchecked += (a, b) => { if (!syncingFight) El<RadioButton>("FightMode_Off").IsChecked = true; }; void FightModePicked(string m) if (syncingFight) return; if (Covered) { SyncFightMode(); return; } ShowState("Working: only fighting players"); if (bot.FightOnly) { bot.StopFightOnly(); ShowState("Stopping"); } string FightMode() { return bot.FightOnly ? "Only" : !s.Fights ? "Off" : "On"; } var rb = El<RadioButton>("FightMode_" + m); var tile = El<CheckBox>("sw_Fights"); if (rb.IsChecked != true || tile.IsChecked != (m != "Off")) try { rb.IsChecked = true; tile.IsChecked = m != "Off"; } finally { syncingFight = false; } El<TextBlock>("FightModeHint").Text = m == "Off" ? "No player fights." : m == "On" ? "In between the other tasks, with the stamina the bosses leave." : s.OnlyFightsSpendAll ? "Nothing else: attacks until the stamina bar is empty, then waits for it. Stops when nobody is worth attacking." : "Nothing else: attacks with the stamina above the bosses' reserve, then waits for it. Stops when nobody is worth attacking."; static readonly Dictionary<string, string> TaskOptions = new Dictionary<string, string> { "Rejoin", "RejoinOpts" }, { "Jobs", "JobsOpts" }, { "Heists", "HeistsOpts" }, { "Bosses", "BossesOpts" }, { "RespectCycle", "RespectOpts" }, { "CrewFill", "CrewFillOpts" }, { "TrainCrew", "TrainOpts" }, { "GiveStamina", "GiveToOpts" }, { "DefendTerritory", "DefendOpts" }, { "GlobalBoss", "GlobalBossOpts" }, { "Animals", "AnimalOpts" }, { "Bank", "BankOpts" }, { "MarketRelist", "RelistEveryRow" }, { "Properties", "PropertiesOpts" }, { "SpendPoints", "PointsOpts" }, { "ShopBuy", "ShopOpts" }, { "Operations", "OperationsOpts" }, string fight = FightMode(); Show("FightsOpts", fight != "Off"); Show("SpendAllRow", fight == "Only"); Show("FightWinRow", s.ScoutFights); Show("BigPayersRow", s.RememberPlayers); Show("WhaleOpts", s.WhaleHunt || s.RememberPlayers); Show("num_WhaleHits", s.WhaleHunt); Show("RerollOpts", RollMode() != "Off"); Show("HireOpts", s.CrewFill || RollMode() != "Off"); Show("AutoRollLimitRow", s.CrewAutoRoll); Show("LuckRow", s.Events); bool pingable = Discord.IsLink(s.DiscordLink); Show("ShopPingLine", pingable); string pingSays = pingable && s.ShopPing && !Discord.IsUserId(s.DiscordUserId) ? "A message only: type your Discord user ID in Settings, Discord messages, to be @mentioned." : null; El<TextBlock>("ShopPingHint").Text = pingSays ?? ""; Show("ShopPingHint", pingSays != null); Show("AnimalPriceRow", s.AnimalBuyFood || s.AnimalBuyCrates); foreach (var kv in TaskOptions) Show(kv.Value, (bool)typeof(Settings).GetField(kv.Key).GetValue(s)); if (optionsOpen != null && TaskOptions.ContainsKey(optionsOpen) && !(bool)typeof(Settings).GetField(optionsOpen).GetValue(s)) off = "Turn on \"" + El<CheckBox>("sw_" + optionsOpen).Content + "\" for more options."; El<TextBlock>("OptionsOff").Text = off ?? ""; Show("OptionsOff", off != null); void Show(string name, bool on) { El<FrameworkElement>(name).Visibility = on ? Visibility.Visible : Visibility.Collapsed; } void ShowCrew(CrewInfo ci) var ic = CultureInfo.InvariantCulture; El<TextBlock>("CrewWhen").Text = ci == null ? "" : "read at " + ci.At.ToString("HH:mm"); El<TextBlock>("CrewCount").Text = ci == null || ci.Henchmen < 0 ? "-" : ci.Henchmen + (ci.Slots > 0 ? " / " + ci.Slots : ""); El<TextBlock>("CrewAttack").Text = ci == null || ci.Attack < 0 ? "-" : "+" + ci.Attack.ToString("N0", ic); El<TextBlock>("CrewDefense").Text = ci == null || ci.Defense < 0 ? "-" : "+" + ci.Defense.ToString("N0", ic); El<TextBlock>("CrewSlot").Text = ci == null || ci.SlotPrice <= 0 ? "-" : Money(ci.SlotPrice); var bar = El<Grid>("CrewBar"); bar.ColumnDefinitions.Clear(); var chips = El<WrapPanel>("CrewChips"); int total = ci == null ? 0 : ci.ByRarity.Sum(); bar.Children.Add(new Border { CornerRadius = new CornerRadius(5), Background = Res("Track") }); for (int r = 7; r >= 0; r--) bar.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(n, GridUnitType.Star) }); var block = new Border { CornerRadius = new CornerRadius(5), Background = Res(RarityBrushes[r]), Margin = new Thickness(0, 0, 3, 0), ToolTip = n + " " + Game.RarityTitle(r) }; Grid.SetColumn(block, bar.ColumnDefinitions.Count - 1); chips.Children.Add(RarityChip(r, n + " " + Game.RarityTitle(r))); string status = ci == null ? "Not read yet. The bot looks at your crew every 30 minutes while it runs, every 6 hours with all its tiles off." : ci.Status; if (ci != null && status.Length == 0) status = s.CrewReroll ? "Nothing to do right now." : "Rerolls are off. Pick On below to reroll the worst henchman."; if (ci != null && s.CrewReroll && ci.Worst.Length > 0 && !status.StartsWith("Rerolling")) status += " The worst now: " + ci.Worst + "."; El<TextBlock>("CrewStatus").Text = status; FrameworkElement RarityChip(int rarity, string text) var p = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 18, 6) }; p.Children.Add(new Ellipse { Width = 9, Height = 9, Fill = Res(RarityBrushes[rarity]), VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 1, 7, 0) }); p.Children.Add(new TextBlock { Text = text, FontSize = 13, Foreground = Res("Text2"), VerticalAlignment = VerticalAlignment.Center }); var box = El<PasswordBox>("DiscordLinkBox"); var test = El<Button>("BtnDiscordTest"); box.Password = s.DiscordLink ?? ""; string t = box.Password.Trim(), saved = (s.DiscordLink ?? "").Trim(); El<TextBlock>("DiscordLinkHint").Visibility = box.Password.Length == 0 ? Visibility.Visible : Visibility.Collapsed; Show("DiscordButtons", t.Length > 0 || saved.Length > 0); Show("DiscordMore", saved.Length > 0); if (t.Length == 0) DiscordSays("Off. Nothing is sent without a link.", null); else if (t == (s.StatsLink ?? "").Trim()) DiscordSays(Discord.StatsLinkHere, "Red"); else if (Discord.IsLink(t)) string trouble = t == saved ? bot.Discord.Trouble : null; if (trouble != null) DiscordSays(trouble, "Red"); else DiscordSays("On. Messages go to your channel. Send a test to check it.", "Green"); else if (saved.Length > 0) DiscordSays("That isn't a Discord webhook link. The link saved before stays until you paste a new one or press Remove.", "Red"); else DiscordSays("That isn't a Discord webhook link, so it's off. A link starts with https://discord.com/api/webhooks/", "Red"); bot.Discord.Changed += () => Ui(show); box.PasswordChanged += (a, b) => string t = box.Password.Trim(); if ((t.Length == 0 || Discord.IsLink(t)) && t != (s.DiscordLink ?? "") && (t.Length == 0 || t != (s.StatsLink ?? "").Trim())) Accounts.ForgetLink(dir, s.Account); DiscordSays("Sending...", null); bot.Discord.Test(box.Password, Discord.ShotOfRoblox, why => Ui(() => if (why.Length == 0) { DiscordSays("Sent. Look in your Discord channel.", "Green"); bot.Log("Discord: a test message sent"); } else DiscordSays(why, "Red"); El<Button>("BtnDiscordClear").Click += (a, b) => box.Password = ""; var idBox = El<TextBox>("DiscordUserIdBox"); var idSays = El<TextBlock>("DiscordUserIdStatus"); string idPlain = idSays.Text; idBox.Text = s.DiscordUserId ?? ""; string t = idBox.Text.Trim(); El<TextBlock>("DiscordUserIdHint").Visibility = idBox.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed; if (t.Length > 0 && !Discord.IsUserId(t)) { idSays.Text = "That isn't a user ID: it's 17 to 20 digits (Copy User ID in Discord)."; idSays.Foreground = Res("Red"); } else if (t.Length > 0) { idSays.Text = "You're @mentioned when you must do something in the game yourself (like a bot check), when a Global Boss is up and on a good shop buy. Send a test to check it."; idSays.Foreground = Res("Green"); } else { idSays.Text = idPlain; idSays.Foreground = Res("Text2"); } idBox.TextChanged += (a, b) => string t = idBox.Text.Trim(); if ((t.Length == 0 || Discord.IsUserId(t)) && t != (s.DiscordUserId ?? "")) { s.DiscordUserId = t; Save(); } void DiscordSays(string text, string brush) var t = El<TextBlock>("DiscordStatus"); t.Foreground = Res(brush ?? "Text2"); var box = El<PasswordBox>("StatsLinkBox"); box.Password = s.StatsLink ?? ""; string t = box.Password.Trim(), saved = (s.StatsLink ?? "").Trim(); El<TextBlock>("StatsLinkHint").Visibility = box.Password.Length == 0 ? Visibility.Visible : Visibility.Collapsed; Show("BtnStatsClear", t.Length > 0 || saved.Length > 0); Show("RemoteRow", saved.Length > 0); if (t.Length == 0) StatsSays("Off. Nothing is sent without a link.", null); else if (t == (s.DiscordLink ?? "").Trim()) StatsSays("That's the link of your own Discord messages (above). Type /link in our Discord and paste the link it gives you here.", "Red"); else if (StatsCard.IsLink(t)) string trouble = t == saved ? bot.Stats.Trouble : null; if (trouble != null) StatsSays(trouble, "Red"); else StatsSays(bot.Stats.Status, "Green"); else if (saved.Length > 0) StatsSays("That isn't the link from /link. The link saved before stays until you paste a new one or press Remove.", "Red"); else StatsSays("That isn't the link from /link, so it's off. Type /link in our Discord to get yours.", "Red"); bot.Stats.Changed += () => Ui(() => { show(); ShowRemote(); }); box.PasswordChanged += (a, b) => string t = box.Password.Trim(), old = (s.StatsLink ?? "").Trim(); if ((t.Length == 0 || StatsCard.IsLink(t)) && t != old && (t.Length == 0 || t != (s.DiscordLink ?? "").Trim())) if (old.Length > 0) bot.Stats.Removed(old); if (t.Length > 0) bot.Stats.Wake(); El<Button>("BtnStatsClear").Click += (a, b) => box.Password = ""; var preview = El<Border>("StatsPreviewBox"); El<Button>("BtnStatsPreview").Click += (a, b) => bool open = preview.Visibility != Visibility.Visible; if (open) El<TextBox>("StatsPreview").Text = bot.Stats.Preview(); preview.Visibility = open ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("StatsPreviewLabel").Text = open ? "Hide it" : "What it sends"; var sw = El<CheckBox>("sw_RemoteControl"); RoutedEventHandler changed = (a, b) => { bot.Remote.Wake(); ShowRemote(); }; bot.Remote.PresetNames = () => List<string> names = new List<string>(); try { Window.Dispatcher.Invoke(new Action(() => { if (presets != null) names = presets.Select(p => p.Name).ToList(); })); } catch (Exception) { } bot.Remote.Shot = (out string why) => Discord.ShotOfRoblox(bot.Pinned, out why); bot.Remote.ShareLost = text => Ui(() => { PresetSays(text); bot.Log("Preset share: " + text); }); bot.Remote.Handler = (what, arg, reply) => Ui(() => try { RemoteDo(what, arg, reply); } catch (Exception e) { reply(false, "Something went wrong: " + e.Message); } El<TextBlock>("RemoteHint").Text = !s.RemoteControl ? "Off. Switch it on to use /remote in our Discord: start, stop, status, a preset, a problem report or a screenshot." : bot.Stats.NotOurs ? "On, but the stats link isn't from our Discord. It only listens to the link /link gives in our Discord." : StatsCard.IsLink((s.StatsLink ?? "").Trim()) ? "On. Type /remote in our Discord. It takes up to 15 seconds." : "On, but it needs the stats link above first."; void RemoteDo(string what, string arg, Action<bool, string> reply) if (bot.Running) { reply(true, "It's already playing."); return; } if (Covered) { reply(false, "The bot's window is waiting for you on the PC (the guidelines or the welcome). Press the button there first."); return; } if (!new RobloxWindow().Find()) { reply(false, "Roblox isn't open on the PC, so the bot wasn't started. Open Idle Mafia Game there first, then send start again."); return; } bot.Start(); ShowState("Starting"); reply(true, "Started. It plays now."); if (!bot.Running) { reply(true, "It's already stopped."); return; } bot.Stop(); ShowState("Stopping"); string state = !bot.Running ? (new RobloxWindow().Find() ? "Stopped" : "Stopped (Roblox isn't open)") : bot.OutOfGame ? "Running, but the game is out of reach (disconnected or Roblox closed)" : "Playing"; var p = System.IO.Path.Combine(dir, "problems", "active"); int n = System.IO.Directory.Exists(p) ? System.IO.Directory.GetFiles(p, "*.png").Length : 0; var now = presets == null ? null : presets.FirstOrDefault(x => x.Matches(s)); string numbers = bot.Stats.NumbersLine(); reply(true, state + "." + (numbers.Length > 0 ? "\n" + char.ToUpper(numbers[0]) + numbers.Substring(1) + "." : "") + "\nPreset: " + (now != null ? now.Name : "none of yours") + "." + "\nProblems: " + (n == 0 ? "none" : n.ToString(CultureInfo.InvariantCulture)) + "." + "\nVersion " + Program.Version + "."); var p = presets == null ? null : presets.FirstOrDefault(x => Preset.SameName(x.Name, arg ?? "")); reply(false, "No preset called \"" + arg + "\"." + (presets != null && presets.Count > 0 ? " Yours: " + string.Join(", ", presets.Select(x => x.Name)) + "." : " You have none yet: save one in the bot (Presets, top bar).")); if (p.Matches(s, true)) { reply(true, "\"" + p.Name + "\" is already on."); return; } reply(true, "Using \"" + p.Name + "\" now."); var p = Preset.Parse(arg, out why); if (p == null) { reply(false, why); return; } int old = presets.FindIndex(o => Preset.SameName(o.Name, p.Name)); if (old >= 0) presets[old] = p; else presets.Add(p); if (!SavePresets()) { reply(false, "The bot couldn't save its presets on the PC."); return; } bot.Log("Preset added from Discord: " + p.Name); reply(true, (old >= 0 ? "Replaced your \"" : "Added \"") + p.Name + "\" in your presets. Use it in the bot (Presets, top bar) or with /remote." + (p.Newer > 0 ? " " + p.Newer + " of its settings are from a newer bot: update yours (Settings → Check for updates) to get them all." : "")); bool ok = arg.StartsWith("ok:", StringComparison.Ordinal); string text = arg.Length > 3 ? arg.Substring(3) : arg; bot.Log("Preset share: " + text); reply(false, "The bot doesn't know that command."); void StatsSays(string text, string brush) var t = El<TextBlock>("StatsStatus"); t.Foreground = Res(brush ?? "Text2"); El<Button>("BtnStart").Click += (a, b) => Toggle(); El<Button>("BtnNeverList").Click += (a, b) => var list = El<StackPanel>("NeverList"); bool show = list.Visibility != Visibility.Visible; list.Visibility = show ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("NeverListText").Text = show ? "Hide the list" : "Show the full list"; El<Button>("BtnLog").Click += (a, b) => Open(System.IO.Path.Combine(dir, "IdleMafiaBot.log")); El<Button>("BtnProblems").Click += (a, b) => var p = System.IO.Path.Combine(dir, "problems"); System.IO.Directory.CreateDirectory(p); El<Button>("BtnMin").Click += (a, b) => Window.WindowState = WindowState.Minimized; El<Button>("BtnClose").Click += (a, b) => { closedBy = "you"; Window.Close(); }; El<Button>("BtnReport").Click += (a, b) => MakeReport(); El<Button>("BtnUpdate").Click += (a, b) => UpdateClicked(); El<Button>("BtnDiscord").Click += (a, b) => Open(DiscordLink); El<Button>("BtnDiscordTop").Click += (a, b) => Open(DiscordLink); El<Button>("BtnDonate").Click += (a, b) => Open(DonateLink); El<Grid>("TopBar").SizeChanged += (a, b) => FitTopBar(); El<StackPanel>("TopRight").SizeChanged += (a, b) => FitTopBar(); El<Button>("BtnWelcomeDiscord").Click += (a, b) => Open(DiscordLink); El<Button>("BtnStar").Click += (a, b) => Open(GitHubPage); El<Button>("BtnDownloads").Click += (a, b) => Open(DownloadPage); El<Button>("BtnReportHome").Click += (a, b) => MakeReport(); El<Button>("BtnOcrSettings").Click += (a, b) => Open("ms-settings:regionlanguage"); El<Button>("BtnWelcomeOk").Click += (a, b) => { s.WelcomeSeen = true; Save(); ShowWelcome(false); UpdateCheckCard(); }; El<Button>("BtnRulesAgree").Click += (a, b) => { bot.Log("Agreed to the guidelines (version " + Program.Version + ")"); ShowRules(false); UpdateCheckCard(); }; El<Button>("BtnRulesClose").Click += (a, b) => { closedBy = "you (didn't agree to the guidelines)"; Window.Close(); }; El<Button>("BtnCheck").Click += (a, b) => StartCheck(); El<Button>("BtnCheckNow").Click += (a, b) => { MarkOffered(); StartCheck(); }; El<Button>("BtnCheckLater").Click += (a, b) => PutOffCheck(); El<Button>("BtnReview").Click += (a, b) => { if (profile != null) ShowReview(true); }; El<Button>("BtnReviewClose").Click += (a, b) => ShowReview(false); El<Button>("BtnKeepSettings").Click += (a, b) => { if (changeRows.Count > 0) bot.Log("Kept your settings after the game check"); ShowReview(false); }; El<Button>("BtnUseSettings").Click += (a, b) => UseProposals(); const string DiscordLink = "https://discord.gg/xw56YcDFyg", GitHubPage = "https://github.com/geenusername/IdleMafiaBot", DownloadPage = "https://afkmacro.com", DonateLink = "https://ko-fi.com/jonesdev"; var left = El<StackPanel>("TopLeft"); var right = El<StackPanel>("TopRight"); var words = new[] { El<TextBlock>("DiscordTopWord"), El<TextBlock>("DonateWord") }; foreach (var w in words) w.Visibility = Visibility.Visible; var any = new Size(double.PositiveInfinity, double.PositiveInfinity); if (left.DesiredSize.Width + right.DesiredSize.Width > El<Grid>("TopBar").ActualWidth) foreach (var w in words) w.Visibility = Visibility.Collapsed; static void Open(string path) try { System.Diagnostics.Process.Start(path); } catch (Exception) { } if (!bot.Running && Covered) return; if (bot.Running) { bot.Stop(); ShowState("Stopping"); return; } if (Program.Reroller) { bot.StartRerollOnly(); ShowState("Working: only rerolling the crew"); return; } var named = (bot.InstancesSeen ?? new List<Instance>()).Where(i => i.Name != null).ToList(); if (named.Count > 1 && named.Any(i => i.Handle == bot.Pinned)) El<TextBlock>("PlayWhichText").Text = named.Count + " accounts are open in Roblox: " + string.Join(", ", named.Select(i => i.Name)) + ". " + "All of them plays them in turns - a round for one account, then the one with the fullest bars, each with its own settings. Rejoining is off while it does."; El<TextBlock>("PlayOneText").Text = "Only " + s.Account; El<TextBlock>("PlayAllText").Text = "All " + named.Count + " accounts"; bot.Start(); ShowState("Starting"); void ShowPlayWhich(bool show) { El<Grid>("PlayWhich").Visibility = show ? Visibility.Visible : Visibility.Collapsed; } bool PlayWhichShowing { get { return El<Grid>("PlayWhich").Visibility == Visibility.Visible; } } El<Button>("BtnPlayCancel").Click += (a, b) => ShowPlayWhich(false); El<Button>("BtnPlayOne").Click += (a, b) => { ShowPlayWhich(false); if (!bot.Running) { bot.Start(); ShowState("Starting"); } }; El<Button>("BtnPlayAll").Click += (a, b) => bot.StartAll(bot.InstancesSeen ?? new List<Instance>()); internal static bool ClosedMessage(bool wasRunning, string closedBy) string by = closedBy ?? ""; return wasRunning && !by.StartsWith("you") && !by.StartsWith("--exit"); internal static bool CloseWaitsForBot(string closedBy, bool playing, bool ownRoll) return (closedBy ?? "").StartsWith("you") && playing && ownRoll; DateTime shownAt = DateTime.MaxValue; internal bool RestartAfterDrawingFailed(Exception e) if (Closing || args.Any(a => a == "--once" || a == "--scan" || a == "--check" || a == "--exit" || a == "--demo")) return false; if (shownAt == DateTime.MaxValue || DateTime.UtcNow - shownAt < TimeSpan.FromMinutes(2)) return false; bot.Log("The window stopped drawing (a problem in Windows' graphics) - starting the bot again" + (!bot.Running || bot.CheckOrScan ? "" : bot.RerollOnly ? ", it goes on only rerolling" : ", it plays on")); bot.LogDetails(e.ToString()); return StartAgain("it plays on behind a window that doesn't draw"); bool StartAgain(string otherwise) bool playing = bot.Running && !bot.CheckOrScan, onlyReroll = playing && bot.RerollOnly; bool inTurns = playing && !onlyReroll && bot.InTurns; for (var until = DateTime.UtcNow.AddSeconds(15); bot.Running && DateTime.UtcNow < until; ) System.Threading.Thread.Sleep(100); var again = args.Where(a => a != "--start" && a != "--rerollonly" && a != "--restarted" && a != "--turns").ToList(); if (playing) again.Add(onlyReroll ? "--rerollonly" : "--start"); if (inTurns) again.Add("--turns"); again.Add("--restarted"); System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(Assembly.GetEntryAssembly().Location, string.Join(" ", again.Select(a => "\"" + a + "\""))) { UseShellExecute = false, WorkingDirectory = dir }); bot.Log("Couldn't start the bot again (" + x.Message + ") - " + otherwise); if (onlyReroll) bot.StartRerollOnly(); else if (playing) bot.Start(); System.Diagnostics.Process.GetCurrentProcess().Kill(); var btn = El<Button>("BtnUpdate"); var status = El<TextBlock>("UpdateStatus"); var text = El<TextBlock>("UpdateText"); text.Text = "Checking..."; System.Threading.ThreadPool.QueueUserWorkItem(_ => Updater.Release r = null; try { r = Updater.Check(); } catch (Exception e) { error = e.Message; } text.Text = "Check for updates"; if (r == null) { status.Text = "Couldn't check: " + error + "."; bot.Log("Update: couldn't check for updates (" + error + ")"); return; } if (!Updater.Newer(r)) { status.Text = "You have the latest version (" + Program.Version + ")."; bot.Log("Update: checked " + r.From + " - " + Program.Version + " is the latest"); return; } text.Text = "Update to " + r.Tag; El<Border>("UpdateNew").Visibility = Visibility.Visible; status.Text = "Idle Mafia Bot " + r.Tag + " is out. Press Update: the bot downloads it from " + r.From + ", checks it, puts it in place of this one and starts again. Your settings stay."; bot.Log("Update: Idle Mafia Bot " + r.Tag + " is out (you have " + Program.Version + ")"); if (Program.InTempFolder(dir)) status.Text = "Unzip the bot first (right-click the zip, Extract All) and start it from that folder, then update."; bot.Log("Update: not updated - the bot runs from a temporary folder (inside the zip?): unzip it first"); text.Text = "Downloading..."; string exe = Assembly.GetEntryAssembly().Location; System.Threading.ThreadPool.QueueUserWorkItem(_ => string error = Updater.Install(r2, exe); text.Text = "Update to " + r2.Tag; status.Text = "Not updated: " + error + "."; bot.Log("Update: not updated to " + r2.Tag + " - " + error); bot.Log("Update: " + r2.Tag + " downloaded and checked - starting the new version"); status.Text = "Starting " + r2.Tag + "..."; if (StartAgain("the old version goes back in its place")) return; string undo = Updater.Undo(exe); bool blocked = startError != null && startError.IndexOf("Application Control", StringComparison.OrdinalIgnoreCase) >= 0; text.Text = "Update to " + r2.Tag; status.Text = (blocked ? "Windows blocked " + r2.Tag + " from starting (Smart App Control: it stops new programs it doesn't know yet)" : r2.Tag + " didn't start (" + startError + ")") + ", so the update was undone - you're still on " + Program.Version + ". Try again later."; bot.Log("Update: " + (blocked ? "Windows blocked " + r2.Tag + " from starting" : r2.Tag + " didn't start") + " (" + startError + ") - the update is undone, " + Program.Version + " plays on"); status.Text = r2.Tag + " is in place but didn't start (" + startError + "), and the old version couldn't be put back: " + undo + "."; bot.Log("Update: " + r2.Tag + " didn't start and couldn't be undone - " + undo); IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) if (msg == 0x0312 && wParam.ToInt32() == 1) { Toggle(); handled = true; } if (msg == 0x0112 && (wParam.ToInt64() & 0xFFF0) == 0xF060) closedBy = "you"; void ShowState(string st) bool running = bot.Running && st != "Stopped"; string lower = st.ToLowerInvariant(); Brush dot = !running ? Res("Text3") : lower.Contains("roblox") || lower.Contains("can't read") || lower.Contains("problem") ? Res("Red") : lower.Contains("using the mouse") ? Res("Gold") : Res("Green"); El<Ellipse>("StateDot").Fill = dot; El<Ellipse>("RunLamp").Fill = running ? Res("Green") : Res("Text3"); El<Ellipse>("RunGlow").Fill = running ? Res("Green") : Res("Text3"); string shortText = !running ? (st == "Scanning" || st == "Checking" ? st : "Stopped") : bot.RerollOnly && !lower.Contains("using the mouse") ? "Rerolling" : bot.FightOnly && !lower.Contains("using the mouse") ? "Fighting" : lower.StartsWith("working") ? "Working" : lower.StartsWith("checking") ? "Checking" : lower.Contains("using the mouse") ? "Waiting for you" : lower.Contains("nothing to do") ? "Running" : "Running"; El<TextBlock>("StateText").Text = shortText; El<TextBlock>("NowText").Text = Friendly(st); var btn = El<Button>("BtnStart"); btn.Background = running ? Res("Red") : Res("Gold"); El<Path>("BtnStartIcon").Data = (Geometry)Window.FindResource(running ? "IcoStop" : "IcoPlay"); El<TextBlock>("BtnStartText").Text = running ? "Stop" : "Start"; var big = El<Button>("BtnStartBig"); big.Background = running ? Res("Red") : Res("Gold"); El<Path>("BtnStartBigIcon").Data = (Geometry)Window.FindResource(running ? "IcoStop" : "IcoPlay"); El<TextBlock>("BtnStartBigText").Text = running ? "Stop" : "Start"; El<StackPanel>("AccountTabs").IsEnabled = !running; El<TextBlock>("HomeNowTitle").Text = running ? shortText : "Stopped"; El<TextBlock>("HomeNow").Text = running ? Friendly(st) : "Press Start and the bot plays for you."; El<Ellipse>("HomeNowDot").Fill = dot; El<Ellipse>("HomeNowGlow").Fill = dot; if (running && st.StartsWith("Working: ")) string w = st.Substring(9); int next = w.IndexOf(". Next", StringComparison.Ordinal); doingTask = (next >= 0 ? w.Substring(0, next) : w.Split(',')[0]).Trim().ToLowerInvariant(); bool show = stripRunning && (shownPage != 0 || El<TextBlock>("StuckText").Visibility == Visibility.Visible); El<Border>("NowStrip").Visibility = show ? Visibility.Visible : Visibility.Collapsed; static string Friendly(string st) if (st.StartsWith("Working: ")) return "Now: " + st.Substring(9) + "."; if (st.StartsWith("Running - nothing")) return "All done for now. Waiting for the next thing."; if (st.StartsWith("Waiting until you stop using")) return "You're using the PC, so the bot waits."; void ShowHeader(Header h) if (h.Level > 0) El<TextBlock>("LevelText").Text = h.Level.ToString(); if (!string.IsNullOrWhiteSpace(h.Name)) { El<TextBlock>("PlayerName").Text = playerName = h.Name; AccountSeen(h.Name); } if (h.Level > 0) UpdateCheckCard(); if (h.Xp >= 0 && h.XpNext > 0) Bar("Xp", (double)h.Xp / h.XpNext); El<TextBlock>("XpText").Text = string.Format(CultureInfo.InvariantCulture, "{0:N0} / {1:N0} XP", h.Xp, h.XpNext); if (h.Cash >= 0) El<TextBlock>("CashText").Text = Money(h.Cash); if (h.Banked >= 0) El<TextBlock>("BankText").Text = Money(h.Banked) + " banked"; ShowBar("Energy", h.Energy, h.EnergyMax, h.EnergyTick, "+1 in {0}s"); ShowBar("Stamina", h.Stamina, h.StaminaMax, h.StaminaTick, "+1 in {0}"); ShowBar("Health", h.Health, h.HealthMax, -2, null); El<TextBlock>("PointsText").Text = h.SkillPoints > 0 ? h.SkillPoints + " skill points to spend" : ""; void ShowBar(string name, int cur, int max, int tick, string tickFormat) if (cur < 0 || max <= 0) return; Bar(name, (double)cur / max); El<TextBlock>(name + "Text").Text = cur + " / " + max; var tickText = Window.FindName(name + "Tick") as TextBlock; if (tickText == null) return; if (cur >= max || tick == 0) tickText.Text = "full"; else if (tick > 0) tickText.Text = string.Format(tickFormat, tick >= 60 ? TimeSpan.FromSeconds(tick).ToString(@"m\:ss") : tick.ToString()); void Bar(string name, double fraction) fraction = Math.Max(0, Math.Min(1, fraction)); El<ColumnDefinition>(name + "A").Width = new GridLength(fraction, GridUnitType.Star); El<ColumnDefinition>(name + "B").Width = new GridLength(1 - fraction, GridUnitType.Star); static string Money(double v) { return SafehouseInfo.Money(v); } void ShowInfo(string key, string value) if (key == "favors") { int n; ShowFavors(int.TryParse(value, out n) ? n : -1); } else if (key == "bossname") El<TextBlock>("BossName").Text = value; else if (key == "bosswhen") El<TextBlock>("BossWhen").Text = value; else if (key == "givewhy") El<Border>("GiveWhyBox").Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible; El<TextBlock>("GiveWhy").Text = string.IsNullOrEmpty(value) ? "" : "Right now: " + value + "."; else if (key == "job") RefreshJobCard(); else if (key == "animal") animalLine = value ?? ""; else if (key == "fullbars") var t = El<TextBlock>("FullBarsText"); t.Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible; var t = El<TextBlock>("StuckText"); t.Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible; else if (key == "roblox") El<TextBlock>("PlayerName").Text = value == "found" ? playerName ?? "Roblox found" : value == "covered" ? playerName ?? "Roblox is covered" : value == "not running" ? "Waiting for Roblox" : value == "minimized" ? "Roblox is minimized" : "Can't read the game. Is it open and not covered?"; const int FavorsPerDay = 30; string who = s.Account ?? ""; if (who != favorsFor) { favorsFor = who; favorsTop = -1; } favorsTop = Math.Max(favorsTop, n); bool more = favorsTop > FavorsPerDay; El<TextBlock>("FavorsText").Text = n < 0 ? "-" : more ? n + " left" : n + " / " + FavorsPerDay; var pips = El<WrapPanel>("FavorPips"); if (n < 0 || more) return; for (int i = 0; i < FavorsPerDay; i++) pips.Children.Add(new Border { Width = 7, Height = 7, CornerRadius = new CornerRadius(4), Margin = new Thickness(0, 0, 4, 4), Background = i < n ? Res("Xp") : Res("Raised2") }); void ShowOps(List<OpSlot> slots) foreach (var sl in slots.Where(x => !x.Locked)) int total = Game.KnownOpSeconds(sl.Name); bool finished = sl.Collect != null || sl.Done; double done = finished ? 1 : sl.Running && total > 0 && sl.SecondsLeft >= 0 ? 1 - (double)sl.SecondsLeft / total : 0; string left = finished ? "ready to collect" : sl.Running ? (sl.SecondsLeft >= 0 ? Span(sl.SecondsLeft) + " left" : "running") : "free slot"; Name = sl.Name.Length > 0 ? sl.Name : "Empty slot", Left = left, A = new GridLength(Math.Max(0, done), GridUnitType.Star), B = new GridLength(Math.Max(0, 1 - done), GridUnitType.Star), El<TextBlock>("OpsEmpty").Visibility = ops.Count == 0 ? Visibility.Visible : Visibility.Collapsed; string read = last != null ? (last.Family ?? "").Trim() : ""; if (read.Length == 0) return; foreach (var known in new[] { fam != null && fam.Overview != null ? fam.Overview.Name : null, safeFamily, profile != null ? profile.Family : null }) string name = Regex.Replace(known ?? "", @"\s*\[[^\]]*\]\s*$", "").Trim(); if (name.Length > 0 && Squash(name) == Squash(read)) { read = name; break; } El<TextBlock>("PlayerFamily").Text = read.ToUpperInvariant(); static string Squash(string s) { return Regex.Replace(s ?? "", @"\s+", "").ToUpperInvariant(); } void ShowSafehouse(SafehouseInfo s) if (!string.IsNullOrWhiteSpace(s.Family)) { safeFamily = s.Family; ShowPlayerFamily(); } var ci = CultureInfo.InvariantCulture; Func<int, string> n = v => v >= 0 ? v.ToString("N0", ci) : "-"; safeNext = s.UpgradeCost > 0 ? "Next: " + Money(s.UpgradeCost) : ""; El<TextBlock>("SafeLevel").Text = s.Level > 0 ? s.Level + (s.MaxLevel > 0 ? " / " + s.MaxLevel : "") : "-"; El<TextBlock>("SafeLevelSub").Text = "safehouse" + (s.Bonus >= 0 ? ", +" + s.Bonus + "%" : ""); El<TextBlock>("SafeRespect").Text = n(s.Respect); El<TextBlock>("SafeIncome").Text = s.Income >= 0 ? Money(s.Income) : "-"; El<TextBlock>("SafeAttack").Text = n(s.AttackPower); El<TextBlock>("SafeDefense").Text = n(s.DefensePower); El<TextBlock>("SafeCrew").Text = s.Hired >= 0 ? s.Hired + " / " + s.HireMax : "-"; El<TextBlock>("SafeCrewSub").Text = "henchmen" + (s.Gear >= 0 ? ", gear " + s.Gear + "/" + s.GearMax : ""); El<TextBlock>("SafeWhen").Text = "read at " + DateTime.Now.ToString("HH:mm"); static string Span(int seconds) var t = TimeSpan.FromSeconds(seconds); return t.TotalHours >= 1 ? string.Format("{0}h {1:00}m", (int)t.TotalHours, t.Minutes) : string.Format("{0}m", Math.Max(1, t.Minutes)); bool all = El<RadioButton>("TotSpanAll").IsChecked == true; var c = all ? bot.AllTime : bot.Count; El<TextBlock>("TotSpanNote").Text = all ? (s.Account.Length > 0 ? s.Account + ", since this version" : "Since this version") : bot.InTurns && s.Account.Length > 0 ? s.Account + ", since Start" : "Since Start"; El<TextBlock>("TotJobs").Text = c.Jobs.ToString("N0"); El<TextBlock>("TotHelps").Text = c.Helps.ToString("N0"); El<TextBlock>("TotBoss").Text = c.BossHits.ToString("N0"); El<TextBlock>("TotFights").Text = c.Fights.ToString("N0"); El<TextBlock>("TotFightsSub").Text = c.Fights > 0 ? string.Format("fights, {0:N0} won", c.Wins) + (c.FightCash > 0 ? ", " + Money(c.FightCash) : "") : "fights"; El<TextBlock>("TotDeposits").Text = c.Deposits.ToString("N0"); El<TextBlock>("TotRewards").Text = (c.Rewards + c.Crates).ToString("N0"); var parts = new List<string>(); if (c.Rerolls > 0) parts.Add(c.Rerolls + (c.Rerolls == 1 ? " reroll" : " rerolls")); if (c.Hired > 0) parts.Add(c.Hired + (c.Hired == 1 ? " hire" : " hires")); El<TextBlock>("CrewSession").Text = parts.Count == 0 ? "" : "Since Start: " + string.Join(" and ", parts) + (c.CrewCash > 0 ? " for " + Money(c.CrewCash) : "") + "."; var job = bot.PickJob(last, bot.CatalogView); bool oneMode = s.JobMode == JobMode.OneJob; El<TextBlock>("JobModeText").Text = oneMode ? "one job" : s.JobMode == JobMode.BestXp ? "best XP" : bot.MasteryDone(job) ? "mastery done: best XP" : "mastery"; if (!s.Jobs) { El<TextBlock>("JobName").Text = "Jobs are off"; El<TextBlock>("JobMeta").Text = ""; Bar("JobMastery", 0); El<TextBlock>("JobMasteryText").Text = ""; return; } string why = OneJobNow(out picked); bool none = why == Bot.OneJobNone; El<TextBlock>("JobName").Text = none ? "No job picked" : s.OneJob.Trim(); El<TextBlock>("JobMeta").Text = none ? "Click one in the list below." : "No jobs now: " + why + "."; Bar("JobMastery", picked == null ? 0 : MasteryFill(picked)); El<TextBlock>("JobMasteryText").Text = picked == null ? "" : "Mastery " + picked.MasteryLabel; El<TextBlock>("JobName").Text = s.JobMode == JobMode.Mastery ? "Every job has the mastery you asked for" : "-"; El<TextBlock>("JobMeta").Text = ""; El<TextBlock>("JobMasteryText").Text = ""; El<TextBlock>("JobName").Text = job.Name; El<TextBlock>("JobMeta").Text = string.Format(CultureInfo.InvariantCulture, "{0} energy, {1:N0} XP, {2:0.0} XP per energy", job.Cost, job.Xp, job.XpPerEnergy); Bar("JobMastery", MasteryFill(job)); El<TextBlock>("JobMasteryText").Text = "Mastery " + job.MasteryLabel; static double MasteryFill(JobInfo j) { return j.MasteryRank >= 3 ? 1 : j.MasteryGoal > 0 && j.MasteryCur >= 0 ? (double)j.MasteryCur / j.MasteryGoal : 0; } string OneJobNow(out JobInfo one) string why = bot.OneJobWhy(last, bot.CatalogView, out one); if (why != null || one == null) return why; var until = bot.JobAsideUntil(one.Name); return until == DateTime.MinValue ? null : "missed twice, tries again at " + until.ToLocalTime().ToString("HH:mm"); string why = s.JobMode == JobMode.OneJob ? OneJobNow(out one) : null; El<TextBlock>("JobModeHint").Text = s.JobMode == JobMode.BestXp ? "The job with the most XP per energy. It moves up as you level." : s.JobMode == JobMode.Mastery ? "Each job from the top until it reaches Gold (100 done, a skill point each), then the next one." : why == Bot.OneJobNone ? "Click a job in the list below." : "Only the job you click in the list below."; bool notNow = why != null && why != Bot.OneJobNone; El<Border>("OneJobWhyBox").Visibility = notNow ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("OneJobWhy").Text = notNow ? "Not doing it now: " + why + "." : ""; void PickOneJob(string name) if (s.JobMode != JobMode.OneJob || name == s.OneJob) return; bot.Log("Jobs: only \"" + name + "\" from now on (One job)"); readonly HashSet<string> openCities = new HashSet<string>(); var catalog = bot.CatalogView; bool oneMode = s.JobMode == JobMode.OneJob; if (oneMode) bot.OneJobWhy(last, catalog, out target); else target = bot.PickJob(last, catalog); int level = last != null && last.Level > 0 ? last.Level : s.LastLevel; if (oneMode && listMode != JobMode.OneJob && listShown && target != null) openCities.Add(target.City); var ti = CultureInfo.InvariantCulture.TextInfo; foreach (var cityJobs in catalog.OrderBy(x => x.Order).GroupBy(x => x.City)) string city = cityJobs.Key; bool open = openCities.Contains(city); int cityLevel = cityJobs.Min(x => x.TierLevel), cityGold = cityJobs.Count(x => x.MasteryRank >= 3); Header = ti.ToTitleCase(city.ToLowerInvariant()), IsHeader = true, Sign = open ? "−" : "+", Summary = level > 0 && cityLevel > level ? "Opens at level " + cityLevel : cityGold + " of " + cityJobs.Count() + " at Gold", Toggle = new RelayCommand(() => { if (!openCities.Remove(city)) openCities.Add(city); jobsDirty = true; RefreshJobs(); }), A = new GridLength(0, GridUnitType.Star), B = new GridLength(1, GridUnitType.Star), foreach (var j in cityJobs) bool locked = level > 0 && j.TierLevel > level; string rankName; Brush rankBrush; double frac; if (j.MasteryRank >= 3) { rankName = "Gold"; rankBrush = Res("Gold"); frac = 1; gold++; } else if (j.MasteryRank < 0) { rankName = "?"; rankBrush = Res("Text3"); frac = 0; } string[] next = { "Bronze", "Silver", "Gold" }; string[] colors = { "#C9956A", "#C4C9D4", "#D4AF37" }; rankName = (j.MasteryRank == 0 ? "" : (j.MasteryRank == 1 ? "Bronze " : "Silver ")) + (j.MasteryCur >= 0 ? j.MasteryCur + "/" + j.MasteryGoal : ""); rankBrush = Hex(colors[Math.Min(2, j.MasteryRank)]); frac = j.MasteryGoal > 0 && j.MasteryCur >= 0 ? (double)j.MasteryCur / j.MasteryGoal : 0; rankName = rankName.Trim().Length == 0 ? "to " + next[Math.Min(2, j.MasteryRank)] : rankName.Trim(); if (j.MasteryRank >= 0) known++; if (locked) { rankName = "level " + j.TierLevel; rankBrush = Res("Text3"); frac = 0; } bool isTarget = target == j; Header = "", Name = j.Name, Cost = j.Cost + " energy", Xp = j.Xp.ToString("N0", CultureInfo.InvariantCulture) + " XP", Rank = rankName, RankBrush = rankBrush, NameBrush = isTarget ? Res("Gold") : locked ? Res("Text3") : Res("Text"), NameWeight = isTarget ? FontWeights.Bold : FontWeights.Normal, RowBg = isTarget ? Res("GoldSoft") : Brushes.Transparent, A = new GridLength(frac, GridUnitType.Star), B = new GridLength(1 - frac, GridUnitType.Star), Pickable = oneMode, Hover = oneMode && !isTarget, Pick = oneMode ? new RelayCommand(() => PickOneJob(name)) : null, El<TextBlock>("MasterySummary").Text = string.Format("{0} of {1} jobs at Gold", gold, catalog.Count) + (known < catalog.Count ? " (some not read yet)" : ""); var page = El<ScrollViewer>("PageOverview"); var grid = El<Grid>("HomeGrid"); var scene = El<Grid>("HeroScene"); Action roundHero = () => scene.Clip = new RectangleGeometry(new Rect(0, 0, scene.ActualWidth, scene.ActualHeight), 13, 13); scene.SizeChanged += (a, b) => roundHero(); scene.Loaded += (a, b) => roundHero(); page.SizeChanged += (a, b) => FitHome(); for (int i = 0; i < 2; i++) var top = (FrameworkElement)grid.Children[i]; top.SizeChanged += (a, b) => FitHome(); top.IsVisibleChanged += (a, b) => Window.Dispatcher.BeginInvoke(new Action(FitHome), System.Windows.Threading.DispatcherPriority.Loaded); const double HomeMinColumns = 220; var page = El<ScrollViewer>("PageOverview"); var grid = El<Grid>("HomeGrid"); for (int i = 0; i < 2; i++) var e = (FrameworkElement)grid.Children[i]; if (e.Visibility == Visibility.Visible) top += e.ActualHeight + e.Margin.Top + e.Margin.Bottom; double h = Math.Max(page.ActualHeight - grid.Margin.Top - grid.Margin.Bottom, top + HomeMinColumns); if (page.ActualHeight > 0 && Math.Abs(grid.Height - h) > 0.5 || double.IsNaN(grid.Height)) grid.Height = h; void AddFeed(string line) if (line.Length < 10) return; string time = line.Substring(0, 8), raw = line.Substring(8).Trim(); var tag = Regex.Match(raw, @"^\[([^\]\r\n]{1,40})\]\s+"); string account = tag.Success ? tag.Groups[1].Value : null; if (tag.Success) raw = raw.Substring(tag.Length); string text = Clean(raw); if ((account == null || Accounts.Same(account, s.Account)) && (raw.StartsWith("Doing:") || raw.StartsWith("You're using the PC"))) return; if (text.StartsWith(crew.Name + ": ") && text.Length > crew.Name.Length + 3) text = char.ToUpperInvariant(text[crew.Name.Length + 2]) + text.Substring(crew.Name.Length + 3); if (account != null) text = account + ": " + text; string lower = raw.ToLowerInvariant(); bool good = raw.Contains("VICTORY") || raw.StartsWith("Defeated boss"); bool bad = raw.Contains(": DEFEAT") || lower.Contains("couldn't") || lower.Contains("problem") || lower.Contains("didn't") || lower.Contains("not recognised") || lower.Contains("refused") || lower.Contains("stopped and") || lower.Contains("is low"); var color = Hex(crew.Color); Who = crew.Name, Color = color, Time = time, Text = text, Stripe = bad ? Res("Red") : good ? Res("Green") : color, Icon = (Geometry)Window.FindResource(crew.Icon), if (feedAll.Count > FeedKept) feedAll.RemoveAt(feedAll.Count - 1); if (feedPart == null || feedPart == item.Who) while (feed.Count > 60) feed.RemoveAt(feed.Count - 1); El<TextBlock>("FeedEmpty").Visibility = Visibility.Collapsed; El<TextBlock>("FeedCount").Text = "since " + started.ToString("HH:mm"); if (lower.Contains("screenshot")) UpdateProblems(); const int FeedKept = 1000; var box = El<ComboBox>("FeedFilter"); foreach (var c in new[] { Runner, Fixer, Enforcer, Capo, Accountant, Consigliere, Collector, Items, Landlord, Underboss, Henchmen, Don }) box.SelectionChanged += (a, b) => feedPart = box.SelectedIndex <= 0 ? null : (string)box.SelectedItem; foreach (var f in feedAll.Where(f => feedPart == null || f.Who == feedPart).Take(60)) feed.Add(f); var empty = El<TextBlock>("FeedEmpty"); empty.Text = feedAll.Count == 0 ? "Nothing yet. Press Start and everything the bot does shows up here." : "Nothing from " + feedPart + " since " + started.ToString("HH:mm") + "."; empty.Visibility = feed.Count == 0 ? Visibility.Visible : Visibility.Collapsed; El<ScrollViewer>("FeedScroll").ScrollToTop(); static Crew CrewFor(string text) string u = text.ToLowerInvariant(); if (u.StartsWith("lucky briefcases: a free pick")) return Collector; if (u.StartsWith("discord")) return Don; if (u.StartsWith("lucky briefcases") || u.StartsWith("events:") || u.StartsWith("global boss")) return Don; if (u.StartsWith("check") || u.StartsWith("--- check") || u.Contains("game check") || u.StartsWith("set up from") || u.Contains("the game's menu")) return Don; if (u.StartsWith("properties") || u.StartsWith("safehouse: upgrade") || u.StartsWith("safehouse: saving")) return Landlord; if (u.StartsWith("safehouse:") || u.StartsWith("your ")) return Don; if (u.StartsWith("crew") && !u.Contains("gear") && !u.Contains("equip")) return Henchmen; if (u.StartsWith("shop") || u.StartsWith("crew") || u.StartsWith("collection") || u.Contains("equip") || u.StartsWith("crate") || u.Contains("crate(s)")) return Items; if (!u.Contains("heist") && (u.Contains("family") || u.Contains("takedown") || u.Contains("stamina perk") || u.Contains("give 5") || u.StartsWith("gave "))) return Underboss; if (u.Contains("boss")) return Capo; if (u.StartsWith("fight") || u.Contains("attack") || u.Contains("fights")) return Enforcer; if (u.Contains("heist") || u.Contains("favor") || u.StartsWith("helped")) return Fixer; if (u.StartsWith("bank") || u.Contains("deposit")) return Accountant; if (u.Contains("operation")) return Consigliere; if (u.Contains("claimed") || u.Contains("skill point") || u.Contains("playtime") || u.Contains("contract")) return Collector; if (u.Contains("job") || u.Contains("mastery") || u.Contains("city")) return Runner; static string Clean(string t) t = Regex.Replace(t, @"\s[&@•»·]\s(?![a-z])", " "); t = Regex.Replace(t, @"\s[t]\s(?=[+\-$])", " "); t = Regex.Replace(t, @"\b[oO0]\s*/\s*[\d,]+\s*", ""); t = Regex.Replace(t, @"\s+x\s*$", ""); t = t.Replace(" -> ", " to ").Replace(" · ", ", "); t = Regex.Replace(t, @"\b1 ([a-z ]+?)\(s\)", "1 $1"); t = t.Replace("(s)", "s"); var fight = Regex.Match(t, @"^Fight vs level (-?\d+) player: (VICTORY|DEFEAT)\s*\(?(.*?)\)?$"); string who = fight.Groups[1].Value == "-1" ? "a player" : "a level " + fight.Groups[1].Value + " player"; string spoils = fight.Groups[3].Value.Replace(" xp", " XP").Trim(); t = (fight.Groups[2].Value == "VICTORY" ? "Beat " : "Lost to ") + who + (spoils.Length > 0 ? ": " + spoils : "."); var m = Regex.Match(t, @"^Bank: Deposit[a-z]*\.?\s*\$([\d,]+)(?:.*?\((\$[\d,.]+\s?[KMBT]?)\s*fee\))?", RegexOptions.IgnoreCase); if (m.Success && double.TryParse(m.Groups[1].Value.Replace(",", ""), NumberStyles.Float, CultureInfo.InvariantCulture, out v)) t = "Put " + Money(v) + " in the bank" + (m.Groups[2].Success ? " (" + m.Groups[2].Value + " fee)." : "."); return Regex.Replace(t, @"\s{2,}", " ").Trim(); Name = "DemoPlayer", Family = "Demo Family", Level = 99, Xp = 51290, XpNext = 52616, Cash = 3.3e9, Banked = 58.1e9, SkillPoints = 20, Energy = 46, EnergyMax = 81, EnergyTick = 4, Stamina = 22, StaminaMax = 40, StaminaTick = 88, Health = 101, HealthMax = 300, ShowSafehouse(new SafehouseInfo Level = 11, MaxLevel = 16, Bonus = 43, Respect = 2974, Income = 9.81e9, AttackPower = 4831, DefensePower = 4697, Hired = 23, HireMax = 23, Gear = 69, GearMax = 69, ShowInfo("bossname", "Jin Shan"); ShowInfo("bosswhen", "back in 1h 12m"); ShowInfo("givewhy", "gave just now, gives again at 14:35"); ShowInfo("fullbars", "Bars sat full in the last hour: energy 3 min, stamina 0 min"); new OpSlot { Name = "Own the Front Page", Running = true, SecondsLeft = 5325 }, new OpSlot { Name = "Own the Front Page", Running = true, SecondsLeft = 5325 }, new OpSlot { Name = "Own the Front Page", Running = true, SecondsLeft = 5538 }, foreach (var line in new[] "14:01:58 Defeated boss \"Maksim Kovac\" in 5 hits: +4,098 XP + IRON CRATE", "14:02:16 Defeated boss \"Dragan Kovac\" in 8 hits: +$1.8M +6,141 XP + IRON CRATE", "14:02:31 Defeated boss \"Zhao Lin\" in 7 hits: +$3.2M + IRON CRATE +7,156 XP", "14:06:18 Crate: got Rail Yard Plate Vest Rare ARMOR", "14:07:13 Did \"Burn the Rival Fleet at Anchor\" x1 - mastery now 13/25 to Bronze", "14:09:56 Claimed 1 contract task(s)", "14:10:02 Helped \"Grand Vault of Caldera\" (+140 XP)", "14:10:05 Fight vs level 75 player: VICTORY (+19 Respect +151 xp)", "14:10:09 Bank: deposited $4,866,297,570 (no fee)", "14:11:30 Crew: 6 rerolls with Professional hires ($60M), better than before: Nico \"The Wire\" Bellini (Epic, +13/+14). The worst now: Dario \"Pockets\" Vale (Rare, +9/+8)", "14:11:52 Crew: sorted by rarity, 3 moves up: Sal \"The Clock\" Moreno (Legendary), Lena \"Marbles\" Fox (Legendary), Rico \"Lanterns\" Stone (Legendary)", "14:12:40 Takedown: 10 free attacks used, none left, +41,250 damage (94,222 this week, #1 in your Family)", "14:13:05 Gave 20 stamina to Hustlers (58 of 160 -> 78 of 160), stamina now 20/40", At = DateTime.Today.AddHours(14).AddMinutes(11), Henchmen = 23, Slots = 23, Attack = 1979, Defense = 2007, SlotPrice = 9.06e9, Worst = "Dario \"Pockets\" Vale (Rare, +37/+40)", Status = "Rerolls wait: saving up for Professional hires ($10M each, $4.2M on hand).", crew.ByRarity[6] = 2; crew.ByRarity[5] = 5; crew.ByRarity[4] = 14; crew.ByRarity[3] = 1; crew.ByRarity[2] = 1; var m = new FightMemory(null); var now = DateTime.UtcNow; Tuple.Create("VelvetKnuckles", 1900, true, 4.2e9), Tuple.Create("VelvetKnuckles", 400, true, 8.6e9), Tuple.Create("VelvetKnuckles", 130, true, 2.9e9), Tuple.Create("DockRat_77", 2500, false, -1.0), Tuple.Create("DockRat_77", 300, true, 3.1e9), Tuple.Create("SilentMarco", 700, true, 1.45e9), Tuple.Create("SilentMarco", 95, true, 960e6), Tuple.Create("NeonCasino", 3000, true, 640e6), Tuple.Create("NeonCasino", 1300, true, 715e6), Tuple.Create("NeonCasino", 1250, false, -1.0), Tuple.Create("LuckyPaulie", 42, true, 312e6), Tuple.Create("TinCanTony", 3600, true, 180e6), Tuple.Create("TinCanTony", 3500, true, 205e6), Tuple.Create("QuietHarbor", 600, true, 12e6), }) m.Add(f.Item1, f.Item3, f.Item4, f.Item3 ? 12 + f.Item1.Length : -14, -1, now.AddMinutes(-f.Item2)); var at = DateTime.Today.AddHours(14).AddMinutes(12); Func<string, int, int, long, long, string, PerkRow> perk = (n, lv, max, have, need, eff) => new PerkRow { Name = n, Level = lv, LevelMax = max, Have = have, Need = need, Effect = eff }; ShowFamily(new FamilyInfo Overview = new FamilyOverview Name = "DEMO FAMILY", Tag = "DEMO", Founded = "September 01, 2026", Donated = "$3.5B and 35 gold bars", Level = 6, Xp = 62441, XpNext = 114218, Members = 18, MembersMax = 50, Respect = 209, PerkLevels = 63, PerkLevelsMax = 350, Territories = 0, TerritoriesMax = 10, Vault = 592e6, Gold = 10, Takedown = new TakedownState Page = true, Name = "Castellane Estate", Free = 0, FreeMax = 10, Tickets = 0, Damage = 94222, Place = "#1 in your Family", Health = 229, HealthMax = 300, Stage = 3, StageName = "Grand Ballroom", StageHp = 337649, StageHpMax = 500000, NewSeconds = 536460, Perks = new List<PerkRow> perk("Enforcers", 11, 20, 86, 1700, "+11% attack power"), perk("Guardians", 6, 20, 12, 440, "+6% defense power"), perk("Hustlers", 3, 20, 78, 160, "+3% job experience"), perk("Medics", 3, 20, 15, 160, "+3% health regen"), perk("Runners", 5, 20, 5, 330, "+2.5% stamina regen"), PerksAt = at.AddMinutes(1), Attacks = 10, StaminaGiven = 20, void ShowFamily(FamilyInfo fi) if (fi != null) { fam = fi; ShowPlayerFamily(); } var ic = CultureInfo.InvariantCulture; bool none = fi != null && fi.NotInFamily; var o = fi == null ? null : fi.Overview; El<TextBlock>("FamName").Text = o != null && o.Name.Length > 0 ? o.Name : none ? "No family" : "Your family"; El<TextBlock>("FamTag").Text = o != null && o.Tag.Length > 0 ? "[" + o.Tag + "]" : ""; El<TextBlock>("FamLevel").Text = o != null && o.Level > 0 ? o.Level.ToString(ic) : "?"; bool xp = o != null && o.Xp >= 0 && o.XpNext > 0; Bar("FamXp", xp ? (double)o.Xp / o.XpNext : 0); El<TextBlock>("FamXpText").Text = xp ? string.Format(ic, "{0:N0} / {1:N0} family XP", o.Xp, o.XpNext) : ""; El<TextBlock>("FamVault").Text = o != null && o.Vault >= 0 ? Money(o.Vault) : "-"; El<TextBlock>("FamGold").Text = o != null && o.Gold >= 0 ? o.Gold.ToString(ic) + " gold bars" : ""; El<TextBlock>("FamMembers").Text = o != null && o.Members >= 0 ? o.Members + " / " + o.MembersMax : "-"; El<TextBlock>("FamRespect").Text = o != null && o.Respect >= 0 ? o.Respect.ToString("N0", ic) : "-"; El<TextBlock>("FamPerkLevels").Text = o != null && o.PerkLevels >= 0 ? o.PerkLevels + " / " + o.PerkLevelsMax : "-"; El<TextBlock>("FamTerritories").Text = o != null && o.Territories >= 0 ? o.Territories + " / " + o.TerritoriesMax : "-"; El<TextBlock>("FamRead").Text = none ? "You're not in a family. The bot looks again every 6 hours." : o == null ? "Not read yet. The bot looks every 3 hours." : "Read at " + fi.OverviewAt.ToString("HH:mm") + "." + (o.Founded.Length > 0 ? " Founded " + o.Founded + "." : "") + (o.Donated.Length > 0 ? " You donated " + o.Donated + "." : ""); var t = fi == null ? null : fi.Takedown; El<TextBlock>("TdDamage").Text = t != null && t.Damage >= 0 ? t.Damage.ToString("N0", ic) : "-"; El<TextBlock>("TdPlace").Text = t != null ? t.Place : ""; El<TextBlock>("TdFree").Text = t != null && t.Free >= 0 ? t.Free + " / " + t.FreeMax : "-"; var pips = El<WrapPanel>("TdPips"); if (t != null && t.Free >= 0) for (int i = 0; i < Math.Min(30, t.FreeMax); i++) pips.Children.Add(new Border { Width = 11, Height = 7, CornerRadius = new CornerRadius(2), Margin = new Thickness(0, 0, 3, 3), Background = i < t.Free ? Res("Gold") : Res("Raised2") }); bool stage = t != null && t.Stage > 0; El<TextBlock>("TdStageLabel").Text = stage ? "Stage " + t.Stage : "Stage"; El<TextBlock>("TdStage").Text = stage ? (t.StageName.Length > 0 ? t.StageName : "Stage " + t.Stage) : "-"; bool hp = stage && t.StageHpMax > 0; Bar("TdHp", hp ? (double)t.StageHp / t.StageHpMax : 0); El<TextBlock>("TdHp").Text = hp ? string.Format(ic, "{0:N0} / {1:N0} health left", t.StageHp, t.StageHpMax) : ""; var td = new List<string>(); if (t.Name.Length > 0) td.Add("This week: " + t.Name + "."); td.Add("Read at " + fi.TakedownAt.ToString("HH:mm") + "."); var left = fi.TakedownAt.AddSeconds(t.NewSeconds) - DateTime.Now; if (left.TotalMinutes > 1) td.Add(left.TotalDays >= 1 ? string.Format("Next one in {0}d {1}h.", (int)left.TotalDays, left.Hours) : string.Format("Next one in {0}h {1:00}m.", (int)left.TotalHours, left.Minutes)); if (fi != null && fi.Attacks > 0) td.Add(fi.Attacks + " free attacks used since Start."); El<TextBlock>("TdRead").Text = none ? "You're not in a family." : td.Count == 0 ? "Not read yet." : string.Join(" ", td); var rows = fi == null || fi.Perks == null ? new List<PerkRow>() : fi.Perks; string target = s.StaminaPerk.Trim().Length > 0 ? s.StaminaPerk.Trim() : rows.Where(r => r.Left > 0 && !r.Maxed).OrderBy(r => r.Left).Select(r => r.Name).FirstOrDefault(); bool gets = s.GiveStamina && target != null && Game.SamePerk(r.Name, target); double frac = r.Have >= 0 && r.Need > 0 ? Math.Min(1, (double)r.Have / r.Need) : 0; perkRows.Add(new PerkView Name = r.Name.Length > 0 ? r.Name : "?", Effect = r.Effect + (gets ? (r.Effect.Length > 0 ? ", " : "") + "gets the spare stamina" : ""), Level = r.Level > 0 ? "level " + r.Level + " / " + r.LevelMax : "", Progress = r.Have >= 0 ? string.Format(ic, "{0:N0} / {1:N0}", r.Have, r.Need) : "-", NameBrush = gets ? Res("Gold") : Res("Text"), NameWeight = gets ? FontWeights.Bold : FontWeights.SemiBold, RowBg = gets ? Res("GoldSoft") : Brushes.Transparent, A = new GridLength(frac, GridUnitType.Star), B = new GridLength(1 - frac, GridUnitType.Star), El<TextBlock>("PerksEmpty").Visibility = perkRows.Count == 0 ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("PerksEmpty").Text = none ? "You're not in a family." : "Not read yet."; El<TextBlock>("PerksRead").Text = fi == null || fi.Perks == null ? "" : "read at " + fi.PerksAt.ToString("HH:mm") + (fi.StaminaGiven > 0 ? ", " + fi.StaminaGiven + " stamina given" : ""); var p = System.IO.Path.Combine(dir, "problems", "active"); int n = System.IO.Directory.Exists(p) ? System.IO.Directory.GetFiles(p, "*.png").Length : 0; El<TextBlock>("ProblemsText").Text = n > 0 ? "Problems (" + n + ")" : "Problems"; El<RadioButton>("RailSettings").Uid = n > 0 ? n.ToString() : ""; if (k == badgeShown) return; if (Window.TaskbarItemInfo == null) Window.TaskbarItemInfo = new TaskbarItemInfo(); if (k <= 0) { Window.TaskbarItemInfo.Overlay = null; return; } string text = k > 9 ? "9+" : k.ToString(); var dv = new DrawingVisual(); using (var dc = dv.RenderOpen()) dc.DrawEllipse(new SolidColorBrush(Color.FromRgb(0xED, 0x42, 0x45)), null, new Point(16, 16), 16, 16); var ft = new FormattedText(text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(new FontFamily("Segoe UI"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal), text.Length > 1 ? 17 : 22, Brushes.White, 1.0); dc.DrawText(ft, new Point(16 - ft.Width / 2, 16 - ft.Height / 2)); var bmp = new System.Windows.Media.Imaging.RenderTargetBitmap(32, 32, 96, 96, PixelFormats.Pbgra32); Window.TaskbarItemInfo.Overlay = bmp; bool ReviewShowing { get { return El<Grid>("Review").Visibility == Visibility.Visible; } } void ShowReview(bool show) if (show) RefreshReview(); El<Grid>("Review").Visibility = show && profile != null ? Visibility.Visible : Visibility.Collapsed; void ClearHeader(string name) El<TextBlock>("LevelText").Text = "?"; El<TextBlock>("PlayerName").Text = playerName = name; El<TextBlock>("XpText").Text = ""; El<TextBlock>("CashText").Text = "-"; El<TextBlock>("BankText").Text = ""; foreach (var bar in new[] { "Energy", "Stamina", "Health" }) El<TextBlock>(bar + "Text").Text = "-"; var tick = Window.FindName(bar + "Tick") as TextBlock; if (tick != null) tick.Text = ""; ShowOps(new List<OpSlot>()); ShowSafehouse(new SafehouseInfo()); El<TextBlock>("SafeWhen").Text = "read once a day"; void AccountSeen(string name) string key = Profile.Key(name); if (key == profileOf) return; profile = Profile.Load(Profile.FileFor(dir, name)); if (bot.Running) { AddFeed(DateTime.Now.ToString("HH:mm:ss") + " Stop the bot first, then press Check my game."); return; } El<Border>("CheckCard").Visibility = Visibility.Collapsed; void CheckDone(Profile p) profileOf = Profile.Key(p.Name); if (profile == null) return; El<TextBlock>("ReviewSub").Text = (p.Name.Length > 0 ? p.Name : "Your account") + (p.Level > 0 ? ", level " + p.Level : "") + ", checked " + p.Checked.ToString("dd-MM", CultureInfo.InvariantCulture) + " at " + p.Checked.ToString("HH:mm", CultureInfo.InvariantCulture); foreach (var r in Setup.Found(p)) foundRows.Add(new FoundRow { Title = r[0], Text = r[1] }); El<TextBlock>("FocusHint").Text = Setup.FocusHint(s.Focus); foreach (var pr in Setup.Propose(p, s.Focus, s, bot.CatalogView)) string own = Setup.OwnChoice(pr, s); Name = pr.Name, Old = Setup.Show(pr.Field, pr.Current(s)), New = Setup.Show(pr.Field, pr.Value), Reason = pr.Reason, Note = own ?? "", Use = own == null, Field = pr.Field, Value = pr.Value, (pr.Changes(s) ? changeRows : keptRows).Add(row); El<TextBlock>("ChangesEmpty").Visibility = changeRows.Count == 0 ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("KeptTitle").Visibility = keptRows.Count == 0 ? Visibility.Collapsed : Visibility.Visible; El<Button>("BtnUseSettings").Visibility = changeRows.Count > 0 ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("KeepText").Text = changeRows.Count > 0 ? "Keep my settings" : "Close"; El<TextBlock>("ReviewNote").Text = changeRows.Count > 0 ? "Nothing changes until you press Use these settings. Switch a line off to keep your own setting." : "Your settings already match this focus."; El<TextBlock>("UseText").Text = changeRows.Count == 1 ? "Use this setting" : "Use these settings"; var chosen = changeRows.Where(r => r.Use).ToList(); foreach (var r in chosen) typeof(Settings).GetField(r.Field).SetValue(s, r.Value); bot.Log(chosen.Count == 0 ? "Kept your settings after the game check" : "Set up from your game check (" + s.Focus + " focus): " + string.Join(", ", chosen.Select(r => r.Name + " " + r.New))); s.FightWinChance = Settings.WinChanceStep(s.FightWinChance); foreach (var f in typeof(Settings).GetFields(BindingFlags.Public | BindingFlags.Instance)) var sw = Window.FindName("sw_" + f.Name) as CheckBox; if (sw != null && f.FieldType == typeof(bool)) sw.IsChecked = (bool)f.GetValue(s); var holder = Window.FindName("num_" + f.Name) as ContentControl; if (holder != null && f.FieldType == typeof(int)) holder.Content = Stepper(f, (string)holder.Tag); var values = f.FieldType.IsEnum ? Enum.GetNames(f.FieldType).Select(n => (object)Enum.Parse(f.FieldType, n)) : f.FieldType == typeof(int) ? Enumerable.Range(0, 10).Concat(Settings.WinChances).Distinct().Select(n => (object)n) : Enumerable.Empty<object>(); foreach (var v in values) var seg = Window.FindName("seg_" + f.Name + "_" + v) as RadioButton; if (seg != null) seg.IsChecked = Equals(f.GetValue(s), v); El<ComboBox>("cmb_PointsStat").SelectedIndex = Math.Max(0, Math.Min(Game.StatNames.Length - 1, s.PointsStat)); if (!whaleValues.Contains(s.WhaleCash)) whaleValues.Add(s.WhaleCash); var c = El<ComboBox>("cmb_WhaleCash"); c.Items.Insert(whaleValues.IndexOf(s.WhaleCash), WhaleName(s.WhaleCash)); El<ComboBox>("cmb_WhaleCash").SelectedIndex = whaleValues.IndexOf(s.WhaleCash); if (!payerValues.Contains(s.BigPayerCash)) payerValues.Add(s.BigPayerCash); El<ComboBox>("cmb_BigPayerCash").Items.Insert(payerValues.IndexOf(s.BigPayerCash), PayerName(s.BigPayerCash)); El<ComboBox>("cmb_BigPayerCash").SelectedIndex = payerValues.IndexOf(s.BigPayerCash); if (!rollValues.Contains(s.AutoRollLimit)) rollValues.Add(s.AutoRollLimit); El<ComboBox>("cmb_AutoRollLimit").Items.Insert(rollValues.IndexOf(s.AutoRollLimit), RollLimitName(s.AutoRollLimit)); El<ComboBox>("cmb_AutoRollLimit").SelectedIndex = rollValues.IndexOf(s.AutoRollLimit); foreach (var c in shopChoices) if (!c.Values.Contains(v)) El<ComboBox>(c.Box).Items.Insert(c.Values.IndexOf(v), c.Name(v)); El<ComboBox>(c.Box).SelectedIndex = c.Values.IndexOf(v); if (animalValues != null) if (!animalValues.Contains(s.AnimalMaxPrice)) animalValues.Add(s.AnimalMaxPrice); El<ComboBox>("cmb_AnimalMaxPrice").Items.Insert(animalValues.IndexOf(s.AnimalMaxPrice), AnimalPriceName(s.AnimalMaxPrice)); El<ComboBox>("cmb_AnimalMaxPrice").SelectedIndex = animalValues.IndexOf(s.AnimalMaxPrice); if (bankAtValues != null) ShowBankMode(); s.CrewRerollStats = s.CrewRerollStats > 0 ? Settings.Perfect : 0; El<ComboBox>("cmb_CrewRerollStats").SelectedIndex = goalValues.IndexOf(s.CrewRerollStats); if (s.StaminaPerk.Trim().Length > 0 && !perkChoices.Any(n => n != ClosestPerk && Game.SamePerk(n, s.StaminaPerk))) perkChoices.Add(s.StaminaPerk.Trim()); El<ComboBox>("cmb_StaminaPerk").Items.Add(s.StaminaPerk.Trim()); int perk = s.StaminaPerk.Trim().Length == 0 ? 0 : perkChoices.FindIndex(n => n != ClosestPerk && Game.SamePerk(n, s.StaminaPerk)); El<ComboBox>("cmb_StaminaPerk").SelectedIndex = Math.Max(0, perk); El<ComboBox>("cmb_StartHeist").SelectedIndex = HeistChoice(); El<ComboBox>("cmb_OperationOnly").SelectedIndex = OperationChoice(); if (!s.StartHeists) return 0; int heist = Array.FindIndex(Game.HeistNames, n => n == s.StartHeist); return 1 + (heist < 0 ? 3 : heist); El<TextBlock>("CheckWhen").Text = profile == null ? "Reads your game for about a minute and suggests settings. Nothing changes until you say so." : string.Format(CultureInfo.InvariantCulture, "Last check {0:dd-MM} at {0:HH:mm}{1}. Check again every few levels.", profile.Checked, profile.Level > 0 ? ", at level " + profile.Level : ""); El<Button>("BtnReview").Visibility = profile != null ? Visibility.Visible : Visibility.Collapsed; var card = El<Border>("CheckCard"); int level = last != null && last.Level > 0 ? last.Level : -1; string text = null, title = null; if (!Covered && profileOf != null && !bot.Running) if (profile == null && !Offered(profileOf)) title = "Set the bot up for your account?"; text = "It reads your game for about a minute and suggests settings. Nothing changes until you say so."; else if (profile != null && level > 0) text = Setup.RecheckHint(profile, level, s.CheckHintLevel); title = "Check your game again?"; if (text != null) { El<TextBlock>("CheckCardTitle").Text = title; El<TextBlock>("CheckCardText").Text = text; } card.Visibility = text != null ? Visibility.Visible : Visibility.Collapsed; bool Offered(string key) { return s.CheckOffered.Split(',').Any(n => n.Trim() == key); } if (profileOf == null || Offered(profileOf)) return; s.CheckOffered = s.CheckOffered.Trim().Length == 0 ? profileOf : s.CheckOffered.Trim() + "," + profileOf; if (profile == null) MarkOffered(); else if (last != null && last.Level > 0) { s.CheckHintLevel = last.Level; Save(); } El<Border>("CheckCard").Visibility = Visibility.Collapsed; void ShowWelcome(bool show) { El<Grid>("Welcome").Visibility = show ? Visibility.Visible : Visibility.Collapsed; } bool WelcomeShowing { get { return El<Grid>("Welcome").Visibility == Visibility.Visible; } } void ShowRules(bool show) { El<Grid>("Rules").Visibility = show ? Visibility.Visible : Visibility.Collapsed; } bool RulesShowing { get { return El<Grid>("Rules").Visibility == Visibility.Visible; } } bool Covered { get { return RulesShowing || WelcomeShowing || PlayWhichShowing; } } void ShowOcr(string problem) readonly List<string[]> warnings = new List<string[]>(); void ShowWarning(string title, string text, bool language) warnings.Add(new[] { title, text, language ? "lang" : "" }); bool any = ocrProblem != null || warnings.Count > 0; El<Border>("OcrBanner").Visibility = any ? Visibility.Visible : Visibility.Collapsed; El<TextBlock>("OcrTitle").Text = ocrProblem != null ? "The bot can't read the game yet" : warnings[0][0]; El<TextBox>("OcrText").Text = ocrProblem ?? string.Join("\n\n", warnings.Select(w => w[1])); El<Button>("BtnOcrSettings").Visibility = ocrProblem != null || warnings.Any(w => w[2] == "lang") ? Visibility.Visible : Visibility.Collapsed; void MakeReport(Action<bool, string> remote = null) var btn = El<Button>("BtnReport"); El<Button>("BtnReportHome").IsEnabled = false; El<TextBlock>("ReportText").Text = El<TextBlock>("ReportHomeText").Text = "Making the report..."; System.Threading.ThreadPool.QueueUserWorkItem(_ => string zip = null, error = null, desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); bool onDesktop = desktop.Length > 0 && System.IO.Directory.Exists(desktop); var before = DateTime.Now; zip = ProblemReport.Write(dir, onDesktop ? desktop : dir, ProblemReport.RobloxWindowInfo(bot.Pinned)); moved = ProblemReport.MoveReported(dir, before); catch (Exception e) { error = e.Message; } El<Button>("BtnReportHome").IsEnabled = true; El<TextBlock>("ReportText").Text = El<TextBlock>("ReportHomeText").Text = "Report a problem"; if (zip == null) { bot.Log("Couldn't make the report: " + error); if (remote != null) remote(false, "Couldn't make the report: " + error); return; } long size = new System.IO.FileInfo(zip).Length; bool big = size > 10L * 1024 * 1024; bot.Log(string.Format(CultureInfo.InvariantCulture, "Report saved {0}: {1} ({2}). {3}{4}", onDesktop ? "on your Desktop" : "next to the bot", System.IO.Path.GetFileName(zip), size < 1048576 ? Math.Max(1, size / 1024) + " KB" : (size / 1048576.0).ToString("0.0", CultureInfo.InvariantCulture) + " MB", big ? "That's too big for Discord, so open an issue on the bot's GitHub page (Settings, Help) and add it there, with a few words about what went wrong." : "Post it in the Discord (Settings, Help) with a few words about what went wrong.", moved > 0 ? " Its " + moved + " problem picture" + (moved == 1 ? "" : "s") + " moved to problems\\reported, so the red number starts from 0 again." : "")); if (remote != null) { bot.Remote.ReportZip = zip; remote(true, "Report saved " + (onDesktop ? "on your Desktop" : "next to the bot") + ": " + System.IO.Path.GetFileName(zip) + ". Post it in #bug-report when you're at your PC."); return; } try { System.Diagnostics.Process.Start("explorer.exe", "/select,\"" + zip + "\""); } catch (Exception) { }