diff --git a/ZeroSpace/ZeroSpace.Web/Pages/Galaxy.razor b/ZeroSpace/ZeroSpace.Web/Pages/Galaxy.razor index 6f0a1a6..2bc844c 100644 --- a/ZeroSpace/ZeroSpace.Web/Pages/Galaxy.razor +++ b/ZeroSpace/ZeroSpace.Web/Pages/Galaxy.razor @@ -1,5 +1,6 @@ @page "/galaxy" @using System.Globalization +@using System.Text.RegularExpressions @inject NavigationManager Navigation Galaxy Map @@ -72,17 +73,6 @@ -
- - -
- @if (HasActiveFilters) { + + + @if (!_drawerCollapsed) + { +
+
+ @foreach (var owner in StarSystemOwners.All) + { + var isActive = string.Equals(owner, _selectedFaction, StringComparison.Ordinal); + + } +
+ +
+

Occupational bonuses

+

+ @FactionSystems.Count systems owned by + @ShortFactionName(_selectedFaction) +

+ @if (FactionBonuses.Count == 0) + { +

No occupation buffs recorded for this faction's systems.

+ } + else + { + + } +
+ +
+

Planets (@FactionSystems.Count)

+ +
+
+ } + + @code { @@ -292,14 +357,16 @@ private StarSystemCategory? _selectedCategory; private string _selectedMastery = string.Empty; private string _selectedYield = string.Empty; - private string _selectedOwner = string.Empty; private int _matchingCount; + // Faction picker: one of the three factions is always selected. + private string _selectedFaction = StarSystemOwners.EdenAccord; + private bool _drawerCollapsed; + private bool HasActiveFilters => _selectedCategory.HasValue || !string.IsNullOrEmpty(_selectedMastery) || - !string.IsNullOrEmpty(_selectedYield) || - !string.IsNullOrEmpty(_selectedOwner); + !string.IsNullOrEmpty(_selectedYield); protected override void OnInitialized() { @@ -356,11 +423,6 @@ else if (_selectedYield == "Items" && !system.HigherChanceForItems) return false; } - if (!string.IsNullOrEmpty(_selectedOwner) && !string.Equals(system.Owner, _selectedOwner, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - return true; } @@ -369,10 +431,101 @@ _selectedCategory = null; _selectedMastery = string.Empty; _selectedYield = string.Empty; - _selectedOwner = string.Empty; ApplyFilters(); } + // ----- faction bonus drawer ----- + + public sealed record FactionBonus(string Label, bool IsPercent, double Total, int PlanetCount); + + private void SelectFaction(string owner) + { + if (string.Equals(_selectedFaction, owner, StringComparison.Ordinal)) + { + return; + } + + _selectedFaction = owner; + } + + private void ToggleDrawer() => _drawerCollapsed = !_drawerCollapsed; + + private int FactionSystemCount(string owner) => + _systems.Count(s => string.Equals(s.Owner, owner, StringComparison.OrdinalIgnoreCase)); + + private IReadOnlyList FactionSystems => + _systems + .Where(s => string.Equals(s.Owner, _selectedFaction, StringComparison.OrdinalIgnoreCase)) + .OrderBy(s => s.Name) + .ToList(); + + /// + /// Groups the selected faction's occupation buffs by bonus type and sums + /// the magnitudes, so e.g. two "+15% cooldown reduction" planets total +30%. + /// + private IReadOnlyList FactionBonuses + { + get + { + var groups = new Dictionary<(string Label, bool IsPercent), (double Total, int Count)>(); + foreach (var system in _systems) + { + if (!string.Equals(system.Owner, _selectedFaction, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (string.IsNullOrWhiteSpace(system.OccupationBuff)) + { + continue; + } + + var (amount, isPercent, label) = ParseOccupationBuff(system.OccupationBuff); + var key = (label, isPercent); + groups.TryGetValue(key, out var current); + groups[key] = (current.Total + amount, current.Count + 1); + } + + return groups + .Select(g => new FactionBonus(g.Key.Label, g.Key.IsPercent, g.Value.Total, g.Value.Count)) + .OrderByDescending(b => b.PlanetCount) + .ThenBy(b => b.Label) + .ToList(); + } + } + + /// Parses buffs shaped like "+50 Starting Hexite" or "+15% cooldown reduction". + private static (double Amount, bool IsPercent, string Label) ParseOccupationBuff(string buff) + { + var match = Regex.Match(buff.Trim(), @"^\+\s*([0-9]*\.?[0-9]+)\s*(%?)\s*(.+)$"); + if (match.Success && + double.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var amount)) + { + return (amount, match.Groups[2].Value == "%", match.Groups[3].Value.Trim()); + } + + return (0, false, buff.Trim()); + } + + private static string FormatBonusTotal(FactionBonus bonus) => + $"+{bonus.Total.ToString("0.##", CultureInfo.InvariantCulture)}{(bonus.IsPercent ? "%" : string.Empty)}"; + + private static string ShortFactionName(string owner) => owner switch + { + StarSystemOwners.EdenAccord => "Eden Accord", + StarSystemOwners.KeeperCouncil => "Keeper Council", + StarSystemOwners.ObsidianOrder => "Obsidian Order", + _ => owner + }; + + private static string FactionDotClass(string owner) => owner switch + { + StarSystemOwners.EdenAccord => "dot-eden", + StarSystemOwners.KeeperCouncil => "dot-keeper", + StarSystemOwners.ObsidianOrder => "dot-obsidian", + _ => string.Empty + }; + private static IReadOnlyList BuildUniqueConnections(IReadOnlyList allSystems) { var edges = new List();