6
StarHorizon_Public/Content.Client/_NF/Shipyard/UI/ShipyardConsoleMenu.xaml.cs
2026-01-24 12:49:55 +03:00

336 lines
12 KiB
C#

using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Client._NF.Shipyard.BUI;
using Content.Shared._NF.Bank;
using Content.Shared._NF.Shipyard.BUI;
using Content.Shared._NF.Shipyard.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton;
using Robust.Client.Player;
namespace Content.Client._NF.Shipyard.UI;
[GenerateTypedNameReferences]
public sealed partial class ShipyardConsoleMenu : FancyWindow
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!; // Horizon
[Dependency] private readonly IPlayerManager _player = default!; // Horizon
public event Action<ButtonEventArgs>? OnSellShip;
public event Action<ButtonEventArgs>? OnOrderApproved;
private readonly List<VesselSize> _categoryStrings = new();
private readonly List<VesselClass> _classStrings = new();
private readonly List<VesselEngine> _engineStrings = new();
private VesselSize? _category;
private VesselClass? _class;
private VesselEngine? _engine;
private List<string> _lastAvailableProtos = new();
private List<string> _lastUnavailableProtos = new();
private bool _freeListings = false;
private bool _validId = false;
private ConfirmButton? _currentlyConfirmingButton = null;
public EntityUid Owner = EntityUid.Invalid; // Horizon
public ShipyardConsoleMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Title = Loc.GetString("shipyard-console-menu-title");
SearchBar.OnTextChanged += OnSearchBarTextChanged;
Categories.OnItemSelected += OnCategoryItemSelected;
Classes.OnItemSelected += OnClassItemSelected;
Engines.OnItemSelected += OnEngineItemSelected;
SellShipButton.OnPressed += (args) => { OnSellShip?.Invoke(args); };
}
private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs args)
{
SetCategoryText(args.Id);
PopulateProducts(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
}
private void OnClassItemSelected(OptionButton.ItemSelectedEventArgs args)
{
SetClassText(args.Id);
PopulateProducts(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
}
private void OnEngineItemSelected(OptionButton.ItemSelectedEventArgs args)
{
SetEngineText(args.Id);
PopulateProducts(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
}
private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
{
PopulateProducts(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
}
private void SetCategoryText(int id)
{
_category = id == 0 ? null : _categoryStrings[id];
Categories.SelectId(id);
}
private void SetClassText(int id)
{
_class = id == 0 ? null : _classStrings[id];
Classes.SelectId(id);
}
private void SetEngineText(int id)
{
_engine = id == 0 ? null : _engineStrings[id];
Engines.SelectId(id);
}
/// <summary>
/// Populates the list of products that will actually be shown, using the current filters.
/// </summary>
public void PopulateProducts(List<string> availablePrototypes, List<string> unavailablePrototypes, bool free, bool canPurchase)
{
Vessels.RemoveAllChildren();
var search = SearchBar.Text.Trim().ToLowerInvariant();
var newVessels = GetVesselPrototypesFromIds(availablePrototypes);
AddVesselsToControls(newVessels, search, free, canPurchase);
var newUnavailableVessels = GetVesselPrototypesFromIds(unavailablePrototypes);
AddVesselsToControls(newUnavailableVessels, search, free, false);
_lastAvailableProtos = availablePrototypes;
_lastUnavailableProtos = unavailablePrototypes;
}
/// <summary>
/// Given a set of prototype IDs, returns a corresponding set of prototypes, ordered by name.
/// </summary>
private List<VesselPrototype?> GetVesselPrototypesFromIds(IEnumerable<string> protoIds)
{
var vesselList = protoIds.Select(it => _protoManager.TryIndex<VesselPrototype>(it, out var proto) ? proto : null)
.Where(it => it != null)
.ToList();
vesselList.Sort((x, y) =>
string.Compare(x!.Name, y!.Name, StringComparison.CurrentCultureIgnoreCase));
return vesselList;
}
/// <summary>
/// Adds all vessels in a given list of prototypes as VesselRows in the UI.
/// </summary>
private void AddVesselsToControls(IEnumerable<VesselPrototype?> vessels, string search, bool free, bool canPurchase)
{
foreach (var prototype in vessels)
{
// Filter any ships
if (_category != null && !prototype!.Category.Equals(_category))
continue;
if (_class != null && !prototype!.Classes.Contains(_class.Value))
continue;
if (_engine != null && !prototype!.Engines.Contains(_engine.Value))
continue;
if (search.Length > 0 && !prototype!.Name.ToLowerInvariant().Contains(search))
continue;
string priceText;
if (free)
priceText = Loc.GetString("shipyard-console-menu-listing-free");
else
{
// Horizon tweak start
var cost = prototype!.Price;
foreach (var item in prototype!.CostModifiers)
item.Modify(_player.LocalEntity, Owner, ref cost, _entMan);
priceText = BankSystemExtensions.ToSpesoString(cost);
var diff = cost - prototype.Price;
var percentage = (float)diff / (float)prototype.Price * 100.0f;
if (diff != 0)
priceText += $" ({(diff > 0 ? "+" : "")}{percentage:F1}%)";
// Horizon tweak end
}
var vesselEntry = new VesselRow
{
Vessel = prototype,
VesselName = { Text = prototype!.Name },
Purchase = { Text = Loc.GetString("shipyard-console-purchase-available"), Disabled = !canPurchase },
Guidebook = { Disabled = prototype.GuidebookPage is null, TooltipDelay = 0.2f, ToolTip = prototype.Description },
Price = { Text = priceText },
};
vesselEntry.Purchase.OnConfirming += OnStartConfirmingPurchase;
vesselEntry.Purchase.OnPressed += (args) => { _currentlyConfirmingButton = null; OnOrderApproved?.Invoke(args); };
Vessels.AddChild(vesselEntry);
}
}
/// <summary>
/// Confirming handler: ensures that only one button is confirming at a time.
/// </summary>
private void OnStartConfirmingPurchase(ButtonEventArgs args)
{
if (args.Button is not ConfirmButton confirmButton)
return;
if (_currentlyConfirmingButton != null)
_currentlyConfirmingButton.ClearIsConfirming();
_currentlyConfirmingButton = confirmButton;
}
/// <summary>
/// Populates the list categories that will actually be shown, using the current filters.
/// </summary>
public void PopulateCategories(List<string> availablePrototypes, List<string> unavailablePrototypes)
{
_categoryStrings.Clear();
Categories.Clear();
AddCategoriesFromPrototypes(availablePrototypes);
AddCategoriesFromPrototypes(unavailablePrototypes);
_categoryStrings.Sort();
// Add "All" category at the top of the list
_categoryStrings.Insert(0, VesselSize.All);
foreach (var str in _categoryStrings)
{
Categories.AddItem(Loc.GetString($"shipyard-console-category-{str}"));
}
}
/// <summary>
/// Adds all ship categories from a list of vessel prototypes to the current control's list if they are missing.
/// </summary>
private void AddCategoriesFromPrototypes(IEnumerable<string> prototypes)
{
foreach (var protoId in prototypes)
{
if (!_protoManager.TryIndex<VesselPrototype>(protoId, out var prototype))
continue;
if (!_categoryStrings.Contains(prototype.Category) && prototype.Category != VesselSize.All)
{
_categoryStrings.Add(prototype.Category);
}
}
}
/// <summary>
/// Populates the list classes that will actually be shown, using the current filters.
/// </summary>
public void PopulateClasses(List<string> availablePrototypes, List<string> unavailablePrototypes)
{
_classStrings.Clear();
Classes.Clear();
AddClassesFromPrototypes(availablePrototypes);
AddClassesFromPrototypes(unavailablePrototypes);
_classStrings.Sort();
// Add "All" category at the top of the list
_classStrings.Insert(0, VesselClass.All);
foreach (var str in _classStrings)
{
Classes.AddItem(Loc.GetString($"shipyard-console-class-{str}"));
}
}
/// <summary>
/// Adds all ship classes from a list of vessel prototypes to the current control's list if they are missing.
/// </summary>
private void AddClassesFromPrototypes(IEnumerable<string> prototypes)
{
foreach (var protoId in prototypes)
{
if (!_protoManager.TryIndex<VesselPrototype>(protoId, out var prototype))
continue;
foreach (var cl in prototype.Classes)
{
if (!_classStrings.Contains(cl) && cl != VesselClass.All)
{
_classStrings.Add(cl);
}
}
}
}
/// <summary>
/// Populates the list engines that will actually be shown, using the current filters.
/// </summary>
public void PopulateEngines(List<string> availablePrototypes, List<string> unavailablePrototypes)
{
_engineStrings.Clear();
Engines.Clear();
AddEnginesFromPrototypes(availablePrototypes);
AddEnginesFromPrototypes(unavailablePrototypes);
_engineStrings.Sort();
// Add "All" category at the top of the list
_engineStrings.Insert(0, VesselEngine.All);
foreach (var str in _engineStrings)
{
Engines.AddItem(Loc.GetString($"shipyard-console-engine-{str}"));
}
}
/// <summary>
/// Adds all ship engine power type from a list of vessel prototypes to the current control's list if they are missing.
/// </summary>
private void AddEnginesFromPrototypes(IEnumerable<string> prototypes)
{
foreach (var protoId in prototypes)
{
if (!_protoManager.TryIndex<VesselPrototype>(protoId, out var prototype))
continue;
foreach (var cl in prototype.Engines)
{
if (!_engineStrings.Contains(cl) && cl != VesselEngine.All)
{
_engineStrings.Add(cl);
}
}
}
}
public void UpdateState(ShipyardConsoleInterfaceState state)
{
BalanceLabel.Text = BankSystemExtensions.ToSpesoString(state.Balance);
var shipPrice = 0;
if (!state.FreeListings)
shipPrice = state.ShipSellValue;
ShipAppraisalLabel.Text = $"{BankSystemExtensions.ToSpesoString(shipPrice)} ({state.SellRate * 100.0f:F1}%)";
SellShipButton.Disabled = state.ShipDeedTitle == null;
TargetIdButton.Text = state.IsTargetIdPresent
? Loc.GetString("id-card-console-window-eject-button")
: Loc.GetString("id-card-console-window-insert-button");
if (state.ShipDeedTitle != null)
{
DeedTitle.Text = state.ShipDeedTitle;
}
else
{
DeedTitle.Text = $"None";
}
_freeListings = state.FreeListings;
_validId = state.IsTargetIdPresent;
PopulateProducts(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
}
}