AFK Macro

Idle Mafia Macro/Source/src/UiWindow.cs

UiWindow.cs

The macro's window · 3,717 lines · 200 KB

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<FeedItem> feed = new ObservableCollection<FeedItem>();
readonly List<FeedItem> feedAll = new List<FeedItem>();
string feedPart;
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>();
FightMemory payers;
readonly ObservableCollection<FoundRow> foundRows = new ObservableCollection<FoundRow>();
readonly ObservableCollection<ChangeRow> changeRows = new ObservableCollection<ChangeRow>();
readonly ObservableCollection<ChangeRow> keptRows = new ObservableCollection<ChangeRow>();
Profile profile;
string profileOf;
List<string> 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<ItemsControl>("Feed").ItemsSource = feed;
WireFeedFilter();
WireHome();
El<ItemsControl>("OpsList").ItemsSource = ops;
El<ItemsControl>("JobList").ItemsSource = jobRows;
El<ItemsControl>("PerkList").ItemsSource = perkRows;
El<ItemsControl>("BigPayerList").ItemsSource = payerRows;
UsePayers(bot.Memory);
El<ItemsControl>("FoundList").ItemsSource = foundRows;
El<ItemsControl>("ChangeList").ItemsSource = changeRows;
El<ItemsControl>("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<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))
{
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<RadioButton>("TotSpanSession").Checked += (a, b) => UpdateTotals();
El<RadioButton>("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<T>(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<Ellipse>(name).BeginAnimation(UIElement.OpacityProperty, anim);
}
void WirePages()
{
for (int i = 0; i < Pages.Length; i++)
{
int page = i;
El<RadioButton>("Rail" + Pages[i]).Checked += (a, b) => ShowPage(page);
}
WireLayout();
ShowPage(0);
}
const double ColsMinWidth = 840, FillMinList = 150;
readonly List<Grid> fills = new List<Grid>();
void WireLayout()
{
var all = new List<FrameworkElement>();
Collect(Window, all);
foreach (var e in all)
{
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(); };
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<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))
{
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<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();
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<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))
{
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<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;
}
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<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();
shownPage = page;
El<Button>("BtnStart").Visibility = page == 0 ? Visibility.Collapsed : Visibility.Visible;
El<Border>("StatePill").Visibility = page == 0 ? Visibility.Collapsed : Visibility.Visible;
FitStrip();
FitFillsSoon();
if (Pages[page] == "Settings") UpdateProblems();
}
string optionsOpen;
readonly List<CheckBox> tiles = new List<CheckBox>();
readonly List<Tuple<HeaderedContentControl, List<CheckBox>>> groups = new List<Tuple<HeaderedContentControl, List<CheckBox>>>();
string doingTask;
void WireTiles()
{
var all = new List<FrameworkElement>();
Collect(Window, all);
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>();
Collect(g, inside);
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"))
{
var t = tile;
string key = TileKey(t);
if (Window.FindName("Opt_" + key) == null) continue;
t.ApplyTemplate();
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();
Pulse("HomeNowGlow");
var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (a, b) => UpdateTileStatus();
timer.Start();
Window.Loaded += (a, b) => UpdateTileStatus();
}
static void UseDecoFont()
{
if (Application.Current == null) return;
try
{
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 (s == null) return;
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");
}
catch (Exception) { }
}
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";
var grid = new Grid();
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) };
stay.Tick += (a, b) =>
{
stay.Stop();
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)));
};
stay.Start();
}
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(); }
void UpdateTileStatus()
{
foreach (var t in tiles)
{
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;
}
SyncBundles();
ShowQuickStarts();
GlowTiles();
ShowPresetNow();
}
string TileLine(string key)
{
Func<string, bool> known = v => v.Length > 0 && v != "-" && v != "?";
switch (key)
{
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" } },
};
void GlowTiles()
{
string[] switches;
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();
foreach (var t in tiles)
{
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 }
: null);
}
}
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;
optionsOpen = key;
ShowOptionsAccount();
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 } });
ShowWhatMatters();
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.Text = text;
t.Visibility = text.Length > 0 ? Visibility.Visible : Visibility.Collapsed;
}
internal void CloseOptions()
{
optionsOpen = null;
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.",
AllTasks),
};
Settings fresh;
bool syncingBundles;
string quickShown;
void WireSimple()
{
fresh = new Settings();
foreach (var g in TaskGroups)
{
string key = g.Key;
var members = g.Value;
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); };
tile.ApplyTemplate();
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);
WhaleWindow();
RerollerWindow();
SyncBundles();
ShowQuickStarts();
}
void WhaleWindow()
{
if (!Program.Whale) return;
var keep = new[] { "sw_Fights", "sw_Bank", "sw_Animals" };
ShowTasksView(false);
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);
row.Children.Add(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.Clear();
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;
}
void RerollerWindow()
{
if (!Program.Reroller) return;
ShowTasksView(false);
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");
p.Children.Add(title);
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");
p.Children.Add(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(); } };
p.Children.Add(combo);
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(); };
p.Children.Add(box);
}
var counts = new TextBlock { Margin = new Thickness(0, 14, 0, 0), FontSize = 15 };
counts.SetResourceReference(TextBlock.ForegroundProperty, "Text");
p.Children.Add(counts);
card.Child = p;
full.Children.Insert(0, card);
El<TextBlock>("LogoText").Text = Program.Name;
var logo = El<Grid>("LogoIcon");
logo.Children.Clear();
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;
CrewInfo crew = null;
bot.CrewRead += i => crew = i;
int startRolls = bot.Count.Rerolls;
bool was = false;
var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (a, b) =>
{
if (bot.RerollOnly && !was) startRolls = bot.Count.Rerolls;
was = bot.RerollOnly;
var c = crew;
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;
};
timer.Start();
}
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.BringIntoView();
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;
Save();
bot.Log("Tasks: " + string.Join(", ", pick.Select(n => El<CheckBox>(n).Content)) + (on ? " on" : " off"));
SyncBundles();
}
void SyncBundles()
{
if (fresh == null) return;
syncingBundles = true;
try
{
foreach (var g in TaskGroups)
{
var tile = El<CheckBox>("grp_" + g.Key);
Lit(tile, g.Value);
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"));
return p;
}
void ShowQuickStarts()
{
var now = QuickStarts.FirstOrDefault(q => QuickPreset(q).Matches(s));
string shown = now == null ? "" : now.Item1;
if (shown == quickShown) return;
quickShown = shown;
var box = El<WrapPanel>("QuickPresets");
box.Children.Clear();
foreach (var q in QuickStarts)
{
var p = QuickPreset(q);
bool on = q == now;
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);
box.Children.Add(b);
}
}
void UseQuickStart(Preset p)
{
p.ApplyTo(s);
s.RerollsFirst = false;
Save();
RefreshControls();
bot.Log("Quick start: " + p.Name);
SyncBundles();
ShowQuickStarts();
}
List<Preset> presets;
Preset pasted;
string deleteAsked;
void WirePresets()
{
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);
}
void ShowPresetNow()
{
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; }
void ShowPresets()
{
var list = El<StackPanel>("PresetList");
list.Children.Clear();
El<TextBlock>("PresetEmpty").Visibility = presets.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
foreach (var p in presets)
{
var preset = p;
bool on = p.Matches(s);
bool changed = !on && Preset.SameName(p.Name, s.PresetInUse);
var row = new Grid();
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) });
row.Children.Add(words);
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" };
copy.Click += (a, b) =>
{
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") } };
del.Click += (a, b) =>
{
if (deleteAsked != preset.Name) { deleteAsked = preset.Name; ShowPresets(); return; }
deleteAsked = null;
presets.Remove(preset);
if (SavePresets()) PresetSays("\"" + preset.Name + "\" deleted.");
ShowPresets();
};
if (changed)
{
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 });
}
ShowPresetNow();
}
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"); }
}
bool SavePresets()
{
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; }
}
void SavePreset()
{
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);
deleteAsked = null;
if (!SavePresets()) return false;
InUse(p.Name);
PresetSays(old >= 0 ? "\"" + p.Name + "\" now holds what's on." : "Saved \"" + p.Name + "\".");
bot.Log("Preset saved: " + p.Name);
ShowPresets();
return true;
}
void InUse(string name)
{
if (s.PresetInUse == name) return;
s.PresetInUse = name;
Save();
}
void UsePreset(Preset p)
{
int skipped = p.ApplyTo(s);
s.PresetInUse = p.Name;
Save();
RefreshControls();
deleteAsked = null;
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)" : ""));
ShowPresets();
}
List<string> PresetTasks(Preset p, bool on)
{
var names = new List<string>();
foreach (var n in AllTasks)
{
string v = p.Value(SwitchField(n));
if (v == null) continue;
bool isOn = v == "1" || v.Equals("true", StringComparison.OrdinalIgnoreCase);
if (isOn == on) names.Add(Convert.ToString(El<CheckBox>(n).Content));
}
return names;
}
void CheckPastedCode()
{
string text = El<TextBox>("PresetCodeBox").Text;
var preview = El<Border>("PresetPreview");
pasted = null;
if (text.Trim().Length == 0) { preview.Visibility = Visibility.Collapsed; PresetSays(""); return; }
string why;
var p = Preset.Parse(text, out why);
if (p == null) { preview.Visibility = Visibility.Collapsed; PresetSays(why); return; }
pasted = p;
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));
p.ApplyTo(then);
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;
bool task;
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);
task = own != null;
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 == null) continue;
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);
}
}
return null;
}
void AddPasted(bool use)
{
if (pasted == null) return;
var p = pasted;
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);
if (use) UsePreset(p);
else { PresetSays("Added \"" + p.Name + "\"."); ShowPresets(); }
}
void WireSettings()
{
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 field = f;
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 value = v;
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(); });
PointsStopBox();
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));
Action picksSay = () =>
{
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;
};
picksSay();
picksBox.TextChanged += (a, b) =>
{
picksSay();
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();
};
ShowBankMode();
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]; });
PropertyChoices();
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(); }
count();
UpdateDerived();
}
const string BestOperation = "Best XP per hour (automatic)";
int OperationChoice()
{
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 };
bool fillingProperties;
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);
row.AppendChild(name);
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));
chip.AppendChild(word);
row.AppendChild(chip);
return new DataTemplate { VisualTree = row };
}
DataTemplate cashTag, goldTag;
void PropertyChoices()
{
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)
{
names.Add(null);
names.Add(city.Key);
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;
extra.Add(n);
if (g) gold.Add(n);
}
string only = (s.PropertyOnly ?? "").Trim();
if (only.Length > 0 && !listed(only) && !extra.Any(n => Bot.SameProperty(n, only))) extra.Add(only);
if (extra.Count > 0)
{
names.Add(null);
names.Add("Read in your game");
names.AddRange(extra);
}
fillingProperties = true;
try
{
var choices = new List<string>();
int at = 0;
c.Items.Clear();
for (int i = 0; i < names.Count; i++)
{
if (names[i] == null)
{
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) });
choices.Add(null);
continue;
}
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;
c.Items.Add(item);
choices.Add(names[i]);
}
propertyChoices = choices;
c.SelectedIndex = at;
}
finally { fillingProperties = false; }
}
List<int> goalValues;
List<long> whaleValues;
List<long> payerValues;
List<long> rollValues;
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]));
}
List<long> animalValues;
string safeNext = "";
string animalLine = "";
List<long> bankAtValues;
const long BankAtCustom = -1;
bool bankModeSync;
static string BankAtName(long v) { return v < 0 ? "My own amount\u2026" : SafehouseInfo.Money(v); }
void ShowBankMode()
{
bankModeSync = true;
El<RadioButton>("seg_BankMode_Round").IsChecked = s.BankAt <= 0;
El<RadioButton>("seg_BankMode_Amount").IsChecked = s.BankAt > 0;
bankModeSync = false;
El<Border>("BankAtRow").Visibility = s.BankAt > 0 ? Visibility.Visible : Visibility.Collapsed;
if (s.BankAt > 0)
{
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));
}
void BankAtPick(long v)
{
if (v > 0) s.BankAt = v;
bool custom = v < 0;
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);
double amt = -1;
if (m.Success)
{
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;
}
}
if (amt < 1)
{
El<TextBlock>("BankAtHint").Text = "Couldn't read that amount - use something like 25B, 750M or 2500000000.";
return;
}
long bank = (long)Math.Min(amt, 1e15);
s.BankAt = bank;
if (!bankAtValues.Contains(bank)) InsertBankAt(bank);
El<TextBlock>("BankAtHint").Text = "";
El<ComboBox>("cmb_BankAt").SelectedIndex = bankAtValues.IndexOf(bank);
Save(); UpdateDerived();
}
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 == m) return;
if (payers != null) payers.Changed -= PayersChanged;
payers = m;
if (payers != null) payers.Changed += PayersChanged;
RefreshBigPayers();
}
void PayersChanged() { Ui(RefreshBigPayers); }
void 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.Clear();
bool odd = false;
foreach (var p in list)
{
string name = p.Name;
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)),
});
odd = !odd;
}
bool none = payerRows.Count == 0;
El<TextBlock>("BigPayersEmpty").Visibility = none ? Visibility.Visible : Visibility.Collapsed;
El<Grid>("BigPayersHead").Visibility = none ? Visibility.Collapsed : Visibility.Visible;
int n = payers.Count;
El<TextBlock>("BigPayersRead").Text = n == 0 ? "" : n + (n == 1 ? " player" : " players") + " remembered";
}
void SaveBigPayers()
{
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");
t.AppendLine();
if (list.Count == 0) t.AppendLine("No player has paid that much in the last 3 days.");
else
{
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));
foreach (var p in list)
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));
}
try
{
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)
{
var d = nowUtc - utc;
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);
}
void SyncRoll()
{
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" };
void PointsStopBox()
{
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) =>
{
int v;
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);
};
ShowPointsStop();
}
void ShowPointsStop()
{
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);
var grid = new Grid();
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.SetColumn(plus, 2);
grid.Children.Add(minus);
grid.Children.Add(value);
grid.Children.Add(plus);
var box = new Border
{
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);
b.Handled = true;
};
show();
return box;
}
void Set(FieldInfo f, object value)
{
f.SetValue(s, value);
Save();
UpdateDerived();
}
void ShowInstances(List<Instance> list)
{
var tabs = El<StackPanel>("AccountTabs");
tabs.Children.Clear();
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;
foreach (var i in list)
{
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); };
tabs.Children.Add(tab);
}
}
void ReadAccount(Instance which)
{
if (bot.Running) { ShowInstances(bot.InstancesSeen); return; }
new System.Threading.Thread(() =>
{
Native.RealPixels();
bool ok = bot.ReadWindow(which);
Ui(() =>
{
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; }
Save();
if (!bot.PlayOn(which)) Ui(() => ShowInstances(bot.InstancesSeen));
}
void Save()
{
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); }
}
string saveFailed;
void SaveFailed(Exception e)
{
if (e.Message == saveFailed) return;
saveFailed = e.Message;
bot.Log("Couldn't save the settings: " + e.Message);
}
void UpdateDerived()
{
RefreshJobCard();
jobsDirty = true;
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.";
ShowFamily(null);
SyncRoll();
ShowCrewOdds();
SyncRollMode();
SyncFightMode();
El<TextBlock>("WhaleHint").Text = WhaleHintFor(s);
RefreshBigPayers();
if (crewSeen != null) ShowCrew(crewSeen);
if (ReviewShowing) RefreshReview();
}
void ShowCrewOdds()
{
var ic = CultureInfo.InvariantCulture;
var odds = El<WrapPanel>("CrewOdds");
odds.Children.Clear();
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." : "");
}
bool syncingRoll;
void WireRollMode()
{
foreach (var mode in new[] { "Off", "On", "First", "Only" })
{
string m = mode;
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 (syncingRoll) return;
if (m == "Only")
{
if (Covered) { SyncRollMode(); return; }
bot.StartRerollOnly();
ShowState("Working: only rerolling the crew");
return;
}
if (bot.RerollOnly) { bot.StopRerollOnly(); ShowState("Stopping"); }
s.CrewReroll = m != "Off";
s.RerollsFirst = m == "First";
Save();
UpdateDerived();
}
string RollMode() { return bot.RerollOnly ? "Only" : !s.CrewReroll ? "Off" : s.RerollsFirst ? "First" : "On"; }
void SyncRollMode()
{
string m = RollMode();
var rb = El<RadioButton>("RollMode_" + m);
var tile = El<CheckBox>("tile_Reroll");
if (rb.IsChecked != true || tile.IsChecked != (m != "Off"))
{
syncingRoll = true;
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.";
}
bool syncingFight;
void WireFightMode()
{
foreach (var mode in new[] { "Off", "On", "Only" })
{
string m = mode;
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 (m == "Only")
{
if (Covered) { SyncFightMode(); return; }
bot.StartFightOnly();
ShowState("Working: only fighting players");
return;
}
if (bot.FightOnly) { bot.StopFightOnly(); ShowState("Stopping"); }
s.Fights = m != "Off";
Save();
UpdateDerived();
}
string FightMode() { return bot.FightOnly ? "Only" : !s.Fights ? "Off" : "On"; }
void SyncFightMode()
{
string m = FightMode();
var rb = El<RadioButton>("FightMode_" + m);
var tile = El<CheckBox>("sw_Fights");
if (rb.IsChecked != true || tile.IsChecked != (m != "Off"))
{
syncingFight = true;
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.";
ShowWhatMatters();
}
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" },
};
void ShowWhatMatters()
{
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));
string off = null;
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; }
CrewInfo crewSeen;
void ShowCrew(CrewInfo ci)
{
crewSeen = 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.Children.Clear();
bar.ColumnDefinitions.Clear();
var chips = El<WrapPanel>("CrewChips");
chips.Children.Clear();
int total = ci == null ? 0 : ci.ByRarity.Sum();
if (total == 0)
bar.Children.Add(new Border { CornerRadius = new CornerRadius(5), Background = Res("Track") });
else
for (int r = 7; r >= 0; r--)
{
int n = ci.ByRarity[r];
if (n == 0) continue;
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);
bar.Children.Add(block);
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;
UpdateTotals();
}
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 });
return p;
}
void WireDiscord()
{
var box = El<PasswordBox>("DiscordLinkBox");
var test = El<Button>("BtnDiscordTest");
box.Password = s.DiscordLink ?? "";
Action show = () =>
{
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");
};
show();
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()))
{
s.DiscordLink = t;
Save();
Accounts.ForgetLink(dir, s.Account);
ShowWhatMatters();
}
show();
};
test.Click += (a, b) =>
{
test.IsEnabled = false;
DiscordSays("Sending...", null);
bot.Discord.Test(box.Password, Discord.ShotOfRoblox, why => Ui(() =>
{
test.IsEnabled = true;
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 ?? "";
Action showId = () =>
{
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"); }
};
showId();
idBox.TextChanged += (a, b) =>
{
string t = idBox.Text.Trim();
if ((t.Length == 0 || Discord.IsUserId(t)) && t != (s.DiscordUserId ?? "")) { s.DiscordUserId = t; Save(); }
showId();
};
}
void DiscordSays(string text, string brush)
{
var t = El<TextBlock>("DiscordStatus");
t.Text = text;
t.Foreground = Res(brush ?? "Text2");
}
void WireStats()
{
var box = El<PasswordBox>("StatsLinkBox");
box.Password = s.StatsLink ?? "";
Action show = () =>
{
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");
};
show();
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()))
{
s.StatsLink = t;
if (old.Length > 0) bot.Stats.Removed(old);
Save();
if (t.Length > 0) bot.Stats.Wake();
bot.Remote.Wake();
}
show();
};
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";
};
}
void WireRemote()
{
var sw = El<CheckBox>("sw_RemoteControl");
RoutedEventHandler changed = (a, b) => { bot.Remote.Wake(); ShowRemote(); };
sw.Checked += changed;
sw.Unchecked += changed;
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) { }
return names;
};
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); }
});
}
void ShowRemote()
{
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)
{
switch (what)
{
case "start":
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.");
return;
case "stop":
if (!bot.Running) { reply(true, "It's already stopped."); return; }
bot.Stop(); ShowState("Stopping");
reply(true, "Stopped.");
return;
case "status":
{
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 + ".");
return;
}
case "preset":
{
var p = presets == null ? null : presets.FirstOrDefault(x => Preset.SameName(x.Name, arg ?? ""));
if (p == null)
{
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)."));
return;
}
if (p.Matches(s, true)) { reply(true, "\"" + p.Name + "\" is already on."); return; }
UsePreset(p);
reply(true, "Using \"" + p.Name + "\" now.");
return;
}
case "report":
MakeReport(reply);
return;
case "addpreset":
{
string why;
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; }
ShowPresets();
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." : ""));
return;
}
case "shared":
{
bool ok = arg.StartsWith("ok:", StringComparison.Ordinal);
string text = arg.Length > 3 ? arg.Substring(3) : arg;
PresetSays(text);
bot.Log("Preset share: " + text);
reply(ok, text);
return;
}
}
reply(false, "The bot doesn't know that command.");
}
void StatsSays(string text, string brush)
{
var t = El<TextBlock>("StatsStatus");
t.Text = text;
t.Foreground = Res(brush ?? "Text2");
}
void WireButtons()
{
El<Button>("BtnStart").Click += (a, b) => Toggle();
WireRollMode();
WireFightMode();
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);
Open(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();
WireDiscord();
WireStats();
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";
void FitTopBar()
{
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);
left.Measure(any);
right.Measure(any);
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) { }
}
void Toggle()
{
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";
ShowPlayWhich(true);
return;
}
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; } }
void WirePlayWhich()
{
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) =>
{
ShowPlayWhich(false);
if (bot.Running) return;
Save();
bot.StartAll(bot.InstancesSeen ?? new List<Instance>());
ShowState("Starting");
};
}
internal static bool ClosedMessage(bool wasRunning, string closedBy)
{
string by = closedBy ?? "";
return wasRunning && !by.StartsWith("you") && !by.StartsWith("--exit");
}
string closedBy;
internal bool Closing;
bool closeAfterStop;
internal static bool CloseWaitsForBot(string closedBy, bool playing, bool ownRoll)
{
return (closedBy ?? "").StartsWith("you") && playing && ownRoll;
}
readonly string[] args;
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");
}
string startError;
bool StartAgain(string otherwise)
{
bool playing = bot.Running && !bot.CheckOrScan, onlyReroll = playing && bot.RerollOnly;
bool inTurns = playing && !onlyReroll && bot.InTurns;
Closing = true;
bot.Stop(true);
for (var until = DateTime.UtcNow.AddSeconds(15); bot.Running && DateTime.UtcNow < until; ) System.Threading.Thread.Sleep(100);
Save();
bot.Discord.Flush(2000);
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");
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(Assembly.GetEntryAssembly().Location,
string.Join(" ", again.Select(a => "\"" + a + "\""))) { UseShellExecute = false, WorkingDirectory = dir });
}
catch (Exception x)
{
startError = x.Message;
bot.Log("Couldn't start the bot again (" + x.Message + ") - " + otherwise);
Closing = false;
if (onlyReroll) bot.StartRerollOnly();
else if (playing) bot.Start();
return false;
}
System.Diagnostics.Process.GetCurrentProcess().Kill();
return true;
}
Updater.Release update;
void UpdateClicked()
{
var btn = El<Button>("BtnUpdate");
var status = El<TextBlock>("UpdateStatus");
var text = El<TextBlock>("UpdateText");
if (update == null)
{
btn.IsEnabled = false;
text.Text = "Checking...";
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
{
Updater.Release r = null;
string error = null;
try { r = Updater.Check(); } catch (Exception e) { error = e.Message; }
Ui(() =>
{
btn.IsEnabled = true;
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; }
update = r;
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 + ")");
});
});
return;
}
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");
return;
}
var r2 = update;
btn.IsEnabled = false;
text.Text = "Downloading...";
string exe = Assembly.GetEntryAssembly().Location;
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
{
string error = Updater.Install(r2, exe);
Ui(() =>
{
if (error != null)
{
btn.IsEnabled = true;
text.Text = "Update to " + r2.Tag;
status.Text = "Not updated: " + error + ".";
bot.Log("Update: not updated to " + r2.Tag + " - " + error);
return;
}
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;
btn.IsEnabled = true;
text.Text = "Update to " + r2.Tag;
if (undo == null)
{
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");
}
else
{
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";
return IntPtr.Zero;
}
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");
SyncRollMode();
SyncFightMode();
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;
stripRunning = running;
FitStrip();
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;
doingTask = null;
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();
}
GlowTiles();
UpdateTotals();
}
int shownPage;
bool stripRunning;
void FitStrip()
{
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.";
return st;
}
void ShowHeader(Header h)
{
last = 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();
ShowPlayerFamily();
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" : "";
RefreshJobCard();
UpdateTotals();
}
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());
else tickText.Text = "";
}
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.Text = value ?? "";
t.Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible;
}
else if (key == "stuck")
{
var t = El<TextBlock>("StuckText");
t.Text = value ?? "";
t.Visibility = string.IsNullOrEmpty(value) ? Visibility.Collapsed : Visibility.Visible;
FitStrip();
}
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;
int favorsTop = -1;
string favorsFor;
void ShowFavors(int n)
{
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");
pips.Children.Clear();
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)
{
ops.Clear();
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";
ops.Add(new OpsRow
{
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 safeFamily;
void ShowPlayerFamily()
{
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));
}
void UpdateTotals()
{
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");
c = bot.Count;
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) : "") + ".";
}
void RefreshJobCard()
{
ShowJobMode();
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; }
if (oneMode)
{
JobInfo picked;
string why = OneJobNow(out picked);
if (why != null)
{
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;
return;
}
job = picked;
}
if (job == null)
{
El<TextBlock>("JobName").Text = s.JobMode == JobMode.Mastery ? "Every job has the mastery you asked for" : "-";
El<TextBlock>("JobMeta").Text = "";
Bar("JobMastery", 1);
El<TextBlock>("JobMasteryText").Text = "";
return;
}
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");
}
void ShowJobMode()
{
JobInfo one;
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;
s.OneJob = name;
Save();
bot.Log("Jobs: only \"" + name + "\" from now on (One job)");
UpdateDerived();
}
readonly HashSet<string> openCities = new HashSet<string>();
JobMode listMode;
bool listShown;
void RefreshJobs()
{
if (!jobsDirty) return;
jobsDirty = false;
jobRows.Clear();
var catalog = bot.CatalogView;
bool oneMode = s.JobMode == JobMode.OneJob;
JobInfo target;
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);
listMode = s.JobMode;
listShown = true;
var ti = CultureInfo.InvariantCulture.TextInfo;
int gold = 0, known = 0;
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);
jobRows.Add(new JobRow
{
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; }
else
{
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; }
if (!open) continue;
bool isTarget = target == j;
string name = j.Name;
jobRows.Add(new JobRow
{
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)" : "");
}
void WireHome()
{
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;
void FitHome()
{
var page = El<ScrollViewer>("PageOverview");
var grid = El<Grid>("HomeGrid");
double top = 0;
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;
var crew = CrewFor(raw);
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);
var item = new FeedItem
{
Who = crew.Name, Color = color, Time = time, Text = text,
Stripe = bad ? Res("Red") : good ? Res("Green") : color,
Icon = (Geometry)Window.FindResource(crew.Icon),
};
feedAll.Insert(0, item);
if (feedAll.Count > FeedKept) feedAll.RemoveAt(feedAll.Count - 1);
if (feedPart == null || feedPart == item.Who)
{
feed.Insert(0, item);
while (feed.Count > 60) feed.RemoveAt(feed.Count - 1);
El<TextBlock>("FeedEmpty").Visibility = Visibility.Collapsed;
}
El<TextBlock>("FeedCount").Text = "since " + started.ToString("HH:mm");
UpdateTotals();
if (lower.Contains("screenshot")) UpdateProblems();
}
const int FeedKept = 1000;
void WireFeedFilter()
{
var box = El<ComboBox>("FeedFilter");
box.Items.Add("All");
foreach (var c in new[] { Runner, Fixer, Enforcer, Capo, Accountant, Consigliere, Collector, Items, Landlord, Underboss, Henchmen, Don })
box.Items.Add(c.Name);
box.SelectedIndex = 0;
box.SelectionChanged += (a, b) =>
{
feedPart = box.SelectedIndex <= 0 ? null : (string)box.SelectedItem;
feed.Clear();
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;
return Don;
}
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*\(?(.*?)\)?$");
if (fight.Success)
{
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);
double v;
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();
}
void Demo()
{
ShowHeader(new Header
{
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,
});
ShowFavors(9);
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");
ShowOps(new List<OpSlot>
{
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",
}) AddFeed(line);
var crew = new CrewInfo
{
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;
ShowCrew(crew);
DemoFamily();
DemoPayers();
}
void DemoPayers()
{
var m = new FightMemory(null);
var now = DateTime.UtcNow;
foreach (var f in new[]
{
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));
UsePayers(m);
}
void DemoFamily()
{
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,
},
OverviewAt = at,
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,
},
TakedownAt = at,
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(); }
fi = fam;
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");
pips.Children.Clear();
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 != null)
{
if (t.Name.Length > 0) td.Add("This week: " + t.Name + ".");
td.Add("Read at " + fi.TakedownAt.ToString("HH:mm") + ".");
if (t.NewSeconds > 0)
{
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);
perkRows.Clear();
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();
foreach (var r in rows)
{
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" : "");
}
void UpdateProblems()
{
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() : "";
TaskbarBadge(n);
}
int badgeShown = -1;
void TaskbarBadge(int n)
{
int k = Math.Min(n, 10);
if (k == badgeShown) return;
badgeShown = k;
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);
bmp.Render(dv);
bmp.Freeze();
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;
}
string accountShown;
void ClearHeader(string name)
{
last = null;
El<TextBlock>("LevelText").Text = "?";
El<TextBlock>("PlayerName").Text = playerName = name;
Bar("Xp", 0);
El<TextBlock>("XpText").Text = "";
El<TextBlock>("CashText").Text = "-";
El<TextBlock>("BankText").Text = "";
foreach (var bar in new[] { "Energy", "Stamina", "Health" })
{
Bar(bar, 0);
El<TextBlock>(bar + "Text").Text = "-";
var tick = Window.FindName(bar + "Tick") as TextBlock;
if (tick != null) tick.Text = "";
}
}
void ForgetAccount()
{
ShowReview(false);
fam = null;
safeFamily = null;
ShowFamily(null);
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;
profileOf = key;
profile = Profile.Load(Profile.FileFor(dir, name));
UpdateCheckWhen();
UpdateCheckCard();
}
void StartCheck()
{
if (Covered) return;
if (bot.Running) { AddFeed(DateTime.Now.ToString("HH:mm:ss") + " Stop the bot first, then press Check my game."); return; }
ShowReview(false);
El<Border>("CheckCard").Visibility = Visibility.Collapsed;
bot.RequestCheck();
ShowState("Checking");
}
void CheckDone(Profile p)
{
profile = p;
profileOf = Profile.Key(p.Name);
UpdateCheckWhen();
UpdateCheckCard();
ShowReview(true);
}
void RefreshReview()
{
if (profile == null) return;
var p = profile;
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);
foundRows.Clear();
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);
changeRows.Clear();
keptRows.Clear();
foreach (var pr in Setup.Propose(p, s.Focus, s, bot.CatalogView))
{
string own = Setup.OwnChoice(pr, s);
var row = new ChangeRow
{
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";
}
void UseProposals()
{
var chosen = changeRows.Where(r => r.Use).ToList();
foreach (var r in chosen) typeof(Settings).GetField(r.Field).SetValue(s, r.Value);
Save();
ShowReview(false);
RefreshControls();
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)));
}
void RefreshControls()
{
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));
ShowPointsStop();
if (whaleValues != null)
{
if (!whaleValues.Contains(s.WhaleCash))
{
whaleValues.Add(s.WhaleCash);
whaleValues.Sort();
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 != null)
{
if (!payerValues.Contains(s.BigPayerCash))
{
payerValues.Add(s.BigPayerCash);
payerValues.Sort();
El<ComboBox>("cmb_BigPayerCash").Items.Insert(payerValues.IndexOf(s.BigPayerCash), PayerName(s.BigPayerCash));
}
El<ComboBox>("cmb_BigPayerCash").SelectedIndex = payerValues.IndexOf(s.BigPayerCash);
}
if (rollValues != null)
{
if (!rollValues.Contains(s.AutoRollLimit))
{
rollValues.Add(s.AutoRollLimit);
rollValues.Sort();
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)
{
long v = c.Get();
if (!c.Values.Contains(v))
{
c.Values.Add(v);
c.Values.Sort();
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);
animalValues.Sort();
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();
if (goalValues != null)
{
s.CrewRerollStats = s.CrewRerollStats > 0 ? Settings.Perfect : 0;
El<ComboBox>("cmb_CrewRerollStats").SelectedIndex = goalValues.IndexOf(s.CrewRerollStats);
}
if (perkChoices != null)
{
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();
PropertyChoices();
UpdateDerived();
}
int HeistChoice()
{
if (!s.StartHeists) return 0;
int heist = Array.FindIndex(Game.HeistNames, n => n == s.StartHeist);
return 1 + (heist < 0 ? 3 : heist);
}
void UpdateCheckWhen()
{
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;
}
void UpdateCheckCard()
{
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); }
void MarkOffered()
{
if (profileOf == null || Offered(profileOf)) return;
s.CheckOffered = s.CheckOffered.Trim().Length == 0 ? profileOf : s.CheckOffered.Trim() + "," + profileOf;
Save();
}
void PutOffCheck()
{
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)
{
ocrProblem = problem;
ShowBanner();
}
string ocrProblem;
readonly List<string[]> warnings = new List<string[]>();
void ShowWarning(string title, string text, bool language)
{
warnings.Add(new[] { title, text, language ? "lang" : "" });
ShowBanner();
}
void ShowBanner()
{
bool any = ocrProblem != null || warnings.Count > 0;
El<Border>("OcrBanner").Visibility = any ? Visibility.Visible : Visibility.Collapsed;
if (!any) return;
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");
btn.IsEnabled = false;
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;
int moved = 0;
try
{
zip = ProblemReport.Write(dir, onDesktop ? desktop : dir, ProblemReport.RobloxWindowInfo(bot.Pinned));
moved = ProblemReport.MoveReported(dir, before);
}
catch (Exception e) { error = e.Message; }
Ui(() =>
{
btn.IsEnabled = true;
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; }
UpdateProblems();
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) { }
});
});
}
}
}