diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 8ba399fb61..e3fabd8cd5 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -84,6 +84,9 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.Arcade1); human.AddFunction(ContentKeyFunctions.Arcade2); human.AddFunction(ContentKeyFunctions.Arcade3); + //CP14 Keys + human.AddFunction(ContentKeyFunctions.CP14OpenSkillMenu); + //CP14 Keys end // actions should be common (for ghosts, mobs, etc) common.AddFunction(ContentKeyFunctions.OpenActionsMenu); @@ -123,10 +126,6 @@ namespace Content.Client.Input common.AddFunction(ContentKeyFunctions.OpenDecalSpawnWindow); common.AddFunction(ContentKeyFunctions.OpenAdminMenu); common.AddFunction(ContentKeyFunctions.OpenGuidebook); - - //CP14 Keys - human.AddFunction(ContentKeyFunctions.CP14OpenKnowledgeMenu); - //CP14 Keys end } } } diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index c9b6ec841a..db12f66df9 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -312,9 +312,8 @@ namespace Content.Client.Options.UI.Tabs //CP14 AddHeader("ui-options-header-cp14"); - AddButton(ContentKeyFunctions.CP14OpenKnowledgeMenu); + AddButton(ContentKeyFunctions.CP14OpenSkillMenu); //CP14 end - foreach (var control in _keyControls.Values) { UpdateKeyControl(control); diff --git a/Content.Client/Parallax/ParallaxControl.cs b/Content.Client/Parallax/ParallaxControl.cs index 8f56857214..02875c302e 100644 --- a/Content.Client/Parallax/ParallaxControl.cs +++ b/Content.Client/Parallax/ParallaxControl.cs @@ -1,7 +1,9 @@ -using System.Numerics; +using System.Numerics; +using Content.Client.Parallax.Data; using Content.Client.Parallax.Managers; using Robust.Client.Graphics; using Robust.Client.UserInterface; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.ViewVariables; @@ -17,27 +19,50 @@ public sealed class ParallaxControl : Control [Dependency] private readonly IParallaxManager _parallaxManager = default!; [Dependency] private readonly IRobustRandom _random = default!; + private string _parallaxPrototype = "FastSpace"; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 Offset { get; set; } + [ViewVariables(VVAccess.ReadWrite)] public float SpeedX { get; set; } = 0.0f; + [ViewVariables(VVAccess.ReadWrite)] public float SpeedY { get; set; } = 0.0f; + [ViewVariables(VVAccess.ReadWrite)] public float ScaleX { get; set; } = 1.0f; + [ViewVariables(VVAccess.ReadWrite)] public float ScaleY { get; set; } = 1.0f; + [ViewVariables(VVAccess.ReadWrite)] public string ParallaxPrototype + { + get => _parallaxPrototype; + set + { + _parallaxPrototype = value; + _parallaxManager.LoadParallaxByName(value); + } + } public ParallaxControl() { IoCManager.InjectDependencies(this); Offset = new Vector2(_random.Next(0, 1000), _random.Next(0, 1000)); + RectClipContent = true; - _parallaxManager.LoadParallaxByName("FastSpace"); + _parallaxManager.LoadParallaxByName(_parallaxPrototype); } protected override void Draw(DrawingHandleScreen handle) { - foreach (var layer in _parallaxManager.GetParallaxLayers("FastSpace")) + var currentTime = (float) _timing.RealTime.TotalSeconds; + var offset = Offset + new Vector2(currentTime * SpeedX, currentTime * SpeedY); + + foreach (var layer in _parallaxManager.GetParallaxLayers(_parallaxPrototype)) { var tex = layer.Texture; - var texSize = (tex.Size.X * (int) Size.X, tex.Size.Y * (int) Size.X) * layer.Config.Scale.Floored() / 1920; + var texSize = new Vector2i( + (int)(tex.Size.X * Size.X * layer.Config.Scale.X / 1920 * ScaleX), + (int)(tex.Size.Y * Size.X * layer.Config.Scale.Y / 1920 * ScaleY) + ); var ourSize = PixelSize; - var currentTime = (float) _timing.RealTime.TotalSeconds; - var offset = Offset + new Vector2(currentTime * 100f, currentTime * 0f); + //Protection from division by zero. + texSize.X = Math.Max(texSize.X, 1); + texSize.Y = Math.Max(texSize.Y, 1); if (layer.Config.Tiled) { diff --git a/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs b/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs index afb45ec172..4ac49ceebc 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs +++ b/Content.Client/UserInterface/Systems/MenuBar/GameTopMenuBarUIController.cs @@ -24,7 +24,7 @@ public sealed class GameTopMenuBarUIController : UIController [Dependency] private readonly SandboxUIController _sandbox = default!; [Dependency] private readonly GuidebookUIController _guidebook = default!; [Dependency] private readonly EmotesUIController _emotes = default!; - [Dependency] private readonly CP14KnowledgeUIController _knowledge = default!; //CP14 + [Dependency] private readonly CP14SkillUIController _skill = default!; //CP14 private GameTopMenuBar? GameTopMenuBar => UIManager.GetActiveUIWidgetOrNull(); @@ -48,7 +48,7 @@ public sealed class GameTopMenuBarUIController : UIController _action.UnloadButton(); _sandbox.UnloadButton(); _emotes.UnloadButton(); - _knowledge.UnloadButton(); //CP14 + _skill.UnloadButton(); //CP14 } public void LoadButtons() @@ -62,6 +62,6 @@ public sealed class GameTopMenuBarUIController : UIController _action.LoadButton(); _sandbox.LoadButton(); _emotes.LoadButton(); - _knowledge.LoadButton(); //CP14 + _skill.LoadButton(); //CP14 } } diff --git a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml index aaf230d266..9bcf64cf0e 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml +++ b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml @@ -35,11 +35,11 @@ /> ? OnKnowledgeUpdate; - - public override void Initialize() - { - base.Initialize(); - - SubscribeNetworkEvent(OnCharacterKnowledgeEvent); - } - - public void RequestKnowledgeInfo() - { - var entity = _players.LocalEntity; - if (entity is null) - return; - - RaiseNetworkEvent(new CP14RequestKnowledgeInfoEvent(GetNetEntity(entity.Value))); - } - - private void OnCharacterKnowledgeEvent(CP14KnowledgeInfoEvent msg, EntitySessionEventArgs args) - { - var entity = GetEntity(msg.NetEntity); - var data = new KnowledgeData(entity, msg.AllKnowledge); - - OnKnowledgeUpdate?.Invoke(data); - } - - public readonly record struct KnowledgeData( - EntityUid Entity, - HashSet> AllKnowledge - ); -} diff --git a/Content.Client/_CP14/Skill/CP14ClientSkillSystem.cs b/Content.Client/_CP14/Skill/CP14ClientSkillSystem.cs new file mode 100644 index 0000000000..f21dccbccf --- /dev/null +++ b/Content.Client/_CP14/Skill/CP14ClientSkillSystem.cs @@ -0,0 +1,54 @@ +using Content.Shared._CP14.Skill; +using Content.Shared._CP14.Skill.Components; +using Content.Shared._CP14.Skill.Prototypes; +using Robust.Client.Player; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Prototypes; + +namespace Content.Client._CP14.Skill; + +public sealed partial class CP14ClientSkillSystem : CP14SharedSkillSystem +{ + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + + public event Action? OnSkillUpdate; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterAutoHandleState); + } + + private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + OnSkillUpdate?.Invoke(ent.Owner); + } + + public void RequestSkillData() + { + var localPlayer = _playerManager.LocalEntity; + + if (!HasComp(localPlayer)) + return; + + OnSkillUpdate?.Invoke(localPlayer.Value); + } + + public void RequestLearnSkill(EntityUid? target, CP14SkillPrototype? skill) + { + if (skill == null || target == null) + return; + + var netEv = new CP14TryLearnSkillMessage(GetNetEntity(target.Value), skill.ID); + RaiseNetworkEvent(netEv); + + if (_proto.TryIndex(skill.Tree, out var indexedTree)) + { + _audio.PlayGlobal(indexedTree.LearnSound, target.Value, AudioParams.Default.WithVolume(6f)); + } + } +} diff --git a/Content.Client/_CP14/Skill/Ui/CP14SkillTreeButtonControl.xaml b/Content.Client/_CP14/Skill/Ui/CP14SkillTreeButtonControl.xaml new file mode 100644 index 0000000000..5cb6f98560 --- /dev/null +++ b/Content.Client/_CP14/Skill/Ui/CP14SkillTreeButtonControl.xaml @@ -0,0 +1,24 @@ + + + + + + diff --git a/Content.Client/_CP14/Skill/Ui/CP14SkillTreeButtonControl.xaml.cs b/Content.Client/_CP14/Skill/Ui/CP14SkillTreeButtonControl.xaml.cs new file mode 100644 index 0000000000..28b19fd3c3 --- /dev/null +++ b/Content.Client/_CP14/Skill/Ui/CP14SkillTreeButtonControl.xaml.cs @@ -0,0 +1,22 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._CP14.Skill.Ui; + +[GenerateTypedNameReferences] +public sealed partial class CP14SkillTreeButtonControl : Control +{ + public event Action? OnPressed; + + public CP14SkillTreeButtonControl(Color color, string label) + { + RobustXamlLoader.Load(this); + + ColorPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = color }; + SkillTreeLabel.Text = label; + + MainButton.OnPressed += args => OnPressed?.Invoke(); + } +} diff --git a/Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml b/Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml new file mode 100644 index 0000000000..4ffd0bde07 --- /dev/null +++ b/Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml @@ -0,0 +1,7 @@ + diff --git a/Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml.cs b/Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml.cs new file mode 100644 index 0000000000..a5a55c27f9 --- /dev/null +++ b/Content.Client/_CP14/Skill/Ui/CP14SkillTreeGraphControl.xaml.cs @@ -0,0 +1,218 @@ +using System.Linq; +using System.Numerics; +using Content.Shared._CP14.Skill; +using Content.Shared._CP14.Skill.Components; +using Content.Shared._CP14.Skill.Prototypes; +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 prerequisite in skill.Prerequisites) + { + if (!_proto.TryIndex(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); + } + } + + //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 learned = playerSkills.Contains(skill.ID); + var available = _skillSystem.CanLearnSkill(_player.Value, skill.ID); + var canBeLearned = learned || skill.Prerequisites.All(prerequisite => playerSkills.Contains(prerequisite)); + 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); + } + + // 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 (available) + { + 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 = Color.White; + if (!learned) + iconColor = Color.FromSrgb(new Color(0.7f, 0.7f, 0.7f)); + if (!canBeLearned && !learned) + iconColor = Color.FromSrgb(new Color(0f, 0f, 0f)); + + handle.DrawTextureRect(baseTexture, baseQuad, iconColor); + } + } +} diff --git a/Content.Client/_CP14/UserInterface/Systems/Knowledge/CP14KnowledgeUIController.cs b/Content.Client/_CP14/UserInterface/Systems/Knowledge/CP14KnowledgeUIController.cs deleted file mode 100644 index 188a10b666..0000000000 --- a/Content.Client/_CP14/UserInterface/Systems/Knowledge/CP14KnowledgeUIController.cs +++ /dev/null @@ -1,158 +0,0 @@ -using Content.Client._CP14.Knowledge; -using Content.Client._CP14.UserInterface.Systems.Knowledge.Windows; -using Content.Client.Gameplay; -using Content.Client.UserInterface.Controls; -using Content.Shared.Input; -using JetBrains.Annotations; -using Robust.Client.Player; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controllers; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.Input.Binding; -using Robust.Shared.Prototypes; -using Robust.Shared.Utility; - -namespace Content.Client.UserInterface.Systems.Character; - -[UsedImplicitly] -public sealed class CP14KnowledgeUIController : UIController, IOnStateEntered, IOnStateExited, - IOnSystemChanged -{ - [Dependency] private readonly IPlayerManager _player = default!; - [Dependency] private readonly IPrototypeManager _proto = default!; - [UISystemDependency] private readonly ClientCP14KnowledgeSystem _knowledge = default!; - - private CP14KnowledgeWindow? _window; - - private MenuButton? KnowledgeButton => UIManager.GetActiveUIWidgetOrNull()?.CP14KnowledgeButton; - - public void OnStateEntered(GameplayState state) - { - DebugTools.Assert(_window == null); - - _window = UIManager.CreateWindow(); - LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); - - CommandBinds.Builder - .Bind(ContentKeyFunctions.CP14OpenKnowledgeMenu, - InputCmdHandler.FromDelegate(_ => ToggleWindow())) - .Register(); - } - - public void OnStateExited(GameplayState state) - { - if (_window != null) - { - _window.Dispose(); - _window = null; - } - - CommandBinds.Unregister(); - } - - public void OnSystemLoaded(ClientCP14KnowledgeSystem system) - { - system.OnKnowledgeUpdate += KnowledgeUpdated; - _player.LocalPlayerDetached += CharacterDetached; - } - - public void OnSystemUnloaded(ClientCP14KnowledgeSystem system) - { - system.OnKnowledgeUpdate -= KnowledgeUpdated; - _player.LocalPlayerDetached -= CharacterDetached; - } - - public void UnloadButton() - { - if (KnowledgeButton is null) - return; - - KnowledgeButton.OnPressed -= KnowledgeButtonPressed; - } - - public void LoadButton() - { - if (KnowledgeButton is null) - return; - - KnowledgeButton.OnPressed += KnowledgeButtonPressed; - - if (_window is null) - return; - - _window.OnClose += DeactivateButton; - _window.OnOpen += ActivateButton; - } - - private void DeactivateButton() - { - KnowledgeButton!.Pressed = false; - } - - private void ActivateButton() - { - KnowledgeButton!.Pressed = true; - } - - private void KnowledgeUpdated(ClientCP14KnowledgeSystem.KnowledgeData data) - { - if (_window is null) - return; - - _window.KnowledgeContent.RemoveAllChildren(); - - var (entity, allKnowledge) = data; - - foreach (var knowledge in allKnowledge) - { - if (!_proto.TryIndex(knowledge, out var indexedKnowledge)) - continue; - - var knowledgeButton = new Button() - { - Access = AccessLevel.Public, - Text = Loc.GetString(indexedKnowledge.Name), - ToolTip = Loc.GetString(indexedKnowledge.Desc), - TextAlign = Label.AlignMode.Center, - }; - - _window.KnowledgeContent.AddChild(knowledgeButton); - } - } - - private void CharacterDetached(EntityUid uid) - { - CloseWindow(); - } - - private void KnowledgeButtonPressed(BaseButton.ButtonEventArgs args) - { - ToggleWindow(); - } - - private void CloseWindow() - { - _window?.Close(); - } - - private void ToggleWindow() - { - if (_window == null) - return; - - if (KnowledgeButton != null) - { - KnowledgeButton.SetClickPressed(!_window.IsOpen); - } - - if (_window.IsOpen) - { - CloseWindow(); - } - else - { - _knowledge.RequestKnowledgeInfo(); - _window.Open(); - } - } -} diff --git a/Content.Client/_CP14/UserInterface/Systems/Knowledge/Windows/CP14KnowledgeWindow.xaml b/Content.Client/_CP14/UserInterface/Systems/Knowledge/Windows/CP14KnowledgeWindow.xaml deleted file mode 100644 index 419d0f56ce..0000000000 --- a/Content.Client/_CP14/UserInterface/Systems/Knowledge/Windows/CP14KnowledgeWindow.xaml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/Content.Client/_CP14/UserInterface/Systems/Skill/CP14SkillUIController.cs b/Content.Client/_CP14/UserInterface/Systems/Skill/CP14SkillUIController.cs new file mode 100644 index 0000000000..23f63b182a --- /dev/null +++ b/Content.Client/_CP14/UserInterface/Systems/Skill/CP14SkillUIController.cs @@ -0,0 +1,249 @@ +using System.Linq; +using System.Numerics; +using Content.Client._CP14.Skill; +using Content.Client._CP14.Skill.Ui; +using Content.Client._CP14.UserInterface.Systems.Skill.Window; +using Content.Client.Gameplay; +using Content.Client.UserInterface.Controls; +using Content.Shared._CP14.Skill.Components; +using Content.Shared._CP14.Skill.Prototypes; +using Content.Shared.Input; +using JetBrains.Annotations; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controllers; +using Robust.Client.UserInterface.Controls; +using Robust.Client.Utility; +using Robust.Shared.Input.Binding; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client.UserInterface.Systems.Character; + +[UsedImplicitly] +public sealed class CP14SkillUIController : UIController, IOnStateEntered, IOnStateExited, + IOnSystemChanged +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [UISystemDependency] private readonly CP14ClientSkillSystem _skill = default!; + + private CP14SkillWindow? _window; + private CP14SkillPrototype? _selectedSkill; + + private MenuButton? SkillButton => UIManager.GetActiveUIWidgetOrNull()?.CP14SkillButton; + + public void OnStateEntered(GameplayState state) + { + DebugTools.Assert(_window == null); + + _window = UIManager.CreateWindow(); + LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.CP14OpenSkillMenu, + InputCmdHandler.FromDelegate(_ => ToggleWindow())) + .Register(); + + _window.LearnButton.OnPressed += _ => _skill.RequestLearnSkill(_player.LocalEntity, _selectedSkill); + _window.GraphControl.OnNodeSelected += SelectNode; + _window.GraphControl.OnOffsetChanged += offset => + { + _window.ParallaxBackground.Offset = -offset * 0.25f + new Vector2(1000,1000); //hardcoding is bad + }; + } + + + public void OnStateExited(GameplayState state) + { + if (_window != null) + { + _window.GraphControl.OnNodeSelected -= SelectNode; + + _window.Dispose(); + _window = null; + } + + CommandBinds.Unregister(); + } + + public void OnSystemLoaded(CP14ClientSkillSystem system) + { + system.OnSkillUpdate += UpdateState; + _player.LocalPlayerDetached += CharacterDetached; + } + + public void OnSystemUnloaded(CP14ClientSkillSystem system) + { + system.OnSkillUpdate -= UpdateState; + _player.LocalPlayerDetached -= CharacterDetached; + } + + public void UnloadButton() + { + if (SkillButton is null) + return; + + SkillButton.OnPressed -= SkillButtonPressed; + } + + public void LoadButton() + { + if (SkillButton is null) + return; + + SkillButton.OnPressed += SkillButtonPressed; + + if (_window is null) + return; + + _window.OnClose += DeactivateButton; + _window.OnOpen += ActivateButton; + } + + private void DeactivateButton() + { + SkillButton!.Pressed = false; + } + + private void ActivateButton() + { + SkillButton!.Pressed = true; + } + + private void SelectNode(CP14SkillPrototype? skill) + { + if (_window is null) + return; + + if (_player.LocalEntity == null) + return; + + _selectedSkill = skill; + + if (skill == null) + { + _window.SkillName.Text = string.Empty; + _window.SkillDescription.Text = string.Empty; + _window.SkillView.Texture = null; + _window.LearnButton.Disabled = true; + } + else + { + _window.SkillName.Text = _skill.GetSkillName(skill); + _window.SkillDescription.SetMessage(GetSkillDescription(skill)); + _window.SkillView.Texture = skill.Icon.Frame0(); + _window.LearnButton.Disabled = !_skill.CanLearnSkill(_player.LocalEntity.Value, skill); + _window.SkillCost.Text = skill.LearnCost.ToString(); + } + } + + private FormattedMessage GetSkillDescription(CP14SkillPrototype skill) + { + var msg = new FormattedMessage(); + + //Description + msg.TryAddMarkup(_skill.GetSkillDescription(skill) + "\n", out _); + + return msg; + } + + private void UpdateState(EntityUid player) + { + if (_window is null) + return; + + if (!EntityManager.TryGetComponent(player, out var storage)) + return; + + _window.GraphControl.UpdateState((player, storage)); + + // Reselect for update state + SelectNode(_selectedSkill); + + //If tree not selected, select the first one + if (_window.GraphControl.Tree == null && storage.Progress.Count > 0) + { + var firstTree = storage.Progress.First().Key; + + if (_proto.TryIndex(firstTree, out var indexedTree)) + { + SelectTree(indexedTree, storage); // Set the first tree from the player's progress + } + } + + // Update the experience points for the selected tree + var playerProgress = storage.Progress; + if (_window.GraphControl.Tree is not null && playerProgress.TryGetValue(_window.GraphControl.Tree, out var skillpoint)) + { + _window.ExpPointLabel.Text = skillpoint.ToString(); + } + + _window.LevelLabel.Text = $"{storage.SkillsSumExperience}/{storage.ExperienceMaxCap}"; + + _window.TreeTabsContainer.RemoveAllChildren(); + foreach (var (tree, progress) in storage.Progress) + { + if (!_proto.TryIndex(tree, out var indexedTree)) + continue; + + var treeButton2 = new CP14SkillTreeButtonControl(indexedTree.Color, Loc.GetString(indexedTree.Name)); + treeButton2.ToolTip = Loc.GetString(indexedTree.Desc ?? string.Empty); + treeButton2.OnPressed += () => + { + SelectTree(indexedTree, storage); + }; + + _window.TreeTabsContainer.AddChild(treeButton2); + } + } + + private void SelectTree(CP14SkillTreePrototype tree, CP14SkillStorageComponent storage) + { + if (_window == null) + return; + + _window.GraphControl.Tree = tree; + _window.ParallaxBackground.ParallaxPrototype = tree.Parallax; + _window.TreeName.Text = Loc.GetString(tree.Name); + + var playerProgress = storage.Progress; + _window.ExpPointLabel.Text = playerProgress.TryGetValue(tree, out var skillpoint) ? skillpoint.ToString() : "0"; + } + + private void CharacterDetached(EntityUid uid) + { + CloseWindow(); + } + + private void SkillButtonPressed(BaseButton.ButtonEventArgs args) + { + ToggleWindow(); + } + + private void CloseWindow() + { + _window?.Close(); + } + + private void ToggleWindow() + { + if (_window == null) + return; + + if (SkillButton != null) + { + SkillButton.SetClickPressed(!_window.IsOpen); + } + + if (_window.IsOpen) + { + CloseWindow(); + } + else + { + _skill.RequestSkillData(); + _window.Open(); + } + } +} diff --git a/Content.Client/_CP14/UserInterface/Systems/Skill/Window/CP14SkillWindow.xaml b/Content.Client/_CP14/UserInterface/Systems/Skill/Window/CP14SkillWindow.xaml new file mode 100644 index 0000000000..275f810555 --- /dev/null +++ b/Content.Client/_CP14/UserInterface/Systems/Skill/Window/CP14SkillWindow.xaml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +