using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Windows; 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; namespace IdleMafiaBot { 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; } internal string Field; internal object Value; } 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 { readonly Action run; 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 { } } } sealed class UiWindow { sealed class Crew { 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 Settings s; readonly Bot bot; readonly string dir; readonly ObservableCollection feed = new ObservableCollection(); readonly List feedAll = new List(); string feedPart; readonly ObservableCollection ops = new ObservableCollection(); readonly ObservableCollection jobRows = new ObservableCollection(); readonly ObservableCollection perkRows = new ObservableCollection(); readonly ObservableCollection payerRows = new ObservableCollection(); FightMemory payers; readonly ObservableCollection foundRows = new ObservableCollection(); readonly ObservableCollection changeRows = new ObservableCollection(); readonly ObservableCollection keptRows = new ObservableCollection(); Profile profile; string profileOf; List perkChoices; FamilyInfo fam; readonly DateTime started = DateTime.Now; Header last; string playerName; bool jobsDirty = true; public UiWindow(Settings settings, Bot bot, string dir, string[] args) { s = settings; this.bot = bot; this.dir = dir; this.args = args; UseDecoFont(); 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); } try { 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); } catch (Exception) { } WindowChrome.SetWindowChrome(Window, new WindowChrome { CaptionHeight = 84, ResizeBorderThickness = new Thickness(6), GlassFrameThickness = new Thickness(0, 0, 0, 1), CornerRadius = new CornerRadius(0), UseAeroCaptionButtons = false, }); WirePages(); WireSettings(); WireButtons(); WireTiles(); WirePresets(); WireSimple(); WirePlayWhich(); El("Feed").ItemsSource = feed; WireFeedFilter(); WireHome(); El("OpsList").ItemsSource = ops; El("JobList").ItemsSource = jobRows; El("PerkList").ItemsSource = perkRows; El("BigPayerList").ItemsSource = payerRows; UsePayers(bot.Memory); El("FoundList").ItemsSource = foundRows; El("ChangeList").ItemsSource = changeRows; El("KeptList").ItemsSource = keptRows; Pulse("RunGlow"); Pulse("NowGlow"); 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); } accountShown = name; UsePayers(bot.Memory); RefreshControls(); UpdateDerived(); jobsDirty = true; RefreshJobs(); AccountSeen(name); ShowCrew(null); ShowOptionsAccount(); }); 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("VersionText").Text = "version " + Program.Version; El("SupportVersion").Text = "version " + Program.Version; El("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)) { b.Cancel = true; closeAfterStop = true; Window.Hide(); 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(); }; wait.Start(); return; } Closing = true; bot.Log("Window closed " + (closedBy != null ? "by " + closedBy : "by another program or Windows (not its Close button)")); bool wasRunning = bot.Running; bot.Stop(true); bot.FoldTotals(false); Save(); 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(); Ui(() => { ShowOcr(problem); 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(); }); }; ShowState("Stopped"); ShowFavors(-1); ShowFamily(null); ShowCrew(null); RefreshJobCard(); RefreshJobs(); El("TotSpanSession").Checked += (a, b) => UpdateTotals(); El("TotSpanAll").Checked += (a, b) => UpdateTotals(); UpdateTotals(); UpdateCheckWhen(); UpdateProblems(); Window.Activated += (a, b) => UpdateProblems(); if (demo) Demo(); else { bot.Stats.Begin(); WireRemote(); bot.Remote.Begin(); } } 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(string name) where T : class { var o = Window.FindName(name) as T; if (o == null) throw new InvalidOperationException("UI element missing: " + name); return o; } 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); } void Pulse(string name) { var anim = new DoubleAnimation(0.08, 0.35, TimeSpan.FromSeconds(1.1)) { AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever }; El(name).BeginAnimation(UIElement.OpacityProperty, anim); } void WirePages() { for (int i = 0; i < Pages.Length; i++) { int page = i; El("Rail" + Pages[i]).Checked += (a, b) => ShowPage(page); } WireLayout(); ShowPage(0); } const double ColsMinWidth = 840, FillMinList = 150; readonly List fills = new List(); void WireLayout() { var all = new List(); Collect(Window, all); foreach (var e in all) { var fold = e as ToggleButton; if (fold != null && fold.Name.StartsWith("Fold_")) { var part = El(fold.Name.Substring(5)); RoutedEventHandler show = (a, b) => { part.Visibility = fold.IsChecked == true ? Visibility.Visible : Visibility.Collapsed; FitFillsSoon(); }; fold.Checked += show; fold.Unchecked += show; show(null, null); } var grid = e as Grid; if (grid == null) continue; if ("Cols".Equals(grid.Tag)) { var g = grid; EvenColumns(g); var right = g.Children.OfType().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)) { var g = grid; fills.Add(g); g.IsVisibleChanged += (a, b) => FitFillsSoon(); foreach (FrameworkElement c in g.Children) if (Grid.GetRow(c) < g.RowDefinitions.Count - 1) c.SizeChanged += (a, b) => FitFillsSoon(); var page = Page(g); if (page != null) page.SizeChanged += (a, b) => FitFillsSoon(); } } } static void EvenColumns(Grid g) { foreach (var stack in g.Children.OfType().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().ToList(); stack.Children.Clear(); foreach (var k in kids) { DockPanel.SetDock(k, Dock.Top); var fe = k as FrameworkElement; if (fe != null && k == kids.Last()) fe.VerticalAlignment = VerticalAlignment.Stretch; dock.Children.Add(k); } int at = g.Children.IndexOf(stack); g.Children.RemoveAt(at); g.Children.Insert(at, dock); } } static void Collect(DependencyObject d, List 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)) { var u = d as UIElement; if (u != null && u.Visibility != Visibility.Visible) return false; if (d == page) return true; } return false; } static void FitCols(Grid g, List right, List 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; } foreach (var r in right) { 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); } } bool fitting; void FitFillsSoon() { if (fitting) return; fitting = true; Window.Dispatcher.BeginInvoke(new Action(() => { fitting = false; FitFills(); }), System.Windows.Threading.DispatcherPriority.Loaded); } void FitFills() { foreach (var g in fills) { var page = Page(g); 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; double rows = 0; 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; } } void ShowPage(int page) { for (int i = 0; i < Pages.Length; i++) El("Page" + Pages[i]).Visibility = i == page ? Visibility.Visible : Visibility.Collapsed; El("Page" + Pages[page]).BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(160))); El("PageTitle").Text = Titles[page].ToUpperInvariant(); shownPage = page; El