190 lines
6.8 KiB
C#
190 lines
6.8 KiB
C#
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
using System.Globalization;
|
||
using System.Windows;
|
||
using RQOverlay.Core;
|
||
using RQOverlay.Plugins;
|
||
|
||
namespace RQOverlay;
|
||
|
||
/// <summary>Состояние текущей сессии фарма: время, показатели включённых плагинов, итог.</summary>
|
||
public sealed class SessionViewModel : Observable
|
||
{
|
||
readonly PriceBook _prices;
|
||
readonly PluginManager _plugins;
|
||
DateTime _start = DateTime.Now;
|
||
|
||
public SessionViewModel(PriceBook prices, PluginManager plugins)
|
||
{
|
||
_prices = prices;
|
||
_plugins = plugins;
|
||
_plugins.Changed += Rebuild;
|
||
Rebuild();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ручная корректировка заработка в золоте (например, минус потраченное на зелья).
|
||
/// Показывается в оверлее отдельной строкой, только когда не ноль.
|
||
/// </summary>
|
||
public Metric Adjustment { get; } = new("Корректировка", "#9AA3B5", isGold: true);
|
||
|
||
public string AdjustmentText
|
||
{
|
||
get => Adjustment.Value == 0 ? "" : Adjustment.Value.ToString("0.##", CultureInfo.CurrentCulture);
|
||
set
|
||
{
|
||
string s = (value ?? "").Trim().Replace(" ", "").Replace(" ", "").Replace(',', '.');
|
||
decimal v;
|
||
if (s.Length == 0 || s == "-") v = 0;
|
||
else if (!decimal.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out v)) return;
|
||
|
||
bool wasShown = Adjustment.Value != 0;
|
||
Adjustment.Value = v;
|
||
Raise();
|
||
if (wasShown != (v != 0)) Rebuild();
|
||
else RaiseTotals();
|
||
}
|
||
}
|
||
|
||
/// <summary>Показатели всех включённых плагинов, по порядку.</summary>
|
||
public ObservableCollection<Metric> Metrics { get; } = new();
|
||
|
||
/// <summary>Золотые показатели — для полоски долей.</summary>
|
||
public ObservableCollection<Metric> GoldMetrics { get; } = new();
|
||
|
||
/// <summary>Дополнительные блоки плагинов в оверлее (список добычи и т.п.).</summary>
|
||
public ObservableCollection<FrameworkElement> OverlayViews { get; } = new();
|
||
|
||
decimal TotalGold => GoldMetrics.Sum(m => m.Value);
|
||
double Hours => (DateTime.Now - _start).TotalHours;
|
||
|
||
public bool HasGold => GoldMetrics.Count > 0;
|
||
public bool HasIncome => TotalGold > 0;
|
||
public string Total => Fmt.Gold(TotalGold);
|
||
|
||
public string PerHour =>
|
||
// Первую минуту темп скачет слишком сильно, чтобы что-то значить.
|
||
Hours < 1.0 / 60 ? "— / ч" : Fmt.Gold(TotalGold / (decimal)Hours) + " / ч";
|
||
|
||
public string Elapsed
|
||
{
|
||
get
|
||
{
|
||
var t = DateTime.Now - _start;
|
||
return $"{(int)t.TotalHours}:{t.Minutes:00}:{t.Seconds:00}";
|
||
}
|
||
}
|
||
|
||
/// <summary>Оверлей пропускает клики мыши в игру.</summary>
|
||
bool _clickThrough;
|
||
public bool ClickThrough { get => _clickThrough; set => Set(ref _clickThrough, value); }
|
||
|
||
bool _listExpanded = true;
|
||
public bool ListExpanded { get => _listExpanded; set => Set(ref _listExpanded, value); }
|
||
|
||
bool _connected;
|
||
public bool Connected { get => _connected; set => Set(ref _connected, value); }
|
||
|
||
string _status = "Запуск…";
|
||
public string Status { get => _status; set => Set(ref _status, value); }
|
||
|
||
/// <summary>Раз в секунду: таймер и темп.</summary>
|
||
public void Tick()
|
||
{
|
||
double hours = Hours;
|
||
foreach (var m in Metrics) m.UpdateRate(hours);
|
||
Raise(nameof(Elapsed));
|
||
Raise(nameof(PerHour));
|
||
}
|
||
|
||
public void Reset()
|
||
{
|
||
_start = DateTime.Now;
|
||
_plugins.ResetSession();
|
||
AdjustmentText = "";
|
||
Tick();
|
||
}
|
||
|
||
/// <param name="initial">Первый снимок — история до запуска оверлея.</param>
|
||
/// <param name="countHistory">Засчитывать ли историю в сессию (флаг --history).</param>
|
||
public void Apply(IEnumerable<LogEntry> entries, bool initial, bool countHistory)
|
||
{
|
||
foreach (var entry in entries)
|
||
{
|
||
var events = GameEvents.Parse(entry);
|
||
if (events.Count == 0) continue;
|
||
|
||
// Цены учим из любых продаж, в том числе из истории лога.
|
||
foreach (var s in events.OfType<ItemSold>())
|
||
_prices.LearnFromSale(s.Name, s.Qty, s.Total);
|
||
|
||
if (!initial)
|
||
_plugins.Dispatch(entry, events, isLive: true);
|
||
else if (countHistory)
|
||
_plugins.Dispatch(entry, events, isLive: false);
|
||
}
|
||
}
|
||
|
||
/// <summary>Набор включённых плагинов изменился — пересобираем оверлей.</summary>
|
||
void Rebuild()
|
||
{
|
||
foreach (var m in Metrics) m.PropertyChanged -= OnMetricChanged;
|
||
Metrics.Clear();
|
||
GoldMetrics.Clear();
|
||
OverlayViews.Clear();
|
||
|
||
foreach (var plugin in _plugins.Enabled)
|
||
{
|
||
foreach (var m in plugin.Metrics)
|
||
{
|
||
Metrics.Add(m);
|
||
if (m.IsGold) GoldMetrics.Add(m);
|
||
m.PropertyChanged += OnMetricChanged;
|
||
}
|
||
try
|
||
{
|
||
if (plugin.CreateOverlayView() is { } view) OverlayViews.Add(view);
|
||
}
|
||
catch { /* сломанный блок плагина просто не показываем */ }
|
||
}
|
||
|
||
if (Adjustment.Value != 0)
|
||
{
|
||
Metrics.Add(Adjustment);
|
||
GoldMetrics.Add(Adjustment);
|
||
Adjustment.PropertyChanged += OnMetricChanged;
|
||
}
|
||
|
||
Tick();
|
||
RaiseTotals();
|
||
Raise(nameof(HasGold));
|
||
}
|
||
|
||
void OnMetricChanged(object? sender, PropertyChangedEventArgs e)
|
||
{
|
||
if (e.PropertyName == nameof(Metric.Value) && sender is Metric { IsGold: true })
|
||
RaiseTotals();
|
||
}
|
||
|
||
void RaiseTotals()
|
||
{
|
||
Raise(nameof(Total)); Raise(nameof(PerHour)); Raise(nameof(HasIncome));
|
||
}
|
||
}
|
||
|
||
public static class Fmt
|
||
{
|
||
static readonly NumberFormatInfo Nfi = new() { NumberGroupSeparator = " ", NumberDecimalSeparator = "," };
|
||
|
||
public static string Gold(decimal v) => Math.Round(v).ToString("#,0", Nfi);
|
||
public static string Gold(long v) => v.ToString("#,0", Nfi);
|
||
|
||
public static string Plural(int n, string one, string few, string many)
|
||
{
|
||
int m10 = n % 10, m100 = n % 100;
|
||
if (m10 == 1 && m100 != 11) return one;
|
||
if (m10 is >= 2 and <= 4 && m100 is < 12 or > 14) return few;
|
||
return many;
|
||
}
|
||
}
|