| 1 | using System.Collections.ObjectModel; |
| 2 | using Microsoft.UI.Xaml; |
| 3 | using Microsoft.UI.Xaml.Controls; |
| 4 | using RblxTool.Models; |
| 5 | using RblxTool.Services; |
| 6 | |
| 7 | namespace RblxTool.Views; |
| 8 | |
| 9 | public sealed partial class SniperPage : Page |
| 10 | { |
| 11 | private const int MaxFeedLines = 3000; |
| 12 | |
| 13 | private readonly ObservableCollection<FeedLine> _feed = new(); |
| 14 | private JobRunner? _runner; |
| 15 | |
| 16 | public SniperPage() |
| 17 | { |
| 18 | InitializeComponent(); |
| 19 | FeedList.ItemsSource = _feed; |
| 20 | Loaded += OnLoaded; |
| 21 | Unloaded += (_, _) => _runner?.Stop(); |
| 22 | } |
| 23 | |
| 24 | private void OnLoaded(object sender, RoutedEventArgs e) |
| 25 | { |
| 26 | _runner = AppState.Current.CreateRunner(DispatcherQueue); |
| 27 | _runner.EventReceived += item => Append(FeedLine.From(item)); |
| 28 | _runner.Completed += _ => |
| 29 | { |
| 30 | StartButton.IsEnabled = true; |
| 31 | StopButton.IsEnabled = false; |
| 32 | Append(new FeedLine(" sniper disarmed", FeedTone.Good)); |
| 33 | }; |
| 34 | } |
| 35 | |
| 36 | private void OnStart(object sender, RoutedEventArgs e) |
| 37 | { |
| 38 | if (AppState.Current.Active is null) |
| 39 | { |
| 40 | Append(new FeedLine(" select an account first", FeedTone.Bad)); |
| 41 | return; |
| 42 | } |
| 43 | var request = new SnipeRequest |
| 44 | { |
| 45 | IntervalMs = (ulong)Math.Max(120, IntervalBox.Value), |
| 46 | Threads = (int)Math.Max(1, ThreadsBox.Value), |
| 47 | Buy = BuyToggle.IsOn, |
| 48 | DryRun = DryRunToggle.IsOn, |
| 49 | WatchReleases = ReleasesToggle.IsOn, |
| 50 | ReleaseMaxPrice = (ulong)Math.Max(0, ReleaseMaxPriceBox.Value), |
| 51 | }; |
| 52 | var defaultMax = (ulong)Math.Max(0, MaxPriceBox.Value); |
| 53 | var underRap = UnderRapBox.Value; |
| 54 | foreach (var line in TargetsBox.Text.Split('\n', StringSplitOptions.RemoveEmptyEntries)) |
| 55 | { |
| 56 | var trimmed = line.Trim(); |
| 57 | if (trimmed.Length == 0) |
| 58 | { |
| 59 | continue; |
| 60 | } |
| 61 | var parts = trimmed.Split(':', 2); |
| 62 | if (!ulong.TryParse(parts[0].Trim(), out var assetId)) |
| 63 | { |
| 64 | Append(new FeedLine($" ignoring target {trimmed}", FeedTone.Warn)); |
| 65 | continue; |
| 66 | } |
| 67 | var price = parts.Length > 1 && ulong.TryParse(parts[1].Trim(), out var parsed) |
| 68 | ? parsed |
| 69 | : defaultMax; |
| 70 | request.Targets.Add(new SnipeTargetRequest |
| 71 | { |
| 72 | AssetId = assetId, |
| 73 | MaxPrice = price, |
| 74 | UnderRap = underRap > 0 ? underRap : null, |
| 75 | }); |
| 76 | } |
| 77 | if (request.Targets.Count == 0 && !request.WatchReleases) |
| 78 | { |
| 79 | Append(new FeedLine(" add at least one target or enable release watching", FeedTone.Bad)); |
| 80 | return; |
| 81 | } |
| 82 | _feed.Clear(); |
| 83 | Append(new FeedLine($" armed on {request.Targets.Count} target(s)", FeedTone.Accent)); |
| 84 | var jobId = AppState.Current.Engine.StartSnipe(request); |
| 85 | _runner?.Start(jobId, 80); |
| 86 | StartButton.IsEnabled = false; |
| 87 | StopButton.IsEnabled = true; |
| 88 | } |
| 89 | |
| 90 | private void OnStop(object sender, RoutedEventArgs e) => _runner?.Cancel(); |
| 91 | |
| 92 | private void Append(FeedLine line) |
| 93 | { |
| 94 | if (_feed.Count >= MaxFeedLines) |
| 95 | { |
| 96 | _feed.RemoveAt(0); |
| 97 | } |
| 98 | _feed.Add(line); |
| 99 | FeedList.ScrollIntoView(_feed[^1]); |
| 100 | } |
| 101 | } |