commit 7f89dba5f81e48ea0daf2f0cde63e77cc1127e24 Author: HeagBoKaT Date: Sat Sep 26 12:46:52 2026 +0300 RQ Overlay: оверлей заработка и статистики для Royal Quest Co-Authored-By: Claude Opus 5.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3aeacaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Сборка +bin/ +obj/ +dist/ + +# IDE +.idea/ +.vs/ +*.user +*.DotSettings.user diff --git a/RQOverlay.slnx b/RQOverlay.slnx new file mode 100644 index 0000000..b09c9dc --- /dev/null +++ b/RQOverlay.slnx @@ -0,0 +1,3 @@ + + + diff --git a/src/RQOverlay/App.xaml b/src/RQOverlay/App.xaml new file mode 100644 index 0000000..9949ab2 --- /dev/null +++ b/src/RQOverlay/App.xaml @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RQOverlay/App.xaml.cs b/src/RQOverlay/App.xaml.cs new file mode 100644 index 0000000..8c87742 --- /dev/null +++ b/src/RQOverlay/App.xaml.cs @@ -0,0 +1,68 @@ +using System.Windows; +using System.Windows.Threading; +using RQOverlay.Core; +using RQOverlay.Plugins; +using RQOverlay.Views; + +namespace RQOverlay; + +public partial class App : Application, IPluginHost +{ + readonly CancellationTokenSource _cts = new(); + + public PriceBook Prices { get; } = new(); + public PluginManager Plugins { get; private set; } = null!; + public SessionViewModel Session { get; private set; } = null!; + public OverlaySettings Settings { get; } = OverlaySettings.Load(); + + public string DataDir => AppPaths.Dir; + + public static new App Current => (App)Application.Current; + + public void OpenPrices(bool unpricedOnly) => AppWindow.ShowSingle(AppWindow.Page.Prices, unpricedOnly); + + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + ThemeManager.Apply(Settings.Theme); + + Prices.Load(); + Prices.UseAuction = Settings.UseAuction; + Plugins = new PluginManager(this, Settings); + Plugins.Load(); + Session = new SessionViewModel(Prices, Plugins); + + var window = new MainWindow { DataContext = Session }; + MainWindow = window; + window.Show(); + + var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; + timer.Tick += (_, _) => Session.Tick(); + timer.Start(); + + // Сканирование памяти — в фоне, результаты — в UI-поток. + var watcher = new LogWatcher(); + watcher.StatusChanged += (connected, status) => Dispatcher.BeginInvoke(() => + { + Session.Connected = connected; + Session.Status = status; + }); + + // --history: засчитать и то, что накопилось в логе до запуска оверлея. + bool countHistory = e.Args.Contains("--history"); + watcher.EntriesReceived += (entries, initial) => + Dispatcher.BeginInvoke(() => Session.Apply(entries, initial, countHistory)); + + new Thread(() => watcher.Run(_cts.Token)) { IsBackground = true, Name = "LogWatcher" }.Start(); + } + + protected override void OnExit(ExitEventArgs e) + { + _cts.Cancel(); + Plugins.Shutdown(); + Prices.Save(); + Settings.Save(); + base.OnExit(e); + } +} diff --git a/src/RQOverlay/Assets/RQOverlay.ico b/src/RQOverlay/Assets/RQOverlay.ico new file mode 100644 index 0000000..6410b9f Binary files /dev/null and b/src/RQOverlay/Assets/RQOverlay.ico differ diff --git a/src/RQOverlay/Converters.cs b/src/RQOverlay/Converters.cs new file mode 100644 index 0000000..47a74fe --- /dev/null +++ b/src/RQOverlay/Converters.cs @@ -0,0 +1,52 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; + +namespace RQOverlay; + +/// bool -> Visibility. ConverterParameter="!" инвертирует. +public sealed class BoolToVis : IValueConverter +{ + public object Convert(object value, Type t, object? p, CultureInfo c) + { + bool v = value is true; + if (p as string == "!") v = !v; + return v ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type t, object? p, CultureInfo c) => throw new NotSupportedException(); +} + +/// +/// Панель-полоска: делит ширину между детьми пропорционально их весу +/// (присоединённое свойство Weight). Для полоски долей дохода. +/// +public sealed class ShareStrip : Panel +{ + public static readonly DependencyProperty WeightProperty = DependencyProperty.RegisterAttached( + "Weight", typeof(double), typeof(ShareStrip), + new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsParentArrange)); + + public static double GetWeight(DependencyObject o) => (double)o.GetValue(WeightProperty); + public static void SetWeight(DependencyObject o, double v) => o.SetValue(WeightProperty, v); + + protected override Size MeasureOverride(Size available) + { + foreach (UIElement child in InternalChildren) child.Measure(available); + return new Size(0, 0); // растягиваемся на всё, что дадут + } + + protected override Size ArrangeOverride(Size final) + { + double total = InternalChildren.Cast().Sum(c => Math.Max(0, GetWeight(c))); + double x = 0; + foreach (UIElement child in InternalChildren) + { + double w = total > 0 ? final.Width * Math.Max(0, GetWeight(child)) / total : 0; + child.Arrange(new Rect(x, 0, w, final.Height)); + x += w; + } + return final; + } +} diff --git a/src/RQOverlay/Core/Config.cs b/src/RQOverlay/Core/Config.cs new file mode 100644 index 0000000..88a1e66 --- /dev/null +++ b/src/RQOverlay/Core/Config.cs @@ -0,0 +1,41 @@ +namespace RQOverlay.Core; + +// --------------------------------------------------------------------- +// НАСТРОЙКИ — правим только здесь +// --------------------------------------------------------------------- +public static class Config +{ + /// Имя процесса БЕЗ расширения .exe + public const string ProcessName = "rqmain"; + + /// + /// Сигнатура для поиска. Должна быть максимально уникальной: + /// чем длиннее и специфичнее — тем меньше мусора и быстрее скан. + /// + public const string Signature = " +/// Цвет записи из разметки, например "#FF0000". +public record LogEntry(string Time, string Text, string Color, string Html); + +public static class LogParser +{ + // Разбираем конструкции вида: + // текст + // Тело заканчивается на следующем ]*title='(?