352 lines
11 KiB
C#
352 lines
11 KiB
C#
using Content.Client.Parallax;
|
|
using Content.Client.Research;
|
|
using Content.Client.UserInterface.Controls;
|
|
using Content.Shared._NF.Research;
|
|
using Content.Shared.Access.Systems;
|
|
using Content.Shared.Research.Components;
|
|
using Content.Shared.Research.Prototypes;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.GameObjects;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.Player;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Shared.Input;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Utility;
|
|
using System.Numerics;
|
|
|
|
namespace Content.Client._NF.Research.UI;
|
|
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class FancyResearchConsoleMenu : FancyWindow
|
|
{
|
|
public Action<string>? OnTechnologyCardPressed;
|
|
public Action? OnServerButtonPressed;
|
|
|
|
[Dependency] private readonly IEntityManager _entity = default!;
|
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
|
[Dependency] private readonly IPlayerManager _player = default!;
|
|
|
|
private readonly ResearchSystem _research;
|
|
private readonly SpriteSystem _sprite;
|
|
private readonly AccessReaderSystem _accessReader;
|
|
private ParallaxControl _parallaxControl;
|
|
|
|
/// <summary>
|
|
/// The parallax prototype to use for the background. Configurable.
|
|
/// </summary>
|
|
public string ParallaxPrototype { get; set; } = "Default";
|
|
|
|
/// <summary>
|
|
/// Updates the parallax background to use a different prototype
|
|
/// </summary>
|
|
/// <param name="parallaxPrototype">The new parallax prototype to use</param>
|
|
public void SetParallaxPrototype(string parallaxPrototype)
|
|
{
|
|
ParallaxPrototype = parallaxPrototype;
|
|
if (_parallaxControl != null)
|
|
{
|
|
_parallaxControl.ParallaxPrototype = parallaxPrototype;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Console entity
|
|
/// </summary>
|
|
public EntityUid Entity;
|
|
|
|
/// <summary>
|
|
/// Currently selected tech
|
|
/// Exists for better UI refreshing
|
|
/// </summary>
|
|
public ProtoId<TechnologyPrototype>? CurrentTech;
|
|
|
|
/// <summary>
|
|
/// All technologies and their availability
|
|
/// </summary>
|
|
public Dictionary<string, ResearchAvailability> List = new();
|
|
|
|
/// <summary>
|
|
/// Cached research points
|
|
/// </summary>
|
|
public int Points = 0;
|
|
|
|
/// <summary>
|
|
/// Is tech currently being dragged
|
|
/// </summary>
|
|
private bool _draggin;
|
|
|
|
/// <summary>
|
|
/// the distance between elements on the grid.
|
|
/// </summary>
|
|
private const int GridSize = 90;
|
|
|
|
/// <summary>
|
|
/// technology cards size.
|
|
/// </summary>
|
|
private const int CardSize = 64;
|
|
|
|
/// <summary>
|
|
/// the origin point of the grid.
|
|
/// </summary>
|
|
private static readonly Vector2i DefaultPosition = Vector2i.Zero;
|
|
|
|
private Box2i _bounds = new(DefaultPosition, DefaultPosition);
|
|
|
|
public FancyResearchConsoleMenu()
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
IoCManager.InjectDependencies(this);
|
|
_research = _entity.System<ResearchSystem>();
|
|
_sprite = _entity.System<SpriteSystem>();
|
|
_accessReader = _entity.System<AccessReaderSystem>();
|
|
|
|
// Set up scroll container properties
|
|
TechScrollContainer.ScrollSpeedX = 100;
|
|
TechScrollContainer.ScrollSpeedY = 100;
|
|
TechScrollContainer.HScrollEnabled = false;
|
|
TechScrollContainer.VScrollEnabled = true;
|
|
|
|
// Frontier: Initialize parallax background
|
|
_parallaxControl = new ParallaxControl
|
|
{
|
|
ParallaxPrototype = ParallaxPrototype,
|
|
HorizontalExpand = true,
|
|
VerticalExpand = true,
|
|
};
|
|
|
|
// Add the parallax control to the ResearchesContainer at the beginning (bottom layer)
|
|
ResearchesContainer.AddChild(_parallaxControl);
|
|
|
|
// Set the proper rendering order by adjusting positions in the parent's child list
|
|
// Controls with a higher position value are drawn on top (foreground)
|
|
// Make sure the parallax is at the bottom of the z-order (drawn first)
|
|
_parallaxControl.SetPositionInParent(0);
|
|
|
|
// The drag container should be in the middle
|
|
TechScrollContainer.SetPositionInParent(1);
|
|
|
|
// Apply scrollbar styling
|
|
ApplyScrollbarStyling();
|
|
|
|
// Set up event handlers
|
|
ServerButton.OnPressed += _ => OnServerButtonPressed?.Invoke();
|
|
DragContainer.OnKeyBindDown += OnKeybindDown;
|
|
DragContainer.OnKeyBindUp += OnKeybindUp;
|
|
RecenterButton.OnPressed += _ => Recenter();
|
|
|
|
// Empty initialization
|
|
UpdatePanels(List);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply scrollbar styling using centralized colors and utilities
|
|
/// </summary>
|
|
private void ApplyScrollbarStyling()
|
|
{
|
|
var scrollBarNormal = ResearchUIHelpers.CreateScrollbarStyleBox("normal");
|
|
var scrollBarHovered = ResearchUIHelpers.CreateScrollbarStyleBox("hovered");
|
|
var scrollBarGrabbed = ResearchUIHelpers.CreateScrollbarStyleBox("grabbed");
|
|
}
|
|
|
|
public void SetEntity(EntityUid entity)
|
|
=> Entity = entity;
|
|
|
|
public void UpdatePanels(Dictionary<string, ResearchAvailability> dict)
|
|
{
|
|
// Clear existing items
|
|
DragContainer.RemoveAllChildren();
|
|
|
|
List = dict;
|
|
var bounds = new Box2i();
|
|
var boundsSet = false;
|
|
|
|
// Calculate bounds
|
|
foreach (var tech in List)
|
|
{
|
|
var proto = _prototype.Index<TechnologyPrototype>(tech.Key);
|
|
var position = DefaultPosition + (GridSize * proto.Position.X, GridSize * proto.Position.Y);
|
|
|
|
if (!boundsSet)
|
|
{
|
|
bounds.BottomLeft = position;
|
|
bounds.TopRight = position;
|
|
boundsSet = true;
|
|
}
|
|
else
|
|
{
|
|
bounds.Left = int.Min(position.X, bounds.Left);
|
|
bounds.Bottom = int.Min(position.Y, bounds.Bottom);
|
|
bounds.Right = int.Max(position.X, bounds.Right);
|
|
bounds.Top = int.Max(position.Y, bounds.Top);
|
|
}
|
|
}
|
|
|
|
if (boundsSet)
|
|
{
|
|
_bounds = bounds;
|
|
|
|
// Set container size with padding
|
|
var padding = 200;
|
|
var totalWidth = _bounds.Width + CardSize + padding;
|
|
var totalHeight = _bounds.Height + CardSize + padding;
|
|
|
|
DragContainer.SetWidth = Math.Max(totalWidth, 1000);
|
|
DragContainer.SetHeight = Math.Max(totalHeight, 1000);
|
|
}
|
|
|
|
// Add tech items
|
|
foreach (var tech in List)
|
|
{
|
|
var proto = _prototype.Index<TechnologyPrototype>(tech.Key);
|
|
var control = new FancyResearchConsoleItem(proto, _sprite, tech.Value);
|
|
|
|
DragContainer.AddChild(control);
|
|
|
|
// Position the tech item
|
|
var leftPadding = 20;
|
|
var topPadding = 20;
|
|
var uiPosition = new Vector2(
|
|
proto.Position.X * GridSize - _bounds.Left + leftPadding,
|
|
proto.Position.Y * GridSize - _bounds.Bottom + topPadding
|
|
);
|
|
|
|
LayoutContainer.SetPosition(control, uiPosition);
|
|
control.SelectAction += SelectTech;
|
|
control.IsSelected = tech.Key == CurrentTech;
|
|
}
|
|
}
|
|
|
|
public void UpdateInformationPanel(int points)
|
|
{
|
|
Points = points;
|
|
|
|
var amountMsg = new FormattedMessage();
|
|
amountMsg.AddMarkupOrThrow(Loc.GetString("research-console-menu-research-points-text",
|
|
("points", points)));
|
|
ResearchAmountLabel.SetMessage(amountMsg);
|
|
|
|
if (!_entity.TryGetComponent(Entity, out TechnologyDatabaseComponent? database))
|
|
return;
|
|
|
|
TierDisplayContainer.RemoveAllChildren();
|
|
foreach (var disciplineId in database.SupportedDisciplines)
|
|
{
|
|
var discipline = _prototype.Index<TechDisciplinePrototype>(disciplineId);
|
|
var tier = _research.GetTierCompletionPercentage(database, discipline, _prototype);
|
|
|
|
var texture = new TextureRect
|
|
{
|
|
TextureScale = new Vector2(2, 2),
|
|
VerticalAlignment = VAlignment.Center
|
|
};
|
|
var label = new RichTextLabel();
|
|
texture.Texture = _sprite.Frame0(discipline.Icon);
|
|
label.SetMessage(Loc.GetString("research-console-tier-percentage", ("perc", tier)));
|
|
|
|
var control = new BoxContainer
|
|
{
|
|
Children =
|
|
{
|
|
texture,
|
|
label,
|
|
new Control
|
|
{
|
|
MinWidth = 10
|
|
}
|
|
}
|
|
};
|
|
TierDisplayContainer.AddChild(control);
|
|
}
|
|
}
|
|
|
|
#region Drag handle
|
|
protected override void MouseMove(GUIMouseMoveEventArgs args)
|
|
{
|
|
base.MouseMove(args);
|
|
|
|
if (!_draggin)
|
|
return;
|
|
|
|
// Adjust scroll position with drag
|
|
var scrollSpeed = 2.0f;
|
|
TechScrollContainer.VScrollTarget -= args.Relative.Y * scrollSpeed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised when LMB is pressed at <see cref="DragContainer"/>
|
|
/// </summary>
|
|
private void OnKeybindDown(GUIBoundKeyEventArgs args)
|
|
{
|
|
if (args.Function == EngineKeyFunctions.Use)
|
|
_draggin = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised when LMB is unpressed at <see cref="DragContainer"/>
|
|
/// </summary>
|
|
private void OnKeybindUp(GUIBoundKeyEventArgs args)
|
|
{
|
|
if (args.Function == EngineKeyFunctions.Use)
|
|
_draggin = false;
|
|
}
|
|
|
|
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
|
=> _draggin ? DragMode.None : base.GetDragModeFor(relativeMousePos);
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Selects a tech prototype and opens info panel
|
|
/// </summary>
|
|
/// <param name="proto">Tech proto</param>
|
|
/// <param name="availability">Tech availability</param>
|
|
public void SelectTech(TechnologyPrototype proto, ResearchAvailability availability)
|
|
{
|
|
InfoContainer.RemoveAllChildren();
|
|
if (!_player.LocalEntity.HasValue)
|
|
return;
|
|
|
|
// Update selection
|
|
CurrentTech = proto.ID;
|
|
|
|
// Update visual selection state
|
|
foreach (var child in DragContainer.Children)
|
|
{
|
|
if (child is FancyResearchConsoleItem techItem)
|
|
{
|
|
techItem.IsSelected = techItem.Prototype.ID == CurrentTech;
|
|
}
|
|
}
|
|
|
|
// Create and add info panel
|
|
var control = new FancyTechnologyInfoPanel(proto, _accessReader.IsAllowed(_player.LocalEntity.Value, Entity), availability, _sprite);
|
|
control.BuyAction += args => OnTechnologyCardPressed?.Invoke(args.ID);
|
|
InfoContainer.AddChild(control);
|
|
}
|
|
|
|
public void Recenter()
|
|
{
|
|
// Reset scroll position
|
|
TechScrollContainer.VScrollTarget = 0;
|
|
}
|
|
|
|
public override void Close()
|
|
{
|
|
base.Close();
|
|
DragContainer.RemoveAllChildren();
|
|
InfoContainer.RemoveAllChildren();
|
|
}
|
|
|
|
private sealed partial class DisciplineButton(TechDisciplinePrototype proto) : Button
|
|
{
|
|
public TechDisciplinePrototype Proto = proto;
|
|
}
|
|
|
|
protected override void MouseWheel(GUIMouseWheelEventArgs args)
|
|
{
|
|
base.MouseWheel(args);
|
|
}
|
|
}
|