using System.Linq; using System.Numerics; using Content.Shared._CP14.Skill; using Content.Shared._CP14.Skill.Components; using Content.Shared._CP14.Skill.Prototypes; using Content.Shared._CP14.Skill.Restrictions; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Client.Utility; using Robust.Shared.Input; using Robust.Shared.Prototypes; namespace Content.Client._CP14.Skill.Ui; [GenerateTypedNameReferences] public sealed partial class CP14SkillTreeGraphControl : BoxContainer { [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IPrototypeManager _proto = default!; private readonly CP14SharedSkillSystem _skillSystem; private Entity? _player; private IEnumerable _allSkills; private CP14SkillPrototype? _hoveredNode; private CP14SkillPrototype? _selectedNode; public CP14SkillTreePrototype? Tree; private bool dragging = false; private Vector2 _previousMousePosition = Vector2.Zero; private Vector2 _globalOffset = new (60,60); private const float GridSize = 25f; public event Action? OnNodeSelected; public event Action? OnOffsetChanged; public CP14SkillTreeGraphControl() { IoCManager.InjectDependencies(this); RobustXamlLoader.Load(this); _skillSystem = _entManager.System(); _allSkills = _proto.EnumeratePrototypes(); _proto.PrototypesReloaded += _ => _allSkills = _proto.EnumeratePrototypes().OrderBy(skill => _skillSystem.GetSkillName(skill)).ToList(); OnOffsetChanged?.Invoke(_globalOffset); } public void UpdateState(Entity? player) { _player = player; OnOffsetChanged?.Invoke(_globalOffset); } protected override void KeyBindDown(GUIBoundKeyEventArgs args) { base.KeyBindDown(args); if (args.Handled) return; if (args.Function == EngineKeyFunctions.UIClick) { dragging = true; if (_hoveredNode == null) return; OnNodeSelected?.Invoke(_hoveredNode); UserInterfaceManager.ClickSound(); _selectedNode = _hoveredNode; } if (args.Function == EngineKeyFunctions.UIRightClick) { _globalOffset = new Vector2(60, 60); // Reset offset on right click OnOffsetChanged?.Invoke(_globalOffset); } } protected override void KeyBindUp(GUIBoundKeyEventArgs args) { base.KeyBindUp(args); if (args.Handled || args.Function != EngineKeyFunctions.UIClick) return; dragging = false; } protected override void ExitedTree() { base.ExitedTree(); OnNodeSelected?.Invoke(null); } protected override void Draw(DrawingHandleScreen handle) { base.Draw(handle); _hoveredNode = null; if (_player == null || Tree == null) { return; } var cursor = (UserInterfaceManager.MousePositionScaled.Position * UIScale) - GlobalPixelPosition; if (dragging) { var delta = cursor - _previousMousePosition; _globalOffset += delta; OnOffsetChanged?.Invoke(_globalOffset); } _previousMousePosition = cursor; var playerSkills = _player.Value.Comp.LearnedSkills; //Draw connection lines foreach (var skill in _allSkills) { if (skill.Tree != Tree) continue; var fromPos = skill.SkillUiPosition * GridSize * UIScale + _globalOffset; foreach (var req in skill.Restrictions) { switch (req) { case NeedPrerequisite prerequisite: if (!_proto.TryIndex(prerequisite.Prerequisite, out var prerequisiteSkill)) continue; if (prerequisiteSkill.Tree != Tree) continue; var learned = playerSkills.Contains(skill.ID); var toPos = prerequisiteSkill.SkillUiPosition * GridSize * UIScale + _globalOffset; var color = learned ? Color.White : Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f)); handle.DrawLine(fromPos, toPos, color); break; } } } //Draw skill icons over lines foreach (var skill in _allSkills) { if (skill.Tree != Tree) continue; //TODO: Not optimized, recalculates every frame. Needs to be calculated on state update and cached. var canBeLearned = _skillSystem.CanLearnSkill(_player.Value, skill, _player.Value.Comp); var pos = skill.SkillUiPosition * GridSize * UIScale + _globalOffset; // Base skill icon var baseTexture = skill.Icon.Frame0(); var baseSize = new Vector2(baseTexture.Width, baseTexture.Height) * 2; var baseQuad = new UIBox2(pos - baseSize / 2, pos + baseSize / 2); var hovered = (cursor - pos).LengthSquared() <= (baseSize.X / 2) * (baseSize.X / 2); // Frame var frameTexture = Tree.FrameIcon.Frame0(); var frameSize = new Vector2(frameTexture.Width, frameTexture.Height) * 2; var frameQuad = new UIBox2(pos - frameSize / 2, pos + frameSize / 2); handle.DrawTextureRect(frameTexture, frameQuad, canBeLearned ? Color.White : Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f))); // Selected Skill if (_selectedNode == skill) { var selectedTexture = Tree.SelectedIcon.Frame0(); var selectedSize = new Vector2(selectedTexture.Width, selectedTexture.Height) * 2; var selectedQuad = new UIBox2(pos - selectedSize / 2, pos + selectedSize / 2); handle.DrawTextureRect(selectedTexture, selectedQuad, Color.White); } // Hovered Skill if (hovered) { _hoveredNode = skill; var hoveredTexture = Tree.HoveredIcon.Frame0(); var hoveredSize = new Vector2(hoveredTexture.Width, hoveredTexture.Height) * 2; var hoveredQuad = new UIBox2(pos - hoveredSize / 2, pos + hoveredSize / 2); handle.DrawTextureRect(hoveredTexture, hoveredQuad, Color.White); } var learned = playerSkills.Contains(skill.ID); var allowedToLearn = _skillSystem.AllowedToLearn(_player.Value, skill, _player.Value.Comp); // Learned Skill if (learned) { var learnedTexture = Tree.LearnedIcon.Frame0(); var learnedSize = new Vector2(learnedTexture.Width, learnedTexture.Height) * 2; var learnedQuad = new UIBox2(pos - learnedSize / 2, pos + learnedSize / 2); handle.DrawTextureRect(learnedTexture, learnedQuad, Color.White); } else if (canBeLearned) { var availableTexture = Tree.AvailableIcon.Frame0(); var availableSize = new Vector2(availableTexture.Width, availableTexture.Height) * 2; var availableQuad = new UIBox2(pos - availableSize / 2, pos + availableSize / 2); handle.DrawTextureRect(availableTexture, availableQuad, Color.White); } var iconColor = allowedToLearn switch { true when !learned => Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f)), false when !learned => Color.FromSrgb(new Color(0f, 0f, 0f)), _ => Color.White }; handle.DrawTextureRect(baseTexture, baseQuad, iconColor); } } }