diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 188d664b395..3556a428fe3 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -97,9 +97,9 @@ public static void SetupContexts(IInputContextContainer contexts) common.AddFunction(ContentKeyFunctions.OpenActionsMenu); foreach (var boundKey in ContentKeyFunctions.GetHotbarBoundKeys()) - { common.AddFunction(boundKey); - } + foreach (var boundKey in ContentKeyFunctions.GetLoadoutBoundKeys()) + common.AddFunction(boundKey); var aghost = contexts.New("aghost", "common"); aghost.AddFunction(EngineKeyFunctions.MoveUp); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 21d9ae45607..a525143e4e1 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -271,9 +271,9 @@ void AddCheckBox(string checkBoxName, bool currentState, Action _actions = new(); + private readonly List _pages = new(); + private int _currentPageIndex = DefaultPageIndex; private readonly DragDropHelper _menuDragHelper; private readonly TextureRect _dragShadow; private ActionsWindow? _window; private ActionsBar? ActionsBar => UIManager.GetActiveUIWidgetOrNull(); private MenuButton? ActionButton => UIManager.GetActiveUIWidgetOrNull()?.ActionButton; + private ActionPage CurrentPage => _pages[_currentPageIndex]; public bool IsDragging => _menuDragHelper.IsDragging; @@ -75,8 +79,13 @@ public ActionUIController() Stretch = StretchMode.Scale, Visible = false, SetSize = new Vector2(64, 64), - MouseFilter = MouseFilterMode.Ignore + MouseFilter = MouseFilterMode.Ignore, }; + + var pageCount = ContentKeyFunctions.GetLoadoutBoundKeys().Length; + var buttonCount = ContentKeyFunctions.GetHotbarBoundKeys().Length; + for (var i = 0; i < pageCount; i++) + _pages.Add(new ActionPage(buttonCount)); } public override void Initialize() @@ -129,6 +138,15 @@ public void OnStateEntered(GameplayState state) }, false, true)); } + var loadoutKeys = ContentKeyFunctions.GetLoadoutBoundKeys(); + for (var i = 0; i < loadoutKeys.Length; i++) + { + var boundId = i; // This is needed, because the lambda captures it. + var boundKey = loadoutKeys[i]; + builder = builder.Bind(boundKey, + InputCmdHandler.FromDelegate(_ => ChangePage(boundId))); + } + builder .Bind(ContentKeyFunctions.OpenActionsMenu, InputCmdHandler.FromDelegate(_ => ToggleWindow())) @@ -315,34 +333,80 @@ public void OnStateExited(GameplayState state) private void TriggerAction(int index) { if (_actionsSystem == null || - !_actions.TryGetValue(index, out var actionId) || + CurrentPage[index] is not { } actionId || !_actionsSystem.TryGetActionData(actionId, out var baseAction)) - { return; - } if (baseAction is BaseTargetActionComponent action) - ToggleTargeting(actionId.Value, action); + ToggleTargeting(actionId, action); else - _actionsSystem?.TriggerAction(actionId.Value, baseAction); + _actionsSystem?.TriggerAction(actionId, baseAction); + } + + private void ChangePage(int index) + { + if (_actionsSystem == null) + return; + + var lastPage = _pages.Count - 1; + if (index < 0) + index = lastPage; + else if (index > lastPage) + index = 0; + + _currentPageIndex = index; + var page = _pages[_currentPageIndex]; + _container?.SetActionData(_actionsSystem, page); + + ActionsBar!.PageButtons.Label.Text = $"{_currentPageIndex + 1}"; + } + + private void OnLeftArrowPressed(ButtonEventArgs args) => ChangePage(_currentPageIndex - 1); + + private void OnRightArrowPressed(ButtonEventArgs args) => ChangePage(_currentPageIndex + 1); + + private void AppendAction(EntityUid action) + { + if (_container == null) + return; + + foreach (var button in _container.GetButtons()) + { + if (button.ActionId != null) + continue; + + SetAction(button, action); + return; + } + + foreach (var page in _pages) + for (var i = 0; i < page.Size; i++) + { + var pageAction = page[i]; + if (pageAction != null) + continue; + + page[i] = action; + return; + } } private void OnActionAdded(EntityUid actionId) { if (_actionsSystem == null || !_actionsSystem.TryGetActionData(actionId, out var action)) - { return; - } // if the action is toggled when we add it, start targeting if (action is BaseTargetActionComponent targetAction && action.Toggled) StartTargeting(actionId, targetAction); - if (_actions.Contains(actionId)) - return; + foreach (var page in _pages) + for (var i = 0; i < page.Size; i++) + if (page[i] == actionId) + return; - _actions.Add(actionId); + AppendAction(actionId); } private void OnActionRemoved(EntityUid actionId) @@ -353,19 +417,24 @@ private void OnActionRemoved(EntityUid actionId) if (actionId == SelectingTargetFor) StopTargeting(); - _actions.RemoveAll(x => x == actionId); + foreach (var page in _pages) + for (var i = 0; i < page.Size; i++) + if (page[i] == actionId) + page[i] = null; } private void OnActionsUpdated() { QueueWindowUpdate(); + if (_container == null) + return; // TODO ACTIONS allow buttons to persist across state applications // Then we don't have to interrupt drags any time the buttons get rebuilt. _menuDragHelper.EndDrag(); if (_actionsSystem != null) - _container?.SetActionData(_actionsSystem, _actions.ToArray()); + _container?.SetActionData(_actionsSystem, _pages[_currentPageIndex]); } private void ActionButtonPressed(ButtonEventArgs args) @@ -512,7 +581,7 @@ private void SearchAndDisplay() PopulateActions(actions); } - private void SetAction(ActionButton button, EntityUid? actionId, bool updateSlots = true) + private void SetAction(ActionButton button, EntityUid? actionId) { if (_actionsSystem == null) return; @@ -523,27 +592,28 @@ private void SetAction(ActionButton button, EntityUid? actionId, bool updateSlot { button.ClearData(); if (_container?.TryGetButtonIndex(button, out position) ?? false) - { - if (_actions.Count > position && position >= 0) - _actions.RemoveAt(position); - } + CurrentPage[position] = null; } else if (button.TryReplaceWith(actionId.Value, _actionsSystem) && _container != null && _container.TryGetButtonIndex(button, out position)) - { - if (position >= _actions.Count) - { - _actions.Add(actionId); - } + if (position >= 0 && position < CurrentPage.Size) + CurrentPage[position] = actionId; else { - _actions[position] = actionId; + if (_pages.Count <= _currentPageIndex) + return; + // Add the button to the next page if there's no space on the current one + var nextPage = _pages[_currentPageIndex + 1]; + int i; + for (i = 0; i < nextPage.Size; i++) + if (nextPage[i] == null) + { + nextPage[i] = actionId; + break; + } + ChangePage(_currentPageIndex + 1); //TODO: Make this a client config? } - } - - if (updateSlots) - _container?.SetActionData(_actionsSystem, _actions.ToArray()); } private void DragAction() @@ -559,14 +629,14 @@ private void DragAction() if (currentlyHovered is ActionButton button) { swapAction = button.ActionId; - SetAction(button, action, false); + SetAction(button, action); } if (dragged.Parent is ActionButtonContainer) - SetAction(dragged, swapAction, false); + SetAction(dragged, swapAction); if (_actionsSystem != null) - _container?.SetActionData(_actionsSystem, _actions.ToArray()); + _container?.SetActionData(_actionsSystem, _pages[_currentPageIndex]); _menuDragHelper.EndDrag(); } @@ -717,9 +787,10 @@ private void UnloadGui() _actionsSystem?.UnlinkAllActions(); if (ActionsBar == null) - { return; - } + + ActionsBar.PageButtons.LeftArrow.OnPressed -= OnLeftArrowPressed; + ActionsBar.PageButtons.RightArrow.OnPressed -= OnRightArrowPressed; if (_window != null) { @@ -747,9 +818,10 @@ private void LoadGui() _window.FilterButton.OnItemSelected += OnFilterSelected; if (ActionsBar == null) - { return; - } + + ActionsBar.PageButtons.LeftArrow.OnPressed += OnLeftArrowPressed; + ActionsBar.PageButtons.RightArrow.OnPressed += OnRightArrowPressed; RegisterActionContainer(ActionsBar.ActionsContainer); @@ -779,13 +851,10 @@ private void AssignSlots(List assignments) if (_actionsSystem == null) return; - _actions.Clear(); - foreach (var assign in assignments) - { - _actions.Add(assign.ActionId); - } + foreach (ref var assignment in CollectionsMarshal.AsSpan(assignments)) + _pages[assignment.Hotbar][assignment.Slot] = assignment.ActionId; - _container?.SetActionData(_actionsSystem, _actions.ToArray()); + _container?.SetActionData(_actionsSystem, _pages[_currentPageIndex]); } public void RemoveActionContainer() @@ -822,7 +891,7 @@ private void OnComponentLinked(ActionsComponent component) return; LoadDefaultActions(); - _container?.SetActionData(_actionsSystem, _actions.ToArray()); + _container?.SetActionData(_actionsSystem, _pages[_currentPageIndex]); QueueWindowUpdate(); } @@ -841,11 +910,27 @@ private void LoadDefaultActions() var actions = _actionsSystem.GetClientActions().Where(action => action.Comp.AutoPopulate).ToList(); actions.Sort(ActionComparer); - _actions.Clear(); - foreach (var (action, _) in actions) + var offset = 0; + var totalPages = _pages.Count; + var pagesLeft = totalPages; + var currentPage = DefaultPageIndex; + while (pagesLeft > 0) { - if (!_actions.Contains(action)) - _actions.Add(action); + var page = _pages[currentPage]; + var pageSize = page.Size; + + for (var slot = 0; slot < pageSize; slot++) + if (slot + offset < actions.Count) + page[slot] = actions[slot + offset].Id; + else + page[slot] = null; + + offset += pageSize; + currentPage++; + if (currentPage == totalPages) + currentPage = 0; + + pagesLeft--; } } @@ -956,4 +1041,22 @@ private void StopTargeting() handOverlay.IconOverride = null; handOverlay.EntityOverride = null; } + + //TODO: Serialize this shit + private sealed class ActionPage(int size) + { + public readonly EntityUid?[] Data = new EntityUid?[size]; + + public EntityUid? this[int index] + { + get => Data[index]; + set => Data[index] = value; + } + + public static implicit operator EntityUid?[](ActionPage p) => p.Data.ToArray(); + + public void Clear() => Array.Fill(Data, null); + + public int Size => Data.Length; + } } diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs index c5f8adbdea3..c028e2a76db 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs @@ -18,25 +18,18 @@ public class ActionButtonContainer : GridContainer public event Action? ActionUnpressed; public event Action? ActionFocusExited; - public ActionButtonContainer() - { - IoCManager.InjectDependencies(this); - } + public ActionButtonContainer() => IoCManager.InjectDependencies(this); - public ActionButton this[int index] - { - get => (ActionButton) GetChild(index); - } + public ActionButton this[int index] => (ActionButton) GetChild(index); private void BuildActionButtons(int count) { var keys = ContentKeyFunctions.GetHotbarBoundKeys(); Children.Clear(); - for (var index = 0; index < count; index++) - { - Children.Add(MakeButton(index)); - } + for (var i = 0; i < count; i++) + AddChild(MakeButton(i)); + return; ActionButton MakeButton(int index) { @@ -47,9 +40,7 @@ ActionButton MakeButton(int index) button.KeyBind = boundKey; if (_input.TryGetKeyBinding(boundKey, out var binding)) - { button.Label.Text = binding.GetKeyString(); - } return button; } @@ -57,7 +48,7 @@ ActionButton MakeButton(int index) public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes) { - var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1); + var uniqueCount = Math.Max(ContentKeyFunctions.GetHotbarBoundKeys().Length, actionTypes.Length + 1); if (ChildCount != uniqueCount) BuildActionButtons(uniqueCount); @@ -72,9 +63,7 @@ public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes) public void ClearActionData() { foreach (var button in Children) - { ((ActionButton) button).ClearData(); - } } protected override void ChildAdded(Control newChild) @@ -114,14 +103,9 @@ public bool TryGetButtonIndex(ActionButton button, out int position) public IEnumerable GetButtons() { foreach (var control in Children) - { if (control is ActionButton button) yield return button; - } } - ~ActionButtonContainer() - { - UserInterfaceManager.GetUIController().RemoveActionContainer(); - } + ~ActionButtonContainer() => UserInterfaceManager.GetUIController().RemoveActionContainer(); } diff --git a/Content.Client/UserInterface/Systems/Actions/Widgets/ActionsBar.xaml b/Content.Client/UserInterface/Systems/Actions/Widgets/ActionsBar.xaml index 1d317f6155d..961f720925a 100644 --- a/Content.Client/UserInterface/Systems/Actions/Widgets/ActionsBar.xaml +++ b/Content.Client/UserInterface/Systems/Actions/Widgets/ActionsBar.xaml @@ -14,6 +14,7 @@ MaxSize="64 9999" Name="ActionsContainer" Access="Public" - Rows="1"/> + Rows="1" /> + diff --git a/Content.Client/UserInterface/Systems/Actions/Widgets/ActionsBar.xaml.cs b/Content.Client/UserInterface/Systems/Actions/Widgets/ActionsBar.xaml.cs index 8e95992ff64..b11357cf8eb 100644 --- a/Content.Client/UserInterface/Systems/Actions/Widgets/ActionsBar.xaml.cs +++ b/Content.Client/UserInterface/Systems/Actions/Widgets/ActionsBar.xaml.cs @@ -1,4 +1,6 @@ -using Robust.Client.AutoGenerated; +using Content.Client.UserInterface.Systems.Actions.Controls; +using Content.Shared.Input; +using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; @@ -7,9 +9,26 @@ namespace Content.Client.UserInterface.Systems.Actions.Widgets; [GenerateTypedNameReferences] public sealed partial class ActionsBar : UIWidget { + [Dependency] private readonly IEntityManager _entity = default!; + + public ActionsBar() { RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + var keys = ContentKeyFunctions.GetHotbarBoundKeys(); + for (var index = 1; index < keys.Length; index++) + ActionsContainer.Children.Add(MakeButton(index)); + ActionsContainer.Children.Add(MakeButton(0)); + + ActionButton MakeButton(int index) + { + var boundKey = keys[index]; + var button = new ActionButton(_entity); + button.KeyBind = boundKey; + button.Label.Text = index.ToString(); + return button; + } } } - diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 2727a400a40..7968de01efd 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -98,12 +98,21 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction Hotbar7 = "Hotbar7"; public static readonly BoundKeyFunction Hotbar8 = "Hotbar8"; public static readonly BoundKeyFunction Hotbar9 = "Hotbar9"; - public static BoundKeyFunction[] GetHotbarBoundKeys() => - new[] - { - Hotbar1, Hotbar2, Hotbar3, Hotbar4, Hotbar5, Hotbar6, Hotbar7, Hotbar8, Hotbar9, Hotbar0 - }; + new[] { Hotbar1, Hotbar2, Hotbar3, Hotbar4, Hotbar5, Hotbar6, Hotbar7, Hotbar8, Hotbar9, Hotbar0, }; + + public static readonly BoundKeyFunction Loadout0 = "Loadout0"; + public static readonly BoundKeyFunction Loadout1 = "Loadout1"; + public static readonly BoundKeyFunction Loadout2 = "Loadout2"; + public static readonly BoundKeyFunction Loadout3 = "Loadout3"; + public static readonly BoundKeyFunction Loadout4 = "Loadout4"; + public static readonly BoundKeyFunction Loadout5 = "Loadout5"; + public static readonly BoundKeyFunction Loadout6 = "Loadout6"; + public static readonly BoundKeyFunction Loadout7 = "Loadout7"; + public static readonly BoundKeyFunction Loadout8 = "Loadout8"; + public static readonly BoundKeyFunction Loadout9 = "Loadout9"; + public static BoundKeyFunction[] GetLoadoutBoundKeys() => + new[] { Loadout1, Loadout2, Loadout3, Loadout4, Loadout5, Loadout6, Loadout7, Loadout8, Loadout9, Loadout0, }; public static readonly BoundKeyFunction Vote0 = "Vote0"; public static readonly BoundKeyFunction Vote1 = "Vote1"; diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 68b84c7fe2e..340280ff798 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -550,6 +550,46 @@ binds: - function: Hotbar9 type: State key: Num9 +- function: Loadout0 + type: State + key: Num0 + mod1: Shift +- function: Loadout1 + type: State + key: Num1 + mod1: Shift +- function: Loadout2 + type: State + key: Num2 + mod1: Shift +- function: Loadout3 + type: State + key: Num3 + mod1: Shift +- function: Loadout4 + type: State + key: Num4 + mod1: Shift +- function: Loadout5 + type: State + key: Num5 + mod1: Shift +- function: Loadout6 + type: State + key: Num6 + mod1: Shift +- function: Loadout7 + type: State + key: Num7 + mod1: Shift +- function: Loadout8 + type: State + key: Num8 + mod1: Shift +- function: Loadout9 + type: State + key: Num9 + mod1: Shift - function: LookUp type: State key: Space