using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Content.Shared._NF.Research; using System.Linq; using System.Numerics; namespace Content.Client._NF.Research.UI; /// /// UI element for visualizing technologies prerequisites with simple L-shaped connections /// public sealed partial class ResearchesContainerPanel : LayoutContainer { public ResearchesContainerPanel() { } protected override void Draw(DrawingHandleScreen handle) { // First draw all children (tech items) base.Draw(handle); // Then draw simple prerequisite lines DrawSimplePrerequisiteLines(handle); } private void DrawSimplePrerequisiteLines(DrawingHandleScreen handle) { foreach (var child in Children) { if (child is not FancyResearchConsoleItem dependentItem) continue; if (dependentItem.Prototype.TechnologyPrerequisites.Count <= 0) continue; var prerequisiteItems = Children.Where(x => x is FancyResearchConsoleItem second && dependentItem.Prototype.TechnologyPrerequisites.Contains(second.Prototype.ID)) .Cast().ToList(); foreach (var prerequisiteItem in prerequisiteItems) { // Calculate simple connection points var startCoords = GetSimpleConnectionPoint(prerequisiteItem, dependentItem, true); var endCoords = GetSimpleConnectionPoint(dependentItem, prerequisiteItem, false); // Determine line color based on dependent tech's availability var lineColor = GetRefinedConnectionColor(prerequisiteItem, dependentItem); // Draw simple L-shaped connection DrawSimpleLShapeConnection(handle, startCoords, endCoords, lineColor); } } } /// /// Get simple connection point on tech edge - centered approach /// private Vector2 GetSimpleConnectionPoint(FancyResearchConsoleItem tech, FancyResearchConsoleItem otherTech, bool isStart) { var techRect = GetTechRect(tech); var techCenter = techRect.Center; // Always connect from the center of the tech box return techCenter; } /// /// Draw a simple L-shaped connection between two points /// private void DrawSimpleLShapeConnection(DrawingHandleScreen handle, Vector2 start, Vector2 end, Color color) { var deltaX = end.X - start.X; var deltaY = end.Y - start.Y; // Check if it's a direct line (same row or column) if (Math.Abs(deltaX) < 10) // Same column - direct vertical { DrawCleanLine(handle, start, end, color); var arrowPos = Vector2.Lerp(start, end, 0.85f); DrawArrowHead(handle, arrowPos, (end - start).Normalized(), color); } else if (Math.Abs(deltaY) < 10) // Same row - direct horizontal { DrawCleanLine(handle, start, end, color); var arrowPos = Vector2.Lerp(start, end, 0.85f); DrawArrowHead(handle, arrowPos, (end - start).Normalized(), color); } else // Different row and column - L-shaped { // Create simple L-shape with a corner Vector2 corner; // Choose corner based on which direction is predominant if (Math.Abs(deltaX) > Math.Abs(deltaY)) { // Horizontal-first L-shape corner = new Vector2(end.X, start.Y); } else { // Vertical-first L-shape corner = new Vector2(start.X, end.Y); } // Draw the L-shaped path DrawCleanLine(handle, start, corner, color); DrawCleanLine(handle, corner, end, color); // Draw arrow at the end var finalDirection = (end - corner).Normalized(); if (finalDirection.LengthSquared() > 0.1f) { var arrowPos = end - finalDirection * 4f; DrawArrowHead(handle, arrowPos, finalDirection, color); } } } /// /// Get the visual rectangle of a tech item /// private UIBox2 GetTechRect(FancyResearchConsoleItem tech) { var position = new Vector2(tech.PixelPosition.X, tech.PixelPosition.Y); var size = new Vector2(tech.PixelWidth, tech.PixelHeight); var padding = 6f; return new UIBox2( position.X + padding, position.Y + padding, position.X + size.X - padding, position.Y + size.Y - padding ); } /// /// Determine connection color based on availability /// private Color GetRefinedConnectionColor(FancyResearchConsoleItem prerequisite, FancyResearchConsoleItem dependent) { return dependent.Availability switch { // Researched - bright green ResearchAvailability.Researched => Color.FromHex("#32CD32"), // Available to research - bright yellow ResearchAvailability.Available => Color.FromHex("#FFD700"), // Prerequisites met but not available - orange ResearchAvailability.PrereqsMet => Color.FromHex("#FFA500"), // Unavailable - muted red ResearchAvailability.Unavailable => Color.FromHex("#CD5C5C"), // Default fallback - neutral gray _ => Color.FromHex("#808080") }; } /// /// Draw a clean line with subtle thickness /// private void DrawCleanLine(DrawingHandleScreen handle, Vector2 start, Vector2 end, Color color) { handle.DrawLine(start, end, color); // Add subtle thickness var direction = (end - start).Normalized(); var perpendicular = new Vector2(-direction.Y, direction.X) * 0.5f; var thicknessColor = new Color(color.R, color.G, color.B, color.A * 0.6f); handle.DrawLine(start + perpendicular, end + perpendicular, thicknessColor); handle.DrawLine(start - perpendicular, end - perpendicular, thicknessColor); } /// /// Draw a directional arrow /// private void DrawArrowHead(DrawingHandleScreen handle, Vector2 position, Vector2 direction, Color color) { if (direction.LengthSquared() < 0.1f) return; var arrowSize = 5f; var arrowAngle = MathF.PI / 4; // 45 degrees var arrowLeft = position - Vector2.Transform(direction, Matrix3x2.CreateRotation(arrowAngle)) * arrowSize; var arrowRight = position - Vector2.Transform(direction, Matrix3x2.CreateRotation(-arrowAngle)) * arrowSize; handle.DrawLine(position, arrowLeft, color); handle.DrawLine(position, arrowRight, color); } }